From 6c42b644661b3790170e3868880afa938b511ed1 Mon Sep 17 00:00:00 2001 From: Shantur Rathore Date: Thu, 5 Feb 2026 23:30:38 +0000 Subject: [PATCH] feat(ui): copy tool call header title --- packages/ui/src/components/tool-call.tsx | 49 +++++++++++++++---- .../ui/src/lib/i18n/messages/en/toolCall.ts | 3 ++ .../ui/src/lib/i18n/messages/es/toolCall.ts | 3 ++ .../ui/src/lib/i18n/messages/fr/toolCall.ts | 3 ++ .../ui/src/lib/i18n/messages/ja/toolCall.ts | 3 ++ .../ui/src/lib/i18n/messages/ru/toolCall.ts | 3 ++ .../src/lib/i18n/messages/zh-Hans/toolCall.ts | 3 ++ .../ui/src/styles/messaging/tool-call.css | 49 ++++++++++++++----- 8 files changed, 94 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/components/tool-call.tsx b/packages/ui/src/components/tool-call.tsx index 242b5a52..9b9e5c01 100644 --- a/packages/ui/src/components/tool-call.tsx +++ b/packages/ui/src/components/tool-call.tsx @@ -1,9 +1,11 @@ import { createSignal, Show, createEffect, createMemo, onCleanup } from "solid-js" +import { Copy } from "lucide-solid" import { messageStoreBus } from "../stores/message-v2/bus" import { useTheme } from "../lib/theme" import { useGlobalCache } from "../lib/hooks/use-global-cache" import { useConfig } from "../stores/preferences" import { activeInterruption, sendPermissionResponse, sendQuestionReject, sendQuestionReply } from "../stores/instances" +import { copyToClipboard } from "../lib/clipboard" import type { PermissionRequestLike } from "../types/permission" import { getPermissionSessionId } from "../types/permission" import type { QuestionRequest } from "@opencode-ai/sdk/v2" @@ -659,6 +661,19 @@ export default function ToolCall(props: ToolCallProps) { return getToolName(currentTool) } + const headerText = createMemo(() => { + // Keep this as a memo so copy always matches what's rendered. + return renderToolTitle() + }) + + const handleCopyHeader = async (event: MouseEvent) => { + event.preventDefault() + event.stopPropagation() + const text = headerText() + if (!text) return + await copyToClipboard(text) + } + const renderToolBody = () => { return renderer().renderBody(rendererContext) } @@ -762,16 +777,32 @@ export default function ToolCall(props: ToolCallProps) { }} class={`tool-call ${combinedStatusClass()}`} > - + + + + - + {expanded() && (
diff --git a/packages/ui/src/lib/i18n/messages/en/toolCall.ts b/packages/ui/src/lib/i18n/messages/en/toolCall.ts index 645a7a86..400899fe 100644 --- a/packages/ui/src/lib/i18n/messages/en/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/en/toolCall.ts @@ -2,6 +2,9 @@ export const toolCallMessages = { "toolCall.pending.waitingToRun": "Waiting to run...", "toolCall.error.label": "Error:", + "toolCall.header.copyTitle": "Copy tool call title", + "toolCall.header.copyAriaLabel": "Copy tool call title", + "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/toolCall.ts b/packages/ui/src/lib/i18n/messages/es/toolCall.ts index e42c6ea0..c5a7c177 100644 --- a/packages/ui/src/lib/i18n/messages/es/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/es/toolCall.ts @@ -2,6 +2,9 @@ export const toolCallMessages = { "toolCall.pending.waitingToRun": "Esperando para ejecutar...", "toolCall.error.label": "Error:", + "toolCall.header.copyTitle": "Copy tool call title", + "toolCall.header.copyAriaLabel": "Copy tool call title", + "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/toolCall.ts b/packages/ui/src/lib/i18n/messages/fr/toolCall.ts index 8de4c8e2..1af99486 100644 --- a/packages/ui/src/lib/i18n/messages/fr/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/fr/toolCall.ts @@ -2,6 +2,9 @@ export const toolCallMessages = { "toolCall.pending.waitingToRun": "En attente d'exécution...", "toolCall.error.label": "Erreur :", + "toolCall.header.copyTitle": "Copy tool call title", + "toolCall.header.copyAriaLabel": "Copy tool call title", + "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/toolCall.ts b/packages/ui/src/lib/i18n/messages/ja/toolCall.ts index 68c1b71f..2e5d036f 100644 --- a/packages/ui/src/lib/i18n/messages/ja/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/ja/toolCall.ts @@ -2,6 +2,9 @@ export const toolCallMessages = { "toolCall.pending.waitingToRun": "実行待ち...", "toolCall.error.label": "エラー:", + "toolCall.header.copyTitle": "Copy tool call title", + "toolCall.header.copyAriaLabel": "Copy tool call title", + "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/toolCall.ts b/packages/ui/src/lib/i18n/messages/ru/toolCall.ts index daa2ae82..8ccc6565 100644 --- a/packages/ui/src/lib/i18n/messages/ru/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/ru/toolCall.ts @@ -2,6 +2,9 @@ export const toolCallMessages = { "toolCall.pending.waitingToRun": "Ожидание запуска…", "toolCall.error.label": "Ошибка:", + "toolCall.header.copyTitle": "Copy tool call title", + "toolCall.header.copyAriaLabel": "Copy tool call title", + "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/toolCall.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts index 7a53ea33..49a848c9 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/toolCall.ts @@ -2,6 +2,9 @@ export const toolCallMessages = { "toolCall.pending.waitingToRun": "等待运行...", "toolCall.error.label": "错误:", + "toolCall.header.copyTitle": "Copy tool call title", + "toolCall.header.copyAriaLabel": "Copy tool call title", + "toolCall.diff.label": "Diff", "toolCall.diff.label.withPath": "Diff · {path}", "toolCall.diff.viewMode.ariaLabel": "Diff 视图模式", diff --git a/packages/ui/src/styles/messaging/tool-call.css b/packages/ui/src/styles/messaging/tool-call.css index b768cb31..47b732fb 100644 --- a/packages/ui/src/styles/messaging/tool-call.css +++ b/packages/ui/src/styles/messaging/tool-call.css @@ -84,36 +84,59 @@ } .tool-call-header { + @apply flex items-stretch w-full; + background-color: transparent; + color: var(--text-primary); +} + +.tool-call-header:hover { + background-color: var(--surface-hover); +} + +.tool-call-header-toggle { @apply flex items-center gap-2 p-2 w-full bg-transparent border-none cursor-pointer text-left; font-family: var(--font-family-mono); font-size: 13px; border-radius: 0; color: var(--text-primary); + flex: 1; } -.tool-call-header::before { +.tool-call-header-toggle::before { content: "▶"; font-size: 11px; margin-right: 0.35rem; color: var(--text-secondary); } -.tool-call-header[aria-expanded="true"]::before { +.tool-call-header-toggle[aria-expanded="true"]::before { content: "▼"; } -.tool-call-header::after { - content: attr(data-status-icon); +.tool-call-header-toggle:hover { + background-color: transparent; +} + +.tool-call-header-copy { + @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-status { + @apply inline-flex items-center justify-center; font-size: 0.95rem; - margin-left: 0.5rem; -} - -.tool-call-header[data-status-icon=""]::after { - margin-left: 0; -} - -.tool-call-header:hover { - background-color: var(--surface-hover); + color: var(--text-secondary); + padding: 0 0.5rem; } .tool-call-summary {