tweak(ui): show assistant response meta on hover
Adds hover-only metadata after the assistant copy icon showing agent, provider, model, and response duration.
This commit is contained in:
@@ -168,6 +168,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
gap: 10px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.15s ease;
|
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] {
|
[data-slot="text-part-copy-wrapper"][data-interrupted] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|||||||
@@ -902,6 +902,47 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
|||||||
() =>
|
() =>
|
||||||
props.message.role === "assistant" && (props.message as AssistantMessage).error?.name === "MessageAbortedError",
|
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 displayText = () => relativizeProjectPaths((part.text ?? "").trim(), data.directory)
|
||||||
const throttledText = createThrottledValue(displayText)
|
const throttledText = createThrottledValue(displayText)
|
||||||
const isLastTextPart = createMemo(() => {
|
const isLastTextPart = createMemo(() => {
|
||||||
@@ -934,11 +975,6 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
|||||||
</div>
|
</div>
|
||||||
<Show when={showCopy()}>
|
<Show when={showCopy()}>
|
||||||
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
|
<div data-slot="text-part-copy-wrapper" data-interrupted={interrupted() ? "" : undefined}>
|
||||||
<Show when={interrupted()}>
|
|
||||||
<span data-slot="text-part-interrupted" class="text-13-regular text-text-weak cursor-default">
|
|
||||||
{i18n.t("ui.message.interrupted")}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
value={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
||||||
placement="top"
|
placement="top"
|
||||||
@@ -953,6 +989,11 @@ PART_MAPPING["text"] = function TextPartDisplay(props) {
|
|||||||
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Show when={meta()}>
|
||||||
|
<span data-slot="text-part-meta" class="text-12-regular text-text-weak cursor-default">
|
||||||
|
{meta()}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user