Added parsing if ignore file and added handling of binary files
Browse files- app/components/chat/ImportFolderButton.tsx +69 -5
- app/utils/logger.ts +1 -1
- package.json +1 -0
- pnpm-lock.yaml +9 -0
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 !
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 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: {}
|