mirror of
https://github.com/stackblitz/bolt.new
synced 2025-02-06 04:48:04 +00:00
Proof of concept for folder import
This commit is contained in:
parent
1cb836a648
commit
3d2ab89cdc
@ -20,6 +20,7 @@ import * as Tooltip from '@radix-ui/react-tooltip';
|
|||||||
import styles from './BaseChat.module.scss';
|
import styles from './BaseChat.module.scss';
|
||||||
import type { ProviderInfo } from '~/utils/types';
|
import type { ProviderInfo } from '~/utils/types';
|
||||||
import { ExportChatButton } from '~/components/chat/ExportChatButton';
|
import { ExportChatButton } from '~/components/chat/ExportChatButton';
|
||||||
|
import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
|
||||||
|
|
||||||
const EXAMPLE_PROMPTS = [
|
const EXAMPLE_PROMPTS = [
|
||||||
{ text: 'Build a todo app in React using Tailwind' },
|
{ text: 'Build a todo app in React using Tailwind' },
|
||||||
@ -184,31 +185,21 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
|
|
||||||
reader.onload = async (e) => {
|
reader.onload = async (e) => {
|
||||||
try {
|
try {
|
||||||
const content = e.target?.result as string;
|
const content = JSON.parse(e.target?.result as string);
|
||||||
const data = JSON.parse(content);
|
await importChat(content.description || '', content.messages || []);
|
||||||
|
} catch (error) {
|
||||||
if (!Array.isArray(data.messages)) {
|
toast.error(`Invalid chat file format: ${error instanceof Error ? ': ' + error.message : ''}`);
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.onerror = () => toast.error('Failed to read chat file');
|
|
||||||
|
reader.onerror = () => {
|
||||||
|
toast.error('Something went wrong');
|
||||||
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : 'Failed to import chat');
|
toast.error(error instanceof Error ? error.message : 'Failed to import chat');
|
||||||
}
|
}
|
||||||
e.target.value = ''; // Reset file input
|
e.target.value = ''; // Reset file input
|
||||||
} else {
|
|
||||||
toast.error('Something went wrong');
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -224,6 +215,10 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
<div className="i-ph:upload-simple" />
|
<div className="i-ph:upload-simple" />
|
||||||
Import Chat
|
Import Chat
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
100
app/components/chat/ImportFolderButton.tsx
Normal file
100
app/components/chat/ImportFolderButton.tsx
Normal file
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user