added context to history
Browse files
app/components/chat/BaseChat.tsx
CHANGED
@@ -22,7 +22,6 @@ import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportCh
|
|
22 |
import { ImportButtons } from '~/components/chat/chatExportAndImport/ImportButtons';
|
23 |
import { ExamplePrompts } from '~/components/chat/ExamplePrompts';
|
24 |
import GitCloneButton from './GitCloneButton';
|
25 |
-
import * as Separator from '@radix-ui/react-separator';
|
26 |
|
27 |
// @ts-ignore TODO: Introduce proper types
|
28 |
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
@@ -256,11 +255,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
256 |
<span>Model Settings</span>
|
257 |
</button>
|
258 |
</div>
|
259 |
-
|
260 |
-
<GitCloneButton />
|
261 |
-
<Separator.Root className="my-[15px] bg-gray6 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px" />
|
262 |
-
<div className="flex items-center gap-3"></div>
|
263 |
-
|
264 |
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
|
265 |
<ModelSelector
|
266 |
key={provider?.name + ':' + modelList.length}
|
@@ -367,7 +361,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|
367 |
</div>
|
368 |
</div>
|
369 |
</div>
|
370 |
-
{!chatStarted &&
|
|
|
|
|
|
|
|
|
|
|
371 |
{!chatStarted && ExamplePrompts(sendMessage)}
|
372 |
</div>
|
373 |
<ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
|
|
|
22 |
import { ImportButtons } from '~/components/chat/chatExportAndImport/ImportButtons';
|
23 |
import { ExamplePrompts } from '~/components/chat/ExamplePrompts';
|
24 |
import GitCloneButton from './GitCloneButton';
|
|
|
25 |
|
26 |
// @ts-ignore TODO: Introduce proper types
|
27 |
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
255 |
<span>Model Settings</span>
|
256 |
</button>
|
257 |
</div>
|
|
|
|
|
|
|
|
|
|
|
258 |
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
|
259 |
<ModelSelector
|
260 |
key={provider?.name + ':' + modelList.length}
|
|
|
361 |
</div>
|
362 |
</div>
|
363 |
</div>
|
364 |
+
{!chatStarted && (
|
365 |
+
<div className="flex justify-center gap-2">
|
366 |
+
{ImportButtons(importChat)}
|
367 |
+
<GitCloneButton importChat={importChat} />
|
368 |
+
</div>
|
369 |
+
)}
|
370 |
{!chatStarted && ExamplePrompts(sendMessage)}
|
371 |
</div>
|
372 |
<ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
|
app/components/chat/GitCloneButton.tsx
CHANGED
@@ -1,7 +1,36 @@
|
|
1 |
-
import
|
2 |
import { useGit } from '~/lib/hooks/useGit';
|
|
|
|
|
3 |
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
const { ready, gitClone } = useGit();
|
6 |
const onClick = async (_e: any) => {
|
7 |
if (!ready) {
|
@@ -11,20 +40,59 @@ export default function GitCloneButton() {
|
|
11 |
const repoUrl = prompt('Enter the Git url');
|
12 |
|
13 |
if (repoUrl) {
|
14 |
-
await gitClone(repoUrl);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
}
|
16 |
};
|
17 |
|
18 |
return (
|
19 |
-
<
|
20 |
-
|
21 |
-
onClick(e)
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
29 |
);
|
30 |
}
|
|
|
1 |
+
import ignore from 'ignore';
|
2 |
import { useGit } from '~/lib/hooks/useGit';
|
3 |
+
import type { Message } from 'ai';
|
4 |
+
import WithTooltip from '~/components/ui/Tooltip';
|
5 |
|
6 |
+
const IGNORE_PATTERNS = [
|
7 |
+
'node_modules/**',
|
8 |
+
'.git/**',
|
9 |
+
'.github/**',
|
10 |
+
'dist/**',
|
11 |
+
'build/**',
|
12 |
+
'.next/**',
|
13 |
+
'coverage/**',
|
14 |
+
'.cache/**',
|
15 |
+
'.vscode/**',
|
16 |
+
'.idea/**',
|
17 |
+
'**/*.log',
|
18 |
+
'**/.DS_Store',
|
19 |
+
'**/npm-debug.log*',
|
20 |
+
'**/yarn-debug.log*',
|
21 |
+
'**/yarn-error.log*',
|
22 |
+
'**/*lock.json',
|
23 |
+
];
|
24 |
+
|
25 |
+
const ig = ignore().add(IGNORE_PATTERNS);
|
26 |
+
const generateId = () => Math.random().toString(36).substring(2, 15);
|
27 |
+
|
28 |
+
interface GitCloneButtonProps {
|
29 |
+
className?: string;
|
30 |
+
importChat?: (description: string, messages: Message[]) => Promise<void>;
|
31 |
+
}
|
32 |
+
|
33 |
+
export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
|
34 |
const { ready, gitClone } = useGit();
|
35 |
const onClick = async (_e: any) => {
|
36 |
if (!ready) {
|
|
|
40 |
const repoUrl = prompt('Enter the Git url');
|
41 |
|
42 |
if (repoUrl) {
|
43 |
+
const { workdir, data } = await gitClone(repoUrl);
|
44 |
+
|
45 |
+
if (importChat) {
|
46 |
+
const filePaths = Object.keys(data).filter((filePath) => !ig.ignores(filePath));
|
47 |
+
console.log(filePaths);
|
48 |
+
|
49 |
+
const textDecoder = new TextDecoder('utf-8');
|
50 |
+
const message: Message = {
|
51 |
+
role: 'assistant',
|
52 |
+
content: `Cloning the repo ${repoUrl} into ${workdir}
|
53 |
+
<boltArtifact id="imported-files" title="Git Cloned Files">
|
54 |
+
${filePaths
|
55 |
+
.map((filePath) => {
|
56 |
+
const { data: content, encoding } = data[filePath];
|
57 |
+
|
58 |
+
if (encoding === 'utf8') {
|
59 |
+
return `<boltAction type="file" filePath="${filePath}">
|
60 |
+
${content}
|
61 |
+
</boltAction>`;
|
62 |
+
} else if (content instanceof Uint8Array) {
|
63 |
+
return `<boltAction type="file" filePath="${filePath}">
|
64 |
+
${textDecoder.decode(content)}
|
65 |
+
</boltAction>`;
|
66 |
+
} else {
|
67 |
+
return '';
|
68 |
+
}
|
69 |
+
})
|
70 |
+
.join('\n')}
|
71 |
+
</boltArtifact>`,
|
72 |
+
id: generateId(),
|
73 |
+
createdAt: new Date(),
|
74 |
+
};
|
75 |
+
console.log(JSON.stringify(message));
|
76 |
+
|
77 |
+
importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, [message]);
|
78 |
+
|
79 |
+
// console.log(files);
|
80 |
+
}
|
81 |
}
|
82 |
};
|
83 |
|
84 |
return (
|
85 |
+
<WithTooltip tooltip="Clone A Git Repo">
|
86 |
+
<button
|
87 |
+
onClick={(e) => {
|
88 |
+
onClick(e);
|
89 |
+
}}
|
90 |
+
title="Clone A Git Repo"
|
91 |
+
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"
|
92 |
+
>
|
93 |
+
<span className="i-ph:git-branch" />
|
94 |
+
Clone A Git Repo
|
95 |
+
</button>
|
96 |
+
</WithTooltip>
|
97 |
);
|
98 |
}
|
app/components/chat/chatExportAndImport/ImportButtons.tsx
CHANGED
@@ -5,7 +5,7 @@ import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
|
|
5 |
|
6 |
export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise<void>) | undefined) {
|
7 |
return (
|
8 |
-
<div className="flex flex-col items-center justify-center
|
9 |
<input
|
10 |
type="file"
|
11 |
id="chat-import"
|
|
|
5 |
|
6 |
export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise<void>) | undefined) {
|
7 |
return (
|
8 |
+
<div className="flex flex-col items-center justify-center w-auto">
|
9 |
<input
|
10 |
type="file"
|
11 |
id="chat-import"
|
app/lib/hooks/useGit.ts
CHANGED
@@ -1,14 +1,16 @@
|
|
1 |
import type { WebContainer } from '@webcontainer/api';
|
2 |
-
import { useCallback, useEffect, useState } from 'react';
|
3 |
import { webcontainer as webcontainerPromise } from '~/lib/webcontainer';
|
4 |
import git, { type PromiseFsClient } from 'isomorphic-git';
|
5 |
import http from 'isomorphic-git/http/web';
|
6 |
import Cookies from 'js-cookie';
|
|
|
7 |
|
8 |
export function useGit() {
|
9 |
const [ready, setReady] = useState(false);
|
10 |
const [webcontainer, setWebcontainer] = useState<WebContainer>();
|
11 |
const [fs, setFs] = useState<PromiseFsClient>();
|
|
|
12 |
const lookupSavedPassword: (url: string) => any | null = (url: string) => {
|
13 |
try {
|
14 |
// Save updated API keys to cookies with 30 day expiry and secure settings
|
@@ -30,8 +32,9 @@ export function useGit() {
|
|
30 |
};
|
31 |
useEffect(() => {
|
32 |
webcontainerPromise.then((container) => {
|
|
|
33 |
setWebcontainer(container);
|
34 |
-
setFs(getFs(container));
|
35 |
setReady(true);
|
36 |
});
|
37 |
}, []);
|
@@ -39,10 +42,11 @@ export function useGit() {
|
|
39 |
const gitClone = useCallback(
|
40 |
async (url: string) => {
|
41 |
if (!webcontainer || !fs || !ready) {
|
42 |
-
|
43 |
}
|
44 |
|
45 |
-
|
|
|
46 |
fs,
|
47 |
http,
|
48 |
dir: webcontainer.workdir,
|
@@ -51,6 +55,8 @@ export function useGit() {
|
|
51 |
singleBranch: true,
|
52 |
corsProxy: 'https://cors.isomorphic-git.org',
|
53 |
onAuth: (url) => {
|
|
|
|
|
54 |
let auth = lookupSavedPassword(url);
|
55 |
|
56 |
if (auth) {
|
@@ -67,8 +73,18 @@ export function useGit() {
|
|
67 |
return { cancel: true };
|
68 |
}
|
69 |
},
|
|
|
|
|
|
|
70 |
});
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
},
|
73 |
[webcontainer],
|
74 |
);
|
@@ -76,7 +92,10 @@ export function useGit() {
|
|
76 |
return { ready, gitClone };
|
77 |
}
|
78 |
|
79 |
-
const getFs
|
|
|
|
|
|
|
80 |
promises: {
|
81 |
readFile: async (path: string, options: any) => {
|
82 |
const encoding = options.encoding;
|
@@ -90,6 +109,10 @@ const getFs: (c: WebContainer) => PromiseFsClient = (webcontainer: WebContainer)
|
|
90 |
const relativePath = pathUtils.relative(webcontainer.workdir, path);
|
91 |
console.log('writeFile', { relativePath, data, encoding });
|
92 |
|
|
|
|
|
|
|
|
|
93 |
return await webcontainer.fs.writeFile(relativePath, data, { ...options, encoding });
|
94 |
},
|
95 |
mkdir: async (path: string, options: any) => {
|
@@ -162,7 +185,7 @@ const getFs: (c: WebContainer) => PromiseFsClient = (webcontainer: WebContainer)
|
|
162 |
* For basic usage, lstat can return the same as stat
|
163 |
* since we're not handling symbolic links
|
164 |
*/
|
165 |
-
return await getFs(webcontainer).promises.stat(path);
|
166 |
},
|
167 |
|
168 |
readlink: async (path: string) => {
|
|
|
1 |
import type { WebContainer } from '@webcontainer/api';
|
2 |
+
import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react';
|
3 |
import { webcontainer as webcontainerPromise } from '~/lib/webcontainer';
|
4 |
import git, { type PromiseFsClient } from 'isomorphic-git';
|
5 |
import http from 'isomorphic-git/http/web';
|
6 |
import Cookies from 'js-cookie';
|
7 |
+
import { toast } from 'react-toastify';
|
8 |
|
9 |
export function useGit() {
|
10 |
const [ready, setReady] = useState(false);
|
11 |
const [webcontainer, setWebcontainer] = useState<WebContainer>();
|
12 |
const [fs, setFs] = useState<PromiseFsClient>();
|
13 |
+
const fileData = useRef<Record<string, { data: any; encoding?: string }>>({});
|
14 |
const lookupSavedPassword: (url: string) => any | null = (url: string) => {
|
15 |
try {
|
16 |
// Save updated API keys to cookies with 30 day expiry and secure settings
|
|
|
32 |
};
|
33 |
useEffect(() => {
|
34 |
webcontainerPromise.then((container) => {
|
35 |
+
fileData.current = {};
|
36 |
setWebcontainer(container);
|
37 |
+
setFs(getFs(container, fileData));
|
38 |
setReady(true);
|
39 |
});
|
40 |
}, []);
|
|
|
42 |
const gitClone = useCallback(
|
43 |
async (url: string) => {
|
44 |
if (!webcontainer || !fs || !ready) {
|
45 |
+
throw 'Webcontainer not initialized';
|
46 |
}
|
47 |
|
48 |
+
fileData.current = {};
|
49 |
+
await git.clone({
|
50 |
fs,
|
51 |
http,
|
52 |
dir: webcontainer.workdir,
|
|
|
55 |
singleBranch: true,
|
56 |
corsProxy: 'https://cors.isomorphic-git.org',
|
57 |
onAuth: (url) => {
|
58 |
+
// let domain=url.split("/")[2]
|
59 |
+
|
60 |
let auth = lookupSavedPassword(url);
|
61 |
|
62 |
if (auth) {
|
|
|
73 |
return { cancel: true };
|
74 |
}
|
75 |
},
|
76 |
+
onAuthFailure: (url, _auth) => {
|
77 |
+
toast.error(`Error Authenticating with ${url.split('/')[2]}`);
|
78 |
+
},
|
79 |
});
|
80 |
+
|
81 |
+
const data: Record<string, { data: any; encoding?: string }> = {};
|
82 |
+
|
83 |
+
for (const [key, value] of Object.entries(fileData.current)) {
|
84 |
+
data[key] = value;
|
85 |
+
}
|
86 |
+
|
87 |
+
return { workdir: webcontainer.workdir, data };
|
88 |
},
|
89 |
[webcontainer],
|
90 |
);
|
|
|
92 |
return { ready, gitClone };
|
93 |
}
|
94 |
|
95 |
+
const getFs = (
|
96 |
+
webcontainer: WebContainer,
|
97 |
+
record: MutableRefObject<Record<string, { data: any; encoding?: string }>>,
|
98 |
+
) => ({
|
99 |
promises: {
|
100 |
readFile: async (path: string, options: any) => {
|
101 |
const encoding = options.encoding;
|
|
|
109 |
const relativePath = pathUtils.relative(webcontainer.workdir, path);
|
110 |
console.log('writeFile', { relativePath, data, encoding });
|
111 |
|
112 |
+
if (record.current) {
|
113 |
+
record.current[relativePath] = { data, encoding };
|
114 |
+
}
|
115 |
+
|
116 |
return await webcontainer.fs.writeFile(relativePath, data, { ...options, encoding });
|
117 |
},
|
118 |
mkdir: async (path: string, options: any) => {
|
|
|
185 |
* For basic usage, lstat can return the same as stat
|
186 |
* since we're not handling symbolic links
|
187 |
*/
|
188 |
+
return await getFs(webcontainer, record).promises.stat(path);
|
189 |
},
|
190 |
|
191 |
readlink: async (path: string) => {
|