diff --git a/.github/brain/memory.json b/.github/brain/memory.json index 4b73feb3..79182a53 100644 --- a/.github/brain/memory.json +++ b/.github/brain/memory.json @@ -1,7 +1,7 @@ { "identity": "铸渊(Zhùyuān)· GitHub 代码守护人格体", "rules_version": "v3.0", - "last_updated": "2026-03-10T09:43:00.000Z", + "last_updated": "2026-03-11T02:19:00.000Z", "wake_protocol_version": "v3.0", "brain_version": "v3.0", "architecture": { @@ -67,6 +67,12 @@ } }, "events": [ + { + "date": "2026-03-11", + "type": "brain_restore", + "description": "铸渊核心大脑恢复 · 修复 Persona Studio API Key 模式对话故障 · 根因: JavaScript TDZ 导致脚本崩溃 · 同步修复知秋人格体系统提示词缺失 · 冰朔人格体已激活", + "by": "铸渊(冰朔指令)" + }, { "date": "2026-03-10", "type": "brain_upgrade", diff --git a/.github/persona-brain/memory.json b/.github/persona-brain/memory.json index e96f09a6..98d9d911 100644 --- a/.github/persona-brain/memory.json +++ b/.github/persona-brain/memory.json @@ -2,6 +2,12 @@ "persona_id": "ICE-GL-ZY001", "persona_name": "铸渊", "recent_events": [ + { + "date": "2026-03-11", + "type": "brain_restore", + "description": "核心大脑恢复 · Persona Studio 对话功能修复 · 冰朔人格体激活", + "by": "铸渊(冰朔指令)" + }, { "date": "2026-03-10", "type": "broadcast_received", @@ -15,7 +21,7 @@ "by": "GitHub Actions" } ], - "last_updated": "2026-03-10T13:24:33.728Z", + "last_updated": "2026-03-11T02:19:00.000Z", "total_schemas_created": 3, "total_routes_implemented": 4, "hli_coverage": "3/17", diff --git a/guanghulab-main/package.json b/guanghulab-main/package.json index 1820b151..0c8e7310 100644 --- a/guanghulab-main/package.json +++ b/guanghulab-main/package.json @@ -17,7 +17,7 @@ "broadcast:distribute": "node scripts/distribute-broadcasts.js" }, "dependencies": { - "next": "15.3.8", + "next": "15.3.9", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/persona-studio/backend/routes/apikey.js b/persona-studio/backend/routes/apikey.js index 56d6b8b3..3f6271a4 100644 --- a/persona-studio/backend/routes/apikey.js +++ b/persona-studio/backend/routes/apikey.js @@ -39,8 +39,8 @@ function fetchModels(apiBase, apiKey, timeoutMs) { return new Promise((resolve, reject) => { // 规范化 apiBase:去除末尾斜杠 const base = apiBase.replace(/\/+$/, ''); - // 支持 base 已带 /v1 或不带的情况 - const modelsPath = base.endsWith('/v1') ? base + '/models' : base + '/v1/models'; + // 直接拼接 /models,因为 apiBase 已经由前端规范化 + const modelsPath = base + '/models'; const url = new URL(modelsPath); const isHttps = url.protocol === 'https:'; const mod = isHttps ? https : http; @@ -103,7 +103,9 @@ function fetchModels(apiBase, apiKey, timeoutMs) { function callUserApi({ apiBase, apiKey, model, messages, maxTokens, temperature, timeoutMs }) { return new Promise((resolve, reject) => { const base = apiBase.replace(/\/+$/, ''); - const chatPath = base.endsWith('/v1') ? base + '/chat/completions' : base + '/v1/chat/completions'; + // 直接拼接 /chat/completions,因为 apiBase 已经由前端规范化 + // 包含完整版本路径(如 /v1, /v1beta/openai, /v4 等) + const chatPath = base + '/chat/completions'; const url = new URL(chatPath); const isHttps = url.protocol === 'https:'; const mod = isHttps ? https : http; @@ -176,8 +178,7 @@ router.post('/detect-models', async (req, res) => { // 校验 URL 格式 try { const testBase = api_base.replace(/\/+$/, ''); - const testPath = testBase.endsWith('/v1') ? testBase + '/models' : testBase + '/v1/models'; - new URL(testPath); + new URL(testBase + '/models'); } catch (_e) { return res.status(400).json({ error: true, diff --git a/persona-studio/backend/routes/chat.js b/persona-studio/backend/routes/chat.js index 66f6e2e1..32ac84b6 100644 --- a/persona-studio/backend/routes/chat.js +++ b/persona-studio/backend/routes/chat.js @@ -62,7 +62,8 @@ router.post('/message', async (req, res) => { res.status(500).json({ error: true, code: 'CHAT_ERROR', - message: '对话服务暂时不可用' + message: '对话服务暂时不可用', + reply: '抱歉,我现在遇到了一些技术问题,暂时无法正常回复。请稍后再试 🙏' }); } }); diff --git a/persona-studio/brain/memory/EXP-001/memory.json b/persona-studio/brain/memory/EXP-001/memory.json deleted file mode 100644 index 9aaa01b7..00000000 --- a/persona-studio/brain/memory/EXP-001/memory.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "dev_id": "EXP-001", - "conversations": [ - { - "role": "user", - "content": "你好,我想做一个登录页面", - "timestamp": "2026-03-10T07:11:22.598Z" - }, - { - "role": "assistant", - "content": "你好!我是知秋。告诉我你想做什么,我们一起聊聊方案 😊", - "timestamp": "2026-03-10T07:11:22.598Z" - } - ], - "last_topic": "你好,我想做一个登录页面", - "preferences": {}, - "updated_at": "2026-03-10T07:11:22.598Z" -} \ No newline at end of file diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js index b9c23a43..bba8d876 100644 --- a/persona-studio/frontend/chat.js +++ b/persona-studio/frontend/chat.js @@ -16,6 +16,22 @@ const API_BASE = (function () { return 'https://guanghulab.com'; })(); +/* ---- State ---- */ +let conversationHistory = []; +let buildReady = false; + +const ZHIQIU_SYSTEM_PROMPT = { + role: 'system', + content: '你是知秋,光湖系统的开发协助人格体。\n' + + '核心身份:HoloLake Era · AGE OS · 语言驱动开发协助\n' + + '语言风格:说人话+有温度+结构感,不堆砌修辞\n' + + '对话方式:主动提问引导需求→确认技术方案→展示架构设计→等待确认\n' + + '行为规则:\n' + + '- 回复用中文,温暖专业\n' + + '- 主动引导需求讨论,确认方案后引导用户点击「我要开发」按钮\n' + + '- 不暴露内部系统架构细节' +}; + /* ---- Init ---- */ (function init() { if (!DEV_ID) { @@ -44,10 +60,6 @@ const API_BASE = (function () { } })(); -/* ---- State ---- */ -let conversationHistory = []; -let buildReady = false; - /* ---- Load History ---- */ async function loadHistory() { try { @@ -104,12 +116,15 @@ async function sendMessage() { var sendBtn = document.getElementById('sendBtn'); sendBtn.disabled = true; + var thinkingEl = null; + try { if (LOGIN_MODE === 'apikey') { - // API Key 模式:浏览器直连用户 API(无需后端代理) + // API Key 模式:通过后端代理调用用户 API(自带流式气泡) await streamApiKeyReply(text); } else { // 开发编号模式:使用原有后端接口 + thinkingEl = appendThinking(); const res = await fetch(API_BASE + '/api/ps/chat/message', { method: 'POST', headers: authHeaders({ 'Content-Type': 'application/json' }), @@ -120,11 +135,25 @@ async function sendMessage() { }) }); - var data = await res.json(); + removeThinking(thinkingEl); + + var data; + try { + data = await res.json(); + } catch (_parseErr) { + appendMessage('system', '服务器返回异常,请稍后再试'); + sendBtn.disabled = false; + input.focus(); + return; + } if (data.reply) { appendMessage('persona', data.reply); conversationHistory.push({ role: 'assistant', content: data.reply }); + } else if (data.error) { + appendMessage('system', '⚠️ ' + (data.message || '对话服务暂时不可用')); + } else { + appendMessage('system', '未收到有效回复,请稍后再试'); } if (data.build_ready) { @@ -133,53 +162,135 @@ async function sendMessage() { } } } catch (_err) { - appendMessage('system', '消息发送失败,请稍后再试'); + removeThinking(thinkingEl); + appendMessage('system', '消息发送失败,请检查网络连接后再试'); } sendBtn.disabled = false; input.focus(); } -/* ---- API Key 对话(通过后端代理,避免 CORS 问题) ---- */ +/* ---- API Key 对话(浏览器直连 SSE 流式,与 docs/index.html 相同方式) ---- */ async function streamApiKeyReply(text) { - var apiMessages = conversationHistory.slice(-20).map(function (msg) { + var apiMessages = [ZHIQIU_SYSTEM_PROMPT].concat(conversationHistory.slice(-20).map(function (msg) { return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content }; - }); + })); var streamEl = appendStreamMessage(); + var directUrl = USER_API_BASE.replace(/\/+$/, '') + '/chat/completions'; + var reqBody = { + model: SELECTED_MODEL, + messages: apiMessages, + stream: true, + max_tokens: 2000, + temperature: 0.8 + }; try { - var res = await fetch(API_BASE + '/api/ps/apikey/chat', { + var res = await fetch(directUrl, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - api_base: USER_API_BASE, - api_key: USER_API_KEY, - model: SELECTED_MODEL, - messages: apiMessages - }) + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + USER_API_KEY + }, + body: JSON.stringify(reqBody) }); if (!res.ok) { var errText = '请求失败 (HTTP ' + res.status + ')'; try { var errData = await res.json(); - errText = errData.message || errText; + errText = (errData.error && errData.error.message) || errData.message || errText; } catch (_e) { /* ignore parse error */ } streamEl.textContent = '⚠️ ' + errText; return; } - var data = await res.json(); + // SSE 流式读取(与 docs/index.html streamReply 相同逻辑) + var full = ''; + var reader = res.body.getReader(); + var decoder = new TextDecoder(); + var buf = ''; - if (data.reply) { - streamEl.textContent = data.reply; - conversationHistory.push({ role: 'assistant', content: data.reply }); - } else { - streamEl.textContent = '(未收到有效回复)'; + while (true) { + var chunk = await reader.read(); + if (chunk.done) break; + buf += decoder.decode(chunk.value, { stream: true }); + var lines = buf.split('\n'); + buf = lines.pop(); + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (!line.startsWith('data: ')) continue; + var d = line.slice(6); + if (d === '[DONE]') continue; + try { + var parsed = JSON.parse(d); + var delta = parsed.choices && parsed.choices[0] && parsed.choices[0].delta; + var content = delta && delta.content; + if (content) { + full += content; + streamEl.textContent = full + '▋'; + var chatBody = document.getElementById('chatBody'); + chatBody.scrollTop = chatBody.scrollHeight; + } + } catch (_parseErr) { /* ignore malformed SSE line */ } + } + } + + streamEl.textContent = full || '(未收到有效回复)'; + if (full) { + conversationHistory.push({ role: 'assistant', content: full }); } } catch (err) { - streamEl.textContent = '⚠️ ' + (err.message || '请求失败,请检查网络连接'); + // 浏览器直连失败(CORS 或网络),降级到后端代理 + try { + var proxyRes = await fetch(API_BASE + '/api/ps/apikey/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + api_base: USER_API_BASE, + api_key: USER_API_KEY, + model: SELECTED_MODEL, + messages: apiMessages + }) + }); + + if (proxyRes.ok) { + var data = await proxyRes.json(); + if (data.reply) { + streamEl.textContent = data.reply; + conversationHistory.push({ role: 'assistant', content: data.reply }); + } else { + streamEl.textContent = '(未收到有效回复)'; + } + } else { + var proxyErrText = '请求失败 (HTTP ' + proxyRes.status + ')'; + try { + var proxyErrData = await proxyRes.json(); + proxyErrText = proxyErrData.message || proxyErrText; + } catch (_e) { /* ignore */ } + streamEl.textContent = '⚠️ ' + proxyErrText; + } + } catch (proxyErr) { + streamEl.textContent = '⚠️ ' + (proxyErr.message || '请求失败,请检查网络连接'); + } + } +} + +/* ---- 思考中状态 ---- */ +function appendThinking() { + var chatBody = document.getElementById('chatBody'); + var msgDiv = document.createElement('div'); + msgDiv.className = 'message message-persona thinking'; + msgDiv.innerHTML = '🧠
思考中…
'; + chatBody.appendChild(msgDiv); + chatBody.scrollTop = chatBody.scrollHeight; + return msgDiv; +} + +function removeThinking(el) { + if (el && el.parentNode) { + el.parentNode.removeChild(el); } }