新增: Phase2 阶段收尾(Sprint 1-20)

重构:删 5 零引用 crate(df-evolve/plugin/stages/task/traceability)+ 清死模块、ai.rs 拆 11 子 module、ai.ts 拆 6 composable、i18n 拆目录
功能:知识库全栈(df-project/scan + CRUD + 时间线 + 前端)、Settings 拆分、appSettings KV 迁移、模型池、LLM 并发 Semaphore
修复:审批持久化根治、ConditionEngine 默认拒绝、NodeRegistry unimplemented 清除、promote 补偿删除、工具结果截断 50KB、路径校验防 symlink 逃逸
文档:B-03 人工审批设计、决策记录三分档、规格契约自检、经验记录、todo 看板、PROGRESS 更新

详见 PROGRESS.md。src-tauri/儿童每日打卡应用/ 与本项目无关,已排除。
This commit is contained in:
lxy
2026-06-14 14:08:20 +08:00
parent 98393b4908
commit cf017f81e2
167 changed files with 19549 additions and 6886 deletions
+107
View File
@@ -0,0 +1,107 @@
//! 面板与持久化 — togglePanel/toggleMaximize + UI 持久化(restoreUiState/persistUiState)
//! + token 开关 + providers/skills/clearChat
//!
//! 模块级私有:
//! - 启动恢复:模块加载时 restoreUiState()(从 appSettings 读上次 UI 布局)
//! - watch appSettings.cache['df-ai-ui']:loadAll() 异步填充后再次应用 UI 布局
//!
//! 耦合:
//! - togglePanel/toggleMaximize/toggleArchivedFold/toggleSidebar 调 persistUiState
import { watch } from 'vue'
import { aiApi } from '../../api'
import { useAppSettingsStore } from '../../stores/appSettings'
import { state } from '../../stores/ai'
const appSettings = useAppSettingsStore()
// 启动恢复:从 appSettings(SQLite)读取上次的 UI 布局(模块级单例 state)
// detached/docked 不恢复 — 重启后分离窗口必然不存在,必须回 false
export function restoreUiState() {
const s = appSettings.get<any>('df-ai-ui', null)
if (!s) return
if (typeof s.panelOpen === 'boolean') state.panelOpen = s.panelOpen
if (typeof s.maximized === 'boolean') state.maximized = s.maximized
if (typeof s.sidebarOpen === 'boolean') state.sidebarOpen = s.sidebarOpen
if (typeof s.archivedCollapsed === 'boolean') state.archivedCollapsed = s.archivedCollapsed
}
restoreUiState()
// loadAll() 异步填充缓存后,再次应用 UI 布局(模块级 state,watch 仅触发一次)
watch(
() => appSettings.cache['df-ai-ui'],
(v) => {
if (!v) return
const s = v as any
if (typeof s.panelOpen === 'boolean') state.panelOpen = s.panelOpen
if (typeof s.maximized === 'boolean') state.maximized = s.maximized
if (typeof s.sidebarOpen === 'boolean') state.sidebarOpen = s.sidebarOpen
if (typeof s.archivedCollapsed === 'boolean') state.archivedCollapsed = s.archivedCollapsed
},
)
/** 持久化 UI 布局到 appSettings(debounced 写 SQLite) */
export function persistUiState() {
void appSettings.set('df-ai-ui', {
panelOpen: state.panelOpen,
maximized: state.maximized,
sidebarOpen: state.sidebarOpen,
archivedCollapsed: state.archivedCollapsed,
})
}
/** 切换面板展开/收起(收起时同步退出最大化,避免 v-show 卡死 main 交互) */
function togglePanel() {
state.panelOpen = !state.panelOpen
// 关闭面板时同步退出最大化,避免 <main> 被 v-show 隐藏导致其他页面无法交互
if (!state.panelOpen) state.maximized = false
persistUiState()
}
/** 最大化/还原侧栏宽度 */
function toggleMaximize() {
state.maximized = !state.maximized
persistUiState()
}
// ── Providers / Skills / 清屏 ──
/** 拉取 provider 列表(用于顶部 bar 展示与切换) */
async function loadProviders() {
state.providers = await aiApi.listProviders()
}
/** 拉取本机技能列表(用于 `/` 联想,静默失败) */
async function loadSkills() {
try {
state.skills = await aiApi.listSkills()
} catch {
// 静默失败,不影响主流程
}
}
/** 切换活跃 provider(后端 + 本地 state) */
async function setProvider(providerId: string) {
await aiApi.setProvider(providerId)
state.activeProvider = providerId
}
/** 清空当前会话消息(后端 + 本地 state) */
async function clearChat() {
await aiApi.clearChat()
state.messages = []
state.currentText = ''
state.pendingApprovals = []
state.streaming = false
}
export function useAiPanel() {
return {
togglePanel,
toggleMaximize,
loadProviders,
loadSkills,
setProvider,
clearChat,
}
}