diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index dd3baed..f7a891d 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -2047,12 +2047,14 @@ const ChatScreen: React.FC = () => { // Chat-Nachricht mit allen Anhaengen. clientMsgId nur wenn Text dabei // ist — files selber haben (noch) kein ACK-Tracking auf der Bridge. const cmid = messageText ? nextClientMsgId() : undefined; + const activePid = focusedProjectIdRef.current; const userMsg: ChatMessage = { id: msgId, sender: 'user', text: messageText || `${pendingAttachments.length} Anhang/Anhaenge`, timestamp: Date.now(), attachments, + projectId: activePid, ...(cmid && { clientMsgId: cmid, deliveryStatus: connectionStateRef.current === 'connected' ? 'sending' : 'queued', @@ -2086,6 +2088,7 @@ const ChatScreen: React.FC = () => { type: mimeType, size: file.size, base64, + projectId: activePid, ...(isPhoto && file.width && { width: file.width, height: file.height }), ...(location && { location }), }); @@ -2099,6 +2102,7 @@ const ChatScreen: React.FC = () => { text: messageText, voice: localXttsVoiceRef.current, speed: ttsSpeedRef.current, + projectId: activePid, ...(location && { location }), }); } diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index 2f39744..37b7b5f 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -658,6 +658,10 @@ class ARIABridge: # Liste von Tuples: (file_path, name, file_type, size_kb, width, height) self._pending_files: list[tuple[str, str, str, int, int, int]] = [] self._pending_files_flush_task: Optional[asyncio.Task] = None + # Projekt-Kontext der gerade gepufferten Anhaenge (aus dem file-Upload). + # Wird beim Flush an send_to_core gegeben, damit Anhaenge im richtigen + # Projekt landen statt im Hauptchat. + self._pending_files_project_id: str = "" self._PENDING_FILES_WINDOW_SEC: float = 0.8 def initialize(self) -> None: @@ -1508,12 +1512,19 @@ class ARIABridge: text = self._build_pending_files_message("") self._pending_files = [] self._pending_files_flush_task = None - await self.send_to_core(text, source="app-file") + pid = self._pending_files_project_id + self._pending_files_project_id = "" + await self.send_to_core(text, source="app-file", project_id=pid) - async def _flush_pending_files_with_text(self, user_text: str) -> bool: + async def _flush_pending_files_with_text(self, user_text: str, + project_id: str = "") -> bool: """Wenn ein chat-Text reinkommt waehrend Files gepuffert sind: Files + Text zu einer einzigen aria-core-Nachricht mergen. - Returns True wenn gemerged wurde (Caller soll dann nicht nochmal senden).""" + Returns True wenn gemerged wurde (Caller soll dann nicht nochmal senden). + + project_id: Projekt-Kontext aus dem chat-Payload (der sichtbare Focus + beim Absenden). Faellt auf den beim File-Upload gemerkten Kontext + zurueck, damit Anhaenge im richtigen Projekt landen statt im Hauptchat.""" if not self._pending_files: return False if self._pending_files_flush_task and not self._pending_files_flush_task.done(): @@ -1521,9 +1532,11 @@ class ARIABridge: self._pending_files_flush_task = None text = self._build_pending_files_message(user_text) self._pending_files = [] + pid = (project_id or "").strip() or self._pending_files_project_id + self._pending_files_project_id = "" # create_task statt await — sonst blockt der RVS-recv-Loop bis Brain # fertig ist (siehe chat-handler oben). - asyncio.create_task(self.send_to_core(text, source="app-file+chat")) + asyncio.create_task(self.send_to_core(text, source="app-file+chat", project_id=pid)) return True async def send_to_core(self, text: str, source: str = "bridge", @@ -1965,9 +1978,11 @@ class ARIABridge: # Wenn Files gerade gepuffert sind (Bild + Text gleichzeitig # gesendet), mergen wir sie zu einer einzigen Anfrage statt # zwei separater send_to_core-Calls. - merged = await self._flush_pending_files_with_text(text) + merged = await self._flush_pending_files_with_text( + text, project_id=str(payload.get("projectId") or "")) if merged: - logger.info("[rvs] App-Chat (mit Anhaengen): '%s'", text[:80]) + logger.info("[rvs] App-Chat (mit Anhaengen) project=%s: '%s'", + str(payload.get("projectId") or "") or "(main)", text[:80]) else: core_text = self._build_core_text(text, interrupted, location) logger.info("[rvs] App-Chat%s%s: '%s'", @@ -2189,8 +2204,14 @@ class ARIABridge: file_b64 = payload.get("base64", "") width = payload.get("width", 0) height = payload.get("height", 0) - logger.info("[rvs] Datei empfangen: %s (%s, %dKB)", - file_name, file_type, len(file_b64) // 1365 if file_b64 else 0) + # Projekt-Kontext des Uploads (sichtbarer App-Focus). Merken, damit + # der spaetere Flush (Files+Text oder Files-only) die Anfrage im + # richtigen Projekt an das Brain schickt statt im Hauptchat. + file_project_id = str(payload.get("projectId") or "") + self._pending_files_project_id = file_project_id + logger.info("[rvs] Datei empfangen: %s (%s, %dKB) project=%s", + file_name, file_type, len(file_b64) // 1365 if file_b64 else 0, + file_project_id or "(main)") SHARED_DIR = "/shared/uploads" os.makedirs(SHARED_DIR, exist_ok=True) @@ -2198,7 +2219,8 @@ class ARIABridge: if not file_b64: text = f"Stefan hat eine Datei gesendet ({file_name}, {file_type}) aber die Daten sind leer angekommen." # create_task statt await — RVS-recv darf nicht blocken - asyncio.create_task(self.send_to_core(text, source="app-file")) + asyncio.create_task(self.send_to_core(text, source="app-file", + project_id=file_project_id)) return if file_type.startswith("image/"): @@ -2212,10 +2234,12 @@ class ARIABridge: f.write(base64.b64decode(file_b64)) size_kb = len(file_b64) // 1365 logger.info("[rvs] Datei gespeichert: %s (%dKB)", file_path, size_kb) - # Datei dem aktuellen Projekt zuordnen (falls Stefan in einem ist). + # Datei dem Projekt des Uploads zuordnen (Multi-Threading: explizit + # aus dem file-Payload, kein globaler active_project-State mehr). # Manifest in /shared/config/file_projects.json — File-Manager - # in App + Diagnostic filtert danach. - self._tag_file_to_active_project(file_path) + # in App + Diagnostic filtert danach. Leer = Hauptchat. + if file_project_id: + self._tag_file_to_project(file_path, file_project_id) # Pixel-Bilder fuer Claude-Vision shrinken wenn > 2 MB. SVG/PDF/ZIP # bleiben unangetastet (Vision laeuft eh nur auf Raster-Formaten).