Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions desktop/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"check:css": "node scripts/check-css-syntax.mjs src/styles.css && node scripts/check-z-index-tokens.mjs src/styles.css",
"test:todo-visibility": "node scripts/test-todo-visibility.mjs",
"typecheck": "tsc --noEmit",
"test": "tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts",
"test": "tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts && tsx src/__tests__/provider-model-refresh.test.ts",
"test:typecheck": "tsc --noEmit -p tsconfig.test.json",
"test:all": "tsc --noEmit -p tsconfig.test.json && tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts"
"test:all": "tsc --noEmit -p tsconfig.test.json && tsx src/__tests__/math-golden.test.ts && tsx src/__tests__/text-size.test.ts && tsx src/__tests__/provider-model-refresh.test.ts"
},
"dependencies": {
"@tanstack/react-virtual": "^3.14.2",
Expand Down
57 changes: 57 additions & 0 deletions desktop/frontend/src/__tests__/provider-model-refresh.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Run: tsx src/__tests__/provider-model-refresh.test.ts

import { mergedFetchedProviderModels, providerDefaultModel } from "../lib/providerModels";

let passed = 0;
let failed = 0;

function eq(a: unknown, b: unknown, label: string) {
if (JSON.stringify(a) === JSON.stringify(b)) {
process.stdout.write(` PASS ${label}\n`);
passed += 1;
} else {
process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
failed += 1;
}
}

console.log("\nprovider model refresh");

eq(
mergedFetchedProviderModels(["coding-pro"], ["coding-pro", "chat", "vision"]),
["coding-pro", "chat", "vision"],
"appends discovered models without removing curated ones",
);

eq(
mergedFetchedProviderModels(["coding-pro"], ["coding-pro", "chat", "vision"], { preserveCurated: true }),
["coding-pro"],
"background refresh preserves manually curated model list",
);

eq(
mergedFetchedProviderModels(["coding-pro"], ["chat", "vision"], { preserveCurated: true }),
["coding-pro"],
"background refresh does not re-add deleted models",
);

eq(
mergedFetchedProviderModels([], ["coding-pro", "chat"], { preserveCurated: true }),
["coding-pro", "chat"],
"background refresh can populate an empty model list",
);

eq(
providerDefaultModel("coding-pro", ["coding-pro", "chat"]),
"coding-pro",
"preserves current default when it remains available",
);

eq(
providerDefaultModel("deleted", ["coding-pro", "chat"]),
"coding-pro",
"falls back to first saved model when default is unavailable",
);

console.log(`\n${passed} passed, ${failed} failed, ${passed + failed} total`);
if (failed > 0) process.exit(1);
22 changes: 13 additions & 9 deletions desktop/frontend/src/components/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Check, ChevronDown } from "lucide-react";
import { asArray } from "../lib/array";
import { app } from "../lib/bridge";
import { normalizeLangPref, useI18n, useT, type DictKey, type LangPref } from "../lib/i18n";
import { mergedFetchedProviderModels, providerDefaultModel } from "../lib/providerModels";
import { useUpdater } from "../lib/useUpdater";
import {
THEME_STYLES,
Expand Down Expand Up @@ -761,9 +762,10 @@ function ModelsSection({ s, busy, apply, backgroundApply }: ModelsSectionProps)
try {
const fetched = await app.FetchProviderModels(provider);
if (fetched.length === 0) continue;
const currentDefault = provider.default && fetched.includes(provider.default) ? provider.default : fetched[0];
if (sameStringList(provider.models, fetched) && provider.default === currentDefault) continue;
await app.SaveProvider({ ...provider, models: fetched, default: currentDefault });
const models = mergedFetchedProviderModels(provider.models, fetched, { preserveCurated: true });
const currentDefault = providerDefaultModel(provider.default, models);
if (sameStringList(provider.models, models) && provider.default === currentDefault) continue;
await app.SaveProvider({ ...provider, models, default: currentDefault });
} catch {
// Background discovery is opportunistic; manual refresh shows errors.
}
Expand Down Expand Up @@ -1143,11 +1145,12 @@ function ProvidersSection({ s, busy, apply }: SectionProps) {
});
return;
}
const currentDefault = p.default && fetched.includes(p.default) ? p.default : fetched[0];
await app.SaveProvider({ ...p, models: fetched, default: currentDefault });
const models = mergedFetchedProviderModels(p.models, fetched);
const currentDefault = providerDefaultModel(p.default, models);
await app.SaveProvider({ ...p, models, default: currentDefault });
setGroupFetchResult(group.id, {
kind: "ok",
text: t("settings.fetchModelsUpdatedForProvider", { provider: group.label, n: fetched.length }),
text: t("settings.fetchModelsUpdatedForProvider", { provider: group.label, n: models.length }),
});
});
} finally {
Expand All @@ -1172,11 +1175,12 @@ function ProvidersSection({ s, busy, apply }: SectionProps) {
try {
const fetched = await app.FetchProviderModels({ ...probe, apiKeyEnv });
if (fetched.length > 0) {
const currentDefault = probe.default && fetched.includes(probe.default) ? probe.default : fetched[0];
await app.SaveProvider({ ...probe, apiKeyEnv, models: fetched, default: currentDefault });
const models = mergedFetchedProviderModels(probe.models, fetched, { preserveCurated: true });
const currentDefault = providerDefaultModel(probe.default, models);
await app.SaveProvider({ ...probe, apiKeyEnv, models, default: currentDefault });
setGroupFetchResult(group.id, {
kind: "ok",
text: t("settings.fetchModelsUpdatedForProvider", { provider: group.label, n: fetched.length }),
text: t("settings.fetchModelsUpdatedForProvider", { provider: group.label, n: models.length }),
});
return;
}
Expand Down
21 changes: 21 additions & 0 deletions desktop/frontend/src/lib/providerModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export function mergedFetchedProviderModels(current: string[], fetched: string[], options: { preserveCurated?: boolean } = {}): string[] {
const saved = uniqueStrings(current);
if (options.preserveCurated && saved.length > 0) return saved;
return uniqueStrings([...saved, ...fetched]);
}

export function providerDefaultModel(currentDefault: string, models: string[]): string {
return currentDefault && models.includes(currentDefault) ? currentDefault : models[0] ?? "";
}

function uniqueStrings(values: string[]): string[] {
const seen = new Set<string>();
const out: string[] = [];
for (const value of values) {
const model = value.trim();
if (!model || seen.has(model)) continue;
seen.add(model);
out.push(model);
}
return out;
}
Loading