added shared volume to diagnostic, added folder picker to android app, fixed bridge for attachment uploading, fixed hopefully chat history in android app
This commit is contained in:
parent
94691f12ab
commit
457b469c96
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
|||
#Sun Mar 29 12:50:35 CEST 2026
|
||||
#Sun Mar 29 13:02:03 CEST 2026
|
||||
base.2=/home/duffy/Dokumente/programmierung/ARIA-AGENT/android/android/app/build/intermediates/dex/release/mergeDexRelease/classes2.dex
|
||||
path.2=classes2.dex
|
||||
base.1=/home/duffy/Dokumente/programmierung/ARIA-AGENT/android/android/app/build/intermediates/global_synthetics_dex/release/classes.dex
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -165,21 +165,14 @@ const ChatScreen: React.FC = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
// STT-Ergebnis: Spracheingabe-Placeholder mit transkribiertem Text ersetzen
|
||||
// STT-Ergebnis: Transkribierten Text unter den Placeholder schreiben
|
||||
if (message.type === 'stt_result') {
|
||||
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: sttText }
|
||||
: m
|
||||
));
|
||||
} else {
|
||||
// Keine Sprache erkannt — Placeholder entfernen
|
||||
setMessages(prev => prev.filter(m =>
|
||||
!(m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet'))
|
||||
));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -282,20 +275,34 @@ const ChatScreen: React.FC = () => {
|
|||
}
|
||||
}, [wakeWordActive]);
|
||||
|
||||
// Chat-Verlauf in AsyncStorage speichern (letzte N Nachrichten)
|
||||
// Chat-Verlauf in AsyncStorage speichern (debounced, nur nach initialem Laden)
|
||||
const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
useEffect(() => {
|
||||
if (messages.length === 0 || isInitialLoad.current) return;
|
||||
// Nur file:// URIs speichern, data: URIs rausfiltern (zu gross)
|
||||
const toStore = messages.slice(-MAX_STORED_MESSAGES).map(msg => ({
|
||||
...msg,
|
||||
attachments: msg.attachments?.map(att => ({
|
||||
...att,
|
||||
uri: att.uri?.startsWith('file://') ? att.uri : undefined,
|
||||
})),
|
||||
}));
|
||||
AsyncStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(toStore)).catch(err =>
|
||||
console.error('[Chat] Fehler beim Speichern:', err),
|
||||
);
|
||||
// Debounce: 1s warten damit persistAttachment fertig werden kann
|
||||
if (saveTimer.current) clearTimeout(saveTimer.current);
|
||||
saveTimer.current = setTimeout(() => {
|
||||
const toStore = messages.slice(-MAX_STORED_MESSAGES).map(msg => ({
|
||||
...msg,
|
||||
attachments: msg.attachments?.map(att => ({
|
||||
...att,
|
||||
// Nur file:// URIs speichern, data: URIs rausfiltern (zu gross fuer AsyncStorage)
|
||||
uri: att.uri?.startsWith('file://') ? att.uri : undefined,
|
||||
})),
|
||||
}));
|
||||
const json = JSON.stringify(toStore);
|
||||
// Sicherheitscheck: nicht speichern wenn >4MB (AsyncStorage Limit)
|
||||
if (json.length > 4 * 1024 * 1024) {
|
||||
console.warn('[Chat] Speicher zu gross, kuerze auf 100 Nachrichten');
|
||||
const shortened = JSON.stringify(toStore.slice(-100));
|
||||
AsyncStorage.setItem(CHAT_STORAGE_KEY, shortened).catch(() => {});
|
||||
} else {
|
||||
AsyncStorage.setItem(CHAT_STORAGE_KEY, json).catch(err =>
|
||||
console.error('[Chat] Speichern fehlgeschlagen:', err),
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
return () => { if (saveTimer.current) clearTimeout(saveTimer.current); };
|
||||
}, [messages]);
|
||||
|
||||
// Auto-Scroll wird ueber onContentSizeChange der FlatList gesteuert
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import RNFS from 'react-native-fs';
|
||||
import DocumentPicker from 'react-native-document-picker';
|
||||
import rvs, { ConnectionState, RVSMessage, ConnectionConfig, ConnectionLogEntry } from '../services/rvs';
|
||||
import ModeSelector from '../components/ModeSelector';
|
||||
import QRScanner from '../components/QRScanner';
|
||||
|
|
@ -115,28 +116,39 @@ const SettingsScreen: React.FC = () => {
|
|||
Alert.alert('Gespeichert', `Neuer Speicherort:\n${clean}\n\nWird ab der naechsten Nachricht verwendet.`);
|
||||
}, []);
|
||||
|
||||
const storagePaths = [
|
||||
{ label: 'App-intern (Standard)', path: DEFAULT_STORAGE_PATH },
|
||||
{ label: 'Externer Speicher', path: '/storage/emulated/0/ARIA/attachments' },
|
||||
{ label: 'SD-Karte', path: '/storage/sdcard1/ARIA/attachments' },
|
||||
{ label: 'Downloads', path: '/storage/emulated/0/Download/ARIA' },
|
||||
];
|
||||
|
||||
const showPathPicker = useCallback(() => {
|
||||
const options = storagePaths.map(p => p.label);
|
||||
options.push('Eigenen Pfad eingeben');
|
||||
options.push('Abbrechen');
|
||||
|
||||
Alert.alert(
|
||||
'Speicherort waehlen',
|
||||
'Wo sollen Anhaenge gespeichert werden?',
|
||||
[
|
||||
...storagePaths.map(p => ({
|
||||
text: p.label,
|
||||
onPress: () => saveStoragePath(p.path),
|
||||
})),
|
||||
{
|
||||
text: 'Eigenen Pfad eingeben',
|
||||
text: 'Ordner auswaehlen...',
|
||||
onPress: async () => {
|
||||
try {
|
||||
const result = await DocumentPicker.pickDirectory();
|
||||
if (result?.uri) {
|
||||
// SAF URI decodieren (content://com.android.externalstorage...)
|
||||
const decoded = decodeURIComponent(result.uri);
|
||||
// Versuche einen lesbaren Pfad zu extrahieren
|
||||
const match = decoded.match(/primary[:%]3A(.+)/);
|
||||
const readablePath = match
|
||||
? `/storage/emulated/0/${match[1].replace(/%2F|%3A/g, '/')}`
|
||||
: decoded;
|
||||
saveStoragePath(readablePath);
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (!DocumentPicker.isCancel(e)) {
|
||||
Alert.alert('Fehler', 'Ordnerauswahl fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'App-intern (Standard)',
|
||||
onPress: () => saveStoragePath(DEFAULT_STORAGE_PATH),
|
||||
},
|
||||
{
|
||||
text: 'Pfad manuell eingeben',
|
||||
onPress: () => { setTempPath(storagePath); setEditingPath(true); },
|
||||
},
|
||||
{ text: 'Abbrechen', style: 'cancel' as const },
|
||||
|
|
|
|||
|
|
@ -977,17 +977,21 @@ class ARIABridge:
|
|||
f.write(base64.b64decode(file_b64))
|
||||
size_kb = len(file_b64) // 1365
|
||||
logger.info("[rvs] Bild gespeichert: %s (%dKB)", file_path, size_kb)
|
||||
# App informieren wo die Datei liegt (fuer Re-Download)
|
||||
await self._send_to_rvs({
|
||||
"type": "file_saved",
|
||||
"payload": {"name": file_name, "serverPath": file_path, "mimeType": file_type},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
# ERST an aria-core senden (wichtigster Schritt)
|
||||
text = (f"Stefan hat dir ein Bild geschickt: {file_name}"
|
||||
f"{f' ({width}x{height}px)' if width else ''}"
|
||||
f", {size_kb}KB."
|
||||
f" Das Bild liegt unter: {file_path}")
|
||||
await self.send_to_core(text, source="app-file")
|
||||
# Dann App informieren (optional, darf nicht crashen)
|
||||
try:
|
||||
await self._send_to_rvs({
|
||||
"type": "file_saved",
|
||||
"payload": {"name": file_name, "serverPath": file_path, "mimeType": file_type},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_saved konnte nicht an App gesendet werden: %s", e)
|
||||
elif file_b64:
|
||||
# Andere Datei in Shared Volume speichern
|
||||
safe_name = f"file_{int(asyncio.get_event_loop().time())}_{file_name.replace('/', '_')}"
|
||||
|
|
@ -996,15 +1000,19 @@ 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)
|
||||
await self._send_to_rvs({
|
||||
"type": "file_saved",
|
||||
"payload": {"name": file_name, "serverPath": file_path, "mimeType": file_type},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
# ERST an aria-core senden
|
||||
text = (f"Stefan hat dir eine Datei geschickt: {file_name}"
|
||||
f" ({file_type}, {size_kb}KB)."
|
||||
f" Die Datei liegt unter: {file_path}")
|
||||
await self.send_to_core(text, source="app-file")
|
||||
try:
|
||||
await self._send_to_rvs({
|
||||
"type": "file_saved",
|
||||
"payload": {"name": file_name, "serverPath": file_path, "mimeType": file_type},
|
||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||
})
|
||||
except Exception as e:
|
||||
logger.warning("[rvs] file_saved konnte nicht an App gesendet werden: %s", e)
|
||||
else:
|
||||
text = f"Stefan hat eine Datei gesendet ({file_name}, {file_type}) aber die Daten sind leer angekommen."
|
||||
await self.send_to_core(text, source="app-file")
|
||||
|
|
|
|||
|
|
@ -859,7 +859,11 @@
|
|||
const el = document.createElement('div');
|
||||
el.className = `chat-msg ${type}`;
|
||||
const escaped = escapeHtml(text);
|
||||
const linked = linkifyText(escaped);
|
||||
let linked = linkifyText(escaped);
|
||||
// /shared/uploads/ Pfade als Inline-Bilder anzeigen
|
||||
linked = linked.replace(/\/shared\/uploads\/[^\s<"]+\.(jpg|jpeg|png|gif)/gi, (match) => {
|
||||
return `<a href="${match}" target="_blank">${match}</a><img src="${match}" class="chat-media" onclick="openLightbox('image','${match}')" onerror="this.style.display='none'">`;
|
||||
});
|
||||
el.innerHTML = `${linked}<div class="meta">${escapeHtml(meta)} — ${new Date().toLocaleTimeString('de-DE')}</div>`;
|
||||
chatBox.appendChild(el);
|
||||
chatBox.scrollTop = chatBox.scrollHeight;
|
||||
|
|
|
|||
|
|
@ -462,6 +462,11 @@ 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 {
|
||||
|
|
@ -945,6 +950,28 @@ const server = http.createServer((req, res) => {
|
|||
} else if (req.url === "/api/session") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ sessionKey: activeSessionKey }));
|
||||
} else if (req.url.startsWith("/shared/")) {
|
||||
// Dateien aus Shared Volume ausliefern (Bilder, Uploads)
|
||||
const filePath = decodeURIComponent(req.url);
|
||||
const safePath = path.resolve(filePath);
|
||||
if (!safePath.startsWith("/shared/")) {
|
||||
res.writeHead(403);
|
||||
res.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!fs.existsSync(safePath)) { res.writeHead(404); res.end("Not Found"); return; }
|
||||
const ext = path.extname(safePath).toLowerCase();
|
||||
const mimeTypes = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".gif": "image/gif",
|
||||
".pdf": "application/pdf", ".txt": "text/plain", ".json": "application/json" };
|
||||
const contentType = mimeTypes[ext] || "application/octet-stream";
|
||||
const data = fs.readFileSync(safePath);
|
||||
res.writeHead(200, { "Content-Type": contentType, "Content-Length": data.length });
|
||||
res.end(data);
|
||||
} catch (err) {
|
||||
res.writeHead(500);
|
||||
res.end("Error");
|
||||
}
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end("Not Found");
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ services:
|
|||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./aria-data/config/diag-state:/data # Persistenter State (aktive Session etc.)
|
||||
- aria-shared:/shared:ro # Shared Volume (Uploads lesen fuer Vorschau)
|
||||
environment:
|
||||
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
|
||||
- PROXY_URL=http://proxy:3456
|
||||
|
|
|
|||
Loading…
Reference in New Issue