// @ts-nocheck import { unloadAllLocalModels, probeLocalModelsLoaded } from '../utils/model-manager.js'; import { log as auditLog } from '../utils/audit-log.js'; let _dialogOpen = false; let _allowLeave = false; export function showLeaveUnloadDialog({ ollama = 0, omlx = 0, sparkle = 0 } = {}) { if (_dialogOpen) return Promise.resolve(null); _dialogOpen = true; return new Promise((resolve) => { const parts = []; if (ollama > 0) parts.push(`Ollama ${ollama} 个`); if (omlx > 0) parts.push(`oMLX ${omlx} 个`); if (sparkle > 0) parts.push(`Sparkle ${sparkle} 个`); const loadedHint = parts.length ? `当前检测到已加载: ${parts.join(' + ')}。` : '关闭后本地模型可能仍占用统一内存。'; const overlay = document.createElement('div'); overlay.className = 'dialog-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); document.body.classList.add('modal-open'); const finish = (choice) => { overlay.remove(); document.body.classList.remove('modal-open'); _dialogOpen = false; resolve(choice); }; overlay.querySelectorAll('[data-choice]').forEach(btn => { btn.addEventListener('click', () => { const c = btn.dataset.choice; if (c === 'unload') finish('unload'); else if (c === 'keep') finish('keep'); else finish(null); }); }); overlay.addEventListener('click', (e) => { if (e.target === overlay) finish(null); }); const onKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); window.removeEventListener('keydown', onKey, true); finish(null); } }; window.addEventListener('keydown', onKey, true); }); } export async function requestLeave(action = 'reload') { if (_allowLeave) return true; let probe = { loaded: false, ollama: 0, omlx: 0, sparkle: 0 }; try { probe = await probeLocalModelsLoaded(); } catch (e) {} if (!probe.loaded) { _allowLeave = true; performLeave(action); return true; } const choice = await showLeaveUnloadDialog(probe); if (choice == null) return false; if (choice === 'unload') { try { const r = await unloadAllLocalModels(); auditLog('system', `离开前卸载本地模型: Ollama ${r.ollama} / oMLX ${r.omlx} / Sparkle ${r.sparkle}`); } catch (e) { auditLog('error', `离开前卸载失败: ${e.message}`); } } else { auditLog('system', '离开时保留本地模型'); } _allowLeave = true; performLeave(action); return true; } function performLeave(action) { if (action === 'close') { window.close(); return; } location.reload(); } export async function confirmClose() { if (_allowLeave) return { allow: true, unloaded: false }; let probe = { loaded: false, ollama: 0, omlx: 0, sparkle: 0 }; try { probe = await probeLocalModelsLoaded(); } catch (e) {} if (!probe.loaded) { _allowLeave = true; return { allow: true, unloaded: false }; } const choice = await showLeaveUnloadDialog(probe); if (choice == null) return { allow: false, unloaded: false }; let unloaded = false; if (choice === 'unload') { try { await unloadAllLocalModels(); unloaded = true; auditLog('system', '窗口关闭前已卸载本地模型'); } catch (e) { auditLog('error', `窗口关闭前卸载失败: ${e.message}`); } } else { auditLog('system', '窗口关闭时保留本地模型'); } _allowLeave = true; return { allow: true, unloaded }; } export function installLeaveGuards() { window.addEventListener('keydown', (e) => { const isRefresh = e.key === 'F5' || ((e.metaKey || e.ctrlKey) && (e.key === 'r' || e.key === 'R')); if (!isRefresh) return; if (_dialogOpen || _allowLeave) return; e.preventDefault(); e.stopPropagation(); requestLeave('reload'); }, true); window.addEventListener('beforeunload', (e) => { if (_allowLeave || _dialogOpen) return; try { const src = localStorage.getItem('ccsparkle_model_source') || ''; if (src === 'ollama' || src === 'omlx' || src === 'sparkle') { e.preventDefault(); e.returnValue = ''; } } catch (_) {} }); window.ccSparkleApp = Object.assign(window.ccSparkleApp || {}, { confirmClose, requestLeave, showLeaveUnloadDialog, }); } function escapeHtml(s) { if (s == null) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); }