diff --git a/android/android/.gradle/8.3/executionHistory/executionHistory.lock b/android/android/.gradle/8.3/executionHistory/executionHistory.lock index 44eabbb..4c1cb55 100644 Binary files a/android/android/.gradle/8.3/executionHistory/executionHistory.lock and b/android/android/.gradle/8.3/executionHistory/executionHistory.lock differ diff --git a/android/android/.gradle/8.3/fileHashes/fileHashes.bin b/android/android/.gradle/8.3/fileHashes/fileHashes.bin index 2e5a738..266ca56 100644 Binary files a/android/android/.gradle/8.3/fileHashes/fileHashes.bin and b/android/android/.gradle/8.3/fileHashes/fileHashes.bin differ diff --git a/android/android/.gradle/8.3/fileHashes/fileHashes.lock b/android/android/.gradle/8.3/fileHashes/fileHashes.lock index 786be81..dbd7b40 100644 Binary files a/android/android/.gradle/8.3/fileHashes/fileHashes.lock and b/android/android/.gradle/8.3/fileHashes/fileHashes.lock differ diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index cd517d5..842e2ea 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -125,7 +125,30 @@ const ChatScreen: React.FC = () => { isInitialLoad.current = false; } }; - loadMessages(); + loadMessages().then(async () => { + // Auto-Re-Download: fehlende Anhänge vom Server nachladen (wenn aktiviert) + const autoDownload = await AsyncStorage.getItem('aria_auto_download'); + if (autoDownload === 'false') return; + setTimeout(() => { + setMessages(prev => { + const missing: {id: string, serverPath: string}[] = []; + for (const msg of prev) { + for (const att of msg.attachments || []) { + if (att.serverPath && !att.uri) { + missing.push({ id: msg.id, serverPath: att.serverPath }); + } + } + } + if (missing.length > 0) { + console.log(`[Chat] ${missing.length} fehlende Anhaenge — lade nach...`); + for (const m of missing) { + rvs.send('file_request' as any, { serverPath: m.serverPath, requestId: m.id }); + } + } + return prev; + }); + }, 2000); // Warten bis RVS verbunden ist + }); }, []); // RVS-Nachrichten abonnieren @@ -165,20 +188,23 @@ const ChatScreen: React.FC = () => { return; } - // STT-Ergebnis: Transkribierten Text unter den Placeholder schreiben - if (message.type === 'stt_result') { - const sttText = (message.payload.text as string) || ''; - setMessages(prev => prev.map(m => - m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet') - ? { ...m, text: sttText ? `\uD83C\uDFA4 ${sttText}` : '\uD83C\uDFA4 (nicht erkannt)' } - : m - )); - return; - } - if (message.type === 'chat') { - // Nur Nachrichten von ARIA anzeigen — eigene Nachrichten werden lokal hinzugefuegt const sender = (message.payload.sender as string) || ''; + + // STT-Ergebnis: Transkribierten Text in die Sprach-Bubble schreiben + if (sender === 'stt') { + const sttText = (message.payload.text as string) || ''; + if (sttText) { + setMessages(prev => prev.map(m => + m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet') + ? { ...m, text: `\uD83C\uDFA4 ${sttText}` } + : m + )); + } + return; + } + + // Eigene Nachrichten ignorieren (werden lokal hinzugefuegt) if (sender === 'user' || sender === 'diagnostic') return; const text = (message.payload.text as string) || ''; diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx index 517c224..eeba70f 100644 --- a/android/src/screens/SettingsScreen.tsx +++ b/android/src/screens/SettingsScreen.tsx @@ -69,6 +69,7 @@ const SettingsScreen: React.FC = () => { const [events, setEvents] = useState([]); const [connLog, setConnLog] = useState(rvs.getConnectionLog()); const [storagePath, setStoragePath] = useState(DEFAULT_STORAGE_PATH); + const [autoDownload, setAutoDownload] = useState(true); const [storageSize, setStorageSize] = useState('...'); const [editingPath, setEditingPath] = useState(false); const [tempPath, setTempPath] = useState(''); @@ -83,10 +84,13 @@ const SettingsScreen: React.FC = () => { setManualPort(String(config.port)); setManualToken(config.token); } - // Speicherpfad laden + // Speicherpfad + Auto-Download laden AsyncStorage.getItem(STORAGE_PATH_KEY).then(saved => { if (saved) setStoragePath(saved); }); + AsyncStorage.getItem('aria_auto_download').then(saved => { + if (saved !== null) setAutoDownload(saved === 'true'); + }); }, []); // Speichergroesse berechnen @@ -441,10 +445,29 @@ const SettingsScreen: React.FC = () => { {/* === Speicher === */} Anhang-Speicher + + + Auto-Download + + Fehlende Anhaenge beim App-Start automatisch vom Server laden + + + { + setAutoDownload(val); + AsyncStorage.setItem('aria_auto_download', String(val)); + }} + trackColor={{ false: '#2A2A3E', true: '#0096FF' }} + thumbColor={autoDownload ? '#FFFFFF' : '#666680'} + /> + + + Lokaler Speicherort Hier werden Bilder und Dateien aus dem Chat gespeichert. - Bei geloeschtem Cache werden Anhaenge automatisch ueber RVS neu geladen. + {autoDownload ? ' Fehlende Dateien werden automatisch nachgeladen.' : ' Fehlende Dateien koennen per Tippen geladen werden.'} {editingPath ? ( diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index 613f92b..26418a0 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -925,7 +925,10 @@ class ARIABridge: payload = message.get("payload", {}) if msg_type == "chat": - # Text von der App → an aria-core + # Nur User-Nachrichten weiterleiten — ARIA/Diagnostic-Antworten ignorieren (sonst Loop!) + sender = payload.get("sender", "") + if sender in ("aria", "diagnostic", "stt"): + return text = payload.get("text", "") if text: logger.info("[rvs] App-Chat: '%s'", text[:80]) @@ -1104,34 +1107,23 @@ class ARIABridge: if text.strip(): logger.info("[rvs] STT Ergebnis: '%s'", text[:80]) - # STT-Ergebnis zurueck an die App senden (zur Anzeige, nicht nochmal verarbeiten) + # ERST an aria-core senden (wichtigster Schritt) + await self.send_to_core(text, source="app-voice") + # STT-Text an RVS senden (fuer Anzeige in App + Diagnostic) + # sender="stt" damit Bridge es ignoriert (kein Loop) try: await self._send_to_rvs({ - "type": "stt_result", + "type": "chat", "payload": { "text": text, - "sender": "user", + "sender": "stt", }, "timestamp": int(asyncio.get_event_loop().time() * 1000), }) except Exception as e: - logger.warning("[rvs] STT-Ergebnis konnte nicht an App gesendet werden: %s", e) - # Text trotzdem an aria-core senden - await self.send_to_core(text, source="app-voice") + logger.warning("[rvs] STT-Text konnte nicht an RVS gesendet werden: %s", e) else: logger.info("[rvs] Keine Sprache erkannt — ignoriert") - try: - await self._send_to_rvs({ - "type": "stt_result", - "payload": { - "text": "", - "error": "Keine Sprache erkannt", - "sender": "user", - }, - "timestamp": int(asyncio.get_event_loop().time() * 1000), - }) - except Exception as e: - logger.warning("[rvs] STT-Fehler konnte nicht an App gesendet werden: %s", e) except Exception: logger.exception("[rvs] Audio-Verarbeitung fehlgeschlagen") diff --git a/diagnostic/index.html b/diagnostic/index.html index f5bd5ed..52d76c7 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -196,7 +196,10 @@
-

Chat Test

+
+

Chat Test

+ +
@@ -206,6 +209,20 @@
+ + +
@@ -503,7 +520,8 @@ const p = msg.msg.payload || {}; const sender = p.sender || '?'; const chatType = (sender === 'aria') ? 'received' : 'sent'; - addChat(chatType, p.text || '?', `via RVS (${sender})`); + const label = sender === 'stt' ? '\uD83C\uDFA4 Spracheingabe' : `via RVS (${sender})`; + addChat(chatType, p.text || '?', label); return; } if (msg.type === 'proxy_result') { @@ -856,19 +874,60 @@ } function addChat(type, text, meta) { - const el = document.createElement('div'); - el.className = `chat-msg ${type}`; const escaped = escapeHtml(text); let linked = linkifyText(escaped); // /shared/uploads/ Pfade als Inline-Bilder anzeigen linked = linked.replace(/\/shared\/uploads\/[^\s<"]+\.(jpg|jpeg|png|gif)/gi, (match) => { return `${match}`; }); - el.innerHTML = `${linked}
${escapeHtml(meta)} — ${new Date().toLocaleTimeString('de-DE')}
`; - chatBox.appendChild(el); - chatBox.scrollTop = chatBox.scrollHeight; + const html = `${linked}
${escapeHtml(meta)} — ${new Date().toLocaleTimeString('de-DE')}
`; + // In beide Chat-Boxen schreiben (normal + Vollbild) + for (const box of [chatBox, document.getElementById('chat-box-fs')]) { + if (!box) continue; + const el = document.createElement('div'); + el.className = `chat-msg ${type}`; + el.innerHTML = html; + box.appendChild(el); + box.scrollTop = box.scrollHeight; + } } + let chatFullscreen = false; + function toggleChatFullscreen() { + const modal = document.getElementById('chat-fullscreen'); + chatFullscreen = !chatFullscreen; + if (chatFullscreen) { + modal.style.display = 'flex'; + // Chat-Inhalt synchronisieren + const fsBox = document.getElementById('chat-box-fs'); + fsBox.innerHTML = chatBox.innerHTML; + fsBox.scrollTop = fsBox.scrollHeight; + document.getElementById('chat-input-fs').focus(); + } else { + modal.style.display = 'none'; + } + } + function testGatewayFS() { + const input = document.getElementById('chat-input-fs'); + const text = input.value.trim(); + if (!text) return; + addChat('sent', text, 'Gateway direkt'); + send({ action: 'test_gateway', text }); + input.value = ''; + } + function testRVSFS() { + const input = document.getElementById('chat-input-fs'); + const text = input.value.trim(); + if (!text) return; + addChat('sent', text, 'via RVS'); + send({ action: 'test_rvs', text }); + input.value = ''; + } + // Escape schliesst Vollbild-Chat + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && chatFullscreen) toggleChatFullscreen(); + }); + function openLightbox(mediaType, url) { const lb = document.getElementById('lightbox'); if (mediaType === 'video') { diff --git a/diagnostic/server.js b/diagnostic/server.js index a16774f..35c9296 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -462,11 +462,6 @@ function connectRVS(forcePlain) { pipelineEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`); } broadcast({ type: "rvs_chat", msg }); - } else if (msg.type === "stt_result" && msg.payload) { - const text = msg.payload.text || "(nicht erkannt)"; - log("info", "rvs", `STT: "${text.slice(0, 100)}"`); - // Im Chat als User-Nachricht anzeigen (zur Info, wurde schon an ARIA gesendet) - broadcast({ type: "rvs_chat", msg: { type: "chat", payload: { text: `\uD83C\uDFA4 ${text}`, sender: "user" } } }); } else if (msg.type === "heartbeat") { // ignorieren } else { diff --git a/release.sh b/release.sh index dbf7811..68c2f62 100755 --- a/release.sh +++ b/release.sh @@ -51,8 +51,30 @@ fi echo -e " ${GREEN}✓${NC} Login erfolgreich" echo "" +# ── Versionsnummern aktualisieren ───────────── +echo -e "${GREEN}[1/5] Versionsnummern auf $VERSION setzen...${NC}" + +# package.json +sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" android/package.json +echo -e " ${GREEN}✓${NC} package.json → $VERSION" + +# build.gradle: versionName + versionCode (aus Major.Minor.Patch berechnen) +MAJOR=$(echo "$VERSION" | cut -d. -f1) +MINOR=$(echo "$VERSION" | cut -d. -f2) +PATCH=$(echo "$VERSION" | cut -d. -f3) +VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH)) +sed -i "s/versionName \"[^\"]*\"/versionName \"$VERSION\"/" android/android/app/build.gradle +sed -i "s/versionCode [0-9]*/versionCode $VERSION_CODE/" android/android/app/build.gradle +echo -e " ${GREEN}✓${NC} build.gradle → versionName $VERSION, versionCode $VERSION_CODE" + +# SettingsScreen: Anzeige-Version +sed -i "s/Version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]* [^<]*/Version $VERSION /" android/src/screens/SettingsScreen.tsx +echo -e " ${GREEN}✓${NC} SettingsScreen → Version $VERSION" + +echo "" + # ── APK bauen ───────────────────────────────── -echo -e "${GREEN}[1/4] APK bauen...${NC}" +echo -e "${GREEN}[2/5] APK bauen...${NC}" cd android ./build.sh release cd .. @@ -70,7 +92,11 @@ echo -e " ${GREEN}✓${NC} APK gebaut ($APK_SIZE)" echo "" # ── Git Tag ─────────────────────────────────── -echo -e "${GREEN}[2/4] Git Tag $TAG...${NC}" +echo -e "${GREEN}[3/5] Git Tag $TAG...${NC}" + +# Versions-Aenderungen committen +git add android/package.json android/android/app/build.gradle android/src/screens/SettingsScreen.tsx +git commit -m "release: bump version to $VERSION" 2>/dev/null || echo -e " ${YELLOW}Keine Aenderungen zum Committen${NC}" if git rev-parse "$TAG" &>/dev/null; then echo -e " ${YELLOW}Tag $TAG existiert bereits — überspringe${NC}" @@ -79,7 +105,7 @@ else echo -e " ${GREEN}✓${NC} Tag $TAG erstellt" fi -git push origin "$TAG" +git push origin main "$TAG" echo -e " ${GREEN}✓${NC} Tag gepusht" echo "" @@ -102,7 +128,7 @@ fi RELEASE_BODY_ESCAPED=$(printf '%s' "$RELEASE_BODY" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))' 2>/dev/null || printf '"%s"' "$RELEASE_BODY" | sed 's/"/\\"/g') # ── Gitea Release erstellen ─────────────────── -echo -e "${GREEN}[3/4] Gitea Release erstellen...${NC}" +echo -e "${GREEN}[4/5] Gitea Release erstellen...${NC}" RELEASE_RESPONSE=$(curl -s -X POST \ "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases" \ @@ -127,7 +153,7 @@ echo -e " ${GREEN}✓${NC} Release #$RELEASE_ID erstellt" echo "" # ── APK hochladen ───────────────────────────── -echo -e "${GREEN}[4/4] APK hochladen...${NC}" +echo -e "${GREEN}[5/5] APK hochladen...${NC}" UPLOAD_RESPONSE=$(curl -s -X POST \ "$GITEA_URL/api/v1/repos/$GITEA_REPO/releases/$RELEASE_ID/assets?name=$APK_NAME" \ diff --git a/rvs/server.js b/rvs/server.js index fef876b..a303a59 100644 --- a/rvs/server.js +++ b/rvs/server.js @@ -9,6 +9,7 @@ const MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS || "10", 10); // Erlaubte Nachrichtentypen — alles andere wird verworfen const ALLOWED_TYPES = new Set([ "chat", "audio", "file", "location", "mode", "log", "event", "heartbeat", + "file_request", "file_response", "file_saved", "stt_result", ]); // Token-Raum: token -> { clients: Set }