优化: 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:
+53
-3
@@ -82,7 +82,10 @@
|
||||
<span v-else class="decided-none">—</span>
|
||||
</td>
|
||||
<td class="col-args"><code class="brief">{{ r.arguments_brief || '—' }}</code></td>
|
||||
<td class="col-result"><code class="brief">{{ r.result_brief || '—' }}</code></td>
|
||||
<td class="col-result">
|
||||
<span v-if="hasOffloadMark(r.result_brief)" class="offload-chip">{{ t('aiTool.resultOffloaded') }}</span>
|
||||
<code v-else class="brief">{{ r.result_brief || '—' }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="expandedId === r.id" class="audit-detail-row">
|
||||
<td colspan="7">
|
||||
@@ -93,7 +96,18 @@
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<span class="detail-label">{{ t('auditLog.col.result') }}</span>
|
||||
<pre class="detail-pre">{{ r.result_brief || '—' }}</pre>
|
||||
<!-- 外置句柄(D2a):显「结果已外置」+ 展开经 IPC 读回原文全文 -->
|
||||
<template v-if="offloadHandle">
|
||||
<OffloadedResultBar
|
||||
:handle="offloadHandle"
|
||||
:expanded="offloadExpanded"
|
||||
:loading="offloadLoading"
|
||||
:error="offloadError"
|
||||
@toggle="toggleOffload"
|
||||
/>
|
||||
<pre v-if="offloadResolved != null" class="detail-pre">{{ offloadResolved }}</pre>
|
||||
</template>
|
||||
<pre v-else class="detail-pre">{{ r.result_brief || '—' }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -175,6 +189,8 @@ import { invoke } from '@tauri-apps/api/core'
|
||||
import { formatRelative, formatDate } from '@/utils/time'
|
||||
import { usePersistedRef } from '@/composables/usePersistedRef'
|
||||
import { aiApi } from '@/api/ai'
|
||||
import { hasOffloadMark, useOffloadedResult } from '@/composables/ai/useOffloadedResult'
|
||||
import OffloadedResultBar from '@/components/OffloadedResultBar.vue'
|
||||
import type { AiConversationSummary } from '@/api/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -255,6 +271,20 @@ function toggleExpand(id: string) {
|
||||
expandedId.value = expandedId.value === id ? null : id
|
||||
}
|
||||
|
||||
// 展开行的外置句柄详情(D2a):同一视图只展开一行,读回状态复用单实例;
|
||||
// 切行时 reset 防串上一行的读回结果/错误。句柄来源=后端截断的 result_brief。
|
||||
const expandedRecord = computed(() => records.value.find(r => r.id === expandedId.value) ?? null)
|
||||
const {
|
||||
handle: offloadHandle,
|
||||
expanded: offloadExpanded,
|
||||
loading: offloadLoading,
|
||||
error: offloadError,
|
||||
resolved: offloadResolved,
|
||||
toggle: toggleOffload,
|
||||
reset: resetOffload,
|
||||
} = useOffloadedResult(() => expandedRecord.value?.result_brief ?? null)
|
||||
watch(expandedId, () => resetOffload())
|
||||
|
||||
// 审批→执行 耗时(秒级,毫秒差太精细)
|
||||
function durationLabel(requested: string, executed: string): string {
|
||||
const ms = new Date(executed).getTime() - new Date(requested).getTime()
|
||||
@@ -356,24 +386,32 @@ async function loadConversations() {
|
||||
convLoading.value = false
|
||||
}
|
||||
}
|
||||
// 对话详情请求序号守卫(对齐上方 _loadSeq 模式):快速切换对话时,
|
||||
// 旧对话 export 响应晚到不覆盖新对话消息;折叠时自增使在途请求作废
|
||||
let _convSeq = 0
|
||||
async function toggleConv(id: string) {
|
||||
if (expandedConv.value === id) {
|
||||
_convSeq++ // 折叠:作废在途请求,避免其回填残留
|
||||
expandedConv.value = null
|
||||
convMessages.value = []
|
||||
convLoading.value = false
|
||||
return
|
||||
}
|
||||
const seq = ++_convSeq
|
||||
expandedConv.value = id
|
||||
convLoading.value = true
|
||||
convError.value = ''
|
||||
try {
|
||||
const json = await aiApi.exportConversation(id, 'json')
|
||||
if (seq !== _convSeq) return // 旧响应丢弃,只取最新
|
||||
const parsed = JSON.parse(json) as ConvMessage[] ?? []
|
||||
convMessages.value = parsed || []
|
||||
} catch (e) {
|
||||
if (seq !== _convSeq) return // 旧请求的失败不污染当前视图
|
||||
convError.value = String(e)
|
||||
convMessages.value = []
|
||||
} finally {
|
||||
convLoading.value = false
|
||||
if (seq === _convSeq) convLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,6 +616,18 @@ onMounted(() => {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* D2a 外置句柄:结果列显标记替代截断 JSON(点行展开看详情并可读回全文) */
|
||||
.offload-chip {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 1px 7px;
|
||||
border-radius: var(--df-radius-xs);
|
||||
background: var(--df-warning-bg);
|
||||
color: var(--df-warning);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 风险 badge */
|
||||
.risk-badge {
|
||||
display: inline-block;
|
||||
|
||||
Reference in New Issue
Block a user