From b4b1798b06787a6a9cf4cd5062ca27894882b7de Mon Sep 17 00:00:00 2001 From: Cheewye Date: Fri, 23 Jan 2026 12:28:59 -0300 Subject: [PATCH] feat(sap): marine status line in ChatShell (existing endpoints) --- frontend/src/components/chat/ChatShell.tsx | 73 ++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/frontend/src/components/chat/ChatShell.tsx b/frontend/src/components/chat/ChatShell.tsx index a09c8b145..a9ab1c297 100644 --- a/frontend/src/components/chat/ChatShell.tsx +++ b/frontend/src/components/chat/ChatShell.tsx @@ -51,6 +51,10 @@ export const ChatShell: React.FC = ({ const [selectedFile, setSelectedFile] = useState(null); const [chatMode, setChatMode] = useState<'general' | 'marine'>('general'); const [showModeDropdown, setShowModeDropdown] = useState(false); + const [marineStatus, setMarineStatus] = useState<{ + ais: 'ok' | 'degraded' | 'down' | 'unknown'; + signalk: 'ok' | 'degraded' | 'down' | 'unknown'; + }>({ ais: 'unknown', signalk: 'unknown' }); const messagesEndRef = useRef(null); const textareaRef = useRef(null); @@ -72,6 +76,50 @@ export const ChatShell: React.FC = ({ } }, [inputText]); + // Fetch marine status when in marine mode + useEffect(() => { + if (chatMode !== 'marine') { + return; + } + + const fetchMarineStatus = async () => { + // Fetch AIS status + try { + const aisRes = await api.get('/vessels/realtime/status'); + const aisData = aisRes.data; + const aisStatus: 'ok' | 'degraded' | 'down' | 'unknown' = + aisData?.service_running === true ? 'ok' : + aisData?.service_running === false ? 'down' : + 'unknown'; + setMarineStatus((prev) => ({ ...prev, ais: aisStatus })); + } catch (err) { + setMarineStatus((prev) => ({ ...prev, ais: 'unknown' })); + } + + // Fetch SignalK status (try vessels endpoint as health check) + try { + const signalkRes = await api.get('/marine/signalk/vessels'); + const signalkStatus: 'ok' | 'degraded' | 'down' | 'unknown' = + signalkRes.status === 200 ? 'ok' : 'degraded'; + setMarineStatus((prev) => ({ ...prev, signalk: signalkStatus })); + } catch (err: any) { + const signalkStatus: 'ok' | 'degraded' | 'down' | 'unknown' = + err.response?.status === 503 ? 'down' : + err.response?.status === 404 ? 'down' : + 'unknown'; + setMarineStatus((prev) => ({ ...prev, signalk: signalkStatus })); + } + }; + + // Fetch immediately + fetchMarineStatus(); + + // Poll every 30s + const interval = setInterval(fetchMarineStatus, 30000); + + return () => clearInterval(interval); + }, [chatMode]); + // Transcribe audio blob to text (SSOT-compliant) const transcribeAudio = async (audioBlob: Blob): Promise => { const formData = new FormData(); @@ -349,6 +397,31 @@ export const ChatShell: React.FC = ({ )} + {/* Marine status line (when in marine mode) */} + {chatMode === 'marine' && ( +
+
+ Marine Status: + + AIS: {marineStatus.ais.toUpperCase()} + + + SignalK: {marineStatus.signalk.toUpperCase()} + +
+
+ )} + {/* Messages */}
{messages.length === 0 ? (