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

Proof of concept for folder import

Browse files
app/components/chat/BaseChat.tsx CHANGED
@@ -20,6 +20,7 @@ import * as Tooltip from '@radix-ui/react-tooltip';
20
  import styles from './BaseChat.module.scss';
21
  import type { ProviderInfo } from '~/utils/types';
22
  import { ExportChatButton } from '~/components/chat/ExportChatButton';
 
23
 
24
  const EXAMPLE_PROMPTS = [
25
  { text: 'Build a todo app in React using Tailwind' },
@@ -184,31 +185,21 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
184
 
185
  reader.onload = async (e) => {
186
  try {
187
- const content = e.target?.result as string;
188
- const data = JSON.parse(content);
189
-
190
- if (!Array.isArray(data.messages)) {
191
- toast.error('Invalid chat file format');
192
- }
193
-
194
- await importChat(data.description, data.messages);
195
- toast.success('Chat imported successfully');
196
- } catch (error: unknown) {
197
- if (error instanceof Error) {
198
- toast.error('Failed to parse chat file: ' + error.message);
199
- } else {
200
- toast.error('Failed to parse chat file');
201
- }
202
  }
203
  };
204
- reader.onerror = () => toast.error('Failed to read chat file');
 
 
 
205
  reader.readAsText(file);
206
  } catch (error) {
207
  toast.error(error instanceof Error ? error.message : 'Failed to import chat');
208
  }
209
  e.target.value = ''; // Reset file input
210
- } else {
211
- toast.error('Something went wrong');
212
  }
213
  }}
214
  />
@@ -224,6 +215,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
224
  <div className="i-ph:upload-simple" />
225
  Import Chat
226
  </button>
 
 
 
 
227
  </div>
228
  </div>
229
  </div>
 
20
  import styles from './BaseChat.module.scss';
21
  import type { ProviderInfo } from '~/utils/types';
22
  import { ExportChatButton } from '~/components/chat/ExportChatButton';
23
+ import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
24
 
25
  const EXAMPLE_PROMPTS = [
26
  { text: 'Build a todo app in React using Tailwind' },
 
185
 
186
  reader.onload = async (e) => {
187
  try {
188
+ const content = JSON.parse(e.target?.result as string);
189
+ await importChat(content.description || '', content.messages || []);
190
+ } catch (error) {
191
+ toast.error(`Invalid chat file format: ${error instanceof Error ? ': ' + error.message : ''}`);
 
 
 
 
 
 
 
 
 
 
 
192
  }
193
  };
194
+
195
+ reader.onerror = () => {
196
+ toast.error('Something went wrong');
197
+ };
198
  reader.readAsText(file);
199
  } catch (error) {
200
  toast.error(error instanceof Error ? error.message : 'Failed to import chat');
201
  }
202
  e.target.value = ''; // Reset file input
 
 
203
  }
204
  }}
205
  />
 
215
  <div className="i-ph:upload-simple" />
216
  Import Chat
217
  </button>
218
+ <ImportFolderButton
219
+ importChat={importChat}
220
+ className="px-4 py-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-all flex items-center gap-2"
221
+ />
222
  </div>
223
  </div>
224
  </div>
app/components/chat/ImportFolderButton.tsx ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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) => {
23
+ const reader = new FileReader();
24
+
25
+ reader.onload = () => {
26
+ const content = reader.result as string;
27
+ const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
28
+ resolve(
29
+ `<boltAction type="file" filePath="${relativePath}">
30
+ ${content}
31
+ </boltAction>`,
32
+ );
33
+ };
34
+ reader.onerror = reject;
35
+ reader.readAsText(file);
36
+ });
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')}
46
+ </boltArtifact>`,
47
+ id: generateId(),
48
+ createdAt: new Date(),
49
+ };
50
+
51
+ const userMessage: Message = {
52
+ role: 'user',
53
+ id: generateId(),
54
+ content: 'Import my files',
55
+ createdAt: new Date(),
56
+ };
57
+
58
+ const description = `Folder Import: ${files[0].webkitRelativePath.split('/')[0]}`;
59
+
60
+ if (importChat) {
61
+ await importChat(description, [userMessage, message]);
62
+ }
63
+ };
64
+
65
+ return (
66
+ <>
67
+ <input
68
+ type="file"
69
+ id="folder-import"
70
+ className="hidden"
71
+ webkitdirectory=""
72
+ directory=""
73
+ onChange={async (e) => {
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');
82
+ }
83
+
84
+ e.target.value = ''; // Reset file input
85
+ }}
86
+ {...({} as any)} // if removed webkitdirectory will throw errors as unknow attribute
87
+ />
88
+ <button
89
+ onClick={() => {
90
+ const input = document.getElementById('folder-import');
91
+ input?.click();
92
+ }}
93
+ className={className}
94
+ >
95
+ <div className="i-ph:folder-simple-upload" />
96
+ Import Folder
97
+ </button>
98
+ </>
99
+ );
100
+ };