|
import { useSearchParams } from '@remix-run/react'; |
|
import { generateId, type Message } from 'ai'; |
|
import ignore from 'ignore'; |
|
import { useEffect, useState } from 'react'; |
|
import { ClientOnly } from 'remix-utils/client-only'; |
|
import { BaseChat } from '~/components/chat/BaseChat'; |
|
import { Chat } from '~/components/chat/Chat.client'; |
|
import { useGit } from '~/lib/hooks/useGit'; |
|
import { useChatHistory } from '~/lib/persistence'; |
|
import { createCommandsMessage, detectProjectCommands, escapeBoltTags } from '~/utils/projectCommands'; |
|
import { LoadingOverlay } from '~/components/ui/LoadingOverlay'; |
|
import { toast } from 'react-toastify'; |
|
|
|
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', |
|
]; |
|
|
|
export function GitUrlImport() { |
|
const [searchParams] = useSearchParams(); |
|
const { ready: historyReady, importChat } = useChatHistory(); |
|
const { ready: gitReady, gitClone } = useGit(); |
|
const [imported, setImported] = useState(false); |
|
const [loading, setLoading] = useState(true); |
|
|
|
const importRepo = async (repoUrl?: string) => { |
|
if (!gitReady && !historyReady) { |
|
return; |
|
} |
|
|
|
if (repoUrl) { |
|
const ig = ignore().add(IGNORE_PATTERNS); |
|
|
|
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'); |
|
|
|
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}"> |
|
${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, { gitUrl: repoUrl }); |
|
} |
|
} catch (error) { |
|
console.error('Error during import:', error); |
|
toast.error('Failed to import repository'); |
|
setLoading(false); |
|
window.location.href = '/'; |
|
|
|
return; |
|
} |
|
} |
|
}; |
|
|
|
useEffect(() => { |
|
if (!historyReady || !gitReady || imported) { |
|
return; |
|
} |
|
|
|
const url = searchParams.get('url'); |
|
|
|
if (!url) { |
|
window.location.href = '/'; |
|
return; |
|
} |
|
|
|
importRepo(url).catch((error) => { |
|
console.error('Error importing repo:', error); |
|
toast.error('Failed to import repository'); |
|
setLoading(false); |
|
window.location.href = '/'; |
|
}); |
|
setImported(true); |
|
}, [searchParams, historyReady, gitReady, imported]); |
|
|
|
return ( |
|
<ClientOnly fallback={<BaseChat />}> |
|
{() => ( |
|
<> |
|
<Chat /> |
|
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />} |
|
</> |
|
)} |
|
</ClientOnly> |
|
); |
|
} |
|
|