From 79dda4c75097d5ab9d315484022b8475ccbfa3f6 Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Sun, 12 Apr 2026 23:38:38 +0530 Subject: [PATCH] better ui tool call --- .../src/components/ai-elements/tool.tsx | 46 +++--- .../renderer/src/lib/chat-conversation.ts | 145 +++++++++++++++++- 2 files changed, 169 insertions(+), 22 deletions(-) diff --git a/apps/x/apps/renderer/src/components/ai-elements/tool.tsx b/apps/x/apps/renderer/src/components/ai-elements/tool.tsx index 18af8b0e..66feb1c6 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/tool.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/tool.tsx @@ -98,24 +98,33 @@ export const ToolHeader = ({ type, state, ...props -}: ToolHeaderProps) => ( - -
- - - {title ?? type.split("-").slice(1).join("-")} - - {getStatusBadge(state)} -
- -
-); +}: ToolHeaderProps) => { + const displayTitle = title ?? type.split("-").slice(1).join("-") + + return ( + +
+ + + {displayTitle} + +
+
+ {getStatusBadge(state)} + +
+
+ ) +}; export type ToolContentProps = ComponentProps; @@ -215,4 +224,3 @@ export const ToolTabbedContent = ({ ); }; - diff --git a/apps/x/apps/renderer/src/lib/chat-conversation.ts b/apps/x/apps/renderer/src/lib/chat-conversation.ts index d457814e..693961c9 100644 --- a/apps/x/apps/renderer/src/lib/chat-conversation.ts +++ b/apps/x/apps/renderer/src/lib/chat-conversation.ts @@ -249,6 +249,143 @@ const BROWSER_PENDING_LABELS: Record = { wait: 'Waiting for page...', } +const truncateLabel = (value: string, max = 72): string => { + const normalized = value.replace(/\s+/g, ' ').trim() + if (normalized.length <= max) return normalized + return `${normalized.slice(0, Math.max(0, max - 3)).trim()}...` +} + +const safeBrowserString = (value: unknown): string | null => { + return typeof value === 'string' && value.trim() ? value.trim() : null +} + +const parseBrowserUrl = (value: string | null): URL | null => { + if (!value) return null + try { + return new URL(value) + } catch { + return null + } +} + +const getGoogleSearchQuery = (value: string | null): string | null => { + const parsed = parseBrowserUrl(value) + if (!parsed) return null + const hostname = parsed.hostname.replace(/^www\./, '') + if (hostname !== 'google.com' && !hostname.endsWith('.google.com')) return null + if (parsed.pathname !== '/search') return null + const query = parsed.searchParams.get('q')?.trim() + return query ? truncateLabel(query, 56) : null +} + +const formatBrowserTarget = (value: string | null): string | null => { + const parsed = parseBrowserUrl(value) + if (!parsed) { + return value ? truncateLabel(value, 56) : null + } + + const hostname = parsed.hostname.replace(/^www\./, '') + const path = parsed.pathname === '/' ? '' : parsed.pathname + const suffix = parsed.search ? `${path}${parsed.search}` : path + return truncateLabel(`${hostname}${suffix}`, 56) +} + +const sanitizeBrowserDescription = (value: string | null): string | null => { + if (!value) return null + + let text = value + .replace(/^(clicked|typed into|pressed)\s+/i, '') + .replace(/\.$/, '') + .replace(/\s+/g, ' ') + .trim() + + if (!text) return null + + const looksLikeCssNoise = + /(^|[\s"])(body|html)\b/i.test(text) + || /display:|position:|background-color|align-items|justify-content|z-index|var\(--|left:|top:/i.test(text) + || /\.[A-Za-z0-9_-]+\{/.test(text) + + if (looksLikeCssNoise || text.length > 88) { + const quoted = Array.from(text.matchAll(/"([^"]+)"/g)) + .map((match) => match[1]?.trim()) + .find((candidate) => candidate && !/display:|position:|background-color|var\(--/i.test(candidate)) + + if (!quoted) return null + text = `"${truncateLabel(quoted, 44)}"` + } + + if (/^(body|html)\b/i.test(text)) return null + return truncateLabel(text, 64) +} + +const getBrowserSuccessLabel = ( + action: string, + input: Record | undefined, + result: Record | undefined, +): string | null => { + const page = result?.page as Record | undefined + const pageUrl = safeBrowserString(page?.url) + const resultMessage = safeBrowserString(result?.message) + + switch (action) { + case 'open': + return 'Opened browser' + case 'get-state': + return 'Read browser state' + case 'new-tab': { + const query = getGoogleSearchQuery(pageUrl) + if (query) return `Opened search for "${query}"` + const target = formatBrowserTarget(pageUrl) || safeBrowserString(input?.target) + return target ? `Opened ${target}` : 'Opened new tab' + } + case 'switch-tab': + return 'Switched browser tab' + case 'close-tab': + return 'Closed browser tab' + case 'navigate': { + const query = getGoogleSearchQuery(pageUrl) + if (query) return `Searched Google for "${query}"` + const target = formatBrowserTarget(pageUrl) || formatBrowserTarget(safeBrowserString(input?.target)) + return target ? `Opened ${target}` : 'Navigated browser' + } + case 'back': + return 'Went back' + case 'forward': + return 'Went forward' + case 'reload': + return 'Reloaded page' + case 'read-page': { + const title = safeBrowserString(page?.title) + return title ? `Read ${truncateLabel(title, 52)}` : 'Read page' + } + case 'click': { + const detail = sanitizeBrowserDescription(resultMessage) + if (detail) return `Clicked ${detail}` + if (typeof input?.index === 'number') return `Clicked element ${input.index}` + return 'Clicked page element' + } + case 'type': { + const detail = sanitizeBrowserDescription(resultMessage) + if (detail) return `Typed into ${detail}` + if (typeof input?.index === 'number') return `Typed into element ${input.index}` + return 'Typed into page' + } + case 'press': { + const key = safeBrowserString(input?.key) + return key ? `Pressed ${truncateLabel(key, 20)}` : 'Sent key press' + } + case 'scroll': + return `Scrolled ${input?.direction === 'up' ? 'up' : 'down'}` + case 'wait': { + const ms = typeof input?.ms === 'number' ? input.ms : 1000 + return `Waited ${ms}ms` + } + default: + return resultMessage ? truncateLabel(resultMessage, 72) : 'Controlled browser' + } +} + export const getBrowserControlLabel = (tool: ToolCall): string | null => { if (tool.name !== 'browser-control') return null @@ -270,11 +407,13 @@ export const getBrowserControlLabel = (tool: ToolCall): string | null => { } if (result?.success === false) { - return typeof result.error === 'string' ? `Browser error: ${result.error}` : 'Browser action failed' + const error = safeBrowserString(result.error) + return error ? `Browser error: ${truncateLabel(error, 84)}` : 'Browser action failed' } - if (typeof result?.message === 'string' && result.message.trim()) { - return result.message + const label = getBrowserSuccessLabel(action, input, result) + if (label) { + return label } return 'Controlled browser'