|
import ignore from 'ignore'; |
|
import { useGit } from '~/lib/hooks/useGit'; |
|
import type { Message } from 'ai'; |
|
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands'; |
|
import { generateId } from '~/utils/fileUtils'; |
|
import { useState } from 'react'; |
|
import { toast } from 'react-toastify'; |
|
import { LoadingOverlay } from '~/components/ui/LoadingOverlay'; |
|
|
|
const IGNORE_PATTERNS = [ |
|
'node_modules/**', |
|
'.git/**', |
|
'.github/**', |
|
'.vscode/**', |
|
'**/*.jpg', |
|
'**/*.jpeg', |
|
'**/*.png', |
|
'dist/**', |
|
'build/**', |
|
'.next/**', |
|
'coverage/**', |
|
'.cache/**', |
|
'.vscode/**', |
|
'.idea/**', |
|
'**/*.log', |
|
'**/.DS_Store', |
|
'**/npm-debug.log*', |
|
'**/yarn-debug.log*', |
|
'**/yarn-error.log*', |
|
'**/*lock.json', |
|
'**/*lock.yaml', |
|
]; |
|
|
|
const ig = ignore().add(IGNORE_PATTERNS); |
|
|
|
interface GitCloneButtonProps { |
|
className?: string; |
|
importChat?: (description: string, messages: Message[]) => Promise<void>; |
|
} |
|
|
|
export default function GitCloneButton({ importChat }: GitCloneButtonProps) { |
|
const { ready, gitClone } = useGit(); |
|
const [loading, setLoading] = useState(false); |
|
|
|
const onClick = async (_e: any) => { |
|
if (!ready) { |
|
return; |
|
} |
|
|
|
const repoUrl = prompt('Enter the Git url'); |
|
|
|
if (repoUrl) { |
|
setLoading(true); |
|
|
|
try { |
|
const { workdir, data } = await gitClone(repoUrl); |
|
|
|
if (importChat) { |
|
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath)); |
|
console.log(filePaths); |
|
|
|
const textDecoder = new TextDecoder('utf-8'); |
|
|
|
const fileContents = filePaths |
|
.map((filePath) => { |
|
const { data: content, encoding } = data[filePath]; |
|
return { |
|
path: filePath, |
|
content: |
|
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '', |
|
}; |
|
}) |
|
.filter((f) => f.content); |
|
|
|
const commands = await detectProjectCommands(fileContents); |
|
const commandsMessage = createCommandsMessage(commands); |
|
|
|
const filesMessage: Message = { |
|
role: 'assistant', |
|
content: `Cloning the repo ${repoUrl} into ${workdir} |
|
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled"> |
|
${fileContents |
|
.map( |
|
(file) => |
|
`<boltAction type="file" filePath="${file.path}"> |
|
${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={onClick} |
|
title="Clone a Git Repo" |
|
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" |
|
> |
|
<span className="i-ph:git-branch" /> |
|
Clone a Git Repo |
|
</button> |
|
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />} |
|
</> |
|
); |
|
} |
|
|