From ff3ec0d024df0f24bcaaea08ffafd0af099f3634 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:04:29 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E5=9B=9E=E5=A4=8D=E7=94=A8=E6=88=B7=E6=B6=88?= =?UTF-8?q?=E6=81=AF=20=E2=80=94=20=E6=B5=8F=E8=A7=88=E5=99=A8=E7=9B=B4?= =?UTF-8?q?=E8=BF=9E=20API=20+=20=E5=90=8E=E7=AB=AF=20URL=20=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com> --- persona-studio/backend/routes/apikey.js | 11 +-- .../brain/memory/EXP-001/memory.json | 28 ------ persona-studio/frontend/chat.js | 89 +++++++++++++------ 3 files changed, 69 insertions(+), 59 deletions(-) delete mode 100644 persona-studio/brain/memory/EXP-001/memory.json 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/brain/memory/EXP-001/memory.json b/persona-studio/brain/memory/EXP-001/memory.json deleted file mode 100644 index 64203813..00000000 --- a/persona-studio/brain/memory/EXP-001/memory.json +++ /dev/null @@ -1,28 +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" - }, - { - "role": "user", - "content": "你好", - "timestamp": "2026-03-11T03:01:16.220Z" - }, - { - "role": "assistant", - "content": "你好!我是知秋。告诉我你想做什么,我们一起聊聊方案 😊", - "timestamp": "2026-03-11T03:01:16.220Z" - } - ], - "last_topic": "你好", - "preferences": {}, - "updated_at": "2026-03-11T03:01:16.220Z" -} \ No newline at end of file diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js index 8a4ddfb3..208c6730 100644 --- a/persona-studio/frontend/chat.js +++ b/persona-studio/frontend/chat.js @@ -170,46 +170,83 @@ async function sendMessage() { input.focus(); } -/* ---- API Key 对话(通过后端代理,避免 CORS 问题) ---- */ +/* ---- API Key 对话(浏览器直连用户 API,后端代理作为降级) ---- */ async function streamApiKeyReply(text) { 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 requestBody = JSON.stringify({ + model: SELECTED_MODEL, + messages: apiMessages, + max_tokens: 2000, + temperature: 0.8 + }); + var reply = null; + + // 策略 1:浏览器直连用户 API(与模型检测相同路径,最可靠) try { - var res = await fetch(API_BASE + '/api/ps/apikey/chat', { + var directUrl = USER_API_BASE.replace(/\/+$/, '') + '/chat/completions'; + var directRes = 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: requestBody }); - if (!res.ok) { - var errText = '请求失败 (HTTP ' + res.status + ')'; - try { - var errData = await res.json(); - errText = errData.message || errText; - } catch (_e) { /* ignore parse error */ } - streamEl.textContent = '⚠️ ' + errText; + if (directRes.ok) { + var directData = await directRes.json(); + if (directData.choices && directData.choices[0] && directData.choices[0].message) { + reply = directData.choices[0].message.content; + } + } + } catch (_directErr) { + // 浏览器直连失败(CORS 或网络),降级到后端代理 + } + + // 策略 2:后端代理(处理 CORS 限制的情况) + if (!reply) { + try { + var res = 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 (res.ok) { + var data = await res.json(); + if (data.reply) { + reply = data.reply; + } + } else { + var errText = '请求失败 (HTTP ' + res.status + ')'; + try { + var errData = await res.json(); + errText = errData.message || errText; + } catch (_e) { /* ignore */ } + streamEl.textContent = '⚠️ ' + errText; + return; + } + } catch (proxyErr) { + streamEl.textContent = '⚠️ ' + (proxyErr.message || '请求失败,请检查网络连接'); return; } + } - var data = await res.json(); - - if (data.reply) { - streamEl.textContent = data.reply; - conversationHistory.push({ role: 'assistant', content: data.reply }); - } else { - streamEl.textContent = '(未收到有效回复)'; - } - } catch (err) { - streamEl.textContent = '⚠️ ' + (err.message || '请求失败,请检查网络连接'); + if (reply) { + streamEl.textContent = reply; + conversationHistory.push({ role: 'assistant', content: reply }); + } else { + streamEl.textContent = '(未收到有效回复)'; } }