mirror of
https://github.com/stackblitz/bolt.new
synced 2025-02-06 04:48:04 +00:00
added context to history
This commit is contained in:
parent
bd236860f9
commit
509390569f
@ -22,7 +22,6 @@ import { ExportChatButton } from '~/components/chat/chatExportAndImport/ExportCh
|
|||||||
import { ImportButtons } from '~/components/chat/chatExportAndImport/ImportButtons';
|
import { ImportButtons } from '~/components/chat/chatExportAndImport/ImportButtons';
|
||||||
import { ExamplePrompts } from '~/components/chat/ExamplePrompts';
|
import { ExamplePrompts } from '~/components/chat/ExamplePrompts';
|
||||||
import GitCloneButton from './GitCloneButton';
|
import GitCloneButton from './GitCloneButton';
|
||||||
import * as Separator from '@radix-ui/react-separator';
|
|
||||||
|
|
||||||
// @ts-ignore TODO: Introduce proper types
|
// @ts-ignore TODO: Introduce proper types
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
@ -256,11 +255,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
<span>Model Settings</span>
|
<span>Model Settings</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GitCloneButton />
|
|
||||||
<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" />
|
|
||||||
<div className="flex items-center gap-3"></div>
|
|
||||||
|
|
||||||
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
|
<div className={isModelSettingsCollapsed ? 'hidden' : ''}>
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
key={provider?.name + ':' + modelList.length}
|
key={provider?.name + ':' + modelList.length}
|
||||||
@ -367,7 +361,12 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!chatStarted && ImportButtons(importChat)}
|
{!chatStarted && (
|
||||||
|
<div className="flex justify-center gap-2">
|
||||||
|
{ImportButtons(importChat)}
|
||||||
|
<GitCloneButton importChat={importChat} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{!chatStarted && ExamplePrompts(sendMessage)}
|
{!chatStarted && ExamplePrompts(sendMessage)}
|
||||||
</div>
|
</div>
|
||||||
<ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
|
<ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
|
||||||
|
@ -1,7 +1,36 @@
|
|||||||
import { IconButton } from '~/components/ui/IconButton';
|
import ignore from 'ignore';
|
||||||
import { useGit } from '~/lib/hooks/useGit';
|
import { useGit } from '~/lib/hooks/useGit';
|
||||||
|
import type { Message } from 'ai';
|
||||||
|
import WithTooltip from '~/components/ui/Tooltip';
|
||||||
|
|
||||||
export default function GitCloneButton() {
|
const IGNORE_PATTERNS = [
|
||||||
|
'node_modules/**',
|
||||||
|
'.git/**',
|
||||||
|
'.github/**',
|
||||||
|
'dist/**',
|
||||||
|
'build/**',
|
||||||
|
'.next/**',
|
||||||
|
'coverage/**',
|
||||||
|
'.cache/**',
|
||||||
|
'.vscode/**',
|
||||||
|
'.idea/**',
|
||||||
|
'**/*.log',
|
||||||
|
'**/.DS_Store',
|
||||||
|
'**/npm-debug.log*',
|
||||||
|
'**/yarn-debug.log*',
|
||||||
|
'**/yarn-error.log*',
|
||||||
|
'**/*lock.json',
|
||||||
|
];
|
||||||
|
|
||||||
|
const ig = ignore().add(IGNORE_PATTERNS);
|
||||||
|
const generateId = () => Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
|
interface GitCloneButtonProps {
|
||||||
|
className?: string;
|
||||||
|
importChat?: (description: string, messages: Message[]) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
|
||||||
const { ready, gitClone } = useGit();
|
const { ready, gitClone } = useGit();
|
||||||
const onClick = async (_e: any) => {
|
const onClick = async (_e: any) => {
|
||||||
if (!ready) {
|
if (!ready) {
|
||||||
@ -11,20 +40,59 @@ export default function GitCloneButton() {
|
|||||||
const repoUrl = prompt('Enter the Git url');
|
const repoUrl = prompt('Enter the Git url');
|
||||||
|
|
||||||
if (repoUrl) {
|
if (repoUrl) {
|
||||||
await gitClone(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');
|
||||||
|
const message: Message = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: `Cloning the repo ${repoUrl} into ${workdir}
|
||||||
|
<boltArtifact id="imported-files" title="Git Cloned Files">
|
||||||
|
${filePaths
|
||||||
|
.map((filePath) => {
|
||||||
|
const { data: content, encoding } = data[filePath];
|
||||||
|
|
||||||
|
if (encoding === 'utf8') {
|
||||||
|
return `<boltAction type="file" filePath="${filePath}">
|
||||||
|
${content}
|
||||||
|
</boltAction>`;
|
||||||
|
} else if (content instanceof Uint8Array) {
|
||||||
|
return `<boltAction type="file" filePath="${filePath}">
|
||||||
|
${textDecoder.decode(content)}
|
||||||
|
</boltAction>`;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n')}
|
||||||
|
</boltArtifact>`,
|
||||||
|
id: generateId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
};
|
||||||
|
console.log(JSON.stringify(message));
|
||||||
|
|
||||||
|
importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, [message]);
|
||||||
|
|
||||||
|
// console.log(files);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<WithTooltip tooltip="Clone A Git Repo">
|
||||||
onClick={(e) => {
|
<button
|
||||||
onClick(e);
|
onClick={(e) => {
|
||||||
}}
|
onClick(e);
|
||||||
className="w-full justify-center"
|
}}
|
||||||
title="Clone A Git Repo"
|
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="mr-2 text-xs lg:text-sm">Clone A Git Repo</span>
|
>
|
||||||
<div className="i-ph:git-branch" />
|
<span className="i-ph:git-branch" />
|
||||||
</IconButton>
|
Clone A Git Repo
|
||||||
|
</button>
|
||||||
|
</WithTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { ImportFolderButton } from '~/components/chat/ImportFolderButton';
|
|||||||
|
|
||||||
export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise<void>) | undefined) {
|
export function ImportButtons(importChat: ((description: string, messages: Message[]) => Promise<void>) | undefined) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center flex-1 p-4">
|
<div className="flex flex-col items-center justify-center w-auto">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
id="chat-import"
|
id="chat-import"
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import type { WebContainer } from '@webcontainer/api';
|
import type { WebContainer } from '@webcontainer/api';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react';
|
||||||
import { webcontainer as webcontainerPromise } from '~/lib/webcontainer';
|
import { webcontainer as webcontainerPromise } from '~/lib/webcontainer';
|
||||||
import git, { type PromiseFsClient } from 'isomorphic-git';
|
import git, { type PromiseFsClient } from 'isomorphic-git';
|
||||||
import http from 'isomorphic-git/http/web';
|
import http from 'isomorphic-git/http/web';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
export function useGit() {
|
export function useGit() {
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
const [webcontainer, setWebcontainer] = useState<WebContainer>();
|
const [webcontainer, setWebcontainer] = useState<WebContainer>();
|
||||||
const [fs, setFs] = useState<PromiseFsClient>();
|
const [fs, setFs] = useState<PromiseFsClient>();
|
||||||
|
const fileData = useRef<Record<string, { data: any; encoding?: string }>>({});
|
||||||
const lookupSavedPassword: (url: string) => any | null = (url: string) => {
|
const lookupSavedPassword: (url: string) => any | null = (url: string) => {
|
||||||
try {
|
try {
|
||||||
// Save updated API keys to cookies with 30 day expiry and secure settings
|
// Save updated API keys to cookies with 30 day expiry and secure settings
|
||||||
@ -30,8 +32,9 @@ export function useGit() {
|
|||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
webcontainerPromise.then((container) => {
|
webcontainerPromise.then((container) => {
|
||||||
|
fileData.current = {};
|
||||||
setWebcontainer(container);
|
setWebcontainer(container);
|
||||||
setFs(getFs(container));
|
setFs(getFs(container, fileData));
|
||||||
setReady(true);
|
setReady(true);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
@ -39,10 +42,11 @@ export function useGit() {
|
|||||||
const gitClone = useCallback(
|
const gitClone = useCallback(
|
||||||
async (url: string) => {
|
async (url: string) => {
|
||||||
if (!webcontainer || !fs || !ready) {
|
if (!webcontainer || !fs || !ready) {
|
||||||
return;
|
throw 'Webcontainer not initialized';
|
||||||
}
|
}
|
||||||
|
|
||||||
const repo = await git.clone({
|
fileData.current = {};
|
||||||
|
await git.clone({
|
||||||
fs,
|
fs,
|
||||||
http,
|
http,
|
||||||
dir: webcontainer.workdir,
|
dir: webcontainer.workdir,
|
||||||
@ -51,6 +55,8 @@ export function useGit() {
|
|||||||
singleBranch: true,
|
singleBranch: true,
|
||||||
corsProxy: 'https://cors.isomorphic-git.org',
|
corsProxy: 'https://cors.isomorphic-git.org',
|
||||||
onAuth: (url) => {
|
onAuth: (url) => {
|
||||||
|
// let domain=url.split("/")[2]
|
||||||
|
|
||||||
let auth = lookupSavedPassword(url);
|
let auth = lookupSavedPassword(url);
|
||||||
|
|
||||||
if (auth) {
|
if (auth) {
|
||||||
@ -67,8 +73,18 @@ export function useGit() {
|
|||||||
return { cancel: true };
|
return { cancel: true };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAuthFailure: (url, _auth) => {
|
||||||
|
toast.error(`Error Authenticating with ${url.split('/')[2]}`);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
console.log(repo);
|
|
||||||
|
const data: Record<string, { data: any; encoding?: string }> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(fileData.current)) {
|
||||||
|
data[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { workdir: webcontainer.workdir, data };
|
||||||
},
|
},
|
||||||
[webcontainer],
|
[webcontainer],
|
||||||
);
|
);
|
||||||
@ -76,7 +92,10 @@ export function useGit() {
|
|||||||
return { ready, gitClone };
|
return { ready, gitClone };
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFs: (c: WebContainer) => PromiseFsClient = (webcontainer: WebContainer) => ({
|
const getFs = (
|
||||||
|
webcontainer: WebContainer,
|
||||||
|
record: MutableRefObject<Record<string, { data: any; encoding?: string }>>,
|
||||||
|
) => ({
|
||||||
promises: {
|
promises: {
|
||||||
readFile: async (path: string, options: any) => {
|
readFile: async (path: string, options: any) => {
|
||||||
const encoding = options.encoding;
|
const encoding = options.encoding;
|
||||||
@ -90,6 +109,10 @@ const getFs: (c: WebContainer) => PromiseFsClient = (webcontainer: WebContainer)
|
|||||||
const relativePath = pathUtils.relative(webcontainer.workdir, path);
|
const relativePath = pathUtils.relative(webcontainer.workdir, path);
|
||||||
console.log('writeFile', { relativePath, data, encoding });
|
console.log('writeFile', { relativePath, data, encoding });
|
||||||
|
|
||||||
|
if (record.current) {
|
||||||
|
record.current[relativePath] = { data, encoding };
|
||||||
|
}
|
||||||
|
|
||||||
return await webcontainer.fs.writeFile(relativePath, data, { ...options, encoding });
|
return await webcontainer.fs.writeFile(relativePath, data, { ...options, encoding });
|
||||||
},
|
},
|
||||||
mkdir: async (path: string, options: any) => {
|
mkdir: async (path: string, options: any) => {
|
||||||
@ -162,7 +185,7 @@ const getFs: (c: WebContainer) => PromiseFsClient = (webcontainer: WebContainer)
|
|||||||
* For basic usage, lstat can return the same as stat
|
* For basic usage, lstat can return the same as stat
|
||||||
* since we're not handling symbolic links
|
* since we're not handling symbolic links
|
||||||
*/
|
*/
|
||||||
return await getFs(webcontainer).promises.stat(path);
|
return await getFs(webcontainer, record).promises.stat(path);
|
||||||
},
|
},
|
||||||
|
|
||||||
readlink: async (path: string) => {
|
readlink: async (path: string) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user