Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [UX] click shortcut in chat to go to source file in workbench #330

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion app/components/chat/Artifact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ActionState } from '~/lib/runtime/action-runner';
import { workbenchStore } from '~/lib/stores/workbench';
import { classNames } from '~/utils/classNames';
import { cubicEasingFn } from '~/utils/easings';
import { WORK_DIR } from '~/utils/constants';

const highlighterOptions = {
langs: ['shell'],
Expand Down Expand Up @@ -129,6 +130,14 @@ const actionVariants = {
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) => {
return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}>
Expand Down Expand Up @@ -169,7 +178,10 @@ const ActionList = memo(({ actions }: ActionListProps) => {
{type === 'file' ? (
<div>
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}
</code>
</div>
Expand Down
4 changes: 2 additions & 2 deletions app/lib/.server/llm/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
Example:

<${MODIFICATIONS_TAG_NAME}>
<diff path="/home/project/src/main.js">
<diff path="${WORK_DIR}/src/main.js">
@@ -2,7 +2,10 @@
return a + b;
}
Expand All @@ -103,7 +103,7 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
+
+console.log('The End');
</diff>
<file path="/home/project/package.json">
<file path="${WORK_DIR}/package.json">
// full file content here
</file>
</${MODIFICATIONS_TAG_NAME}>
Expand Down
8 changes: 4 additions & 4 deletions app/lib/stores/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { saveAs } from 'file-saver';
import { Octokit, type RestEndpointMethodTypes } from "@octokit/rest";
import * as nodePath from 'node:path';
import type { WebContainerProcess } from '@webcontainer/api';
import { extractRelativePath } from '~/utils/diff';

export interface ArtifactState {
id: string;
Expand Down Expand Up @@ -312,8 +313,7 @@ export class WorkbenchStore {

for (const [filePath, dirent] of Object.entries(files)) {
if (dirent?.type === 'file' && !dirent.isBinary) {
// remove '/home/project/' from the beginning of the path
const relativePath = filePath.replace(/^\/home\/project\//, '');
const relativePath = extractRelativePath(filePath);

// split the path into segments
const pathSegments = relativePath.split('/');
Expand Down Expand Up @@ -343,7 +343,7 @@ export class WorkbenchStore {

for (const [filePath, dirent] of Object.entries(files)) {
if (dirent?.type === 'file' && !dirent.isBinary) {
const relativePath = filePath.replace(/^\/home\/project\//, '');
const relativePath = extractRelativePath(filePath);
const pathSegments = relativePath.split('/');
let currentHandle = targetHandle;

Expand Down Expand Up @@ -417,7 +417,7 @@ export class WorkbenchStore {
content: Buffer.from(dirent.content).toString('base64'),
encoding: 'base64',
});
return { path: filePath.replace(/^\/home\/project\//, ''), sha: blob.sha };
return { path: extractRelativePath(filePath), sha: blob.sha };
}
})
);
Expand Down
11 changes: 11 additions & 0 deletions app/utils/diff.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
11 changes: 10 additions & 1 deletion app/utils/diff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createTwoFilesPatch } from 'diff';
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(
`^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`,
Expand Down Expand Up @@ -75,6 +75,15 @@ export function diffFiles(fileName: string, oldFileContent: string, newFileConte
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.
*
Expand Down