Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 327b043b89 | |||
| c1854da671 |
@@ -38,6 +38,15 @@ const fmtCost = (c: number | null) => (c == null ? '-' : `¥${c.toFixed(4)}`);
|
||||
const fmtTok = (inTok: number | null, outTok: number | null) =>
|
||||
inTok == null && outTok == null ? '-' : `${inTok ?? 0}/${outTok ?? 0}`;
|
||||
|
||||
// LLM 价格快照只展示 prices 里各模型的单价(元/每百万 token),忽略 mode/_source 等元字段。
|
||||
function llmPriceRows(snapshot: Record<string, unknown> | null): string[] {
|
||||
const prices = snapshot?.prices;
|
||||
if (!prices || typeof prices !== 'object') return [];
|
||||
return Object.entries(prices as Record<string, { input_per_1m?: number; output_per_1m?: number }>).map(
|
||||
([model, p]) => `${model} 输入 ${p?.input_per_1m ?? '-'}元/每百万tokens,输出${p?.output_per_1m ?? '-'}元/每百万tokens`,
|
||||
);
|
||||
}
|
||||
|
||||
// LLM message content 常是 json.dumps 的卡片列表;能 parse 成 JSON 就缩进展开,否则原样。
|
||||
function pretty(s: string | null): string {
|
||||
if (!s) return '';
|
||||
@@ -215,7 +224,16 @@ export default function ComparisonRecordsPage() {
|
||||
title: '成本',
|
||||
key: 'cost',
|
||||
width: 90,
|
||||
render: (_, r) => fmtCost(calcCost(r.input_tokens, r.output_tokens, pricePerMTok)),
|
||||
// 有后端冻结的实际成本(当时价)就用它;否则回退老的前端估算(需顶部填 LLM 单价)。
|
||||
render: (_, r) => {
|
||||
if (r.llm_cost_yuan != null) {
|
||||
return <span title="实际成本(按调用时单价冻结)" style={{ color: '#389e0d' }}>{fmtCost(r.llm_cost_yuan)}</span>;
|
||||
}
|
||||
const est = calcCost(r.input_tokens, r.output_tokens, pricePerMTok);
|
||||
return est == null
|
||||
? '-'
|
||||
: <span title="估算(顶部 LLM 单价 × token)" style={{ color: '#999' }}>{fmtCost(est)}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '机型/ROM',
|
||||
@@ -292,7 +310,7 @@ export default function ComparisonRecordsPage() {
|
||||
min={0}
|
||||
step={0.1}
|
||||
style={{ width: 210 }}
|
||||
addonAfter="元/百万token"
|
||||
suffix="元/百万token"
|
||||
/>
|
||||
</Space>
|
||||
|
||||
@@ -331,12 +349,27 @@ export default function ComparisonRecordsPage() {
|
||||
? '-'
|
||||
: `${detail.input_tokens ?? 0} / ${detail.output_tokens ?? 0} / ${(detail.input_tokens ?? 0) + (detail.output_tokens ?? 0)}`}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="估算成本">
|
||||
{fmtCost(calcCost(detail.input_tokens, detail.output_tokens, pricePerMTok))}
|
||||
{pricePerMTok == null && (detail.input_tokens != null || detail.output_tokens != null)
|
||||
? <span style={{ color: '#999', fontSize: 12 }}>(顶部填 LLM 单价后显示)</span>
|
||||
: null}
|
||||
<Descriptions.Item label="LLM 成本">
|
||||
{detail.llm_cost_yuan != null ? (
|
||||
<>
|
||||
{fmtCost(detail.llm_cost_yuan)}
|
||||
<Tag color="green" style={{ marginLeft: 6 }}>实际·当时价</Tag>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{fmtCost(calcCost(detail.input_tokens, detail.output_tokens, pricePerMTok))}
|
||||
<span style={{ color: '#999', fontSize: 12, marginLeft: 6 }}>估算</span>
|
||||
{pricePerMTok == null && (detail.input_tokens != null || detail.output_tokens != null)
|
||||
? <span style={{ color: '#999', fontSize: 12 }}>(顶部填 LLM 单价后显示)</span>
|
||||
: null}
|
||||
</>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
{detail.llm_price_snapshot ? (
|
||||
<Descriptions.Item label="LLM价格快照" span={2}>
|
||||
{llmPriceRows(detail.llm_price_snapshot).map((r) => <div key={r}>{r}</div>)}
|
||||
</Descriptions.Item>
|
||||
) : null}
|
||||
<Descriptions.Item label="源平台">{detail.source_platform_name || '-'}({cents(detail.source_price_cents)})</Descriptions.Item>
|
||||
<Descriptions.Item label="最优">{detail.best_platform_name || '-'}({cents(detail.best_price_cents)})</Descriptions.Item>
|
||||
<Descriptions.Item label="省" span={2}>{cents(detail.saved_amount_cents)}{detail.is_source_best ? '(源平台最便宜)' : ''}</Descriptions.Item>
|
||||
@@ -432,7 +465,7 @@ export default function ComparisonRecordsPage() {
|
||||
),
|
||||
children: (
|
||||
<div>
|
||||
{c.input_messages.map((m, j) => (
|
||||
{(c.input_messages ?? []).map((m, j) => (
|
||||
<div key={j} style={{ marginBottom: 8 }}>
|
||||
<Tag>{m.role}</Tag>
|
||||
<pre style={preStyle}>{pretty(m.content)}</pre>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface ConfigItem {
|
||||
key: string;
|
||||
label: string;
|
||||
group: string;
|
||||
type: string; // int / int_list / dict_str_int / bool
|
||||
type: string; // int / int_list / dict_str_int / bool / json
|
||||
help: string | null;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
default: any;
|
||||
@@ -24,7 +24,7 @@ interface ConfigItem {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function toEdit(item: ConfigItem): any {
|
||||
if (item.type === 'int_list') return (item.value as number[]).join(', ');
|
||||
if (item.type === 'dict_str_int') return JSON.stringify(item.value);
|
||||
if (item.type === 'dict_str_int' || item.type === 'json') return JSON.stringify(item.value);
|
||||
return item.value;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ function fromEdit(type: string, raw: any): any {
|
||||
.split(',')
|
||||
.map((s) => parseInt(s.trim(), 10));
|
||||
}
|
||||
if (type === 'dict_str_int') return JSON.parse(raw);
|
||||
if (type === 'dict_str_int' || type === 'json') return JSON.parse(raw);
|
||||
return raw;
|
||||
}
|
||||
|
||||
|
||||
@@ -354,6 +354,7 @@ export interface ComparisonRecordListItem {
|
||||
retry_count: number | null;
|
||||
input_tokens: number | null;
|
||||
output_tokens: number | null;
|
||||
llm_cost_yuan: number | null; // 后端按「当时价」冻结的实际成本(元);旧记录 null → 「成本」列回退估算
|
||||
device_model: string | null;
|
||||
rom_vendor: string | null;
|
||||
rom_name: string | null;
|
||||
@@ -396,6 +397,8 @@ export interface ComparisonRecordDetail extends ComparisonRecordListItem {
|
||||
latitude: number | null;
|
||||
llm_calls: LlmCall[] | null;
|
||||
raw_payload: Record<string, unknown> | null;
|
||||
// 算成本所用单价快照 {mode, prices:{model:{...}}}(llm_cost_yuan 继承自列表项)。
|
||||
llm_price_snapshot: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export interface AdRevenueImpression {
|
||||
|
||||
Reference in New Issue
Block a user