diff --git a/README.md b/README.md index 88fb836..1c156ce 100644 --- a/README.md +++ b/README.md @@ -487,7 +487,7 @@ Flynn includes a built-in web control dashboard served by the WebSocket gateway. | Page | Description | |------|-------------| -| **Dashboard** | System health cards, channel status, usage stats. Auto-refreshes every 10s | +| **Dashboard** | System health cards, channel status, usage stats, assistant-health quick actions, and daily briefing preview/test-send. Auto-refreshes every 10s | | **Chat** | Session selector, streaming tool events, markdown rendering with syntax highlighting | | **Sessions** | Browse all sessions, view message history, delete sessions | | **Usage** | Token usage summary cards, per-session breakdown table, auto-refresh | diff --git a/docs/plans/state.json b/docs/plans/state.json index 33b8151..f58c2d4 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -5480,6 +5480,19 @@ "docs/plans/state.json" ], "test_status": "pnpm typecheck passing" + }, + "dashboard-morning-brief-preview-test-send": { + "status": "completed", + "date": "2026-02-18", + "updated": "2026-02-18", + "summary": "Extended Dashboard Assistant Health with a Morning Brief Preview panel (prompt/schedule/tier/output metadata) and a one-click Test Briefing action that triggers the configured daily briefing via cron.trigger through gateway tools.invoke, including inline save/trigger status feedback.", + "files_modified": [ + "src/gateway/ui/pages/dashboard.js", + "src/gateway/ui/style.css", + "README.md", + "docs/plans/state.json" + ], + "test_status": "pnpm typecheck passing" } }, "overall_progress": { diff --git a/src/gateway/ui/pages/dashboard.js b/src/gateway/ui/pages/dashboard.js index 1eda2e8..0b7c0f7 100644 --- a/src/gateway/ui/pages/dashboard.js +++ b/src/gateway/ui/pages/dashboard.js @@ -453,6 +453,39 @@ async function applyAssistantPatch(patches, statusEl) { } } +async function triggerDailyBriefingTest(jobName, statusEl) { + if (!_dashboardClient) {return;} + if (statusEl) { + statusEl.textContent = 'Triggering test briefing...'; + statusEl.className = 'text-sm text-muted'; + } + try { + const result = await _dashboardClient.call('tools.invoke', { + tool: 'cron.trigger', + args: { name: jobName }, + }); + + if (result?.success) { + const output = typeof result.output === 'string' ? result.output : 'Triggered.'; + if (statusEl) { + statusEl.textContent = output; + statusEl.className = 'text-sm text-success'; + } + return; + } + + if (statusEl) { + statusEl.textContent = result?.error ?? 'Failed to trigger briefing.'; + statusEl.className = 'text-sm text-error'; + } + } catch (error) { + if (statusEl) { + statusEl.textContent = `Trigger error: ${error instanceof Error ? error.message : String(error)}`; + statusEl.className = 'text-sm text-error'; + } + } +} + function updateAssistantHealth(configData) { const el = document.getElementById('ops-assistant-health'); if (!el) {return;} @@ -468,6 +501,17 @@ function updateAssistantHealth(configData) { const memoryProactive = Boolean(memory.proactive_extract?.enabled); const proactiveThreshold = Number(memory.proactive_extract?.min_tool_calls ?? 1); const ttsEnabled = Boolean(tts.enabled); + const briefing = automation.daily_briefing ?? {}; + const briefingName = briefing.name ?? 'daily-briefing'; + const briefingSchedule = briefing.schedule ?? '0 8 * * *'; + const briefingPrompt = briefing.prompt ?? ''; + const briefingOutput = briefing.output ?? null; + const briefingModelTier = briefing.model_tier ?? 'default'; + const briefingTimezone = briefing.timezone ?? 'system'; + const briefingOutputLabel = briefingOutput?.channel && briefingOutput?.peer + ? `${briefingOutput.channel}/${briefingOutput.peer}` + : 'not configured'; + const briefingReady = dailyBriefing && Boolean(briefingOutput?.channel && briefingOutput?.peer); const chip = (label, value) => `
${escapeHtml(briefingPrompt || 'No daily briefing prompt configured.')}