codacus commited on
Commit
5093905
·
1 Parent(s): bd23686

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 && ImportButtons(importChat)}
 
 
 
 
 
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 { IconButton } from '~/components/ui/IconButton';
2
  import { useGit } from '~/lib/hooks/useGit';
 
 
3
 
4
- export default function GitCloneButton() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- <IconButton
20
- onClick={(e) => {
21
- onClick(e);
22
- }}
23
- className="w-full justify-center"
24
- title="Clone A Git Repo"
25
- >
26
- <span className="mr-2 text-xs lg:text-sm">Clone A Git Repo</span>
27
- <div className="i-ph:git-branch" />
28
- </IconButton>
 
 
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 flex-1 p-4">
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
- return;
43
  }
44
 
45
- const repo = await git.clone({
 
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
- console.log(repo);
 
 
 
 
 
 
 
72
  },
73
  [webcontainer],
74
  );
@@ -76,7 +92,10 @@ export function useGit() {
76
  return { ready, gitClone };
77
  }
78
 
79
- const getFs: (c: WebContainer) => PromiseFsClient = (webcontainer: WebContainer) => ({
 
 
 
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) => {