feat(desktop): better task tool rendering

This commit is contained in:
Adam 2025-12-21 05:50:26 -06:00
parent d04a72a4ad
commit 986d12fd20
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
3 changed files with 167 additions and 11 deletions

View file

@ -287,6 +287,44 @@
}
}
[data-component="task-tools"] {
padding: 8px 12px;
display: flex;
flex-direction: column;
gap: 6px;
[data-slot="task-tool-item"] {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-weak);
[data-slot="icon-svg"] {
flex-shrink: 0;
color: var(--icon-weak);
}
}
[data-slot="task-tool-title"] {
font-family: var(--font-family-sans);
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-large);
color: var(--text-weak);
}
[data-slot="task-tool-subtitle"] {
font-family: var(--font-family-sans);
font-size: var(--font-size-small);
font-weight: var(--font-weight-regular);
line-height: var(--line-height-large);
color: var(--text-weaker);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
[data-component="diagnostics"] {
display: flex;
flex-direction: column;

View file

@ -87,6 +87,110 @@ function getDirectory(path: string | undefined) {
return relativizeProjectPaths(_getDirectory(path), data.directory)
}
export function getSessionToolParts(store: ReturnType<typeof useData>["store"], sessionId: string): ToolPart[] {
const messages = store.message[sessionId]?.filter((m) => m.role === "assistant")
if (!messages) return []
const parts: ToolPart[] = []
for (const m of messages) {
const msgParts = store.part[m.id]
if (msgParts) {
for (const p of msgParts) {
if (p && p.type === "tool") parts.push(p as ToolPart)
}
}
}
return parts
}
import type { IconProps } from "./icon"
export type ToolInfo = {
icon: IconProps["name"]
title: string
subtitle?: string
}
export function getToolInfo(tool: string, input: Record<string, any> = {}): ToolInfo {
switch (tool) {
case "read":
return {
icon: "glasses",
title: "Read",
subtitle: input.filePath ? getFilename(input.filePath) : undefined,
}
case "list":
return {
icon: "bullet-list",
title: "List",
subtitle: input.path ? getFilename(input.path) : undefined,
}
case "glob":
return {
icon: "magnifying-glass-menu",
title: "Glob",
subtitle: input.pattern,
}
case "grep":
return {
icon: "magnifying-glass-menu",
title: "Grep",
subtitle: input.pattern,
}
case "webfetch":
return {
icon: "window-cursor",
title: "Webfetch",
subtitle: input.url,
}
case "task":
return {
icon: "task",
title: `${input.subagent_type || "task"} Agent`,
subtitle: input.description,
}
case "bash":
return {
icon: "console",
title: "Shell",
subtitle: input.description,
}
case "edit":
return {
icon: "code-lines",
title: "Edit",
subtitle: input.filePath ? getFilename(input.filePath) : undefined,
}
case "write":
return {
icon: "code-lines",
title: "Write",
subtitle: input.filePath ? getFilename(input.filePath) : undefined,
}
case "todowrite":
return {
icon: "checklist",
title: "To-dos",
}
case "todoread":
return {
icon: "checklist",
title: "Read to-dos",
}
default:
return {
icon: "mcp",
title: tool,
}
}
}
function getToolPartInfo(part: ToolPart): ToolInfo {
const state = part.state as any
const input = state.input || {}
return getToolInfo(part.tool, input)
}
export function registerPartComponent(type: string, component: PartComponent) {
PART_MAPPING[type] = component
}
@ -453,23 +557,37 @@ ToolRegistry.register({
ToolRegistry.register({
name: "task",
render(props) {
const summary = () =>
(props.metadata.summary ?? []) as { id: string; tool: string; state: { status: string; title?: string } }[]
return (
<BasicTool
{...props}
icon="task"
defaultOpen={true}
trigger={{
title: `${props.input.subagent_type || props.tool} Agent`,
titleClass: "capitalize",
subtitle: props.input.description,
}}
>
{/* <Show when={false && props.output}> */}
{/* {(output) => ( */}
{/* <div data-component="tool-output" data-scrollable> */}
{/* <Markdown text={output()} /> */}
{/* </div> */}
{/* )} */}
{/* </Show> */}
<div data-component="tool-output" data-scrollable>
<div data-component="task-tools">
<For each={summary()}>
{(item) => {
const info = getToolInfo(item.tool)
return (
<div data-slot="task-tool-item">
<Icon name={info.icon} size="small" />
<span data-slot="task-tool-title">{info.title}</span>
<Show when={item.state.title}>
<span data-slot="task-tool-subtitle">{item.state.title}</span>
</Show>
</div>
)
}}
</For>
</div>
</div>
</BasicTool>
)
},

View file

@ -12,16 +12,16 @@
/* } */
::-webkit-scrollbar-track {
background: var(--theme-background-panel);
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: var(--theme-border-subtle);
background-color: var(--surface-float-base);
border-radius: var(--radius-md);
}
* {
scrollbar-color: var(--theme-border-subtle) var(--theme-background-panel);
scrollbar-color: var(--surface-float-base) transparent;
}
}