fixed auto download

This commit is contained in:
duffyduck 2026-03-29 13:58:51 +02:00
parent 867b03aa1e
commit fbdd4274ac
10 changed files with 173 additions and 51 deletions

View File

@ -125,7 +125,30 @@ const ChatScreen: React.FC = () => {
isInitialLoad.current = false;
}
};
loadMessages();
loadMessages().then(async () => {
// Auto-Re-Download: fehlende Anhänge vom Server nachladen (wenn aktiviert)
const autoDownload = await AsyncStorage.getItem('aria_auto_download');
if (autoDownload === 'false') return;
setTimeout(() => {
setMessages(prev => {
const missing: {id: string, serverPath: string}[] = [];
for (const msg of prev) {
for (const att of msg.attachments || []) {
if (att.serverPath && !att.uri) {
missing.push({ id: msg.id, serverPath: att.serverPath });
}
}
}
if (missing.length > 0) {
console.log(`[Chat] ${missing.length} fehlende Anhaenge — lade nach...`);
for (const m of missing) {
rvs.send('file_request' as any, { serverPath: m.serverPath, requestId: m.id });
}
}
return prev;
});
}, 2000); // Warten bis RVS verbunden ist
});
}, []);
// RVS-Nachrichten abonnieren
@ -165,20 +188,23 @@ const ChatScreen: React.FC = () => {
return;
}
// STT-Ergebnis: Transkribierten Text unter den Placeholder schreiben
if (message.type === 'stt_result') {
if (message.type === 'chat') {
const sender = (message.payload.sender as string) || '';
// STT-Ergebnis: Transkribierten Text in die Sprach-Bubble schreiben
if (sender === 'stt') {
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 ? `\uD83C\uDFA4 ${sttText}` : '\uD83C\uDFA4 (nicht erkannt)' }
? { ...m, text: `\uD83C\uDFA4 ${sttText}` }
: m
));
}
return;
}
if (message.type === 'chat') {
// Nur Nachrichten von ARIA anzeigen — eigene Nachrichten werden lokal hinzugefuegt
const sender = (message.payload.sender as string) || '';
// Eigene Nachrichten ignorieren (werden lokal hinzugefuegt)
if (sender === 'user' || sender === 'diagnostic') return;
const text = (message.payload.text as string) || '';

View File

@ -69,6 +69,7 @@ const SettingsScreen: React.FC = () => {
const [events, setEvents] = useState<EventEntry[]>([]);
const [connLog, setConnLog] = useState<ConnectionLogEntry[]>(rvs.getConnectionLog());
const [storagePath, setStoragePath] = useState(DEFAULT_STORAGE_PATH);
const [autoDownload, setAutoDownload] = useState(true);
const [storageSize, setStorageSize] = useState('...');
const [editingPath, setEditingPath] = useState(false);
const [tempPath, setTempPath] = useState('');
@ -83,10 +84,13 @@ const SettingsScreen: React.FC = () => {
setManualPort(String(config.port));
setManualToken(config.token);
}
// Speicherpfad laden
// Speicherpfad + Auto-Download laden
AsyncStorage.getItem(STORAGE_PATH_KEY).then(saved => {
if (saved) setStoragePath(saved);
});
AsyncStorage.getItem('aria_auto_download').then(saved => {
if (saved !== null) setAutoDownload(saved === 'true');
});
}, []);
// Speichergroesse berechnen
@ -441,10 +445,29 @@ const SettingsScreen: React.FC = () => {
{/* === Speicher === */}
<Text style={styles.sectionTitle}>Anhang-Speicher</Text>
<View style={styles.card}>
<View style={styles.toggleRow}>
<View style={styles.toggleInfo}>
<Text style={styles.toggleLabel}>Auto-Download</Text>
<Text style={styles.toggleHint}>
Fehlende Anhaenge beim App-Start automatisch vom Server laden
</Text>
</View>
<Switch
value={autoDownload}
onValueChange={(val) => {
setAutoDownload(val);
AsyncStorage.setItem('aria_auto_download', String(val));
}}
trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
thumbColor={autoDownload ? '#FFFFFF' : '#666680'}
/>
</View>
<View style={{height: 16}} />
<Text style={styles.toggleLabel}>Lokaler Speicherort</Text>
<Text style={styles.toggleHint}>
Hier werden Bilder und Dateien aus dem Chat gespeichert.
Bei geloeschtem Cache werden Anhaenge automatisch ueber RVS neu geladen.
{autoDownload ? ' Fehlende Dateien werden automatisch nachgeladen.' : ' Fehlende Dateien koennen per Tippen geladen werden.'}
</Text>
{editingPath ? (

View File

@ -925,7 +925,10 @@ class ARIABridge:
payload = message.get("payload", {})
if msg_type == "chat":
# Text von der App → an aria-core
# Nur User-Nachrichten weiterleiten — ARIA/Diagnostic-Antworten ignorieren (sonst Loop!)
sender = payload.get("sender", "")
if sender in ("aria", "diagnostic", "stt"):
return
text = payload.get("text", "")
if text:
logger.info("[rvs] App-Chat: '%s'", text[:80])
@ -1104,34 +1107,23 @@ class ARIABridge:
if text.strip():
logger.info("[rvs] STT Ergebnis: '%s'", text[:80])
# STT-Ergebnis zurueck an die App senden (zur Anzeige, nicht nochmal verarbeiten)
# ERST an aria-core senden (wichtigster Schritt)
await self.send_to_core(text, source="app-voice")
# STT-Text an RVS senden (fuer Anzeige in App + Diagnostic)
# sender="stt" damit Bridge es ignoriert (kein Loop)
try:
await self._send_to_rvs({
"type": "stt_result",
"type": "chat",
"payload": {
"text": text,
"sender": "user",
"sender": "stt",
},
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})
except Exception as e:
logger.warning("[rvs] STT-Ergebnis konnte nicht an App gesendet werden: %s", e)
# Text trotzdem an aria-core senden
await self.send_to_core(text, source="app-voice")
logger.warning("[rvs] STT-Text konnte nicht an RVS gesendet werden: %s", e)
else:
logger.info("[rvs] Keine Sprache erkannt — ignoriert")
try:
await self._send_to_rvs({
"type": "stt_result",
"payload": {
"text": "",
"error": "Keine Sprache erkannt",
"sender": "user",
},
"timestamp": int(asyncio.get_event_loop().time() * 1000),
})
except Exception as e:
logger.warning("[rvs] STT-Fehler konnte nicht an App gesendet werden: %s", e)
except Exception:
logger.exception("[rvs] Audio-Verarbeitung fehlgeschlagen")

View File

@ -196,7 +196,10 @@
<!-- Chat Test -->
<div class="grid">
<div class="card full">
<h2>Chat Test</h2>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<h2 style="margin:0;">Chat Test</h2>
<button class="btn secondary" onclick="toggleChatFullscreen()" id="btn-chat-fs" style="padding:4px 10px;font-size:11px;">Vollbild</button>
</div>
<div class="chat-box" id="chat-box"></div>
<div class="input-row">
<input type="text" id="chat-input" placeholder="Nachricht an ARIA...">
@ -206,6 +209,20 @@
</div>
</div>
<!-- Chat Vollbild Modal -->
<div id="chat-fullscreen" style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0D0D1A;z-index:1000;padding:16px;flex-direction:column;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;">
<h2 style="margin:0;color:#0096FF;">ARIA Chat</h2>
<button class="btn secondary" onclick="toggleChatFullscreen()" style="padding:6px 14px;">Schliessen</button>
</div>
<div id="chat-box-fs" class="chat-box" style="flex:1;max-height:none;min-height:0;overflow-y:auto;"></div>
<div class="input-row" style="margin-top:8px;">
<input type="text" id="chat-input-fs" placeholder="Nachricht an ARIA..." onkeydown="if(event.key==='Enter'){testGatewayFS();event.preventDefault();}">
<button class="btn" onclick="testGatewayFS()">Gateway senden</button>
<button class="btn" onclick="testRVSFS()">Via RVS senden</button>
</div>
</div>
<!-- Session + Brain Viewer -->
<div class="grid" style="grid-template-columns: 1fr 1fr;">
<div class="card">
@ -503,7 +520,8 @@
const p = msg.msg.payload || {};
const sender = p.sender || '?';
const chatType = (sender === 'aria') ? 'received' : 'sent';
addChat(chatType, p.text || '?', `via RVS (${sender})`);
const label = sender === 'stt' ? '\uD83C\uDFA4 Spracheingabe' : `via RVS (${sender})`;
addChat(chatType, p.text || '?', label);
return;
}
if (msg.type === 'proxy_result') {
@ -856,18 +874,59 @@
}
function addChat(type, text, meta) {
const el = document.createElement('div');
el.className = `chat-msg ${type}`;
const escaped = escapeHtml(text);
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;
const html = `${linked}<div class="meta">${escapeHtml(meta)} — ${new Date().toLocaleTimeString('de-DE')}</div>`;
// In beide Chat-Boxen schreiben (normal + Vollbild)
for (const box of [chatBox, document.getElementById('chat-box-fs')]) {
if (!box) continue;
const el = document.createElement('div');
el.className = `chat-msg ${type}`;
el.innerHTML = html;
box.appendChild(el);
box.scrollTop = box.scrollHeight;
}
}
let chatFullscreen = false;
function toggleChatFullscreen() {
const modal = document.getElementById('chat-fullscreen');
chatFullscreen = !chatFullscreen;
if (chatFullscreen) {
modal.style.display = 'flex';
// Chat-Inhalt synchronisieren
const fsBox = document.getElementById('chat-box-fs');
fsBox.innerHTML = chatBox.innerHTML;
fsBox.scrollTop = fsBox.scrollHeight;
document.getElementById('chat-input-fs').focus();
} else {
modal.style.display = 'none';
}
}
function testGatewayFS() {
const input = document.getElementById('chat-input-fs');
const text = input.value.trim();
if (!text) return;
addChat('sent', text, 'Gateway direkt');
send({ action: 'test_gateway', text });
input.value = '';
}
function testRVSFS() {
const input = document.getElementById('chat-input-fs');
const text = input.value.trim();
if (!text) return;
addChat('sent', text, 'via RVS');
send({ action: 'test_rvs', text });
input.value = '';
}
// Escape schliesst Vollbild-Chat
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && chatFullscreen) toggleChatFullscreen();
});
function openLightbox(mediaType, url) {
const lb = document.getElementById('lightbox');

View File

@ -462,11 +462,6 @@ 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 {

View File

@ -51,8 +51,30 @@ fi
echo -e " ${GREEN}${NC} Login erfolgreich"
echo ""
# ── Versionsnummern aktualisieren ─────────────
echo -e "${GREEN}[1/5] Versionsnummern auf $VERSION setzen...${NC}"
# package.json
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" android/package.json
echo -e " ${GREEN}${NC} package.json → $VERSION"
# build.gradle: versionName + versionCode (aus Major.Minor.Patch berechnen)
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH))
sed -i "s/versionName \"[^\"]*\"/versionName \"$VERSION\"/" android/android/app/build.gradle
sed -i "s/versionCode [0-9]*/versionCode $VERSION_CODE/" android/android/app/build.gradle
echo -e " ${GREEN}${NC} build.gradle → versionName $VERSION, versionCode $VERSION_CODE"
# SettingsScreen: Anzeige-Version
sed -i "s/Version [0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]* [^<]*/Version $VERSION /" android/src/screens/SettingsScreen.tsx
echo -e " ${GREEN}${NC} SettingsScreen → Version $VERSION"
echo ""
# ── APK bauen ─────────────────────────────────
echo -e "${GREEN}[1/4] APK bauen...${NC}"
echo -e "${GREEN}[2/5] APK bauen...${NC}"
cd android
./build.sh release
cd ..
@ -70,7 +92,11 @@ echo -e " ${GREEN}✓${NC} APK gebaut ($APK_SIZE)"
echo ""
# ── Git Tag ───────────────────────────────────
echo -e "${GREEN}[2/4] Git Tag $TAG...${NC}"
echo -e "${GREEN}[3/5] Git Tag $TAG...${NC}"
# Versions-Aenderungen committen
git add android/package.json android/android/app/build.gradle android/src/screens/SettingsScreen.tsx
git commit -m "release: bump version to $VERSION" 2>/dev/null || echo -e " ${YELLOW}Keine Aenderungen zum Committen${NC}"
if git rev-parse "$TAG" &>/dev/null; then
echo -e " ${YELLOW}Tag $TAG existiert bereits — überspringe${NC}"
@ -79,7 +105,7 @@ else
echo -e " ${GREEN}${NC} Tag $TAG erstellt"
fi
git push origin "$TAG"
git push origin main "$TAG"
echo -e " ${GREEN}${NC} Tag gepusht"
echo ""
@ -102,7 +128,7 @@ fi
RELEASE_BODY_ESCAPED=$(printf '%s' "$RELEASE_BODY" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))' 2>/dev/null || printf '"%s"' "$RELEASE_BODY" | sed 's/"/\\"/g')
# ── Gitea Release erstellen ───────────────────
echo -e "${GREEN}[3/4] Gitea Release erstellen...${NC}"
echo -e "${GREEN}[4/5] Gitea Release erstellen...${NC}"
RELEASE_RESPONSE=$(curl -s -X POST \
"$GITEA_URL/api/v1/repos/$GITEA_REPO/releases" \
@ -127,7 +153,7 @@ echo -e " ${GREEN}✓${NC} Release #$RELEASE_ID erstellt"
echo ""
# ── APK hochladen ─────────────────────────────
echo -e "${GREEN}[4/4] APK hochladen...${NC}"
echo -e "${GREEN}[5/5] APK hochladen...${NC}"
UPLOAD_RESPONSE=$(curl -s -X POST \
"$GITEA_URL/api/v1/repos/$GITEA_REPO/releases/$RELEASE_ID/assets?name=$APK_NAME" \

View File

@ -9,6 +9,7 @@ const MAX_SESSIONS = parseInt(process.env.MAX_SESSIONS || "10", 10);
// Erlaubte Nachrichtentypen — alles andere wird verworfen
const ALLOWED_TYPES = new Set([
"chat", "audio", "file", "location", "mode", "log", "event", "heartbeat",
"file_request", "file_response", "file_saved", "stt_result",
]);
// Token-Raum: token -> { clients: Set<ws> }