| <script lang="ts"> |
| import { briefingState } from '$lib/stores/briefingState.svelte'; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const SHORT: Record<string, string> = { |
| geocode: 'geocoding', |
| nta_resolve: 'resolving NTA', |
| sandy_inundation: 'Sandy 2012', |
| dep_stormwater: 'DEP scenarios', |
| floodnet: 'FloodNet sensors', |
| nyc311: 'NYC 311 history', |
| noaa_tides: 'NOAA tides', |
| nws_alerts: 'NWS alerts', |
| nws_obs: 'NWS hourly obs', |
| ttm_forecast: 'TTM r2 surge (zero-shot)', |
| ttm_311_forecast: 'TTM r2 weekly 311', |
| ttm_battery_surge: 'TTM Battery (NYC fine-tune)', |
| floodnet_forecast: 'FloodNet recurrence forecast', |
| ida_hwm_2021: 'Ida 2021 HWMs', |
| |
| |
| |
| |
| |
| |
| prithvi_eo_v2: 'Ida 2021 polygons (baked lookup)', |
| prithvi_eo_live: 'Prithvi-NYC-Pluvial v2 segmentation', |
| microtopo_lidar: 'LiDAR microtopo', |
| mta_entrance_exposure: 'MTA entrances', |
| nycha_development_exposure: 'NYCHA developments', |
| doe_school_exposure: 'DOE schools', |
| doh_hospital_exposure: 'NYS DOH hospitals', |
| terramind_synthesis: 'TerraMind v1 synthesis', |
| terramind_lulc: 'TerraMind LULC', |
| terramind_buildings: 'TerraMind Buildings', |
| eo_chip_fetch: 'fetching S2/S1/DEM chip', |
| rag_granite_embedding: 'RAG retrieval', |
| gliner_extract: 'GLiNER typed extraction', |
| }; |
| |
| let visible = $derived( |
| briefingState.phase !== 'idle' && briefingState.phase !== 'done' |
| ); |
| |
| let phaseLabel = $derived.by(() => { |
| switch (briefingState.phase) { |
| case 'planning': return 'planning intent'; |
| case 'specialists': return 'gathering evidence'; |
| case 'reconciling': return 'reconciling'; |
| case 'streaming': return briefingState.attempt > 1 |
| ? `writing (reroll ${briefingState.attempt - 1})` |
| : 'writing briefing'; |
| case 'error': return 'error'; |
| default: return ''; |
| } |
| }); |
| |
| let stepLabel = $derived.by(() => { |
| const s = briefingState.activeStep; |
| if (!s) return null; |
| return SHORT[s] ?? s; |
| }); |
| |
| let progress = $derived.by(() => { |
| if (briefingState.phase !== 'specialists' && briefingState.phase !== 'reconciling') return null; |
| const fired = briefingState.firedCount; |
| const total = briefingState.totalSpecialists; |
| if (!total) return fired > 0 ? `${fired}` : null; |
| return `${fired}/${total}`; |
| }); |
| |
| let kind = $derived.by(() => { |
| |
| if (briefingState.phase === 'error') return 'err'; |
| return 'live'; |
| }); |
| </script> |
|
|
| {#if visible} |
| <span class="status" data-kind={kind} aria-live="polite" aria-atomic="true"> |
| <span class="status-dot" aria-hidden="true"></span> |
| <span class="status-phase">{phaseLabel}</span> |
| {#if stepLabel} |
| <span class="status-sep">·</span> |
| <span class="status-step">{stepLabel}</span> |
| {/if} |
| {#if progress} |
| <span class="status-sep">·</span> |
| <span class="status-progress">{progress}</span> |
| {/if} |
| {#if briefingState.phase === 'error' && briefingState.errorMessage} |
| <span class="status-sep">·</span> |
| <span class="status-err">{briefingState.errorMessage}</span> |
| {/if} |
| </span> |
| {/if} |
|
|
| <style> |
| .status { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| padding: 2px 10px; |
| background: var(--paper-deep); |
| border: 1px solid var(--rule-soft); |
| font-family: var(--font-mono); |
| font-size: 11px; |
| color: var(--ink-secondary); |
| letter-spacing: 0.04em; |
| max-width: min(60ch, 50vw); |
| overflow: hidden; |
| white-space: nowrap; |
| text-overflow: ellipsis; |
| } |
| .status[data-kind='err'] { |
| border-color: #B91C1C; |
| color: #B91C1C; |
| } |
| .status-dot { |
| width: 7px; |
| height: 7px; |
| border-radius: 50%; |
| flex: none; |
| background: var(--accent-graphical); |
| animation: pulse 1.4s ease-in-out infinite; |
| } |
| .status[data-kind='err'] .status-dot { |
| background: #B91C1C; |
| animation: none; |
| } |
| .status-phase { |
| color: var(--ink); |
| text-transform: lowercase; |
| letter-spacing: 0.05em; |
| font-weight: 600; |
| } |
| .status-sep { color: var(--ink-tertiary); opacity: 0.6; } |
| .status-step { |
| color: var(--ink-secondary); |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| .status-progress { color: var(--ink); font-weight: 600; } |
| .status-err { color: #B91C1C; } |
| @keyframes pulse { |
| 0%, 100% { opacity: 0.35; transform: scale(0.85); } |
| 50% { opacity: 1; transform: scale(1.1); } |
| } |
| @media (prefers-reduced-motion: reduce) { |
| .status-dot { animation: none; opacity: 0.7; } |
| } |
| </style> |
|
|