From 3d2ab89cdc01b822a4fcfbd4ba4ec61ee66c3622 Mon Sep 17 00:00:00 2001 From: eduardruzga Date: Mon, 25 Nov 2024 10:24:03 +0200 Subject: [PATCH] Proof of concept for folder import --- app/components/chat/BaseChat.tsx | 31 +++---- app/components/chat/ImportFolderButton.tsx | 100 +++++++++++++++++++++ 2 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 app/components/chat/ImportFolderButton.tsx diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 1a33375d6..4da64935a 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -20,6 +20,7 @@ import * as Tooltip from '@radix-ui/react-tooltip'; import styles from './BaseChat.module.scss'; import type { ProviderInfo } from '~/utils/types'; import { ExportChatButton } from '~/components/chat/ExportChatButton'; +import { ImportFolderButton } from '~/components/chat/ImportFolderButton'; const EXAMPLE_PROMPTS = [ { text: 'Build a todo app in React using Tailwind' }, @@ -184,31 +185,21 @@ export const BaseChat = React.forwardRef( reader.onload = async (e) => { try { - const content = e.target?.result as string; - const data = JSON.parse(content); - - if (!Array.isArray(data.messages)) { - toast.error('Invalid chat file format'); - } - - await importChat(data.description, data.messages); - toast.success('Chat imported successfully'); - } catch (error: unknown) { - if (error instanceof Error) { - toast.error('Failed to parse chat file: ' + error.message); - } else { - toast.error('Failed to parse chat file'); - } + const content = JSON.parse(e.target?.result as string); + await importChat(content.description || '', content.messages || []); + } catch (error) { + toast.error(`Invalid chat file format: ${error instanceof Error ? ': ' + error.message : ''}`); } }; - reader.onerror = () => toast.error('Failed to read chat file'); + + reader.onerror = () => { + toast.error('Something went wrong'); + }; reader.readAsText(file); } catch (error) { toast.error(error instanceof Error ? error.message : 'Failed to import chat'); } e.target.value = ''; // Reset file input - } else { - toast.error('Something went wrong'); } }} /> @@ -224,6 +215,10 @@ export const BaseChat = React.forwardRef(
Import Chat +
diff --git a/app/components/chat/ImportFolderButton.tsx b/app/components/chat/ImportFolderButton.tsx new file mode 100644 index 000000000..1444580ed --- /dev/null +++ b/app/components/chat/ImportFolderButton.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import type { Message } from 'ai'; +import { toast } from 'react-toastify'; + +interface ImportFolderButtonProps { + className?: string; + importChat?: (description: string, messages: Message[]) => Promise; +} + +const IGNORED_FOLDERS = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.cache', '.vscode', '.idea']; + +const generateId = () => Math.random().toString(36).substring(2, 15); + +export const ImportFolderButton: React.FC = ({ className, importChat }) => { + const shouldIncludeFile = (path: string): boolean => { + return !IGNORED_FOLDERS.some((folder) => path.includes(`/${folder}/`)); + }; + + const createChatFromFolder = async (files: File[]) => { + const fileArtifacts = await Promise.all( + files.map(async (file) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = () => { + const content = reader.result as string; + const relativePath = file.webkitRelativePath.split('/').slice(1).join('/'); + resolve( + ` +${content} +`, + ); + }; + reader.onerror = reject; + reader.readAsText(file); + }); + }), + ); + + const message: Message = { + role: 'assistant', + content: `I'll help you set up these files. + + +${fileArtifacts.join('\n\n')} +`, + id: generateId(), + createdAt: new Date(), + }; + + const userMessage: Message = { + role: 'user', + id: generateId(), + content: 'Import my files', + createdAt: new Date(), + }; + + const description = `Folder Import: ${files[0].webkitRelativePath.split('/')[0]}`; + + if (importChat) { + await importChat(description, [userMessage, message]); + } + }; + + return ( + <> + { + const allFiles = Array.from(e.target.files || []); + const filteredFiles = allFiles.filter((file) => shouldIncludeFile(file.webkitRelativePath)); + + try { + await createChatFromFolder(filteredFiles); + } catch (error) { + console.error('Failed to import folder:', error); + toast.error('Failed to import folder'); + } + + e.target.value = ''; // Reset file input + }} + {...({} as any)} // if removed webkitdirectory will throw errors as unknow attribute + /> + + + ); +};