/** * 关于页面 * 版本信息、项目链接、相关项目、系统环境 */ import { api } from '../lib/tauri-api.js' import { toast } from '../components/toast.js' import { showUpgradeModal, showConfirm } from '../components/modal.js' import { setUpgrading } from '../lib/app-state.js' import { icon, statusIcon } from '../lib/icons.js' export async function render() { const page = document.createElement('div') page.className = 'page' page.innerHTML = `
社群交流
相关项目
参与贡献
快捷链接
关于我们

ClawPanel 基于 Tauri v2 构建,前端 Vanilla JS + Vite,后端 Rust。

MIT License © 2026 武汉晴辰天下网络科技有限公司

` loadData(page) renderCommunity(page) renderProjects(page) renderContribute(page) renderLinks(page) renderCompany(page) return page } async function loadData(page) { const cards = page.querySelector('#version-cards') try { const [version, install] = await Promise.all([ api.getVersionInfo(), api.checkInstallation(), ]) // 尝试从 Tauri API 获取 ClawPanel 自身版本号,失败则 fallback let panelVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.1.0' try { const { getVersion } = await import('@tauri-apps/api/app') panelVersion = await getVersion() } catch { // 非 Tauri 环境或 API 不可用,使用构建时注入的版本号 } // 异步检查前端热更新 let panelUpdateHtml = '检查更新中...' checkHotUpdate(cards, panelVersion) const isInstalled = !!version.current const sourceLabel = version.source === 'official' ? '官方版' : '汉化版' const btnSm = 'padding:2px 8px;font-size:var(--font-size-xs)' const hasRecommended = !!version.recommended const aheadOfRecommended = isInstalled && hasRecommended && !!version.ahead_of_recommended const driftFromRecommended = isInstalled && hasRecommended && !version.is_recommended && !aheadOfRecommended const policyRiskHint = aheadOfRecommended ? `检测到你本地安装的是高于推荐稳定版的 ${version.current},可能存在接口、事件或配置兼容性问题。建议回退到 ${version.recommended};如果你要继续使用高版本,请自行验证兼容性并关注 issue / release。` : '当前面板默认只保证推荐稳定版的兼容性;如果你要尝试其他版本或预览版,请自行验证兼容性。若希望面板尽快支持最新版特性,欢迎提交 issue 告诉我们。' cards.innerHTML = `
ClawPanel
${panelVersion}
${panelUpdateHtml}
OpenClaw · ${sourceLabel}
${version.current || '未安装'}
${isInstalled && hasRecommended ? (aheadOfRecommended ? `当前版本高于推荐稳定版: ${version.recommended} ` : driftFromRecommended ? `推荐稳定版: ${version.recommended} ` : '已是推荐稳定版') : ''} ${version.latest_update_available && version.latest ? `最新上游: ${version.latest}` : ''} ${isInstalled ? `` : ''}
${policyRiskHint}
安装路径
${install.path || '未知'}
${install.installed ? '配置文件存在' : '未找到配置文件'}
` const applyRecommendedBtn = cards.querySelector('#btn-apply-recommended') if (applyRecommendedBtn && version.recommended) { applyRecommendedBtn.onclick = () => doInstall(page, aheadOfRecommended ? '回退到推荐稳定版' : '切换到推荐稳定版', version.source, version.recommended) } // 版本管理 / 安装 const versionMgmtBtn = cards.querySelector('#btn-version-mgmt') if (versionMgmtBtn) { versionMgmtBtn.onclick = () => showVersionPicker(page, version) } // 卸载 const uninstallBtn = cards.querySelector('#btn-uninstall') if (uninstallBtn) { uninstallBtn.onclick = async () => { const confirmed = await showConfirm('确定要卸载 OpenClaw 吗?\n\n这将停止 Gateway 服务并卸载 npm 全局包。\n配置文件(~/.openclaw/)默认保留,可稍后手动删除。') if (!confirmed) return const modal = showUpgradeModal('卸载 OpenClaw') modal.onClose(() => loadData(page)) modal.appendLog('开始卸载 OpenClaw...') let unlistenLog, unlistenProgress, unlistenDone, unlistenError const cleanup = () => { unlistenLog?.(); unlistenProgress?.(); unlistenDone?.(); unlistenError?.() } try { if (window.__TAURI_INTERNALS__) { const { listen } = await import('@tauri-apps/api/event') unlistenLog = await listen('upgrade-log', (e) => modal.appendLog(e.payload)) unlistenProgress = await listen('upgrade-progress', (e) => modal.setProgress(e.payload)) unlistenDone = await listen('upgrade-done', (e) => { cleanup(); modal.setDone(typeof e.payload === 'string' ? e.payload : '卸载完成') }) unlistenError = await listen('upgrade-error', (e) => { cleanup(); modal.setError('卸载失败: ' + (e.payload || '未知错误')) }) await api.uninstallOpenclaw(false) modal.appendLog('后台卸载任务已启动...') } else { const msg = await api.uninstallOpenclaw(false) modal.setDone(typeof msg === 'string' ? msg : '卸载完成') cleanup() } } catch (e) { cleanup() modal.setError('卸载失败: ' + (e?.message || e)) } } } } catch { cards.innerHTML = '
加载失败
' } } /** * 版本选择器弹窗 — 选择版本(汉化版/原版)+ 版本号 */ async function showVersionPicker(page, currentVersion) { const isInstalled = !!currentVersion.current const overlay = document.createElement('div') overlay.className = 'modal-overlay' overlay.innerHTML = ` ` document.body.appendChild(overlay) const select = overlay.querySelector('#oc-version-select') const confirmBtn = overlay.querySelector('#oc-confirm-btn') const hintEl = overlay.querySelector('#oc-action-hint') const radios = overlay.querySelectorAll('input[name="oc-source"]') const lblChinese = overlay.querySelector('#lbl-chinese') const lblOfficial = overlay.querySelector('#lbl-official') const close = () => overlay.remove() overlay.querySelector('[data-action="cancel"]').onclick = close overlay.addEventListener('click', (e) => { if (e.target === overlay) close() }) overlay.addEventListener('keydown', (e) => { if (e.key === 'Escape') close() }) let versionsCache = {} let currentSelect = currentVersion.source === 'chinese' ? 'chinese' : 'official' function updateRadioStyle() { const sel = currentSelect lblChinese.style.borderColor = sel !== 'official' ? 'var(--primary)' : 'var(--border)' lblChinese.style.background = sel !== 'official' ? 'var(--primary-bg, rgba(99,102,241,0.06))' : '' lblOfficial.style.borderColor = sel === 'official' ? 'var(--primary)' : 'var(--border)' lblOfficial.style.background = sel === 'official' ? 'var(--primary-bg, rgba(99,102,241,0.06))' : '' } function updateHint() { const targetSource = currentSelect const targetVer = select.value if (!targetVer || targetVer === '') { hintEl.textContent = ''; confirmBtn.disabled = true; return } const targetTag = select.selectedIndex === 0 ? '(推荐稳定版)' : '(需自测兼容性)' const sameSource = targetSource === (currentVersion.source === 'official' ? 'official' : 'chinese') if (!isInstalled) { confirmBtn.textContent = '安装' hintEl.textContent = `将安装 ${targetSource === 'official' ? '原版' : '汉化版'} ${targetVer}${targetTag}` confirmBtn.disabled = false return } if (!sameSource) { confirmBtn.textContent = '切换' hintEl.innerHTML = `当前: ${currentVersion.source === 'official' ? '原版' : '汉化版'} ${currentVersion.current}${targetSource === 'official' ? '原版' : '汉化版'} ${targetVer}${targetTag}` confirmBtn.disabled = false return } // 同源,比较版本 const parseVer = v => v.split(/[^0-9]/).filter(Boolean).map(Number) const cur = parseVer(currentVersion.current) const tgt = parseVer(targetVer) let cmp = 0 for (let i = 0; i < Math.max(cur.length, tgt.length); i++) { if ((tgt[i] || 0) > (cur[i] || 0)) { cmp = 1; break } if ((tgt[i] || 0) < (cur[i] || 0)) { cmp = -1; break } } if (cmp === 0) { confirmBtn.textContent = '重新安装' hintEl.textContent = `当前已是 ${targetVer}${targetTag}` confirmBtn.disabled = false } else if (cmp > 0) { confirmBtn.textContent = '升级' hintEl.innerHTML = `${currentVersion.current} → ${targetVer}${targetTag}` confirmBtn.disabled = false } else { confirmBtn.textContent = '降级' hintEl.innerHTML = `${currentVersion.current} → ${targetVer}${targetTag}` confirmBtn.disabled = false } } let showNightly = false async function loadVersions(source) { select.innerHTML = '' confirmBtn.disabled = true hintEl.textContent = '' try { if (!versionsCache[source]) { versionsCache[source] = await api.listOpenclawVersions(source) } const allVersions = versionsCache[source] if (!allVersions.length) { select.innerHTML = '' return } const stable = allVersions.filter(v => !v.includes('nightly') && !v.includes('canary') && !v.includes('alpha') && !v.includes('beta') && !v.includes('rc') && !v.includes('dev') && !v.includes('next')) const versions = showNightly ? allVersions : (stable.length > 0 ? stable : allVersions) const nightlyCount = allVersions.length - stable.length select.innerHTML = versions.map((v, idx) => { const isCurrent = isInstalled && v === currentVersion.current && source === (currentVersion.source === 'official' ? 'official' : 'chinese') return `` }).join('') // nightly 切换提示 const toggleEl = overlay.querySelector('#nightly-toggle') if (toggleEl) { if (nightlyCount > 0) { toggleEl.style.display = '' toggleEl.innerHTML = showNightly ? `隐藏预览版 (${nightlyCount})` : `显示预览版 (${nightlyCount})` toggleEl.querySelector('#btn-toggle-nightly').onclick = (e) => { e.preventDefault(); showNightly = !showNightly; loadVersions(source) } } else { toggleEl.style.display = 'none' } } updateHint() } catch (e) { select.innerHTML = `` } } radios.forEach(radio => { radio.addEventListener('change', () => { currentSelect = radio.value updateRadioStyle() loadVersions(currentSelect) }) }) select.addEventListener('change', updateHint) confirmBtn.onclick = () => { const source = currentSelect const ver = select.value const action = confirmBtn.textContent close() doInstall(page, `${action} OpenClaw`, source, ver) } updateRadioStyle() loadVersions(currentSelect) } /** * 执行安装/升级/降级/切换操作(带进度弹窗) */ async function doInstall(page, title, source, version) { const modal = showUpgradeModal(title) modal.onClose(() => loadData(page)) let unlistenLog, unlistenProgress, unlistenDone, unlistenError setUpgrading(true) const cleanup = () => { setUpgrading(false) unlistenLog?.(); unlistenProgress?.(); unlistenDone?.(); unlistenError?.() } try { if (window.__TAURI_INTERNALS__) { const { listen } = await import('@tauri-apps/api/event') unlistenLog = await listen('upgrade-log', (e) => modal.appendLog(e.payload)) unlistenProgress = await listen('upgrade-progress', (e) => modal.setProgress(e.payload)) unlistenDone = await listen('upgrade-done', (e) => { cleanup() modal.setDone(typeof e.payload === 'string' ? e.payload : '操作完成') }) unlistenError = await listen('upgrade-error', async (e) => { cleanup() const errStr = String(e.payload || '未知错误') modal.appendLog(errStr) const { diagnoseInstallError } = await import('../lib/error-diagnosis.js') const fullLog = modal.getLogText() + '\n' + errStr const diagnosis = diagnoseInstallError(fullLog) modal.setError(diagnosis.title) if (diagnosis.hint) modal.appendLog('') if (diagnosis.hint) modal.appendHtmlLog(`${statusIcon('info', 14)} ${diagnosis.hint}`) if (diagnosis.command) modal.appendHtmlLog(`${icon('clipboard', 14)} ${diagnosis.command}`) if (window.__openAIDrawerWithError) { window.__openAIDrawerWithError({ title: diagnosis.title, error: fullLog, scene: title, hint: diagnosis.hint }) } }) await api.upgradeOpenclaw(source, version) modal.appendLog('后台任务已启动,请等待完成...') } else { modal.appendLog('Web 模式:安装过程日志不可用,请等待完成...') const msg = await api.upgradeOpenclaw(source, version) modal.setDone(typeof msg === 'string' ? msg : (msg?.message || '操作完成')) cleanup() } } catch (e) { cleanup() const errStr = String(e) modal.appendLog(errStr) const { diagnoseInstallError } = await import('../lib/error-diagnosis.js') const fullLog = modal.getLogText() + '\n' + errStr const diagnosis = diagnoseInstallError(fullLog) modal.setError(diagnosis.title) } } async function checkHotUpdate(cards, panelVersion) { const el = () => cards.querySelector('#panel-update-meta') try { const info = await api.checkFrontendUpdate() const meta = el() if (!meta) return if (info.updateReady) { // 已下载更新,等待重载 const ver = info.manifest?.version || info.latestVersion || '' meta.innerHTML = ` v${ver} 已就绪 ` meta.querySelector('#btn-hot-reload')?.addEventListener('click', () => { window.location.reload() }) meta.querySelector('#btn-hot-rollback')?.addEventListener('click', async () => { try { await api.rollbackFrontendUpdate() toast('已回退到内嵌版本,重载中...', 'success') setTimeout(() => window.location.reload(), 800) } catch (e) { toast('回退失败: ' + (e.message || e), 'error') } }) } else if (info.hasUpdate) { // 有新版本可下载 const ver = info.latestVersion const manifest = info.manifest || {} const changelog = manifest.changelog || '' meta.innerHTML = ` 新版本: v${ver} ${changelog ? `${changelog}` : ''} 完整安装包 ` meta.querySelector('#btn-hot-download')?.addEventListener('click', async () => { const btn = meta.querySelector('#btn-hot-download') if (btn) { btn.disabled = true; btn.textContent = '下载中...' } try { await api.downloadFrontendUpdate(manifest.url, manifest.hash || '') toast('更新下载完成,点击「重载应用」生效', 'success') checkHotUpdate(cards, panelVersion) } catch (e) { toast('下载失败: ' + (e.message || e), 'error') if (btn) { btn.disabled = false; btn.textContent = '重试' } } }) } else if (!info.compatible) { meta.innerHTML = '需要更新完整安装包 前往官网下载 GitHub' } else { meta.innerHTML = '已是最新' } } catch (err) { const meta = el() if (!meta) return meta.innerHTML = `暂无法检查更新 前往官网下载` } } 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 = `
QQ 交流群
QQ 交流群
微信交流群
微信交流群
抖音交流群
抖音交流群
飞书交流群
飞书交流群
扫码或点击链接加入交流群,反馈问题、获取帮助
加入 QQ 群 加入微信群 加入抖音群 加入飞书群 元宝派社群
2000 人大群,满员自动切换 · 碰到问题可直接在群内反馈
` } const PROJECTS = [ { name: 'OpenClaw', desc: 'AI Agent 框架,支持多模型协作、工具调用、记忆管理', url: 'https://github.com/openclaw/openclaw', }, { name: 'OpenClaw-zh', desc: '我们维护的 OpenClaw 汉化版,3000+ Star,中文界面 + 国内镜像优化', url: 'https://github.com/1186258278/OpenClawChineseTranslation', }, { name: 'ClawPanel', desc: 'OpenClaw 可视化管理面板,Tauri v2 桌面应用', url: 'https://github.com/qingchencloud/clawpanel', gitee: 'https://gitee.com/QtCodeCreators/clawpanel', }, { name: 'ClawApp', desc: '跨平台移动聊天客户端,H5 + 代理服务器架构,支持离线和流式传输', url: 'https://github.com/qingchencloud/clawapp', }, { name: 'cftunnel', desc: '全协议内网穿透工具,Cloud 模式免费 HTTP/WS + Relay 模式自建中继', url: 'https://github.com/qingchencloud/cftunnel', }, ] function renderProjects(page) { const el = page.querySelector('#projects-list') el.innerHTML = PROJECTS.map(p => `
${p.name}
${p.desc}
GitHub ${p.gitee ? `国内镜像` : ''}
`).join('') } const LINKS = [ { label: 'Claw 项目官网', url: 'https://claw.qt.cool', primary: true }, { label: 'OpenClaw 中文翻译', url: 'https://github.com/1186258278/OpenClawChineseTranslation' }, { label: 'ClawApp 手机客户端', url: 'https://clawapp.qt.cool' }, { label: 'cftunnel 内网穿透', url: 'https://cftunnel.qt.cool' }, ] function renderContribute(page) { const el = page.querySelector('#contribute-section') el.innerHTML = `
ClawPanel 是开源项目,欢迎参与贡献!遇到问题请提 Issue,功能建议和代码改进欢迎提 PR。
提交 Issue 提交 PR 贡献指南 查看 Issues
国内镜像:Gitee(无法访问 GitHub 时可用)
` } function renderLinks(page) { const el = page.querySelector('#links-list') el.innerHTML = `
${LINKS.map(l => `${l.label}`).join('')}
` } function renderCompany(page) { const el = page.querySelector('#company-section') el.innerHTML = `
晴辰云
武汉晴辰天下网络科技有限公司
QingchenCloud
官方网站
qingchencloud.com
产品官网
claw.qt.cool
开源仓库
github.com/qingchencloud
商务合作
请通过官网联系我们
我们是 OpenClaw 汉化版(3000+ Star)和 ClawPanel 的作者团队。日常做 AI Agent 相关的产品和开源工具,也接企业私有化部署、定制开发之类的活儿。有事直接群里找我们就行。
` }