better ui tool call

This commit is contained in:
Arjun 2026-04-12 23:38:38 +05:30
parent 7a8dc20f3e
commit 79dda4c750
2 changed files with 169 additions and 22 deletions

View file

@ -98,24 +98,33 @@ export const ToolHeader = ({
type,
state,
...props
}: ToolHeaderProps) => (
<CollapsibleTrigger
className={cn(
"flex w-full items-center justify-between gap-4 p-3",
className
)}
{...props}
>
<div className="flex items-center gap-2">
<WrenchIcon className="size-4 text-muted-foreground" />
<span className="font-medium text-sm">
{title ?? type.split("-").slice(1).join("-")}
</span>
{getStatusBadge(state)}
</div>
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</CollapsibleTrigger>
);
}: ToolHeaderProps) => {
const displayTitle = title ?? type.split("-").slice(1).join("-")
return (
<CollapsibleTrigger
className={cn(
"flex w-full items-center justify-between gap-4 p-3",
className
)}
{...props}
>
<div className="flex min-w-0 flex-1 items-center gap-2">
<WrenchIcon className="size-4 shrink-0 text-muted-foreground" />
<span
className="min-w-0 flex-1 truncate text-left font-medium text-sm"
title={displayTitle}
>
{displayTitle}
</span>
</div>
<div className="flex shrink-0 items-center gap-3">
{getStatusBadge(state)}
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</div>
</CollapsibleTrigger>
)
};
export type ToolContentProps = ComponentProps<typeof CollapsibleContent>;
@ -215,4 +224,3 @@ export const ToolTabbedContent = ({
</div>
);
};

View file

@ -249,6 +249,143 @@ const BROWSER_PENDING_LABELS: Record<string, string> = {
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<string, unknown> | undefined,
result: Record<string, unknown> | undefined,
): string | null => {
const page = result?.page as Record<string, unknown> | 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'