ui: add input/output accordions in tool calls

This commit is contained in:
Shantur Rathore
2026-02-19 18:37:46 +00:00
parent 5fd985f0c2
commit 6b7162f50f
2 changed files with 75 additions and 43 deletions

View File

@@ -172,7 +172,9 @@ export default function ToolCall(props: ToolCallProps) {
})
const [userExpanded, setUserExpanded] = createSignal<boolean | null>(null)
const [inputExpanded, setInputExpanded] = createSignal(false)
const [inputVisible, setInputVisible] = createSignal(false)
const [inputSectionExpanded, setInputSectionExpanded] = createSignal(false)
const [outputSectionExpanded, setOutputSectionExpanded] = createSignal(true)
const isPermissionActive = createMemo(() => {
const pending = pendingPermission()
@@ -589,13 +591,19 @@ export default function ToolCall(props: ToolCallProps) {
})
}
const handleToggleInput = (event: MouseEvent) => {
const handleToggleInputVisibility = (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()
if (!expanded()) {
toggle()
}
setInputExpanded((prev) => !prev)
setInputVisible((prev) => {
const next = !prev
if (!next) {
setInputSectionExpanded(false)
}
return next
})
}
const renderer = createMemo(() => resolveToolRenderer(toolName()))
@@ -728,17 +736,6 @@ export default function ToolCall(props: ToolCallProps) {
return renderer().renderBody(rendererContext)
}
const renderToolBodyWithHeader = () => {
const body = renderToolBody()
if (!body) return null
return (
<>
<div class="tool-call-io-header">{t("toolCall.io.output")}</div>
{body}
</>
)
}
async function handlePermissionResponse(permission: PermissionRequestLike, response: "once" | "always" | "reject") {
if (!permission) return
setPermissionSubmitting(true)
@@ -854,14 +851,14 @@ export default function ToolCall(props: ToolCallProps) {
<button
type="button"
class="tool-call-header-input"
onClick={handleToggleInput}
aria-pressed={inputExpanded()}
onClick={handleToggleInputVisibility}
aria-pressed={inputVisible()}
aria-label={
inputExpanded()
inputVisible()
? t("toolCall.header.hideInputAriaLabel")
: t("toolCall.header.showInputAriaLabel")
}
title={inputExpanded() ? t("toolCall.header.hideInputTitle") : t("toolCall.header.showInputTitle")}
title={inputVisible() ? t("toolCall.header.hideInputTitle") : t("toolCall.header.showInputTitle")}
>
<ArrowRightSquare class="w-3.5 h-3.5" />
</button>
@@ -884,34 +881,48 @@ export default function ToolCall(props: ToolCallProps) {
{expanded() && (
<div class="tool-call-details">
<Show when={inputExpanded() && hasToolInput()}>
{(() => {
const content = toolInputMarkdown()
if (!content) return null
return (
<>
<div class="tool-call-io-header">{t("toolCall.io.input")}</div>
{renderMarkdownContent({ content, cacheKey: "input" })}
</>
)
})()}
<Show when={inputVisible() && hasToolInput()}>
<button
type="button"
class="tool-call-io-toggle"
aria-expanded={inputSectionExpanded()}
onClick={() => setInputSectionExpanded((prev) => !prev)}
>
<span class="tool-call-io-title">{t("toolCall.io.input")}</span>
</button>
<Show when={inputSectionExpanded()}>
{(() => {
const content = toolInputMarkdown()
if (!content) return null
return renderMarkdownContent({ content, cacheKey: "input" })
})()}
</Show>
</Show>
<Show when={inputExpanded() && hasToolInput()} fallback={renderToolBody()}>
{renderToolBodyWithHeader()}
<button
type="button"
class="tool-call-io-toggle"
aria-expanded={outputSectionExpanded()}
onClick={() => setOutputSectionExpanded((prev) => !prev)}
>
<span class="tool-call-io-title">{t("toolCall.io.output")}</span>
</button>
<Show when={outputSectionExpanded()}>
{renderToolBody()}
{renderError()}
<Show when={status() === "pending" && !pendingPermission()}>
<div class="tool-call-pending-message">
<span class="spinner-small"></span>
<span>{t("toolCall.pending.waitingToRun")}</span>
</div>
</Show>
</Show>
{renderError()}
{renderPermissionBlock()}
{renderQuestionBlock()}
<Show when={status() === "pending" && !pendingPermission()}>
<div class="tool-call-pending-message">
<span class="spinner-small"></span>
<span>{t("toolCall.pending.waitingToRun")}</span>
</div>
</Show>
</div>
)}

View File

@@ -232,12 +232,33 @@
font-size: var(--font-size-xs);
}
.tool-call-io-header {
@apply flex items-center justify-between gap-3 px-3 py-2;
.tool-call-io-toggle {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.5rem;
background-color: var(--surface-secondary);
border: none;
border-top: 1px solid var(--tool-call-border-color);
font-family: var(--font-family-mono);
font-size: 13px;
color: inherit;
cursor: pointer;
}
.tool-call-io-toggle::before {
content: "▶";
font-size: 11px;
margin-right: 0.35rem;
color: var(--text-secondary);
}
.tool-call-io-toggle[aria-expanded="true"]::before {
content: "▼";
}
.tool-call-io-title {
font-weight: var(--font-weight-semibold);
color: var(--text-primary);
}