Skip to content

Commit 57a5d5f

Browse files
committed
tweak(ui): show assistant response meta on hover
Adds hover-only metadata after the assistant copy icon showing agent, provider, model, and response duration.
1 parent 14684d8 commit 57a5d5f

2 files changed

Lines changed: 51 additions & 5 deletions

File tree

packages/ui/src/components/message-part.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
display: flex;
169169
align-items: center;
170170
justify-content: flex-start;
171+
gap: 10px;
171172
opacity: 0;
172173
pointer-events: none;
173174
transition: opacity 0.15s ease;
@@ -179,6 +180,10 @@
179180
}
180181
}
181182

183+
[data-slot="text-part-meta"] {
184+
user-select: none;
185+
}
186+
182187
[data-slot="text-part-copy-wrapper"][data-interrupted] {
183188
width: 100%;
184189
justify-content: flex-end;

packages/ui/src/components/message-part.tsx

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,47 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
902902
() =>
903903
props.message.role === "assistant" && (props.message as AssistantMessage).error?.name === "MessageAbortedError",
904904
)
905+
906+
const provider = createMemo(() => {
907+
if (props.message.role !== "assistant") return ""
908+
const id = (props.message as AssistantMessage).providerID
909+
const match = data.store.provider?.all?.find((p) => p.id === id)
910+
return match?.name ?? id
911+
})
912+
913+
const model = createMemo(() => {
914+
if (props.message.role !== "assistant") return ""
915+
const message = props.message as AssistantMessage
916+
const match = data.store.provider?.all?.find((p) => p.id === message.providerID)
917+
return match?.models?.[message.modelID]?.name ?? message.modelID
918+
})
919+
920+
const duration = createMemo(() => {
921+
if (props.message.role !== "assistant") return ""
922+
const message = props.message as AssistantMessage
923+
const completed = message.time.completed
924+
if (typeof completed !== "number") return ""
925+
const ms = completed - message.time.created
926+
if (!(ms >= 0)) return ""
927+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`
928+
const minutes = Math.floor(ms / 60_000)
929+
const seconds = Math.round((ms - minutes * 60_000) / 1000)
930+
return `${minutes}m ${seconds}s`
931+
})
932+
933+
const meta = createMemo(() => {
934+
if (props.message.role !== "assistant") return ""
935+
const agent = (props.message as AssistantMessage).agent
936+
const items = [
937+
agent ? agent[0]?.toUpperCase() + agent.slice(1) : "",
938+
provider(),
939+
model(),
940+
duration(),
941+
interrupted() ? i18n.t("ui.message.interrupted") : "",
942+
]
943+
return items.filter((x) => !!x).join(" \u00B7 ")
944+
})
945+
905946
const displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
906947
const throttledText = createThrottledValue(displayText)
907948
const isLastTextPart = createMemo(() => {
@@ -934,11 +975,6 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
934975
</div>
935976
<Show when={showCopy()}>
936977
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
937-
<Show when={interrupted()}>
938-
<span data-slot="text-part-interrupted" class="text-13-regular text-text-weak cursor-default">
939-
{i18n.t("ui.message.interrupted")}
940-
</span>
941-
</Show>
942978
<Tooltip
943979
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
944980
placement="top"
@@ -953,6 +989,11 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
953989
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
954990
/>
955991
</Tooltip>
992+
<Show when={meta()}>
993+
<span data-slot="text-part-meta" class="text-12-regular text-text-weak cursor-default">
994+
{meta()}
995+
</span>
996+
</Show>
956997
</div>
957998
</Show>
958999
</div>

0 commit comments

Comments
 (0)