mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-30 12:50:14 +08:00
feat: improve gateway compatibility and complete i18n cleanup
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
* 自动注入当前页面上下文到 AI 助手会话
|
||||
*/
|
||||
|
||||
import { t } from '../lib/i18n.js'
|
||||
|
||||
const BOT_ICON = '<svg viewBox="0 0 24 24"><path d="M12 8V4H8"/><rect x="5" y="8" width="14" height="12" rx="2"/><path d="M9 13h0"/><path d="M15 13h0"/><path d="M10 17h4"/></svg>'
|
||||
|
||||
const POS_KEY = 'clawpanel-fab-pos'
|
||||
@@ -41,7 +43,7 @@ export function initAIFab() {
|
||||
export function openAIDrawerWithError(errorCtx) {
|
||||
sessionStorage.setItem('assistant-error-context', JSON.stringify({
|
||||
scene: errorCtx.scene || '',
|
||||
title: errorCtx.title || '操作失败',
|
||||
title: errorCtx.title || t('common.operationFailed'),
|
||||
hint: errorCtx.hint || '',
|
||||
error: truncate(errorCtx.error || '', 3000),
|
||||
ts: Date.now(),
|
||||
@@ -53,7 +55,7 @@ export function openAIDrawerWithError(errorCtx) {
|
||||
_fab.el.classList.add('has-error')
|
||||
} else {
|
||||
import('./toast.js')
|
||||
.then(({ toast }) => toast('已保存诊断上下文,可从侧边栏进入「晴辰助手」继续处理', 'info'))
|
||||
.then(({ toast }) => toast(t('assistant.contextSavedToast', { assistant: t('sidebar.assistant') }), 'info'))
|
||||
.catch(() => {})
|
||||
}
|
||||
} else {
|
||||
@@ -71,7 +73,7 @@ function truncate(str, max) {
|
||||
function createFab() {
|
||||
const fab = document.createElement('button')
|
||||
fab.className = 'ai-fab'
|
||||
fab.title = 'AI 助手'
|
||||
fab.title = t('sidebar.assistant')
|
||||
fab.innerHTML = BOT_ICON
|
||||
document.body.appendChild(fab)
|
||||
|
||||
@@ -228,7 +230,7 @@ function showDragHintOnce(el) {
|
||||
if (!el || localStorage.getItem(HINT_KEY)) return
|
||||
const tip = document.createElement('div')
|
||||
tip.className = 'ai-fab-hint'
|
||||
tip.textContent = '长按可拖动'
|
||||
tip.textContent = t('assistant.dragHint')
|
||||
el.appendChild(tip)
|
||||
localStorage.setItem(HINT_KEY, '1')
|
||||
setTimeout(() => tip.remove(), 4000)
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
* 6. 不在聊天/助手页面时触发(避免打断对话)
|
||||
*/
|
||||
|
||||
import { t } from '../lib/i18n.js'
|
||||
|
||||
const KEYS = {
|
||||
firstOpen: 'clawpanel_first_open',
|
||||
openCount: 'clawpanel_open_count',
|
||||
@@ -66,23 +68,23 @@ export function tryShowEngagement() {
|
||||
_showing = true
|
||||
localStorage.setItem(KEYS.lastShown, String(Date.now()))
|
||||
|
||||
const shareText = '推荐一个开源的 OpenClaw 管理面板 — ClawPanel,一键搭建、便捷管理模型和 Agent,还内置 AI 助手帮你排查问题,小白也能轻松上手:https://claw.qt.cool'
|
||||
const shareText = t('engagement.shareText')
|
||||
|
||||
const overlay = document.createElement('div')
|
||||
overlay.className = 'engage-overlay'
|
||||
overlay.innerHTML = `
|
||||
<div class="engage-modal">
|
||||
<button class="engage-close" title="关闭">×</button>
|
||||
<button class="engage-close" title="${t('common.close')}">×</button>
|
||||
|
||||
<div class="engage-header">
|
||||
<div class="engage-icon">
|
||||
<svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
|
||||
</div>
|
||||
<div class="engage-title">感谢你使用 ClawPanel</div>
|
||||
<div class="engage-title">${t('engagement.title')}</div>
|
||||
</div>
|
||||
|
||||
<div class="engage-message">
|
||||
ClawPanel 是一个<strong>完全开源、免费</strong>的项目,由晴辰云团队专职维护、持续更新。如果它帮到了你,对我们最大的鼓励就是:
|
||||
${t('engagement.message')}
|
||||
</div>
|
||||
|
||||
<div class="engage-actions-grid">
|
||||
@@ -91,8 +93,8 @@ export function tryShowEngagement() {
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="#f59e0b" stroke="#f59e0b" stroke-width="1"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
||||
</div>
|
||||
<div class="engage-action-text">
|
||||
<div class="engage-action-title">GitHub Star</div>
|
||||
<div class="engage-action-desc">点个 Star 是最直接的支持</div>
|
||||
<div class="engage-action-title">${t('engagement.starTitle')}</div>
|
||||
<div class="engage-action-desc">${t('engagement.starDesc')}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="engage-action-card engage-action-share" data-action="copy-share">
|
||||
@@ -100,34 +102,34 @@ export function tryShowEngagement() {
|
||||
<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
|
||||
</div>
|
||||
<div class="engage-action-text">
|
||||
<div class="engage-action-title">分享给朋友</div>
|
||||
<div class="engage-action-desc">复制推荐文案,让更多人知道</div>
|
||||
<div class="engage-action-title">${t('engagement.shareTitle')}</div>
|
||||
<div class="engage-action-desc">${t('engagement.shareDesc')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="engage-section-label">扫码加入社区交流群,第一时间获取更新和帮助</div>
|
||||
<div class="engage-section-label">${t('engagement.communityLabel')}</div>
|
||||
<div class="engage-qrcodes">
|
||||
<a class="engage-qr-item" href="https://qt.cool/c/OpenClaw" target="_blank" rel="noopener">
|
||||
<img src="/images/OpenClaw-QQ.png" alt="QQ 交流群" />
|
||||
<div class="engage-qr-label">QQ 群</div>
|
||||
<img src="/images/OpenClaw-QQ.png" alt="${t('engagement.qqAlt')}" />
|
||||
<div class="engage-qr-label">${t('engagement.qqLabel')}</div>
|
||||
</a>
|
||||
<a class="engage-qr-item" href="https://qt.cool/c/OpenClawWx" target="_blank" rel="noopener">
|
||||
<img src="/images/OpenClawWx.png" alt="微信交流群" />
|
||||
<div class="engage-qr-label">微信群</div>
|
||||
<img src="/images/OpenClawWx.png" alt="${t('engagement.wechatAlt')}" />
|
||||
<div class="engage-qr-label">${t('engagement.wechatLabel')}</div>
|
||||
</a>
|
||||
<a class="engage-qr-item" href="https://qt.cool/c/OpenClawDY" target="_blank" rel="noopener">
|
||||
<img src="/images/OpenClaw-DY.png" alt="抖音交流群" />
|
||||
<div class="engage-qr-label">抖音群</div>
|
||||
<img src="/images/OpenClaw-DY.png" alt="${t('engagement.douyinAlt')}" />
|
||||
<div class="engage-qr-label">${t('engagement.douyinLabel')}</div>
|
||||
</a>
|
||||
<a class="engage-qr-item" href="https://qt.cool/c/feishu" target="_blank" rel="noopener">
|
||||
<img src="https://qt.cool/c/feishu/qr.png" alt="飞书交流群" />
|
||||
<div class="engage-qr-label">飞书群</div>
|
||||
<img src="https://qt.cool/c/feishu/qr.png" alt="${t('engagement.feishuAlt')}" />
|
||||
<div class="engage-qr-label">${t('engagement.feishuLabel')}</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="engage-footer">
|
||||
<span class="engage-today-dismiss">今日不再弹窗</span>
|
||||
<span class="engage-today-dismiss">${t('engagement.dismissToday')}</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -146,7 +148,7 @@ export function tryShowEngagement() {
|
||||
overlay.querySelector('[data-action="copy-share"]').onclick = () => {
|
||||
navigator.clipboard.writeText(shareText).then(() => {
|
||||
const desc = overlay.querySelector('[data-action="copy-share"] .engage-action-desc')
|
||||
if (desc) { desc.textContent = '✅ 已复制,去分享吧!'; setTimeout(() => { desc.textContent = '复制推荐文案,让更多人知道' }, 2000) }
|
||||
if (desc) { desc.textContent = t('engagement.shareCopied'); setTimeout(() => { desc.textContent = t('engagement.shareDesc') }, 2000) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Modal 弹窗组件
|
||||
*/
|
||||
|
||||
import { t } from '../lib/i18n.js'
|
||||
|
||||
// 转义 HTML 属性值,防止双引号等字符破坏 HTML 结构
|
||||
function escapeAttr(str) {
|
||||
if (!str) return ''
|
||||
@@ -24,11 +26,11 @@ export function showConfirm(message) {
|
||||
overlay.className = 'modal-overlay'
|
||||
overlay.innerHTML = `
|
||||
<div class="modal" style="max-width:400px">
|
||||
<div class="modal-title">确认操作</div>
|
||||
<div class="modal-title">${t('common.confirmAction')}</div>
|
||||
<div style="font-size:var(--font-size-sm);color:var(--text-secondary);white-space:pre-wrap;line-height:1.6">${escapeAttr(message)}</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary btn-sm" data-action="cancel">取消</button>
|
||||
<button class="btn btn-danger btn-sm" data-action="confirm">确定</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="cancel">${t('common.cancel')}</button>
|
||||
<button class="btn btn-danger btn-sm" data-action="confirm">${t('common.confirm')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -91,8 +93,8 @@ export function showModal({ title, fields, onConfirm }) {
|
||||
<div class="modal-title">${title}</div>
|
||||
${fieldHtml}
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary btn-sm" data-action="cancel">取消</button>
|
||||
<button class="btn btn-primary btn-sm" data-action="confirm">确定</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="cancel">${t('common.cancel')}</button>
|
||||
<button class="btn btn-primary btn-sm" data-action="confirm">${t('common.confirm')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -156,7 +158,7 @@ export function showContentModal({ title, content, buttons = [], width = 480 })
|
||||
<div class="modal-title">${title}</div>
|
||||
<div class="modal-content-body">${content}</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary btn-sm" data-action="cancel">取消</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="cancel">${t('common.cancel')}</button>
|
||||
${btnsHtml}
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,14 +192,14 @@ export function showUpgradeModal(title) {
|
||||
overlay.className = 'modal-overlay'
|
||||
overlay.innerHTML = `
|
||||
<div class="modal" style="max-width:520px">
|
||||
<div class="modal-title">${title || '升级 OpenClaw'}</div>
|
||||
<div class="modal-title">${title || t('common.upgradeOpenClaw')}</div>
|
||||
<div class="upgrade-progress-wrap">
|
||||
<div class="upgrade-progress-bar"><div class="upgrade-progress-fill" style="width:0%"></div></div>
|
||||
<div class="upgrade-progress-text">准备中...</div>
|
||||
<div class="upgrade-progress-text">${t('common.preparing')}</div>
|
||||
</div>
|
||||
<div class="upgrade-log-box"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary btn-sm" data-action="close">关闭</button>
|
||||
<button class="btn btn-secondary btn-sm" data-action="close">${t('common.close')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -237,7 +239,7 @@ export function showUpgradeModal(title) {
|
||||
_taskBar.className = 'upgrade-task-bar'
|
||||
_taskBar.innerHTML = `
|
||||
<span class="upgrade-task-bar-text">${text.textContent}</span>
|
||||
<button class="btn btn-sm upgrade-task-bar-open">查看详情</button>
|
||||
<button class="btn btn-sm upgrade-task-bar-open">${t('common.viewDetails')}</button>
|
||||
<button class="btn btn-sm btn-ghost upgrade-task-bar-dismiss">×</button>
|
||||
`
|
||||
_taskBar.querySelector('.upgrade-task-bar-open').onclick = reopenModal
|
||||
@@ -276,16 +278,16 @@ export function showUpgradeModal(title) {
|
||||
setProgress(pct) {
|
||||
fill.style.width = pct + '%'
|
||||
let statusText
|
||||
if (pct >= 100) statusText = '完成'
|
||||
else if (pct >= 75) statusText = '正在安装...'
|
||||
else if (pct >= 30) statusText = '正在下载依赖...'
|
||||
else statusText = '准备中...'
|
||||
if (pct >= 100) statusText = t('common.completed')
|
||||
else if (pct >= 75) statusText = t('common.installingProgress')
|
||||
else if (pct >= 30) statusText = t('common.downloadingDependencies')
|
||||
else statusText = t('common.preparing')
|
||||
text.textContent = statusText
|
||||
updateTaskBar(statusText)
|
||||
},
|
||||
setDone(msg) {
|
||||
_finished = true
|
||||
text.textContent = msg || '升级完成'
|
||||
text.textContent = msg || t('common.upgradeCompleted')
|
||||
fill.style.width = '100%'
|
||||
fill.classList.add('done')
|
||||
if (_taskBar) { _taskBar.remove(); _taskBar = null }
|
||||
@@ -293,11 +295,11 @@ export function showUpgradeModal(title) {
|
||||
},
|
||||
setError(msg) {
|
||||
_finished = true
|
||||
text.textContent = msg || '升级失败'
|
||||
text.textContent = msg || t('common.upgradeFailed')
|
||||
fill.classList.add('error')
|
||||
if (_taskBar) {
|
||||
const span = _taskBar.querySelector('.upgrade-task-bar-text')
|
||||
if (span) { span.textContent = msg || '升级失败'; span.style.color = 'var(--error)' }
|
||||
if (span) { span.textContent = msg || t('common.upgradeFailed'); span.style.color = 'var(--error)' }
|
||||
}
|
||||
closeBtn.focus()
|
||||
},
|
||||
|
||||
@@ -139,8 +139,8 @@ export function renderSidebar(el) {
|
||||
<img src="/images/logo.png" alt="ClawPanel">
|
||||
</div>
|
||||
<span class="sidebar-title">ClawPanel</span>
|
||||
<button class="sidebar-collapse-btn" id="btn-sidebar-collapse" title="折叠/展开">${collapsed ? '»' : '«'}</button>
|
||||
<button class="sidebar-close-btn" id="btn-sidebar-close" title="关闭菜单">×</button>
|
||||
<button class="sidebar-collapse-btn" id="btn-sidebar-collapse" title="${t('sidebar.collapse')}">${collapsed ? '»' : '«'}</button>
|
||||
<button class="sidebar-close-btn" id="btn-sidebar-close" title="${t('sidebar.closeMenu')}">×</button>
|
||||
</div>
|
||||
${showSwitcher ? `<div class="instance-switcher" id="instance-switcher">
|
||||
<button class="instance-current" id="btn-instance-toggle">
|
||||
|
||||
Reference in New Issue
Block a user