From f9460646ae56a9d913807af8c87d170e019704f3 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Sun, 9 Aug 2026 19:02:17 +0900 Subject: [PATCH] fix(dashboard): safely group prototype-named providers --- gui/src/pages/use-dashboard-data.ts | 16 +++++++++++----- gui/tests/dashboard-model-grouping.test.ts | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 gui/tests/dashboard-model-grouping.test.ts diff --git a/gui/src/pages/use-dashboard-data.ts b/gui/src/pages/use-dashboard-data.ts index 0264781fa6..4814ad8887 100644 --- a/gui/src/pages/use-dashboard-data.ts +++ b/gui/src/pages/use-dashboard-data.ts @@ -68,6 +68,16 @@ type CachedOverview = { type MaMode = "v1" | "default" | "v2"; +export function groupDashboardModels(models: ModelInfo[]): Array<[string, ModelInfo[]]> { + const groups = new Map(); + for (const model of models) { + const rows = groups.get(model.provider); + if (rows) rows.push(model); + else groups.set(model.provider, [model]); + } + return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)); +} + function controlsCacheKey(apiBase: string): string { return `${CONTROLS_CACHE_PREFIX}${apiBase}`; } @@ -436,11 +446,7 @@ export function useDashboardData(apiBase: string) { }, [updatePoll.data]); /* eslint-enable react-hooks/set-state-in-effect */ - const grouped = useMemo(() => { - const g: Record = {}; - for (const m of models) (g[m.provider] ??= []).push(m); - return Object.entries(g).sort(([a], [b]) => a.localeCompare(b)); - }, [models]); + const grouped = useMemo(() => groupDashboardModels(models), [models]); const filteredGroups = useMemo(() => { const q = modelQuery.trim().toLowerCase(); if (!q) return grouped; diff --git a/gui/tests/dashboard-model-grouping.test.ts b/gui/tests/dashboard-model-grouping.test.ts new file mode 100644 index 0000000000..fb6670c375 --- /dev/null +++ b/gui/tests/dashboard-model-grouping.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from "bun:test"; +import { groupDashboardModels } from "../src/pages/use-dashboard-data"; + +test("Dashboard groups models whose provider names match object prototype properties", () => { + const models = [ + { id: "proto-model", provider: "__proto__" }, + { id: "constructor-model", provider: "constructor" }, + { id: "string-model", provider: "toString" }, + { id: "second-proto-model", provider: "__proto__" }, + ]; + + expect(groupDashboardModels(models)).toEqual([ + ["__proto__", [models[0], models[3]]], + ["constructor", [models[1]]], + ["toString", [models[2]]], + ]); +});