Skip to content

Commit 1a420a1

Browse files
committed
fix(app): websearch and codesearch tool rendering
1 parent 4c185c7 commit 1a420a1

20 files changed

Lines changed: 213 additions & 2 deletions

File tree

packages/ui/src/components/basic-tool.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,41 @@ export function BasicTool(props: BasicToolProps) {
203203
)
204204
}
205205

206-
export function GenericTool(props: { tool: string; status?: string; hideDetails?: boolean }) {
207-
return <BasicTool icon="mcp" status={props.status} trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
206+
function label(input: Record<string, unknown> | undefined) {
207+
const keys = ["description", "query", "url", "filePath", "path", "pattern", "name"]
208+
return keys.map((key) => input?.[key]).find((value): value is string => typeof value === "string" && value.length > 0)
209+
}
210+
211+
function args(input: Record<string, unknown> | undefined) {
212+
if (!input) return []
213+
const skip = new Set(["description", "query", "url", "filePath", "path", "pattern", "name"])
214+
return Object.entries(input)
215+
.filter(([key]) => !skip.has(key))
216+
.flatMap(([key, value]) => {
217+
if (typeof value === "string") return [`${key}=${value}`]
218+
if (typeof value === "number") return [`${key}=${value}`]
219+
if (typeof value === "boolean") return [`${key}=${value}`]
220+
return []
221+
})
222+
.slice(0, 3)
223+
}
224+
225+
export function GenericTool(props: {
226+
tool: string
227+
status?: string
228+
hideDetails?: boolean
229+
input?: Record<string, unknown>
230+
}) {
231+
return (
232+
<BasicTool
233+
icon="mcp"
234+
status={props.status}
235+
trigger={{
236+
title: `Called \`${props.tool}\``,
237+
subtitle: label(props.input),
238+
args: args(props.input),
239+
}}
240+
hideDetails={props.hideDetails}
241+
/>
242+
)
208243
}

packages/ui/src/components/message-part.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,46 @@
577577
justify-content: center;
578578
}
579579

580+
[data-component="exa-tool-output"] {
581+
width: 100%;
582+
padding-top: 8px;
583+
display: flex;
584+
flex-direction: column;
585+
}
586+
587+
[data-slot="basic-tool-tool-subtitle"].exa-tool-query {
588+
display: block;
589+
max-width: 100%;
590+
overflow: hidden;
591+
text-overflow: ellipsis;
592+
white-space: nowrap;
593+
}
594+
595+
[data-slot="exa-tool-links"] {
596+
display: flex;
597+
flex-direction: column;
598+
gap: 4px;
599+
}
600+
601+
[data-slot="exa-tool-link"] {
602+
display: block;
603+
max-width: 100%;
604+
color: var(--text-interactive-base);
605+
text-decoration: underline;
606+
text-underline-offset: 2px;
607+
overflow: hidden;
608+
text-overflow: ellipsis;
609+
white-space: nowrap;
610+
611+
&:hover {
612+
color: var(--text-interactive-base);
613+
}
614+
615+
&:visited {
616+
color: var(--text-interactive-base);
617+
}
618+
}
619+
580620
[data-component="todos"] {
581621
padding: 10px 0 24px 0;
582622
display: flex;

packages/ui/src/components/message-part.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
243243
title: i18n.t("ui.tool.webfetch"),
244244
subtitle: input.url,
245245
}
246+
case "websearch":
247+
return {
248+
icon: "window-cursor",
249+
title: i18n.t("ui.tool.websearch"),
250+
subtitle: input.query,
251+
}
252+
case "codesearch":
253+
return {
254+
icon: "code",
255+
title: i18n.t("ui.tool.codesearch"),
256+
subtitle: input.query,
257+
}
246258
case "task":
247259
return {
248260
icon: "task",
@@ -303,6 +315,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
303315
}
304316
}
305317

