fix: sidebar clock icon + cron button loading + #35 _normalizeBaseUrl crash

- sidebar.js: add clock SVG to ICONS map for 定时任务 nav item
- cron.js: add loading feedback to toggle/delete buttons
- dev-api.js: extract _normalizeBaseUrl from handlers object to standalone function
  Fixes #35: 'Cannot read properties of undefined (reading _normalizeBaseUrl)'
  Root cause: handlers are called as standalone functions via API middleware,
  so 'this' is undefined. Moving to a standalone function fixes it.
This commit is contained in:
晴天
2026-03-11 02:03:45 +08:00
parent 8602008a13
commit 01561820be
2 changed files with 12 additions and 9 deletions

View File

@@ -992,6 +992,15 @@ const ALWAYS_LOCAL = new Set([
'assistant_ensure_data_dir', 'assistant_save_image', 'assistant_load_image', 'assistant_delete_image',
])
// === 工具函数 ===
// 清理 base URL去掉尾部斜杠和已知端点路径防止路径重复
function _normalizeBaseUrl(raw) {
let base = (raw || '').replace(/\/+$/, '')
base = base.replace(/\/(chat\/completions|completions|responses|messages|models)\/?$/, '')
return base.replace(/\/+$/, '')
}
// === API Handlers ===
const handlers = {
@@ -2291,16 +2300,9 @@ const handlers = {
return { current, latest: null, update_available: false, source: 'chinese' }
},
// 清理 base URL去掉尾部斜杠和已知端点路径防止路径重复
_normalizeBaseUrl(raw) {
let base = raw.replace(/\/+$/, '')
base = base.replace(/\/(chat\/completions|completions|responses|messages|models)\/?$/, '')
return base.replace(/\/+$/, '')
},
// 模型测试
async test_model({ baseUrl, apiKey, modelId }) {
const url = `${this._normalizeBaseUrl(baseUrl)}/chat/completions`
const url = `${_normalizeBaseUrl(baseUrl)}/chat/completions`
const body = JSON.stringify({
model: modelId,
messages: [{ role: 'user', content: 'Hi' }],
@@ -2332,7 +2334,7 @@ const handlers = {
},
async list_remote_models({ baseUrl, apiKey }) {
const url = `${this._normalizeBaseUrl(baseUrl)}/models`
const url = `${_normalizeBaseUrl(baseUrl)}/models`
const headers = {}
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`
const controller = new AbortController()

View File

@@ -91,6 +91,7 @@ const ICONS = {
skills: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg>',
docker: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="11" width="4" height="3" rx=".5"/><rect x="6" y="11" width="4" height="3" rx=".5"/><rect x="11" y="11" width="4" height="3" rx=".5"/><rect x="6" y="7" width="4" height="3" rx=".5"/><rect x="11" y="7" width="4" height="3" rx=".5"/><rect x="16" y="11" width="4" height="3" rx=".5"/><rect x="11" y="3" width="4" height="3" rx=".5"/><path d="M2 17c1 3 4 5 10 5s9-2 10-5"/></svg>',
channels: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>',
clock: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
debug: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/><circle cx="12" cy="12" r="3"/></svg>',
}