Skip to content

Commit

Permalink
fix: introduce our own cors proxy for git import to fix 403 errors on…
Browse files Browse the repository at this point in the history
… isometric git cors proxy (stackblitz-labs#924)

* Exploration of improving git import

* Fix our own git proxy

* Clean out file counting for progress, does not seem to work well anyways
  • Loading branch information
wonderwhy-er authored Jan 5, 2025
1 parent 31e03ce commit b1f9380
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 150 deletions.
101 changes: 58 additions & 43 deletions app/components/chat/GitCloneButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ 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/**',
Expand Down Expand Up @@ -37,6 +40,8 @@ interface GitCloneButtonProps {

export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
const { ready, gitClone } = useGit();
const [loading, setLoading] = useState(false);

const onClick = async (_e: any) => {
if (!ready) {
return;
Expand All @@ -45,33 +50,34 @@ export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
const repoUrl = prompt('Enter the Git url');

if (repoUrl) {
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');

// Convert files to common format for command detection
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);

// Detect and create commands message
const commands = await detectProjectCommands(fileContents);
const commandsMessage = createCommandsMessage(commands);

// Create files message
const filesMessage: Message = {
role: 'assistant',
content: `Cloning the repo ${repoUrl} into ${workdir}
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(
Expand All @@ -82,29 +88,38 @@ ${file.content}
)
.join('\n')}
</boltArtifact>`,
id: generateId(),
createdAt: new Date(),
};
id: generateId(),
createdAt: new Date(),
};

const messages = [filesMessage];
const messages = [filesMessage];

if (commandsMessage) {
messages.push(commandsMessage);
}
if (commandsMessage) {
messages.push(commandsMessage);
}

await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
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>
<>
<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..." />}
</>
);
}
75 changes: 41 additions & 34 deletions app/components/git/GitUrlImport.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,32 @@ export function GitUrlImport() {

if (repoUrl) {
const ig = ignore().add(IGNORE_PATTERNS);
const { workdir, data } = await gitClone(repoUrl);

if (importChat) {
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));

const textDecoder = new TextDecoder('utf-8');

// Convert files to common format for command detection
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);

// Detect and create commands message
const commands = await detectProjectCommands(fileContents);
const commandsMessage = createCommandsMessage(commands);

// Create files message
const filesMessage: Message = {
role: 'assistant',
content: `Cloning the repo ${repoUrl} into ${workdir}
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">

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) =>
Expand All @@ -85,17 +84,25 @@ ${file.content}
)
.join('\n')}
</boltArtifact>`,
id: generateId(),
createdAt: new Date(),
};
id: generateId(),
createdAt: new Date(),
};

const messages = [filesMessage];

const messages = [filesMessage];
if (commandsMessage) {
messages.push(commandsMessage);
}

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');
setLoading(false);
window.location.href = '/';

await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
return;
}
}
};
Expand Down
22 changes: 20 additions & 2 deletions app/components/ui/LoadingOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
export const LoadingOverlay = ({ message = 'Loading...' }) => {
export const LoadingOverlay = ({
message = 'Loading...',
progress,
progressText,
}: {
message?: string;
progress?: number;
progressText?: string;
}) => {
return (
<div className="fixed inset-0 flex items-center justify-center bg-black/80 z-50 backdrop-blur-sm">
{/* Loading content */}
<div className="relative flex flex-col items-center gap-4 p-8 rounded-lg bg-bolt-elements-background-depth-2 shadow-lg">
<div
className={'i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress'}
style={{ fontSize: '2rem' }}
></div>
<p className="text-lg text-bolt-elements-textTertiary">{message}</p>
{progress !== undefined && (
<div className="w-64 flex flex-col gap-2">
<div className="w-full h-2 bg-bolt-elements-background-depth-1 rounded-full overflow-hidden">
<div
className="h-full bg-bolt-elements-loader-progress transition-all duration-300 ease-out rounded-full"
style={{ width: `${Math.min(100, Math.max(0, progress))}%` }}
/>
</div>
{progressText && <p className="text-sm text-bolt-elements-textTertiary text-center">{progressText}</p>}
</div>
)}
</div>
</div>
);
Expand Down
Loading

0 comments on commit b1f9380

Please sign in to comment.