refine step and stream spacing
This commit is contained in:
@@ -339,13 +339,22 @@ export default function MessageStreamV2(props: MessageStreamV2Props) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.type === "step-start" || part.type === "step-finish") {
|
if (part.type === "step-start") {
|
||||||
flushContent()
|
flushContent()
|
||||||
const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`)
|
const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`)
|
||||||
items.push({ type: part.type, key, part, messageInfo })
|
items.push({ type: part.type, key, part, messageInfo })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (part.type === "step-finish") {
|
||||||
|
flushContent()
|
||||||
|
if (showUsageMetrics) {
|
||||||
|
const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`)
|
||||||
|
items.push({ type: part.type, key, part, messageInfo })
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (part.type === "reasoning") {
|
if (part.type === "reasoning") {
|
||||||
flushContent()
|
flushContent()
|
||||||
if (showThinking && reasoningHasRenderableContent(part)) {
|
if (showThinking && reasoningHasRenderableContent(part)) {
|
||||||
@@ -741,17 +750,6 @@ interface StepCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StepCard(props: StepCardProps) {
|
function StepCard(props: StepCardProps) {
|
||||||
const snapshot = () => {
|
|
||||||
const value = (props.part as { snapshot?: string }).snapshot
|
|
||||||
return typeof value === "string" ? value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const reason = () => {
|
|
||||||
if (props.kind !== "finish") return ""
|
|
||||||
const value = (props.part as { reason?: string }).reason
|
|
||||||
return typeof value === "string" ? value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestamp = () => {
|
const timestamp = () => {
|
||||||
const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now()
|
const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now()
|
||||||
const date = new Date(value)
|
const date = new Date(value)
|
||||||
@@ -800,60 +798,67 @@ function StepCard(props: StepCardProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UsageInfo = NonNullable<ReturnType<typeof usageStats>>
|
||||||
|
|
||||||
|
const renderUsageChips = (usage: UsageInfo) => (
|
||||||
|
<div class="message-step-usage">
|
||||||
|
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
||||||
|
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Input</span>
|
||||||
|
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage.input)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
||||||
|
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Output</span>
|
||||||
|
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage.output)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
||||||
|
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Reasoning</span>
|
||||||
|
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage.reasoning)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
||||||
|
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Cache Read</span>
|
||||||
|
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage.cacheRead)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
||||||
|
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Cache Write</span>
|
||||||
|
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage.cacheWrite)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
||||||
|
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Cost</span>
|
||||||
|
<span class="font-semibold text-[var(--text-primary)]">{formatCostValue(usage.cost)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (props.kind === "finish") {
|
||||||
|
const usage = usageStats()
|
||||||
|
if (!usage) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class={`message-step-card message-step-finish`}>
|
||||||
|
{renderUsageChips(usage)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`message-step-card ${props.kind === "start" ? "message-step-start" : "message-step-finish"}`}>
|
<div class={`message-step-card message-step-start`}>
|
||||||
<div class="message-step-heading">
|
<div class="message-step-heading">
|
||||||
<div class="message-step-title">
|
<div class="message-step-title">
|
||||||
<div class="message-step-title-left">
|
<div class="message-step-title-left">
|
||||||
<Show when={props.kind === "start" && props.showAgentMeta && (agentIdentifier() || modelIdentifier())}>
|
<Show when={props.showAgentMeta && (agentIdentifier() || modelIdentifier())}>
|
||||||
<span class="message-step-meta-inline">
|
<span class="message-step-meta-inline">
|
||||||
<Show when={agentIdentifier()}>{(value) => <span>Agent: {value()}</span>}</Show>
|
<Show when={agentIdentifier()}>{(value) => <span>Agent: {value()}</span>}</Show>
|
||||||
<Show when={modelIdentifier()}>{(value) => <span>Model: {value()}</span>}</Show>
|
<Show when={modelIdentifier()}>{(value) => <span>Model: {value()}</span>}</Show>
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
<span>{props.kind === "start" ? "Step started" : "Step finished"}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="message-step-time">{timestamp()}</span>
|
<span class="message-step-time">{timestamp()}</span>
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.kind === "finish" && reason()}>{(value) => <span class="message-step-reason">{value()}</span>}</Show>
|
|
||||||
</div>
|
</div>
|
||||||
<Show when={usageStats()}>
|
|
||||||
{(usage) => (
|
|
||||||
<div class="mt-3 flex flex-wrap items-center gap-1 text-[10px] text-[var(--text-muted)]">
|
|
||||||
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
|
||||||
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Input</span>
|
|
||||||
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage().input)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
|
||||||
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Output</span>
|
|
||||||
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage().output)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
|
||||||
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Reasoning</span>
|
|
||||||
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage().reasoning)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
|
||||||
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Cache Read</span>
|
|
||||||
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage().cacheRead)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
|
||||||
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Cache Write</span>
|
|
||||||
<span class="font-semibold text-[var(--text-primary)]">{formatTokenTotal(usage().cacheWrite)}</span>
|
|
||||||
</div>
|
|
||||||
<div class="inline-flex items-center gap-1 rounded-full border border-[var(--border-base)] px-2 py-0.5 text-[10px]">
|
|
||||||
<span class="uppercase text-[9px] tracking-wide text-[var(--text-muted)]">Cost</span>
|
|
||||||
<span class="font-semibold text-[var(--text-primary)]">{formatCostValue(usage().cost)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<Show when={props.kind === "finish" && !props.showUsage}>
|
|
||||||
<div class="message-step-finish-spacer" aria-hidden="true" />
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCostValue(value: number) {
|
function formatCostValue(value: number) {
|
||||||
if (!value) return "$0.00"
|
if (!value) return "$0.00"
|
||||||
if (value < 0.01) return `$${value.toPrecision(2)}`
|
if (value < 0.01) return `$${value.toPrecision(2)}`
|
||||||
|
|||||||
@@ -18,15 +18,25 @@
|
|||||||
.message-step-start {
|
.message-step-start {
|
||||||
background-color: var(--message-assistant-bg);
|
background-color: var(--message-assistant-bg);
|
||||||
border-left: 4px solid var(--message-assistant-border);
|
border-left: 4px solid var(--message-assistant-border);
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-step-finish {
|
.message-step-finish {
|
||||||
background-color: var(--message-assistant-bg);
|
background-color: var(--message-assistant-bg);
|
||||||
border-left: 4px solid var(--message-assistant-border);
|
border-left: 4px solid var(--message-assistant-border);
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-step-usage {
|
||||||
|
@apply flex flex-wrap items-center gap-1 text-[10px] text-[var(--text-muted)];
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-step-heading {
|
||||||
|
@apply flex flex-wrap items-center gap-2 text-xs;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.message-queued-badge {
|
.message-queued-badge {
|
||||||
@apply inline-block font-bold px-3 py-1 rounded mb-3 text-xs tracking-wide;
|
@apply inline-block font-bold px-3 py-1 rounded mb-3 text-xs tracking-wide;
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
.message-stream-block {
|
.message-stream-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-scroll-button-wrapper {
|
.message-scroll-button-wrapper {
|
||||||
|
|||||||
Reference in New Issue
Block a user