Compare commits

..

1 Commits

Author SHA1 Message Date
guke 327b043b89 feat(comparison-records): 比价记录展示 LLM 实际成本(列表列 + 详情 + 价格快照) (#48)
调整LLM价格快照展示样式

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #48
2026-07-14 14:38:38 +08:00
+5 -16
View File
@@ -38,24 +38,13 @@ const fmtCost = (c: number | null) => (c == null ? '-' : `¥${c.toFixed(4)}`);
const fmtTok = (inTok: number | null, outTok: number | null) => const fmtTok = (inTok: number | null, outTok: number | null) =>
inTok == null && outTok == null ? '-' : `${inTok ?? 0}/${outTok ?? 0}`; inTok == null && outTok == null ? '-' : `${inTok ?? 0}/${outTok ?? 0}`;
// LLM 价格快照:每模型单价(元/每百万 token)+ 本次分桶 token + 后端按当时价冻结的该模型成本 // LLM 价格快照只展示 prices 里各模型单价(元/每百万 token),忽略 mode/_source 等元字段。
// cost_yuan(=null 即该模型待补价)。旧记录快照只有单价、无 token/成本字段(input_tokens 缺失)→
// 退回只显示单价那行,不硬凑 0/0。忽略 mode/_source 等元字段。
function llmPriceRows(snapshot: Record<string, unknown> | null): string[] { function llmPriceRows(snapshot: Record<string, unknown> | null): string[] {
const prices = snapshot?.prices; const prices = snapshot?.prices;
if (!prices || typeof prices !== 'object') return []; if (!prices || typeof prices !== 'object') return [];
return Object.entries( return Object.entries(prices as Record<string, { input_per_1m?: number; output_per_1m?: number }>).map(
prices as Record< ([model, p]) => `${model} 输入 ${p?.input_per_1m ?? '-'}元/每百万tokens,输出${p?.output_per_1m ?? '-'}元/每百万tokens`,
string, );
{ input_per_1m?: number; output_per_1m?: number; input_tokens?: number; output_tokens?: number; cost_yuan?: number | null }
>,
).map(([model, p]) => {
const unit = `输入 ${p?.input_per_1m ?? '-'}元/每百万tokens,输出${p?.output_per_1m ?? '-'}元/每百万tokens`;
if (p?.input_tokens == null) return `${model} ${unit}`; // 旧记录:无 token/成本字段
const tok = `${p.input_tokens}/${p.output_tokens ?? 0} tokens`;
const cost = p.cost_yuan == null ? '待补价' : `¥${p.cost_yuan}`;
return `${model} ${unit},本次 ${tok},成本 ${cost}`;
});
} }
// LLM message content 常是 json.dumps 的卡片列表;能 parse 成 JSON 就缩进展开,否则原样。 // LLM message content 常是 json.dumps 的卡片列表;能 parse 成 JSON 就缩进展开,否则原样。
@@ -377,7 +366,7 @@ export default function ComparisonRecordsPage() {
)} )}
</Descriptions.Item> </Descriptions.Item>
{detail.llm_price_snapshot ? ( {detail.llm_price_snapshot ? (
<Descriptions.Item label="LLM 单价/成本快照" span={2}> <Descriptions.Item label="LLM价格快照" span={2}>
{llmPriceRows(detail.llm_price_snapshot).map((r) => <div key={r}>{r}</div>)} {llmPriceRows(detail.llm_price_snapshot).map((r) => <div key={r}>{r}</div>)}
</Descriptions.Item> </Descriptions.Item>
) : null} ) : null}