feat: QR-Code Onboarding + TTS-Audio-Cache im Filesystem
QR-Code Onboarding - Diagnostic: GET /api/onboarding gibt RVS-Credentials zurueck - Einstellungen-UI: neue Sektion mit QR-Code (qrcode-generator via CDN) - Format kompatibel mit bestehendem QRScanner.parseQRData (host/port/tls/token) - App-SettingsScreen hatte QR-Scanner bereits — funktioniert out of the box - Warnhinweis zu Token im Klartext TTS-Audio-Cache - Bridge: jede ARIA-Chat-Nachricht bekommt eine messageId (UUID) Audio-Payload wird mit messageId verknuepft (Piper-Pfade) - ChatScreen: messageId + audioPath in ChatMessage Interface - audioService.cacheAudio(): speichert Base64 in DocumentDirectory/tts_cache/<id>.wav - audioService.playFromPath(): spielt aus Cache ohne Regenerierung - Play-Button: wenn audioPath gesetzt → aus Cache, sonst tts_request - cleanupOldTTSCache(): alte unreferenzierte WAVs (>30 Tage) weg - Persistiert via AsyncStorage — ueberlebt App-Restart Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+68
-1
@@ -523,6 +523,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App-Onboarding via QR-Code -->
|
||||
<div class="settings-section">
|
||||
<h2>App-Onboarding (QR-Code)</h2>
|
||||
<div style="font-size:11px;color:#8888AA;margin-bottom:8px;">
|
||||
RVS-Credentials als QR-Code — App scannt, keine manuelle Eingabe.
|
||||
Enthaelt Host, Port, TLS-Flag und Token.
|
||||
</div>
|
||||
<div class="card" style="max-width:500px;">
|
||||
<div style="display:flex;gap:12px;align-items:flex-start;">
|
||||
<div id="onboarding-qr" style="width:220px;height:220px;background:#1E1E2E;border-radius:6px;display:flex;align-items:center;justify-content:center;color:#555570;font-size:11px;text-align:center;padding:8px;">
|
||||
QR-Code wird geladen...
|
||||
</div>
|
||||
<div style="flex:1;font-size:11px;color:#8888AA;line-height:1.5;">
|
||||
<div style="color:#FF9500;font-weight:bold;margin-bottom:4px;">Achtung</div>
|
||||
Dieser QR enthaelt den RVS-Token im Klartext — zeige ihn niemandem,
|
||||
speichere keine Screenshots davon in unsicheren Cloud-Diensten.
|
||||
<button class="btn" onclick="loadOnboardingQR()" style="margin-top:10px;width:100%;">
|
||||
QR neu generieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Highlight-Trigger -->
|
||||
<div class="settings-section">
|
||||
<h2>Highlight-Trigger</h2>
|
||||
@@ -1441,6 +1465,48 @@
|
||||
send({ action: 'send_voice_config', defaultVoice, highlightVoice, ttsEnabled, speedRamona, speedThorsten, ttsEngine, xttsVoice, whisperModel });
|
||||
}
|
||||
|
||||
// ── App-Onboarding QR-Code ────────────────────
|
||||
let qrLibReady = false;
|
||||
function ensureQRLib() {
|
||||
return new Promise((resolve) => {
|
||||
if (qrLibReady || window.qrcode) { qrLibReady = true; resolve(); return; }
|
||||
const s = document.createElement('script');
|
||||
s.src = 'https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js';
|
||||
s.onload = () => { qrLibReady = true; resolve(); };
|
||||
s.onerror = () => resolve(); // silent fail
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOnboardingQR() {
|
||||
const box = document.getElementById('onboarding-qr');
|
||||
box.textContent = 'Lade...';
|
||||
try {
|
||||
await ensureQRLib();
|
||||
if (!window.qrcode) throw new Error('QR-Library nicht geladen');
|
||||
const resp = await fetch('/api/onboarding');
|
||||
const cfg = await resp.json();
|
||||
if (!cfg.rvsHost || !cfg.rvsToken) {
|
||||
box.innerHTML = '<div style="color:#FF6B6B;">RVS nicht konfiguriert (ENV Variablen fehlen)</div>';
|
||||
return;
|
||||
}
|
||||
// Format kompatibel mit android/src/components/QRScanner.tsx parseQRData()
|
||||
const payload = JSON.stringify({
|
||||
host: cfg.rvsHost,
|
||||
port: Number(cfg.rvsPort) || 443,
|
||||
tls: cfg.rvsTLS !== false,
|
||||
token: cfg.rvsToken,
|
||||
});
|
||||
const qr = window.qrcode(0, 'M');
|
||||
qr.addData(payload);
|
||||
qr.make();
|
||||
box.innerHTML = qr.createImgTag(6, 4);
|
||||
box.querySelector('img').style.cssText = 'background:#fff;padding:8px;border-radius:4px;display:block;';
|
||||
} catch (e) {
|
||||
box.innerHTML = `<div style="color:#FF6B6B;">Fehler: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Highlight-Trigger ────────────────────────
|
||||
function loadHighlightTriggers() {
|
||||
send({ action: 'get_triggers' });
|
||||
@@ -1921,10 +1987,11 @@
|
||||
document.querySelectorAll('.main-nav-btn').forEach(b => {
|
||||
if (b.textContent.trim().toLowerCase().includes(tab === 'main' ? 'main' : 'einstellung')) b.classList.add('active');
|
||||
});
|
||||
// Einstellungen: Config + Trigger laden
|
||||
// Einstellungen: Config + Trigger + QR laden
|
||||
if (tab === 'settings') {
|
||||
loadHighlightTriggers();
|
||||
send({ action: 'get_voice_config' });
|
||||
loadOnboardingQR();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1169,6 +1169,15 @@ 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 === "/api/onboarding") {
|
||||
// RVS-Credentials fuer QR-Code App-Onboarding
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({
|
||||
rvsHost: RVS_HOST,
|
||||
rvsPort: RVS_PORT,
|
||||
rvsTLS: RVS_TLS === "true" || RVS_TLS === true,
|
||||
rvsToken: RVS_TOKEN,
|
||||
}));
|
||||
} else if (req.url === "/api/cancel" && req.method === "POST") {
|
||||
log("warn", "server", "HTTP /api/cancel — Cancel-Request (von Bridge)");
|
||||
pendingMessageTime = 0;
|
||||
|
||||
Reference in New Issue
Block a user