feat: ARIA kann Dateien an User zurueckgeben (PDFs, Bilder, Office-Docs, ...)
ARIA setzt im Antworttext einen Marker `[FILE: /shared/uploads/aria_xxx.ext]`. Bridge extrahiert ihn (Marker wird aus dem TTS-Text entfernt) und sendet ein neues file_from_aria-Event ueber RVS an App + Diagnostic. Diagnostic: - Eigene Bubble mit Datei-Icon + Klick-Handler - PDF/Bild → neuer Browser-Tab via /shared/* HTTP-Route - Andere → Download via download-Attribut App: - Neues FileOpenerModule (Kotlin) — Intent.ACTION_VIEW mit FileProvider, Android-Picker waehlt App nach MIME-Type - file_paths.xml erweitert (cache + files + external) - file_response liefert jetzt mimeType mit - Klick auf ARIA-Anhang: lokal vorhanden → direkt oeffnen, sonst file_request mit autoOpen-Flag → bei Empfang persistAttachment + open Stefan muss noch im aria-core/OpenClaw System-Prompt einen Hinweis einbauen: "Wenn du dem User eine Datei erstellt hast (Pfad in /shared/uploads/), haenge am Ende deiner Antwort einmalig [FILE: /shared/uploads/aria_<name>.<ext>] an. Der Marker wird aus dem sichtbaren Text entfernt und als Anhang in App und Diagnostic angezeigt." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,9 @@ import asyncio
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import ssl
|
||||
import sys
|
||||
@@ -882,6 +884,48 @@ class ARIABridge:
|
||||
pass
|
||||
return payload.get("text", "")
|
||||
|
||||
# File-Marker-Pattern: `[FILE: /pfad/zur/datei.ext]` (Pfad kann Spaces
|
||||
# enthalten, Endung beliebig). Mehrfach im Text moeglich.
|
||||
_FILE_MARKER_RE = re.compile(r"\[FILE:\s*(/shared/uploads/[^\]]+?)\s*\]", re.IGNORECASE)
|
||||
|
||||
def _extract_file_markers(self, text: str) -> tuple[str, list[dict]]:
|
||||
"""Sucht [FILE: /shared/uploads/...]-Marker, gibt (cleaned_text, file_list) zurueck."""
|
||||
files: list[dict] = []
|
||||
for m in self._FILE_MARKER_RE.finditer(text):
|
||||
path = m.group(1).strip()
|
||||
if not path.startswith("/shared/uploads/"):
|
||||
logger.warning("[core] FILE-Marker mit unerlaubtem Pfad ignoriert: %s", path)
|
||||
continue
|
||||
if not os.path.isfile(path):
|
||||
logger.warning("[core] FILE-Marker zeigt auf nicht existente Datei: %s", path)
|
||||
continue
|
||||
name = os.path.basename(path)
|
||||
mime, _ = mimetypes.guess_type(path)
|
||||
size = os.path.getsize(path)
|
||||
files.append({
|
||||
"serverPath": path,
|
||||
"name": name,
|
||||
"mimeType": mime or "application/octet-stream",
|
||||
"size": size,
|
||||
})
|
||||
cleaned = self._FILE_MARKER_RE.sub("", text).strip()
|
||||
# Zwei aufeinanderfolgende Leerzeilen → eine
|
||||
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
||||
return cleaned, files
|
||||
|
||||
async def _broadcast_aria_file(self, file_info: dict) -> None:
|
||||
"""ARIA hat eine Datei fuer den User erstellt — App+Diagnostic informieren."""
|
||||
logger.info("[rvs] ARIA-Datei rausgeben: %s (%s, %dKB)",
|
||||
file_info["name"], file_info["mimeType"], file_info["size"] // 1024)
|
||||
try:
|
||||
await self._send_to_rvs({
|
||||
"type": "file_from_aria",
|
||||
"payload": file_info,
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_from_aria broadcast fehlgeschlagen: %s", e)
|
||||
|
||||
async def _process_core_response(self, text: str, payload: dict) -> None:
|
||||
"""Verarbeitet eine fertige Antwort von aria-core.
|
||||
|
||||
@@ -896,6 +940,14 @@ class ARIABridge:
|
||||
logger.info("[core] NO_REPLY empfangen — Antwort still verworfen")
|
||||
return
|
||||
|
||||
# File-Marker `[FILE: /shared/uploads/aria_xyz.pdf]` extrahieren —
|
||||
# ARIA legt damit Dateien fuer den User bereit (Bilder, PDFs, etc.).
|
||||
# Der Marker wird aus dem Antworttext entfernt (TTS soll ihn nicht
|
||||
# vorlesen) und parallel als file_from_aria-Event geschickt.
|
||||
text, aria_files = self._extract_file_markers(text)
|
||||
for f in aria_files:
|
||||
await self._broadcast_aria_file(f)
|
||||
|
||||
metadata = payload.get("metadata", {})
|
||||
is_critical = metadata.get("critical", False)
|
||||
requested_voice = metadata.get("voice")
|
||||
@@ -1545,6 +1597,7 @@ class ARIABridge:
|
||||
return
|
||||
with open(server_path, "rb") as f:
|
||||
file_b64 = base64.b64encode(f.read()).decode("ascii")
|
||||
mime, _ = mimetypes.guess_type(server_path)
|
||||
logger.info("[rvs] Re-Download: %s (%dKB)", server_path, len(file_b64) // 1365)
|
||||
await self._send_to_rvs({
|
||||
"type": "file_response",
|
||||
@@ -1553,6 +1606,7 @@ class ARIABridge:
|
||||
"serverPath": server_path,
|
||||
"base64": file_b64,
|
||||
"name": os.path.basename(server_path),
|
||||
"mimeType": mime or "application/octet-stream",
|
||||
},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user