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:
2026-05-10 17:56:47 +02:00
parent fc3ecaacca
commit 3b19f05c5b
8 changed files with 224 additions and 6 deletions
@@ -7,7 +7,7 @@ import com.facebook.react.uimanager.ViewManager
class ApkInstallerPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(ApkInstallerModule(reactContext))
return listOf(ApkInstallerModule(reactContext), FileOpenerModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
@@ -0,0 +1,54 @@
package com.ariacockpit
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.FileProvider
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import java.io.File
/**
* Oeffnet eine beliebige Datei (PDF, Bild, Office-Doc, ...) mit der vom User
* gewaehlten App via Android-Intent-Picker. Nutzt FileProvider damit auch
* Android 7+ (content:// statt file://) das URI lesen darf.
*
* MIME-Type wird vom Caller bestimmt — App-Auswahl ist davon abhaengig (PDF
* → PDF-Viewer, image/* → Galerie, etc.).
*/
class FileOpenerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "FileOpener"
@ReactMethod
fun open(filePath: String, mimeType: String, promise: Promise) {
try {
val cleanPath = filePath.removePrefix("file://")
val file = File(cleanPath)
if (!file.exists()) {
promise.reject("FILE_NOT_FOUND", "Datei nicht gefunden: $cleanPath")
return
}
val context = reactApplicationContext
val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
} else {
Uri.fromFile(file)
}
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, mimeType.ifBlank { "*/*" })
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
// Chooser zeigt Android-Auswahl falls mehrere Apps das MIME oeffnen koennen.
val chooser = Intent.createChooser(intent, "Oeffnen mit").apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(chooser)
promise.resolve(true)
} catch (e: Exception) {
promise.reject("OPEN_ERROR", e.message, e)
}
}
}
@@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="." />
<files-path name="files" path="." />
<external-path name="external" path="." />
<external-files-path name="external_files" path="." />
<external-cache-path name="external_cache" path="." />
</paths>