mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 10:16:01 +00:00
Remove redundant error type handling in webcontainer to simplify logic and improve maintainability. Additionally, comment out lock file patterns to speed up npm install process.
183 lines
5.4 KiB
TypeScript
183 lines
5.4 KiB
TypeScript
import ignore from 'ignore';
|
|
import { useGit } from '~/lib/hooks/useGit';
|
|
import type { Message } from 'ai';
|
|
import { detectProjectCommands, createCommandsMessage, escapeBoltTags } from '~/utils/projectCommands';
|
|
import { generateId } from '~/utils/fileUtils';
|
|
import { useState } from 'react';
|
|
import { toast } from 'react-toastify';
|
|
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
|
|
import { RepositorySelectionDialog } from '~/components/@settings/tabs/connections/components/RepositorySelectionDialog';
|
|
import { classNames } from '~/utils/classNames';
|
|
import { Button } from '~/components/ui/Button';
|
|
import type { IChatMetadata } from '~/lib/persistence/db';
|
|
|
|
const IGNORE_PATTERNS = [
|
|
'node_modules/**',
|
|
'.git/**',
|
|
'.github/**',
|
|
'.vscode/**',
|
|
'dist/**',
|
|
'build/**',
|
|
'.next/**',
|
|
'coverage/**',
|
|
'.cache/**',
|
|
'.idea/**',
|
|
'**/*.log',
|
|
'**/.DS_Store',
|
|
'**/npm-debug.log*',
|
|
'**/yarn-debug.log*',
|
|
'**/yarn-error.log*',
|
|
|
|
// Include this so npm install runs much faster '**/*lock.json',
|
|
'**/*lock.yaml',
|
|
];
|
|
|
|
const ig = ignore().add(IGNORE_PATTERNS);
|
|
|
|
const MAX_FILE_SIZE = 100 * 1024; // 100KB limit per file
|
|
const MAX_TOTAL_SIZE = 500 * 1024; // 500KB total limit
|
|
|
|
interface GitCloneButtonProps {
|
|
className?: string;
|
|
importChat?: (description: string, messages: Message[], metadata?: IChatMetadata) => Promise<void>;
|
|
}
|
|
|
|
export default function GitCloneButton({ importChat, className }: GitCloneButtonProps) {
|
|
const { ready, gitClone } = useGit();
|
|
const [loading, setLoading] = useState(false);
|
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
|
|
const handleClone = async (repoUrl: string) => {
|
|
if (!ready) {
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
|
|
try {
|
|
const { workdir, data } = await gitClone(repoUrl);
|
|
|
|
if (importChat) {
|
|
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
|
|
const textDecoder = new TextDecoder('utf-8');
|
|
|
|
let totalSize = 0;
|
|
const skippedFiles: string[] = [];
|
|
const fileContents = [];
|
|
|
|
for (const filePath of filePaths) {
|
|
const { data: content, encoding } = data[filePath];
|
|
|
|
// Skip binary files
|
|
if (
|
|
content instanceof Uint8Array &&
|
|
!filePath.match(/\.(txt|md|astro|mjs|js|jsx|ts|tsx|json|html|css|scss|less|yml|yaml|xml|svg|vue|svelte)$/i)
|
|
) {
|
|
skippedFiles.push(filePath);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const textContent =
|
|
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '';
|
|
|
|
if (!textContent) {
|
|
continue;
|
|
}
|
|
|
|
// Check file size
|
|
const fileSize = new TextEncoder().encode(textContent).length;
|
|
|
|
if (fileSize > MAX_FILE_SIZE) {
|
|
skippedFiles.push(`${filePath} (too large: ${Math.round(fileSize / 1024)}KB)`);
|
|
continue;
|
|
}
|
|
|
|
// Check total size
|
|
if (totalSize + fileSize > MAX_TOTAL_SIZE) {
|
|
skippedFiles.push(`${filePath} (would exceed total size limit)`);
|
|
continue;
|
|
}
|
|
|
|
totalSize += fileSize;
|
|
fileContents.push({
|
|
path: filePath,
|
|
content: textContent,
|
|
});
|
|
} catch (e: any) {
|
|
skippedFiles.push(`${filePath} (error: ${e.message})`);
|
|
}
|
|
}
|
|
|
|
const commands = await detectProjectCommands(fileContents);
|
|
const commandsMessage = createCommandsMessage(commands);
|
|
|
|
const filesMessage: Message = {
|
|
role: 'assistant',
|
|
content: `Cloning the repo ${repoUrl} into ${workdir}
|
|
${
|
|
skippedFiles.length > 0
|
|
? `\nSkipped files (${skippedFiles.length}):
|
|
${skippedFiles.map((f) => `- ${f}`).join('\n')}`
|
|
: ''
|
|
}
|
|
|
|
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
|
|
${fileContents
|
|
.map(
|
|
(file) =>
|
|
`<boltAction type="file" filePath="${file.path}">
|
|
${escapeBoltTags(file.content)}
|
|
</boltAction>`,
|
|
)
|
|
.join('\n')}
|
|
</boltArtifact>`,
|
|
id: generateId(),
|
|
createdAt: new Date(),
|
|
};
|
|
|
|
const messages = [filesMessage];
|
|
|
|
if (commandsMessage) {
|
|
messages.push(commandsMessage);
|
|
}
|
|
|
|
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error during import:', error);
|
|
toast.error('Failed to import repository');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Button
|
|
onClick={() => setIsDialogOpen(true)}
|
|
title="Clone a Git Repo"
|
|
variant="default"
|
|
size="lg"
|
|
className={classNames(
|
|
'gap-2 bg-bolt-elements-background-depth-1',
|
|
'text-bolt-elements-textPrimary',
|
|
'hover:bg-bolt-elements-background-depth-2',
|
|
'border border-bolt-elements-borderColor',
|
|
'h-10 px-4 py-2 min-w-[120px] justify-center',
|
|
'transition-all duration-200 ease-in-out',
|
|
className,
|
|
)}
|
|
disabled={!ready || loading}
|
|
>
|
|
<span className="i-ph:git-branch w-4 h-4" />
|
|
Clone a Git Repo
|
|
</Button>
|
|
|
|
<RepositorySelectionDialog isOpen={isDialogOpen} onClose={() => setIsDialogOpen(false)} onSelect={handleClone} />
|
|
|
|
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
|
|
</>
|
|
);
|
|
}
|