Eduards commited on
Commit
050bf20
·
1 Parent(s): 3d2ab89

Added parsing if ignore file and added handling of binary files

Browse files
app/components/chat/ImportFolderButton.tsx CHANGED
@@ -1,22 +1,55 @@
1
  import React from 'react';
2
  import type { Message } from 'ai';
3
  import { toast } from 'react-toastify';
 
4
 
5
  interface ImportFolderButtonProps {
6
  className?: string;
7
  importChat?: (description: string, messages: Message[]) => Promise<void>;
8
  }
9
 
10
- const IGNORED_FOLDERS = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.cache', '.vscode', '.idea'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
 
12
  const generateId = () => Math.random().toString(36).substring(2, 15);
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ className, importChat }) => {
15
  const shouldIncludeFile = (path: string): boolean => {
16
- return !IGNORED_FOLDERS.some((folder) => path.includes(`/${folder}/`));
17
  };
18
 
19
- const createChatFromFolder = async (files: File[]) => {
20
  const fileArtifacts = await Promise.all(
21
  files.map(async (file) => {
22
  return new Promise<string>((resolve, reject) => {
@@ -37,9 +70,14 @@ ${content}
37
  }),
38
  );
39
 
 
 
 
 
 
40
  const message: Message = {
41
  role: 'assistant',
42
- content: `I'll help you set up these files.
43
 
44
  <boltArtifact id="imported-files" title="Imported Files">
45
  ${fileArtifacts.join('\n\n')}
@@ -74,8 +112,34 @@ ${fileArtifacts.join('\n\n')}
74
  const allFiles = Array.from(e.target.files || []);
75
  const filteredFiles = allFiles.filter((file) => shouldIncludeFile(file.webkitRelativePath));
76
 
 
 
 
 
 
77
  try {
78
- await createChatFromFolder(filteredFiles);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  } catch (error) {
80
  console.error('Failed to import folder:', error);
81
  toast.error('Failed to import folder');
 
1
  import React from 'react';
2
  import type { Message } from 'ai';
3
  import { toast } from 'react-toastify';
4
+ import ignore from 'ignore';
5
 
6
  interface ImportFolderButtonProps {
7
  className?: string;
8
  importChat?: (description: string, messages: Message[]) => Promise<void>;
9
  }
10
 
11
+ // Common patterns to ignore, similar to .gitignore
12
+ const IGNORE_PATTERNS = [
13
+ 'node_modules/**',
14
+ '.git/**',
15
+ 'dist/**',
16
+ 'build/**',
17
+ '.next/**',
18
+ 'coverage/**',
19
+ '.cache/**',
20
+ '.vscode/**',
21
+ '.idea/**',
22
+ '**/*.log',
23
+ '**/.DS_Store',
24
+ '**/npm-debug.log*',
25
+ '**/yarn-debug.log*',
26
+ '**/yarn-error.log*',
27
+ ];
28
 
29
+ const ig = ignore().add(IGNORE_PATTERNS);
30
  const generateId = () => Math.random().toString(36).substring(2, 15);
31
 
32
+ const isBinaryFile = async (file: File): Promise<boolean> => {
33
+ const chunkSize = 1024; // Read the first 1 KB of the file
34
+ const buffer = new Uint8Array(await file.slice(0, chunkSize).arrayBuffer());
35
+
36
+ for (let i = 0; i < buffer.length; i++) {
37
+ const byte = buffer[i];
38
+
39
+ if (byte === 0 || (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13)) {
40
+ return true; // Found a binary character
41
+ }
42
+ }
43
+
44
+ return false;
45
+ };
46
+
47
  export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ className, importChat }) => {
48
  const shouldIncludeFile = (path: string): boolean => {
49
+ return !ig.ignores(path);
50
  };
51
 
52
+ const createChatFromFolder = async (files: File[], binaryFiles: string[]) => {
53
  const fileArtifacts = await Promise.all(
54
  files.map(async (file) => {
55
  return new Promise<string>((resolve, reject) => {
 
70
  }),
71
  );
72
 
73
+ const binaryFilesMessage =
74
+ binaryFiles.length > 0
75
+ ? `\n\nSkipped ${binaryFiles.length} binary files:\n${binaryFiles.map((f) => `- ${f}`).join('\n')}`
76
+ : '';
77
+
78
  const message: Message = {
79
  role: 'assistant',
80
+ content: `I'll help you set up these files.${binaryFilesMessage}
81
 
82
  <boltArtifact id="imported-files" title="Imported Files">
83
  ${fileArtifacts.join('\n\n')}
 
112
  const allFiles = Array.from(e.target.files || []);
113
  const filteredFiles = allFiles.filter((file) => shouldIncludeFile(file.webkitRelativePath));
114
 
115
+ if (filteredFiles.length === 0) {
116
+ toast.error('No files found in the selected folder');
117
+ return;
118
+ }
119
+
120
  try {
121
+ const fileChecks = await Promise.all(
122
+ filteredFiles.map(async (file) => ({
123
+ file,
124
+ isBinary: await isBinaryFile(file),
125
+ })),
126
+ );
127
+
128
+ const textFiles = fileChecks.filter((f) => !f.isBinary).map((f) => f.file);
129
+ const binaryFilePaths = fileChecks
130
+ .filter((f) => f.isBinary)
131
+ .map((f) => f.file.webkitRelativePath.split('/').slice(1).join('/'));
132
+
133
+ if (textFiles.length === 0) {
134
+ toast.error('No text files found in the selected folder');
135
+ return;
136
+ }
137
+
138
+ if (binaryFilePaths.length > 0) {
139
+ toast.info(`Skipping ${binaryFilePaths.length} binary files`);
140
+ }
141
+
142
+ await createChatFromFolder(textFiles, binaryFilePaths);
143
  } catch (error) {
144
  console.error('Failed to import folder:', error);
145
  toast.error('Failed to import folder');
app/utils/logger.ts CHANGED
@@ -11,7 +11,7 @@ interface Logger {
11
  setLevel: (level: DebugLevel) => void;
12
  }
13
 
14
- let currentLevel: DebugLevel = (import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV) ? 'debug' : 'info';
15
 
16
  const isWorker = 'HTMLRewriter' in globalThis;
17
  const supportsColor = !isWorker;
 
11
  setLevel: (level: DebugLevel) => void;
12
  }
13
 
14
+ let currentLevel: DebugLevel = import.meta.env.VITE_LOG_LEVEL ?? import.meta.env.DEV ? 'debug' : 'info';
15
 
16
  const isWorker = 'HTMLRewriter' in globalThis;
17
  const supportsColor = !isWorker;
package.json CHANGED
@@ -70,6 +70,7 @@
70
  "diff": "^5.2.0",
71
  "file-saver": "^2.0.5",
72
  "framer-motion": "^11.2.12",
 
73
  "isbot": "^4.1.0",
74
  "istextorbinary": "^9.5.0",
75
  "jose": "^5.6.3",
 
70
  "diff": "^5.2.0",
71
  "file-saver": "^2.0.5",
72
  "framer-motion": "^11.2.12",
73
+ "ignore": "^6.0.2",
74
  "isbot": "^4.1.0",
75
  "istextorbinary": "^9.5.0",
76
  "jose": "^5.6.3",
pnpm-lock.yaml CHANGED
@@ -143,6 +143,9 @@ importers:
143
  framer-motion:
144
  specifier: ^11.2.12
145
  version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
 
 
 
146
  isbot:
147
  specifier: ^4.1.0
148
  version: 4.4.0
@@ -3399,6 +3402,10 @@ packages:
3399
  resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
3400
  engines: {node: '>= 4'}
3401
 
 
 
 
 
3402
  immediate@3.0.6:
3403
  resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
3404
 
@@ -9290,6 +9297,8 @@ snapshots:
9290
 
9291
  ignore@5.3.1: {}
9292
 
 
 
9293
  immediate@3.0.6: {}
9294
 
9295
  immutable@4.3.7: {}
 
143
  framer-motion:
144
  specifier: ^11.2.12
145
  version: 11.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
146
+ ignore:
147
+ specifier: ^6.0.2
148
+ version: 6.0.2
149
  isbot:
150
  specifier: ^4.1.0
151
  version: 4.4.0
 
3402
  resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
3403
  engines: {node: '>= 4'}
3404
 
3405
+ ignore@6.0.2:
3406
+ resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==}
3407
+ engines: {node: '>= 4'}
3408
+
3409
  immediate@3.0.6:
3410
  resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
3411
 
 
9297
 
9298
  ignore@5.3.1: {}
9299
 
9300
+ ignore@6.0.2: {}
9301
+
9302
  immediate@3.0.6: {}
9303
 
9304
  immutable@4.3.7: {}