mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-07-01 20:51:34 +08:00
feat: prepare v0.18.0 release
This commit is contained in:
@@ -9,12 +9,16 @@ import { toast } from './toast.js'
|
||||
const APP_VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.0.0'
|
||||
import { t, getLang, setLang, getAvailableLangs } from '../lib/i18n.js'
|
||||
import { isFeatureAvailable } from '../lib/feature-gates.js'
|
||||
import { getKernelSnapshot } from '../lib/kernel.js'
|
||||
import { getKernelSnapshot, recommendedIsNewer } from '../lib/kernel.js'
|
||||
import { triggerKernelUpgrade } from '../lib/kernel-upgrade.js'
|
||||
import { getActiveEngine, getActiveEngineId, listEngines, needsInitialEngineChoice, isEngineSetupDeferred, switchEngine, onEngineChange } from '../lib/engine-manager.js'
|
||||
|
||||
// 当用户点 "暂时不升级" 时,本地会话内不再显示升级提示
|
||||
const SS_DISMISSED_KERNEL_UPGRADE = 'clawpanel_kernel_upgrade_dismissed'
|
||||
const KERNEL_POLICY_TTL = 5 * 60 * 1000
|
||||
let _kernelPolicyInfo = null
|
||||
let _kernelPolicyFetchedAt = 0
|
||||
let _kernelPolicyLoading = false
|
||||
|
||||
function NAV_ITEMS_FULL() { return [
|
||||
{
|
||||
@@ -282,6 +286,7 @@ export function renderSidebar(el) {
|
||||
|
||||
el.innerHTML = html
|
||||
window.dispatchEvent(new CustomEvent('clawpanel:site-message-launcher-mounted'))
|
||||
_ensureKernelPolicyInfo(el)
|
||||
|
||||
// 应用折叠态(桌面端)
|
||||
_setDesktopSidebarCollapsed(collapsed)
|
||||
@@ -422,6 +427,34 @@ export function renderSidebar(el) {
|
||||
|
||||
function _escSidebar(s) { return String(s || '').replace(/</g, '<').replace(/>/g, '>') }
|
||||
|
||||
function _kernelPolicyTarget(snap) {
|
||||
return _kernelPolicyInfo?.recommended || snap?.target || ''
|
||||
}
|
||||
|
||||
function _isRunningGatewayBelowTarget(snap) {
|
||||
if (!snap?.version) return false
|
||||
const target = _kernelPolicyTarget(snap)
|
||||
return target ? recommendedIsNewer(target, snap.version) : !snap.isLatest
|
||||
}
|
||||
|
||||
function _ensureKernelPolicyInfo(el) {
|
||||
const snap = getKernelSnapshot()
|
||||
if (getActiveEngineId() !== 'openclaw' || !snap?.version) return
|
||||
const now = Date.now()
|
||||
if (_kernelPolicyLoading) return
|
||||
if (_kernelPolicyInfo && now - _kernelPolicyFetchedAt < KERNEL_POLICY_TTL) return
|
||||
|
||||
_kernelPolicyLoading = true
|
||||
api.getVersionInfo()
|
||||
.then(info => {
|
||||
_kernelPolicyInfo = info || null
|
||||
_kernelPolicyFetchedAt = Date.now()
|
||||
if (el?.isConnected) renderSidebar(el)
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => { _kernelPolicyLoading = false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染"内核可升级"卡片。
|
||||
*
|
||||
@@ -429,7 +462,7 @@ function _escSidebar(s) { return String(s || '').replace(/</g, '<').replace(/
|
||||
* - 当前引擎是 openclaw
|
||||
* - 已成功握手 Gateway(snapshot 有 version)
|
||||
* - 高于硬地板(< floor 由 floor-blocker 接管)
|
||||
* - 低于推荐目标(!isLatest)
|
||||
* - 运行中的 Gateway 低于推荐目标
|
||||
* - 用户未在本会话中点击过 "暂不升级"
|
||||
*
|
||||
* 不满足任何一条返回空串。
|
||||
@@ -441,13 +474,13 @@ function _renderKernelUpgradeHint() {
|
||||
const snap = getKernelSnapshot()
|
||||
if (!snap || !snap.version) return ''
|
||||
if (!snap.aboveFloor) return '' // floor-blocker 处理
|
||||
if (snap.isLatest) return '' // 已经是推荐目标
|
||||
if (!_isRunningGatewayBelowTarget(snap)) return '' // 运行中的 Gateway 已经达到推荐目标
|
||||
|
||||
const arrowIcon = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg>'
|
||||
const sparkIcon = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L13.5 8.5 20 10l-6.5 1.5L12 18l-1.5-6.5L4 10l6.5-1.5z"/></svg>'
|
||||
|
||||
const fromLabel = snap.versionLabel || snap.version
|
||||
const toLabel = snap.target || ''
|
||||
const toLabel = _kernelPolicyTarget(snap)
|
||||
|
||||
return `
|
||||
<div class="kernel-upgrade-hint" id="kernel-upgrade-hint" role="button" tabindex="0">
|
||||
|
||||
@@ -65,7 +65,7 @@ export function normalizeSiteMessagePayload(payload = {}) {
|
||||
export function openSiteMessageCenter({ force = false, tab = null } = {}) {
|
||||
const visible = getVisibleMessages()
|
||||
if (!force && !visible.notifications.length && !visible.announcements.length) return
|
||||
_activeTab = tab || (visible.notifications.length ? 'notifications' : 'announcements')
|
||||
_activeTab = tab || getPreferredTab()
|
||||
renderModal()
|
||||
}
|
||||
|
||||
@@ -81,9 +81,11 @@ function bindLaunchers() {
|
||||
}
|
||||
|
||||
function updateLauncherBadge() {
|
||||
const count = getVisibleMessages().notifications.length + getVisibleMessages().announcements.length
|
||||
const visible = getVisibleMessages()
|
||||
const count = isClosedToday() ? 0 : visible.notifications.length + visible.announcements.length
|
||||
document.querySelectorAll(LAUNCHER_SELECTOR).forEach((launcher) => {
|
||||
launcher.classList.toggle('has-unread', count > 0)
|
||||
launcher.classList.toggle('is-muted-today', isClosedToday())
|
||||
const badge = launcher.querySelector('.site-message-tool-badge, .site-message-fab-badge')
|
||||
if (badge) badge.textContent = count > 9 ? '9+' : String(count || '')
|
||||
})
|
||||
@@ -94,6 +96,12 @@ function renderModal() {
|
||||
if (old) old.remove()
|
||||
|
||||
const visible = getVisibleMessages()
|
||||
const dismissed = getDismissedMessages()
|
||||
const displayCounts = {
|
||||
notifications: visible.notifications.length + dismissed.notifications.length,
|
||||
announcements: visible.announcements.length + dismissed.announcements.length,
|
||||
}
|
||||
const activeVisibleCount = visible[_activeTab]?.length || 0
|
||||
|
||||
const overlay = document.createElement('div')
|
||||
overlay.id = 'site-message-overlay'
|
||||
@@ -107,21 +115,25 @@ function renderModal() {
|
||||
<span class="site-message-title-icon" aria-hidden="true">${ICON_BELL}</span>
|
||||
<div>
|
||||
<h2>${t('siteMessages.title')}</h2>
|
||||
<p>${formatSummary(visible)}</p>
|
||||
<p>${formatSummary(displayCounts)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="site-message-tabs" role="tablist">
|
||||
${renderTab('notifications', t('siteMessages.notifications'), ICON_BELL, visible.notifications.length)}
|
||||
${renderTab('announcements', t('siteMessages.announcements'), ICON_SEND, visible.announcements.length)}
|
||||
${renderTab('notifications', t('siteMessages.notifications'), ICON_BELL, displayCounts.notifications)}
|
||||
${renderTab('announcements', t('siteMessages.announcements'), ICON_SEND, displayCounts.announcements)}
|
||||
</div>
|
||||
<button class="site-message-close" type="button" title="${t('common.close')}">${ICON_X}</button>
|
||||
</header>
|
||||
<div class="site-message-body">
|
||||
${_activeTab === 'notifications' ? renderNotifications(visible.notifications) : renderAnnouncements(visible.announcements)}
|
||||
${_activeTab === 'notifications'
|
||||
? renderNotifications(visible.notifications, dismissed.notifications)
|
||||
: renderAnnouncements(visible.announcements, dismissed.announcements)}
|
||||
</div>
|
||||
<footer class="site-message-footer">
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-site-message-today>${t('siteMessages.closeToday')}</button>
|
||||
<button class="btn btn-primary btn-sm" type="button" data-site-message-dismiss>${t('siteMessages.closeCurrent')}</button>
|
||||
${activeVisibleCount
|
||||
? `<button class="btn btn-primary btn-sm" type="button" data-site-message-dismiss>${getDismissButtonLabel()}</button>`
|
||||
: `<button class="btn btn-primary btn-sm" type="button" data-site-message-close-current>${t('common.close')}</button>`}
|
||||
</footer>
|
||||
</section>
|
||||
`
|
||||
@@ -142,8 +154,9 @@ function renderTab(tab, label, icon, count) {
|
||||
`
|
||||
}
|
||||
|
||||
function renderNotifications(items) {
|
||||
function renderNotifications(items, dismissedItems = []) {
|
||||
if (!items.length) {
|
||||
if (dismissedItems.length) return renderDismissedState('notifications', dismissedItems.length)
|
||||
return `
|
||||
<div class="site-message-empty">
|
||||
<span class="site-message-empty-icon" aria-hidden="true">${ICON_BELL}</span>
|
||||
@@ -177,8 +190,9 @@ function renderNotifications(items) {
|
||||
`
|
||||
}
|
||||
|
||||
function renderAnnouncements(items) {
|
||||
function renderAnnouncements(items, dismissedItems = []) {
|
||||
if (!items.length) {
|
||||
if (dismissedItems.length) return renderDismissedState('announcements', dismissedItems.length)
|
||||
return `
|
||||
<div class="site-message-empty">
|
||||
<span class="site-message-empty-icon" aria-hidden="true">${ICON_SEND}</span>
|
||||
@@ -213,6 +227,18 @@ function renderAnnouncements(items) {
|
||||
`
|
||||
}
|
||||
|
||||
function renderDismissedState(tab, count) {
|
||||
const icon = tab === 'notifications' ? ICON_BELL : ICON_SEND
|
||||
return `
|
||||
<div class="site-message-empty site-message-dismissed-empty">
|
||||
<span class="site-message-empty-icon" aria-hidden="true">${icon}</span>
|
||||
<strong>${t('siteMessages.dismissedTitle')}</strong>
|
||||
<p>${t('siteMessages.dismissedHint', { count })}</p>
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-site-message-restore>${t('siteMessages.restoreDismissed')}</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function renderMessageCta(item, prominent = false) {
|
||||
if (!item.ctaText || !item.ctaUrl) return ''
|
||||
return `<a class="${prominent ? 'site-message-card-cta' : 'site-message-inline-cta'}" href="${escapeAttr(item.ctaUrl)}" target="_blank" rel="noopener">${escapeHtml(item.ctaText)}</a>`
|
||||
@@ -223,12 +249,19 @@ function bindModalEvents(overlay) {
|
||||
overlay.querySelector('[data-site-message-today]')?.addEventListener('click', () => {
|
||||
localStorage.setItem(TODAY_CLOSE_KEY, todayKey())
|
||||
closeModal()
|
||||
updateLauncherBadge()
|
||||
})
|
||||
overlay.querySelector('[data-site-message-dismiss]')?.addEventListener('click', () => {
|
||||
dismissItems(getVisibleMessages()[_activeTab])
|
||||
closeModal()
|
||||
updateLauncherBadge()
|
||||
})
|
||||
overlay.querySelector('[data-site-message-close-current]')?.addEventListener('click', closeModal)
|
||||
overlay.querySelector('[data-site-message-restore]')?.addEventListener('click', () => {
|
||||
restoreItems(_messages[_activeTab])
|
||||
renderModal()
|
||||
updateLauncherBadge()
|
||||
})
|
||||
overlay.querySelectorAll('[data-site-message-tab]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
_activeTab = btn.dataset.siteMessageTab || 'notifications'
|
||||
@@ -255,12 +288,23 @@ function dismissItems(items) {
|
||||
}
|
||||
}
|
||||
|
||||
function restoreItems(items) {
|
||||
for (const item of items || []) {
|
||||
const key = dismissStorageKey(item)
|
||||
if (key) localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
|
||||
function shouldAutoOpen() {
|
||||
if (localStorage.getItem(TODAY_CLOSE_KEY) === todayKey()) return false
|
||||
if (isClosedToday()) return false
|
||||
const visible = getVisibleMessages()
|
||||
return visible.notifications.length > 0 || visible.announcements.length > 0
|
||||
}
|
||||
|
||||
function isClosedToday() {
|
||||
return localStorage.getItem(TODAY_CLOSE_KEY) === todayKey()
|
||||
}
|
||||
|
||||
function getVisibleMessages() {
|
||||
return {
|
||||
notifications: _messages.notifications.filter(item => !isDismissed(item)),
|
||||
@@ -268,6 +312,27 @@ function getVisibleMessages() {
|
||||
}
|
||||
}
|
||||
|
||||
function getDismissedMessages() {
|
||||
return {
|
||||
notifications: _messages.notifications.filter(isDismissed),
|
||||
announcements: _messages.announcements.filter(isDismissed),
|
||||
}
|
||||
}
|
||||
|
||||
function getPreferredTab() {
|
||||
const visible = getVisibleMessages()
|
||||
if (visible.notifications.length) return 'notifications'
|
||||
if (visible.announcements.length) return 'announcements'
|
||||
const dismissed = getDismissedMessages()
|
||||
if (dismissed.notifications.length) return 'notifications'
|
||||
return 'announcements'
|
||||
}
|
||||
|
||||
function getDismissButtonLabel() {
|
||||
if (_activeTab === 'notifications') return t('siteMessages.dismissCurrentNotifications')
|
||||
return t('siteMessages.dismissCurrentAnnouncements')
|
||||
}
|
||||
|
||||
function normalizePayload(payload = {}) {
|
||||
const notifications = []
|
||||
const announcements = []
|
||||
|
||||
Reference in New Issue
Block a user