/** * 画个标 · AI 客户端:DeepSeek API 接入 * 职责:API Key 管理(localStorage)、对话调用、错误分类、连接测试 */ const HGB_AI = (function () { 'use strict'; const LS_KEY = 'hgb_ai_key'; const LS_MODEL = 'hgb_ai_model'; const ENDPOINT = 'https://api.deepseek.com/chat/completions'; const MODELS = ['deepseek-v4-flash', 'deepseek-chat', 'deepseek-v4-pro']; /** * 不内置任何 Key:Key 仅由使用者在「AI 设置」中填写,存本机 localStorage,只发给 DeepSeek。 * 因此源码、仓库与部署产物里都不含 Key,公网也无法被他人读取(BYOK 模式)。 */ const DEFAULT_KEY = ''; /* ---------- Key 管理 ---------- */ function getKey() { try { const v = localStorage.getItem(LS_KEY); // 未配置时返回空串,由调用方提示「未配置 API Key」并降级为内置引擎 if (v) return v; return DEFAULT_KEY; } catch (e) { return ''; } } function setKey(key) { try { localStorage.setItem(LS_KEY, (key || '').trim()); } catch (e) { /* ignore */ } } function hasKey() { return !!getKey(); } function getModel() { try { return localStorage.getItem(LS_MODEL) || 'deepseek-v4-flash'; } catch (e) { return 'deepseek-v4-flash'; } } function setModel(m) { try { localStorage.setItem(LS_MODEL, m); } catch (e) { /* ignore */ } } function clearKey() { // 清除本机保存的 Key(已无内置 Key 可回落,清除后即降级为内置引擎) try { localStorage.removeItem(LS_KEY); } catch (e) { /* ignore */ } } /* ---------- 调用 ---------- */ /** * 调用 DeepSeek 对话补全 * @param {Array} messages [{role:'system'|'user', content}] * @param {object} opts { json?: boolean, maxTokens?: number, temperature?: number } * @returns {Promise} 返回内容文本 */ async function chat(messages, opts) { const key = getKey(); if (!key) { const err = new Error('未配置 API Key,请先在「AI 设置」中填写'); err.code = 'NO_KEY'; throw err; } opts = opts || {}; const body = { model: getModel(), messages, max_tokens: opts.maxTokens || 4096, temperature: opts.temperature != null ? opts.temperature : 0.5, stream: false }; if (opts.json) { body.response_format = { type: 'json_object' }; } let res; try { res = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + key }, body: JSON.stringify(body) }); } catch (e) { const err = new Error('网络请求失败:' + e.message + '(请检查网络/代理)'); err.code = 'NETWORK'; throw err; } if (!res.ok) { let detail = ''; try { detail = (await res.json()).error?.message || ''; } catch (e) { /* ignore */ } const err = new Error(detail || 'HTTP ' + res.status); if (res.status === 401) err.code = 'AUTH'; else if (res.status === 402) err.code = 'BALANCE'; else if (res.status === 429) err.code = 'RATE_LIMIT'; else err.code = 'API'; err.status = res.status; throw err; } const data = await res.json(); const content = data.choices?.[0]?.message?.content; if (!content) { const err = new Error('AI 返回内容为空'); err.code = 'EMPTY'; throw err; } return content; } /** 测试连接:发送最小请求验证 Key 是否有效 */ async function testConnection() { const content = await chat([ { role: 'user', content: '回复两个字:正常' } ], { maxTokens: 16, temperature: 0 }); return content; } return { getKey, setKey, hasKey, getModel, setModel, clearKey, chat, testConnection, MODELS }; })();