diff --git a/diagnostic/index.html b/diagnostic/index.html index 80c3e17..8c240ea 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -2301,7 +2301,7 @@ // project_changed an App + andere Tabs). await refreshDiagProjectsCache(); } catch (e) { - alert('Verstecken/Anzeigen fehlgeschlagen: ' + e.message); + uiAlert('Verstecken/Anzeigen fehlgeschlagen: ' + e.message); } } @@ -2771,9 +2771,9 @@ if (msg.type === 'voice_id_delete_response') { const p = msg.payload || msg; if (p.removed) { - alert('Fingerprint gelöscht — Voice-ID-Gating fällt zurück auf Fail-Open.'); + uiAlert('Fingerprint gelöscht — Voice-ID-Gating fällt zurück auf Fail-Open.'); } else { - alert('Es war kein Fingerprint vorhanden.'); + uiAlert('Es war kein Fingerprint vorhanden.'); } refreshVoiceIdStatus(); loadLlmCatalog(); @@ -3195,7 +3195,7 @@ * Backend (Bridge) raeumt chat_backup.jsonl + Brain-Conversation * und broadcastet danach chat_message_deleted — wir entfernen die * Bubble lokal erst dann, nicht optimistisch. */ - function deleteDiagBubble(ts) { + async function deleteDiagBubble(ts) { if (!ts) return; let preview = ''; for (const box of [chatBox, document.getElementById('chat-box-fs')]) { @@ -3203,7 +3203,7 @@ const el = box.querySelector(`.chat-msg[data-ts="${ts}"]`); if (el) { preview = (el.textContent || '').slice(0, 80); break; } } - if (!confirm(`Diese Bubble wirklich loeschen?\n\n"${preview}…"\n\nWird aus chat_backup, Brain-Konversation und allen Clients entfernt.`)) return; + if (!await uiConfirm(`Diese Bubble wirklich loeschen?\n\n"${preview}…"\n\nWird aus chat_backup, Brain-Konversation und allen Clients entfernt.`)) return; send({ action: 'delete_chat_message', ts }); } @@ -3619,7 +3619,7 @@ diagModelNames = diagCtxModels.names || {}; renderContextStrip(); renderCtxModelBar(); - } catch (e) { alert('Modell setzen fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Modell setzen fehlgeschlagen: ' + e.message); } } async function ctxModelBack() { @@ -3634,7 +3634,7 @@ renderContextStrip(); renderCtxModelBar(); try { loadSafeguard(); } catch {} - } catch (e) { alert('Zurückschalten fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Zurückschalten fehlgeschlagen: ' + e.message); } } // Untertitel der Kontext-Karten: welches Modell der naechste Turn nutzt. @@ -4105,9 +4105,9 @@ if (modal) modal.style.display = 'none'; } - function clearThoughtStream() { + async function clearThoughtStream() { if (thoughtStream.length === 0) return; - if (!confirm(`Gedanken-Stream leeren? ${thoughtStream.length} Eintraege werden geloescht.`)) return; + if (!await uiConfirm(`Gedanken-Stream leeren? ${thoughtStream.length} Eintraege werden geloescht.`)) return; thoughtStream = []; lastThoughtKey = ''; updateThoughtsBadge(); @@ -4202,7 +4202,7 @@ const b64 = btoa(bin); // Name aus Dateinamen ableiten — z.B. "maia.tar.gz" → "maia" const baseName = file.name.replace(/\.tar\.gz$|\.tgz$/i, ''); - if (!confirm(`Stimme "${baseName}" importieren?\n\nFalls schon vorhanden, wird sie überschrieben.`)) { + if (!await uiConfirm(`Stimme "${baseName}" importieren?\n\nFalls schon vorhanden, wird sie überschrieben.`)) { event.target.value = ''; return; } @@ -4278,8 +4278,8 @@ send({ action: 'voice_id_status' }); } - function deleteVoiceId() { - if (!confirm('Voice-ID-Fingerprint loeschen?\n\nDanach muss in der App neu enrolled werden.')) return; + async function deleteVoiceId() { + if (!await uiConfirm('Voice-ID-Fingerprint loeschen?\n\nDanach muss in der App neu enrolled werden.')) return; send({ action: 'voice_id_delete' }); } @@ -4377,7 +4377,7 @@ // Beim Verstecken bleibt der „anzeigen"-Modus wie er ist; beim // Wieder-Sichtbarmachen sieht man es sofort ohne Umschalten. loadProjects(); - } catch (e) { alert('Verstecken/Anzeigen fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Verstecken/Anzeigen fehlgeschlagen: ' + e.message); } } async function setProjectKind(id, kind) { @@ -4389,7 +4389,7 @@ }); if (!r.ok) throw new Error('HTTP ' + r.status); loadProjects(); - } catch (e) { alert('Code-Markierung fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Code-Markierung fehlgeschlagen: ' + e.message); } } async function switchProject(projectId) { @@ -4400,23 +4400,23 @@ body: JSON.stringify({ project_id: projectId }), }); loadProjects(); - } catch (e) { alert('Wechsel fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Wechsel fehlgeschlagen: ' + e.message); } } async function endProject(id, name) { - if (!confirm(`Projekt "${name}" beenden?\n\nBleibt sichtbar, aktiv ist dann der Hauptchat.`)) return; + if (!await uiConfirm(`Projekt "${name}" beenden?\n\nBleibt sichtbar, aktiv ist dann der Hauptchat.`)) return; try { await fetch(`/api/brain/projects/${encodeURIComponent(id)}/end`, { method: 'POST' }); loadProjects(); - } catch (e) { alert('Beenden fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Beenden fehlgeschlagen: ' + e.message); } } async function archiveProject(id, name) { - if (!confirm(`Projekt "${name}" archivieren?\n\nVerschwindet aus der Liste.`)) return; + if (!await uiConfirm(`Projekt "${name}" archivieren?\n\nVerschwindet aus der Liste.`)) return; try { await fetch(`/api/brain/projects/${encodeURIComponent(id)}/archive`, { method: 'POST' }); loadProjects(); - } catch (e) { alert('Archivieren fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Archivieren fehlgeschlagen: ' + e.message); } } function openCreateProjectModal() { @@ -4433,7 +4433,7 @@ async function submitCreateProject() { const name = document.getElementById('project-create-name').value.trim(); const description = document.getElementById('project-create-desc').value.trim(); - if (!name) { alert('Name darf nicht leer sein.'); return; } + if (!name) { uiAlert('Name darf nicht leer sein.'); return; } try { const r = await fetch('/api/brain/projects/create', { method: 'POST', @@ -4458,7 +4458,7 @@ // ein Projekt an und schreibt weiter in den Hauptchat. const pid = (d && d.id) ? d.id : ''; if (pid) switchDiagFocus(pid); - } catch (e) { alert('Anlegen fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Anlegen fehlgeschlagen: ' + e.message); } } function escapeHtml(str) { @@ -4468,8 +4468,72 @@ return String(str).replace(/['"\\]/g, '\\$&'); } - function deleteXttsVoice(name) { - if (!confirm(`Stimme "${name}" endgueltig loeschen?`)) return; + // ── Eigene Dialoge statt alert/confirm/prompt ───────────────────── + // Gleicher Stil wie der Aufteilen-Dialog. Alle liefern ein Promise: + // uiConfirm → true/false, uiPrompt → Text oder null, uiAlert → undefined. + // Erster Absatz der Meldung = Ueberschrift, der Rest = Erklaerung. + // Esc / Klick daneben = Abbrechen, Enter = OK. + const _UI_DANGER = /l[öo]e?schen|entfernen|zur[üu]cksetzen|NOT-AUS|AGGRESSIV|WIRKLICH|endg[üu]ltig|archivieren|beenden|verloren|[üu]berschrieben|leeren/i; + function _uiDialog({ message, input, defaultValue, buttons }) { + return new Promise(resolve => { + const text = String(message ?? ''); + const cut = text.indexOf('\n\n'); + const title = cut > 0 ? text.slice(0, cut) : text; + const rest = cut > 0 ? text.slice(cut + 2) : ''; + const bg = document.createElement('div'); + bg.style.cssText = 'position:fixed;inset:0;z-index:10001;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;padding:16px;'; + bg.innerHTML = `
+
${escapeHtml(title)}
+ ${rest ? `
${escapeHtml(rest)}
` : ''} + ${input ? `` : ''} +
+ ${buttons.map((b, i) => ``).join('')} +
`; + const inp = bg.querySelector('input'); + if (inp) inp.value = defaultValue ?? ''; + const prevFocus = document.activeElement; + const done = v => { + document.removeEventListener('keydown', onKey, true); + bg.remove(); + try { prevFocus && prevFocus.focus && prevFocus.focus(); } catch (_) {} + resolve(v); + }; + const value = b => (inp && b.value === true ? inp.value : b.value); + const primary = buttons.find(b => b.primary) || buttons[buttons.length - 1]; + const cancel = buttons.find(b => b.cancel) || primary; + const onKey = e => { + if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); done(cancel.value); } + else if (e.key === 'Enter' && (!inp || document.activeElement === inp)) { e.preventDefault(); e.stopPropagation(); done(value(primary)); } + }; + bg.addEventListener('click', e => { + const b = e.target.closest('[data-i]'); + if (b) done(value(buttons[+b.dataset.i])); + else if (e.target === bg) done(cancel.value); + }); + document.addEventListener('keydown', onKey, true); + document.body.appendChild(bg); + (inp || bg.querySelector(`[data-i="${buttons.indexOf(primary)}"]`)).focus(); + if (inp) inp.select(); + }); + } + function uiConfirm(message) { + const danger = _UI_DANGER.test(String(message ?? '')); + return _uiDialog({ message, buttons: [ + { label: 'Abbrechen', value: false, cancel: true }, + { label: danger ? 'Ja, ausführen' : 'OK', value: true, primary: true, danger } ] }); + } + function uiPrompt(message, defaultValue = '') { + return _uiDialog({ message, input: true, defaultValue, buttons: [ + { label: 'Abbrechen', value: null, cancel: true }, + { label: 'OK', value: true, primary: true } ] }); + } + function uiAlert(message) { + return _uiDialog({ message, buttons: [{ label: 'OK', value: undefined, primary: true }] }); + } + + async function deleteXttsVoice(name) { + if (!await uiConfirm(`Stimme "${name}" endgueltig loeschen?`)) return; send({ action: 'xtts_delete_voice', name }); // Bei aktueller Auswahl: auf Default zuruecksetzen const sel = document.getElementById('diag-xtts-voice'); @@ -4498,9 +4562,9 @@ async function uploadVoiceSamples() { const name = document.getElementById('xtts-clone-name').value.trim(); const files = document.getElementById('xtts-clone-files').files; - if (!name) { alert('Bitte einen Namen eingeben'); return; } - if (!files || files.length === 0) { alert('Bitte Audio-Dateien auswaehlen'); return; } - if (files.length > 10) { alert('Maximal 10 Dateien'); return; } + if (!name) { uiAlert('Bitte einen Namen eingeben'); return; } + if (!files || files.length === 0) { uiAlert('Bitte Audio-Dateien auswaehlen'); return; } + if (files.length > 10) { uiAlert('Maximal 10 Dateien'); return; } const status = document.getElementById('xtts-clone-status'); status.textContent = `Lade ${files.length} Datei(en)...`; @@ -4592,7 +4656,7 @@ const text = await file.text(); // Sanity-Check: muss JSON sein JSON.parse(text); - if (!confirm(`Voice-Settings aus "${file.name}" importieren?\n\nAktuelle voice_config + highlight_triggers werden überschrieben.`)) { + if (!await uiConfirm(`Voice-Settings aus "${file.name}" importieren?\n\nAktuelle voice_config + highlight_triggers werden überschrieben.`)) { event.target.value = ''; return; } @@ -4603,13 +4667,13 @@ }); const d = await r.json(); if (d.ok) { - alert('✓ ' + d.message); + uiAlert('✓ ' + d.message); send({ action: 'get_voice_config' }); } else { - alert('Import fehlgeschlagen: ' + d.error); + uiAlert('Import fehlgeschlagen: ' + d.error); } } catch (e) { - alert('Ungültige Datei: ' + e.message); + uiAlert('Ungültige Datei: ' + e.message); } finally { event.target.value = ''; } @@ -4618,8 +4682,8 @@ // ── Wipe All — Komplett-Reset ────────────────────────── async function wipeAll() { const statusEl = document.getElementById('wipe-status'); - if (!confirm('WIRKLICH alles löschen?\n\nGedächtnis + Stimmen + Settings → komplett weg.\n.env + SSH-Keys bleiben.\n\nVorher exportieren wenn was erhalten bleiben soll.')) return; - if (!confirm('Letzte Warnung: Das KANN NICHT rückgängig gemacht werden.\n\nFortfahren?')) return; + if (!await uiConfirm('WIRKLICH alles löschen?\n\nGedächtnis + Stimmen + Settings → komplett weg.\n.env + SSH-Keys bleiben.\n\nVorher exportieren wenn was erhalten bleiben soll.')) return; + if (!await uiConfirm('Letzte Warnung: Das KANN NICHT rückgängig gemacht werden.\n\nFortfahren?')) return; statusEl.innerHTML = '⏳ Stoppe Container, lösche Daten...'; try { const r = await fetch('/api/wipe-all', { method: 'POST' }); @@ -4638,7 +4702,7 @@ // ── Generischer Container-Restart (Bridge/Brain/Qdrant) ────── async function restartContainer(name) { const statusEl = document.getElementById('restart-status'); - if (!confirm(`Container "${name}" wirklich neu starten?\n\nLaufende Anfragen gehen verloren.`)) return; + if (!await uiConfirm(`Container "${name}" wirklich neu starten?\n\nLaufende Anfragen gehen verloren.`)) return; if (statusEl) statusEl.innerHTML = `⏳ Restart ${name}...`; try { const r = await fetch('/api/container-restart', { @@ -5175,8 +5239,8 @@ } return `
${time} ${_ariaEsc(kind)}: ${_ariaEsc(JSON.stringify(p).slice(0, 500))}
`; } - function ariaPanicStop() { - if (!confirm('Wirklich NOT-AUS? Alle aktiven Claude-Subprocesses werden sofort gekillt.')) return; + async function ariaPanicStop() { + if (!await uiConfirm('Wirklich NOT-AUS? Alle aktiven Claude-Subprocesses werden sofort gekillt.')) return; send({ action: 'aria_panic_stop' }); _ariaPushLine( `━━━ ${_ariaTimePrefix()} ⛔ NOT-AUS ausgeloest ━━━`, @@ -5679,8 +5743,8 @@ } /** Entfernt einen Agenten aus der Liste (z.B. den alten Namen nach dem * Umbenennen). Läuft er noch, meldet er sich von selbst wieder. */ - function forgetHost(hostId, name) { - if (!confirm('"' + name + '" aus der Liste entfernen?\n\n' + + async function forgetHost(hostId, name) { + if (!await uiConfirm('"' + name + '" aus der Liste entfernen?\n\n' + 'Das stoppt den Agenten NICHT — läuft er noch, erscheint er ' + 'beim nächsten Lebenszeichen wieder.')) return; send({ action: 'host_forget', hostId }); @@ -5756,7 +5820,7 @@ if (pw) body.password = pw; if (spw) body.smtp_password = spw; if (body.imap_encryption === 'none' || body.smtp_encryption === 'none') { - if (!confirm('Ohne Verschlüsselung gehen Passwort und Mails im Klartext durchs Netz. Trotzdem speichern?')) return; + if (!await uiConfirm('Ohne Verschlüsselung gehen Passwort und Mails im Klartext durchs Netz. Trotzdem speichern?')) return; } _mailStatus('Speichere …'); try { @@ -5809,9 +5873,9 @@ renderJellyfin(); } - function removeJellyfinServer(i) { + async function removeJellyfinServer(i) { const s = jellyfinServers[i]; - if (!confirm('Server "' + (s.name || s.url || 'neu') + '" entfernen?')) return; + if (!await uiConfirm('Server "' + (s.name || s.url || 'neu') + '" entfernen?')) return; jellyfinServers.splice(i, 1); if (jellyfinServers.length && !jellyfinServers.some(x => x.isDefault)) { jellyfinServers[0].isDefault = true; @@ -6110,9 +6174,9 @@ * sofort und fuer jeden schon ausgerollten Agenten. Die Bridge loest ihn * beim Ansprechen wieder auf, damit sich der Agent weiter angesprochen * fuehlt. */ - function renameEntity(kind, id, currentAlias, ownName) { + async function renameEntity(kind, id, currentAlias, ownName) { const was = kind === 'satellite' ? 'Satellit' : 'Agent'; - const name = prompt( + const name = await uiPrompt( was + ' umbenennen\n\n' + 'Heißt sich selbst: ' + ownName + '\n' + 'ARIA spricht ihn danach unter dem neuen Namen an.\n' @@ -6126,8 +6190,8 @@ * ("mach mir Port 5000 von duffy-desktop auf") statt ueber eine IP. * Gespeichert wird an der MAC — ein IP-Wechsel per DHCP verliert den * Namen dann nicht. */ - function renameSatDevice(satId, mac, ip, current) { - const name = prompt( + async function renameSatDevice(satId, mac, ip, current) { + const name = await uiPrompt( 'Name für ' + (ip || mac) + '\n\n' + 'ARIA spricht das Gerät danach so an. Leer lassen = Namen entfernen.', current || ''); @@ -6354,17 +6418,17 @@ }); loadTriggers(); } catch (e) { - alert('Toggle fehlgeschlagen: ' + e.message); + uiAlert('Toggle fehlgeschlagen: ' + e.message); } } async function deleteTrigger(name) { - if (!confirm(`Trigger "${name}" wirklich löschen?`)) return; + if (!await uiConfirm(`Trigger "${name}" wirklich löschen?`)) return; try { await fetch('/api/brain/triggers/' + encodeURIComponent(name), { method: 'DELETE' }); loadTriggers(); } catch (e) { - alert('Löschen fehlgeschlagen: ' + e.message); + uiAlert('Löschen fehlgeschlagen: ' + e.message); } } @@ -6423,7 +6487,7 @@ /** Edit-Modus: Modal mit existierenden Trigger-Werten fuellen. */ async function openTriggerEdit(name) { const t = triggersCache.find(x => x.name === name); - if (!t) { alert('Trigger nicht in cache, lade neu...'); loadTriggers(); return; } + if (!t) { uiAlert('Trigger nicht in cache, lade neu...'); loadTriggers(); return; } editingTriggerName = name; document.getElementById('trigger-modal-title').textContent = 'Trigger bearbeiten — ' + name; document.getElementById('trigger-modal-save-btn').textContent = 'Speichern'; @@ -6725,7 +6789,7 @@ if (stat) { stat.textContent = 'Fehler ' + r.status; stat.style.color = '#FF6B6B'; } } } catch (e) { - alert('Speichern fehlgeschlagen: ' + e.message); + uiAlert('Speichern fehlgeschlagen: ' + e.message); } } @@ -6760,7 +6824,7 @@ } async function rollbackSkillVersion(name, versionId) { - if (!confirm(`Skill "${name}" auf Version ${versionId} zurückrollen?\n\nDer aktuelle Stand wird vorher automatisch gesichert (safety-snapshot).`)) return; + if (!await uiConfirm(`Skill "${name}" auf Version ${versionId} zurückrollen?\n\nDer aktuelle Stand wird vorher automatisch gesichert (safety-snapshot).`)) return; try { const r = await fetch('/api/brain/skills/' + encodeURIComponent(name) + '/rollback', { method: 'POST', headers: {'Content-Type':'application/json'}, @@ -6768,24 +6832,24 @@ }); const d = await r.json(); if (r.ok) { - alert(`✓ Rollback OK\nSicherheits-Snapshot: ${d.safety_snapshot}`); + uiAlert(`✓ Rollback OK\nSicherheits-Snapshot: ${d.safety_snapshot}`); loadSkillVersionsSection(name); loadSkills(); } else { - alert('Rollback fehlgeschlagen: ' + (d.detail || JSON.stringify(d))); + uiAlert('Rollback fehlgeschlagen: ' + (d.detail || JSON.stringify(d))); } - } catch (e) { alert('Rollback-Fehler: ' + e.message); } + } catch (e) { uiAlert('Rollback-Fehler: ' + e.message); } } async function deleteSkillVersion(name, versionId) { - if (!confirm(`Version ${versionId} von "${name}" wirklich löschen?\n\nNicht rückholbar.`)) return; + if (!await uiConfirm(`Version ${versionId} von "${name}" wirklich löschen?\n\nNicht rückholbar.`)) return; try { const r = await fetch('/api/brain/skills/' + encodeURIComponent(name) + '/versions/' + encodeURIComponent(versionId), { method: 'DELETE', }); if (r.ok) loadSkillVersionsSection(name); - else { const d = await r.json().catch(()=>({})); alert('Löschen fehlgeschlagen: ' + (d.detail || r.status)); } - } catch (e) { alert('Fehler: ' + e.message); } + else { const d = await r.json().catch(()=>({})); uiAlert('Löschen fehlgeschlagen: ' + (d.detail || r.status)); } + } catch (e) { uiAlert('Fehler: ' + e.message); } } async function toggleSkillActive(name, newActive) { @@ -6797,29 +6861,29 @@ }); loadSkills(); } catch (e) { - alert('Toggle fehlgeschlagen: ' + e.message); + uiAlert('Toggle fehlgeschlagen: ' + e.message); } } async function deleteSkill(name) { - if (!confirm(`Skill "${name}" wirklich löschen?\n\nFolder, venv, Logs — alles weg.`)) return; + if (!await uiConfirm(`Skill "${name}" wirklich löschen?\n\nFolder, venv, Logs — alles weg.`)) return; try { await fetch('/api/brain/skills/' + encodeURIComponent(name), { method: 'DELETE' }); skillExpanded.delete(name); loadSkills(); } catch (e) { - alert('Löschen fehlgeschlagen: ' + e.message); + uiAlert('Löschen fehlgeschlagen: ' + e.message); } } async function runSkillPrompt(name) { - const argsStr = prompt(`Skill "${name}" ausführen.\n\nArgs als JSON (oder leer):`, '{}'); + const argsStr = await uiPrompt(`Skill "${name}" ausführen.\n\nArgs als JSON (oder leer):`, '{}'); if (argsStr === null) return; let args = {}; try { args = JSON.parse(argsStr || '{}'); } catch (e) { - alert('Ungültiges JSON: ' + e.message); + uiAlert('Ungültiges JSON: ' + e.message); return; } try { @@ -6830,11 +6894,11 @@ }); const d = await r.json(); const summary = `${d.ok ? '✓ OK' : '✗ FEHLER'} · ${d.duration_sec}s · exit=${d.exit_code}\n\nstdout:\n${(d.stdout||'').slice(0,1500)}\n\nstderr:\n${(d.stderr||'').slice(0,500)}`; - alert(summary); + uiAlert(summary); if (skillExpanded.has(name)) toggleSkillExpand(name); // refresh logs by collapse+expand loadSkills(); } catch (e) { - alert('Run fehlgeschlagen: ' + e.message); + uiAlert('Run fehlgeschlagen: ' + e.message); } } @@ -6845,7 +6909,7 @@ async function importSkillFile(event) { const file = event.target.files[0]; if (!file) return; - if (!confirm(`Skill aus "${file.name}" importieren?\n\nFalls schon vorhanden mit gleichem Namen, wird er überschrieben.`)) { + if (!await uiConfirm(`Skill aus "${file.name}" importieren?\n\nFalls schon vorhanden mit gleichem Namen, wird er überschrieben.`)) { event.target.value = ''; return; } try { @@ -6856,13 +6920,13 @@ }); const d = await r.json(); if (r.ok) { - alert(`✓ Skill "${d.imported?.name}" importiert.`); + uiAlert(`✓ Skill "${d.imported?.name}" importiert.`); loadSkills(); } else { - alert('Import fehlgeschlagen: ' + (d.detail || JSON.stringify(d))); + uiAlert('Import fehlgeschlagen: ' + (d.detail || JSON.stringify(d))); } } catch (e) { - alert('Import fehlgeschlagen: ' + e.message); + uiAlert('Import fehlgeschlagen: ' + e.message); } finally { event.target.value = ''; } @@ -7027,7 +7091,7 @@ if (f) f.projectId = projectId || ''; renderFilesList(); } catch (e) { - alert('Zuordnung fehlgeschlagen: ' + e.message); + uiAlert('Zuordnung fehlgeschlagen: ' + e.message); renderFilesList(); // Dropdown auf alten Wert zuruecksetzen } } @@ -7086,7 +7150,7 @@ } async function restoreVersion(rel, hash) { - if (!confirm(`Diese Version (${hash.slice(0,7)}) als aktive Version setzen?\n\nDie aktuelle Version bleibt rollback-bar in der Historie.`)) return; + if (!await uiConfirm(`Diese Version (${hash.slice(0,7)}) als aktive Version setzen?\n\nDie aktuelle Version bleibt rollback-bar in der Historie.`)) return; try { const r = await fetch('/api/files-version-restore', { method: 'POST', @@ -7099,7 +7163,7 @@ showVersions(rel); loadFiles(); } catch (e) { - alert('Restore fehlgeschlagen: ' + e.message); + uiAlert('Restore fehlgeschlagen: ' + e.message); } } @@ -7131,14 +7195,14 @@ document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 100); } catch (e) { - alert('ZIP-Download fehlgeschlagen: ' + e.message); + uiAlert('ZIP-Download fehlgeschlagen: ' + e.message); } } async function deleteSelected() { const paths = [...filesSelected]; if (!paths.length) return; - if (!confirm(`${paths.length} Datei(en) wirklich löschen?\n\nIn allen Chat-Bubbles werden sie als gelöscht markiert.`)) return; + if (!await uiConfirm(`${paths.length} Datei(en) wirklich löschen?\n\nIn allen Chat-Bubbles werden sie als gelöscht markiert.`)) return; try { const r = await fetch('/api/files-delete-batch', { method: 'POST', @@ -7150,10 +7214,10 @@ filesSelected.clear(); loadFiles(); } else { - alert('Bulk-Delete fehlgeschlagen: ' + (d.error || 'unbekannt')); + uiAlert('Bulk-Delete fehlgeschlagen: ' + (d.error || 'unbekannt')); } } catch (e) { - alert('Bulk-Delete fehlgeschlagen: ' + e.message); + uiAlert('Bulk-Delete fehlgeschlagen: ' + e.message); } } @@ -7168,7 +7232,7 @@ } async function deleteFile(p, name) { - if (!confirm(`Datei "${name}" wirklich löschen?\n\nIn allen Chat-Bubbles wird sie als gelöscht markiert.`)) return; + if (!await uiConfirm(`Datei "${name}" wirklich löschen?\n\nIn allen Chat-Bubbles wird sie als gelöscht markiert.`)) return; try { const r = await fetch('/api/files-delete', { method: 'POST', @@ -7180,10 +7244,10 @@ loadFiles(); // Server broadcastet file_deleted Event → markFileDeletedInChat() updated bubbles } else { - alert('Löschen fehlgeschlagen: ' + d.error); + uiAlert('Löschen fehlgeschlagen: ' + d.error); } } catch (e) { - alert('Löschen fehlgeschlagen: ' + e.message); + uiAlert('Löschen fehlgeschlagen: ' + e.message); } } @@ -7354,17 +7418,17 @@ listEl.innerHTML = `
Fehler beim Laden: ${_ofmt(e.message)}
`; } } - function openOAuthCustomDialog() { - const name = (prompt('Service-Name (z.B. dropbox, discord) — a-z 0-9 _ -:') || '').trim().toLowerCase(); + async function openOAuthCustomDialog() { + const name = (await uiPrompt('Service-Name (z.B. dropbox, discord) — a-z 0-9 _ -:') || '').trim().toLowerCase(); if (!name || !/^[a-z0-9_-]+$/.test(name)) { - if (name) alert('Ungueltiger Name. Erlaubt: a-z 0-9 _ -'); + if (name) uiAlert('Ungueltiger Name. Erlaubt: a-z 0-9 _ -'); return; } - const authUrl = (prompt(`auth_url fuer ${name}:`, 'https://') || '').trim(); + const authUrl = (await uiPrompt(`auth_url fuer ${name}:`, 'https://') || '').trim(); if (!authUrl) return; - const tokenUrl = (prompt(`token_url fuer ${name}:`, 'https://') || '').trim(); + const tokenUrl = (await uiPrompt(`token_url fuer ${name}:`, 'https://') || '').trim(); if (!tokenUrl) return; - const scopesRaw = (prompt(`scopes (space-separated, optional):`, '') || '').trim(); + const scopesRaw = (await uiPrompt(`scopes (space-separated, optional):`, '') || '').trim(); const scopes = scopesRaw ? scopesRaw.split(/\s+/).filter(Boolean) : undefined; fetch('/api/brain/oauth/apps', { method: 'POST', @@ -7373,19 +7437,19 @@ }) .then(r => r.ok ? r.json() : r.text().then(t => Promise.reject(new Error(t)))) .then(() => loadOAuthServices()) - .catch(e => alert('Custom-Service anlegen fehlgeschlagen: ' + e.message)); + .catch(e => uiAlert('Custom-Service anlegen fehlgeschlagen: ' + e.message)); } async function deleteOAuthApp(service) { - if (!confirm(`Service "${service}" komplett entfernen? client_id/secret + Token werden geloescht.`)) return; + if (!await uiConfirm(`Service "${service}" komplett entfernen? client_id/secret + Token werden geloescht.`)) return; try { const r = await fetch('/api/brain/oauth/apps/' + encodeURIComponent(service), { method: 'DELETE' }); if (!r.ok) { - alert('Loeschen fehlgeschlagen: ' + (await r.text())); + uiAlert('Loeschen fehlgeschlagen: ' + (await r.text())); return; } loadOAuthServices(); } catch (e) { - alert('Loeschen fehlgeschlagen: ' + e.message); + uiAlert('Loeschen fehlgeschlagen: ' + e.message); } } async function saveOAuthApp(service) { @@ -7395,7 +7459,7 @@ const tokenUrl = (document.getElementById('oauth-tok-' + service)?.value || '').trim(); const scopesRaw = (document.getElementById('oauth-scopes-' + service)?.value || '').trim(); if (!cid) { - alert('client_id darf nicht leer sein.'); + uiAlert('client_id darf nicht leer sein.'); return; } const body = { service, client_id: cid, client_secret: sec }; @@ -7410,12 +7474,12 @@ }); if (!r.ok) { const t = await r.text(); - alert('Speichern fehlgeschlagen: ' + t); + uiAlert('Speichern fehlgeschlagen: ' + t); return; } loadOAuthServices(); } catch (e) { - alert('Speichern fehlgeschlagen: ' + e.message); + uiAlert('Speichern fehlgeschlagen: ' + e.message); } } async function authorizeOAuth(service) { @@ -7427,7 +7491,7 @@ }); if (!r.ok) { const t = await r.text(); - alert('Authorize fehlgeschlagen: ' + t); + uiAlert('Authorize fehlgeschlagen: ' + t); return; } const data = await r.json(); @@ -7436,39 +7500,39 @@ // Status nach ein paar Sekunden refreshen — Provider redirect → RVS → Brain setTimeout(loadOAuthServices, 8000); } catch (e) { - alert('Authorize fehlgeschlagen: ' + e.message); + uiAlert('Authorize fehlgeschlagen: ' + e.message); } } async function revokeOAuth(service) { - if (!confirm(`Token fuer ${service} wirklich loeschen? ARIA muss danach neu autorisiert werden.`)) return; + if (!await uiConfirm(`Token fuer ${service} wirklich loeschen? ARIA muss danach neu autorisiert werden.`)) return; try { const r = await fetch('/api/brain/oauth/' + service + '/revoke', { method: 'POST' }); if (!r.ok) { const t = await r.text(); - alert('Revoke fehlgeschlagen: ' + t); + uiAlert('Revoke fehlgeschlagen: ' + t); return; } loadOAuthServices(); } catch (e) { - alert('Revoke fehlgeschlagen: ' + e.message); + uiAlert('Revoke fehlgeschlagen: ' + e.message); } } async function distillNow() { - if (!confirm('Destillat manuell auslösen?\n\nDie ältesten Turns werden zu fact-Memories verdichtet — kostet einen Claude-Call.')) return; + if (!await uiConfirm('Destillat manuell auslösen?\n\nDie ältesten Turns werden zu fact-Memories verdichtet — kostet einen Claude-Call.')) return; try { const r = await fetch('/api/brain/conversation/distill', { method: 'POST' }); const d = await r.json(); - alert(`Destillat: ${d.distilled || 0} Facts geschrieben, ${d.removed_turns || 0} Turns entfernt.${d.error ? '\nFehler: ' + d.error : ''}`); + uiAlert(`Destillat: ${d.distilled || 0} Facts geschrieben, ${d.removed_turns || 0} Turns entfernt.${d.error ? '\nFehler: ' + d.error : ''}`); loadBrainStatus(); loadBrainMemoryList(); } catch (e) { - alert('Destillat fehlgeschlagen: ' + e.message); + uiAlert('Destillat fehlgeschlagen: ' + e.message); } } async function resetConversation() { - if (!confirm('Konversation komplett zurücksetzen?\n\n• ARIAs Rolling-Window (Brain) wird geleert — sie "vergisst" die letzten Turns\n• Chat-Anzeige in der Diagnostic wird geleert\n\nDestillierte Facts + andere Memories bleiben in der Vector-DB.')) return; + if (!await uiConfirm('Konversation komplett zurücksetzen?\n\n• ARIAs Rolling-Window (Brain) wird geleert — sie "vergisst" die letzten Turns\n• Chat-Anzeige in der Diagnostic wird geleert\n\nDestillierte Facts + andere Memories bleiben in der Vector-DB.')) return; try { // Beides parallel — Brain Window + Diagnostic chat_backup const [brainR, histR] = await Promise.all([ @@ -7484,10 +7548,10 @@ if (fsBox) fsBox.innerHTML = ''; loadBrainStatus(); } else { - alert(`Reset teilweise fehlgeschlagen — Brain: ${brainOk ? 'OK' : 'fail'}, History: ${histOk ? 'OK' : 'fail'}`); + uiAlert(`Reset teilweise fehlgeschlagen — Brain: ${brainOk ? 'OK' : 'fail'}, History: ${histOk ? 'OK' : 'fail'}`); } } catch (e) { - alert('Reset fehlgeschlagen: ' + e.message); + uiAlert('Reset fehlgeschlagen: ' + e.message); } } @@ -8142,14 +8206,14 @@ const win = window.open('', '_blank'); if (!win) { - alert('Popup blockiert — bitte Popups für Diagnostic erlauben und nochmal klicken.'); + uiAlert('Popup blockiert — bitte Popups für Diagnostic erlauben und nochmal klicken.'); return; } win.document.open(); win.document.write(html); win.document.close(); } catch (e) { - alert('Druckansicht konnte nicht geladen werden: ' + e.message); + uiAlert('Druckansicht konnte nicht geladen werden: ' + e.message); } } @@ -8282,7 +8346,7 @@ const id = document.getElementById('memory-edit-id').value; if (!id) return; const name = decodeURIComponent(filenameEncoded); - if (!confirm(`Anhang "${name}" wirklich loeschen?`)) return; + if (!await uiConfirm(`Anhang "${name}" wirklich loeschen?`)) return; try { const r = await fetch(`/api/brain/memory/${encodeURIComponent(id)}/attachments/${filenameEncoded}`, { method: 'DELETE', @@ -8294,7 +8358,7 @@ const status = document.getElementById('memory-attachment-status'); if (status) status.textContent = `✓ "${name}" geloescht`; } catch (e) { - alert('Loeschen fehlgeschlagen: ' + e.message); + uiAlert('Loeschen fehlgeschlagen: ' + e.message); } } @@ -8348,7 +8412,7 @@ async function deleteMemory(id) { const m = brainMemoryCache[id]; const label = m ? `"${m.title}" (${m.type})` : id; - if (!confirm(`Memory ${label} wirklich löschen?`)) return; + if (!await uiConfirm(`Memory ${label} wirklich löschen?`)) return; try { const r = await fetch('/api/brain/memory/delete/' + encodeURIComponent(id), { method: 'DELETE' }); if (!r.ok) throw new Error('HTTP ' + r.status); @@ -8366,7 +8430,7 @@ else await loadBrainMemoryList(); loadBrainStatus(); } catch (e) { - alert('Löschen fehlgeschlagen: ' + e.message); + uiAlert('Löschen fehlgeschlagen: ' + e.message); } } @@ -8667,7 +8731,7 @@ // ── Migration aus brain-import/ ──────────────────────── async function runMigration() { const status = document.getElementById('brain-import-files-status'); - if (!confirm('Migration starten?\n\nParst die Markdown-Dateien aus brain-import/ und schreibt sie als pinned Memory-Punkte in die DB. Bei Re-Run werden nur die Migration-Punkte ersetzt — eigene Memories bleiben.')) return; + if (!await uiConfirm('Migration starten?\n\nParst die Markdown-Dateien aus brain-import/ und schreibt sie als pinned Memory-Punkte in die DB. Bei Re-Run werden nur die Migration-Punkte ersetzt — eigene Memories bleiben.')) return; if (status) status.innerHTML = '⏳ Migriere...'; try { const r = await fetch('/api/brain/memory/migrate', { method: 'POST' }); @@ -8741,7 +8805,7 @@ const scopeInfo = bScope === 'system' ? 'Nur die aktuell pinned SYSTEM-Regeln werden ersetzt — Persönliches bleibt.' : bScope === 'personal' ? 'Nur die aktuell pinned PERSÖNLICHEN Memories werden ersetzt — System-Regeln bleiben.' : 'ALLE aktuell pinned Memories werden überschrieben.'; - if (!confirm(`Bootstrap importieren? (scope: ${bScope})\n\n${bundle.memories.length} pinned Memories aus "${file.name}".\n\n${scopeInfo} Cold Memory bleibt unverändert.`)) { + if (!await uiConfirm(`Bootstrap importieren? (scope: ${bScope})\n\n${bundle.memories.length} pinned Memories aus "${file.name}".\n\n${scopeInfo} Cold Memory bleibt unverändert.`)) { event.target.value = ''; return; } @@ -8770,7 +8834,7 @@ const file = event.target.files[0]; if (!file) return; const statusEl = document.getElementById('brain-import-status'); - if (!confirm(`Wirklich importieren? ALLE aktuellen Memories + Skills gehen verloren.\nDatei: ${file.name} (${(file.size/1024/1024).toFixed(1)} MB)`)) { + if (!await uiConfirm(`Wirklich importieren? ALLE aktuellen Memories + Skills gehen verloren.\nDatei: ${file.name} (${(file.size/1024/1024).toFixed(1)} MB)`)) { event.target.value = ''; return; } @@ -8962,7 +9026,7 @@ } async function unquarantineTurn(ts) { - if (!confirm('Turn wieder in den Verlauf aufnehmen?\n\nWar er der Auslöser, ' + if (!await uiConfirm('Turn wieder in den Verlauf aufnehmen?\n\nWar er der Auslöser, ' + 'blockt Anthropics Filter beim nächsten Mal erneut — dann landet ' + 'er automatisch wieder hier.')) return; try { @@ -8972,11 +9036,11 @@ }); if (!r.ok) throw new Error('HTTP ' + r.status); loadSafeguard(); - } catch (e) { alert('Zurückholen fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Zurückholen fehlgeschlagen: ' + e.message); } } async function clearSafeguard() { - if (!confirm('Vorfallsliste leeren?\n\nDanach läuft wieder das Hauptmodell — ' + if (!await uiConfirm('Vorfallsliste leeren?\n\nDanach läuft wieder das Hauptmodell — ' + 'auch in Projekten, die gerade aufs Ausweich-Modell ausweichen. ' + 'Zurückgeholte Turns bleiben zurückgeholt.')) return; try { @@ -8986,7 +9050,7 @@ }); if (!r.ok) throw new Error('HTTP ' + r.status); loadSafeguard(); - } catch (e) { alert('Leeren fehlgeschlagen: ' + e.message); } + } catch (e) { uiAlert('Leeren fehlgeschlagen: ' + e.message); } } // ── Datenschutz-Schalter ────────────────────────────────────────── @@ -9681,11 +9745,11 @@ try { const j = await (await fetch('/api/llm-catalog/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ hfRepo: repo }) })).json(); - if (!j.ok) { btn.disabled = false; btn.textContent = 'In Katalog'; alert('Nicht übernommen: ' + (j.error || 'Fehler')); return; } + if (!j.ok) { btn.disabled = false; btn.textContent = 'In Katalog'; uiAlert('Nicht übernommen: ' + (j.error || 'Fehler')); return; } _llmCatalog = j.models || _llmCatalog; renderLlmCatalog(); btn.outerHTML = 'im Katalog ✓ — dort „Laden"'; - } catch (e) { btn.disabled = false; btn.textContent = 'In Katalog'; alert(e.message); } + } catch (e) { btn.disabled = false; btn.textContent = 'In Katalog'; uiAlert(e.message); } } // ── Belege: was hat ARIA fuer eine Antwort wirklich ausgefuehrt? ── @@ -9735,15 +9799,15 @@ // laden. Programme bekommen den neuen Wert automatisch (ctx in // llm_cluster_models). Passt es nicht in den Grafikspeicher, setzt die Box // die Karte auf den alten Stand zurueck. - function onLlmCardCtx(sel) { + async function onLlmCardCtx(sel) { const target = sel.dataset.box, gpu = sel.dataset.gpu, model = sel.dataset.model; sel.blur(); // Fokus weg → aufgeschobenes Neuzeichnen holt den Stand nach const cur = Number(sel.dataset.current || 0), next = Number(sel.value); if (next === cur) return; const m = _llmCatalog.find(x => x.id === model); - if (!m || !m.hfRepo) { alert(`${model} steht nicht im Modell-Katalog — Kontext so nicht änderbar.`); sel.value = cur; return; } + if (!m || !m.hfRepo) { uiAlert(`${model} steht nicht im Modell-Katalog — Kontext so nicht änderbar.`); sel.value = cur; return; } const more = next > cur; - if (!confirm(`${model} auf ${_boxName(target)} · Karte ${gpu} mit ${next} statt ${cur || '?'} Tokens Kontext neu laden?\n\n` + if (!await uiConfirm(`${model} auf ${_boxName(target)} · Karte ${gpu} mit ${next} statt ${cur || '?'} Tokens Kontext neu laden?\n\n` + (more ? 'Mehr Kontext braucht mehr Grafikspeicher. Passt es nicht, setzt die Box die Karte von selbst auf den alten Stand zurück.\n\n' : '') + 'Die Box startet ihre Modelle dabei kurz neu. Programme bekommen den neuen Wert automatisch.')) { sel.value = cur; return; } const sl = (workers.find(w => w.instanceId === target)?.slots || []).find(x => String(x.gpu) === String(gpu)) || {}; @@ -9758,13 +9822,13 @@ const node = _boxName(target); if (next === current) return; if (!next) { - if (!confirm(`Karte ${gpu} auf ${node} leeren?\n\n${current} wird entladen; ARIA und Programme können es dann nicht mehr wählen. ` + if (!await uiConfirm(`Karte ${gpu} auf ${node} leeren?\n\n${current} wird entladen; ARIA und Programme können es dann nicht mehr wählen. ` + `Die Datei bleibt auf der Box.\n\nDie anderen Karten dieser Box starten dabei kurz neu.`)) { sel.value = current; return; } send({ action: 'llm_unload_model', targetInstance: target, gpu }); return; } const m = _llmCatalog.find(x => x.id === next); - if (!m || !m.hfRepo) { alert(`${next} steht nicht im Modell-Katalog — Herkunft unbekannt.`); sel.value = current; return; } + if (!m || !m.hfRepo) { uiAlert(`${next} steht nicht im Modell-Katalog — Herkunft unbekannt.`); sel.value = current; return; } const sl = (workers.find(w => w.instanceId === target)?.slots || []).find(x => String(x.gpu) === String(gpu)) || {}; const vramGB = sl.vramMB ? sl.vramMB / 1024 : 0; const cpuMoe = await askCpuMoe(m, sl.vramMB || 0); @@ -9772,20 +9836,20 @@ const warn = (!cpuMoe && vramGB && m.sizeGB && m.sizeGB > vramGB * 0.92) ? `\n\n⚠ ${next} ist etwa ${m.sizeGB} GB groß, die Karte hat ${vramGB.toFixed(0)} GB — wahrscheinlich passt es nicht.` : ''; const repl = current ? `\n\nErsetzt ${current} — Programme, die es nutzen, verlieren es.` : ''; - if (!confirm(`${next} auf ${node} · Karte ${gpu} laden?${repl}${warn}\n\nDie anderen Karten dieser Box starten dabei kurz neu.`)) { sel.value = current; return; } + if (!await uiConfirm(`${next} auf ${node} · Karte ${gpu} laden?${repl}${warn}\n\nDie anderen Karten dieser Box starten dabei kurz neu.`)) { sel.value = current; return; } send({ action: 'llm_provision_model', targetInstance: target, gpu, key: m.id, hfRepo: m.hfRepo, quant: m.quant, ctx: m.ctx, cpuMoe }); openLlmDownloads(); } // MoE: Experten zwischen Karte und Arbeitsspeicher — gleiches Modell neu laden - function onLlmCardMoe(sel) { + async function onLlmCardMoe(sel) { const target = sel.dataset.box, gpu = sel.dataset.gpu, model = sel.dataset.model; sel.blur(); // Fokus weg → aufgeschobenes Neuzeichnen holt den Stand nach const cur = Number(sel.dataset.current || 0), next = Number(sel.value); const m = _llmCatalog.find(x => x.id === model); - if (!m || !m.hfRepo) { alert(`${model} steht nicht im Modell-Katalog.`); sel.value = String(cur); return; } + if (!m || !m.hfRepo) { uiAlert(`${model} steht nicht im Modell-Katalog.`); sel.value = String(cur); return; } const txt = { '0': 'alle Experten auf die Karte', '-2': 'automatisch aufteilen (so viele wie passen auf die Karte)', '-1': 'alle Experten in den Arbeitsspeicher' }; - if (!confirm(`${model} neu laden: ${txt[String(next)] || next}?\n\nDie Box startet ihre Modelle dabei kurz neu.`)) { sel.value = String(cur); return; } + if (!await uiConfirm(`${model} neu laden: ${txt[String(next)] || next}?\n\nDie Box startet ihre Modelle dabei kurz neu.`)) { sel.value = String(cur); return; } const sl = (workers.find(w => w.instanceId === target)?.slots || []).find(x => String(x.gpu) === String(gpu)) || {}; send({ action: 'llm_provision_model', targetInstance: target, gpu, key: m.id, hfRepo: m.hfRepo, quant: m.quant, ctx: sl.ctx || m.ctx, cpuMoe: next }); openLlmDownloads(); @@ -9817,8 +9881,8 @@ }).join(''); return `
${rows || 'Keine LLM-Karte konfiguriert (LLM_GPUS in der .env der Box).'}
`; } - function unloadLlmCard(instanceId, gpu, model) { - if (!confirm(`Karte ${gpu} leeren?\n\n${model} wird entladen; Clients können es danach nicht mehr wählen. ` + async function unloadLlmCard(instanceId, gpu, model) { + if (!await uiConfirm(`Karte ${gpu} leeren?\n\n${model} wird entladen; Clients können es danach nicht mehr wählen. ` + `Die Datei bleibt auf der Box — erneutes Laden geht ohne Download.\n\n` + `Die anderen Karten dieser Box starten dabei kurz neu (wenige Sekunden).`)) return; send({ action: 'llm_unload_model', targetInstance: instanceId, gpu }); @@ -9874,18 +9938,18 @@ }); } - function deleteLlmFiles(instanceId, i) { + async function deleteLlmFiles(instanceId, i) { const m = _llmCatalog[i]; if (!m) return; const b = onlineLlmBoxes().find(w => w.instanceId === instanceId); const d = ((b && b.disk && b.disk.models) || []).find(x => x.repo === m.hfRepo); - if (!confirm(`${m.id} von ${(b && b.node) || instanceId} löschen?\n\n` + if (!await uiConfirm(`${m.id} von ${(b && b.node) || instanceId} löschen?\n\n` + `Die heruntergeladene Datei (${d ? _fmtBytes(d.bytes) : '?'}, Repo ${m.hfRepo}) wird von der Box entfernt. ` + `Wer es wieder will, muss es neu herunterladen.`)) return; send({ action: 'llm_remove_model', targetInstance: instanceId, key: m.id, hfRepo: m.hfRepo, deleteFiles: true }); } - function cancelLlmJob(instanceId, key) { - if (!confirm(`Laden von ${key} abbrechen?\n\nDer halbe Download wird gelöscht, die Karte bleibt bzw. wird wie vorher.`)) return; + async function cancelLlmJob(instanceId, key) { + if (!await uiConfirm(`Laden von ${key} abbrechen?\n\nDer halbe Download wird gelöscht, die Karte bleibt bzw. wird wie vorher.`)) return; send({ action: 'llm_remove_model', targetInstance: instanceId, key, cancel: true }); } @@ -9896,11 +9960,11 @@ const [target, gpu] = String((sel && sel.value) || '').split('|'); if (!target) return; const tgt = llmCardTargets().find(t => t.value === sel.value); - if (tgt && tgt.slot && tgt.slot.model === m.id) { alert(`${m.id} liegt schon auf dieser Karte.`); return; } + if (tgt && tgt.slot && tgt.slot.model === m.id) { uiAlert(`${m.id} liegt schon auf dieser Karte.`); return; } const busyBox = ((tgt && tgt.box.slots) || []).find(sl => sl.model === m.id); - if (busyBox) { alert(`${m.id} liegt auf dieser Box schon auf Karte ${busyBox.gpu}. Ein Modell pro Box nur einmal.`); return; } + if (busyBox) { uiAlert(`${m.id} liegt auf dieser Box schon auf Karte ${busyBox.gpu}. Ein Modell pro Box nur einmal.`); return; } if (tgt && tgt.slot && tgt.slot.model && - !confirm(`Karte ${gpu} auf ${tgt.box.node} hält gerade ${tgt.slot.model}.\n\n` + !await uiConfirm(`Karte ${gpu} auf ${tgt.box.node} hält gerade ${tgt.slot.model}.\n\n` + `${m.id} ersetzt es. Clients, die ${tgt.slot.model} nutzen, verlieren es dann.\n` + `Die anderen Karten dieser Box starten dabei kurz neu.\n\nErsetzen?`)) return; const cpuMoe = await askCpuMoe(m, (tgt && tgt.slot && tgt.slot.vramMB) || 0); @@ -9974,10 +10038,10 @@ // den Klick, wenn der Browser Dialoge unterdrueckt hatte ("Verhindern, // dass diese Seite weitere Dialoge erstellt") → confirm()===false → es ging // NICHTS raus. Nur "aggressive" (Volumes!) fragt noch nach. - function runDiskCleanup(variant) { + async function runDiskCleanup(variant) { const aggressive = variant === 'aggressive'; if (aggressive) { - const ok = confirm('AGGRESSIV aufraeumen? Loescht zusaetzlich ungenutzte Volumes — nur wenn ALLE ARIA-Container laufen, sonst Datenverlust!'); + const ok = await uiConfirm('AGGRESSIV aufraeumen? Loescht zusaetzlich ungenutzte Volumes — nur wenn ALLE ARIA-Container laufen, sonst Datenverlust!'); if (!ok) return; } const btn = document.getElementById('disk-clean-btn'); @@ -9994,7 +10058,7 @@ btn.textContent = 'Kopiert!'; setTimeout(() => { btn.textContent = old; }, 1500); }).catch(() => { - alert('Kopieren fehlgeschlagen — Befehl: ' + cmd); + uiAlert('Kopieren fehlgeschlagen — Befehl: ' + cmd); }); }