From 8ba6a71a490f658d8ff5d522ab72a22bd253ea04 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 24 Apr 2026 16:24:47 +0200 Subject: [PATCH] feat(app): service_status Banner oben in ChatScreen App-Pendant zum Diagnostic-Banner. Wenn die Gamebox-Bridges (F5-TTS / Whisper) ihren Lade-Status broadcasten, zeigt die App oben unter der Verbindungs-Statusleiste ein farbiges Banner: Gelb = irgendwas laedt (NICHT wegtippbar) Gruen = alles bereit (tippbar zum Schliessen) Rot = Fehler Banner aggregiert beide Services in einer Kachel. Dismiss-State wird zurueckgesetzt sobald irgendein Service wieder in 'loading' geht (z.B. Modell-Wechsel via Diagnostic). Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/screens/ChatScreen.tsx | 83 ++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 1cbab16..8f156db 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -108,6 +108,9 @@ const ChatScreen: React.FC = () => { const [searchVisible, setSearchVisible] = useState(false); const [pendingAttachments, setPendingAttachments] = useState<{file: any, isPhoto: boolean}[]>([]); const [agentActivity, setAgentActivity] = useState<{activity: string, tool: string}>({activity: 'idle', tool: ''}); + // Service-Status (Gamebox: F5-TTS / Whisper Lade-Status) + Banner-Sichtbarkeit + const [serviceStatus, setServiceStatus] = useState>({}); + const [serviceBannerDismissed, setServiceBannerDismissed] = useState(false); // Gerätelokale TTS-Config: globaler Toggle (aus Settings) + temporäres Muten (Mund-Button) const [ttsDeviceEnabled, setTtsDeviceEnabled] = useState(true); const [ttsMuted, setTtsMuted] = useState(false); @@ -351,6 +354,24 @@ const ChatScreen: React.FC = () => { ToastAndroid.show(`Stimme "${v || 'Standard'}" bereit`, ToastAndroid.SHORT); } } + + // Gamebox-Bridges (f5tts/whisper) melden Lade-Status — Banner oben + if (message.type === ('service_status' as any)) { + const p = message.payload as any; + const svc = (p?.service as string) || ''; + if (!svc) return; + setServiceStatus(prev => ({ + ...prev, + [svc]: { + state: (p?.state as string) || 'unknown', + model: p?.model as string | undefined, + loadSeconds: p?.loadSeconds as number | undefined, + error: p?.error as string | undefined, + }, + })); + // Bei neuer Loading-Phase Banner wieder aktivieren + if (p?.state === 'loading') setServiceBannerDismissed(false); + } }); const unsubState = rvs.onStateChange((state) => { @@ -764,6 +785,49 @@ const ChatScreen: React.FC = () => { + {/* Service-Status Banner (Gamebox: F5-TTS / Whisper Lade-Status) */} + {(() => { + const entries = Object.entries(serviceStatus); + if (entries.length === 0 || serviceBannerDismissed) return null; + const anyLoading = entries.some(([, v]) => v.state === 'loading'); + const anyError = entries.some(([, v]) => v.state === 'error'); + const allReady = !anyLoading && !anyError && entries.every(([, v]) => v.state === 'ready'); + const bg = anyError ? '#3A1F1F' : anyLoading ? '#3A331F' : '#1F3A2A'; + const border = anyError ? '#FF3B30' : anyLoading ? '#FFD60A' : '#34C759'; + const labels: Record = { f5tts: 'F5-TTS', whisper: 'Whisper STT' }; + return ( + { if (allReady) setServiceBannerDismissed(true); }} + style={[styles.serviceBanner, { backgroundColor: bg, borderColor: border }]} + > + {entries.map(([svc, info]) => { + let icon = '\u23F3', text = ''; + if (info.state === 'loading') { + text = `${labels[svc] || svc}: laedt${info.model ? ' ' + info.model : ''}...`; + } else if (info.state === 'ready') { + icon = '\u2705'; + const sec = info.loadSeconds ? ` (${info.loadSeconds.toFixed(1)}s)` : ''; + text = `${labels[svc] || svc}: bereit${info.model ? ' ' + info.model : ''}${sec}`; + } else if (info.state === 'error') { + icon = '\u274C'; + text = `${labels[svc] || svc}: Fehler ${info.error || ''}`; + } else { + text = `${labels[svc] || svc}: ${info.state}`; + } + return ( + + {icon} {text} + + ); + })} + + {allReady ? 'Tippen zum Schliessen' : 'Bitte warten...'} + + + ); + })()} + {/* Suchleiste */} {searchVisible && ( @@ -978,6 +1042,25 @@ const styles = StyleSheet.create({ color: '#8888AA', fontSize: 12, }, + serviceBanner: { + paddingVertical: 8, + paddingHorizontal: 12, + borderTopWidth: 0, + borderBottomWidth: 1, + borderLeftWidth: 0, + borderRightWidth: 0, + }, + serviceBannerLine: { + color: '#FFFFFF', + fontSize: 12, + lineHeight: 18, + }, + serviceBannerHint: { + color: '#AAAACC', + fontSize: 10, + marginTop: 2, + fontStyle: 'italic', + }, messageList: { padding: 12, paddingBottom: 8,