From 57a5d5fd342b6451384d7549b00189b6891116bf Mon Sep 17 00:00:00 2001 From: David Hill Date: Tue, 17 Feb 2026 15:06:53 +0000 Subject: [PATCH] tweak(ui): show assistant response meta on hover Adds hover-only metadata after the assistant copy icon showing agent, provider, model, and response duration. --- packages/ui/src/components/message-part.css | 5 ++ packages/ui/src/components/message-part.tsx | 51 +++++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index cd15117a0..5b0a3cc17 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -168,6 +168,7 @@ display: flex; align-items: center; justify-content: flex-start; + gap: 10px; opacity: 0; pointer-events: none; transition: opacity 0.15s ease; @@ -179,6 +180,10 @@ } } + [data-slot="text-part-meta"] { + user-select: none; + } + [data-slot="text-part-copy-wrapper"][data-interrupted] { width: 100%; justify-content: flex-end; diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index bdd323b71..8e74b14ba 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -902,6 +902,47 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { () => props.message.role === "assistant" && (props.message as AssistantMessage).error?.name === "MessageAbortedError", ) + + const provider = createMemo(() => { + if (props.message.role !== "assistant") return "" + const id = (props.message as AssistantMessage).providerID + const match = data.store.provider?.all?.find((p) => p.id === id) + return match?.name ?? id + }) + + const model = createMemo(() => { + if (props.message.role !== "assistant") return "" + const message = props.message as AssistantMessage + const match = data.store.provider?.all?.find((p) => p.id === message.providerID) + return match?.models?.[message.modelID]?.name ?? message.modelID + }) + + const duration = createMemo(() => { + if (props.message.role !== "assistant") return "" + const message = props.message as AssistantMessage + const completed = message.time.completed + if (typeof completed !== "number") return "" + const ms = completed - message.time.created + if (!(ms >= 0)) return "" + if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s` + const minutes = Math.floor(ms / 60_000) + const seconds = Math.round((ms - minutes * 60_000) / 1000) + return `${minutes}m ${seconds}s` + }) + + const meta = createMemo(() => { + if (props.message.role !== "assistant") return "" + const agent = (props.message as AssistantMessage).agent + const items = [ + agent ? agent[0]?.toUpperCase() + agent.slice(1) : "", + provider(), + model(), + duration(), + interrupted() ? i18n.t("ui.message.interrupted") : "", + ] + return items.filter((x) => !!x).join(" \u00B7 ") + }) + const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory) const throttledText = createThrottledValue(displayText) const isLastTextPart = createMemo(() => { @@ -934,11 +975,6 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
- - - {i18n.t("ui.message.interrupted")} - - + + + {meta()} + +