From 4fd50403557bcf83ecffd59dacee019436e25f60 Mon Sep 17 00:00:00 2001 From: Siddharth V <94043668+sidbetatester@users.noreply.github.com> Date: Tue, 7 Jan 2025 06:46:42 -0800 Subject: [PATCH] feat: enhance chat import with multi-format support (#936) * 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 | 1 + .../chatExportAndImport/ImportButtons.tsx | 17 ++-- app/components/settings/data/DataTab.tsx | 79 ++++++++++++++++++- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f99948b8..8d0c1571 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ bolt.diy was originally started by [Cole Medin](https://www.youtube.com/@ColeMed - ✅ Bolt terminal to see the output of LLM run commands (@thecodacus) - ✅ Streaming of code output (@thecodacus) - ✅ Ability to revert code to earlier version (@wonderwhy-er) +- ✅ Chat history backup and restore functionality (@sidbetatester) - ✅ Cohere Integration (@hasanraiyan) - ✅ Dynamic model max token length (@hasanraiyan) - ✅ Better prompt enhancing (@SujalXplores) diff --git a/app/components/chat/chatExportAndImport/ImportButtons.tsx b/app/components/chat/chatExportAndImport/ImportButtons.tsx index 208fd02b..c6b25579 100644 --- a/app/components/chat/chatExportAndImport/ImportButtons.tsx +++ b/app/components/chat/chatExportAndImport/ImportButtons.tsx @@ -2,6 +2,11 @@ import type { Message } from 'ai'; import { toast } from 'react-toastify'; import { ImportFolderButton } from '~/components/chat/ImportFolderButton'; +type ChatData = { + messages?: Message[]; // Standard Bolt format + description?: string; // Optional description +}; + export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise) | undefined) { return (
@@ -20,14 +25,16 @@ export function ImportButtons(importChat: ((description: string, messages: Messa reader.onload = async (e) => { try { const content = e.target?.result as string; - const data = JSON.parse(content); + const data = JSON.parse(content) as ChatData; - if (!Array.isArray(data.messages)) { - toast.error('Invalid chat file format'); + // Standard format + if (Array.isArray(data.messages)) { + await importChat(data.description || 'Imported Chat', data.messages); + toast.success('Chat imported successfully'); + return; } - await importChat(data.description, data.messages); - toast.success('Chat imported successfully'); + toast.error('Invalid chat file format'); } catch (error: unknown) { if (error instanceof Error) { toast.error('Failed to parse chat file: ' + error.message); diff --git a/app/components/settings/data/DataTab.tsx b/app/components/settings/data/DataTab.tsx index c78491e2..531db3c0 100644 --- a/app/components/settings/data/DataTab.tsx +++ b/app/components/settings/data/DataTab.tsx @@ -2,9 +2,10 @@ import React, { useState } from 'react'; import { useNavigate } from '@remix-run/react'; import Cookies from 'js-cookie'; import { toast } from 'react-toastify'; -import { db, deleteById, getAll } from '~/lib/persistence'; +import { db, deleteById, getAll, setMessages } from '~/lib/persistence'; import { logStore } from '~/lib/stores/logs'; import { classNames } from '~/utils/classNames'; +import type { Message } from 'ai'; // List of supported providers that can have API keys const API_KEY_PROVIDERS = [ @@ -232,6 +233,76 @@ export default function DataTab() { event.target.value = ''; }; + const processChatData = (data: any): Array<{ + id: string; + messages: Message[]; + description: string; + urlId?: string; + }> => { + // Handle Bolt standard format (single chat) + if (data.messages && Array.isArray(data.messages)) { + const chatId = crypto.randomUUID(); + return [{ + id: chatId, + messages: data.messages, + description: data.description || 'Imported Chat', + urlId: chatId + }]; + } + + // Handle Bolt export format (multiple chats) + if (data.chats && Array.isArray(data.chats)) { + return data.chats.map((chat: { id?: string; messages: Message[]; description?: string; urlId?: string; }) => ({ + id: chat.id || crypto.randomUUID(), + messages: chat.messages, + description: chat.description || 'Imported Chat', + urlId: chat.urlId, + })); + } + + console.error('No matching format found for:', data); + throw new Error('Unsupported chat format'); + }; + + const handleImportChats = () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + input.onchange = async (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + + if (!file || !db) { + toast.error('Something went wrong'); + return; + } + + try { + const content = await file.text(); + const data = JSON.parse(content); + const chatsToImport = processChatData(data); + + for (const chat of chatsToImport) { + await setMessages(db, chat.id, chat.messages, chat.urlId, chat.description); + } + + logStore.logSystem('Chats imported successfully', { count: chatsToImport.length }); + toast.success(`Successfully imported ${chatsToImport.length} chat${chatsToImport.length > 1 ? 's' : ''}`); + window.location.reload(); + } catch (error) { + if (error instanceof Error) { + logStore.logError('Failed to import chats:', error); + toast.error('Failed to import chats: ' + error.message); + } else { + toast.error('Failed to import chats'); + } + console.error(error); + } + }; + + input.click(); + }; + return (
@@ -248,6 +319,12 @@ export default function DataTab() { > Export All Chats +