diff --git a/.github/brain/bingshuo-issues-index.json b/.github/brain/bingshuo-issues-index.json
index cb79a6cc..25ece0c5 100644
--- a/.github/brain/bingshuo-issues-index.json
+++ b/.github/brain/bingshuo-issues-index.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控问题索引库 — 记录已知问题、根因与排查路由",
- "updated_at": "2026-03-10T12:37:06.767Z",
+ "updated_at": "2026-03-10T13:11:12.874Z",
"issues": [
{
"id": "BS-001",
diff --git a/.github/brain/bingshuo-master-brain.md b/.github/brain/bingshuo-master-brain.md
index e088296b..1c565e58 100644
--- a/.github/brain/bingshuo-master-brain.md
+++ b/.github/brain/bingshuo-master-brain.md
@@ -1,7 +1,7 @@
# 冰朔主控神经系统 · 核心主控大脑 v1.0
> 本文件为冰朔主控神经系统的总控脑文件。
-> 最后编译时间:2026-03-10T12:37:06.767Z
+> 最后编译时间:2026-03-10T13:11:12.875Z
---
@@ -85,7 +85,7 @@
> 本区块由 master-brain-compiler 自动编译。
-- **编译时间**:2026-03-10T12:37:06.767Z
+- **编译时间**:2026-03-10T13:11:12.875Z
- **脑文件规则版本**:v3.0
- **脑文件完整性**:✅ 完整
diff --git a/.github/brain/bingshuo-system-health.json b/.github/brain/bingshuo-system-health.json
index 8d288a72..661160d4 100644
--- a/.github/brain/bingshuo-system-health.json
+++ b/.github/brain/bingshuo-system-health.json
@@ -1,7 +1,7 @@
{
"version": "1.0",
"description": "冰朔主控系统健康状态",
- "updated_at": "2026-03-10T12:37:06.766Z",
+ "updated_at": "2026-03-10T13:11:12.874Z",
"health": {
"brain_consistency": {
"status": "yellow",
diff --git a/.github/brain/repo-map.json b/.github/brain/repo-map.json
index a192f0c5..8ff7495d 100644
--- a/.github/brain/repo-map.json
+++ b/.github/brain/repo-map.json
@@ -1,7 +1,7 @@
{
"description": "铸渊图书馆目录 · Library Catalog for 铸渊 (Zhùyuān)",
"version": "2.0",
- "generated_at": "2026-03-10T12:37:16.388Z",
+ "generated_at": "2026-03-10T13:11:27.110Z",
"generated_by": "scripts/generate-repo-map.js",
"repo": "qinfendebingshuo/guanghulab",
"stats": {
diff --git a/.github/brain/repo-snapshot.md b/.github/brain/repo-snapshot.md
index 60c49ad2..1bd3bc2e 100644
--- a/.github/brain/repo-snapshot.md
+++ b/.github/brain/repo-snapshot.md
@@ -1,5 +1,5 @@
# 铸渊图书馆快照 · Repo Snapshot
-> 生成于 2026-03-10 20:37 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
+> 生成于 2026-03-10 21:11 CST · 每次 push 自动更新 · 铸渊唤醒时优先读取此文件
---
@@ -13,7 +13,7 @@
| 脚本 | 26 个执行脚本 |
| 开发者节点 | 8 人 |
| HLI 接口覆盖率 | 7/21 (33%) |
-| 快照生成时间 | 2026-03-10 20:37 CST |
+| 快照生成时间 | 2026-03-10 21:11 CST |
---
diff --git a/README.md b/README.md
index 083a0ed8..e0b25b98 100644
--- a/README.md
+++ b/README.md
@@ -78,29 +78,30 @@
| 时间 | 检查项 | 状态 |
|------|--------|------|
-| 03-09 17:29 | 🔧 系统更新: `.github/` | 铸渊 (Copilot) |
-| 03-09 17:29 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
-| 03-09 17:15 | 🔧 系统更新: `docs/` | 铸渊 (Copilot) |
-| 03-09 17:15 | 🔧 系统更新: `persona-brain-db/` | 铸渊 (Copilot) |
+| 03-10 20:38 | 🔧 系统更新: `.github/` | 铸渊 (ZhùYuān) |
+| 03-10 20:37 | ❌ 📢 更新系统公告区 · 失败 | 冰朔 |
+| 03-10 20:37 | 🔧 系统更新: `docs/` | 铸渊 (ZhùYuān) |
+| 03-10 19:15 | 🔧 系统更新: `scripts/` | 铸渊 (Copilot) |
+| 03-10 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
+| 03-10 08:00 | 🧠 核心大脑升级 v3.0 · 壳-核分离架构 · 前端壳(UI/IO) + 后端脑(prompt/routing/memory/context) · BRAIN域4接口上线 · 覆盖率 7/21 (33.3%) | 铸渊(冰朔指令) |
+| 03-10 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 3 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
+| 03-10 08:00 | 🔵 CI 构建 失败 | GitHub Actions |
| 03-09 16:56 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-09 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-09 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
-| 03-08 16:41 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-08 08:00 | ✅ 铸渊 PSP 巡检通过 · 全部检查项 ✅ | 铸渊PSP巡检 |
-| 03-08 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
-| 03-07 16:41 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
| 03-07 08:00 | ⚠️ 铸渊 PSP 巡检完成 · 发现 1 个问题 · 自动修复 0 项 | 铸渊PSP巡检 |
-| 03-07 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
-| 03-06 16:51 | ✅ 每日巡检 ✅ 通过 | 冰朔 |
-| 03-06 08:00 | 🔍 每日自检完成 · 知识库4条 · 缺失文件4个 | 铸渊自检 |
+| 03-06 00:07 | ✅ CI 构建 通过 | 冰朔 |
+| 03-05 20:32 | 🚀 广播分发系统 + 唤醒协议全部激活 | 冰朔 |
### 🤖 铸渊自动提醒
-> 🟢 **今日无需冰朔手动干预** · 系统一切正常
+> 🔴 **需要冰朔手动干预!**
>
-> 🗓️ 2026-03-09 · 铸渊自动检测
+> - ❌ 📢 更新系统公告区 · 失败
+>
+> 🗓️ 2026-03-10 · 铸渊已发送邮件提醒
---
@@ -114,24 +115,10 @@
| 时间 | 合作者 | 模块 | 状态 |
|------|--------|------|------|
-| 03-09 17:15 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
-| 03-09 17:15 | 铸渊 (Copilot) | `backend/` | 📦 上传成功 |
-| 03-09 17:15 | 铸渊 (Copilot) | `cloud-drive/` | 📦 上传成功 |
-| 03-09 17:15 | 🤖 之之 | `dingtalk-bot/` | 📦 上传成功 |
-| 03-09 17:15 | 铸渊 (Copilot) | `frontend/` | 📦 上传成功 |
-| 03-09 17:15 | 🐱 肥猫 | `m01-login/` | 📦 上传成功 |
-| 03-09 17:15 | 🐱 肥猫 | `m03-personality/` | 📦 上传成功 |
-| 03-09 17:15 | 🌸 花尔 | `m05-user-center/` | 📦 上传成功 |
-| 03-09 17:15 | 🍊 桔子 | `m06-ticket/` | 📦 上传成功 |
-| 03-09 17:15 | 🎨 燕樊 | `m07-dialogue-ui/` | 📦 上传成功 |
-| 03-09 17:15 | 🎨 燕樊 | `m10-cloud/` | 📦 上传成功 |
-| 03-09 17:15 | 🍊 桔子 | `m11-module/` | 📦 上传成功 |
-| 03-09 17:15 | 🍓 小草莓 | `m12-kanban/` | 📦 上传成功 |
-| 03-09 17:15 | 🎨 燕樊 | `m15-cloud-drive/` | 📦 上传成功 |
-| 03-09 17:15 | 铸渊 (Copilot) | `m18-health-check/` | 📦 上传成功 |
-| 03-09 17:15 | 🌟 Awen | `notification/` | 📦 上传成功 |
-| 03-09 17:15 | 🍓 小草莓 | `status-board/` | 📦 上传成功 |
-| 03-09 17:15 | 🍊 桔子 | `ticket-system/` | 📦 上传成功 |
+| 03-10 20:49 | zhizhi200271 | `—/` | ✅ 上传成功 |
+| 03-10 20:40 | Copilot | `—/` | 🔵 已更新 |
+| 03-10 20:37 | 冰朔 | `—/` | ✅ 上传成功 |
+| 03-10 20:31 | 🛠️ 页页 | `backend-integration/` | 📦 上传成功 |
### 🤖 铸渊自动提醒 · 合作者
@@ -142,7 +129,7 @@
> **🎨 燕樊(DEV-003):**
> - ⚠️ `m15-cloud-drive/` 缺少 README.md
>
-> 🗓️ 2026-03-09 · 铸渊已发送邮件提醒
+> 🗓️ 2026-03-10 · 铸渊已发送邮件提醒
---
diff --git a/persona-studio/backend/routes/apikey.js b/persona-studio/backend/routes/apikey.js
index ebfcb434..56d6b8b3 100644
--- a/persona-studio/backend/routes/apikey.js
+++ b/persona-studio/backend/routes/apikey.js
@@ -81,12 +81,16 @@ function fetchModels(apiBase, apiKey, timeoutMs) {
});
req.on('error', (err) => {
- reject(new Error('API Base 不可访问: ' + err.message));
+ const wrapped = new Error('API Base 不可访问: ' + err.message);
+ wrapped.code = err.code;
+ reject(wrapped);
});
req.on('timeout', () => {
req.destroy();
- reject(new Error('API Base 不可访问(请求超时)'));
+ const err = new Error('API Base 不可访问(请求超时)');
+ err.code = 'ETIMEDOUT';
+ reject(err);
});
req.end();
@@ -169,6 +173,19 @@ 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);
+ } catch (_e) {
+ return res.status(400).json({
+ error: true,
+ code: 'INVALID_API_BASE',
+ message: 'API Base URL 格式无效'
+ });
+ }
+
if (!api_key || typeof api_key !== 'string') {
return res.status(400).json({
error: true,
@@ -216,10 +233,23 @@ router.post('/detect-models', async (req, res) => {
cached: false
});
} catch (err) {
+ const errMsg = err.message || '模型检测失败';
+ const errCode = err.code || '';
+ let code = 'DETECT_FAILED';
+
+ // 区分 DNS / 网络 / 超时错误(优先使用 Node.js 错误码)
+ if (errCode === 'ENOTFOUND' || errCode === 'EAI_AGAIN' || /ENOTFOUND|getaddrinfo/.test(errMsg)) {
+ code = 'DNS_ERROR';
+ } else if (errCode === 'ECONNREFUSED' || errCode === 'ECONNRESET' || errCode === 'EHOSTUNREACH' || errCode === 'ENETUNREACH' || /ECONNREFUSED|ECONNRESET|EHOSTUNREACH|ENETUNREACH|socket hang up/.test(errMsg)) {
+ code = 'NETWORK_ERROR';
+ } else if (errCode === 'ETIMEDOUT' || errCode === 'ESOCKETTIMEDOUT' || /timeout|ETIMEDOUT/.test(errMsg)) {
+ code = 'TIMEOUT';
+ }
+
res.status(502).json({
error: true,
- code: 'DETECT_FAILED',
- message: err.message || '模型检测失败'
+ code: code,
+ message: errMsg
});
}
});
diff --git a/persona-studio/backend/server.js b/persona-studio/backend/server.js
index 03186e7f..a4899137 100644
--- a/persona-studio/backend/server.js
+++ b/persona-studio/backend/server.js
@@ -33,6 +33,16 @@ app.get('/api/ps/health', (_req, res) => {
});
});
+// 兼容 /api/health 路径
+app.get('/api/health', (_req, res) => {
+ res.json({
+ status: 'ok',
+ service: 'persona-studio',
+ version: '1.0.0',
+ timestamp: new Date().toISOString()
+ });
+});
+
// ── 根路由 ──
app.get('/', (_req, res) => {
res.json({
diff --git a/persona-studio/frontend/chat.js b/persona-studio/frontend/chat.js
index c1c55e03..a365219e 100644
--- a/persona-studio/frontend/chat.js
+++ b/persona-studio/frontend/chat.js
@@ -105,26 +105,9 @@ async function sendMessage() {
sendBtn.disabled = true;
try {
- let data;
-
if (LOGIN_MODE === 'apikey') {
- // API Key 模式:通过后端代理调用用户的 API
- const apiMessages = conversationHistory.slice(-20).map(function (msg) {
- return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content };
- });
-
- const 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
- })
- });
-
- data = await res.json();
+ // API Key 模式:浏览器直连用户 API(无需后端代理)
+ await streamApiKeyReply(text);
} else {
// 开发编号模式:使用原有后端接口
const res = await fetch(API_BASE + '/api/ps/chat/message', {
@@ -137,17 +120,17 @@ async function sendMessage() {
})
});
- data = await res.json();
- }
+ var data = await res.json();
- if (data.reply) {
- appendMessage('persona', data.reply);
- conversationHistory.push({ role: 'assistant', content: data.reply });
- }
+ if (data.reply) {
+ appendMessage('persona', data.reply);
+ conversationHistory.push({ role: 'assistant', content: data.reply });
+ }
- if (data.build_ready) {
- buildReady = true;
- document.getElementById('buildBtn').style.display = 'inline-flex';
+ if (data.build_ready) {
+ buildReady = true;
+ document.getElementById('buildBtn').style.display = 'inline-flex';
+ }
}
} catch (_err) {
appendMessage('system', '消息发送失败,请稍后再试');
@@ -157,6 +140,105 @@ async function sendMessage() {
input.focus();
}
+/* ---- API Key 流式响应(浏览器直连) ---- */
+async function streamApiKeyReply(text) {
+ var apiMessages = conversationHistory.slice(-20).map(function (msg) {
+ return { role: msg.role === 'assistant' ? 'assistant' : 'user', content: msg.content };
+ });
+
+ var chatUrl = USER_API_BASE + '/chat/completions';
+ var reqBody = {
+ model: SELECTED_MODEL,
+ messages: apiMessages,
+ stream: true,
+ max_tokens: 2000,
+ temperature: 0.8
+ };
+
+ var res = await fetch(chatUrl, {
+ method: 'POST',
+ 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.error?.message || errText;
+ } catch (_e) { /* ignore parse error */ }
+ appendMessage('system', '⚠️ ' + errText);
+ return;
+ }
+
+ // 流式读取响应
+ var streamEl = appendStreamMessage();
+ var full = '';
+ var reader = res.body.getReader();
+ var decoder = new TextDecoder();
+ var buf = '';
+
+ 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
+ && parsed.choices[0].delta.content;
+ if (delta) {
+ full += delta;
+ streamEl.textContent = full;
+ }
+ } catch (_e) { /* ignore parse error */ }
+ }
+ }
+
+ // 如果没有流式内容,尝试非流式解析
+ if (!full && buf) {
+ try {
+ var jsonRes = JSON.parse(buf);
+ if (jsonRes.choices && jsonRes.choices[0] && jsonRes.choices[0].message) {
+ full = jsonRes.choices[0].message.content;
+ streamEl.textContent = full;
+ }
+ } catch (_e) { /* ignore */ }
+ }
+
+ if (full) {
+ conversationHistory.push({ role: 'assistant', content: full });
+ } else {
+ streamEl.textContent = '(未收到有效回复)';
+ }
+}
+
+/* ---- 创建流式消息气泡 ---- */
+function appendStreamMessage() {
+ var chatBody = document.getElementById('chatBody');
+ var msgDiv = document.createElement('div');
+ msgDiv.className = 'message message-persona';
+ var contentEl = document.createElement('div');
+ contentEl.className = 'msg-content';
+ contentEl.textContent = '▋';
+ msgDiv.innerHTML = '🧠';
+ msgDiv.appendChild(contentEl);
+ chatBody.appendChild(msgDiv);
+ chatBody.scrollTop = chatBody.scrollHeight;
+ return contentEl;
+}
+
/* ---- Key Handler ---- */
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
diff --git a/persona-studio/frontend/index.html b/persona-studio/frontend/index.html
index 0fe20170..eb8c080d 100644
--- a/persona-studio/frontend/index.html
+++ b/persona-studio/frontend/index.html
@@ -43,7 +43,7 @@
type="text"
id="apiBaseInput"
class="apikey-input"
- placeholder="API Base URL(如 https://api.openai.com)"
+ placeholder="API Base URL(可留空自动探测,如 https://api.openai.com/v1)"
autocomplete="off"
/>
const DEV_ID_RE = /^EXP-\d{3,}$/;
+ const PROBE_TIMEOUT_MS = 8000;
const API_BASE = getApiBase();
+ /* ---- 已知 API 端点列表(浏览器直连探测) ---- */
+ const KNOWN_ENDPOINTS = [
+ { label: '云雾 AI', base: 'https://api.yunwu.ai/v1' },
+ { label: 'OpenAI', base: 'https://api.openai.com/v1' },
+ { label: 'Google Gemini', base: 'https://generativelanguage.googleapis.com/v1beta/openai' },
+ { label: 'DeepSeek', base: 'https://api.deepseek.com/v1' },
+ { label: 'Moonshot', base: 'https://api.moonshot.cn/v1' },
+ { label: '智谱 AI', base: 'https://open.bigmodel.cn/api/paas/v4' },
+ ];
+
function getApiBase() {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3721';
@@ -84,6 +95,18 @@
return 'https://guanghulab.com';
}
+ /**
+ * 规范化 API Base URL
+ * 确保末尾无斜杠,并包含 /v1 路径
+ */
+ function normalizeApiBase(base) {
+ var normalized = base.replace(/\/+$/, '');
+ if (!normalized.match(/\/v\d+(\/|$)/) && !normalized.endsWith('/openai')) {
+ normalized += '/v1';
+ }
+ return normalized;
+ }
+
/* ---- 开发编号登录 ---- */
async function handleLogin(e) {
e.preventDefault();
@@ -136,24 +159,18 @@
return false;
}
- /* ---- API Key 模型检测 ---- */
+ /* ---- API Key 模型检测(浏览器直连,无需后端代理) ---- */
async function handleDetectModels() {
- const apiBase = document.getElementById('apiBaseInput').value.trim();
- const apiKey = document.getElementById('apiKeyInput').value.trim();
- const errorEl = document.getElementById('errorMsg');
- const statusEl = document.getElementById('detectStatus');
- const modelContainer = document.getElementById('modelListContainer');
- const btn = document.getElementById('detectBtn');
+ var apiBase = document.getElementById('apiBaseInput').value.trim();
+ var apiKey = document.getElementById('apiKeyInput').value.trim();
+ var errorEl = document.getElementById('errorMsg');
+ var statusEl = document.getElementById('detectStatus');
+ var modelContainer = document.getElementById('modelListContainer');
+ var btn = document.getElementById('detectBtn');
errorEl.style.display = 'none';
modelContainer.style.display = 'none';
- if (!apiBase) {
- errorEl.textContent = '请输入 API Base URL';
- errorEl.style.display = 'block';
- return;
- }
-
if (!apiKey) {
errorEl.textContent = '请输入 API Key';
errorEl.style.display = 'block';
@@ -161,59 +178,67 @@
}
btn.disabled = true;
- btn.textContent = '正在检测可用模型…';
- statusEl.textContent = '正在检测可用模型...';
+ btn.textContent = '⏳ 正在检测可用模型…';
statusEl.className = 'detect-status detect-loading';
statusEl.style.display = 'block';
- try {
- const res = await fetch(API_BASE + '/api/ps/apikey/detect-models', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ api_base: apiBase, api_key: apiKey })
- });
-
- const data = await res.json();
-
- if (!res.ok || data.error) {
- statusEl.textContent = data.message || '未检测到模型,请检查 API Key';
- statusEl.className = 'detect-status detect-error';
- btn.disabled = false;
- btn.textContent = '🔍 检测可用模型';
- return;
- }
-
- const models = data.models || [];
- if (models.length === 0) {
- statusEl.textContent = '未检测到可用模型';
- statusEl.className = 'detect-status detect-error';
- btn.disabled = false;
- btn.textContent = '🔍 检测可用模型';
- return;
- }
-
- statusEl.textContent = '检测到 ' + models.length + ' 个模型,请选择一个模型进入对话';
- statusEl.className = 'detect-status detect-success';
-
- renderModelList(models, apiBase, apiKey);
- modelContainer.style.display = 'block';
-
- btn.disabled = false;
- btn.textContent = '🔍 重新检测';
- } catch (fetchErr) {
- var errMsg = '检测失败';
- if (fetchErr instanceof TypeError) {
- errMsg = '服务器代理未部署或不可达,请联系管理员';
- } else if (fetchErr.name === 'AbortError') {
- errMsg = '请求超时,请检查网络连接';
- } else {
- errMsg = '网络错误: ' + (fetchErr.message || '请检查网络连接');
- }
- statusEl.textContent = errMsg;
- statusEl.className = 'detect-status detect-error';
- btn.disabled = false;
- btn.textContent = '🔍 检测可用模型';
+ /* 构建探测列表:用户端点优先,然后已知端点 */
+ var candidates = [];
+ if (apiBase) {
+ var normalizedUserBase = normalizeApiBase(apiBase);
+ candidates.push({ label: apiBase, base: normalizedUserBase });
}
+ for (var i = 0; i < KNOWN_ENDPOINTS.length; i++) {
+ var ep = KNOWN_ENDPOINTS[i];
+ if (!apiBase || ep.base !== normalizedUserBase) {
+ candidates.push(ep);
+ }
+ }
+
+ var matched = false;
+ for (var ci = 0; ci < candidates.length; ci++) {
+ var candidate = candidates[ci];
+ statusEl.textContent = '🔍 探测 ' + candidate.label + '…';
+
+ try {
+ var ctrl = new AbortController();
+ var tid = setTimeout(function () { ctrl.abort(); }, PROBE_TIMEOUT_MS);
+
+ var res = await fetch(candidate.base + '/models', {
+ headers: { 'Authorization': 'Bearer ' + apiKey },
+ signal: ctrl.signal
+ });
+ clearTimeout(tid);
+
+ if (res.ok) {
+ var data = await res.json();
+ var models = (data.data || []).map(function (m) { return m.id; }).filter(Boolean).sort();
+
+ if (models.length === 0) continue;
+
+ /* 回填端点地址 */
+ document.getElementById('apiBaseInput').value = candidate.base;
+
+ statusEl.textContent = '✅ 检测成功(' + candidate.label + ')· 发现 ' + models.length + ' 个可用模型';
+ statusEl.className = 'detect-status detect-success';
+
+ renderModelList(models, candidate.base, apiKey);
+ modelContainer.style.display = 'block';
+ matched = true;
+ break;
+ }
+ } catch (probeErr) {
+ /* 单个端点探测失败,继续下一个 */
+ }
+ }
+
+ if (!matched) {
+ statusEl.textContent = '❌ 未能检测到可用模型,请检查端点地址和密钥是否正确';
+ statusEl.className = 'detect-status detect-error';
+ }
+
+ btn.disabled = false;
+ btn.textContent = '🔍 检测可用模型';
}
/* ---- 渲染模型列表 ---- */