fix(diagnostic+app): Chat-UI bubblig, mehrzeilig + persistente RVS + STT-Logs
Diagnostic-UI: - chat-msg ist jetzt eine richtige Bubble (border-radius 14px, Schatten, flex-Layout statt margin-Hack, Tail-Radius zur Sender-Seite hin). - Eingabefelder (haupt + Vollbild) jetzt textarea mit Auto-Resize. Enter sendet, Shift+Enter macht neue Zeile. - white-space: pre-wrap behaelt Zeilenumbrueche aus dem Text bei. Diagnostic-Server: - sendToRVS_raw nutzt jetzt die persistente rvsWs statt fuer jedes Send eine frische Verbindung aufzubauen. Der frische-WS-Pfad hatte Race- Probleme (WS schloss bevor RVS broadcasten konnte → User-Nachrichten von Diagnostic kamen nicht in der App an). Frische WS bleibt als Fallback wenn die persistente gerade tot ist. App: - console.log am Anfang des chat-handlers + im STT-Result-Handler mit findIndex-Result und Placeholder-Count. Bei nicht-erkanntem STT-Text liefert `adb logcat -s ReactNativeJS:V` jetzt direkt den Befund: kommt das Event ueberhaupt an, findet er die Placeholder? Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -283,6 +283,8 @@ const ChatScreen: React.FC = () => {
|
||||
|
||||
if (message.type === 'chat') {
|
||||
const sender = (message.payload.sender as string) || '';
|
||||
const dbgText = ((message.payload.text as string) || '').slice(0, 60);
|
||||
console.log('[Chat] chat-event sender=%s text=%s', sender || '(none)', dbgText);
|
||||
|
||||
// STT-Ergebnis: Transkribierten Text in die Sprach-Bubble schreiben.
|
||||
// WICHTIG: Nur die ERSTE noch unaufgeloeste Aufnahme matchen — sonst
|
||||
@@ -295,6 +297,9 @@ const ChatScreen: React.FC = () => {
|
||||
const idx = prev.findIndex(m =>
|
||||
m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')
|
||||
);
|
||||
console.log('[Chat] STT-Result: idx=%d text="%s" placeholders=%d',
|
||||
idx, sttText.slice(0, 60),
|
||||
prev.filter(m => m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')).length);
|
||||
const newText = `\uD83C\uDFA4 ${sttText}`;
|
||||
if (idx < 0) {
|
||||
// Defensiv: wenn keine Placeholder im State (z.B. weil sie nie
|
||||
|
||||
+40
-16
@@ -63,24 +63,35 @@
|
||||
.log-entry.pipeline-sep { color: #333; margin: 6px 0 2px; }
|
||||
|
||||
.chat-box { background: #080810; border: 1px solid #1E1E2E; border-radius: 6px;
|
||||
min-height: 120px; max-height: 250px; overflow-y: auto; padding: 8px; margin-bottom: 8px; }
|
||||
.chat-msg { margin-bottom: 6px; padding: 6px 10px; border-radius: 6px; font-size: 13px; line-height: 1.5; word-wrap: break-word; }
|
||||
.chat-msg.sent { background: #0096FF; color: #fff; margin-left: 20%; text-align: right; }
|
||||
.chat-msg.received { background: #1E1E2E; margin-right: 20%; }
|
||||
.chat-msg.error { background: #3B1010; color: #FF6B6B; }
|
||||
.chat-msg .meta { font-size: 10px; color: rgba(255,255,255,0.4); margin-top: 2px; }
|
||||
min-height: 120px; max-height: 250px; overflow-y: auto;
|
||||
padding: 12px; margin-bottom: 8px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.chat-msg { padding: 10px 14px; border-radius: 14px; font-size: 14px; line-height: 1.5;
|
||||
word-wrap: break-word; max-width: 80%; white-space: pre-wrap;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.4); }
|
||||
.chat-msg.sent { background: #0096FF; color: #fff; align-self: flex-end;
|
||||
border-bottom-right-radius: 4px; }
|
||||
.chat-msg.received { background: #1E1E2E; color: #E8E8F0; align-self: flex-start;
|
||||
border-bottom-left-radius: 4px; }
|
||||
.chat-msg.error { background: #3B1010; color: #FF6B6B; align-self: flex-start; }
|
||||
.chat-msg .meta { font-size: 10px; color: rgba(255,255,255,0.4); margin-top: 4px;
|
||||
display: block; }
|
||||
.chat-msg a { color: #66BBFF; text-decoration: underline; }
|
||||
.chat-msg.sent a { color: #CCEEFF; }
|
||||
.chat-msg .chat-media { max-width: 100%; max-height: 200px; border-radius: 4px; margin-top: 4px; cursor: pointer; display: block; }
|
||||
.chat-msg .chat-media { max-width: 100%; max-height: 200px; border-radius: 8px;
|
||||
margin-top: 6px; cursor: pointer; display: block; }
|
||||
.chat-msg .chat-media:hover { opacity: 0.85; }
|
||||
.lightbox-overlay { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.92);
|
||||
z-index:2000; justify-content:center; align-items:center; cursor:pointer; }
|
||||
.lightbox-overlay.open { display:flex; }
|
||||
.lightbox-overlay img, .lightbox-overlay video { max-width:95vw; max-height:95vh; border-radius:8px; }
|
||||
|
||||
.input-row { display: flex; gap: 6px; }
|
||||
.input-row input { flex: 1; background: #1E1E2E; border: 1px solid #333; border-radius: 6px;
|
||||
padding: 8px 12px; color: #E0E0F0; font-family: inherit; font-size: 13px; }
|
||||
.input-row { display: flex; gap: 6px; align-items: flex-end; }
|
||||
.input-row input, .input-row textarea {
|
||||
flex: 1; background: #1E1E2E; border: 1px solid #333; border-radius: 6px;
|
||||
padding: 8px 12px; color: #E0E0F0; font-family: inherit; font-size: 13px;
|
||||
}
|
||||
.input-row textarea { resize: none; min-height: 38px; max-height: 200px; line-height: 1.4;
|
||||
overflow-y: auto; }
|
||||
|
||||
/* Terminal Modal */
|
||||
.modal-overlay { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.85);
|
||||
@@ -282,7 +293,7 @@
|
||||
📎
|
||||
<input type="file" id="diag-file-input" multiple accept="image/*,application/pdf,.doc,.docx,.txt" style="display:none;" onchange="handleDiagFileSelect(this.files)">
|
||||
</label>
|
||||
<input type="text" id="chat-input" placeholder="Nachricht an ARIA..." onpaste="handleDiagPaste(event)">
|
||||
<textarea id="chat-input" placeholder="Nachricht an ARIA... (Enter sendet, Shift+Enter neue Zeile)" rows="2" onpaste="handleDiagPaste(event)" oninput="autoResizeTextarea(this)"></textarea>
|
||||
<button class="btn" id="btn-gw" onclick="testGateway()">Gateway senden</button>
|
||||
<button class="btn" id="btn-rvs" onclick="testRVS()">Via RVS senden</button>
|
||||
</div>
|
||||
@@ -300,7 +311,7 @@
|
||||
<span style="animation:pulse 1s infinite;">💭</span> <span id="thinking-text-fs">ARIA denkt...</span>
|
||||
</div>
|
||||
<div class="input-row" style="margin-top:8px;">
|
||||
<input type="text" id="chat-input-fs" placeholder="Nachricht an ARIA..." onkeydown="if(event.key==='Enter'){testRVSFS();event.preventDefault();}">
|
||||
<textarea id="chat-input-fs" placeholder="Nachricht an ARIA... (Enter sendet, Shift+Enter neue Zeile)" rows="2" oninput="autoResizeTextarea(this)"></textarea>
|
||||
<button class="btn" onclick="testGatewayFS()">Gateway senden</button>
|
||||
<button class="btn" onclick="testRVSFS()">Via RVS senden</button>
|
||||
</div>
|
||||
@@ -2069,10 +2080,23 @@
|
||||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// Enter-Taste sendet via Gateway
|
||||
document.getElementById('chat-input').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') testRVS();
|
||||
});
|
||||
// Auto-Resize fuer Textarea — wuchst mit dem Inhalt bis zum max-height
|
||||
function autoResizeTextarea(el) {
|
||||
el.style.height = 'auto';
|
||||
el.style.height = Math.min(el.scrollHeight, 200) + 'px';
|
||||
}
|
||||
|
||||
// Enter sendet, Shift+Enter macht neue Zeile (chat-Standard).
|
||||
function chatInputKeydown(e, sendFn) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendFn();
|
||||
// Textarea zurueck auf 2 rows setzen
|
||||
e.target.style.height = 'auto';
|
||||
}
|
||||
}
|
||||
document.getElementById('chat-input').addEventListener('keydown', (e) => chatInputKeydown(e, testRVS));
|
||||
document.getElementById('chat-input-fs').addEventListener('keydown', (e) => chatInputKeydown(e, testRVSFS));
|
||||
|
||||
// Escape schliesst Lightbox
|
||||
document.addEventListener('keydown', (e) => {
|
||||
|
||||
+14
-1
@@ -716,11 +716,24 @@ function sendToRVS_withResponse(sendType, sendPayload, expectType, clientWs) {
|
||||
|
||||
function sendToRVS_raw(msgObj) {
|
||||
if (!RVS_HOST || !RVS_TOKEN) return;
|
||||
const payload = JSON.stringify(msgObj);
|
||||
// Persistente Connection bevorzugen — die ist garantiert connected
|
||||
// und wird vom RVS direkt an alle anderen Clients (App, Bridge) broadcastet.
|
||||
// Frische Connections hatten Race-Probleme: die WS war nach dem send manchmal
|
||||
// schon zu, bevor RVS broadcasten konnte → App-Nachrichten verloren.
|
||||
if (rvsWs && rvsWs.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
rvsWs.send(payload);
|
||||
return;
|
||||
} catch (err) {
|
||||
log("warn", "rvs", `persistente Verbindung send failed (${err.message}) — Fallback frische WS`);
|
||||
}
|
||||
}
|
||||
const proto = RVS_TLS === "true" ? "wss" : "ws";
|
||||
const url = `${proto}://${RVS_HOST}:${RVS_PORT}?token=${RVS_TOKEN}`;
|
||||
const freshWs = new WebSocket(url);
|
||||
freshWs.on("open", () => {
|
||||
freshWs.send(JSON.stringify(msgObj));
|
||||
freshWs.send(payload);
|
||||
setTimeout(() => { try { freshWs.close(); } catch (_) {} }, 5000);
|
||||
});
|
||||
freshWs.on("error", () => {});
|
||||
|
||||
Reference in New Issue
Block a user