|
import React, { useState } from 'react'; |
|
import type { Message } from 'ai'; |
|
import { toast } from 'react-toastify'; |
|
import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils'; |
|
import { createChatFromFolder } from '~/utils/folderImport'; |
|
import { logStore } from '~/lib/stores/logs'; |
|
import { Button } from '~/components/ui/Button'; |
|
import { classNames } from '~/utils/classNames'; |
|
|
|
interface ImportFolderButtonProps { |
|
className?: string; |
|
importChat?: (description: string, messages: Message[]) => Promise<void>; |
|
} |
|
|
|
export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ className, importChat }) => { |
|
const [isLoading, setIsLoading] = useState(false); |
|
|
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { |
|
const allFiles = Array.from(e.target.files || []); |
|
|
|
const filteredFiles = allFiles.filter((file) => { |
|
const path = file.webkitRelativePath.split('/').slice(1).join('/'); |
|
const include = shouldIncludeFile(path); |
|
|
|
return include; |
|
}); |
|
|
|
if (filteredFiles.length === 0) { |
|
const error = new Error('No valid files found'); |
|
logStore.logError('File import failed - no valid files', error, { folderName: 'Unknown Folder' }); |
|
toast.error('No files found in the selected folder'); |
|
|
|
return; |
|
} |
|
|
|
if (filteredFiles.length > MAX_FILES) { |
|
const error = new Error(`Too many files: ${filteredFiles.length}`); |
|
logStore.logError('File import failed - too many files', error, { |
|
fileCount: filteredFiles.length, |
|
maxFiles: MAX_FILES, |
|
}); |
|
toast.error( |
|
`This folder contains ${filteredFiles.length.toLocaleString()} files. This product is not yet optimized for very large projects. Please select a folder with fewer than ${MAX_FILES.toLocaleString()} files.`, |
|
); |
|
|
|
return; |
|
} |
|
|
|
const folderName = filteredFiles[0]?.webkitRelativePath.split('/')[0] || 'Unknown Folder'; |
|
setIsLoading(true); |
|
|
|
const loadingToast = toast.loading(`Importing ${folderName}...`); |
|
|
|
try { |
|
const fileChecks = await Promise.all( |
|
filteredFiles.map(async (file) => ({ |
|
file, |
|
isBinary: await isBinaryFile(file), |
|
})), |
|
); |
|
|
|
const textFiles = fileChecks.filter((f) => !f.isBinary).map((f) => f.file); |
|
const binaryFilePaths = fileChecks |
|
.filter((f) => f.isBinary) |
|
.map((f) => f.file.webkitRelativePath.split('/').slice(1).join('/')); |
|
|
|
if (textFiles.length === 0) { |
|
const error = new Error('No text files found'); |
|
logStore.logError('File import failed - no text files', error, { folderName }); |
|
toast.error('No text files found in the selected folder'); |
|
|
|
return; |
|
} |
|
|
|
if (binaryFilePaths.length > 0) { |
|
logStore.logWarning(`Skipping binary files during import`, { |
|
folderName, |
|
binaryCount: binaryFilePaths.length, |
|
}); |
|
toast.info(`Skipping ${binaryFilePaths.length} binary files`); |
|
} |
|
|
|
const messages = await createChatFromFolder(textFiles, binaryFilePaths, folderName); |
|
|
|
if (importChat) { |
|
await importChat(folderName, [...messages]); |
|
} |
|
|
|
logStore.logSystem('Folder imported successfully', { |
|
folderName, |
|
textFileCount: textFiles.length, |
|
binaryFileCount: binaryFilePaths.length, |
|
}); |
|
toast.success('Folder imported successfully'); |
|
} catch (error) { |
|
logStore.logError('Failed to import folder', error, { folderName }); |
|
console.error('Failed to import folder:', error); |
|
toast.error('Failed to import folder'); |
|
} finally { |
|
setIsLoading(false); |
|
toast.dismiss(loadingToast); |
|
e.target.value = ''; |
|
} |
|
}; |
|
|
|
return ( |
|
<> |
|
<input |
|
type="file" |
|
id="folder-import" |
|
className="hidden" |
|
webkitdirectory="" |
|
directory="" |
|
onChange={handleFileChange} |
|
{...({} as any)} |
|
/> |
|
<Button |
|
onClick={() => { |
|
const input = document.getElementById('folder-import'); |
|
input?.click(); |
|
}} |
|
title="Import Folder" |
|
variant="outline" |
|
size="lg" |
|
className={classNames( |
|
'gap-2 bg-[#F5F5F5] dark:bg-[#252525]', |
|
'text-bolt-elements-textPrimary dark:text-white', |
|
'hover:bg-[#E5E5E5] dark:hover:bg-[#333333]', |
|
'border-[#E5E5E5] dark:border-[#333333]', |
|
'h-10 px-4 py-2 min-w-[120px] justify-center', |
|
'transition-all duration-200 ease-in-out', |
|
className, |
|
)} |
|
disabled={isLoading} |
|
> |
|
<span className="i-ph:upload-simple w-4 h-4" /> |
|
{isLoading ? 'Importing...' : 'Import Folder'} |
|
</Button> |
|
</> |
|
); |
|
}; |
|
|