Skip to content

Commit ea73342

Browse files
wonderwhy-erapple
authored andcommitted
fix: introduce our own cors proxy for git import to fix 403 errors on 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
1 parent 4d7d1a0 commit ea73342

File tree

5 files changed

+279
-150
lines changed

5 files changed

+279
-150
lines changed

app/components/chat/GitCloneButton.tsx

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { useGit } from '~/lib/hooks/useGit';
33
import type { Message } from 'ai';
44
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
55
import { generateId } from '~/utils/fileUtils';
6+
import { useState } from 'react';
7+
import { toast } from 'react-toastify';
8+
import { LoadingOverlay } from '~/components/ui/LoadingOverlay';
69

710
const IGNORE_PATTERNS = [
811
'node_modules/**',
@@ -37,6 +40,8 @@ interface GitCloneButtonProps {
3740

3841
export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
3942
const { ready, gitClone } = useGit();
43+
const [loading, setLoading] = useState(false);
44+
4045
const onClick = async (_e: any) => {
4146
if (!ready) {
4247
return;
@@ -45,33 +50,34 @@ export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
4550
const repoUrl = prompt('Enter the Git url');
4651

4752
if (repoUrl) {
48-
const { workdir, data } = await gitClone(repoUrl);
49-
50-
if (importChat) {
51-
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
52-
console.log(filePaths);
53-
54-
const textDecoder = new TextDecoder('utf-8');
55-
56-
// Convert files to common format for command detection
57-
const fileContents = filePaths
58-
.map((filePath) => {
59-
const { data: content, encoding } = data[filePath];
60-
return {
61-
path: filePath,
62-
content: encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '',
63-
};
64-
})
65-
.filter((f) => f.content);
66-
67-
// Detect and create commands message
68-
const commands = await detectProjectCommands(fileContents);
69-
const commandsMessage = createCommandsMessage(commands);
70-
71-
// Create files message
72-
const filesMessage: Message = {
73-
role: 'assistant',
74-
content: `Cloning the repo ${repoUrl} into ${workdir}
53+
setLoading(true);
54+
55+
try {
56+
const { workdir, data } = await gitClone(repoUrl);
57+
58+
if (importChat) {
59+
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
60+
console.log(filePaths);
61+
62+
const textDecoder = new TextDecoder('utf-8');
63+
64+
const fileContents = filePaths
65+
.map((filePath) => {
66+
const { data: content, encoding } = data[filePath];
67+
return {
68+
path: filePath,
69+
content:
70+
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '',
71+
};
72+
})
73+
.filter((f) => f.content);
74+
75+
const commands = await detectProjectCommands(fileContents);
76+
const commandsMessage = createCommandsMessage(commands);
77+
78+
const filesMessage: Message = {
79+
role: 'assistant',
80+
content: `Cloning the repo ${repoUrl} into ${workdir}
7581
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
7682
${fileContents
7783
.map(
@@ -82,29 +88,38 @@ ${file.content}
8288
)
8389
.join('\n')}
8490
</boltArtifact>`,
85-
id: generateId(),
86-
createdAt: new Date(),
87-
};
91+
id: generateId(),
92+
createdAt: new Date(),
93+
};
8894

89-
const messages = [filesMessage];
95+
const messages = [filesMessage];
9096

91-
if (commandsMessage) {
92-
messages.push(commandsMessage);
93-
}
97+
if (commandsMessage) {
98+
messages.push(commandsMessage);
99+
}
94100

95-
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
101+
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
102+
}
103+
} catch (error) {
104+
console.error('Error during import:', error);
105+
toast.error('Failed to import repository');
106+
} finally {
107+
setLoading(false);
96108
}
97109
}
98110
};
99111

100112
return (
101-
<button
102-
onClick={onClick}
103-
title="Clone a Git Repo"
104-
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"
105-
>
106-
<span className="i-ph:git-branch" />
107-
Clone a Git Repo
108-
</button>
113+
<>
114+
<button
115+
onClick={onClick}
116+
title="Clone a Git Repo"
117+
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"
118+
>
119+
<span className="i-ph:git-branch" />
120+
Clone a Git Repo
121+
</button>
122+
{loading && <LoadingOverlay message="Please wait while we clone the repository..." />}
123+
</>
109124
);
110125
}

app/components/git/GitUrlImport.client.tsx

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -49,33 +49,32 @@ export function GitUrlImport() {
4949

5050
if (repoUrl) {
5151
const ig = ignore().add(IGNORE_PATTERNS);
52-
const { workdir, data } = await gitClone(repoUrl);
53-
54-
if (importChat) {
55-
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
56-
57-
const textDecoder = new TextDecoder('utf-8');
58-
59-
// Convert files to common format for command detection
60-
const fileContents = filePaths
61-
.map((filePath) => {
62-
const { data: content, encoding } = data[filePath];
63-
return {
64-
path: filePath,
65-
content: encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '',
66-
};
67-
})
68-
.filter((f) => f.content);
69-
70-
// Detect and create commands message
71-
const commands = await detectProjectCommands(fileContents);
72-
const commandsMessage = createCommandsMessage(commands);
73-
74-
// Create files message
75-
const filesMessage: Message = {
76-
role: 'assistant',
77-
content: `Cloning the repo ${repoUrl} into ${workdir}
78-
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
52+
53+
try {
54+
const { workdir, data } = await gitClone(repoUrl);
55+
56+
if (importChat) {
57+
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
58+
const textDecoder = new TextDecoder('utf-8');
59+
60+
const fileContents = filePaths
61+
.map((filePath) => {
62+
const { data: content, encoding } = data[filePath];
63+
return {
64+
path: filePath,
65+
content:
66+
encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '',
67+
};
68+
})
69+
.filter((f) => f.content);
70+
71+
const commands = await detectProjectCommands(fileContents);
72+
const commandsMessage = createCommandsMessage(commands);
73+
74+
const filesMessage: Message = {
75+
role: 'assistant',
76+
content: `Cloning the repo ${repoUrl} into ${workdir}
77+
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
7978
${fileContents
8079
.map(
8180
(file) =>
@@ -85,17 +84,25 @@ ${file.content}
8584
)
8685
.join('\n')}
8786
</boltArtifact>`,
88-
id: generateId(),
89-
createdAt: new Date(),
90-
};
87+
id: generateId(),
88+
createdAt: new Date(),
89+
};
90+
91+
const messages = [filesMessage];
9192

92-
const messages = [filesMessage];
93+
if (commandsMessage) {
94+
messages.push(commandsMessage);
95+
}
9396

94-
if (commandsMessage) {
95-
messages.push(commandsMessage);
97+
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
9698
}
99+
} catch (error) {
100+
console.error('Error during import:', error);
101+
toast.error('Failed to import repository');
102+
setLoading(false);
103+
window.location.href = '/';
97104

98-
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
105+
return;
99106
}
100107
}
101108
};

app/components/ui/LoadingOverlay.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
1-
export const LoadingOverlay = ({ message = 'Loading...' }) => {
1+
export const LoadingOverlay = ({
2+
message = 'Loading...',
3+
progress,
4+
progressText,
5+
}: {
6+
message?: string;
7+
progress?: number;
8+
progressText?: string;
9+
}) => {
210
return (
311
<div className="fixed inset-0 flex items-center justify-center bg-black/80 z-50 backdrop-blur-sm">
4-
{/* Loading content */}
512
<div className="relative flex flex-col items-center gap-4 p-8 rounded-lg bg-bolt-elements-background-depth-2 shadow-lg">
613
<div
714
className={'i-svg-spinners:90-ring-with-bg text-bolt-elements-loader-progress'}
815
style={{ fontSize: '2rem' }}
916
></div>
1017
<p className="text-lg text-bolt-elements-textTertiary">{message}</p>
18+
{progress !== undefined && (
19+
<div className="w-64 flex flex-col gap-2">
20+
<div className="w-full h-2 bg-bolt-elements-background-depth-1 rounded-full overflow-hidden">
21+
<div
22+
className="h-full bg-bolt-elements-loader-progress transition-all duration-300 ease-out rounded-full"
23+
style={{ width: `${Math.min(100, Math.max(0, progress))}%` }}
24+
/>
25+
</div>
26+
{progressText && <p className="text-sm text-bolt-elements-textTertiary text-center">{progressText}</p>}
27+
</div>
28+
)}
1129
</div>
1230
</div>
1331
);

0 commit comments

Comments
 (0)