Skip to content

Commit 5370ae1

Browse files
authored
feat: use ai elements web preview (#2882)
1 parent dfc9618 commit 5370ae1

File tree

11 files changed

+783
-173
lines changed

11 files changed

+783
-173
lines changed

apps/web/client/src/app/project/[id]/_components/canvas/frame/view.tsx

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type PenpalParentMethods,
99
type PromisifiedPendpalChildMethods,
1010
} from '@onlook/penpal';
11+
import { WebPreview, WebPreviewBody } from '@onlook/ui/ai-elements';
1112
import { cn } from '@onlook/ui/utils';
1213
import { observer } from 'mobx-react-lite';
1314
import { WindowMessenger, connect } from 'penpal';
@@ -296,22 +297,23 @@ export const FrameComponent = observer(
296297
}, []);
297298

298299
return (
299-
<iframe
300-
ref={iframeRef}
301-
id={frame.id}
302-
className={cn(
303-
'backdrop-blur-sm transition outline outline-4',
304-
isActiveBranch && 'outline-teal-400',
305-
isActiveBranch && !isSelected && 'outline-dashed',
306-
!isActiveBranch && isInDragSelection && 'outline-teal-500',
307-
)}
308-
src={frame.url}
309-
sandbox="allow-modals allow-forms allow-same-origin allow-scripts allow-popups allow-downloads"
310-
allow="geolocation; microphone; camera; midi; encrypted-media"
311-
style={{ width: frame.dimension.width, height: frame.dimension.height }}
312-
onLoad={setupPenpalConnection}
313-
{...props}
314-
/>
300+
<WebPreview>
301+
<WebPreviewBody
302+
ref={iframeRef}
303+
id={frame.id}
304+
className={cn(
305+
'backdrop-blur-sm transition outline outline-4',
306+
isActiveBranch && 'outline-teal-400',
307+
isActiveBranch && !isSelected && 'outline-dashed',
308+
!isActiveBranch && isInDragSelection && 'outline-teal-500',
309+
)}
310+
src={frame.url}
311+
sandbox="allow-modals allow-forms allow-same-origin allow-scripts allow-popups allow-downloads"
312+
allow="geolocation; microphone; camera; midi; encrypted-media"
313+
style={{ width: frame.dimension.width, height: frame.dimension.height }}
314+
onLoad={setupPenpalConnection}
315+
{...props} />
316+
</WebPreview>
315317
);
316318
}),
317319
);

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-display.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME,
77
type SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS,
88
TERMINAL_COMMAND_TOOL_NAME, TERMINAL_COMMAND_TOOL_PARAMETERS, TODO_WRITE_TOOL_NAME,
9-
type TODO_WRITE_TOOL_PARAMETERS, TYPECHECK_TOOL_NAME,
9+
type TODO_WRITE_TOOL_PARAMETERS,
10+
TYPECHECK_TOOL_NAME,
1011
WEB_SEARCH_TOOL_NAME,
1112
type WEB_SEARCH_TOOL_PARAMETERS,
1213
WRITE_FILE_TOOL_NAME,

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-simple.tsx

Lines changed: 149 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ import {
3333
WRITE_FILE_TOOL_NAME,
3434
type WRITE_FILE_TOOL_PARAMETERS
3535
} from '@onlook/ai';
36+
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@onlook/ui/ai-elements';
3637
import { Icons } from '@onlook/ui/icons';
37-
import { cn } from '@onlook/ui/utils';
3838
import type { ToolUIPart } from 'ai';
3939
import { type z } from 'zod';
4040

@@ -62,10 +62,6 @@ const TOOL_ICONS: Record<string, any> = {
6262
[GLOB_TOOL_NAME]: Icons.MagnifyingGlass,
6363
} as const;
6464

