mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-03 14:49:49 +08:00
fix: wsClient.close→disconnect, model vision input, memory leaks; feat: loading skeletons, panel update check; bump v0.2.0
This commit is contained in:
@@ -65,11 +65,26 @@ async function loadData(page) {
|
||||
// 非 Tauri 环境或 API 不可用,使用 fallback
|
||||
}
|
||||
|
||||
// 异步检查 ClawPanel 自身更新
|
||||
let panelUpdateHtml = '<span style="color:var(--text-tertiary)">检查更新中...</span>'
|
||||
api.checkPanelUpdate().then(info => {
|
||||
const panelCard = cards.querySelector('#panel-update-meta')
|
||||
if (!panelCard) return
|
||||
if (info.latest && info.latest !== panelVersion && compareVersions(info.latest, panelVersion) > 0) {
|
||||
panelCard.innerHTML = `<span style="color:var(--accent)">新版本: ${info.latest}</span> <a class="btn btn-primary btn-sm" href="${info.url}" target="_blank" rel="noopener" style="padding:2px 8px;font-size:var(--font-size-xs)">下载更新</a>`
|
||||
} else {
|
||||
panelCard.innerHTML = '<span style="color:var(--success)">已是最新</span>'
|
||||
}
|
||||
}).catch(() => {
|
||||
const panelCard = cards.querySelector('#panel-update-meta')
|
||||
if (panelCard) panelCard.innerHTML = '<span style="color:var(--text-tertiary)">检查更新失败</span>'
|
||||
})
|
||||
|
||||
cards.innerHTML = `
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header"><span class="stat-card-label">ClawPanel</span></div>
|
||||
<div class="stat-card-value">${panelVersion}</div>
|
||||
<div class="stat-card-meta">Tauri v2 桌面应用</div>
|
||||
<div class="stat-card-meta" id="panel-update-meta" style="display:flex;align-items:center;gap:8px">${panelUpdateHtml}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header"><span class="stat-card-label">OpenClaw · ${version.source === 'official' ? '官方版' : '汉化版'}</span></div>
|
||||
@@ -114,6 +129,18 @@ async function loadData(page) {
|
||||
}
|
||||
}
|
||||
|
||||
function compareVersions(a, b) {
|
||||
const pa = a.split('.').map(Number)
|
||||
const pb = b.split('.').map(Number)
|
||||
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
||||
const na = pa[i] || 0
|
||||
const nb = pb[i] || 0
|
||||
if (na > nb) return 1
|
||||
if (na < nb) return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function renderCommunity(page) {
|
||||
const el = page.querySelector('#community-section')
|
||||
el.innerHTML = `
|
||||
|
||||
@@ -27,12 +27,12 @@ export async function render() {
|
||||
<div id="cftunnel-card" class="config-section">
|
||||
<div class="config-section-title">cftunnel 内网穿透</div>
|
||||
<div class="form-hint" style="margin-bottom:var(--space-md)">通过 Cloudflare Tunnel 将本地服务暴露到公网,无需公网 IP 和端口映射。</div>
|
||||
<div id="cftunnel-content"></div>
|
||||
<div id="cftunnel-content"><div class="stat-card loading-placeholder" style="height:64px"></div></div>
|
||||
</div>
|
||||
<div id="clawapp-card" class="config-section">
|
||||
<div class="config-section-title">ClawApp 移动客户端</div>
|
||||
<div class="form-hint" style="margin-bottom:var(--space-md)">基于 LobeChat 的 AI 对话客户端,通过 Gateway 连接模型服务。支持本地和外网访问。</div>
|
||||
<div id="clawapp-content"></div>
|
||||
<div id="clawapp-content"><div class="stat-card loading-placeholder" style="height:64px"></div></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
|
||||
@@ -13,7 +13,11 @@ export async function render() {
|
||||
<h1 class="page-title">Gateway 配置</h1>
|
||||
<p class="page-desc">Gateway 是 AI 模型的统一入口,所有应用通过它来调用模型服务</p>
|
||||
</div>
|
||||
<div id="gateway-config"></div>
|
||||
<div id="gateway-config">
|
||||
<div class="config-section"><div class="stat-card loading-placeholder" style="height:80px"></div></div>
|
||||
<div class="config-section"><div class="stat-card loading-placeholder" style="height:80px"></div></div>
|
||||
<div class="config-section"><div class="stat-card loading-placeholder" style="height:80px"></div></div>
|
||||
</div>
|
||||
<div class="gw-save-bar">
|
||||
<button class="btn btn-primary" id="btn-save-gw">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/></svg>
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function render() {
|
||||
<input type="checkbox" id="log-autoscroll" checked> 自动滚动
|
||||
</label>
|
||||
</div>
|
||||
<div class="log-viewer" id="log-content" style="height:calc(100vh - 280px)"></div>
|
||||
<div class="log-viewer" id="log-content" style="height:calc(100vh - 280px)"><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div><div class="stat-card loading-placeholder" style="height:16px;margin:8px 0"></div></div>
|
||||
`
|
||||
|
||||
let currentTab = 'gateway'
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function render() {
|
||||
<div style="padding:0 var(--space-sm) var(--space-sm)">
|
||||
<button class="btn btn-sm btn-secondary" id="btn-export-zip" style="width:100%">打包下载全部</button>
|
||||
</div>
|
||||
<div id="file-tree"></div>
|
||||
<div id="file-tree"><div class="stat-card loading-placeholder" style="height:32px;margin:8px"></div><div class="stat-card loading-placeholder" style="height:32px;margin:8px"></div><div class="stat-card loading-placeholder" style="height:32px;margin:8px"></div></div>
|
||||
</div>
|
||||
<div class="memory-editor">
|
||||
<div class="editor-toolbar">
|
||||
|
||||
@@ -64,7 +64,10 @@ export async function render() {
|
||||
<div style="margin-bottom:var(--space-md)">
|
||||
<input class="form-input" id="model-search" placeholder="搜索模型(按 ID 或名称过滤)" style="max-width:360px">
|
||||
</div>
|
||||
<div id="providers-list"></div>
|
||||
<div id="providers-list">
|
||||
<div class="config-section"><div class="stat-card loading-placeholder" style="height:120px"></div></div>
|
||||
<div class="config-section"><div class="stat-card loading-placeholder" style="height:120px"></div></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
const state = { config: null, search: '', undoStack: [] }
|
||||
@@ -349,6 +352,12 @@ async function undo(page, state) {
|
||||
// 自动保存(防抖 300ms)
|
||||
let _saveTimer = null
|
||||
let _batchTestAbort = null // 批量测试终止控制器
|
||||
|
||||
export function cleanup() {
|
||||
clearTimeout(_saveTimer)
|
||||
_saveTimer = null
|
||||
if (_batchTestAbort) { _batchTestAbort.abort = true; _batchTestAbort = null }
|
||||
}
|
||||
function autoSave(state) {
|
||||
clearTimeout(_saveTimer)
|
||||
_saveTimer = setTimeout(() => doAutoSave(state), 300)
|
||||
@@ -1074,7 +1083,7 @@ async function fetchRemoteModels(btn, page, state, providerKey) {
|
||||
if (!selected.length) { toast('请至少选择一个模型', 'warning'); return }
|
||||
pushUndo(state)
|
||||
for (const id of selected) {
|
||||
provider.models.push({ id, input: ['text'] })
|
||||
provider.models.push({ id, input: ['text', 'image'] })
|
||||
}
|
||||
overlay.remove()
|
||||
renderProviders(page, state)
|
||||
|
||||
@@ -25,8 +25,8 @@ export async function render() {
|
||||
<h1 class="page-title">服务管理</h1>
|
||||
<p class="page-desc">管理 OpenClaw 服务、检查更新、配置备份</p>
|
||||
</div>
|
||||
<div id="version-bar"></div>
|
||||
<div id="services-list"></div>
|
||||
<div id="version-bar"><div class="stat-card loading-placeholder" style="height:80px;margin-bottom:var(--space-lg)"></div></div>
|
||||
<div id="services-list"><div class="stat-card loading-placeholder" style="height:64px"></div></div>
|
||||
<div class="config-section" id="registry-section">
|
||||
<div class="config-section-title">npm 源设置</div>
|
||||
<div id="registry-bar"></div>
|
||||
@@ -37,7 +37,7 @@ export async function render() {
|
||||
<div id="backup-actions" style="margin-bottom:var(--space-md)">
|
||||
<button class="btn btn-primary btn-sm" data-action="create-backup">创建备份</button>
|
||||
</div>
|
||||
<div id="backup-list"></div>
|
||||
<div id="backup-list"><div class="stat-card loading-placeholder" style="height:48px"></div></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user