From 47db5547bd8c4523209160b7d3f09a9e9039a61b Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Fri, 17 Jan 2025 16:50:33 -0500 Subject: [PATCH] feat: add private repo cloning add private repo cloning --- app/components/chat/GitCloneButton.tsx | 92 +++++++++++---------- app/components/git/GitCloneDialog.tsx | 110 +++++++++++++++++++++++++ app/components/ui/Dialog.tsx | 6 +- 3 files changed, 163 insertions(+), 45 deletions(-) create mode 100644 app/components/git/GitCloneDialog.tsx diff --git a/app/components/chat/GitCloneButton.tsx b/app/components/chat/GitCloneButton.tsx index 2a20199..0f14d75 100644 --- a/app/components/chat/GitCloneButton.tsx +++ b/app/components/chat/GitCloneButton.tsx @@ -3,6 +3,8 @@ import ignore from 'ignore'; import WithTooltip from '~/components/ui/Tooltip'; import { IGNORE_PATTERNS } from '~/constants/ignorePatterns'; import { useGit } from '~/lib/hooks/useGit'; +import { useState } from 'react'; +import { GitCloneDialog } from '~/components/git/GitCloneDialog'; const ig = ignore().add(IGNORE_PATTERNS); const generateId = () => Math.random().toString(36).substring(2, 15); @@ -14,65 +16,69 @@ interface GitCloneButtonProps { export default function GitCloneButton({ importChat }: GitCloneButtonProps) { const { ready, gitClone } = useGit(); - const onClick = async (_e: any) => { + const [showDialog, setShowDialog] = useState(false); + + const handleClone = async (repoUrl: string) => { if (!ready) { return; } - const repoUrl = prompt('Enter the Git url'); + const { workdir, data } = await gitClone(repoUrl); - if (repoUrl) { - const { workdir, data } = await gitClone(repoUrl); + if (importChat) { + const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath)); + console.log(filePaths); - if (importChat) { - const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath)); - console.log(filePaths); - - const textDecoder = new TextDecoder('utf-8'); - const message: Message = { - role: 'assistant', - content: `Cloning the repo ${repoUrl} into ${workdir} + const textDecoder = new TextDecoder('utf-8'); + const message: Message = { + role: 'assistant', + content: `Cloning the repo ${repoUrl} into ${workdir} - ${filePaths - .map((filePath) => { - const { data: content, encoding } = data[filePath]; + ${filePaths + .map((filePath) => { + const { data: content, encoding } = data[filePath]; - if (encoding === 'utf8') { - return ` + if (encoding === 'utf8') { + return ` ${content} `; - } else if (content instanceof Uint8Array) { - return ` + } else if (content instanceof Uint8Array) { + return ` ${textDecoder.decode(content)} `; - } else { - return ''; - } - }) - .join('\n')} - `, - id: generateId(), - createdAt: new Date(), - }; - console.log(JSON.stringify(message)); + } else { + return ''; + } + }) + .join('\n')} +`, + id: generateId(), + createdAt: new Date(), + }; + console.log(JSON.stringify(message)); - importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, [message]); - } + importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, [message]); } }; return ( - - - + <> + + + + + setShowDialog(false)} + onClone={handleClone} + /> + ); } diff --git a/app/components/git/GitCloneDialog.tsx b/app/components/git/GitCloneDialog.tsx new file mode 100644 index 0000000..5d91139 --- /dev/null +++ b/app/components/git/GitCloneDialog.tsx @@ -0,0 +1,110 @@ +import { useState } from 'react'; +import { Dialog, DialogButton, DialogDescription, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; +import { useGit } from '~/lib/hooks/useGit'; +import { toast } from 'react-toastify'; + +interface GitCloneDialogProps { + isOpen: boolean; + onClose: () => void; + onClone?: (repoUrl: string) => Promise; +} + +export function GitCloneDialog({ isOpen, onClose, onClone }: GitCloneDialogProps) { + const [isPrivate, setIsPrivate] = useState(false); + const [repoUrl, setRepoUrl] = useState(''); + const [token, setToken] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + setIsLoading(true); + try { + // If it's a private repo, construct the URL with the token + const cloneUrl = isPrivate + ? repoUrl.replace('https://', `https://${token}@`) + : repoUrl; + + if (onClone) { + await onClone(cloneUrl); + } + toast.success('Repository cloned successfully!'); + onClose(); + } catch (error) { + toast.error(error instanceof Error ? error.message : 'Failed to clone repository'); + } finally { + setIsLoading(false); + } + }; + + return ( + + + Clone Repository + +
+
+ + +
+ +
+ +
+ + {isPrivate && ( +
+ +
+ )} + +
+ + Cancel + + + {isLoading ? 'Cloning...' : 'Clone Repository'} + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/components/ui/Dialog.tsx b/app/components/ui/Dialog.tsx index ad5e9f8..99467ac 100644 --- a/app/components/ui/Dialog.tsx +++ b/app/components/ui/Dialog.tsx @@ -44,13 +44,14 @@ interface DialogButtonProps { type: 'primary' | 'secondary' | 'danger'; children: ReactNode; onClick?: (event: React.UIEvent) => void; + disabled?: boolean; } -export const DialogButton = memo(({ type, children, onClick }: DialogButtonProps) => { +export const DialogButton = memo(({ type, children, onClick, disabled = false }: DialogButtonProps) => { return (