feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能

This commit is contained in:
sgp
2026-10-03 21:44:18 +08:00
commit 9e222877ea
497 changed files with 43611 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
/**
* 画个标 · 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<string>} 返回内容文本
*/
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 };
})();