mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
fix(vscode): polish chat tool call previews
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Polish chat tool-call previews for shell, search, and background-process output.
|
||||
@@ -35,14 +35,23 @@
|
||||
|
||||
[data-slot="basic-tool-tool-subtitle"] {
|
||||
font-size: var(--kilo-font-size-12);
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-title"] {
|
||||
font-size: var(--kilo-font-size-14);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
font-weight: var(--font-weight-regular);
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="message-part-title-text"] {
|
||||
font-size: var(--kilo-font-size-14);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
font-weight: var(--font-weight-regular);
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-arg"] {
|
||||
font-size: var(--kilo-font-size-12);
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="message-part-title-filename"] {
|
||||
@@ -108,7 +117,7 @@ html[data-theme="kilo-vscode"] [data-component="tool-trigger"] {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--icon-base);
|
||||
color: var(--text-weak);
|
||||
|
||||
[data-component="icon"] {
|
||||
display: flex;
|
||||
@@ -118,27 +127,71 @@ html[data-theme="kilo-vscode"] [data-component="tool-trigger"] {
|
||||
|
||||
/* Card styling for tool calls in the VS Code sidebar theme */
|
||||
html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] {
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-weak-base, var(--vscode-panel-border));
|
||||
overflow: hidden;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
overflow: visible;
|
||||
|
||||
/* Header trigger — add bottom border when the collapsible is open */
|
||||
[data-component="collapsible"].tool-collapsible {
|
||||
gap: 0px;
|
||||
gap: 8px;
|
||||
border-radius: 0;
|
||||
|
||||
[data-slot="collapsible-trigger"] {
|
||||
padding: 0 8px;
|
||||
height: 36px;
|
||||
background-color: var(--surface-inset-base, var(--vscode-sideBar-background));
|
||||
margin-inline: -6px;
|
||||
width: calc(100% + 12px);
|
||||
padding: 2px 6px;
|
||||
min-height: 24px;
|
||||
height: auto;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-inset-base-hover, var(--vscode-list-hoverBackground));
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-trigger-content"] {
|
||||
align-self: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-info-structured"],
|
||||
[data-slot="basic-tool-tool-info-main"] {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-title"]
|
||||
+ :is(
|
||||
[data-slot="basic-tool-tool-subtitle"],
|
||||
[data-slot="basic-tool-tool-arg"],
|
||||
[data-slot="message-part-meta-line"],
|
||||
[data-slot="webfetch-meta"],
|
||||
[data-component="shell-submessage"]
|
||||
)::before {
|
||||
content: "·";
|
||||
margin-right: 6px;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-tool-title"],
|
||||
[data-slot="basic-tool-tool-subtitle"],
|
||||
[data-slot="basic-tool-tool-arg"],
|
||||
[data-slot="message-part-title-text"],
|
||||
[data-slot="message-part-title-filename"],
|
||||
[data-slot="message-part-directory-inline"],
|
||||
[data-slot="message-part-meta-line"] {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"][aria-expanded="true"] {
|
||||
border-bottom: 1px solid var(--border-weak-base, var(--vscode-panel-border));
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"]:has([data-slot="collapsible-arrow"]) {
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
}
|
||||
|
||||
/* Arrow always visible (no fade-on-hover) */
|
||||
@@ -146,13 +199,32 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Arrow: DOWN when collapsed, UP when expanded (matches question-dock toggle) */
|
||||
/* Arrow: RIGHT when collapsed, DOWN when expanded. */
|
||||
[data-slot="collapsible-arrow-icon"] {
|
||||
transform: translateZ(0) rotate(0deg);
|
||||
transform: translateZ(0) rotate(-90deg);
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"][aria-expanded="true"] [data-slot="collapsible-arrow-icon"] {
|
||||
transform: translateZ(0) rotate(180deg);
|
||||
transform: translateZ(0) rotate(0deg);
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"]:has([data-slot="collapsible-arrow"]):hover {
|
||||
background-color: var(--vscode-list-hoverBackground, var(--surface-raised-base-hover));
|
||||
|
||||
[data-slot="basic-tool-tool-title"],
|
||||
[data-slot="basic-tool-tool-subtitle"],
|
||||
[data-slot="basic-tool-tool-arg"],
|
||||
[data-slot="message-part-title-text"],
|
||||
[data-slot="message-part-title-filename"],
|
||||
[data-slot="message-part-directory-inline"],
|
||||
[data-slot="message-part-meta-line"] {
|
||||
color: var(--vscode-list-hoverForeground, var(--vscode-foreground, var(--text-strong)));
|
||||
}
|
||||
|
||||
[data-slot="basic-tool-icon"],
|
||||
[data-slot="collapsible-arrow-icon"] {
|
||||
color: var(--vscode-list-hoverForeground, var(--vscode-foreground, var(--icon-base)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +246,76 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
[data-component="context-tool-group-trigger"] {
|
||||
min-height: 20px;
|
||||
|
||||
[data-slot="context-tool-group-title"] {
|
||||
gap: 6px;
|
||||
font-size: var(--kilo-font-size-12);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: 16px;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="context-tool-group-summary"] {
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="context-tool-group-summary"]::before {
|
||||
content: "·";
|
||||
margin-right: 6px;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="context-tool-expanded-list"] {
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
border: 1px solid var(--border-weak-base);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--vscode-terminal-background, var(--vscode-panel-background)) 92%,
|
||||
black 8%
|
||||
);
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="context-tool-expanded-row"],
|
||||
[data-component="context-tool-rolling-row"] {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
align-items: baseline;
|
||||
column-gap: 8px;
|
||||
min-height: 20px;
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
[data-component="context-tool-expanded-row"] {
|
||||
padding: 2px 12px;
|
||||
}
|
||||
|
||||
[data-slot="context-tool-expanded-action"],
|
||||
[data-slot="context-tool-rolling-action"] {
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="context-tool-expanded-detail"],
|
||||
[data-slot="context-tool-rolling-detail"] {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--vscode-terminal-foreground, var(--vscode-editor-foreground));
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
}
|
||||
|
||||
/* Expandable tool output content */
|
||||
[data-component="tool-output"] {
|
||||
padding: 8px 12px;
|
||||
@@ -186,4 +328,42 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
[data-component="tool-output"][data-variant="preview"] {
|
||||
max-height: 240px;
|
||||
padding: 8px 12px 10px;
|
||||
border: 1px solid var(--border-weak-base);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--vscode-terminal-background, var(--vscode-panel-background)) 92%,
|
||||
black 8%
|
||||
);
|
||||
color: var(--vscode-terminal-foreground, var(--vscode-editor-foreground));
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: var(--line-height-large);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-component="markdown"] {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
line-height: inherit;
|
||||
white-space: inherit;
|
||||
overflow-wrap: inherit;
|
||||
}
|
||||
|
||||
[data-component="markdown"] > *,
|
||||
[data-component="markdown"] p,
|
||||
[data-component="markdown"] pre {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* VS Code theme: tool-collapsible arrows point DOWN (collapsed) and UP (expanded), never right */
|
||||
/* VS Code theme: tool-collapsible arrows point RIGHT when collapsed and DOWN when expanded. */
|
||||
html[data-theme="kilo-vscode"] [data-component="collapsible"].tool-collapsible {
|
||||
[data-slot="collapsible-arrow-icon"] {
|
||||
transform: translateZ(0) rotate(0deg);
|
||||
transform: translateZ(0) rotate(-90deg);
|
||||
}
|
||||
|
||||
[data-slot="collapsible-trigger"][aria-expanded="true"] [data-slot="collapsible-arrow-icon"] {
|
||||
transform: translateZ(0) rotate(180deg);
|
||||
transform: translateZ(0) rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,23 +210,68 @@
|
||||
|
||||
html[data-theme="kilo-vscode"] [data-component="bash-output"] {
|
||||
border-radius: 0;
|
||||
background: var(--vscode-terminal-background, var(--vscode-panel-background));
|
||||
border: 0;
|
||||
background: transparent;
|
||||
overflow: visible;
|
||||
|
||||
[data-slot="bash-terminal"] {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--vscode-terminal-background, var(--vscode-panel-background)) 92%,
|
||||
black 8%
|
||||
);
|
||||
}
|
||||
|
||||
[data-slot="bash-pre"] code {
|
||||
color: var(--vscode-terminal-foreground, var(--vscode-editor-foreground));
|
||||
}
|
||||
|
||||
[data-slot="bash-divider"] {
|
||||
background: var(--vscode-panel-border, var(--border-weak-base));
|
||||
}
|
||||
}
|
||||
|
||||
/* Bash tool: section layout (command + output) */
|
||||
/* Bash tool: static terminal preview with separate command/output surfaces. */
|
||||
[data-component="bash-output"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] [data-slot="bash-terminal"] {
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--border-weak-base);
|
||||
background: var(--surface-raised-base);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] [data-slot="bash-section"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] [data-slot="bash-section"][data-kind="command"] {
|
||||
display: grid;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
align-items: start;
|
||||
column-gap: 8px;
|
||||
padding: 8px 12px 7px;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] [data-slot="bash-section"][data-kind="output"] {
|
||||
padding: 8px 12px 10px;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] [data-slot="bash-prompt"] {
|
||||
color: var(--text-weak, var(--vscode-descriptionForeground));
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: var(--line-height-large);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] [data-slot="bash-section-code"] {
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 240px;
|
||||
@@ -254,25 +299,22 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* Edge-to-edge divider (matches MCP tool divider) */
|
||||
[data-component="bash-output"] [data-slot="bash-divider"] {
|
||||
height: 1px;
|
||||
background: var(--border-weak-base);
|
||||
[data-component="bash-output"] [data-slot="bash-pre"] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Shiki syntax-highlighted shell output */
|
||||
[data-component="bash-output"] .shiki,
|
||||
[data-component="shell-expanded-output"] .shiki {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
[data-component="bash-output"] .shiki code,
|
||||
[data-component="bash-output"] [data-slot="bash-pre"] code,
|
||||
[data-component="shell-expanded-output"] .shiki code {
|
||||
font-family: var(--font-family-mono);
|
||||
font-feature-settings: var(--font-family-mono--font-feature-settings);
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: var(--line-height-large);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
@@ -56,8 +56,6 @@ import { ShellRollingResults } from "./shell-rolling-results"
|
||||
import { reasoningHeading } from "./reasoning-heading"
|
||||
import { extractFilePathFromHref } from "../file-path"
|
||||
import { normalize } from "./session-diff"
|
||||
import { deferredHighlight } from "../context/marked"
|
||||
import { escapeHtml } from "../util/escape-html"
|
||||
|
||||
// Windows CLI tools (e.g. winget) use \r to overwrite progress bars in-place.
|
||||
// Without this, every progress frame renders as a separate visual line.
|
||||
@@ -1846,7 +1844,7 @@ ToolRegistry.register({
|
||||
>
|
||||
<Show when={props.output}>
|
||||
{(output) => (
|
||||
<div data-component="tool-output" data-scrollable>
|
||||
<div data-component="tool-output" data-variant="preview" data-scrollable>
|
||||
<Markdown text={output()} />
|
||||
</div>
|
||||
)}
|
||||
@@ -1877,7 +1875,7 @@ ToolRegistry.register({
|
||||
>
|
||||
<Show when={props.output}>
|
||||
{(output) => (
|
||||
<div data-component="tool-output" data-scrollable>
|
||||
<div data-component="tool-output" data-variant="preview" data-scrollable>
|
||||
<Markdown text={output()} />
|
||||
</div>
|
||||
)}
|
||||
@@ -1911,7 +1909,7 @@ ToolRegistry.register({
|
||||
>
|
||||
<Show when={props.output}>
|
||||
{(output) => (
|
||||
<div data-component="tool-output" data-scrollable>
|
||||
<div data-component="tool-output" data-variant="preview" data-scrollable>
|
||||
<Markdown text={output()} />
|
||||
</div>
|
||||
)}
|
||||
@@ -2111,37 +2109,6 @@ function BashCopyButton(props: { value: () => string; label: string }) {
|
||||
function BashHighlightedOutput(props: { cmd: string; output: string; outputPath?: string; active?: boolean }) {
|
||||
const data = useData()
|
||||
const i18n = useI18n()
|
||||
const cmdState = { signal: { aborted: false } }
|
||||
const outState = { signal: { aborted: false } }
|
||||
let cmdRef: HTMLDivElement | undefined
|
||||
let outRef: HTMLDivElement | undefined
|
||||
|
||||
createEffect(() => {
|
||||
cmdState.signal.aborted = true
|
||||
if (!props.active) return
|
||||
const cmd = props.cmd
|
||||
if (!cmdRef || !cmd) return
|
||||
const signal = { aborted: false }
|
||||
cmdState.signal = signal
|
||||
cmdRef.innerHTML = `<pre data-slot="bash-pre"><code data-lang="shellscript">${escapeHtml(cmd)}</code></pre>`
|
||||
void deferredHighlight(cmdRef, undefined, signal)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
outState.signal.aborted = true
|
||||
if (!props.active) return
|
||||
const out = props.output
|
||||
if (!outRef || !out) return
|
||||
const signal = { aborted: false }
|
||||
outState.signal = signal
|
||||
outRef.innerHTML = `<pre data-slot="bash-pre"><code data-lang="log">${escapeHtml(out)}</code></pre>`
|
||||
void deferredHighlight(outRef, undefined, signal)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
cmdState.signal.aborted = true
|
||||
outState.signal.aborted = true
|
||||
})
|
||||
|
||||
const openInEditor = () => {
|
||||
// When output was truncated, open the full output file on disk
|
||||
@@ -2156,34 +2123,40 @@ function BashHighlightedOutput(props: { cmd: string; output: string; outputPath?
|
||||
return (
|
||||
<div data-component="bash-output">
|
||||
<Show when={props.cmd}>
|
||||
<div data-slot="mcp-section-label">{i18n.t("ui.messagePart.shell.command")}</div>
|
||||
<div data-slot="bash-section">
|
||||
<div data-slot="bash-section-code" ref={cmdRef} />
|
||||
<div data-slot="bash-section-actions">
|
||||
<BashCopyButton value={() => props.cmd} label={i18n.t("ui.message.copy")} />
|
||||
<div data-slot="bash-terminal" data-kind="command">
|
||||
<div data-slot="bash-section" data-kind="command">
|
||||
<span data-slot="bash-prompt" aria-hidden="true">
|
||||
$
|
||||
</span>
|
||||
<div data-slot="bash-section-code">
|
||||
<pre data-slot="bash-pre"><code>{props.cmd}</code></pre>
|
||||
</div>
|
||||
<div data-slot="bash-section-actions">
|
||||
<BashCopyButton value={() => props.cmd} label={i18n.t("ui.message.copy")} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={props.cmd && props.output}>
|
||||
<div data-slot="bash-divider" />
|
||||
</Show>
|
||||
<Show when={props.output}>
|
||||
<div data-slot="mcp-section-label">{i18n.t("ui.messagePart.shell.output")}</div>
|
||||
<div data-slot="bash-section">
|
||||
<div data-slot="bash-section-code" data-scrollable ref={outRef} />
|
||||
<div data-slot="bash-section-actions">
|
||||
<Show when={data.openContent || (props.outputPath && data.openFile)}>
|
||||
<Tooltip value={i18n.t("ui.messagePart.openInEditor")} placement="bottom" gutter={4}>
|
||||
<IconButton
|
||||
icon="square-arrow-top-right"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={openInEditor}
|
||||
aria-label={i18n.t("ui.messagePart.openInEditor")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<BashCopyButton value={() => props.output} label={i18n.t("ui.message.copy")} />
|
||||
<div data-slot="bash-terminal" data-kind="output">
|
||||
<div data-slot="bash-section" data-kind="output">
|
||||
<div data-slot="bash-section-code" data-scrollable>
|
||||
<pre data-slot="bash-pre"><code>{props.output}</code></pre>
|
||||
</div>
|
||||
<div data-slot="bash-section-actions">
|
||||
<Show when={data.openContent || (props.outputPath && data.openFile)}>
|
||||
<Tooltip value={i18n.t("ui.messagePart.openInEditor")} placement="bottom" gutter={4}>
|
||||
<IconButton
|
||||
icon="square-arrow-top-right"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={openInEditor}
|
||||
aria-label={i18n.t("ui.messagePart.openInEditor")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<BashCopyButton value={() => props.output} label={i18n.t("ui.message.copy")} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
@@ -2223,7 +2196,6 @@ ToolRegistry.register({
|
||||
<BasicTool
|
||||
{...props}
|
||||
icon="console"
|
||||
animated
|
||||
hasDetails
|
||||
defaultOpen={props.defaultOpen ?? true}
|
||||
onOpenChange={setOpen}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TextShimmer } from "./text-shimmer"
|
||||
import { Tooltip } from "./tooltip"
|
||||
import { GROW_SPRING } from "./motion"
|
||||
import { useSpring } from "./motion-spring"
|
||||
import { busy, createThrottledValue, updateScrollMask, useCollapsible, useRowWipe, useToolFade } from "./tool-utils"
|
||||
import { busy, createThrottledValue, updateScrollMask, useRowWipe, useToolFade } from "./tool-utils"
|
||||
import { readToolOpen, toolOpenKey, writeToolOpen } from "./tool-open-state"
|
||||
|
||||
function ShellRollingSubtitle(props: { text: string; animate?: boolean }) {
|
||||
@@ -88,8 +88,6 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
|
||||
const rowHeight = 22
|
||||
const max = rows * rowHeight
|
||||
|
||||
let contentRef: HTMLDivElement | undefined
|
||||
let bodyRef: HTMLDivElement | undefined
|
||||
let scrollRef: HTMLDivElement | undefined
|
||||
let topRef: HTMLDivElement | undefined
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
@@ -104,11 +102,6 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
|
||||
setCap(Math.max(rowHeight * 2, max - top - (props.out ? 1 : 0)))
|
||||
}
|
||||
|
||||
const measure = () => {
|
||||
resize()
|
||||
return Math.ceil(bodyRef?.getBoundingClientRect().height ?? 0)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
resize()
|
||||
if (!topRef) return
|
||||
@@ -120,20 +113,13 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
|
||||
createEffect(() => {
|
||||
props.cmd
|
||||
props.out
|
||||
if (!props.open) return
|
||||
queueMicrotask(() => {
|
||||
resize()
|
||||
updateMask()
|
||||
})
|
||||
})
|
||||
|
||||
useCollapsible({
|
||||
content: () => contentRef,
|
||||
body: () => bodyRef,
|
||||
open: () => props.open,
|
||||
measure,
|
||||
onOpen: updateMask,
|
||||
})
|
||||
|
||||
const handleCopy = async (e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
const cmd = props.cmd ? `$ ${props.cmd}` : ""
|
||||
@@ -145,8 +131,14 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={contentRef} style={{ overflow: "clip", height: "0px", display: "none" }}>
|
||||
<div ref={bodyRef} data-component="shell-expanded-shell">
|
||||
<div
|
||||
style={{
|
||||
overflow: "clip",
|
||||
height: props.open ? "auto" : "0px",
|
||||
display: props.open ? "block" : "none",
|
||||
}}
|
||||
>
|
||||
<div data-component="shell-expanded-shell">
|
||||
<div data-slot="shell-expanded-body">
|
||||
<div ref={topRef} data-slot="shell-expanded-top">
|
||||
<div data-slot="shell-expanded-command">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { Icon } from "../components/icon"
|
||||
|
||||
const meta: Meta<typeof Icon> = {
|
||||
title: "Components/Icon",
|
||||
@@ -89,6 +89,26 @@ const iconNames = [
|
||||
"layout-bottom",
|
||||
] as const
|
||||
|
||||
const toolIcons = [
|
||||
{ tool: "Read", icon: "glasses", note: "File reads" },
|
||||
{ tool: "List", icon: "bullet-list", note: "Directory lists" },
|
||||
{ tool: "Glob", icon: "magnifying-glass-menu", note: "File pattern search" },
|
||||
{ tool: "Grep", icon: "magnifying-glass-menu", note: "Content search" },
|
||||
{ tool: "WebFetch", icon: "window-cursor", note: "Fetch URL" },
|
||||
{ tool: "WebSearch", icon: "window-cursor", note: "Search web" },
|
||||
{ tool: "CodeSearch", icon: "code", note: "Code search" },
|
||||
{ tool: "Task", icon: "task", note: "Subagent" },
|
||||
{ tool: "Bash", icon: "console", note: "Shell command" },
|
||||
{ tool: "Edit", icon: "code-lines", note: "Modify file" },
|
||||
{ tool: "Write", icon: "code-lines", note: "Write file" },
|
||||
{ tool: "Apply Patch", icon: "code-lines", note: "Patch files" },
|
||||
{ tool: "TodoWrite", icon: "checklist", note: "Update todos" },
|
||||
{ tool: "TodoRead", icon: "checklist", note: "Read todos" },
|
||||
{ tool: "Question", icon: "bubble-5", note: "Ask user" },
|
||||
{ tool: "Skill", icon: "brain", note: "Load skill" },
|
||||
{ tool: "MCP/default", icon: "mcp", note: "Fallback tool" },
|
||||
] as const
|
||||
|
||||
export const AllIcons: Story = {
|
||||
render: () => (
|
||||
<div
|
||||
@@ -116,3 +136,38 @@ export const AllIcons: Story = {
|
||||
),
|
||||
parameters: { layout: "fullscreen" },
|
||||
}
|
||||
|
||||
export const ToolCallIcons: Story = {
|
||||
render: () => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
"grid-template-columns": "repeat(auto-fill, minmax(180px, 1fr))",
|
||||
gap: "8px",
|
||||
padding: "16px",
|
||||
}}
|
||||
>
|
||||
{toolIcons.map((item) => (
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
"grid-template-columns": "24px 1fr",
|
||||
gap: "8px",
|
||||
"align-items": "center",
|
||||
padding: "8px",
|
||||
"border-radius": "6px",
|
||||
background: "var(--surface-inset-base)",
|
||||
}}
|
||||
>
|
||||
<Icon name={item.icon} size="normal" />
|
||||
<div style={{ display: "flex", "flex-direction": "column", gap: "2px", "min-width": "0" }}>
|
||||
<span style={{ "font-size": "12px", color: "var(--text-strong)" }}>{item.tool}</span>
|
||||
<span style={{ "font-size": "11px", color: "var(--text-weak)" }}>{item.icon}</span>
|
||||
<span style={{ "font-size": "11px", color: "var(--text-weaker)" }}>{item.note}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
parameters: { layout: "fullscreen" },
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { UserMessageDisplay, AssistantParts } from "../components/message-part"
|
||||
import { UserMessageDisplay, AssistantParts, Part } from "../components/message-part"
|
||||
import { DataProvider } from "@opencode-ai/ui/context/data"
|
||||
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
|
||||
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
|
||||
@@ -274,6 +274,38 @@ const mockDataContextGroup = createMockData([completedToolPart, grepCompleted, g
|
||||
const mockDataEdit = createMockData([editCompletedPart])
|
||||
const mockDataWrite = createMockData([writeCompletedPart])
|
||||
|
||||
const vscodeReadPart: ToolPart = {
|
||||
...completedToolPart,
|
||||
id: "part-vscode-read-001",
|
||||
callID: "call-vscode-read-001",
|
||||
state: {
|
||||
...completedToolPart.state,
|
||||
input: {
|
||||
filePath: "/Users/helios/.superconductor/worktrees/kilo-code/sc-zero-helium-f589/packages/kilo-vscode/AGENTS.md",
|
||||
offset: 1,
|
||||
limit: 200,
|
||||
},
|
||||
metadata: {
|
||||
loaded: ["/project/packages/kilo-vscode/AGENTS.md"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const vscodeGlobPart: ToolPart = {
|
||||
...globCompleted,
|
||||
id: "part-vscode-glob-001",
|
||||
callID: "call-vscode-glob-001",
|
||||
state: {
|
||||
...globCompleted.state,
|
||||
input: {
|
||||
path: "/Users/helios/.superconductor/worktrees/kilo-code/sc-zero-helium-f589",
|
||||
pattern: "packages/kilo-vscode/webview-ui/src/**/*.tsx",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const vscodeToolParts = [vscodeGlobPart, vscodeGlobPart, vscodeGlobPart, vscodeReadPart]
|
||||
|
||||
function AllProviders(props: { children: any; data?: MockData; onOpenDiff?: () => void }) {
|
||||
return (
|
||||
<DataProvider data={props.data ?? mockData} directory="/project" onOpenDiff={props.onOpenDiff}>
|
||||
@@ -326,6 +358,25 @@ export const AssistantMessageStory: Story = {
|
||||
),
|
||||
}
|
||||
|
||||
export const VscodeToolBlocks: Story = {
|
||||
globals: { theme: "kilo-vscode", vscodeTheme: "dark-modern" },
|
||||
render: () => (
|
||||
<AllProviders>
|
||||
<div style={{ display: "flex", "flex-direction": "column", gap: "8px", width: "700px" }}>
|
||||
<p style={{ margin: 0, color: "var(--text-base)", "font-size": "16px", "line-height": "1.5" }}>
|
||||
I’ll inspect the UI test setup and existing patterns first, then identify the smallest relevant test change
|
||||
to make.
|
||||
</p>
|
||||
<div style={{ display: "flex", "flex-direction": "column", gap: "2px" }}>
|
||||
{vscodeToolParts.map((part) => (
|
||||
<Part part={part} message={mockAssistantMessage} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AllProviders>
|
||||
),
|
||||
}
|
||||
|
||||
// --- Edit tool in running/pending state (Part → ToolPartDisplay path) ---
|
||||
|
||||
export const WithRunningTool: Story = {
|
||||
|
||||
@@ -138,7 +138,7 @@ function BackgroundProcessTool(props: ToolProps) {
|
||||
</Show>
|
||||
<Show when={data().output}>
|
||||
{(value) => (
|
||||
<div data-component="tool-output" data-scrollable>
|
||||
<div data-component="tool-output" data-variant="preview" data-scrollable>
|
||||
<pre data-slot="background-process-output">{value()}</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -187,6 +187,9 @@ export function mockSessionValue(overrides?: {
|
||||
loading: () => false,
|
||||
loadingOlderMessages: () => false,
|
||||
hasOlderMessages: () => false,
|
||||
submitting: () => false,
|
||||
draftSessionID: () => undefined,
|
||||
setDraftSessionID: noop,
|
||||
messageMutation: () => undefined,
|
||||
messages: () => [],
|
||||
visibleMessages: () => [],
|
||||
@@ -195,6 +198,8 @@ export function mockSessionValue(overrides?: {
|
||||
allParts: () => ({}),
|
||||
allStatusMap: () => ({}),
|
||||
getParts: () => [],
|
||||
getSessionToolParts: () => [],
|
||||
getSessionToolCount: () => 0,
|
||||
isErrorHidden: () => false,
|
||||
hydrateParts: noop,
|
||||
todos: () => [],
|
||||
@@ -255,6 +260,7 @@ export function mockSessionValue(overrides?: {
|
||||
deleteSession: noop,
|
||||
renameSession: noop,
|
||||
syncSession: noop,
|
||||
exportSessionTranscript: noop,
|
||||
cloudPreviewId: () => null,
|
||||
selectCloudSession: noop,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
import { For } from "solid-js"
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite"
|
||||
import { Part } from "@kilocode/kilo-ui/message-part"
|
||||
import type { AssistantMessage as SDKAssistantMessage, Part as SDKPart, ToolPart } from "@kilocode/sdk/v2"
|
||||
import { StoryProviders } from "./StoryProviders"
|
||||
import { registerVscodeToolOverrides } from "../components/chat/VscodeToolOverrides"
|
||||
|
||||
registerVscodeToolOverrides()
|
||||
|
||||
const SID = "tool-call-lab-session"
|
||||
const MID = "tool-call-lab-message"
|
||||
const stamp = Date.now()
|
||||
|
||||
const base: SDKAssistantMessage = {
|
||||
id: MID,
|
||||
sessionID: SID,
|
||||
role: "assistant",
|
||||
parentID: "tool-call-lab-user-message",
|
||||
time: { created: stamp - 9000, completed: stamp - 1000 },
|
||||
modelID: "anthropic/claude-sonnet-4-6",
|
||||
providerID: "kilo",
|
||||
mode: "default",
|
||||
agent: "default",
|
||||
path: { cwd: "/project", root: "/project" },
|
||||
cost: 0.0021,
|
||||
tokens: { total: 742, input: 386, output: 356, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
}
|
||||
|
||||
const long = [
|
||||
"packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx",
|
||||
"packages/kilo-vscode/webview-ui/src/stories/tool-call-lab.stories.tsx",
|
||||
"packages/kilo-ui/src/components/message-part.tsx",
|
||||
].join("\n")
|
||||
|
||||
const hits = [
|
||||
"packages/kilo-ui/src/components/message-part.tsx:1847: <div data-component=\"tool-output\">",
|
||||
"packages/kilo-ui/src/components/basic-tool.css:250: [data-component=\"tool-output\"]",
|
||||
"packages/kilo-vscode/webview-ui/src/components/chat/VscodeToolOverrides.tsx:141: background process output",
|
||||
].join("\n")
|
||||
|
||||
const tree = [
|
||||
"packages/kilo-ui/src/components/",
|
||||
" basic-tool.css",
|
||||
" context-tool-results.tsx",
|
||||
" message-part.css",
|
||||
" message-part.tsx",
|
||||
"packages/kilo-vscode/webview-ui/src/stories/",
|
||||
" tool-call-lab.stories.tsx",
|
||||
].join("\n")
|
||||
|
||||
const proc = [
|
||||
"pid: 48122",
|
||||
"status: running",
|
||||
"cwd: /project",
|
||||
"command: bun run --cwd packages/kilo-vscode storybook",
|
||||
"last_output:",
|
||||
"Storybook 9.0.18 for solid-vite started",
|
||||
"Local: http://localhost:6007/",
|
||||
].join("\n")
|
||||
|
||||
function completed(input: Record<string, unknown>, title: string, value: string): ToolPart["state"] {
|
||||
return {
|
||||
status: "completed",
|
||||
input,
|
||||
output: value,
|
||||
title,
|
||||
metadata: {},
|
||||
time: { start: stamp - 5000, end: stamp - 4400 },
|
||||
}
|
||||
}
|
||||
|
||||
function tool(id: string, call: string, name: string, state: ToolPart["state"]): ToolPart {
|
||||
return {
|
||||
id,
|
||||
sessionID: SID,
|
||||
messageID: MID,
|
||||
type: "tool",
|
||||
callID: call,
|
||||
tool: name,
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
const previews: SDKPart[] = [
|
||||
tool(
|
||||
"lab-preview-glob",
|
||||
"lab-call-preview-glob",
|
||||
"glob",
|
||||
completed({ pattern: "packages/**/tool*.tsx", path: "." }, "Find tool files", long),
|
||||
),
|
||||
tool(
|
||||
"lab-preview-grep",
|
||||
"lab-call-preview-grep",
|
||||
"grep",
|
||||
completed({ pattern: "data-component=\"tool-output\"", include: "*.tsx", path: "." }, "Search tool output", hits),
|
||||
),
|
||||
tool(
|
||||
"lab-preview-list",
|
||||
"lab-call-preview-list",
|
||||
"list",
|
||||
completed({ path: "packages/kilo-ui/src/components" }, "List component files", tree),
|
||||
),
|
||||
tool(
|
||||
"lab-preview-background",
|
||||
"lab-call-preview-background",
|
||||
"background_process",
|
||||
completed({ action: "status", id: "bgp_storybook", description: "Check Storybook server" }, "Check Storybook server", proc),
|
||||
),
|
||||
]
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Labs/Tool Call Lab",
|
||||
parameters: { layout: "padded" },
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
type Story = StoryObj
|
||||
|
||||
export const SearchPreviews: Story = {
|
||||
name: "Search Previews",
|
||||
render: () => (
|
||||
<StoryProviders noPadding sessionID={SID} status="idle">
|
||||
<div style={{ padding: "16px", width: "520px", "max-width": "100%" }}>
|
||||
<div style={{ display: "flex", "flex-direction": "column", gap: "10px" }}>
|
||||
<For each={previews}>{(part) => <Part part={part} message={base} defaultOpen />}</For>
|
||||
</div>
|
||||
</div>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
@@ -211,10 +211,17 @@
|
||||
.vscode-session-turn-assistant {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
gap: 2px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vscode-session-turn-assistant [data-component="tool-part-wrapper"][data-part-type="text"]
|
||||
+ [data-component="tool-part-wrapper"][data-part-type="tool"],
|
||||
.vscode-session-turn-assistant [data-component="tool-part-wrapper"][data-part-type="tool"]
|
||||
+ [data-component="tool-part-wrapper"][data-part-type="text"] {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.vscode-session-turn-diffs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,9 @@
|
||||
[data-slot="background-process-output"] {
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
font-family: var(--font-mono);
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: var(--line-height-large);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user