console: 任务详情自动显示成本明细

打开详情就自动拉 GET /tasks/{id}/cost(不再只靠
“查成本”按钮出 JSON),详情顶部新一行:美元、总
token(实际/估算、缓存、无价单列)、轮数、fallback
次数;美元小于 1 美分时用 4 位小数,避免小任务
显示成 $0.00 误导成免费;加载中/失败有各自的
弱化文案,成本拉失败不影响详情其它内容;原
“查成本”按钮不动,仍可手动拉原始 JSON。
服务端不用改:成本接口与 _get_task 的 token 聚合
都已在 #150 / 详情 meta 那单里就位。
This commit is contained in:
host committed 2026-10-30 00:39:13 +08:00
1 parent 0f7162934b
commit 994c4ea5d1
7 files changed
+57 -31

No files matched your search

@@ -130,7 +130,7 @@ export function useConsoleStore(): ConsoleCtx {
filteredTasks: taskList.filteredTasks,
currentTask: detail.currentTask, detailBody: detail.detailBody, actionMsg: detail.actionMsg, actionReasons: detail.actionReasons,
showDetail: (id: string) => void detail.showDetail(id),
doAction: detail.doAction, taskCost: detail.taskCost, costBox: detail.costBox,
doAction: detail.doAction, taskCost: detail.taskCost, costBox: detail.costBox, costSummary: detail.costSummary,
forkVisible: detail.forkVisible, setForkVisible: detail.setForkVisible, forkTask: detail.forkTask, forkMsg: detail.forkMsg,
coordChildren: detail.coordChildren, coordText: "", agents: detail.agents, agentsNote: detail.agentsNote,
reviews: detail.reviews, reviewMsg: detail.reviewMsg, reviewError: detail.reviewError,
+17 -4
View File
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { api, getKey } from "../api/client";
import { paths } from "../api/paths";
import { TRACE_TERMINAL } from "../api/types";
import type { Agent, Approval, ChatHistory, Followup, PeerMessage, Review, Task, ThreadInfo, TraceEntry } from "../api/types";
import type { Agent, Approval, ChatHistory, Followup, PeerMessage, Review, Task, TaskCost, ThreadInfo, TraceEntry } from "../api/types";
// Task detail state: the selected task, its trace SSE/one-shot stream,
// and every structured sub-list. Chat sending lives here too:
@@ -13,6 +13,9 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
const [actionMsg, setActionMsg] = useState("");
const [actionReasons, setActionReasons] = useState("");
const [costBox, setCostBox] = useState("");
// 成本明细:打开详情就自动加载(不再只靠“查成本”按钮出 JSON)。
// undefined = 加载中,null = 加载失败,对象 = 已加载。
const [costSummary, setCostSummary] = useState<TaskCost | null | undefined>(undefined);
const [forkVisible, setForkVisible] = useState(false);
const [forkMsg, setForkMsg] = useState("");
const [coordChildren, setCoordChildren] = useState<Task[]>([]);
@@ -68,6 +71,13 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
} catch { setCoordChildren([]); }
}, []);
const loadCost = useCallback(async (taskId: string) => {
try {
const c = await api<TaskCost>(paths.taskCost(taskId));
setCostSummary(c);
} catch { setCostSummary(null); }
}, []);
const loadFollowups = useCallback(async (taskId: string) => {
try { const d = await api<{ followups: Followup[] }>(paths.taskFollowups(taskId)); setFollowups(d.followups || []); }
catch { setFollowups([]); }
@@ -215,6 +225,8 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
if (!body) body = "(暂无 result/error 输出)";
setDetailBody(body);
setCostBox("");
setCostSummary(undefined);
void loadCost(taskId);
setForkVisible(false);
setForkMsg("");
setPeerMsg("");
@@ -247,7 +259,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
setCurrentTask(null);
setDetailBody("加载失败:" + e.message);
}
}, [stopTrace, startTrace, loadAgents, loadCoordChildren, loadChatHistory, loadFollowups, loadPeerMessages, loadThread, loadThreads, loadReviews, loadApprovals, getRole]);
}, [stopTrace, startTrace, loadAgents, loadCoordChildren, loadChatHistory, loadFollowups, loadPeerMessages, loadThread, loadThreads, loadReviews, loadApprovals, loadCost, getRole]);
const refreshDetailLists = useCallback(async () => {
const t = currentTaskRef.current;
@@ -269,7 +281,8 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
void loadPeerMessages(t.id);
void loadThread(t.id);
void loadThreads(t.id);
}, [loadFollowups, loadChatHistory, loadReviews, loadApprovals, loadCoordChildren, loadAgents, loadPeerMessages, loadThread, loadThreads]);
void loadCost(t.id);
}, [loadFollowups, loadChatHistory, loadReviews, loadApprovals, loadCoordChildren, loadAgents, loadPeerMessages, loadThread, loadThreads, loadCost]);
const doAction = useCallback(async (kind: string) => {
const t = currentTaskRef.current;
@@ -409,7 +422,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
useEffect(() => () => stopTrace(), [stopTrace]);
return {
currentTask, detailBody, actionMsg, actionReasons, costBox, forkVisible, setForkVisible, forkMsg,
currentTask, detailBody, actionMsg, actionReasons, costBox, costSummary, forkVisible, setForkVisible, forkMsg,
coordChildren, agents, agentsNote, reviews, reviewMsg, reviewError, traceEntries, traceNote, traceDone,
traceKind, setTraceKind, traceMsg, followups, approvals, approvalMsg, peerMessages, peerMsg,
threadText, threadGateNote, threadMsg, threadList, chatMsg, chatHistory,