feat(comparison-records): 比价记录展示 LLM 实际成本(列表列 + 详情 + 价格快照) (#48)
调整LLM价格快照展示样式 --------- Co-authored-by: guke <guke@autohome.com.cn> Reviewed-on: #48
This commit was merged in pull request #48.
This commit is contained in:
@@ -38,6 +38,15 @@ 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 价格快照只展示 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 就缩进展开,否则原样。
|
// LLM message content 常是 json.dumps 的卡片列表;能 parse 成 JSON 就缩进展开,否则原样。
|
||||||
function pretty(s: string | null): string {
|
function pretty(s: string | null): string {
|
||||||
if (!s) return '';
|
if (!s) return '';
|
||||||
@@ -357,8 +366,8 @@ export default function ComparisonRecordsPage() {
|
|||||||
)}
|
)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
{detail.llm_price_snapshot ? (
|
{detail.llm_price_snapshot ? (
|
||||||
<Descriptions.Item label="价格快照" span={2}>
|
<Descriptions.Item label="LLM价格快照" span={2}>
|
||||||
<pre style={preStyle}>{JSON.stringify(detail.llm_price_snapshot, null, 2)}</pre>
|
{llmPriceRows(detail.llm_price_snapshot).map((r) => <div key={r}>{r}</div>)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Descriptions.Item label="源平台">{detail.source_platform_name || '-'}({cents(detail.source_price_cents)})</Descriptions.Item>
|
<Descriptions.Item label="源平台">{detail.source_platform_name || '-'}({cents(detail.source_price_cents)})</Descriptions.Item>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface ConfigItem {
|
|||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
group: 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;
|
help: string | null;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
default: any;
|
default: any;
|
||||||
@@ -24,7 +24,7 @@ interface ConfigItem {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function toEdit(item: ConfigItem): any {
|
function toEdit(item: ConfigItem): any {
|
||||||
if (item.type === 'int_list') return (item.value as number[]).join(', ');
|
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;
|
return item.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ function fromEdit(type: string, raw: any): any {
|
|||||||
.split(',')
|
.split(',')
|
||||||
.map((s) => parseInt(s.trim(), 10));
|
.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;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user