FN-7217: map GLM models to Z.ai icons
Command Center token analytics now render standalone GLM model rows with Z.ai provider icons. - Infer Z.ai provider icons for standalone glm-* model ids without matching unrelated text. - Cover GLM icon rendering in Overview token bars and Tokens Area bars/table rows. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-7217-zai-glm-icons.md | 7 +++ .../__tests__/CommandCenter.test.tsx | 39 ++++++++++++++++ .../areas/__tests__/TokensArea.test.tsx | 53 ++++++++++++++++++++++ .../app/utils/__tests__/providerIconKey.test.ts | 4 ++ packages/dashboard/app/utils/providerIconKey.ts | 7 ++- 5 files changed, 109 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7217 Fusion-Task-Lineage: e784413e-2dcf-4b38-ba01-5f9d825e07b8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7217-zai-glm-icons.md
Normal file
7
.changeset/fn-7217-zai-glm-icons.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show Z.ai icons for GLM model rows in Command Center token analytics.
|
||||
category: fix
|
||||
dev: Maps standalone glm-* model labels through the shared provider-icon inference helper.
|
||||
@@ -106,6 +106,31 @@ function tokenFixture(totalTokens = 1_500) {
|
||||
};
|
||||
}
|
||||
|
||||
function glmOverviewTokenFixture() {
|
||||
const groups = [
|
||||
makeTokenGroup("glm-5.1", 1_400),
|
||||
makeTokenGroup("gpt-4o", 1_200),
|
||||
makeTokenGroup("claude-sonnet", 1_000),
|
||||
];
|
||||
const totals = groups.reduce(
|
||||
(acc, group) => ({
|
||||
inputTokens: acc.inputTokens + group.inputTokens,
|
||||
outputTokens: acc.outputTokens + group.outputTokens,
|
||||
cachedTokens: acc.cachedTokens + group.cachedTokens,
|
||||
cacheWriteTokens: acc.cacheWriteTokens + group.cacheWriteTokens,
|
||||
totalTokens: acc.totalTokens + group.totalTokens,
|
||||
nTasks: acc.nTasks + group.nTasks,
|
||||
}),
|
||||
{ inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, totalTokens: 0, nTasks: 0 },
|
||||
);
|
||||
|
||||
return {
|
||||
...tokenFixture(totals.totalTokens),
|
||||
totals,
|
||||
groups,
|
||||
};
|
||||
}
|
||||
|
||||
function manyModelTokenFixture() {
|
||||
const groups = [
|
||||
makeTokenGroup("model-01", 2_000),
|
||||
@@ -639,6 +664,20 @@ describe("CommandCenter shell", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("renders standalone GLM model rows with the Z.ai icon in Overview token bars", async () => {
|
||||
mockOverviewApi({ tokens: glmOverviewTokenFixture() });
|
||||
render(<CommandCenter />);
|
||||
|
||||
const overviewTokenChart = await screen.findByTestId("command-center-overview-chart-tokens");
|
||||
const glmBarLabel = within(overviewTokenChart).getByText("glm-5.1").closest(".cc-bar-label");
|
||||
expect(glmBarLabel).toBeTruthy();
|
||||
expect(providerIconIn(overviewTokenChart, "zai")).toBeTruthy();
|
||||
expect(providerIconIn(overviewTokenChart, "openai")).toBeTruthy();
|
||||
expect(providerIconIn(overviewTokenChart, "anthropic")).toBeTruthy();
|
||||
expect(screen.getByRole("img", { name: "zai glm-5.1: 1,400" })).toBeTruthy();
|
||||
expect(providerIconIn(screen.getByTestId("cc-overview-pie"), "zai")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders Overview providerless and unknown model icons without touching pie labels", async () => {
|
||||
mockOverviewApi({
|
||||
tokens: {
|
||||
|
||||
@@ -83,6 +83,36 @@ function tokenFixture() {
|
||||
};
|
||||
}
|
||||
|
||||
function glmMixedProviderTokenFixture() {
|
||||
const groups = [
|
||||
makeTokenGroup("glm-5.1", 2_400),
|
||||
makeTokenGroup("glm-4.5-air", 2_200),
|
||||
makeTokenGroup("glm-5v-turbo", 2_000),
|
||||
makeTokenGroup("gpt-4o-mini", 1_800),
|
||||
makeTokenGroup("claude-sonnet-4-5", 1_600),
|
||||
makeTokenGroup("custom-model-v1", 1_400),
|
||||
makeTokenGroup(null, 1_200),
|
||||
];
|
||||
const totals = groups.reduce(
|
||||
(acc, group) => ({
|
||||
inputTokens: acc.inputTokens + group.inputTokens,
|
||||
outputTokens: acc.outputTokens + group.outputTokens,
|
||||
cachedTokens: acc.cachedTokens + group.cachedTokens,
|
||||
cacheWriteTokens: acc.cacheWriteTokens + group.cacheWriteTokens,
|
||||
totalTokens: acc.totalTokens + group.totalTokens,
|
||||
nTasks: acc.nTasks + group.nTasks,
|
||||
}),
|
||||
{ inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, totalTokens: 0, nTasks: 0 },
|
||||
);
|
||||
|
||||
return {
|
||||
...tokenFixture(),
|
||||
totals,
|
||||
cost: { usd: null, unavailable: true, stale: false },
|
||||
groups,
|
||||
};
|
||||
}
|
||||
|
||||
function manyModelTokenFixture() {
|
||||
const groups = [
|
||||
makeTokenGroup("claude-sonnet-4-5", 2_000),
|
||||
@@ -152,6 +182,29 @@ describe("TokensArea provider model icons", () => {
|
||||
expect(screen.getByTestId("cc-tokens-pie")).toHaveTextContent("(unknown)");
|
||||
});
|
||||
|
||||
it("renders standalone GLM model rows with Z.ai icons across bars and table", async () => {
|
||||
apiMock.mockResolvedValue(glmMixedProviderTokenFixture());
|
||||
render(<TokensArea range={range7d} />);
|
||||
|
||||
const table = await screen.findByTestId("cc-tokens-table");
|
||||
const byModelChart = screen.getByRole("list", { name: "Tokens by model" });
|
||||
|
||||
for (const label of ["glm-5.1", "glm-4.5-air", "glm-5v-turbo"]) {
|
||||
const row = screen.getByTestId(`cc-tokens-row-${label}`);
|
||||
expect(within(row).getByText(label)).toBeTruthy();
|
||||
expect(within(row).getByTestId("provider-icon-zai")).toHaveAttribute("data-provider", "zai");
|
||||
const barLabel = within(byModelChart).getByText(label).closest(".cc-bar-label");
|
||||
expect(barLabel?.firstElementChild).toHaveAttribute("data-testid", "provider-icon-zai");
|
||||
expect(barLabel?.firstElementChild).toHaveAttribute("data-provider", "zai");
|
||||
}
|
||||
|
||||
expect(within(screen.getByTestId("cc-tokens-row-gpt-4o-mini")).getByTestId("provider-icon-openai")).toBeTruthy();
|
||||
expect(within(screen.getByTestId("cc-tokens-row-claude-sonnet-4-5")).getByTestId("provider-icon-anthropic")).toBeTruthy();
|
||||
expect(within(screen.getByTestId("cc-tokens-row-custom-model-v1")).getByTestId("provider-icon-custom-model-v1")).toBeTruthy();
|
||||
expect(within(screen.getByTestId("cc-tokens-row-unknown")).getByTestId("provider-icon-")).toBeTruthy();
|
||||
expect(table.querySelectorAll('.provider-icon[data-provider="zai"]').length).toBe(3);
|
||||
});
|
||||
|
||||
it("renders every analytics model group in detail bar, pie, and table even beyond the old cap", async () => {
|
||||
apiMock.mockResolvedValue(manyModelTokenFixture());
|
||||
render(<TokensArea range={range7d} />);
|
||||
|
||||
@@ -13,6 +13,9 @@ describe("inferProviderIconKey", () => {
|
||||
["minimax-text-01", "minimax"],
|
||||
["zhipu-glm-4", "zai"],
|
||||
["zai/glm-4.5", "zai"],
|
||||
["glm-5.1", "zai"],
|
||||
["glm-4.5-air", "zai"],
|
||||
["glm-5v-turbo", "zai"],
|
||||
["kimi-k2", "kimi"],
|
||||
["moonshot-v1", "kimi"],
|
||||
["amazon-bedrock-claude", "anthropic"],
|
||||
@@ -27,6 +30,7 @@ describe("inferProviderIconKey", () => {
|
||||
|
||||
it("returns unknown ids unchanged so ProviderIcon can render its fallback", () => {
|
||||
expect(inferProviderIconKey("custom-model-v1")).toBe("custom-model-v1");
|
||||
expect(inferProviderIconKey("not-a-glmish-model")).toBe("not-a-glmish-model");
|
||||
expect(inferProviderIconKey("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
/*
|
||||
FNXC:ProviderIcons 2026-06-25-00:00:
|
||||
Command Center model analytics group rows by model id only, so dashboard model-name surfaces must infer the provider icon from the model/provider text before handing off to ProviderIcon's fallback-safe renderer.
|
||||
|
||||
FNXC:ProviderIcons 2026-06-28-20:46:
|
||||
Standalone GLM model ids such as glm-5.1, glm-4.5-air, and glm-5v-turbo are Z.ai/Zhipu-family models even when the analytics label omits zai/zhipu. Match GLM only at model-id segment boundaries so unrelated words do not hijack the Z.ai provider icon.
|
||||
*/
|
||||
const GLM_MODEL_SEGMENT_PATTERN = /(?:^|[/:_-])glm(?:$|[\d._:-]|-[\da-z])/;
|
||||
|
||||
export function inferProviderIconKey(modelOrProviderName: string): string {
|
||||
const normalized = modelOrProviderName.toLowerCase();
|
||||
|
||||
@@ -21,7 +26,7 @@ export function inferProviderIconKey(modelOrProviderName: string): string {
|
||||
if (normalized.includes("minimax")) {
|
||||
return "minimax";
|
||||
}
|
||||
if (normalized.includes("zai") || normalized.includes("zhipu")) {
|
||||
if (normalized.includes("zai") || normalized.includes("zhipu") || GLM_MODEL_SEGMENT_PATTERN.test(normalized)) {
|
||||
return "zai";
|
||||
}
|
||||
if (normalized.includes("kimi") || normalized.includes("moonshot")) {
|
||||
|
||||
Reference in New Issue
Block a user