refactor(usage): switch breakdown dimension from model to route

Align with gpustack backend commits 7b57cd3b (add route dimension to
usage breakdown) and 51fd25c8 (use model_route_id as models_called):

* group_by, filters, and BreakdownItem now use the `route` dimension;
  `models` filter cascader is replaced with a flat `routes` selector
  (routes have no provider/cluster grouping).
* Tab / group_by option / column header keep the user-facing "模型 / Model"
  wording — only the underlying data dimension changed.
* Drop cluster_name / provider_type columns from the breakdown table;
  drop api_keys_used column per design.
* Fix pre-existing excel export bug in use-export-table — string
  dataIndex was lodash-joined char-by-char ("input_tokens" →
  "i_n_p_u_t..."), array dataIndex was used as a flat key against nested
  rows. Rebuilt around buildSheetMeta that emits a stable field key plus
  a formatMap using _.get(row, path) for nested values. Sheet name kept
  as `models` to match the visible column label.
This commit is contained in:
Yuxing Deng
2026-05-19 16:50:42 +08:00
parent 0274bcb8cd
commit 38107a67fe
15 changed files with 135 additions and 254 deletions
+42 -33
View File
@@ -10,6 +10,34 @@ import useAPIKeysColumns from './use-apikeys-columns';
import useModelsColumns from './use-models-columns';
import useUsersColumns from './use-users-columns';
type ColumnLike = {
title: any;
dataIndex: string | string[];
key?: string;
};
const toFieldKey = (dataIndex: string | string[]): string =>
Array.isArray(dataIndex) ? dataIndex.join('_') : dataIndex;
const buildSheetMeta = (columns: ColumnLike[]) => {
const fields: string[] = [];
const fieldLabels: Record<string, any> = {};
const formatMap: Record<string, (raw: any, row: any) => any> = {};
columns.forEach((col) => {
if (!col.dataIndex) return;
const key = toFieldKey(col.dataIndex);
fields.push(key);
fieldLabels[key] = col.title;
if (Array.isArray(col.dataIndex)) {
const path = col.dataIndex;
formatMap[key] = (_raw, row) => _.get(row, path);
}
});
return { fields, fieldLabels, formatMap };
};
const useExportTable = () => {
const store = useStore();
const modelsColumns = useModelsColumns();
@@ -20,53 +48,34 @@ const useExportTable = () => {
const usersTableData = store.get(usersTableDataAtom);
const apiKeysTableData = store.get(apiKeysTableDataAtom);
const modelsTableData = store.get(modelsTableDataAtom);
const modelsMeta = buildSheetMeta(modelsColumns);
const apiKeysMeta = buildSheetMeta(apiKeysColumns);
const usersMeta = buildSheetMeta(usersColumns);
exportJsonToExcel({
fileName: 'table_data.xlsx',
sheets: [
{
jsonData: modelsTableData.dataList || [],
sheetName: 'models',
fields: modelsColumns
.map((col: any) => _.join(col.dataIndex, '_'))
.filter(Boolean) as string[],
fieldLabels: modelsColumns.reduce(
(map, col) => {
map[_.join(col.dataIndex, '_')] = col.title;
return map;
},
{} as Record<string, any>
),
formatMap: {}
fields: modelsMeta.fields,
fieldLabels: modelsMeta.fieldLabels,
formatMap: modelsMeta.formatMap
},
{
jsonData: apiKeysTableData.dataList || [],
sheetName: 'api_keys',
fields: apiKeysColumns
.map((col: any) => col.dataIndex)
.filter(Boolean) as string[],
fieldLabels: apiKeysColumns.reduce(
(map, col) => {
map[_.join(col.dataIndex, '_')] = col.title;
return map;
},
{} as Record<string, any>
),
formatMap: {}
fields: apiKeysMeta.fields,
fieldLabels: apiKeysMeta.fieldLabels,
formatMap: apiKeysMeta.formatMap
},
{
jsonData: usersTableData.dataList || [],
sheetName: 'users',
fields: usersColumns
.map((col: any) => col.dataIndex)
.filter(Boolean) as string[],
fieldLabels: usersColumns.reduce(
(map, col) => {
map[_.join(col.dataIndex, '_')] = col.title;
return map;
},
{} as Record<string, any>
),
formatMap: {}
fields: usersMeta.fields,
fieldLabels: usersMeta.fieldLabels,
formatMap: usersMeta.formatMap
}
]
});