Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 457b469c96 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -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
|
base.2=/home/duffy/Dokumente/programmierung/ARIA-AGENT/android/android/app/build/intermediates/dex/release/mergeDexRelease/classes2.dex
|
||||||
path.2=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
|
base.1=/home/duffy/Dokumente/programmierung/ARIA-AGENT/android/android/app/build/intermediates/global_synthetics_dex/release/classes.dex
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -165,21 +165,14 @@ const ChatScreen: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// STT-Ergebnis: Spracheingabe-Placeholder mit transkribiertem Text ersetzen
|
// STT-Ergebnis: Transkribierten Text unter den Placeholder schreiben
|
||||||
if (message.type === 'stt_result') {
|
if (message.type === 'stt_result') {
|
||||||
const sttText = (message.payload.text as string) || '';
|
const sttText = (message.payload.text as string) || '';
|
||||||
if (sttText) {
|
setMessages(prev => prev.map(m =>
|
||||||
setMessages(prev => prev.map(m =>
|
m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')
|
||||||
m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')
|
? { ...m, text: sttText ? `\uD83C\uDFA4 ${sttText}` : '\uD83C\uDFA4 (nicht erkannt)' }
|
||||||
? { ...m, text: sttText }
|
: m
|
||||||
: m
|
));
|
||||||
));
|
|
||||||
} else {
|
|
||||||
// Keine Sprache erkannt — Placeholder entfernen
|
|
||||||
setMessages(prev => prev.filter(m =>
|
|
||||||
!(m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet'))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,20 +275,34 @@ const ChatScreen: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [wakeWordActive]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (messages.length === 0 || isInitialLoad.current) return;
|
if (messages.length === 0 || isInitialLoad.current) return;
|
||||||
// Nur file:// URIs speichern, data: URIs rausfiltern (zu gross)
|
// Debounce: 1s warten damit persistAttachment fertig werden kann
|
||||||
const toStore = messages.slice(-MAX_STORED_MESSAGES).map(msg => ({
|
if (saveTimer.current) clearTimeout(saveTimer.current);
|
||||||
...msg,
|
saveTimer.current = setTimeout(() => {
|
||||||
attachments: msg.attachments?.map(att => ({
|
const toStore = messages.slice(-MAX_STORED_MESSAGES).map(msg => ({
|
||||||
...att,
|
...msg,
|
||||||
uri: att.uri?.startsWith('file://') ? att.uri : undefined,
|
attachments: msg.attachments?.map(att => ({
|
||||||
})),
|
...att,
|
||||||
}));
|
// Nur file:// URIs speichern, data: URIs rausfiltern (zu gross fuer AsyncStorage)
|
||||||
AsyncStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(toStore)).catch(err =>
|
uri: att.uri?.startsWith('file://') ? att.uri : undefined,
|
||||||
console.error('[Chat] Fehler beim Speichern:', err),
|
})),
|
||||||
);
|
}));
|
||||||
|
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]);
|
}, [messages]);
|
||||||
|
|
||||||
// Auto-Scroll wird ueber onContentSizeChange der FlatList gesteuert
|
// Auto-Scroll wird ueber onContentSizeChange der FlatList gesteuert
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
import rvs, { ConnectionState, RVSMessage, ConnectionConfig, ConnectionLogEntry } from '../services/rvs';
|
import rvs, { ConnectionState, RVSMessage, ConnectionConfig, ConnectionLogEntry } from '../services/rvs';
|
||||||
import ModeSelector from '../components/ModeSelector';
|
import ModeSelector from '../components/ModeSelector';
|
||||||
import QRScanner from '../components/QRScanner';
|
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.`);
|
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 showPathPicker = useCallback(() => {
|
||||||
const options = storagePaths.map(p => p.label);
|
|
||||||
options.push('Eigenen Pfad eingeben');
|
|
||||||
options.push('Abbrechen');
|
|
||||||
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Speicherort waehlen',
|
'Speicherort waehlen',
|
||||||
'Wo sollen Anhaenge gespeichert werden?',
|
'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); },
|
onPress: () => { setTempPath(storagePath); setEditingPath(true); },
|
||||||
},
|
},
|
||||||
{ text: 'Abbrechen', style: 'cancel' as const },
|
{ text: 'Abbrechen', style: 'cancel' as const },
|
||||||
|
|||||||
+19
-11
@@ -977,17 +977,21 @@ 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] Bild gespeichert: %s (%dKB)", file_path, size_kb)
|
logger.info("[rvs] Bild gespeichert: %s (%dKB)", file_path, size_kb)
|
||||||
# App informieren wo die Datei liegt (fuer Re-Download)
|
# ERST an aria-core senden (wichtigster Schritt)
|
||||||
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),
|
|
||||||
})
|
|
||||||
text = (f"Stefan hat dir ein Bild geschickt: {file_name}"
|
text = (f"Stefan hat dir ein Bild geschickt: {file_name}"
|
||||||
f"{f' ({width}x{height}px)' if width else ''}"
|
f"{f' ({width}x{height}px)' if width else ''}"
|
||||||
f", {size_kb}KB."
|
f", {size_kb}KB."
|
||||||
f" Das Bild liegt unter: {file_path}")
|
f" Das Bild liegt unter: {file_path}")
|
||||||
await self.send_to_core(text, source="app-file")
|
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:
|
elif file_b64:
|
||||||
# Andere Datei in Shared Volume speichern
|
# Andere Datei in Shared Volume speichern
|
||||||
safe_name = f"file_{int(asyncio.get_event_loop().time())}_{file_name.replace('/', '_')}"
|
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))
|
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)
|
||||||
await self._send_to_rvs({
|
# ERST an aria-core senden
|
||||||
"type": "file_saved",
|
|
||||||
"payload": {"name": file_name, "serverPath": file_path, "mimeType": file_type},
|
|
||||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
|
||||||
})
|
|
||||||
text = (f"Stefan hat dir eine Datei geschickt: {file_name}"
|
text = (f"Stefan hat dir eine Datei geschickt: {file_name}"
|
||||||
f" ({file_type}, {size_kb}KB)."
|
f" ({file_type}, {size_kb}KB)."
|
||||||
f" Die Datei liegt unter: {file_path}")
|
f" Die Datei liegt unter: {file_path}")
|
||||||
await self.send_to_core(text, source="app-file")
|
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:
|
else:
|
||||||
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."
|
||||||
await self.send_to_core(text, source="app-file")
|
await self.send_to_core(text, source="app-file")
|
||||||
|
|||||||
@@ -859,7 +859,11 @@
|
|||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = `chat-msg ${type}`;
|
el.className = `chat-msg ${type}`;
|
||||||
const escaped = escapeHtml(text);
|
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>`;
|
el.innerHTML = `${linked}<div class="meta">${escapeHtml(meta)} — ${new Date().toLocaleTimeString('de-DE')}</div>`;
|
||||||
chatBox.appendChild(el);
|
chatBox.appendChild(el);
|
||||||
chatBox.scrollTop = chatBox.scrollHeight;
|
chatBox.scrollTop = chatBox.scrollHeight;
|
||||||
|
|||||||
@@ -462,6 +462,11 @@ function connectRVS(forcePlain) {
|
|||||||
pipelineEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`);
|
pipelineEnd(true, `Antwort via RVS von ${sender}: "${(msg.payload.text || "").slice(0, 120)}"`);
|
||||||
}
|
}
|
||||||
broadcast({ type: "rvs_chat", msg });
|
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") {
|
} else if (msg.type === "heartbeat") {
|
||||||
// ignorieren
|
// ignorieren
|
||||||
} else {
|
} else {
|
||||||
@@ -945,6 +950,28 @@ const server = http.createServer((req, res) => {
|
|||||||
} else if (req.url === "/api/session") {
|
} else if (req.url === "/api/session") {
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
res.end(JSON.stringify({ sessionKey: activeSessionKey }));
|
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 {
|
} else {
|
||||||
res.writeHead(404);
|
res.writeHead(404);
|
||||||
res.end("Not Found");
|
res.end("Not Found");
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ./aria-data/config/diag-state:/data # Persistenter State (aktive Session etc.)
|
- ./aria-data/config/diag-state:/data # Persistenter State (aktive Session etc.)
|
||||||
|
- aria-shared:/shared:ro # Shared Volume (Uploads lesen fuer Vorschau)
|
||||||
environment:
|
environment:
|
||||||
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
|
- ARIA_AUTH_TOKEN=${ARIA_AUTH_TOKEN:-}
|
||||||
- PROXY_URL=http://proxy:3456
|
- PROXY_URL=http://proxy:3456
|
||||||
|
|||||||
Reference in New Issue
Block a user