Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/api/routers/runs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,17 @@ export const runsRouter = router({
b.kind === 'tool_use',
)
.map((b) => ({ name: b.name, inputSummary: b.inputSummary }));
const thinkingChars = blocks
.filter((b): b is { kind: 'thinking'; text: string } => b.kind === 'thinking')
.reduce((sum, b) => sum + b.text.length, 0);
const thinkingBlocks = blocks.filter(
(b): b is { kind: 'thinking'; text: string } => b.kind === 'thinking',
);
const thinkingChars = thinkingBlocks.reduce((sum, b) => sum + b.text.length, 0);
const thinkingPreview =
thinkingChars > 0
? thinkingBlocks
.map((b) => b.text)
.join(' ')
.slice(0, 200)
: null;
return {
id: c.id,
runId: c.runId,
Expand All @@ -143,6 +151,7 @@ export const runsRouter = router({
toolCalls,
textPreview,
thinkingChars: thinkingChars > 0 ? thinkingChars : null,
thinkingPreview,
};
});
return { engine: run.engine ?? 'unknown', calls };
Expand Down
55 changes: 3 additions & 52 deletions web/src/components/llm-calls/llm-call-detail.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type ParsedBlock, parseLlmResponse } from '@/lib/llm-response-parser.js';
import { getToolStyle } from '@/lib/tool-style.js';
import { trpc } from '@/lib/trpc.js';
import { formatCost } from '@/lib/utils.js';
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';

Expand All @@ -10,11 +9,6 @@ interface LlmCallDetailProps {
callNumber: number;
}

interface MetaItem {
label: string;
mono?: boolean;
}

function TextBlock({ text }: { text: string }) {
return (
<div className="rounded-md bg-muted/30 p-3 text-sm whitespace-pre-wrap leading-relaxed">
Expand Down Expand Up @@ -76,38 +70,8 @@ function formatRawContent(response: string | null | undefined): string {
}
}

function buildMetaItems(call: {
model?: string | null;
createdAt?: Date | string | null;
inputTokens?: number | null;
outputTokens?: number | null;
cachedTokens?: number | null;
costUsd?: string | null;
}): MetaItem[] {
const items: MetaItem[] = [];
if (call.model) items.push({ label: call.model, mono: true });
if (call.createdAt) {
const timeStr = new Date(call.createdAt).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
items.push({ label: timeStr });
}
const tokenParts: string[] = [];
if (call.inputTokens != null) tokenParts.push(`${call.inputTokens.toLocaleString()} in`);
if (call.outputTokens != null) tokenParts.push(`${call.outputTokens.toLocaleString()} out`);
if (tokenParts.length > 0) items.push({ label: tokenParts.join(' / ') });
if (call.cachedTokens && call.cachedTokens > 0)
items.push({ label: `+${call.cachedTokens.toLocaleString()} cached` });
const costStr = formatCost(call.costUsd);
if (costStr !== '—') items.push({ label: costStr });
return items;
}

export function LlmCallDetail({ runId, callNumber }: LlmCallDetailProps) {
const [showRaw, setShowRaw] = useState(false);
const [showRaw, setShowRaw] = useState(true);

const callQuery = useQuery(trpc.runs.getLlmCall.queryOptions({ runId, callNumber }));

Expand All @@ -122,24 +86,11 @@ export function LlmCallDetail({ runId, callNumber }: LlmCallDetailProps) {
const call = callQuery.data;
const parsed = parseLlmResponse(call.response);
const hasContent = parsed.blocks.length > 0;
const metaItems = buildMetaItems(call);

return (
<div className="border-t border-border bg-muted/10 p-4 space-y-3">
{/* Metadata bar */}
{metaItems.length > 0 && (
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{metaItems.map((item) => (
<span key={item.label} className={item.mono ? 'font-mono' : undefined}>
{item.label}
</span>
))}
</div>
)}

{/* Raw toggle */}
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-muted-foreground">Content</span>
{/* Raw / Structured toggle */}
<div className="flex items-center justify-end">
<button
type="button"
onClick={() => setShowRaw((v) => !v)}
Expand Down
10 changes: 7 additions & 3 deletions web/src/components/llm-calls/llm-call-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type CallMeta = {
toolCalls?: ToolCall[] | null;
textPreview?: string | null;
thinkingChars?: number | null;
thinkingPreview?: string | null;
};

function ToolCallList({ toolCalls }: { toolCalls: ToolCall[] }) {
Expand Down Expand Up @@ -87,9 +88,12 @@ function CallRow({ runId, call, delta, isExpanded, onToggle }: CallRowProps) {
{call.textPreview}
</span>
)}
{call.thinkingChars != null && (
<span className="text-xs text-muted-foreground/60 italic">
thinking ({call.thinkingChars.toLocaleString()} chars)
{call.thinkingPreview && (
<span className="text-xs text-muted-foreground/60 italic truncate max-w-sm">
{call.thinkingPreview}
{call.thinkingChars != null && call.thinkingChars > 200 && (
<span className="not-italic"> ({call.thinkingChars.toLocaleString()} chars)</span>
)}
</span>
)}
{!call.toolCalls?.length && !call.textPreview && !call.thinkingChars && (
Expand Down
Loading