From 9800afb785f0828b2f14e133bbbf24f111696448 Mon Sep 17 00:00:00 2001 From: "codenomadbot[bot]" <261069733+codenomadbot[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:08:41 +0000 Subject: [PATCH] feat(ui): toggle tool call input YAML (#182) * feat(ui): toggle tool call input yaml * ui: rename tool input toggle and add IO headers * ui: add input/output accordions in tool calls * ui: refine tool IO accordion styling * ui: remove extra padding around IO sections * ui: remove semibold from IO headers * feat(ui): add tool input visibility preference * fix(ui): scope tool input toggle to current tool call * ui: left-align tool IO header text * fix(ui): let palette tool input visibility override per-call * ui: default tool input visibility to collapsed * fix(ui): expand read tool calls on error --------- Co-authored-by: Shantur Rathore --- package-lock.json | 3 +- packages/ui/package.json | 3 +- packages/ui/src/App.tsx | 2 + packages/ui/src/components/tool-call.tsx | 183 ++++++++++++++++-- packages/ui/src/lib/hooks/use-commands.ts | 26 ++- .../ui/src/lib/i18n/messages/en/commands.ts | 4 + .../ui/src/lib/i18n/messages/en/toolCall.ts | 8 + .../ui/src/lib/i18n/messages/es/commands.ts | 4 + .../ui/src/lib/i18n/messages/es/toolCall.ts | 8 + .../ui/src/lib/i18n/messages/fr/commands.ts | 4 + .../ui/src/lib/i18n/messages/fr/toolCall.ts | 8 + .../ui/src/lib/i18n/messages/ja/commands.ts | 4 + .../ui/src/lib/i18n/messages/ja/toolCall.ts | 8 + .../ui/src/lib/i18n/messages/ru/commands.ts | 4 + .../ui/src/lib/i18n/messages/ru/toolCall.ts | 8 + .../src/lib/i18n/messages/zh-Hans/commands.ts | 4 + .../src/lib/i18n/messages/zh-Hans/toolCall.ts | 8 + packages/ui/src/stores/preferences.tsx | 14 ++ .../ui/src/styles/messaging/tool-call.css | 77 ++++++++ 19 files changed, 364 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71bff3c9..8f529449 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12092,7 +12092,8 @@ "shiki": "^3.13.0", "solid-js": "^1.8.0", "solid-toast": "^0.5.0", - "tauri-plugin-keepawake-api": "^0.1.0" + "tauri-plugin-keepawake-api": "^0.1.0", + "yaml": "^2.4.2" }, "devDependencies": { "@vite-pwa/assets-generator": "^1.0.2", diff --git a/packages/ui/package.json b/packages/ui/package.json index a6235618..42aae094 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -30,7 +30,8 @@ "shiki": "^3.13.0", "solid-js": "^1.8.0", "solid-toast": "^0.5.0", - "tauri-plugin-keepawake-api": "^0.1.0" + "tauri-plugin-keepawake-api": "^0.1.0", + "yaml": "^2.4.2" }, "devDependencies": { "@vite-pwa/assets-generator": "^1.0.2", diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index b02861c1..5ad213a6 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -72,6 +72,7 @@ const App: Component = () => { setToolOutputExpansion, setDiagnosticsExpansion, setThinkingBlocksExpansion, + setToolInputsVisibility, } = useConfig() const [escapeInDebounce, setEscapeInDebounce] = createSignal(false) interface LaunchErrorState { @@ -402,6 +403,7 @@ const App: Component = () => { setToolOutputExpansion, setDiagnosticsExpansion, setThinkingBlocksExpansion, + setToolInputsVisibility, handleNewInstanceRequest, handleCloseInstance, handleNewSession, diff --git a/packages/ui/src/components/tool-call.tsx b/packages/ui/src/components/tool-call.tsx index 9b9e5c01..97164b1b 100644 --- a/packages/ui/src/components/tool-call.tsx +++ b/packages/ui/src/components/tool-call.tsx @@ -1,5 +1,6 @@ import { createSignal, Show, createEffect, createMemo, onCleanup } from "solid-js" -import { Copy } from "lucide-solid" +import { ArrowRightSquare, Copy } from "lucide-solid" +import { stringify as stringifyYaml } from "yaml" import { messageStoreBus } from "../stores/message-v2/bus" import { useTheme } from "../lib/theme" import { useGlobalCache } from "../lib/hooks/use-global-cache" @@ -27,7 +28,17 @@ import type { ToolRendererContext, ToolScrollHelpers, } from "./tool-call/types" -import { getRelativePath, getToolIcon, getToolName, isToolStateCompleted, isToolStateError, isToolStateRunning, getDefaultToolAction } from "./tool-call/utils" +import { + ensureMarkdownContent, + getRelativePath, + getToolIcon, + getToolName, + isToolStateCompleted, + isToolStateError, + isToolStateRunning, + getDefaultToolAction, + readToolStatePayload, +} from "./tool-call/utils" import { resolveTitleForTool } from "./tool-call/tool-title" import { getLogger } from "../lib/logger" @@ -155,12 +166,33 @@ export default function ToolCall(props: ToolCallProps) { const prefExpanded = toolOutputDefaultExpanded() const toolName = toolCallMemo()?.tool || "" if (toolName === "read") { + const state = toolState() + if (state?.status === "error") { + return true + } return false } return prefExpanded }) const [userExpanded, setUserExpanded] = createSignal(null) + const toolInputsVisibility = createMemo(() => preferences().toolInputsVisibility || "collapsed") + const [toolInputVisibilityOverride, setToolInputVisibilityOverride] = createSignal<"hidden" | "expanded" | null>(null) + const effectiveToolInputsVisibility = createMemo(() => toolInputVisibilityOverride() ?? toolInputsVisibility()) + const isToolInputVisible = createMemo(() => effectiveToolInputsVisibility() !== "hidden") + const inputDefaultExpanded = createMemo(() => effectiveToolInputsVisibility() === "expanded") + const [inputSectionOverride, setInputSectionOverride] = createSignal(null) + const [outputSectionOverride, setOutputSectionOverride] = createSignal(null) + const inputSectionExpanded = () => { + const override = inputSectionOverride() + if (override !== null) return override + return inputDefaultExpanded() + } + const outputSectionExpanded = () => { + const override = outputSectionOverride() + if (override !== null) return override + return true + } const isPermissionActive = createMemo(() => { const pending = pendingPermission() @@ -183,6 +215,35 @@ export default function ToolCall(props: ToolCallProps) { return defaultExpandedForTool() } + const toolInput = createMemo(() => { + const state = toolState() + return readToolStatePayload(state).input + }) + + const hasToolInput = createMemo(() => { + const input = toolInput() + return input && Object.keys(input).length > 0 + }) + + const toolInputMarkdown = createMemo(() => { + const input = toolInput() + if (!input || Object.keys(input).length === 0) return null + + try { + const yamlText = stringifyYaml(input) + return ensureMarkdownContent(yamlText, "yaml", true) + } catch (error) { + log.error("Failed to convert tool call input to YAML", error) + try { + const jsonText = JSON.stringify(input, null, 2) + return ensureMarkdownContent(jsonText, "json", true) + } catch (nestedError) { + log.error("Failed to stringify tool call input", nestedError) + return null + } + } + }) + const permissionDetails = createMemo(() => pendingPermission()?.permission) const questionDetails = createMemo(() => pendingQuestion()?.request) @@ -548,6 +609,25 @@ export default function ToolCall(props: ToolCallProps) { }) } + createEffect(() => { + // When global preference changes, reset per-tool-call overrides so palette changes apply. + toolInputsVisibility() + setToolInputVisibilityOverride(null) + setInputSectionOverride(null) + setOutputSectionOverride(null) + }) + + const handleToggleInputVisibility = (event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + if (!expanded()) { + toggle() + } + + const currentlyVisible = isToolInputVisible() + setToolInputVisibilityOverride(currentlyVisible ? "hidden" : "expanded") + } + const renderer = createMemo(() => resolveToolRenderer(toolName())) const { renderAnsiContent } = createAnsiContentRenderer({ @@ -789,6 +869,23 @@ export default function ToolCall(props: ToolCallProps) { + + + + + + +
+ {(() => { + const content = toolInputMarkdown() + if (!content) return null + return renderMarkdownContent({ content, cacheKey: "input" }) + })()} +
+
+ + +
+ + + +
+ {renderToolBody()} + {renderError()} + + +
+ + {t("toolCall.pending.waitingToRun")} +
+
+
+
+
+ + {renderPermissionBlock()} + {renderQuestionBlock()} )} diff --git a/packages/ui/src/lib/hooks/use-commands.ts b/packages/ui/src/lib/hooks/use-commands.ts index c89a95d4..4105e274 100644 --- a/packages/ui/src/lib/hooks/use-commands.ts +++ b/packages/ui/src/lib/hooks/use-commands.ts @@ -1,6 +1,6 @@ import { createSignal, onMount } from "solid-js" import type { Accessor } from "solid-js" -import type { Preferences, ExpansionPreference } from "../../stores/preferences" +import type { Preferences, ExpansionPreference, ToolInputsVisibilityPreference } from "../../stores/preferences" import { createCommandRegistry, type Command } from "../commands" import { instances, activeInstanceId, setActiveInstanceId } from "../../stores/instances" import type { ClientPart, MessageInfo } from "../../types/message" @@ -38,6 +38,7 @@ export interface UseCommandsOptions { setToolOutputExpansion: (mode: ExpansionPreference) => void setDiagnosticsExpansion: (mode: ExpansionPreference) => void setThinkingBlocksExpansion: (mode: ExpansionPreference) => void + setToolInputsVisibility: (mode: ToolInputsVisibilityPreference) => void handleNewInstanceRequest: () => void handleCloseInstance: (instanceId: string) => Promise handleNewSession: (instanceId: string) => Promise @@ -551,6 +552,29 @@ export function useCommands(options: UseCommandsOptions) { }, }) + commandRegistry.register({ + id: "tool-inputs-visibility", + label: () => { + const mode = options.preferences().toolInputsVisibility || "hidden" + const state = + mode === "expanded" + ? tGlobal("commands.common.expanded") + : mode === "collapsed" + ? tGlobal("commands.common.collapsed") + : tGlobal("commands.common.hidden") + return tGlobal("commands.toolInputsVisibility.label", { state }) + }, + description: () => tGlobal("commands.toolInputsVisibility.description"), + category: "System", + keywords: () => splitKeywords("commands.toolInputsVisibility.keywords"), + action: () => { + const mode = options.preferences().toolInputsVisibility || "hidden" + const next: ToolInputsVisibilityPreference = + mode === "hidden" ? "collapsed" : mode === "collapsed" ? "expanded" : "hidden" + options.setToolInputsVisibility(next) + }, + }) + commandRegistry.register({ id: "token-usage-visibility", label: () => { diff --git a/packages/ui/src/lib/i18n/messages/en/commands.ts b/packages/ui/src/lib/i18n/messages/en/commands.ts index dd0d12f7..b396200c 100644 --- a/packages/ui/src/lib/i18n/messages/en/commands.ts +++ b/packages/ui/src/lib/i18n/messages/en/commands.ts @@ -130,6 +130,10 @@ export const commandMessages = { "commands.diagnosticsDefault.description": "Toggle default expansion for diagnostics output", "commands.diagnosticsDefault.keywords": "diagnostics, expand, collapse", + "commands.toolInputsVisibility.label": "Tool Inputs Visibility · {state}", + "commands.toolInputsVisibility.description": "Set default visibility for tool call input arguments", + "commands.toolInputsVisibility.keywords": "tool, inputs, arguments, visibility, hide, show, expand, collapse", + "commands.tokenUsageDisplay.label": "Token Usage Display · {state}", "commands.tokenUsageDisplay.description": "Show or hide token and cost stats for assistant messages", "commands.tokenUsageDisplay.keywords": "token, usage, cost, stats", diff --git a/packages/ui/src/lib/i18n/messages/en/toolCall.ts b/packages/ui/src/lib/i18n/messages/en/toolCall.ts index 400899fe..7b380522 100644 --- a/packages/ui/src/lib/i18n/messages/en/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/en/toolCall.ts @@ -5,6 +5,14 @@ export const toolCallMessages = { "toolCall.header.copyTitle": "Copy tool call title", "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.header.showInputTitle": "Show Tool Arguments", + "toolCall.header.showInputAriaLabel": "Show Tool Arguments", + "toolCall.header.hideInputTitle": "Hide Tool Arguments", + "toolCall.header.hideInputAriaLabel": "Hide Tool Arguments", + + "toolCall.io.input": "Tool Input", + "toolCall.io.output": "Tool Output", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "Diff view mode", diff --git a/packages/ui/src/lib/i18n/messages/es/commands.ts b/packages/ui/src/lib/i18n/messages/es/commands.ts index c6a75e7e..43e1fbb1 100644 --- a/packages/ui/src/lib/i18n/messages/es/commands.ts +++ b/packages/ui/src/lib/i18n/messages/es/commands.ts @@ -130,6 +130,10 @@ export const commandMessages = { "commands.diagnosticsDefault.description": "Alternar la expansión por defecto de la salida de diagnósticos", "commands.diagnosticsDefault.keywords": "diagnósticos, expandir, colapsar", + "commands.toolInputsVisibility.label": "Visibilidad de entradas de herramientas · {state}", + "commands.toolInputsVisibility.description": "Configurar la visibilidad por defecto de los argumentos de entrada de llamadas de herramienta", + "commands.toolInputsVisibility.keywords": "herramienta, entradas, argumentos, visibilidad, ocultar, mostrar, expandir, colapsar", + "commands.tokenUsageDisplay.label": "Mostrar uso de tokens · {state}", "commands.tokenUsageDisplay.description": "Mostrar u ocultar estadísticas de tokens y costo en los mensajes del asistente", "commands.tokenUsageDisplay.keywords": "token, uso, costo, estadísticas", diff --git a/packages/ui/src/lib/i18n/messages/es/toolCall.ts b/packages/ui/src/lib/i18n/messages/es/toolCall.ts index c5a7c177..f0453187 100644 --- a/packages/ui/src/lib/i18n/messages/es/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/es/toolCall.ts @@ -5,6 +5,14 @@ export const toolCallMessages = { "toolCall.header.copyTitle": "Copy tool call title", "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.header.showInputTitle": "Show Tool Arguments", + "toolCall.header.showInputAriaLabel": "Show Tool Arguments", + "toolCall.header.hideInputTitle": "Hide Tool Arguments", + "toolCall.header.hideInputAriaLabel": "Hide Tool Arguments", + + "toolCall.io.input": "Tool Input", + "toolCall.io.output": "Tool Output", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "Modo de vista de diff", diff --git a/packages/ui/src/lib/i18n/messages/fr/commands.ts b/packages/ui/src/lib/i18n/messages/fr/commands.ts index 63e7c666..505baa19 100644 --- a/packages/ui/src/lib/i18n/messages/fr/commands.ts +++ b/packages/ui/src/lib/i18n/messages/fr/commands.ts @@ -130,6 +130,10 @@ export const commandMessages = { "commands.diagnosticsDefault.description": "Choisir l'ouverture par défaut de la sortie des diagnostics", "commands.diagnosticsDefault.keywords": "diagnostics, développer, réduire", + "commands.toolInputsVisibility.label": "Visibilité des entrées d'outil · {state}", + "commands.toolInputsVisibility.description": "Définir la visibilité par défaut des arguments d'entrée des appels d'outil", + "commands.toolInputsVisibility.keywords": "outil, entrées, arguments, visibilité, masquer, afficher, développer, réduire", + "commands.tokenUsageDisplay.label": "Affichage de l'usage des tokens · {state}", "commands.tokenUsageDisplay.description": "Afficher ou masquer les stats de tokens et de coût pour les messages de l'assistant", "commands.tokenUsageDisplay.keywords": "token, usage, coût, stats", diff --git a/packages/ui/src/lib/i18n/messages/fr/toolCall.ts b/packages/ui/src/lib/i18n/messages/fr/toolCall.ts index 1af99486..75685849 100644 --- a/packages/ui/src/lib/i18n/messages/fr/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/fr/toolCall.ts @@ -5,6 +5,14 @@ export const toolCallMessages = { "toolCall.header.copyTitle": "Copy tool call title", "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.header.showInputTitle": "Show Tool Arguments", + "toolCall.header.showInputAriaLabel": "Show Tool Arguments", + "toolCall.header.hideInputTitle": "Hide Tool Arguments", + "toolCall.header.hideInputAriaLabel": "Hide Tool Arguments", + + "toolCall.io.input": "Tool Input", + "toolCall.io.output": "Tool Output", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "Mode d'affichage du diff", diff --git a/packages/ui/src/lib/i18n/messages/ja/commands.ts b/packages/ui/src/lib/i18n/messages/ja/commands.ts index 75c1c5f3..de21f94c 100644 --- a/packages/ui/src/lib/i18n/messages/ja/commands.ts +++ b/packages/ui/src/lib/i18n/messages/ja/commands.ts @@ -130,6 +130,10 @@ export const commandMessages = { "commands.diagnosticsDefault.description": "診断出力を既定で展開するか切り替え", "commands.diagnosticsDefault.keywords": "診断, 展開, 折りたたみ, diagnostics, expand, collapse", + "commands.toolInputsVisibility.label": "ツール入力の表示 · {state}", + "commands.toolInputsVisibility.description": "ツール呼び出しの入力引数の既定の表示状態を設定します", + "commands.toolInputsVisibility.keywords": "ツール, 入力, 引数, 表示, 非表示, 展開, 折りたたみ, tool, inputs, arguments, visibility, hide, show, expand, collapse", + "commands.tokenUsageDisplay.label": "トークン使用量表示 · {state}", "commands.tokenUsageDisplay.description": "アシスタントメッセージのトークン/コスト統計を表示/非表示", "commands.tokenUsageDisplay.keywords": "トークン, 使用量, コスト, 統計, token, usage, cost, stats", diff --git a/packages/ui/src/lib/i18n/messages/ja/toolCall.ts b/packages/ui/src/lib/i18n/messages/ja/toolCall.ts index 2e5d036f..9251c719 100644 --- a/packages/ui/src/lib/i18n/messages/ja/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/ja/toolCall.ts @@ -5,6 +5,14 @@ export const toolCallMessages = { "toolCall.header.copyTitle": "Copy tool call title", "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.header.showInputTitle": "Show Tool Arguments", + "toolCall.header.showInputAriaLabel": "Show Tool Arguments", + "toolCall.header.hideInputTitle": "Hide Tool Arguments", + "toolCall.header.hideInputAriaLabel": "Hide Tool Arguments", + + "toolCall.io.input": "Tool Input", + "toolCall.io.output": "Tool Output", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "diff 表示モード", diff --git a/packages/ui/src/lib/i18n/messages/ru/commands.ts b/packages/ui/src/lib/i18n/messages/ru/commands.ts index 068f020d..55d2a791 100644 --- a/packages/ui/src/lib/i18n/messages/ru/commands.ts +++ b/packages/ui/src/lib/i18n/messages/ru/commands.ts @@ -130,6 +130,10 @@ export const commandMessages = { "commands.diagnosticsDefault.description": "Переключить, разворачивать ли вывод диагностики по умолчанию", "commands.diagnosticsDefault.keywords": "diagnostics, развернуть, свернуть", + "commands.toolInputsVisibility.label": "Видимость входных данных инструмента · {state}", + "commands.toolInputsVisibility.description": "Установить видимость аргументов входа вызовов инструментов по умолчанию", + "commands.toolInputsVisibility.keywords": "инструмент, вход, аргументы, видимость, скрыть, показать, раскрыть, свернуть, tool, inputs, arguments, visibility, hide, show, expand, collapse", + "commands.tokenUsageDisplay.label": "Отображение token-статистики · {state}", "commands.tokenUsageDisplay.description": "Показать или скрыть статистику token и стоимости для сообщений ассистента", "commands.tokenUsageDisplay.keywords": "token, usage, cost, статистика", diff --git a/packages/ui/src/lib/i18n/messages/ru/toolCall.ts b/packages/ui/src/lib/i18n/messages/ru/toolCall.ts index 8ccc6565..6ca953df 100644 --- a/packages/ui/src/lib/i18n/messages/ru/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/ru/toolCall.ts @@ -5,6 +5,14 @@ export const toolCallMessages = { "toolCall.header.copyTitle": "Copy tool call title", "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.header.showInputTitle": "Show Tool Arguments", + "toolCall.header.showInputAriaLabel": "Show Tool Arguments", + "toolCall.header.hideInputTitle": "Hide Tool Arguments", + "toolCall.header.hideInputAriaLabel": "Hide Tool Arguments", + + "toolCall.io.input": "Tool Input", + "toolCall.io.output": "Tool Output", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "Режим просмотра diff", diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts index 85997488..69eba72f 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts @@ -130,6 +130,10 @@ export const commandMessages = { "commands.diagnosticsDefault.description": "切换诊断输出是否默认展开", "commands.diagnosticsDefault.keywords": "diagnostics, expand, collapse, 诊断, 展开, 折叠", + "commands.toolInputsVisibility.label": "工具输入可见性 · {state}", + "commands.toolInputsVisibility.description": "设置工具调用输入参数的默认可见性", + "commands.toolInputsVisibility.keywords": "工具, 输入, 参数, 可见性, 隐藏, 显示, 展开, 折叠, tool, inputs, arguments, visibility, hide, show, expand, collapse", + "commands.tokenUsageDisplay.label": "Token 使用显示 · {state}", "commands.tokenUsageDisplay.description": "显示或隐藏助手消息的 token 和费用统计", "commands.tokenUsageDisplay.keywords": "token, usage, cost, stats, 令牌, 用量, 费用, 统计", diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts index 49a848c9..a0f5d30c 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts @@ -5,6 +5,14 @@ export const toolCallMessages = { "toolCall.header.copyTitle": "Copy tool call title", "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.header.showInputTitle": "Show Tool Arguments", + "toolCall.header.showInputAriaLabel": "Show Tool Arguments", + "toolCall.header.hideInputTitle": "Hide Tool Arguments", + "toolCall.header.hideInputAriaLabel": "Hide Tool Arguments", + + "toolCall.io.input": "Tool Input", + "toolCall.io.output": "Tool Output", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "Diff 视图模式", diff --git a/packages/ui/src/stores/preferences.tsx b/packages/ui/src/stores/preferences.tsx index 00b0a04e..8ac2ead0 100644 --- a/packages/ui/src/stores/preferences.tsx +++ b/packages/ui/src/stores/preferences.tsx @@ -25,6 +25,7 @@ export interface ModelPreference { export type DiffViewMode = "split" | "unified" export type ExpansionPreference = "expanded" | "collapsed" +export type ToolInputsVisibilityPreference = "hidden" | "collapsed" | "expanded" export type ListeningMode = "local" | "all" export interface UiSettings { @@ -37,6 +38,7 @@ export interface UiSettings { diffViewMode: DiffViewMode toolOutputExpansion: ExpansionPreference diagnosticsExpansion: ExpansionPreference + toolInputsVisibility: ToolInputsVisibilityPreference showUsageMetrics: boolean autoCleanupBlankSessions: boolean @@ -108,6 +110,7 @@ const defaultUiSettings: UiSettings = { diffViewMode: "split", toolOutputExpansion: "expanded", diagnosticsExpansion: "expanded", + toolInputsVisibility: "collapsed", showUsageMetrics: true, autoCleanupBlankSessions: true, @@ -130,6 +133,10 @@ function normalizeUiSettings(input?: Partial | null): UiSettings { diffViewMode: sanitized.diffViewMode ?? defaultUiSettings.diffViewMode, toolOutputExpansion: sanitized.toolOutputExpansion ?? defaultUiSettings.toolOutputExpansion, diagnosticsExpansion: sanitized.diagnosticsExpansion ?? defaultUiSettings.diagnosticsExpansion, + toolInputsVisibility: + sanitized.toolInputsVisibility === "hidden" || sanitized.toolInputsVisibility === "collapsed" || sanitized.toolInputsVisibility === "expanded" + ? sanitized.toolInputsVisibility + : defaultUiSettings.toolInputsVisibility, showUsageMetrics: sanitized.showUsageMetrics ?? defaultUiSettings.showUsageMetrics, autoCleanupBlankSessions: sanitized.autoCleanupBlankSessions ?? defaultUiSettings.autoCleanupBlankSessions, osNotificationsEnabled: sanitized.osNotificationsEnabled ?? defaultUiSettings.osNotificationsEnabled, @@ -439,6 +446,11 @@ function setDiagnosticsExpansion(mode: ExpansionPreference): void { updateUiSettings({ diagnosticsExpansion: mode }) } +function setToolInputsVisibility(mode: ToolInputsVisibilityPreference): void { + if (preferences().toolInputsVisibility === mode) return + updateUiSettings({ toolInputsVisibility: mode }) +} + function setThinkingBlocksExpansion(mode: ExpansionPreference): void { if (preferences().thinkingBlocksExpansion === mode) return updateUiSettings({ thinkingBlocksExpansion: mode }) @@ -536,6 +548,7 @@ interface ConfigContextValue { setToolOutputExpansion: typeof setToolOutputExpansion setDiagnosticsExpansion: typeof setDiagnosticsExpansion setThinkingBlocksExpansion: typeof setThinkingBlocksExpansion + setToolInputsVisibility: typeof setToolInputsVisibility // instance scoped setAgentModelPreference: typeof setAgentModelPreference @@ -579,6 +592,7 @@ const configContextValue: ConfigContextValue = { setToolOutputExpansion, setDiagnosticsExpansion, setThinkingBlocksExpansion, + setToolInputsVisibility, setAgentModelPreference, getAgentModelPreference, } diff --git a/packages/ui/src/styles/messaging/tool-call.css b/packages/ui/src/styles/messaging/tool-call.css index 47b732fb..8997f1e8 100644 --- a/packages/ui/src/styles/messaging/tool-call.css +++ b/packages/ui/src/styles/messaging/tool-call.css @@ -87,6 +87,7 @@ @apply flex items-stretch w-full; background-color: transparent; color: var(--text-primary); + border-bottom: 1px solid var(--tool-call-border-color); } .tool-call-header:hover { @@ -127,11 +128,30 @@ cursor: pointer; } +.tool-call-header-input { + @apply inline-flex items-center justify-center; + background-color: transparent; + border: none; + color: var(--text-secondary); + padding: 0 0.5rem; + border-radius: 0; + cursor: pointer; +} + .tool-call-header-copy:hover { background-color: transparent; color: var(--text-primary); } +.tool-call-header-input:hover { + background-color: transparent; + color: var(--text-primary); +} + +.tool-call-header-input[aria-pressed="true"] { + color: var(--text-primary); +} + .tool-call-header-status { @apply inline-flex items-center justify-center; font-size: 0.95rem; @@ -213,6 +233,63 @@ font-size: var(--font-size-xs); } + +.tool-call-io-sections { + display: flex; + flex-direction: column; + gap: var(--space-xs); + padding: 0; +} + +.tool-call-io-section { + border: 1px solid var(--tool-call-border-color); + overflow: hidden; + background-color: transparent; + border-radius: 0; +} + +.tool-call-io-toggle { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.75rem; + padding: 0.5rem; + background-color: var(--surface-secondary); + border: none; + border-bottom: 1px solid var(--tool-call-border-color); + width: 100%; + text-align: left; + font-size: 0.875rem; + font-weight: normal; + color: var(--text-primary); + 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: inherit; + color: inherit; +} + +.tool-call-io-body { + background-color: var(--surface-code); +} + +/* IO sections provide the outer frame; avoid double borders on markdown frames. */ +.tool-call-io-body .tool-call-markdown { + border: none; +} + .tool-call-markdown { background-color: var(--surface-code); /* Keep a visible frame around the scroll viewport (not the content). */