新增: 后台会话系统通知与托盘运行态

- 完成/失败/需介入三类系统通知,主窗聚焦静默
- 同类通知五秒合并,用户主动停止不通知
- 托盘菜单与悬浮提示实时显运行中会话数
- 关窗遇运行会话提示后台续跑,每进程一次
- 侧栏常驻徽章显后台运行会话数
This commit is contained in:
lxy
2026-10-01 19:35:20 +08:00
parent 66c6553cdf
commit e1dd0bc3a3
13 changed files with 441 additions and 24 deletions
+33 -2
View File
@@ -51,12 +51,14 @@
<!-- Footer -->
<div class="sidebar-footer">
<!-- AI 面板切换按钮 -->
<button class="nav-link ai-toggle-btn" :class="{ 'ai-toggle-btn--active': aiStore.state.panelOpen }" @click="aiStore.togglePanel()" :title="collapsed ? $t('nav.aiPanel') : undefined">
<!-- AI 面板切换按钮(footer 常驻跨路由渲染,承担后台运行中会话的全局可见标识) -->
<button class="nav-link ai-toggle-btn" :class="{ 'ai-toggle-btn--active': aiStore.state.panelOpen }" @click="aiStore.togglePanel()" :title="runningConvCount > 0 ? `${runningConvCount} 个会话运行中` : (collapsed ? $t('nav.aiPanel') : undefined)">
<span class="nav-link-icon ai-spark-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/><path d="M19 3v3M21 4.5h-3M5 17v3M6.5 18.5h-3"/></svg>
</span>
<span v-if="!collapsed" class="nav-link-text">{{ $t('nav.aiPanel') }}</span>
<span v-if="!collapsed && runningConvCount > 0" class="nav-link-badge">{{ runningConvCount }}</span>
<span v-if="collapsed && runningConvCount > 0" class="ai-running-dot"></span>
<span v-if="!collapsed" class="ai-toggle-shortcut">⌘I</span>
</button>
<router-link to="/settings" class="nav-link" active-class="nav-link--active" :title="collapsed ? $t('nav.settings') : undefined">
@@ -83,6 +85,7 @@ import { computed, ref, onMounted, onBeforeUnmount } from 'vue'
import { useAiStore } from '@/stores/ai'
import { useKnowledgeStore } from '@/stores/knowledge'
import { useAppSettingsStore } from '@/stores/appSettings'
import { convStates } from '@/composables/ai/aiShared'
const aiStore = useAiStore()
const knowledgeStore = useKnowledgeStore()
@@ -135,6 +138,17 @@ onBeforeUnmount(() => {
// 知识库候选数量(侧栏 badge)— 解包 computed 取值保持响应式
const candidateCount = computed(() => knowledgeStore.candidateCount)
// 全局运行中会话数(AI 面板关闭或切到 Tasks/Ideas 等页面时,后台生成在 footer 常驻按钮上仍可见)。
// 真相源 convStates(reactive Map,消费后端 AiConvStateChanged,写入在 isCurrent 守卫之前故后台会话也更新),
// 非终止三态计入,与 stores/ai.ts isGenerating 桥接语义一致;computed 内遍历 Map 即建立响应式依赖
const runningConvCount = computed(() => {
let n = 0
for (const s of convStates.values()) {
if (s === 'generating' || s === 'stopping' || s === 'compressed') n++
}
return n
})
const primaryNav = [
{ path: '/dashboard', label: 'nav.overview', badge: null,
svg: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>' },
@@ -349,6 +363,23 @@ const secondaryNav = [
border-radius: var(--df-radius-sm);
}
/* 收缩态运行中标识:绿点钉在按钮右上角(展开态走 nav-link-badge 数字徽章,无空间放数字)。
脉冲范式与 ConversationSidebar 的 ai-conv-gen-dot 同源 */
.ai-running-dot {
position: absolute;
top: 2px;
right: 6px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--df-accent);
animation: ai-sidebar-gen-pulse 1.4s ease-in-out infinite;
}
@keyframes ai-sidebar-gen-pulse {
0%, 100% { opacity: 0.35; transform: scale(0.85); }
50% { opacity: 1; transform: scale(1.15); }
}
/* — Footer — */
.sidebar-footer {
padding: 6px 8px;