优化: AI Chat 渲染与无障碍 + 文件查看 + 同步 IO 收口

- 渲染: 结果/过程分离(气泡只放最终结果,过程轮叙述+工具卡收进轨迹卡默认收起可展开——治『流式后正文消失』且不再 N 气泡打扰)
- 渲染 P0: v-memo 定向 dep(只有真正渲染流式块的 item 失效,其余取占位) + renderItems 桶 memo(签名覆盖全消费输入,item 引用稳定) + 指纹 watch 降为 O(1) toolEpoch
- TopBar 三处 O(n) messages computed 合并单次遍历;md 缓存改有界 LRU(除满即全清抖动);useTurnTrace 补 never 穷尽 + default;MessageItem 模板去重
- 无障碍(四批累计): 可点容器键盘化(含 role/tabindex/keydown/aria-expanded) + icon-only aria-label 约 60 处 + 装饰图标 aria-hidden + 52 个滚动容器 overscroll-behavior:contain + 设置开关聚焦可见环 + outline 零补偿站点补 :focus-visible
- UX 走查项: 灵感 IDEA-10/11、知识 KNOW-6/7/9、项目详情 O8、审计 SA-4、设置 SA-6/26/27/28(脏表单/冗余 save/clamp/删除二次确认)
- 文件查看专项: 切工程状态残留 F5/F6、FileTree 去重与展开反馈 F7/F15/F17、切文件头部残留 F18、R 状态着色 F19;FilePreview diff 文件头(---/+++)误当增删渲染修复 + 大纲行高亮死样式修复 + 图片失败文案 i18n 化
- 同步 IO 收口(异步上下文内阻塞): unattended 看板读写改 tokio::fs、fetch_url 探活改 spawn_blocking、http/generate_image 清理改 tokio::fs
- 子代理 P3 批量: 任务书钉扎上限/截断文案区分父子语境/环境画像日期/temperature 可覆盖/model 回退上报/lang 透传
- crate 清理: df-execute·df-types 注释与格式、df-ideas 过时注释、df-nodes subflow 夸大描述、df-project 死变量
This commit is contained in:
lxy
2026-10-10 08:09:45 +08:00
parent 8df4e33656
commit 39e0eac39d
67 changed files with 1344 additions and 364 deletions
+12 -6
View File
@@ -71,20 +71,20 @@
</div>
<div class="toolbar-right">
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.editCurrentModule')" @click="openEditModule">
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.editCurrentModule')" :aria-label="$t('fileExplorer.editCurrentModule')" @click="openEditModule">
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
</button>
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.deleteCurrentModule')" @click="confirmRemoveModule">
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.deleteCurrentModule')" :aria-label="$t('fileExplorer.deleteCurrentModule')" @click="confirmRemoveModule">
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
</button>
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.addModuleTitle')" @click="openAddModule">
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.addModuleTitle')" :aria-label="$t('fileExplorer.addModuleTitle')" @click="openAddModule">
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<button class="btn btn-ghost btn-sm" type="button" :disabled="refreshing" @click="refresh">
<span v-if="refreshing" class="spinner"></span>
{{ refreshing ? $t('fileExplorer.refreshing') : $t('fileExplorer.refresh') }}
</button>
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.detachToWindow')" @click="onDetach">
<button class="btn btn-ghost btn-sm" type="button" :title="$t('fileExplorer.detachToWindow')" :aria-label="$t('fileExplorer.detachToWindow')" @click="onDetach">
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M15 3h6v6"/>
<path d="M10 14L21 3"/>
@@ -99,10 +99,10 @@
<!-- 左:视图切换按钮(文件树 / 变更) -->
<div class="explorer-sidebar">
<div class="explorer-view-tabs">
<button class="view-tab" :class="{ active: viewMode === 'tree' }" @click="viewMode = 'tree'">
<button class="view-tab" :class="{ active: viewMode === 'tree' }" type="button" :title="$t('fileExplorer.viewTree')" :aria-label="$t('fileExplorer.viewTree')" @click="viewMode = 'tree'">
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
</button>
<button class="view-tab" :class="{ active: viewMode === 'changes' }" @click="switchToChanges">
<button class="view-tab" :class="{ active: viewMode === 'changes' }" type="button" :title="$t('fileExplorer.viewChanges')" :aria-label="$t('fileExplorer.viewChanges')" @click="switchToChanges">
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
<span v-if="changedCount > 0" class="changed-badge">{{ changedCount }}</span>
</button>
@@ -426,6 +426,12 @@ onDeactivated(() => { viewVisible.value = false })
const changedCount = computed(() => gitStatusData.value?.changed_files.length ?? 0)
// F5:切工程清 git 状态缓存 —— 否则 changedCount 徽标沿用上一工程的计数(张冠李戴),
// 且 switchToChanges 因 gitStatusData 非空短路,不再为新工程重拉。进入变更视图时自然重拉。
watch(currentModuleId, () => {
gitStatusData.value = null
})
async function switchToChanges() {
viewMode.value = 'changes'
if (!currentModuleId.value || gitStatusData.value) return