// @ts-nocheck import { loadOllamaConfig, loadOmlxConfig, loadSparkleConfig, loadModelSource, CONFIG, DEFAULT_OLLAMA, DEFAULT_OMLX, DEFAULT_SPARKLE } from './config.js'; export async function probeOllama(timeoutMs = 1500) { const cfg = loadOllamaConfig(); const base = String(cfg.baseUrl || DEFAULT_OLLAMA.baseUrl).replace(/\/+$/, ''); try { const r = await fetch(`${base}/api/version`, { signal: AbortSignal.timeout(timeoutMs) }); if (!r.ok) return { ok: false, baseUrl: base, model: cfg.model || '', error: `HTTP ${r.status}` }; return { ok: true, baseUrl: base, model: cfg.model || '' }; } catch (e) { return { ok: false, baseUrl: base, model: cfg.model || '', error: e.message || String(e) }; } } export async function probeOmlx(timeoutMs = 1500) { const cfg = loadOmlxConfig(); const base = String(cfg.baseUrl || DEFAULT_OMLX.baseUrl).replace(/\/+$/, ''); if (!String(cfg.apiKey || '').trim()) { return { ok: false, baseUrl: base, model: cfg.model || '', reason: 'no_key' }; } try { const r = await fetch(`${base}/v1/models`, { headers: { Authorization: `Bearer ${cfg.apiKey}` }, signal: AbortSignal.timeout(timeoutMs), }); if (!r.ok) return { ok: false, baseUrl: base, model: cfg.model || '', error: `HTTP ${r.status}` }; return { ok: true, baseUrl: base, model: cfg.model || '' }; } catch (e) { return { ok: false, baseUrl: base, model: cfg.model || '', error: e.message || String(e) }; } } export async function probeSparkle(timeoutMs = 8000) { const cfg = loadSparkleConfig(); const base = String(cfg.baseUrl || DEFAULT_SPARKLE.baseUrl).replace(/\/+$/, ''); try { const r = await fetch(`${base}/v1/models`, { headers: { Authorization: `Bearer ${cfg.apiKey || 'sparkle'}` }, signal: AbortSignal.timeout(timeoutMs), }); if (!r.ok) return { ok: false, baseUrl: base, model: cfg.model || '', error: `HTTP ${r.status}` }; return { ok: true, baseUrl: base, model: cfg.model || '' }; } catch (e) { return { ok: false, baseUrl: base, model: cfg.model || '', error: e.message || String(e) }; } } export function localLlmUnavailableMessage(source, probe = {}) { if (source === 'ollama') { const base = probe.baseUrl || DEFAULT_OLLAMA.baseUrl; return `未检测到 Ollama(${base})。请先安装并启动 Ollama,或在「配」中切换到云端 API / oMLX / Sparkle。`; } if (source === 'omlx') { if (probe.reason === 'no_key') { return '未配置 oMLX API Key。请在「配」中填写,或切换到云端 API / Ollama / Sparkle。'; } const base = probe.baseUrl || DEFAULT_OMLX.baseUrl; return `未检测到 oMLX(${base})。请先启动 oMLX,或在「配」中切换到云端 API / Ollama / Sparkle。`; } if (source === 'sparkle') { const base = probe.baseUrl || DEFAULT_SPARKLE.baseUrl; return `未检测到 Sparkle(${base})。请先启动 Sparkle 推理服务,或在「配」中切换到云端 API / Ollama / oMLX。`; } return '本地模型服务不可用,请检查配置。'; } export async function ensureLocalSourceReady(source) { const src = source || loadModelSource(); if (src === 'ollama') { const p = await probeOllama(); if (!p.ok) throw new Error(localLlmUnavailableMessage('ollama', p)); const cfg = loadOllamaConfig(); if (!String(cfg.model || '').trim()) { throw new Error('未选择 Ollama 模型。请在「配」中刷新模型列表并选择,或切换到云端 API。'); } return p; } if (src === 'omlx') { const p = await probeOmlx(); if (!p.ok) throw new Error(localLlmUnavailableMessage('omlx', p)); const cfg = loadOmlxConfig(); if (!String(cfg.model || '').trim()) { throw new Error('未选择 oMLX 模型。请在「配」中刷新模型列表并选择,或切换到云端 API。'); } return p; } if (src === 'sparkle') { const p = await probeSparkle(); if (!p.ok) throw new Error(localLlmUnavailableMessage('sparkle', p)); const cfg = loadSparkleConfig(); if (!String(cfg.model || '').trim()) { throw new Error('未选择 Sparkle 模型。请在「配」中刷新模型列表并选择,或切换到云端 API。'); } return p; } return { ok: true }; } export function friendlyLocalFetchError(source, err) { const msg = String(err?.message || err || ''); if (/abort|AbortError/i.test(err?.name || '') || /abort/i.test(msg)) { return err; } if (/Failed to fetch|NetworkError|Load failed|ECONNREFUSED|ENOTFOUND|fetch failed|Network request failed/i.test(msg) || err?.name === 'TypeError') { return new Error(localLlmUnavailableMessage(source, { baseUrl: source === 'omlx' ? (loadOmlxConfig().baseUrl || DEFAULT_OMLX.baseUrl) : source === 'sparkle' ? (loadSparkleConfig().baseUrl || DEFAULT_SPARKLE.baseUrl) : (loadOllamaConfig().baseUrl || DEFAULT_OLLAMA.baseUrl), })); } return err instanceof Error ? err : new Error(msg); } export async function unloadOllamaModels() { const cfg = loadOllamaConfig(); let unloaded = 0; try { const r = await fetch(`${cfg.baseUrl}/api/ps`, { signal: AbortSignal.timeout(1500) }); if (r.ok) { const data = await r.json(); const models = data.models || []; for (const m of models) { await fetch(`${cfg.baseUrl}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: m.name, keep_alive: 0 }), keepalive: true, }); unloaded++; } } } catch (e) {} try { await fetch(`${CONFIG.ZHONGTAI_BASE}/admin/kill-ollama-runner`, { method: 'POST', keepalive: true, signal: AbortSignal.timeout(2000), }); } catch (e) {} return unloaded; } export async function unloadOmlxModels() { const cfg = loadOmlxConfig(); if (!cfg.apiKey) return 0; const base = cfg.baseUrl.replace(/\/+$/, ''); try { const r = await fetch(`${base}/v1/models`, { headers: { 'Authorization': `Bearer ${cfg.apiKey}` }, }); if (!r.ok) return 0; const data = await r.json(); const models = data.data || []; let unloaded = 0; for (const m of models) { const endpoints = [ `${base}/admin/api/models/${encodeURIComponent(m.id)}/unload`, `${base}/v1/models/${encodeURIComponent(m.id)}/unload`, ]; for (const url of endpoints) { try { const resp = await fetch(url, { method: 'POST', headers: { 'Authorization': `Bearer ${cfg.apiKey}` }, keepalive: true, }); if (resp.ok) { unloaded++; break; } } catch (e) {} } } return unloaded; } catch (e) { return 0; } } export async function unloadSparkleModels() { try { const r = await fetch(`${CONFIG.ZHONGTAI_BASE}/sparkle-engine/stop`, { method: 'POST', keepalive: true, signal: AbortSignal.timeout(20000), }); if (!r.ok) return 0; const data = await r.json().catch(() => null); return data && data.ok ? 1 : 0; } catch (e) { return 0; } } export async function unloadAllLocalModels() { const ollamaCount = await unloadOllamaModels(); const omlxCount = await unloadOmlxModels(); const sparkleCount = await unloadSparkleModels(); return { ollama: ollamaCount, omlx: omlxCount, sparkle: sparkleCount }; } export async function probeLocalModelsLoaded() { let ollama = 0; let omlx = 0; let sparkle = 0; try { const cfg = loadOllamaConfig(); const base = (cfg.baseUrl || '').replace(/\/+$/, ''); if (base) { const r = await fetch(`${base}/api/ps`, { signal: AbortSignal.timeout(1500) }); if (r.ok) { const data = await r.json(); ollama = (data.models || []).length; } } } catch (e) {} try { const cfg = loadOmlxConfig(); const base = (cfg.baseUrl || '').replace(/\/+$/, ''); if (base && cfg.apiKey) { const r = await fetch(`${base}/v1/models`, { headers: { 'Authorization': `Bearer ${cfg.apiKey}` }, signal: AbortSignal.timeout(1500), }); if (r.ok) { const data = await r.json(); const models = data.data || []; omlx = models.filter(m => m.loaded || m.status === 'loaded' || m.running).length; if (omlx === 0 && models.some(m => 'loaded' in m || 'status' in m || 'running' in m)) { } else if (omlx === 0 && models.length > 0 && !models.some(m => ('loaded' in m) || ('status' in m) || ('running' in m))) { omlx = 0; } } } } catch (e) {} try { const cfg = loadSparkleConfig(); const base = (cfg.baseUrl || '').replace(/\/+$/, ''); if (base) { const r = await fetch(`${base}/v1/models`, { headers: { 'Authorization': `Bearer ${cfg.apiKey || 'sparkle'}` }, signal: AbortSignal.timeout(1500), }); if (r.ok) { const data = await r.json(); const models = data.data || []; sparkle = models.filter(m => m.loaded || m.status === 'loaded' || m.running).length; } } } catch (e) {} return { loaded: ollama + omlx + sparkle > 0, ollama, omlx, sparkle }; }