fix(projects): Anhaenge landen im gewaehlten Projekt statt im Hauptchat
Bug: Bild/Datei ins Textfeld + Frage → beides landete im Hauptchat, egal
welches Projekt fokussiert war. Der Anhang-Pfad reichte die projectId nirgends
durch (im Gegensatz zum reinen Text-Pfad).
App (sendPendingAttachments):
- lokale Anhang-Bubble bekommt projectId (App-Focus)
- file-Upload (rvs.send('file')) schickt projectId mit
- Merge-Trigger chat-Nachricht schickt projectId mit
Bridge:
- file-Handler liest payload.projectId, merkt sie (_pending_files_project_id)
und taggt die Datei per _tag_file_to_project ins richtige Projekt
- _flush_pending_files_with_text(user_text, project_id): reicht die projectId
aus dem chat-Payload an send_to_core (Fallback: gemerkter Upload-Kontext)
- _flush_pending_files_after (Files-only): nutzt den gemerkten Upload-Kontext
- merged-Aufruf im chat-Handler gibt payload.projectId mit
Damit tragen User-Bubble, Datei-Manifest, Brain-Turn und ARIA-Antwort alle
denselben Projekt-Tag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2047,12 +2047,14 @@ const ChatScreen: React.FC = () => {
|
|||||||
// Chat-Nachricht mit allen Anhaengen. clientMsgId nur wenn Text dabei
|
// Chat-Nachricht mit allen Anhaengen. clientMsgId nur wenn Text dabei
|
||||||
// ist — files selber haben (noch) kein ACK-Tracking auf der Bridge.
|
// ist — files selber haben (noch) kein ACK-Tracking auf der Bridge.
|
||||||
const cmid = messageText ? nextClientMsgId() : undefined;
|
const cmid = messageText ? nextClientMsgId() : undefined;
|
||||||
|
const activePid = focusedProjectIdRef.current;
|
||||||
const userMsg: ChatMessage = {
|
const userMsg: ChatMessage = {
|
||||||
id: msgId,
|
id: msgId,
|
||||||
sender: 'user',
|
sender: 'user',
|
||||||
text: messageText || `${pendingAttachments.length} Anhang/Anhaenge`,
|
text: messageText || `${pendingAttachments.length} Anhang/Anhaenge`,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
attachments,
|
attachments,
|
||||||
|
projectId: activePid,
|
||||||
...(cmid && {
|
...(cmid && {
|
||||||
clientMsgId: cmid,
|
clientMsgId: cmid,
|
||||||
deliveryStatus: connectionStateRef.current === 'connected' ? 'sending' : 'queued',
|
deliveryStatus: connectionStateRef.current === 'connected' ? 'sending' : 'queued',
|
||||||
@@ -2086,6 +2088,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
type: mimeType,
|
type: mimeType,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
base64,
|
base64,
|
||||||
|
projectId: activePid,
|
||||||
...(isPhoto && file.width && { width: file.width, height: file.height }),
|
...(isPhoto && file.width && { width: file.width, height: file.height }),
|
||||||
...(location && { location }),
|
...(location && { location }),
|
||||||
});
|
});
|
||||||
@@ -2099,6 +2102,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
text: messageText,
|
text: messageText,
|
||||||
voice: localXttsVoiceRef.current,
|
voice: localXttsVoiceRef.current,
|
||||||
speed: ttsSpeedRef.current,
|
speed: ttsSpeedRef.current,
|
||||||
|
projectId: activePid,
|
||||||
...(location && { location }),
|
...(location && { location }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-12
@@ -658,6 +658,10 @@ class ARIABridge:
|
|||||||
# Liste von Tuples: (file_path, name, file_type, size_kb, width, height)
|
# 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: list[tuple[str, str, str, int, int, int]] = []
|
||||||
self._pending_files_flush_task: Optional[asyncio.Task] = None
|
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
|
self._PENDING_FILES_WINDOW_SEC: float = 0.8
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
@@ -1508,12 +1512,19 @@ class ARIABridge:
|
|||||||
text = self._build_pending_files_message("")
|
text = self._build_pending_files_message("")
|
||||||
self._pending_files = []
|
self._pending_files = []
|
||||||
self._pending_files_flush_task = None
|
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:
|
"""Wenn ein chat-Text reinkommt waehrend Files gepuffert sind:
|
||||||
Files + Text zu einer einzigen aria-core-Nachricht mergen.
|
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:
|
if not self._pending_files:
|
||||||
return False
|
return False
|
||||||
if self._pending_files_flush_task and not self._pending_files_flush_task.done():
|
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
|
self._pending_files_flush_task = None
|
||||||
text = self._build_pending_files_message(user_text)
|
text = self._build_pending_files_message(user_text)
|
||||||
self._pending_files = []
|
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
|
# create_task statt await — sonst blockt der RVS-recv-Loop bis Brain
|
||||||
# fertig ist (siehe chat-handler oben).
|
# 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
|
return True
|
||||||
|
|
||||||
async def send_to_core(self, text: str, source: str = "bridge",
|
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
|
# Wenn Files gerade gepuffert sind (Bild + Text gleichzeitig
|
||||||
# gesendet), mergen wir sie zu einer einzigen Anfrage statt
|
# gesendet), mergen wir sie zu einer einzigen Anfrage statt
|
||||||
# zwei separater send_to_core-Calls.
|
# 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:
|
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:
|
else:
|
||||||
core_text = self._build_core_text(text, interrupted, location)
|
core_text = self._build_core_text(text, interrupted, location)
|
||||||
logger.info("[rvs] App-Chat%s%s: '%s'",
|
logger.info("[rvs] App-Chat%s%s: '%s'",
|
||||||
@@ -2189,8 +2204,14 @@ class ARIABridge:
|
|||||||
file_b64 = payload.get("base64", "")
|
file_b64 = payload.get("base64", "")
|
||||||
width = payload.get("width", 0)
|
width = payload.get("width", 0)
|
||||||
height = payload.get("height", 0)
|
height = payload.get("height", 0)
|
||||||
logger.info("[rvs] Datei empfangen: %s (%s, %dKB)",
|
# Projekt-Kontext des Uploads (sichtbarer App-Focus). Merken, damit
|
||||||
file_name, file_type, len(file_b64) // 1365 if file_b64 else 0)
|
# 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"
|
SHARED_DIR = "/shared/uploads"
|
||||||
os.makedirs(SHARED_DIR, exist_ok=True)
|
os.makedirs(SHARED_DIR, exist_ok=True)
|
||||||
@@ -2198,7 +2219,8 @@ class ARIABridge:
|
|||||||
if not file_b64:
|
if not file_b64:
|
||||||
text = f"Stefan hat eine Datei gesendet ({file_name}, {file_type}) aber die Daten sind leer angekommen."
|
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
|
# 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
|
return
|
||||||
|
|
||||||
if file_type.startswith("image/"):
|
if file_type.startswith("image/"):
|
||||||
@@ -2212,10 +2234,12 @@ class ARIABridge:
|
|||||||
f.write(base64.b64decode(file_b64))
|
f.write(base64.b64decode(file_b64))
|
||||||
size_kb = len(file_b64) // 1365
|
size_kb = len(file_b64) // 1365
|
||||||
logger.info("[rvs] Datei gespeichert: %s (%dKB)", file_path, size_kb)
|
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
|
# Manifest in /shared/config/file_projects.json — File-Manager
|
||||||
# in App + Diagnostic filtert danach.
|
# in App + Diagnostic filtert danach. Leer = Hauptchat.
|
||||||
self._tag_file_to_active_project(file_path)
|
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
|
# Pixel-Bilder fuer Claude-Vision shrinken wenn > 2 MB. SVG/PDF/ZIP
|
||||||
# bleiben unangetastet (Vision laeuft eh nur auf Raster-Formaten).
|
# bleiben unangetastet (Vision laeuft eh nur auf Raster-Formaten).
|
||||||
|
|||||||
Reference in New Issue
Block a user