feat: show token usage per assistant response (#2270)

* feat: show token usage per assistant response

* fix: align client models response with token usage

* fix: address token usage review feedback

* docs: clarify token usage config example

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
YuJitang
2026-04-16 08:56:49 +08:00
committed by GitHub
parent 0e16a7fe55
commit 105db00987
17 changed files with 271 additions and 50 deletions
+4
View File
@@ -298,9 +298,13 @@ export const enUS: Translations = {
// Token Usage
tokenUsage: {
title: "Token Usage",
label: "Tokens",
input: "Input",
output: "Output",
total: "Total",
unavailable:
"No token usage yet. Usage appears only after a successful model response when the provider returns usage_metadata.",
unavailableShort: "No usage returned",
},
// Shortcuts
+3
View File
@@ -229,9 +229,12 @@ export interface Translations {
// Token Usage
tokenUsage: {
title: string;
label: string;
input: string;
output: string;
total: string;
unavailable: string;
unavailableShort: string;
};
// Shortcuts
+4
View File
@@ -284,9 +284,13 @@ export const zhCN: Translations = {
// Token Usage
tokenUsage: {
title: "Token 用量",
label: "Tokens",
input: "输入",
output: "输出",
total: "总计",
unavailable:
"暂无 Token 用量。只有模型成功返回且供应商提供 usage_metadata 时才会显示。",
unavailableShort: "未返回用量",
},
// Shortcuts
+1 -1
View File
@@ -10,7 +10,7 @@ export interface TokenUsage {
* Extract usage_metadata from an AI message if present.
* The field is added by the backend (PR #1218) but not typed in the SDK.
*/
function getUsageMetadata(message: Message): TokenUsage | null {
export function getUsageMetadata(message: Message): TokenUsage | null {
if (message.type !== "ai") {
return null;
}
+7 -4
View File
@@ -1,9 +1,12 @@
import { getBackendBaseURL } from "../config";
import type { Model } from "./types";
import type { ModelsResponse } from "./types";
export async function loadModels() {
export async function loadModels(): Promise<ModelsResponse> {
const res = await fetch(`${getBackendBaseURL()}/api/models`);
const { models } = (await res.json()) as { models: Model[] };
return models;
const data = (await res.json()) as Partial<ModelsResponse>;
return {
models: data.models ?? [],
token_usage: data.token_usage ?? { enabled: false },
};
}
+6 -1
View File
@@ -9,5 +9,10 @@ export function useModels({ enabled = true }: { enabled?: boolean } = {}) {
enabled,
refetchOnWindowFocus: false,
});
return { models: data ?? [], isLoading, error };
return {
models: data?.models ?? [],
tokenUsageEnabled: data?.token_usage.enabled ?? false,
isLoading,
error,
};
}
+9
View File
@@ -7,3 +7,12 @@ export interface Model {
supports_thinking?: boolean;
supports_reasoning_effort?: boolean;
}
export interface TokenUsageSettings {
enabled: boolean;
}
export interface ModelsResponse {
models: Model[];
token_usage: TokenUsageSettings;
}