Skip to content

Commit

Permalink
Proof of concept for folder import
Browse files Browse the repository at this point in the history
  • Loading branch information
wonderwhy-er committed Nov 25, 2024
1 parent 1cb836a commit 3d2ab89
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 18 deletions.
31 changes: 13 additions & 18 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -184,31 +185,21 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(

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');
}
}}
/>
Expand All @@ -224,6 +215,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
<div className="i-ph:upload-simple" />
Import Chat
</button>
<ImportFolderButton
importChat={importChat}
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"
/>
</div>
</div>
</div>
Expand Down
100 changes: 100 additions & 0 deletions app/components/chat/ImportFolderButton.tsx
Original file line number Diff line number Diff line change
@@ -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<void>;
}

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<ImportFolderButtonProps> = ({ 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<string>((resolve, reject) => {
const reader = new FileReader();

reader.onload = () => {
const content = reader.result as string;
const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
resolve(
`<boltAction type="file" filePath="${relativePath}">
${content}
</boltAction>`,
);
};
reader.onerror = reject;
reader.readAsText(file);
});
}),
);

const message: Message = {
role: 'assistant',
content: `I'll help you set up these files.
<boltArtifact id="imported-files" title="Imported Files">
${fileArtifacts.join('\n\n')}
</boltArtifact>`,
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 (
<>
<input
type="file"
id="folder-import"
className="hidden"
webkitdirectory=""
directory=""
onChange={async (e) => {
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
/>
<button
onClick={() => {
const input = document.getElementById('folder-import');
input?.click();
}}
className={className}
>
<div className="i-ph:folder-simple-upload" />
Import Folder
</button>
</>
);
};

0 comments on commit 3d2ab89

Please sign in to comment.