feat(dashboard): show persistent assistant-health save status
This commit is contained in:
@@ -10,6 +10,7 @@ let _slowTimer = null;
|
||||
let _dashboardClient = null;
|
||||
let _lastPlaybookRollbackPatches = null;
|
||||
let _lastBriefingTestAt = null;
|
||||
let _assistantSaveState = null;
|
||||
|
||||
function formatUptime(seconds) {
|
||||
const d = Math.floor(seconds / 86400);
|
||||
@@ -122,6 +123,36 @@ function buildRollbackPatchesFromSnapshot(snapshot) {
|
||||
};
|
||||
}
|
||||
|
||||
function setAssistantSaveState(message, tone = 'neutral') {
|
||||
_assistantSaveState = {
|
||||
message,
|
||||
tone,
|
||||
at: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
function renderAssistantSaveState() {
|
||||
if (!_assistantSaveState) {
|
||||
return '<div id="ops-assistant-status" class="text-sm text-zinc-500 mt-4">No recent save action.</div>';
|
||||
}
|
||||
|
||||
const toneClass = _assistantSaveState.tone === 'success'
|
||||
? 'text-green-500'
|
||||
: _assistantSaveState.tone === 'warning'
|
||||
? 'text-amber-500'
|
||||
: _assistantSaveState.tone === 'error'
|
||||
? 'text-red-500'
|
||||
: 'text-zinc-500';
|
||||
|
||||
const at = new Date(_assistantSaveState.at).toLocaleTimeString(undefined, {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
return `<div id="ops-assistant-status" class="text-sm ${toneClass} mt-4">${escapeHtml(_assistantSaveState.message)} <span class="text-zinc-500">(at ${escapeHtml(at)})</span></div>`;
|
||||
}
|
||||
|
||||
// ── Initial full render ─────────────────────────────────────────
|
||||
|
||||
function renderSkeleton(el) {
|
||||
@@ -492,43 +523,49 @@ function updateContextHealth(contextData) {
|
||||
|
||||
async function applyAssistantPatch(patches, statusEl) {
|
||||
if (!_dashboardClient) {
|
||||
console.warn('[Flynn] applyAssistantPatch: no client');
|
||||
setAssistantSaveState('Save skipped: dashboard client unavailable.', 'error');
|
||||
return;
|
||||
}
|
||||
console.log('[Flynn] applyAssistantPatch:', JSON.stringify(patches));
|
||||
if (statusEl) {
|
||||
statusEl.textContent = 'Saving...';
|
||||
statusEl.className = 'text-sm text-zinc-500';
|
||||
}
|
||||
try {
|
||||
const result = await _dashboardClient.call('config.patch', { patches });
|
||||
console.log('[Flynn] config.patch result:', JSON.stringify(result));
|
||||
const rejected = result?.rejected ?? [];
|
||||
const persistError = result?.persistError;
|
||||
const applied = result?.applied ?? [];
|
||||
const persisted = result?.persisted === true;
|
||||
|
||||
let message = '';
|
||||
let tone = 'neutral';
|
||||
if (statusEl) {
|
||||
if (persistError) {
|
||||
statusEl.textContent = `Save failed: ${persistError}`;
|
||||
statusEl.className = 'text-sm text-red-500';
|
||||
message = `Save failed: ${persistError}`;
|
||||
tone = 'error';
|
||||
} else if (rejected.length > 0) {
|
||||
statusEl.textContent = `Rejected: ${rejected.join(', ')}`;
|
||||
statusEl.className = 'text-sm text-red-500';
|
||||
message = `Rejected: ${rejected.join(', ')}`;
|
||||
tone = 'error';
|
||||
} else if (!persisted) {
|
||||
statusEl.textContent = `Runtime saved (${applied.length} updated)`;
|
||||
statusEl.className = 'text-sm text-amber-500';
|
||||
message = `Runtime-only save (${applied.length} updated, file persistence unavailable)`;
|
||||
tone = 'warning';
|
||||
} else {
|
||||
statusEl.textContent = `Saved & persisted (${applied.length} updated)`;
|
||||
statusEl.className = 'text-sm text-green-500';
|
||||
message = `Saved to runtime + config file (${applied.length} updated)`;
|
||||
tone = 'success';
|
||||
}
|
||||
setAssistantSaveState(message, tone);
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = `text-sm ${tone === 'success' ? 'text-green-500' : tone === 'warning' ? 'text-amber-500' : tone === 'error' ? 'text-red-500' : 'text-zinc-500'}`;
|
||||
}
|
||||
return { persisted, applied, rejected, persistError };
|
||||
} catch (error) {
|
||||
console.error('[Flynn] config.patch error:', error);
|
||||
const msg = `Save error: ${error instanceof Error ? error.message : String(error)}`;
|
||||
setAssistantSaveState(msg, 'error');
|
||||
if (statusEl) {
|
||||
statusEl.textContent = `Save error: ${error instanceof Error ? error.message : String(error)}`;
|
||||
statusEl.textContent = msg;
|
||||
statusEl.className = 'text-sm text-red-500';
|
||||
}
|
||||
return { persisted: false, applied: [], rejected: [], persistError: msg };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,7 +737,7 @@ function updateAssistantHealth(configData) {
|
||||
<div class="max-h-44 overflow-y-auto bg-zinc-950 border border-zinc-800 rounded-md p-3"><code class="text-sm text-zinc-400 font-mono whitespace-pre-wrap">${escapeHtml(briefingPrompt || 'No daily briefing prompt configured.')}</code></div>
|
||||
${briefingReady ? '' : '<div class="text-sm text-zinc-500 mt-2">Enable daily briefing and set output channel/peer to test-send.</div>'}
|
||||
</div>
|
||||
<div id="ops-assistant-status" class="text-sm text-zinc-500 mt-4"></div>
|
||||
${renderAssistantSaveState()}
|
||||
`;
|
||||
|
||||
const statusEl = el.querySelector('#ops-assistant-status');
|
||||
@@ -765,15 +802,7 @@ function updateAssistantHealth(configData) {
|
||||
updateSessionAnalytics(refreshed.sessionAnalytics);
|
||||
updateContextHealth(refreshed.contextUsage);
|
||||
// Capture status message before re-render destroys the DOM element
|
||||
const savedText = statusEl?.textContent ?? '';
|
||||
const savedClass = statusEl?.className ?? '';
|
||||
updateAssistantHealth(refreshed.config);
|
||||
// Restore status message in the new DOM
|
||||
const newStatusEl = document.getElementById('ops-assistant-status');
|
||||
if (newStatusEl && savedText) {
|
||||
newStatusEl.textContent = savedText;
|
||||
newStatusEl.className = savedClass;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -963,5 +992,6 @@ export const DashboardPage = {
|
||||
_dashboardClient = null;
|
||||
_lastPlaybookRollbackPatches = null;
|
||||
_lastBriefingTestAt = null;
|
||||
_assistantSaveState = null;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user