318+
function urls(text: string | undefined) {
319+
if (!text) return []
320+
const seen = new Set<string>()
321+
return [...text.matchAll(/https?:\/\/[^\s<>"'`)\]]+/g)]
322+
.map((item) => item[0].replace(/[),.;:!?]+$/g, ""))
323+
.filter((item) => {
324+
if (seen.has(item)) return false
325+
seen.add(item)
326+
return true
327+
})
328+
}
329+
306330
const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"])
307331
const HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
308332

@@ -598,6 +622,32 @@ function contextToolSummary(parts: ToolPart[]) {
598622
return { read, search, list }
599623
}
600624

625+
function ExaOutput(props: { output?: string }) {
626+
const links = createMemo(() => urls(props.output))
627+
628+
return (
629+
<Show when={links().length > 0}>
630+
<div data-component="exa-tool-output">
631+
<div data-slot="exa-tool-links">
632+
<For each={links()}>
633+
{(url) => (
634+
<a
635+
data-slot="exa-tool-link"
636+
href={url}
637+
target="_blank"
638+
rel="noopener noreferrer"
639+
onClick={(event) => event.stopPropagation()}
640+
>
641+
{url}
642+
</a>
643+
)}
644+
</For>
645+
</div>
646+
</div>
647+
</Show>
648+
)
649+
}
650+
601651
export function registerPartComponent(type: string, component: PartComponent) {
602652
PART_MAPPING[type] = component
603653
}
@@ -1467,6 +1517,58 @@ ToolRegistry.register({
14671517
},
14681518
})
14691519

1520+
ToolRegistry.register({
1521+
name: "websearch",
1522+
render(props) {
1523+
const i18n = useI18n()
1524+
const query = createMemo(() => {
1525+
const value = props.input.query
1526+
if (typeof value !== "string") return ""
1527+
return value
1528+
})
1529+
1530+
return (
1531+
<BasicTool
1532+
{...props}
1533+
icon="window-cursor"
1534+
trigger={{
1535+
title: i18n.t("ui.tool.websearch"),
1536+
subtitle: query(),
1537+
subtitleClass: "exa-tool-query",
1538+
}}
1539+
>
1540+
<ExaOutput output={props.output} />
1541+
</BasicTool>
1542+
)
1543+
},
1544+
})
1545+
1546+
ToolRegistry.register({
1547+
name: "codesearch",
1548+
render(props) {
1549+
const i18n = useI18n()
1550+
const query = createMemo(() => {
1551+
const value = props.input.query
1552+
if (typeof value !== "string") return ""
1553+
return value
1554+
})
1555+
1556+
return (
1557+
<BasicTool
1558+
{...props}
1559+
icon="code"
1560+
trigger={{
1561+
title: i18n.t("ui.tool.codesearch"),
1562+
subtitle: query(),
1563+
subtitleClass: "exa-tool-query",
1564+
}}
1565+
>
1566+
<ExaOutput output={props.output} />
1567+
</BasicTool>
1568+
)
1569+
},
1570+
})
1571+
14701572
ToolRegistry.register({
14711573
name: "task",
14721574
render(props) {

packages/ui/src/i18n/ar.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export const dict = {
9494
"ui.tool.glob": "Glob",
9595
"ui.tool.grep": "Grep",
9696
"ui.tool.webfetch": "جلب الويب",
97+
"ui.tool.websearch": "بحث الويب",
98+
"ui.tool.codesearch": "بحث الكود",
9799
"ui.tool.shell": "Shell",
98100
"ui.tool.patch": "تصحيح",
99101
"ui.tool.todos": "المهام",

packages/ui/src/i18n/br.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export const dict = {
9494
"ui.tool.glob": "Glob",
9595
"ui.tool.grep": "Grep",
9696
"ui.tool.webfetch": "Buscar Web",
97+
"ui.tool.websearch": "Pesquisa na Web",
98+
"ui.tool.codesearch": "Pesquisa de Código",
9799
"ui.tool.shell": "Shell",
98100
"ui.tool.patch": "Patch",
99101
"ui.tool.todos": "Tarefas",

packages/ui/src/i18n/bs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ export const dict = {
9898
"ui.tool.glob": "Glob",
9999
"ui.tool.grep": "Grep",
100100
"ui.tool.webfetch": "Web preuzimanje",
101+
"ui.tool.websearch": "Pretraga weba",
102+
"ui.tool.codesearch": "Pretraga koda",
101103
"ui.tool.shell": "Shell",
102104
"ui.tool.patch": "Patch",
103105
"ui.tool.todos": "Lista zadataka",

packages/ui/src/i18n/da.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export const dict = {
9393
"ui.tool.glob": "Glob",
9494
"ui.tool.grep": "Grep",
9595
"ui.tool.webfetch": "Webhentning",
96+
"ui.tool.websearch": "Websøgning",
97+
"ui.tool.codesearch": "Kodesøgning",
9698
"ui.tool.shell": "Shell",
9799
"ui.tool.patch": "Patch",
98100
"ui.tool.todos": "Opgaver",

packages/ui/src/i18n/de.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export const dict = {
9999
"ui.tool.glob": "Glob",
100100
"ui.tool.grep": "Grep",
101101
"ui.tool.webfetch": "Webabruf",
102+
"ui.tool.websearch": "Websuche",
103+
"ui.tool.codesearch": "Codesuche",
102104
"ui.tool.shell": "Shell",
103105
"ui.tool.patch": "Patch",
104106
"ui.tool.todos": "Aufgaben",

packages/ui/src/i18n/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export const dict: Record<string, string> = {
9595
"ui.tool.glob": "Glob",
9696
"ui.tool.grep": "Grep",
9797
"ui.tool.webfetch": "Webfetch",
98+
"ui.tool.websearch": "Web Search",
99+
"ui.tool.codesearch": "Code Search",
98100
"ui.tool.shell": "Shell",
99101
"ui.tool.patch": "Patch",
100102
"ui.tool.todos": "To-dos",

packages/ui/src/i18n/es.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export const dict = {
9494
"ui.tool.glob": "Glob",
9595
"ui.tool.grep": "Grep",
9696
"ui.tool.webfetch": "Webfetch",
97+
"ui.tool.websearch": "Búsqueda web",
98+
"ui.tool.codesearch": "Búsqueda de código",
9799
"ui.tool.shell": "Shell",
98100
"ui.tool.patch": "Parche",
99101
"ui.tool.todos": "Tareas",

0 commit comments

Comments
 (0)