fix: address review issues across settings, sites, brand matcher, and token routes

- settings.ts: trigger route rebuild after globalBlockedBrands changes in both
  import and PUT handler; reject non-array payloads with 400 instead of silently
  defaulting to empty array
- sites.ts: trim model names before deduplication so whitespace variants merge
- brandMatcher.ts: align boundary regex with frontend delimiter class [/:_\-\s]
- Settings.tsx: add role="switch" and aria-checked to brand filter chips; make
  getBrandList best-effort so its failure doesn't block page load
- Sites.tsx: build brandGroups from union of availableModels and disabledModels
  so legacy/removed models remain visible; add stale-response guard and error
  logging for disabled/available model fetches
- TokenRoutes.tsx: use baseFilteredRoutes.length for totalRouteCount to keep
  enabled/disabled counts in sync with status filter; add candidatesVersionRef
  to invalidate lazy caches on payload change; add per-channel in-flight guard
  on toggle; invalidate channel cache after rebuild and site-block operations
This commit is contained in:
Hureru
2026-03-22 20:16:29 +08:00
parent 911d76fa08
commit 26be7c024a
6 changed files with 69 additions and 20 deletions
+27 -4
View File
@@ -401,7 +401,19 @@ function applyImportedSettingToRuntime(key: string, value: unknown) {
try {
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
if (Array.isArray(parsed)) {
config.globalBlockedBrands = parsed.filter((b): b is string => typeof b === 'string').map((b) => b.trim()).filter(Boolean);
const nextBrands = parsed.filter((b): b is string => typeof b === 'string').map((b) => b.trim()).filter(Boolean);
const prev = JSON.stringify(config.globalBlockedBrands);
config.globalBlockedBrands = nextBrands;
if (prev !== JSON.stringify(nextBrands)) {
startBackgroundTask(
{
type: 'maintenance',
title: '品牌屏蔽变更后重建路由',
dedupeKey: 'refresh-models-and-rebuild-routes',
},
async () => refreshModelsAndRebuildRoutes(),
);
}
}
} catch {
return;
@@ -965,9 +977,10 @@ export async function settingsRoutes(app: FastifyInstance) {
}
if (body.globalBlockedBrands !== undefined) {
const nextBrands = Array.isArray(body.globalBlockedBrands)
? body.globalBlockedBrands.filter((b): b is string => typeof b === 'string').map((b) => b.trim()).filter(Boolean)
: [];
if (!Array.isArray(body.globalBlockedBrands)) {
return reply.code(400).send({ error: 'globalBlockedBrands must be an array of strings' });
}
const nextBrands = body.globalBlockedBrands.filter((b): b is string => typeof b === 'string').map((b) => b.trim()).filter(Boolean);
const uniqueBrands = Array.from(new Set(nextBrands));
const prev = JSON.stringify(config.globalBlockedBrands);
const next = JSON.stringify(uniqueBrands);
@@ -976,6 +989,16 @@ export async function settingsRoutes(app: FastifyInstance) {
}
config.globalBlockedBrands = uniqueBrands;
upsertSetting('global_blocked_brands', JSON.stringify(uniqueBrands));
if (prev !== next) {
startBackgroundTask(
{
type: 'maintenance',
title: '品牌屏蔽变更后重建路由',
dedupeKey: 'refresh-models-and-rebuild-routes',
},
async () => refreshModelsAndRebuildRoutes(),
);
}
}
if (body.webhookUrl !== undefined) {
+3 -3
View File
@@ -534,9 +534,9 @@ export async function sitesRoutes(app: FastifyInstance) {
.all();
const models = Array.from(new Set([
...accountModels.map((r) => r.modelName),
...tokenModels.map((r) => r.modelName),
])).filter((m) => m.trim().length > 0).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
...accountModels.map((r) => r.modelName.trim()),
...tokenModels.map((r) => r.modelName.trim()),
])).filter((m) => m.length > 0).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
return { siteId: id, models };
});