Skip to content

Commit 99ac6fa

Browse files
authored
Improve chatting with project (#1644)
* Add project context icon * Allow chatting without selecting elements * Better tool use * Read files * Add Onlook tool * Clean up
1 parent a0b8dcb commit 99ac6fa

File tree

7 files changed

+68
-23
lines changed

7 files changed

+68
-23
lines changed

apps/studio/src/lib/editor/engine/chat/context.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,23 @@ export class ChatContext {
2424
() => this.editorEngine.elements.selected,
2525
() => this.getChatContext().then((context) => (this.context = context)),
2626
);
27+
reaction(
28+
() => this.projectsManager.project?.folderPath,
29+
(folderPath) => {
30+
if (folderPath) {
31+
this.getChatContext().then((context) => (this.context = context));
32+
}
33+
},
34+
);
2735
}
2836

2937
async getChatContext(): Promise<ChatMessageContext[]> {
3038
const selected = this.editorEngine.elements.selected;
31-
if (selected.length === 0) {
32-
return [];
33-
}
3439
const fileNames = new Set<string>();
35-
const highlightedContext = await this.getHighlightedContext(selected, fileNames);
40+
let highlightedContext: HighlightMessageContext[] = [];
41+
if (selected.length) {
42+
highlightedContext = await this.getHighlightedContext(selected, fileNames);
43+
}
3644
const fileContext = await this.getFileContext(fileNames);
3745
const imageContext = await this.getImageContext();
3846
const projectContext = await this.getProjectContext();

apps/studio/src/lib/editor/engine/chat/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ export class ChatManager {
111111
messages,
112112
requestType,
113113
);
114-
this.stream.clear();
115-
this.isWaiting = false;
116114
if (res) {
117115
this.handleChatResponse(res, requestType);
118116
} else {
119117
console.error('No stream response found');
120118
}
119+
this.stream.clear();
120+
this.isWaiting = false;
121121
sendAnalytics('receive chat response');
122122
}
123123

