[fix(web)] localize copied task-stats line instead of hardcoded Chinese (#8)
This commit is contained in:
1 parent
81d8c0045c
commit
1425b2fb5f
5 files changed
+104
-29
No files matched your search
@@ -71,7 +71,16 @@ export function TaskStatsLine({
|
|||||||
|
|
||||||
const copy = () => {
|
const copy = () => {
|
||||||
const text =
|
const text =
|
||||||
assistantText?.trim() || stats === null ? (assistantText ?? "") : formatTaskStats(stats);
|
assistantText?.trim() || stats === null
|
||||||
|
? (assistantText ?? "")
|
||||||
|
: formatTaskStats(stats, {
|
||||||
|
stats: S.chat.statsLabel,
|
||||||
|
input: S.chat.statInput,
|
||||||
|
cached: S.chat.statCached,
|
||||||
|
output: S.chat.statOutput,
|
||||||
|
parenOpen: S.chat.statParenOpen,
|
||||||
|
parenClose: S.chat.statParenClose,
|
||||||
|
});
|
||||||
void navigator.clipboard?.writeText(text).then(() => {
|
void navigator.clipboard?.writeText(text).then(() => {
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 1500);
|
setTimeout(() => setCopied(false), 1500);
|
||||||
|
|||||||
@@ -287,17 +287,34 @@ export function endTask(t: TaskStatsTracker, elapsedMs: number): TaskStats | nul
|
|||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Localized labels for {@link formatTaskStats} (supplied by the view layer's active dictionary). */
|
||||||
|
export interface TaskStatsLabels {
|
||||||
|
/** Row prefix, e.g. "Stats" / "统计信息". */
|
||||||
|
stats: string;
|
||||||
|
/** Input-tokens label, e.g. "Input tokens" / "输入 tokens". */
|
||||||
|
input: string;
|
||||||
|
/** Cached-amount label, e.g. "cached" / "已缓存". */
|
||||||
|
cached: string;
|
||||||
|
/** Output-tokens label, e.g. "Output tokens" / "输出 tokens". */
|
||||||
|
output: string;
|
||||||
|
/** Opening wrapper before the cached amount — keeps per-locale typography (e.g. " (" / "("). */
|
||||||
|
parenOpen: string;
|
||||||
|
/** Closing wrapper after the cached amount (e.g. ")" / ")"). */
|
||||||
|
parenClose: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stats row text (copy fallback / used when there's no body text): same basis as the stats row —
|
* Stats row text (copy fallback / used when there's no body text): same basis as the stats row —
|
||||||
* this round's usage "input tokens (including cached amount) · output tokens · output TPS", e.g.
|
* this round's usage "input tokens (including cached amount) · output tokens · output TPS", e.g.
|
||||||
* `[统计信息] 输入 tokens 4k(已缓存 3k) · 输出 tokens 1.2k · 42.5 tok/s`; label can be overridden
|
* `[Stats] Input tokens 4k (cached 3k) · Output tokens 1.2k · 42.5 tok/s`. Labels (including the
|
||||||
* per UI locale.
|
* parenthesis wrappers) come from the caller's active locale dictionary, so the copied text keeps
|
||||||
|
* per-locale typography — ASCII `(…)` in English, fullwidth `(…)` in Chinese.
|
||||||
*/
|
*/
|
||||||
export function formatTaskStats(s: TaskStats, label = "统计信息"): string {
|
export function formatTaskStats(s: TaskStats, labels: TaskStatsLabels): string {
|
||||||
const b = s.tokensByBucket;
|
const b = s.tokensByBucket;
|
||||||
return (
|
return (
|
||||||
`[${label}] 输入 tokens ${humanizeTokens(b.cacheRead + b.cacheWrite)}(已缓存 ${humanizeTokens(b.cacheRead)})` +
|
`[${labels.stats}] ${labels.input} ${humanizeTokens(b.cacheRead + b.cacheWrite)}${labels.parenOpen}${labels.cached} ${humanizeTokens(b.cacheRead)}${labels.parenClose}` +
|
||||||
` · 输出 tokens ${humanizeTokens(b.output)}` +
|
` · ${labels.output} ${humanizeTokens(b.output)}` +
|
||||||
` · ${formatTps(s.outputTps)}`
|
` · ${formatTps(s.outputTps)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -609,8 +609,12 @@ When done, open index.html in a browser and self-test once.`,
|
|||||||
statCost: "Cost",
|
statCost: "Cost",
|
||||||
statElapsed: "Elapsed",
|
statElapsed: "Elapsed",
|
||||||
statInput: "Input tokens",
|
statInput: "Input tokens",
|
||||||
|
statCached: "cached",
|
||||||
statOutput: "Output tokens",
|
statOutput: "Output tokens",
|
||||||
statTps: "Output TPS",
|
statTps: "Output TPS",
|
||||||
|
/** Copied-stats-line parenthesis wrappers around the cached amount (ASCII with a leading space for en). */
|
||||||
|
statParenOpen: " (",
|
||||||
|
statParenClose: ")",
|
||||||
noSessions: "No Sessions yet",
|
noSessions: "No Sessions yet",
|
||||||
emptyStream: "Send a message to start the conversation",
|
emptyStream: "Send a message to start the conversation",
|
||||||
historyLoadFailed: "Failed to load history",
|
historyLoadFailed: "Failed to load history",
|
||||||
|
|||||||
@@ -589,8 +589,12 @@ Penguin 视觉风格(见 web-design 技能),深色/浅色主题(<html da
|
|||||||
statCost: "成本",
|
statCost: "成本",
|
||||||
statElapsed: "用时",
|
statElapsed: "用时",
|
||||||
statInput: "输入 tokens",
|
statInput: "输入 tokens",
|
||||||
|
statCached: "已缓存",
|
||||||
statOutput: "输出 tokens",
|
statOutput: "输出 tokens",
|
||||||
statTps: "输出 TPS",
|
statTps: "输出 TPS",
|
||||||
|
/** Copied-stats-line parenthesis wrappers around the cached amount (fullwidth for zh typography). */
|
||||||
|
statParenOpen: "(",
|
||||||
|
statParenClose: ")",
|
||||||
noSessions: "还没有 Session",
|
noSessions: "还没有 Session",
|
||||||
emptyStream: "发送一条消息开始对话",
|
emptyStream: "发送一条消息开始对话",
|
||||||
historyLoadFailed: "历史消息加载失败",
|
historyLoadFailed: "历史消息加载失败",
|
||||||
|
|||||||
@@ -195,33 +195,74 @@ describe("TaskStatsTracker", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("formatTaskStats", () => {
|
describe("formatTaskStats", () => {
|
||||||
it("the convention is this round's usage \"输入(已缓存)· 输出 · 输出 TPS\", taking this Task's three-bucket usage + this Task's TPS", () => {
|
const EN_LABELS = {
|
||||||
|
stats: "Stats",
|
||||||
|
input: "Input tokens",
|
||||||
|
cached: "cached",
|
||||||
|
output: "Output tokens",
|
||||||
|
parenOpen: " (",
|
||||||
|
parenClose: ")",
|
||||||
|
};
|
||||||
|
const ZH_LABELS = {
|
||||||
|
stats: "统计信息",
|
||||||
|
input: "输入 tokens",
|
||||||
|
cached: "已缓存",
|
||||||
|
output: "输出 tokens",
|
||||||
|
parenOpen: "(",
|
||||||
|
parenClose: ")",
|
||||||
|
};
|
||||||
|
|
||||||
|
it("the convention is this round's usage \"input (cached) · output · output TPS\", taking this Task's three-bucket usage + this Task's TPS", () => {
|
||||||
expect(
|
expect(
|
||||||
formatTaskStats({
|
formatTaskStats(
|
||||||
context: 40000, // context usage isn't in the stats row (shown by the input-box ring instead)
|
{
|
||||||
contextDelta: 1000,
|
context: 40000, // context usage isn't in the stats row (shown by the input-box ring instead)
|
||||||
tokens: 60000,
|
contextDelta: 1000,
|
||||||
tokensDelta: 1200,
|
tokens: 60000,
|
||||||
elapsedMs: 5100,
|
tokensDelta: 1200,
|
||||||
elapsedDeltaMs: 2300,
|
elapsedMs: 5100,
|
||||||
tokensByBucket: { cacheRead: 3000, cacheWrite: 1000, output: 1200 },
|
elapsedDeltaMs: 2300,
|
||||||
outputTps: 42.5,
|
tokensByBucket: { cacheRead: 3000, cacheWrite: 1000, output: 1200 },
|
||||||
}),
|
outputTps: 42.5,
|
||||||
).toBe("[统计信息] 输入 tokens 4k(已缓存 3k) · 输出 tokens 1.2k · 42.5 tok/s");
|
},
|
||||||
|
EN_LABELS,
|
||||||
|
),
|
||||||
|
).toBe("[Stats] Input tokens 4k (cached 3k) · Output tokens 1.2k · 42.5 tok/s");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("TPS shows — without LLM timing", () => {
|
it("TPS shows — without LLM timing", () => {
|
||||||
expect(
|
expect(
|
||||||
formatTaskStats({
|
formatTaskStats(
|
||||||
context: 500,
|
{
|
||||||
contextDelta: 0,
|
context: 500,
|
||||||
tokens: 500,
|
contextDelta: 0,
|
||||||
tokensDelta: 500,
|
tokens: 500,
|
||||||
elapsedMs: 900,
|
tokensDelta: 500,
|
||||||
elapsedDeltaMs: 900,
|
elapsedMs: 900,
|
||||||
tokensByBucket: { cacheRead: 500, cacheWrite: 0, output: 0 },
|
elapsedDeltaMs: 900,
|
||||||
outputTps: null,
|
tokensByBucket: { cacheRead: 500, cacheWrite: 0, output: 0 },
|
||||||
}),
|
outputTps: null,
|
||||||
).toBe("[统计信息] 输入 tokens 500(已缓存 500) · 输出 tokens 0 · —");
|
},
|
||||||
|
EN_LABELS,
|
||||||
|
),
|
||||||
|
).toBe("[Stats] Input tokens 500 (cached 500) · Output tokens 0 · —");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders with the supplied locale labels (zh)", () => {
|
||||||
|
expect(
|
||||||
|
formatTaskStats(
|
||||||
|
{
|
||||||
|
context: 40000,
|
||||||
|
contextDelta: 1000,
|
||||||
|
tokens: 60000,
|
||||||
|
tokensDelta: 1200,
|
||||||
|
elapsedMs: 5100,
|
||||||
|
elapsedDeltaMs: 2300,
|
||||||
|
tokensByBucket: { cacheRead: 3000, cacheWrite: 1000, output: 1200 },
|
||||||
|
outputTps: 42.5,
|
||||||
|
},
|
||||||
|
ZH_LABELS,
|
||||||
|
),
|
||||||
|
).toBe("[统计信息] 输入 tokens 4k(已缓存 3k) · 输出 tokens 1.2k · 42.5 tok/s");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user