fix(dashboard): preserve unsaved model tier selections across refresh

This commit is contained in:
William Valentin
2026-02-19 10:29:36 -08:00
parent 708683297a
commit 4e40878ad5
2 changed files with 72 additions and 13 deletions
+61 -13
View File
@@ -13,6 +13,10 @@ let _lastBriefingTestAt = null;
let _assistantSaveState = null;
let _lastAssistantConfig = null;
let _assistantManualOverrides = new Set();
let _assistantModelDefaultsDraft = null;
const MODEL_DEFAULT_TASK_KEYS = ['compaction', 'memory_extraction', 'classification', 'tool_summarisation', 'complex_reasoning'];
const MODEL_DEFAULT_TIER_KEYS = ['default', 'fast', 'complex', 'local'];
function formatUptime(seconds) {
const d = Math.floor(seconds / 86400);
@@ -666,6 +670,7 @@ async function triggerDailyBriefingTest(jobName, statusEl) {
function updateAssistantHealth(configData) {
const el = document.getElementById('ops-assistant-health');
if (!el) {return;}
_assistantModelDefaultsDraft = readAssistantModelDefaultsDraft(el) ?? _assistantModelDefaultsDraft;
const snapshot = getAssistantStateSnapshot(configData);
@@ -692,7 +697,7 @@ function updateAssistantHealth(configData) {
: 'not configured';
const briefingReady = dailyBriefing && Boolean(briefingOutput?.channel && briefingOutput?.peer);
const playbookLikeReady = announce || (memoryDaily && memoryProactive);
const modelTier = configData?.agents?.primary_tier ?? 'default';
const modelTier = _assistantModelDefaultsDraft?.primaryTier ?? configData?.agents?.primary_tier ?? 'default';
const delegation = configData?.agents?.delegation ?? {};
const backgroundModels = configData?.agents?.background_models ?? {};
const tiers = configData?.models ?? {};
@@ -787,10 +792,10 @@ function updateAssistantHealth(configData) {
<div class="text-sm font-semibold text-zinc-50 mb-3">Model Tier Defaults</div>
<div class="text-sm text-zinc-500 mb-3">Tier provider/model definitions</div>
<div class="space-y-3 mb-4">
${['default', 'fast', 'complex', 'local'].map((tier) => {
${MODEL_DEFAULT_TIER_KEYS.map((tier) => {
const cfg = tiers?.[tier] ?? {};
const provider = cfg.provider ?? tiers?.default?.provider ?? 'openai';
const model = cfg.model ?? '';
const provider = _assistantModelDefaultsDraft?.tiers?.[tier]?.provider ?? cfg.provider ?? tiers?.default?.provider ?? 'openai';
const model = _assistantModelDefaultsDraft?.tiers?.[tier]?.model ?? cfg.model ?? '';
return `
<div class="p-3 border border-zinc-800 rounded-md bg-zinc-950/60">
<div class="text-sm text-zinc-50 mb-2">${escapeHtml(tier)} tier</div>
@@ -822,8 +827,9 @@ function updateAssistantHealth(configData) {
<div class="space-y-3">
${taskRows.map((task) => {
const background = backgroundModels?.[task.key] ?? {};
const delegationTier = delegation?.[task.key] ?? 'fast';
const backgroundEnabled = Boolean(backgroundModels?.[task.key] && background?.enabled !== false);
const draftTask = _assistantModelDefaultsDraft?.tasks?.[task.key] ?? {};
const delegationTier = draftTask.delegationTier ?? delegation?.[task.key] ?? 'fast';
const backgroundEnabled = draftTask.backgroundEnabled ?? Boolean(backgroundModels?.[task.key] && background?.enabled !== false);
return `
<div class="p-3 border border-zinc-800 rounded-md bg-zinc-950/60">
<div class="text-sm text-zinc-50 mb-2">${escapeHtml(task.label)}</div>
@@ -841,17 +847,21 @@ function updateAssistantHealth(configData) {
<label class="flex flex-col gap-1">
<span class="text-xs text-zinc-500">Provider</span>
<select id="assist-bg-${task.key}-provider" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none">
${providerOption(background?.provider ?? tiers?.default?.provider ?? 'openai')}
${providerOption(draftTask.provider ?? background?.provider ?? tiers?.default?.provider ?? 'openai')}
</select>
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-zinc-500">Model</span>
${modelDataList(`assist-bg-${task.key}-model`, background?.provider ?? tiers?.default?.provider ?? 'openai', background?.model ?? '')}
${modelDataList(
`assist-bg-${task.key}-model`,
draftTask.provider ?? background?.provider ?? tiers?.default?.provider ?? 'openai',
draftTask.model ?? background?.model ?? '',
)}
</label>
<label class="flex flex-col gap-1">
<span class="text-xs text-zinc-500">Fallback tier</span>
<select id="assist-bg-${task.key}-fallback" class="bg-zinc-950 text-zinc-50 border border-zinc-800 rounded-md px-2 py-1.5 text-sm focus:border-blue-500 outline-none">
${tierOption(background?.fallback_tier ?? 'fast')}
${tierOption(draftTask.fallbackTier ?? background?.fallback_tier ?? 'fast')}
</select>
</label>
</div>
@@ -928,7 +938,7 @@ function updateAssistantHealth(configData) {
});
}
const taskRowsForModels = ['compaction', 'memory_extraction', 'classification', 'tool_summarisation', 'complex_reasoning'];
const taskRowsForModels = MODEL_DEFAULT_TASK_KEYS;
for (const task of taskRowsForModels) {
const providerSelect = el.querySelector(`#assist-bg-${task}-provider`);
if (!providerSelect) {continue;}
@@ -996,11 +1006,11 @@ function updateAssistantHealth(configData) {
};
_assistantManualOverrides.add('automation.daily_briefing.enabled');
} else if (action === 'save-model-defaults') {
const tasks = ['compaction', 'memory_extraction', 'classification', 'tool_summarisation', 'complex_reasoning'];
const tasks = MODEL_DEFAULT_TASK_KEYS;
patches = {
'agents.primary_tier': (el.querySelector('#assist-primary-tier')?.value ?? 'default'),
};
const tiers = ['default', 'fast', 'complex', 'local'];
const tiers = MODEL_DEFAULT_TIER_KEYS;
for (const tier of tiers) {
const provider = (el.querySelector(`#assist-tier-${tier}-provider`)?.value ?? '').trim();
const model = (el.querySelector(`#assist-tier-${tier}-model`)?.value ?? '').trim();
@@ -1033,7 +1043,12 @@ function updateAssistantHealth(configData) {
}
}
if (!patches) {return;}
await applyAssistantPatch(patches, statusEl);
const patchResult = await applyAssistantPatch(patches, statusEl);
if (action === 'save-model-defaults' && patchResult.applied.length > 0 && patchResult.rejected.length === 0) {
_assistantModelDefaultsDraft = null;
} else if (action === 'save-model-defaults') {
_assistantModelDefaultsDraft = readAssistantModelDefaultsDraft(el) ?? _assistantModelDefaultsDraft;
}
// Force immediate refresh of slow sections after applying.
const refreshed = await fetchSlow(_dashboardClient);
if (refreshed) {
@@ -1052,6 +1067,38 @@ function updateAssistantHealth(configData) {
});
}
function readAssistantModelDefaultsDraft(rootEl) {
const primaryTier = rootEl.querySelector('#assist-primary-tier')?.value;
if (!primaryTier) {
return null;
}
const tiers = {};
for (const tier of MODEL_DEFAULT_TIER_KEYS) {
const provider = (rootEl.querySelector(`#assist-tier-${tier}-provider`)?.value ?? '').trim();
const model = (rootEl.querySelector(`#assist-tier-${tier}-model`)?.value ?? '').trim();
if (!provider && !model) {continue;}
tiers[tier] = { provider, model };
}
const tasks = {};
for (const task of MODEL_DEFAULT_TASK_KEYS) {
tasks[task] = {
delegationTier: rootEl.querySelector(`#assist-delegation-${task}`)?.value ?? 'fast',
backgroundEnabled: Boolean(rootEl.querySelector(`#assist-bg-${task}-enabled`)?.checked),
provider: (rootEl.querySelector(`#assist-bg-${task}-provider`)?.value ?? '').trim(),
model: (rootEl.querySelector(`#assist-bg-${task}-model`)?.value ?? '').trim(),
fallbackTier: rootEl.querySelector(`#assist-bg-${task}-fallback`)?.value ?? 'fast',
};
}
return {
primaryTier,
tiers,
tasks,
};
}
function _updateChannels(channelsData) {
const el = document.getElementById('ops-channels');
if (!el) {return;}
@@ -1248,5 +1295,6 @@ export const DashboardPage = {
_assistantSaveState = null;
_lastAssistantConfig = null;
_assistantManualOverrides = new Set();
_assistantModelDefaultsDraft = null;
},
};