chore: release v0.11.3

This commit is contained in:
晴天
2026-04-03 00:16:50 +08:00
parent 61400397ec
commit ce4e9ee8b0
19 changed files with 269 additions and 76 deletions

View File

@@ -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)

View File

@@ -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')

View File

@@ -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">