65-
function truncateString(str: string, maxLength: number = 30) {
66-
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
67-
}
68-
6965
export function ToolCallSimple({
7066
toolPart,
7167
className,
@@ -77,149 +73,155 @@ export function ToolCallSimple({
7773
}) {
7874
const toolName = toolPart.type.split('-')[1] ?? '';
7975
const Icon = TOOL_ICONS[toolName] ?? Icons.QuestionMarkCircled;
76+
const title = getLabel(toolName, toolPart);
77+
return (
78+
<Tool className={className}>
79+
<ToolHeader loading={loading} title={title} type={toolPart.type} state={toolPart.state} icon={<Icon className="w-4 h-4 flex-shrink-0" />} />
80+
<ToolContent>
81+
<ToolInput input={toolPart.input} />
82+
<ToolOutput errorText={toolPart.errorText} output={toolPart.output} />
83+
</ToolContent>
84+
</Tool>
85+
)
86+
}
8087

81-
const getLabel = () => {
82-
try {
83-
switch (toolName) {
84-
case TERMINAL_COMMAND_TOOL_NAME:
85-
return 'Terminal';
86-
case LIST_BRANCHES_TOOL_NAME:
87-
return 'Listing branches';
88-
case SEARCH_REPLACE_EDIT_FILE_TOOL_NAME:
89-
const params = toolPart.input as z.infer<typeof SEARCH_REPLACE_EDIT_FILE_TOOL_PARAMETERS>;
90-
if (params?.file_path) {
91-
return 'Editing ' + (params.file_path.split('/').pop() || '');
92-
} else {
93-
return 'Editing file';
94-
}
95-
case SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME:
96-
const params1 = toolPart.input as z.infer<typeof SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS>;
97-
if (params1?.edits) {
98-
return 'Editing ' + (params1.file_path.split('/').pop() || '');
99-
} else {
100-
return 'Editing files';
101-
}
102-
case FUZZY_EDIT_FILE_TOOL_NAME:
103-
const params2 = toolPart.input as z.infer<typeof FUZZY_EDIT_FILE_TOOL_PARAMETERS>;
104-
if (params2?.file_path) {
105-
return 'Editing ' + (params2.file_path.split('/').pop() || '');
106-
} else {
107-
return 'Editing file';
108-
}
109-
case WRITE_FILE_TOOL_NAME:
110-
const params3 = toolPart.input as z.infer<typeof WRITE_FILE_TOOL_PARAMETERS>;
111-
if (params3?.file_path) {
112-
return 'Writing file ' + (params3.file_path.split('/').pop() || '');
113-
} else {
114-
return 'Writing file';
115-
}
116-
case LIST_FILES_TOOL_NAME:
117-
const params4 = toolPart.input as z.infer<typeof LIST_FILES_TOOL_PARAMETERS>;
118-
if (params4?.path) {
119-
return 'Reading directory ' + (params4.path.split('/').pop() || '');
120-
} else {
121-
return 'Reading directory';
122-
}
123-
case READ_FILE_TOOL_NAME:
124-
const params5 = toolPart.input as z.infer<typeof READ_FILE_TOOL_PARAMETERS>;
125-
if (params5?.file_path) {
126-
return 'Reading file ' + (params5.file_path.split('/').pop() || '');
127-
} else {
128-
return 'Reading files';
129-
}
130-
case SCRAPE_URL_TOOL_NAME:
131-
const params6 = toolPart.input as z.infer<typeof SCRAPE_URL_TOOL_PARAMETERS>;
132-
if (params6?.url) {
133-
return 'Visiting ' + (new URL(params6.url).hostname || 'URL');
134-
} else {
135-
return 'Visiting URL';
136-
}
137-
case WEB_SEARCH_TOOL_NAME:
138-
if (toolPart.input && typeof toolPart.input === 'object' && 'query' in toolPart.input) {
139-
const params10 = toolPart.input as z.infer<typeof WEB_SEARCH_TOOL_PARAMETERS>;
140-
const query = params10.query;
141-
return "Searching \"" + truncateString(query) + "\"";
142-
} else {
143-
return 'Searching web';
144-
}
145-
case SANDBOX_TOOL_NAME:
146-
if (toolPart.input && typeof toolPart.input === 'object' && 'command' in toolPart.input) {
147-
return 'Sandbox: ' + toolPart.input?.command;
148-
} else {
149-
return 'Sandbox';
150-
}
151-
case GREP_TOOL_NAME:
152-
if (toolPart.input && typeof toolPart.input === 'object' && 'pattern' in toolPart.input) {
153-
const params11 = toolPart.input as z.infer<typeof GREP_TOOL_PARAMETERS>;
154-
const pattern = params11.pattern;
155-
return 'Searching for ' + truncateString(pattern);
156-
} else {
157-
return 'Searching';
158-
}
159-
case BASH_EDIT_TOOL_NAME:
160-
const params7 = toolPart.input as z.infer<typeof BASH_EDIT_TOOL_PARAMETERS>;
161-
if (params7?.command) {
162-
return 'Running command ' + (params7.command.split('/').pop() || '');
163-
} else {
164-
return 'Running command';
165-
}
166-
case BASH_READ_TOOL_NAME:
167-
const params8 = toolPart.input as z.infer<typeof BASH_READ_TOOL_PARAMETERS>;
168-
if (params8?.command) {
169-
return 'Reading file ' + (params8.command.split('/').pop() || '');
170-
} else {
171-
return 'Reading file';
172-
}
173-
case TODO_WRITE_TOOL_NAME:
174-
const params9 = toolPart.input as z.infer<typeof TODO_WRITE_TOOL_PARAMETERS>;
175-
if (params9?.todos) {
176-
return 'Writing todos ' + (params9?.todos.map((todo: { content: string; status: string; priority: string; }) => todo.content).join(', ') || '');
177-
} else {
178-
return 'Writing todos';
179-
}
180-
case GLOB_TOOL_NAME:
181-
const params12 = toolPart.input as z.infer<typeof GLOB_TOOL_PARAMETERS>;
182-
if (params12?.pattern) {
183-
return 'Searching for ' + truncateString(params12.pattern);
184-
} else {
185-
return 'Searching';
186-
}
187-
case EXIT_PLAN_MODE_TOOL_NAME:
188-
return 'Exiting plan mode';
189-
case READ_STYLE_GUIDE_TOOL_NAME:
190-
return 'Reading style guide';
191-
case ONLOOK_INSTRUCTIONS_TOOL_NAME:
192-
return 'Reading Onlook instructions';
193-
case TYPECHECK_TOOL_NAME:
194-
return 'Checking types';
195-
default:
196-
return toolName?.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
88+
function truncateString(str: string, maxLength: number = 30) {
89+
return str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
90+
}
91+
92+
const getLabel = (toolName: string, toolPart: ToolUIPart) => {
93+
try {
94+
switch (toolName) {
95+
case TERMINAL_COMMAND_TOOL_NAME:
96+
return 'Terminal';
97+
case LIST_BRANCHES_TOOL_NAME:
98+
return 'Listing branches';
99+
case SEARCH_REPLACE_EDIT_FILE_TOOL_NAME: {
100+
const params = toolPart.input as z.infer<typeof SEARCH_REPLACE_EDIT_FILE_TOOL_PARAMETERS>;
101+
if (params?.file_path) {
102+
return 'Editing ' + (params.file_path.split('/').pop() || '');
103+
} else {
104+
return 'Editing file';
105+
}
106+
}
107+
case SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_NAME: {
108+
const params = toolPart.input as z.infer<typeof SEARCH_REPLACE_MULTI_EDIT_FILE_TOOL_PARAMETERS>;
109+
if (params?.edits) {
110+
return 'Editing ' + (params.file_path.split('/').pop() || '');
111+
} else {
112+
return 'Editing files';
113+
}
114+
}
115+
case FUZZY_EDIT_FILE_TOOL_NAME: {
116+
const params = toolPart.input as z.infer<typeof FUZZY_EDIT_FILE_TOOL_PARAMETERS>;
117+
if (params?.file_path) {
118+
return 'Editing ' + (params.file_path.split('/').pop() || '');
119+
} else {
120+
return 'Editing file';
121+
}
197122
}
198-
} catch (error) {
199-
console.error('Error getting label', error);
200-
return toolName?.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
123+
case WRITE_FILE_TOOL_NAME: {
124+
const params = toolPart.input as z.infer<typeof WRITE_FILE_TOOL_PARAMETERS>;
125+
if (params?.file_path) {
126+
return 'Writing file ' + (params.file_path.split('/').pop() || '');
127+
} else {
128+
return 'Writing file';
129+
}
130+
}
131+
case LIST_FILES_TOOL_NAME: {
132+
const params = toolPart.input as z.infer<typeof LIST_FILES_TOOL_PARAMETERS>;
133+
if (params?.path) {
134+
return 'Reading directory ' + (params.path.split('/').pop() || '');
135+
} else {
136+
return 'Reading directory';
137+
}
138+
}
139+
case READ_FILE_TOOL_NAME: {
140+
const params = toolPart.input as z.infer<typeof READ_FILE_TOOL_PARAMETERS>;
141+
if (params?.file_path) {
142+
return 'Reading file ' + (params.file_path.split('/').pop() || '');
143+
} else {
144+
return 'Reading files';
145+
}
146+
}
147+
case SCRAPE_URL_TOOL_NAME: {
148+
const params = toolPart.input as z.infer<typeof SCRAPE_URL_TOOL_PARAMETERS>;
149+
if (params?.url) {
150+
return 'Visiting ' + (new URL(params.url).hostname || 'URL');
151+
} else {
152+
return 'Visiting URL';
153+
}
154+
}
155+
case WEB_SEARCH_TOOL_NAME: {
156+
if (toolPart.input && typeof toolPart.input === 'object' && 'query' in toolPart.input) {
157+
const params = toolPart.input as z.infer<typeof WEB_SEARCH_TOOL_PARAMETERS>;
158+
const query = params.query;
159+
return "Searching \"" + truncateString(query) + "\"";
160+
} else {
161+
return 'Searching web';
162+
}
163+
}
164+
case SANDBOX_TOOL_NAME: {
165+
if (toolPart.input && typeof toolPart.input === 'object' && 'command' in toolPart.input) {
166+
return 'Sandbox: ' + toolPart.input?.command;
167+
} else {
168+
return 'Sandbox';
169+
}
170+
}
171+
case GREP_TOOL_NAME: {
172+
if (toolPart.input && typeof toolPart.input === 'object' && 'pattern' in toolPart.input) {
173+
const params = toolPart.input as z.infer<typeof GREP_TOOL_PARAMETERS>;
174+
const pattern = params.pattern;
175+
return 'Searching for ' + truncateString(pattern);
176+
} else {
177+
return 'Searching';
178+
}
179+
}
180+
case BASH_EDIT_TOOL_NAME: {
181+
const params = toolPart.input as z.infer<typeof BASH_EDIT_TOOL_PARAMETERS>;
182+
if (params?.command) {
183+
return 'Running command ' + (params.command.split('/').pop() || '');
184+
} else {
185+
return 'Running command';
186+
}
187+
}
188+
case BASH_READ_TOOL_NAME: {
189+
const params = toolPart.input as z.infer<typeof BASH_READ_TOOL_PARAMETERS>;
190+
if (params?.command) {
191+
return 'Reading file ' + (params.command.split('/').pop() || '');
192+
} else {
193+
return 'Reading file';
194+
}
195+
}
196+
case TODO_WRITE_TOOL_NAME: {
197+
const params = toolPart.input as z.infer<typeof TODO_WRITE_TOOL_PARAMETERS>;
198+
if (params?.todos) {
199+
return 'Writing todos ' + (params.todos.map((todo: { content: string; status: string; priority: string; }) => todo.content).join(', ') || '');
200+
} else {
201+
return 'Writing todos';
202+
}
203+
}
204+
case GLOB_TOOL_NAME: {
205+
const params = toolPart.input as z.infer<typeof GLOB_TOOL_PARAMETERS>;
206+
if (params?.pattern) {
207+
return 'Searching for ' + truncateString(params.pattern);
208+
} else {
209+
return 'Searching';
210+
}
211+
}
212+
case EXIT_PLAN_MODE_TOOL_NAME:
213+
return 'Exiting plan mode';
214+
case READ_STYLE_GUIDE_TOOL_NAME:
215+
return 'Reading style guide';
216+
case ONLOOK_INSTRUCTIONS_TOOL_NAME:
217+
return 'Reading Onlook instructions';
218+
case TYPECHECK_TOOL_NAME:
219+
return 'Checking types';
220+
default:
221+
return toolName?.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
201222
}
223+
} catch (error) {
224+
console.error('Error getting label', error);
225+
return toolName?.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
202226
}
203-
return (
204-
<div className="flex flex-col gap-2">
205-
<div className={cn('flex items-center gap-2 ml-2 text-foreground-tertiary/80', className)}>
206-
<Icon className="w-4 h-4 flex-shrink-0" />
207-
<span
208-
className={cn(
209-
'text-regularPlus',
210-
loading &&
211-
'bg-gradient-to-l from-white/20 via-white/90 to-white/20 bg-[length:200%_100%] bg-clip-text text-transparent animate-shimmer filter drop-shadow-[0_0_10px_rgba(255,255,255,0.4)]'
212-
)}
213-
>
214-
{getLabel()}
215-
</span>
216-
</div>
217-
{(toolPart.state === 'output-error') && (
218-
<div className="flex items-start gap-2 ml-2 text-red-500 text-small max-h-32 overflow-y-auto border-l">
219-
<Icons.ExclamationTriangle className="w-4 h-4 flex-shrink-0" />
220-
<span className="text-regularPlus">{toolPart.errorText || 'Error calling tool'}</span>
221-
</div>
222-
)}
223-
</div>
224-
);
225-
}
227+
}

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/code-display/search-sources-display.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const SearchSourcesDisplay = observer(({
3434
/>
3535
<div className="flex flex-col">
3636
<span>Searched web</span>
37-
<span className="text-foreground-tertiary text-xs">
37+
<span className="text-foreground-tertiary text-xs truncate">
3838
{query}
3939
</span>
4040
</div>

0 commit comments

Comments
 (0)