mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-31 05:10:14 +08:00
chore: release v0.11.3
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* 系统诊断页面
|
||||
* 全面检测 ClawPanel 各项功能状态,快速定位问题
|
||||
*/
|
||||
import { api, getRequestLogs, clearRequestLogs } from '../lib/tauri-api.js'
|
||||
import { api, getRequestLogs, clearRequestLogs, isTauriRuntime } from '../lib/tauri-api.js'
|
||||
import { wsClient } from '../lib/ws-client.js'
|
||||
import { isOpenclawReady, isGatewayRunning } from '../lib/app-state.js'
|
||||
import { isForeignGatewayError, showGatewayConflictGuidance } from '../lib/gateway-ownership.js'
|
||||
@@ -396,7 +396,7 @@ function testWebSocket(page) {
|
||||
const port = config?.gateway?.port || 18789
|
||||
const rawToken = config?.gateway?.auth?.token
|
||||
const token = (typeof rawToken === 'string') ? rawToken : ''
|
||||
const wsHost = window.__TAURI_INTERNALS__ ? `127.0.0.1:${port}` : location.host
|
||||
const wsHost = isTauriRuntime() ? `127.0.0.1:${port}` : location.host
|
||||
const url = `ws://${wsHost}/ws?token=${encodeURIComponent(token)}`
|
||||
|
||||
addLog(`${icon('radio', 14)} ${t('chatDebug.wsAddress', { url })}`)
|
||||
@@ -640,7 +640,7 @@ async function fixPairing(page) {
|
||||
const port = config?.gateway?.port || 18789
|
||||
const rawToken = config?.gateway?.auth?.token
|
||||
const token = (typeof rawToken === 'string') ? rawToken : ''
|
||||
const wsHost = window.__TAURI_INTERNALS__ ? `127.0.0.1:${port}` : location.host
|
||||
const wsHost = isTauriRuntime() ? `127.0.0.1:${port}` : location.host
|
||||
const url = `ws://${wsHost}/ws?token=${encodeURIComponent(token)}`
|
||||
|
||||
const ws = new WebSocket(url)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 聊天页面 - 完整版,对接 OpenClaw Gateway
|
||||
* 支持:流式响应、Markdown 渲染、会话管理、Agent 选择、快捷指令
|
||||
*/
|
||||
import { api, invalidate } from '../lib/tauri-api.js'
|
||||
import { api, invalidate, isTauriRuntime } from '../lib/tauri-api.js'
|
||||
import { navigate } from '../router.js'
|
||||
import { wsClient, uuid } from '../lib/ws-client.js'
|
||||
import { renderMarkdown } from '../lib/markdown.js'
|
||||
@@ -1233,7 +1233,7 @@ async function connectGateway() {
|
||||
// 未连接,发起新连接
|
||||
const config = await api.readOpenclawConfig()
|
||||
const gw = config?.gateway || {}
|
||||
const host = window.__TAURI_INTERNALS__ ? `127.0.0.1:${gw.port || 18789}` : location.host
|
||||
const host = isTauriRuntime() ? `127.0.0.1:${gw.port || 18789}` : location.host
|
||||
const token = gw.auth?.token || gw.authToken || ''
|
||||
wsClient.connect(host, token)
|
||||
} catch (e) {
|
||||
@@ -2970,7 +2970,8 @@ async function callHostedAI(messages, onChunk) {
|
||||
|
||||
if (!config.baseUrl || !config.model) throw new Error(t('chat.hostedModelNotConfigured'))
|
||||
|
||||
let base = config.baseUrl.replace(/\/+$/, '').replace(/\/chat\/completions\/?$/, '').replace(/\/completions\/?$/, '').replace(/\/messages\/?$/, '').replace(/\/models\/?$/, '')
|
||||
const apiType = normalizeHostedApiType(config.apiType)
|
||||
const base = normalizeHostedBaseUrl(config.baseUrl, apiType)
|
||||
if (_hostedAbort) { _hostedAbort.abort(); _hostedAbort = null }
|
||||
_hostedAbort = new AbortController()
|
||||
const signal = _hostedAbort.signal
|
||||
@@ -3010,6 +3011,51 @@ async function callHostedAI(messages, onChunk) {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHostedApiType(raw) {
|
||||
const type = (raw || '').trim()
|
||||
if (type === 'anthropic' || type === 'anthropic-messages') return 'anthropic-messages'
|
||||
if (type === 'google-gemini' || type === 'google-generative-ai') return 'google-generative-ai'
|
||||
if (type === 'ollama') return 'ollama'
|
||||
return 'openai-completions'
|
||||
}
|
||||
|
||||
function normalizeHostedBaseUrl(raw, apiType) {
|
||||
let base = (raw || '').trim()
|
||||
if (!base) throw new Error(t('chat.hostedModelNotConfigured'))
|
||||
if (/^\/\//.test(base)) base = `http:${base}`
|
||||
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(base) && /^(localhost|(?:\d{1,3}\.){3}\d{1,3}|\[[0-9a-f:.]+\]|[^/\s]+:\d+)(?:\/|$)/i.test(base)) {
|
||||
base = `http://${base}`
|
||||
}
|
||||
let url
|
||||
try {
|
||||
url = new URL(base)
|
||||
} catch {
|
||||
throw new Error(t('chat.hostedModelUrlInvalid'))
|
||||
}
|
||||
if (!/^https?:$/.test(url.protocol) || url.hostname === 'tauri.localhost') {
|
||||
throw new Error(t('chat.hostedModelUrlInvalid'))
|
||||
}
|
||||
base = `${url.origin}${url.pathname}`
|
||||
.replace(/\/+$/, '')
|
||||
.replace(/\/api\/chat\/?$/, '')
|
||||
.replace(/\/api\/generate\/?$/, '')
|
||||
.replace(/\/api\/tags\/?$/, '')
|
||||
.replace(/\/api\/?$/, '')
|
||||
.replace(/\/chat\/completions\/?$/, '')
|
||||
.replace(/\/completions\/?$/, '')
|
||||
.replace(/\/responses\/?$/, '')
|
||||
.replace(/\/messages\/?$/, '')
|
||||
.replace(/\/models\/?$/, '')
|
||||
const type = normalizeHostedApiType(apiType)
|
||||
if (type === 'anthropic-messages') {
|
||||
if (!base.endsWith('/v1')) base += '/v1'
|
||||
return base
|
||||
}
|
||||
if (type === 'google-generative-ai') return base
|
||||
if (/:(11434)$/i.test(base) && !base.endsWith('/v1')) return `${base}/v1`
|
||||
return base
|
||||
}
|
||||
|
||||
function appendHostedOutput(text) {
|
||||
if (!text || !_messagesEl) return
|
||||
const wrap = document.createElement('div')
|
||||
|
||||
@@ -118,6 +118,7 @@ async function loadDashboardData(page, fullRefresh = false) {
|
||||
api.readOpenclawConfig(),
|
||||
// 版本信息:首次加载或手动刷新时才查询(避免 ARM 设备上频繁查 npm registry)
|
||||
(!_dashboardInitialized || fullRefresh || !_dashboardVersionCache) ? api.getVersionInfo() : Promise.resolve(_dashboardVersionCache),
|
||||
api.readPanelConfig(),
|
||||
]), 15000)
|
||||
const secondaryP = withTimeout(Promise.allSettled([
|
||||
api.listAgents(),
|
||||
@@ -127,12 +128,13 @@ async function loadDashboardData(page, fullRefresh = false) {
|
||||
const logsP = api.readLogTail('gateway', 20).catch(() => '')
|
||||
|
||||
// 第一波:服务状态 + 配置 + 版本 → 立即渲染统计卡片
|
||||
const [servicesRes, configRes, versionRes] = await coreP
|
||||
const [servicesRes, configRes, versionRes, panelConfigRes] = await coreP
|
||||
const services = servicesRes.status === 'fulfilled' ? servicesRes.value : []
|
||||
const version = (versionRes.status === 'fulfilled' && versionRes.value)
|
||||
? (_dashboardVersionCache = versionRes.value)
|
||||
: (_dashboardVersionCache || {})
|
||||
const config = configRes.status === 'fulfilled' ? configRes.value : null
|
||||
const panelConfig = panelConfigRes.status === 'fulfilled' ? panelConfigRes.value : null
|
||||
const gw = services.find(s => s.label === 'ai.openclaw.gateway')
|
||||
const shouldLoadStatusSummary = gw?.running === true
|
||||
if (!shouldLoadStatusSummary) {
|
||||
@@ -166,7 +168,7 @@ async function loadDashboardData(page, fullRefresh = false) {
|
||||
}
|
||||
}
|
||||
|
||||
renderStatCards(page, services, version, [], config)
|
||||
renderStatCards(page, services, version, [], config, panelConfig)
|
||||
if (gw) {
|
||||
maybeShowForeignGatewayBindingPrompt({
|
||||
service: gw,
|
||||
@@ -191,7 +193,7 @@ async function loadDashboardData(page, fullRefresh = false) {
|
||||
}
|
||||
}
|
||||
|
||||
renderStatCards(page, services, version, agents, config)
|
||||
renderStatCards(page, services, version, agents, config, panelConfig)
|
||||
renderOverview(page, services, mcpConfig, backups, config, agents, statusSummary)
|
||||
|
||||
// 第三波:日志(最低优先级)
|
||||
@@ -212,7 +214,7 @@ async function openGatewayConflict(page, error = null, reason = null) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderStatCards(page, services, version, agents, config) {
|
||||
function renderStatCards(page, services, version, agents, config, panelConfig) {
|
||||
const cardsEl = page.querySelector('#stat-cards')
|
||||
const gw = services.find(s => s.label === 'ai.openclaw.gateway')
|
||||
const foreignGateway = isForeignGatewayService(gw)
|
||||
@@ -225,6 +227,7 @@ function renderStatCards(page, services, version, agents, config) {
|
||||
const cliSourceLabel = { standalone: t('dashboard.cliSourceStandalone'), 'npm-zh': t('dashboard.cliSourceNpmZh'), 'npm-official': t('dashboard.cliSourceNpmOfficial'), 'npm-global': t('dashboard.cliSourceNpmGlobal') }[version.cli_source] || t('dashboard.cliSourceUnknown')
|
||||
const installCount = dedupeOpenclawInstallations(version.all_installations).length
|
||||
const multiInstall = installCount > 1
|
||||
const cliBound = !!(panelConfig?.openclawCliPath && String(panelConfig.openclawCliPath).trim())
|
||||
|
||||
const defaultAgent = agents.find(a => a.id === 'main')?.name || 'main'
|
||||
const modelCount = config?.models?.providers ? Object.values(config.models.providers).reduce((acc, p) => acc + (p.models?.length || 0), 0) : 0
|
||||
@@ -252,13 +255,15 @@ function renderStatCards(page, services, version, agents, config) {
|
||||
</div>
|
||||
<div class="stat-card-value">${version.current || t('common.unknown')}</div>
|
||||
<div class="stat-card-meta">${versionMeta}</div>
|
||||
${version.cli_path ? `<div class="stat-card-meta" style="margin-top:2px;font-size:11px;opacity:0.7" title="${escapeHtml(version.cli_path)}">${cliSourceLabel}${multiInstall ? ' · <span style="color:var(--warning)">' + t('dashboard.installCount', { count: installCount }) + '</span>' : ''}</div>` : ''}
|
||||
${multiInstall
|
||||
${version.cli_path ? `<div class="stat-card-meta" style="margin-top:2px;font-size:11px;opacity:0.7" title="${escapeHtml(version.cli_path)}">${cliSourceLabel}${multiInstall ? ' · <span' + (cliBound ? '' : ' style="color:var(--warning)"') + '>' + t('dashboard.installCount', { count: installCount }) + '</span>' : ''}</div>` : ''}
|
||||
${multiInstall && !cliBound
|
||||
? `<div class="stat-card-meta" style="margin-top:8px;color:var(--warning);line-height:1.6">${t('dashboard.multiInstallCardHint')}</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:10px">
|
||||
<button class="btn btn-secondary btn-xs" data-action="resolve-multi-install">${t('dashboard.viewGuidance')}</button>
|
||||
<button class="btn btn-primary btn-xs" data-action="open-settings">${t('dashboard.goSettings')}</button>
|
||||
</div>`
|
||||
: multiInstall && cliBound
|
||||
? `<div class="stat-card-meta" style="margin-top:4px;color:var(--text-tertiary);font-size:11px">✓ ${t('dashboard.multiInstallBoundOk', { count: installCount })}</div>`
|
||||
: ''}
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
|
||||
Reference in New Issue
Block a user