|
|
@@ -243,6 +243,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
|
|
|
title: i18n.t("ui.tool.webfetch"),
|
|
|
subtitle: input.url,
|
|
|
}
|
|
|
+ case "websearch":
|
|
|
+ return {
|
|
|
+ icon: "window-cursor",
|
|
|
+ title: i18n.t("ui.tool.websearch"),
|
|
|
+ subtitle: input.query,
|
|
|
+ }
|
|
|
+ case "codesearch":
|
|
|
+ return {
|
|
|
+ icon: "code",
|
|
|
+ title: i18n.t("ui.tool.codesearch"),
|
|
|
+ subtitle: input.query,
|
|
|
+ }
|
|
|
case "task":
|
|
|
return {
|
|
|
icon: "task",
|
|
|
@@ -303,6 +315,18 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function urls(text: string | undefined) {
|
|
|
+ if (!text) return []
|
|
|
+ const seen = new Set<string>()
|
|
|
+ return [...text.matchAll(/https?:\/\/[^\s<>"'`)\]]+/g)]
|
|
|
+ .map((item) => item[0].replace(/[),.;:!?]+$/g, ""))
|
|
|
+ .filter((item) => {
|
|
|
+ if (seen.has(item)) return false
|
|
|
+ seen.add(item)
|
|
|
+ return true
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"])
|
|
|
const HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
|
|
|
|
|
|
@@ -598,6 +622,32 @@ function contextToolSummary(parts: ToolPart[]) {
|
|
|
return { read, search, list }
|
|
|
}
|
|
|
|
|
|
+function ExaOutput(props: { output?: string }) {
|
|
|
+ const links = createMemo(() => urls(props.output))
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Show when={links().length > 0}>
|
|
|
+ <div data-component="exa-tool-output">
|
|
|
+ <div data-slot="exa-tool-links">
|
|
|
+ <For each={links()}>
|
|
|
+ {(url) => (
|
|
|
+ <a
|
|
|
+ data-slot="exa-tool-link"
|
|
|
+ href={url}
|
|
|
+ target="_blank"
|
|
|
+ rel="noopener noreferrer"
|
|
|
+ onClick={(event) => event.stopPropagation()}
|
|
|
+ >
|
|
|
+ {url}
|
|
|
+ </a>
|
|
|
+ )}
|
|
|
+ </For>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Show>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
export function registerPartComponent(type: string, component: PartComponent) {
|
|
|
PART_MAPPING[type] = component
|
|
|
}
|
|
|
@@ -1467,6 +1517,58 @@ ToolRegistry.register({
|
|
|
},
|
|
|
})
|
|
|
|
|
|
+ToolRegistry.register({
|
|
|
+ name: "websearch",
|
|
|
+ render(props) {
|
|
|
+ const i18n = useI18n()
|
|
|
+ const query = createMemo(() => {
|
|
|
+ const value = props.input.query
|
|
|
+ if (typeof value !== "string") return ""
|
|
|
+ return value
|
|
|
+ })
|
|
|
+
|
|
|
+ return (
|
|
|
+ <BasicTool
|
|
|
+ {...props}
|
|
|
+ icon="window-cursor"
|
|
|
+ trigger={{
|
|
|
+ title: i18n.t("ui.tool.websearch"),
|
|
|
+ subtitle: query(),
|
|
|
+ subtitleClass: "exa-tool-query",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ExaOutput output={props.output} />
|
|
|
+ </BasicTool>
|
|
|
+ )
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
+ToolRegistry.register({
|
|
|
+ name: "codesearch",
|
|
|
+ render(props) {
|
|
|
+ const i18n = useI18n()
|
|
|
+ const query = createMemo(() => {
|
|
|
+ const value = props.input.query
|
|
|
+ if (typeof value !== "string") return ""
|
|
|
+ return value
|
|
|
+ })
|
|
|
+
|
|
|
+ return (
|
|
|
+ <BasicTool
|
|
|
+ {...props}
|
|
|
+ icon="code"
|
|
|
+ trigger={{
|
|
|
+ title: i18n.t("ui.tool.codesearch"),
|
|
|
+ subtitle: query(),
|
|
|
+ subtitleClass: "exa-tool-query",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <ExaOutput output={props.output} />
|
|
|
+ </BasicTool>
|
|
|
+ )
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
ToolRegistry.register({
|
|
|
name: "task",
|
|
|
render(props) {
|