Siddharth V commited on
Commit
4fd5040
Β·
unverified Β·
1 Parent(s): 7004c89

feat: enhance chat import with multi-format support (#936)

Browse files

* feat: enhance chat import with multi-format support

- Add support for importing chats from different formats:
- Standard Bolt format
- Chrome extension format
- History array format
- Bolt export format
- Add Import Chats button to Data Management
- Add proper error handling and logging
- Update README with backup/restore feature

* refactor: simplify chat import formats

- Remove multi-format support from DataTab
- Keep only standard Bolt export formats
- Simplify ImportButtons to handle standard format only

README.md CHANGED
@@ -49,6 +49,7 @@ bolt.diy was originally started by [Cole Medin](https://www.youtube.com/@ColeMed
49
  - βœ… Bolt terminal to see the output of LLM run commands (@thecodacus)
50
  - βœ… Streaming of code output (@thecodacus)
51
  - βœ… Ability to revert code to earlier version (@wonderwhy-er)
 
52
  - βœ… Cohere Integration (@hasanraiyan)
53
  - βœ… Dynamic model max token length (@hasanraiyan)
54
  - βœ… Better prompt enhancing (@SujalXplores)
 
49
  - βœ… Bolt terminal to see the output of LLM run commands (@thecodacus)
50
  - βœ… Streaming of code output (@thecodacus)
51
  - βœ… Ability to revert code to earlier version (@wonderwhy-er)
52
+ - βœ… Chat history backup and restore functionality (@sidbetatester)
53
  - βœ… Cohere Integration (@hasanraiyan)
54
  - βœ… Dynamic model max token length (@hasanraiyan)
55
  - βœ… Better prompt enhancing (@SujalXplores)
app/components/chat/chatExportAndImport/ImportButtons.tsx CHANGED
@@ -2,6 +2,11 @@ import type { Message } from 'ai';
2
  import { toast } from 'react-toastify';
3
  import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
4
 
 
 
 
 
 
5
  export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise<void>) | undefined) {
6
  return (
7
  <div className="flex flex-col items-center justify-center w-auto">
@@ -20,14 +25,16 @@ export function ImportButtons(importChat: ((description: string, messages: Messa
20
  reader.onload = async (e) => {
21
  try {
22
  const content = e.target?.result as string;
23
- const data = JSON.parse(content);
24
 
25
- if (!Array.isArray(data.messages)) {
26
- toast.error('Invalid chat file format');
 
 
 
27
  }
28
 
29
- await importChat(data.description, data.messages);
30
- toast.success('Chat imported successfully');
31
  } catch (error: unknown) {
32
  if (error instanceof Error) {
33
  toast.error('Failed to parse chat file: ' + error.message);
 
2
  import { toast } from 'react-toastify';
3
  import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
4
 
5
+ type ChatData = {
6
+ messages?: Message[]; // Standard Bolt format
7
+ description?: string; // Optional description
8
+ };
9
+
10
  export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise<void>) | undefined) {
11
  return (
12
  <div className="flex flex-col items-center justify-center w-auto">
 
25
  reader.onload = async (e) => {
26
  try {
27
  const content = e.target?.result as string;
28
+ const data = JSON.parse(content) as ChatData;
29
 
30
+ // Standard format
31
+ if (Array.isArray(data.messages)) {
32
+ await importChat(data.description || 'Imported Chat', data.messages);
33
+ toast.success('Chat imported successfully');
34
+ return;
35
  }
36
 
37
+ toast.error('Invalid chat file format');
 
38
  } catch (error: unknown) {
39
  if (error instanceof Error) {
40
  toast.error('Failed to parse chat file: ' + error.message);
app/components/settings/data/DataTab.tsx CHANGED
@@ -2,9 +2,10 @@ import React, { useState } from 'react';
2
  import { useNavigate } from '@remix-run/react';
3
  import Cookies from 'js-cookie';
4
  import { toast } from 'react-toastify';
5
- import { db, deleteById, getAll } from '~/lib/persistence';
6
  import { logStore } from '~/lib/stores/logs';
7
  import { classNames } from '~/utils/classNames';
 
8
 
9
  // List of supported providers that can have API keys
10
  const API_KEY_PROVIDERS = [
@@ -232,6 +233,76 @@ export default function DataTab() {
232
  event.target.value = '';
233
  };
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  return (
236
  <div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
237
  <div className="mb-6">
@@ -248,6 +319,12 @@ export default function DataTab() {
248
  >
249
  Export All Chats
250
  </button>
 
 
 
 
 
 
251
  <button
252
  onClick={handleDeleteAllChats}
253
  disabled={isDeleting}
 
2
  import { useNavigate } from '@remix-run/react';
3
  import Cookies from 'js-cookie';
4
  import { toast } from 'react-toastify';
5
+ import { db, deleteById, getAll, setMessages } from '~/lib/persistence';
6
  import { logStore } from '~/lib/stores/logs';
7
  import { classNames } from '~/utils/classNames';
8
+ import type { Message } from 'ai';
9
 
10
  // List of supported providers that can have API keys
11
  const API_KEY_PROVIDERS = [
 
233
  event.target.value = '';
234
  };
235
 
236
+ const processChatData = (data: any): Array<{
237
+ id: string;
238
+ messages: Message[];
239
+ description: string;
240
+ urlId?: string;
241
+ }> => {
242
+ // Handle Bolt standard format (single chat)
243
+ if (data.messages && Array.isArray(data.messages)) {
244
+ const chatId = crypto.randomUUID();
245
+ return [{
246
+ id: chatId,
247
+ messages: data.messages,
248
+ description: data.description || 'Imported Chat',
249
+ urlId: chatId
250
+ }];
251
+ }
252
+
253
+ // Handle Bolt export format (multiple chats)
254
+ if (data.chats && Array.isArray(data.chats)) {
255
+ return data.chats.map((chat: { id?: string; messages: Message[]; description?: string; urlId?: string; }) => ({
256
+ id: chat.id || crypto.randomUUID(),
257
+ messages: chat.messages,
258
+ description: chat.description || 'Imported Chat',
259
+ urlId: chat.urlId,
260
+ }));
261
+ }
262
+
263
+ console.error('No matching format found for:', data);
264
+ throw new Error('Unsupported chat format');
265
+ };
266
+
267
+ const handleImportChats = () => {
268
+ const input = document.createElement('input');
269
+ input.type = 'file';
270
+ input.accept = '.json';
271
+
272
+ input.onchange = async (e) => {
273
+ const file = (e.target as HTMLInputElement).files?.[0];
274
+
275
+ if (!file || !db) {
276
+ toast.error('Something went wrong');
277
+ return;
278
+ }
279
+
280
+ try {
281
+ const content = await file.text();
282
+ const data = JSON.parse(content);
283
+ const chatsToImport = processChatData(data);
284
+
285
+ for (const chat of chatsToImport) {
286
+ await setMessages(db, chat.id, chat.messages, chat.urlId, chat.description);
287
+ }
288
+
289
+ logStore.logSystem('Chats imported successfully', { count: chatsToImport.length });
290
+ toast.success(`Successfully imported ${chatsToImport.length} chat${chatsToImport.length > 1 ? 's' : ''}`);
291
+ window.location.reload();
292
+ } catch (error) {
293
+ if (error instanceof Error) {
294
+ logStore.logError('Failed to import chats:', error);
295
+ toast.error('Failed to import chats: ' + error.message);
296
+ } else {
297
+ toast.error('Failed to import chats');
298
+ }
299
+ console.error(error);
300
+ }
301
+ };
302
+
303
+ input.click();
304
+ };
305
+
306
  return (
307
  <div className="p-4 bg-bolt-elements-bg-depth-2 border border-bolt-elements-borderColor rounded-lg mb-4">
308
  <div className="mb-6">
 
319
  >
320
  Export All Chats
321
  </button>
322
+ <button
323
+ onClick={handleImportChats}
324
+ className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors"
325
+ >
326
+ Import Chats
327
+ </button>
328
  <button
329
  onClick={handleDeleteAllChats}
330
  disabled={isDeleting}