apps/studio/src/routes/editor/EditPanel/ChatTab/ChatInput.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,7 @@ export const ChatInput = observer(() => {
295295
disabled={disabled}
296296
placeholder={
297297
disabled
298-
? projectsManager.runner?.isRunning ||
299-
projectsManager.runner?.isStarting
300-
? t('editor.panels.edit.tabs.chat.emptyState')
301-
: t('editor.panels.edit.tabs.chat.emptyStateStart')
298+
? t('editor.panels.edit.tabs.chat.emptyState')
302299
: t('editor.panels.edit.tabs.chat.input.placeholder')
303300
}
304301
className={cn(

apps/studio/src/routes/editor/EditPanel/ChatTab/ContextPills/helpers.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { getTruncatedFileName } from '@/lib/utils';
2-
import { type ChatMessageContext } from '@onlook/models/chat';
2+
import { MessageContextType, type ChatMessageContext } from '@onlook/models/chat';
33
import { Icons } from '@onlook/ui/icons/index';
44
import React from 'react';
55
import NodeIcon from '../../../LayersPanel/Tree/NodeIcon';
6+
import { assertNever } from '/common/helpers';
67

78
export function getTruncatedName(context: ChatMessageContext) {
89
let name = context.displayName;
@@ -18,19 +19,24 @@ export function getTruncatedName(context: ChatMessageContext) {
1819
export function getContextIcon(context: ChatMessageContext) {
1920
let icon: React.ComponentType | React.ReactElement | null = null;
2021
switch (context.type) {
21-
case 'file':
22+
case MessageContextType.FILE:
2223
icon = Icons.File;
2324
break;
24-
case 'image':
25+
case MessageContextType.IMAGE:
2526
icon = Icons.Image;
2627
break;
27-
case 'error':
28+
case MessageContextType.ERROR:
2829
icon = Icons.InfoCircled;
2930
break;
30-
case 'highlight':
31+
case MessageContextType.HIGHLIGHT:
3132
return (
3233
<NodeIcon tagName={context.displayName} iconClass="w-3 h-3 ml-1 mr-2 flex-none" />
3334
);
35+
case MessageContextType.PROJECT:
36+
icon = Icons.Cube;
37+
break;
38+
default:
39+
assertNever(context);
3440
}
3541
if (icon) {
3642
return React.createElement(icon);

packages/ai/src/prompt/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PROJECT_ROOT_SIGNATURE } from './signatures';
22

3-
const reactRole = `Act as an expert React and Tailwind developer. Your goal is to analyze the provided code, understand the requested modifications, and implement them accurately while explaining your thought process.`;
3+
const reactRole = `You are running in Onlook to help users develop their app. Act as an expert React, Next.js and Tailwind developer. Your goal is to analyze the provided code, understand the requested modifications, and implement them accurately while explaining your thought process.`;
44

55
const lazy = `You are diligent and tireless! You NEVER leave comments describing code without implementing it! You always COMPLETELY IMPLEMENT the needed code!`;
66

packages/ai/src/prompt/onlook.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const ONLOOK_PROMPT = `You are currently running Onlook, it is an electron app that allows users to run their own React Next.js app on their machine, chat, style and deploy it.
2+
The available project folder is where the user's Next.js app lives. This is the app they are running through Onlook.
3+
4+
Onlook has a few features users could use:
5+
6+
# Chat panel
7+
To chat with an element, select it through the UI. Let the users know what you can do through the chat whether it's with tools or otherwise.
8+
9+
# Style editor
10+
There is a figma-like design panel on the right side of the app where they can change any styles. Let the users know they can also right click to get more actions.
11+
12+
# Bottom tool bar
13+
There is a bottom toolbar with controls users can use for things like adding new elements, starting, and stopping their project, and even open up the terminal.
14+
15+
# Publish
16+
Once done, the user can also publish their project on the top right corner of the app. Either to a preview link or to a custom domain if they own one
17+
18+
# Pro plan
19+
Upgrading to the Pro plan gives unlimited messages, more than 1 custom domain, among other perks.`;

packages/ai/src/tools/index.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { anthropic } from '@ai-sdk/anthropic';
22
import { tool, type ToolSet } from 'ai';
33
import { readFile } from 'fs/promises';
4+
import { ONLOOK_PROMPT } from 'src/prompt/onlook';
45
import { z } from 'zod';
56
import { getAllFiles } from './helpers';
67

@@ -22,21 +23,34 @@ export const listFilesTool = tool({
2223
},
2324
});
2425

25-
export const readFileTool = tool({
26-
description: 'Read the contents of a file',
26+
export const readFilesTool = tool({
27+
description: 'Read the contents of files',
2728
parameters: z.object({
28-
path: z.string().describe('The absolute path to the file to read'),
29+
paths: z.array(z.string()).describe('The absolute paths to the files to read'),
2930
}),
30-
execute: async ({ path }) => {
31+
execute: async ({ paths }) => {
3132
try {
32-
const file = await readFile(path, 'utf8');
33-
return file;
33+
const files = await Promise.all(
34+
paths.map(async (path) => {
35+
const file = await readFile(path, 'utf8');
36+
return { path, content: file };
37+
}),
38+
);
39+
return files;
3440
} catch (error) {
3541
return `Error: ${error instanceof Error ? error.message : error}`;
3642
}
3743
},
3844
});
3945

46+
export const onlookInstructionsTool = tool({
47+
description: 'Get the instructions for the Onlook AI',
48+
parameters: z.object({}),
49+
execute: async () => {
50+
return ONLOOK_PROMPT;
51+
},
52+
});
53+
4054
// https://docs.anthropic.com/en/docs/agents-and-tools/computer-use#understand-anthropic-defined-tools
4155
// https://sdk.vercel.ai/docs/guides/computer-use#get-started-with-computer-use
4256

@@ -118,5 +132,6 @@ export const getStrReplaceEditorTool = (handlers: FileOperationHandlers) => {
118132

119133
export const chatToolSet: ToolSet = {
120134
list_files: listFilesTool,
121-
read_file: readFileTool,
135+
read_files: readFilesTool,
136+
onlook_instructions: onlookInstructionsTool,
122137
};

0 commit comments

Comments
 (0)