[UX] click shortcut in chat to go to source file in workbench

This commit is contained in:
Hugo SANSON 2024-11-18 22:37:33 +01:00 committed by Hgosansn
parent 233d22e080
commit d419a3c4b5
6 changed files with 47 additions and 15 deletions

View File

@ -7,6 +7,7 @@ import type { ActionState } from '~/lib/runtime/action-runner';
import { workbenchStore } from '~/lib/stores/workbench'; import { workbenchStore } from '~/lib/stores/workbench';
import { classNames } from '~/utils/classNames'; import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings'; import { cubicEasingFn } from '~/utils/easings';
import { WORK_DIR } from '~/utils/constants';
const highlighterOptions = { const highlighterOptions = {
langs: ['shell'], langs: ['shell'],
@ -129,6 +130,14 @@ const actionVariants = {
visible: { opacity: 1, y: 0 }, visible: { opacity: 1, y: 0 },
}; };
function openArtifactInWorkbench(filePath: any) {
if (workbenchStore.currentView.get() !== 'code') {
workbenchStore.currentView.set('code');
}
workbenchStore.setSelectedFile(`${WORK_DIR}/${filePath}`);
}
const ActionList = memo(({ actions }: ActionListProps) => { const ActionList = memo(({ actions }: ActionListProps) => {
return ( return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}> <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}>
@ -169,7 +178,10 @@ const ActionList = memo(({ actions }: ActionListProps) => {
{type === 'file' ? ( {type === 'file' ? (
<div> <div>
Create{' '} Create{' '}
<code className="bg-bolt-elements-artifacts-inlineCode-background text-bolt-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md"> <code
className="bg-bolt-elements-artifacts-inlineCode-background text-bolt-elements-artifacts-inlineCode-text px-1.5 py-1 rounded-md text-bolt-elements-item-contentAccent hover:underline cursor-pointer"
onClick={() => openArtifactInWorkbench(action.filePath)}
>
{action.filePath} {action.filePath}
</code> </code>
</div> </div>

View File

@ -180,8 +180,8 @@ export const Workbench = memo(({ chatStarted, isStreaming }: WorkspaceProps) =>
alert("GitHub token is required. Push to GitHub cancelled."); alert("GitHub token is required. Push to GitHub cancelled.");
return; return;
} }
workbenchStore.pushToGitHub(repoName, githubUsername, githubToken); workbenchStore.pushToGitHub(repoName, githubUsername, githubToken);
}} }}
> >
<div className="i-ph:github-logo" /> <div className="i-ph:github-logo" />

View File

@ -39,20 +39,20 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
- rm: Remove files - rm: Remove files
- rmdir: Remove empty directories - rmdir: Remove empty directories
- touch: Create empty file/update timestamp - touch: Create empty file/update timestamp
System Information: System Information:
- hostname: Show system name - hostname: Show system name
- ps: Display running processes - ps: Display running processes
- pwd: Print working directory - pwd: Print working directory
- uptime: Show system uptime - uptime: Show system uptime
- env: Environment variables - env: Environment variables
Development Tools: Development Tools:
- node: Execute Node.js code - node: Execute Node.js code
- python3: Run Python scripts - python3: Run Python scripts
- code: VSCode operations - code: VSCode operations
- jq: Process JSON - jq: Process JSON
Other Utilities: Other Utilities:
- curl, head, sort, tail, clear, which, export, chmod, scho, hostname, kill, ln, xxd, alias, false, getconf, true, loadenv, wasm, xdg-open, command, exit, source - curl, head, sort, tail, clear, which, export, chmod, scho, hostname, kill, ln, xxd, alias, false, getconf, true, loadenv, wasm, xdg-open, command, exit, source
</system_constraints> </system_constraints>
@ -88,7 +88,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
Example: Example:
<${MODIFICATIONS_TAG_NAME}> <${MODIFICATIONS_TAG_NAME}>
<diff path="/home/project/src/main.js"> <diff path="${WORK_DIR}/src/main.js">
@@ -2,7 +2,10 @@ @@ -2,7 +2,10 @@
return a + b; return a + b;
} }
@ -103,7 +103,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
+ +
+console.log('The End'); +console.log('The End');
</diff> </diff>
<file path="/home/project/package.json"> <file path="${WORK_DIR}/package.json">
// full file content here // full file content here
</file> </file>
</${MODIFICATIONS_TAG_NAME}> </${MODIFICATIONS_TAG_NAME}>
@ -124,7 +124,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
2. Create TodoList and TodoItem components 2. Create TodoList and TodoItem components
3. Implement localStorage for persistence 3. Implement localStorage for persistence
4. Add CRUD operations 4. Add CRUD operations
Let's start now. Let's start now.
[Rest of response...]" [Rest of response...]"
@ -134,7 +134,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
1. Check network requests 1. Check network requests
2. Verify API endpoint format 2. Verify API endpoint format
3. Examine error handling 3. Examine error handling
[Rest of response...]" [Rest of response...]"
</chain_of_thought_instructions> </chain_of_thought_instructions>

View File

@ -14,6 +14,7 @@ import { saveAs } from 'file-saver';
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest"; import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
import * as nodePath from 'node:path'; import * as nodePath from 'node:path';
import type { WebContainerProcess } from '@webcontainer/api'; import type { WebContainerProcess } from '@webcontainer/api';
import { extractRelativePath } from '~/utils/diff';
export interface ArtifactState { export interface ArtifactState {
id: string; id: string;
@ -312,8 +313,7 @@ export class WorkbenchStore {
for (const [filePath, dirent] of Object.entries(files)) { for (const [filePath, dirent] of Object.entries(files)) {
if (dirent?.type === 'file' && !dirent.isBinary) { if (dirent?.type === 'file' && !dirent.isBinary) {
// remove '/home/project/' from the beginning of the path const relativePath = extractRelativePath(filePath);
const relativePath = filePath.replace(/^\/home\/project\//, '');
// split the path into segments // split the path into segments
const pathSegments = relativePath.split('/'); const pathSegments = relativePath.split('/');
@ -343,7 +343,7 @@ export class WorkbenchStore {
for (const [filePath, dirent] of Object.entries(files)) { for (const [filePath, dirent] of Object.entries(files)) {
if (dirent?.type === 'file' && !dirent.isBinary) { if (dirent?.type === 'file' && !dirent.isBinary) {
const relativePath = filePath.replace(/^\/home\/project\//, ''); const relativePath = extractRelativePath(filePath);
const pathSegments = relativePath.split('/'); const pathSegments = relativePath.split('/');
let currentHandle = targetHandle; let currentHandle = targetHandle;
@ -417,7 +417,7 @@ export class WorkbenchStore {
content: Buffer.from(dirent.content).toString('base64'), content: Buffer.from(dirent.content).toString('base64'),
encoding: 'base64', encoding: 'base64',
}); });
return { path: filePath.replace(/^\/home\/project\//, ''), sha: blob.sha }; return { path: extractRelativePath(filePath), sha: blob.sha };
} }
}) })
); );

11
app/utils/diff.spec.ts Normal file
View File

@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import { extractRelativePath } from './diff';
import { WORK_DIR } from './constants';
describe('Diff', () => {
it('should strip out Work_dir', () => {
const filePath = `${WORK_DIR}/index.js`;
const result = extractRelativePath(filePath);
expect(result).toBe('index.js');
});
});

View File

@ -1,6 +1,6 @@
import { createTwoFilesPatch } from 'diff'; import { createTwoFilesPatch } from 'diff';
import type { FileMap } from '~/lib/stores/files'; import type { FileMap } from '~/lib/stores/files';
import { MODIFICATIONS_TAG_NAME } from './constants'; import { MODIFICATIONS_TAG_NAME, WORK_DIR } from './constants';
export const modificationsRegex = new RegExp( export const modificationsRegex = new RegExp(
`^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`, `^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`,
@ -75,6 +75,15 @@ export function diffFiles(fileName: string, oldFileContent: string, newFileConte
return unifiedDiff; return unifiedDiff;
} }
const regex = new RegExp(`^${WORK_DIR}\/`);
/**
* Strips out the work directory from the file path.
*/
export function extractRelativePath(filePath: string) {
return filePath.replace(regex, '');
}
/** /**
* Converts the unified diff to HTML. * Converts the unified diff to HTML.
* *