story-kit: merge 309_story_show_token_cost_breakdown_in_expanded_work_item_detail_panel

This commit is contained in:
Dave
2026-03-19 15:40:47 +00:00
parent be9c15efe0
commit ce380ffb52
4 changed files with 259 additions and 2 deletions

View File

@@ -2,7 +2,12 @@ import * as React from "react";
import Markdown from "react-markdown";
import type { AgentEvent, AgentInfo, AgentStatusValue } from "../api/agents";
import { agentsApi, subscribeAgentStream } from "../api/agents";
import type { TestCaseResult, TestResultsResponse } from "../api/client";
import type {
AgentCostEntry,
TestCaseResult,
TestResultsResponse,
TokenCostResponse,
} from "../api/client";
import { api } from "../api/client";
const { useEffect, useRef, useState } = React;
@@ -125,6 +130,7 @@ export function WorkItemDetailPanel({
const [testResults, setTestResults] = useState<TestResultsResponse | null>(
null,
);
const [tokenCost, setTokenCost] = useState<TokenCostResponse | null>(null);
const panelRef = useRef<HTMLDivElement>(null);
const cleanupRef = useRef<(() => void) | null>(null);
@@ -159,6 +165,18 @@ export function WorkItemDetailPanel({
});
}, [storyId, pipelineVersion]);
// Fetch token cost on mount and when pipeline updates arrive.
useEffect(() => {
api
.getTokenCost(storyId)
.then((data) => {
setTokenCost(data);
})
.catch(() => {
// Silently ignore — token cost may not exist yet.
});
}, [storyId, pipelineVersion]);
useEffect(() => {
cleanupRef.current?.();
cleanupRef.current = null;
@@ -365,6 +383,96 @@ export function WorkItemDetailPanel({
</div>
)}
{/* Token Cost section */}
<div
data-testid="token-cost-section"
style={{
border: "1px solid #2a2a2a",
borderRadius: "8px",
padding: "10px 12px",
background: "#161616",
}}
>
<div
style={{
fontWeight: 600,
fontSize: "0.8em",
color: "#555",
marginBottom: "8px",
}}
>
Token Cost
</div>
{tokenCost && tokenCost.agents.length > 0 ? (
<div data-testid="token-cost-content">
<div
style={{
fontSize: "0.75em",
color: "#888",
marginBottom: "8px",
}}
>
Total:{" "}
<span data-testid="token-cost-total" style={{ color: "#ccc" }}>
${tokenCost.total_cost_usd.toFixed(6)}
</span>
</div>
{tokenCost.agents.map((agent: AgentCostEntry) => (
<div
key={agent.agent_name}
data-testid={`token-cost-agent-${agent.agent_name}`}
style={{
fontSize: "0.75em",
color: "#888",
padding: "4px 0",
borderTop: "1px solid #222",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "2px",
}}
>
<span style={{ color: "#ccc", fontWeight: 600 }}>
{agent.agent_name}
{agent.model ? (
<span
style={{ color: "#666", fontWeight: 400 }}
>{` (${agent.model})`}</span>
) : null}
</span>
<span style={{ color: "#aaa" }}>
${agent.total_cost_usd.toFixed(6)}
</span>
</div>
<div style={{ color: "#555" }}>
in {agent.input_tokens.toLocaleString()} / out{" "}
{agent.output_tokens.toLocaleString()}
{(agent.cache_creation_input_tokens > 0 ||
agent.cache_read_input_tokens > 0) && (
<>
{" "}
/ cache +
{agent.cache_creation_input_tokens.toLocaleString()}{" "}
read {agent.cache_read_input_tokens.toLocaleString()}
</>
)}
</div>
</div>
))}
</div>
) : (
<div
data-testid="token-cost-empty"
style={{ fontSize: "0.75em", color: "#444" }}
>
No token data recorded
</div>
)}
</div>
{/* Test Results section */}
<div
data-testid="test-results-section"