优化: UI/UX遗留批(审计/AI命令层/前端组件,会话前基线收尾)
- AuditLog +298(筛选/详情/i18n)+ audit 后端 record/mod - AI 命令层:generate_image +81 / fetch_url / fetch_search / skills / tool_registry / tools/file / provider / conversation - 前端组件:AiChat/TopBar/ConversationSidebar/GitChanges/ApprovalPopup/Dashboard/ProjectDetail 等 30+ + composables + i18n - 诊断文档: aichat历史会话实证诊断-2026-08-04 + project_soft_delete 测试
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! 面板与持久化 — togglePanel/toggleMaximize + UI 持久化(restoreUiState/persistUiState)
|
||||
//! 面板与持久化 — togglePanel/toggleMaximize/cyclePanelMode + UI 持久化(restoreUiState/persistUiState)
|
||||
//! + token 开关 + providers/skills/clearChat
|
||||
//!
|
||||
//! 模块级私有:
|
||||
@@ -13,7 +13,9 @@ import { aiApi } from '@/api'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { state } from '@/stores/ai'
|
||||
import { setStreaming } from './streamingGuard'
|
||||
import type { AiProviderConfig } from '@/api/types'
|
||||
import { nextMsgId } from './aiShared'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import type { AiMessage, AiProviderConfig } from '@/api/types'
|
||||
|
||||
const appSettings = useAppSettingsStore()
|
||||
|
||||
@@ -46,16 +48,25 @@ function applyUiState(s: any) {
|
||||
state.sidebarWidth = clampSidebarWidth(s.sidebarWidth)
|
||||
}
|
||||
|
||||
// 启动恢复:从 appSettings(SQLite)读取上次的 UI 布局(模块级单例 state)
|
||||
// 启动恢复:从 appSettings(SQLite)读取上次的 UI 布局 + activeProvider(模块级单例 state)
|
||||
// F-260616-10: activeProvider 恢复对齐 df-ai-active-conv 模式;loadProviders 后还会二次校验
|
||||
// DB is_default(双保险:localStorage 可能与 DB 不同步时以 DB 为准)。
|
||||
function restoreUiState() {
|
||||
applyUiState(appSettings.get<any>('df-ai-ui', null))
|
||||
const savedProvider = appSettings.get<string | null>('df-ai-active-provider', null)
|
||||
if (savedProvider) state.activeProvider = savedProvider
|
||||
}
|
||||
restoreUiState()
|
||||
|
||||
// F-260616-10: 启动恢复 activeProvider(对齐 df-ai-active-conv 模式)。
|
||||
// loadProviders 后还会二次校验 DB is_default(双保险:localStorage 可能与 DB 不同步时以 DB 为准)。
|
||||
const savedProvider = appSettings.get<string | null>('df-ai-active-provider', null)
|
||||
if (savedProvider) state.activeProvider = savedProvider
|
||||
// 循环依赖兜底:stores/ai ↔ useAiPanel 互 import,顶层 restoreUiState 执行时 state 可能尚未
|
||||
// 初始化(TDZ)。冷启动靠 appSettings 缓存为空 → applyUiState(null) 提前 return 绕过访问;
|
||||
// 但 HMR 热重载 stores/ai.ts 时 appSettings 已有缓存,restoreUiState 真正访问 state 触发
|
||||
// ReferenceError: Cannot access 'state' before initialization。对齐 stores/ai.ts __bindMessages
|
||||
// 既存模式:try 失败则微任务延迟重试,待 state 就绪后落库恢复。
|
||||
try {
|
||||
restoreUiState()
|
||||
} catch {
|
||||
Promise.resolve().then(restoreUiState)
|
||||
}
|
||||
|
||||
// loadAll() 异步填充缓存后,再次应用 UI 布局(模块级 state,watch 仅触发一次)
|
||||
watch(() => appSettings.cache['df-ai-ui'], (v) => applyUiState(v as any))
|
||||
@@ -96,6 +107,34 @@ function toggleMaximize() {
|
||||
persistUiState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ctrl+I 三态循环(关闭 → 分栏打开 → 最大化 → 关闭)。
|
||||
*
|
||||
* 与 togglePanel(二态开关)区分:本函数仅供全局快捷键 Ctrl+I 用;UI 按钮(侧栏 AI 按钮 /
|
||||
* TopBar 关闭钮)与审批浮层激活仍走 togglePanel,保持"开/关"二态语义不被破坏。
|
||||
*
|
||||
* 循环方向逐级增强再回关闭,贴合"要用 AI → 专注看 AI → 用完了"心智:
|
||||
* 关闭(panelOpen=false) → 分栏打开(panelOpen=true, maximized=false)
|
||||
* 分栏打开(maximized=false) → 最大化(maximized=true,全宽占满 main)
|
||||
* 最大化(maximized=true) → 关闭(panelOpen=false, maximized=false)
|
||||
*
|
||||
* detached 模式 no-op:分离窗口下主窗口不渲染面板,循环无意义;分离窗口自身走系统标题栏最大化。
|
||||
*/
|
||||
function cyclePanelMode() {
|
||||
// 分离窗口模式:主窗口无面板,三态循环无意义(no-op;关闭分离窗口时 destroyed 钩子自恢复 panelOpen)
|
||||
if (state.detached) return
|
||||
if (!state.panelOpen) {
|
||||
state.panelOpen = true
|
||||
state.maximized = false
|
||||
} else if (!state.maximized) {
|
||||
state.maximized = true
|
||||
} else {
|
||||
state.panelOpen = false
|
||||
state.maximized = false
|
||||
}
|
||||
persistUiState()
|
||||
}
|
||||
|
||||
// ── Providers / Skills / 清屏 ──
|
||||
|
||||
/** 拉取 provider 列表(用于顶部 bar 展示与切换) + 同步 DB 默认 provider 到 activeProvider */
|
||||
@@ -129,17 +168,35 @@ async function setProvider(providerId: string) {
|
||||
|
||||
/** 清空当前会话消息(后端 + 本地 state) */
|
||||
async function clearChat() {
|
||||
await aiApi.clearChat()
|
||||
state.messages = []
|
||||
state.currentText = ''
|
||||
state.pendingApprovals = []
|
||||
setStreaming(false, { reason: 'clearChat' })
|
||||
try {
|
||||
await aiApi.clearChat()
|
||||
state.messages = []
|
||||
state.currentText = ''
|
||||
state.pendingApprovals = []
|
||||
setStreaming(false, { reason: 'clearChat' })
|
||||
} catch (e) {
|
||||
// A2-B9(G3.2):清空失败需反馈,原裸 await 失败静默(异常进 unhandledrejection,用户无感)。
|
||||
// 后端裁决「先停 loop → DB 单事务 → 内存清」:DB 失败时内存不动(回滚一致),故此处
|
||||
// 只推错误气泡告知用户(对齐 useAiConversations.pushConvOpFail 模式),不吞异常日志。
|
||||
// 注意:不 rethrow——confirmClearChat 无 try,裸抛会进 unhandledrejection 且用户无感;
|
||||
// 错误气泡已足够反馈,且状态保持未清(与后端内存一致)。
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
state.messages.push({
|
||||
id: `conv-op-fail-${nextMsgId()}`,
|
||||
role: 'assistant',
|
||||
content: t('aiChat.clearError', { msg }),
|
||||
isError: true,
|
||||
timestamp: Date.now(),
|
||||
} as AiMessage)
|
||||
console.error('[AI] 清空对话失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
export function useAiPanel() {
|
||||
return {
|
||||
togglePanel,
|
||||
toggleMaximize,
|
||||
cyclePanelMode,
|
||||
loadProviders,
|
||||
loadSkills,
|
||||
setProvider,
|
||||
|
||||
Reference in New Issue
Block a user