优化: 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:
@@ -24,7 +24,7 @@
|
||||
<span class="ai-tool-name">{{ toolDisplayName(tc) }}</span>
|
||||
<span v-if="tc.status === 'running'" class="ai-tool-sub">{{ $t('aiTool.running') }}</span>
|
||||
<span v-else-if="isFailed" class="ai-tool-sub ai-tool-sub--failed" :title="summaryTitle">{{ $t('aiTool.executionFailed') }}</span>
|
||||
<span v-else-if="toolResultSummary(tc)" class="ai-tool-sub" :title="summaryTitle">{{ toolResultSummary(tc) }}</span>
|
||||
<span v-else-if="resultSummary" class="ai-tool-sub" :title="summaryTitle">{{ resultSummary }}</span>
|
||||
</div>
|
||||
<span class="ai-tool-status-dot" :class="'ai-tool-status-dot--' + (isFailed ? 'failed' : tc.status)" :title="statusTitle"></span>
|
||||
<!-- 统一卡形态第4要素:耗时(前端 startedAt/completedAt 差;历史消息无时间戳隐藏不显 0)。
|
||||
@@ -70,14 +70,14 @@
|
||||
{{ argsExpanded ? $t('aiTool.argsCollapse') : $t('aiTool.argsMore', { n: argsCount - 1 }) }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="tc.reason" class="ai-tool-approval-reason">⚠ {{ tc.reason }}</div>
|
||||
<div v-if="tc.reason" class="ai-tool-approval-reason"><span aria-hidden="true">⚠</span> {{ tc.reason }}</div>
|
||||
<!-- BUG-260624-02: 审批到期倒计时(与前端自动拒绝时刻精确对齐,≤60s 红色脉冲);
|
||||
无活跃计时器(不限时/已到期)回退显挂起时长(>2分钟橙色,>5分钟红色) -->
|
||||
<div v-if="expireSecs !== null" class="ai-tool-wait-time" :class="'is-' + expireLevel">
|
||||
{{ $t('aiTool.expireIn', { time: formatWaitTime(expireSecs) }) }}
|
||||
</div>
|
||||
<div v-else-if="waitSecs > 0" class="ai-tool-wait-time" :class="'is-' + waitLevel">
|
||||
⏱ {{ $t('aiTool.waitTime', { time: formatWaitTime(waitSecs) }) }}
|
||||
<span aria-hidden="true">⏱</span> {{ $t('aiTool.waitTime', { time: formatWaitTime(waitSecs) }) }}
|
||||
</div>
|
||||
<!-- AE-2025-03: write_file 审批 diff 预览(红删绿增,行级)。旧文件不存在→tc.diff 为空,回退显上方 args content -->
|
||||
<div v-if="tc.diff" class="ai-tool-approval-diff">
|
||||
@@ -137,14 +137,32 @@
|
||||
|
||||
<!-- 已拒绝提示 -->
|
||||
<div v-if="tc.status === 'rejected'" class="ai-tool-rejected">
|
||||
<span class="ai-tool-rejected-icon">✕</span>
|
||||
<span class="ai-tool-rejected-icon" aria-hidden="true">✕</span>
|
||||
{{ tc.result || $t('aiTool.rejectedHint') }}
|
||||
</div>
|
||||
|
||||
<!-- D2a 外置结果(句柄 JSON):显「结果已外置」+ 展开;读回原文后复用 ToolResultBody 渲染 -->
|
||||
<div v-if="tc.status === 'completed' && offloadHandle" class="ai-tool-offloaded">
|
||||
<OffloadedResultBar
|
||||
:handle="offloadHandle"
|
||||
:expanded="offloadExpanded"
|
||||
:loading="offloadLoading"
|
||||
:error="offloadError"
|
||||
@toggle="toggleOffload"
|
||||
/>
|
||||
<ToolResultBody
|
||||
v-if="offloadExpanded && offloadResolved != null"
|
||||
:tc="resolvedOffloadTc"
|
||||
:isContentExpanded="isContentExpanded"
|
||||
@expand-content="(id) => emit('expand-content', id)"
|
||||
@install-obscura="() => emit('install-obscura')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 各工具结果展示(read_file/list_directory/search_files/write_file/patch_file/run_command/http_request/fetch_url/通用)
|
||||
委托 ToolResultBody 子组件,透传 tc + 内容展开态 + expand-content/install-obscura 冒泡 -->
|
||||
<ToolResultBody
|
||||
v-if="tc.status === 'completed'"
|
||||
v-else-if="tc.status === 'completed'"
|
||||
:tc="tc"
|
||||
:isContentExpanded="isContentExpanded"
|
||||
@expand-content="(id) => emit('expand-content', id)"
|
||||
@@ -179,6 +197,7 @@ import {
|
||||
argString,
|
||||
} from '@/composables/ai/useToolCard'
|
||||
import { parseDiffLines } from '@/composables/ai/useToolCardRender'
|
||||
import { useOffloadedResult } from '@/composables/ai/useOffloadedResult'
|
||||
import { useToolCardHeader } from '@/composables/ai/useToolCardHeader'
|
||||
import { useToolApproval, toolRiskLevel } from '@/composables/ai/useToolApproval'
|
||||
import { useTimerOwnership } from '@/composables/useTimerOwnership'
|
||||
@@ -187,6 +206,7 @@ import { formatDate, formatDuration } from '@/utils/time'
|
||||
import type { AiToolCallInfo } from '@/api/types'
|
||||
import ConfirmDialog from './ConfirmDialog.vue'
|
||||
import ToolResultBody from './ToolResultBody.vue'
|
||||
import OffloadedResultBar from './OffloadedResultBar.vue'
|
||||
|
||||
// 审批按钮图标 SVG 常量(消除模板 6 处内联重复;v-html 渲染完整 svg 元素)
|
||||
const ICON_CHECK = '<svg aria-hidden="true" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>'
|
||||
@@ -246,6 +266,27 @@ const {
|
||||
/** 一次解析结果供失败判定复用(头部 isFailed 用于卡片 border/status-dot 着色) */
|
||||
const parsed = computed(() => parseResult(props.tc.result))
|
||||
|
||||
/**
|
||||
* D2a 外置句柄:completed 结果若是句柄 JSON(超 8KB 落盘、DB 只留句柄),
|
||||
* 头部摘要与结果区改走「结果已外置」+ 展开读回,不把句柄当普通结果解析渲染。
|
||||
*/
|
||||
const {
|
||||
handle: offloadHandle,
|
||||
expanded: offloadExpanded,
|
||||
loading: offloadLoading,
|
||||
error: offloadError,
|
||||
resolved: offloadResolved,
|
||||
toggle: toggleOffload,
|
||||
} = useOffloadedResult(() => (props.tc.status === 'completed' ? props.tc.result : null))
|
||||
|
||||
/** 读回后把原文塞回 tc.result,交 ToolResultBody 按工具名走既有渲染分支(零重复渲染逻辑) */
|
||||
const resolvedOffloadTc = computed(() => ({ ...props.tc, result: offloadResolved.value ?? props.tc.result }))
|
||||
|
||||
/** 头部副标题:外置句柄显「结果已外置」,其余走既有摘要函数 */
|
||||
const resultSummary = computed(() =>
|
||||
offloadHandle.value ? t('aiTool.resultOffloaded') : toolResultSummary(props.tc),
|
||||
)
|
||||
|
||||
/**
|
||||
* 是否工具失败(completed 语义)。头部多处复用(.ai-tool-card--failed / 状态点 / sub 文案)。
|
||||
* 逻辑与模块级 isToolFailure 同源;结果展示区的 isFailed 已迁入 ToolResultBody(各自 computed)。
|
||||
@@ -253,6 +294,8 @@ const parsed = computed(() => parseResult(props.tc.result))
|
||||
const isFailed = computed(() => {
|
||||
const tc = props.tc
|
||||
if (tc.status !== 'completed') return false
|
||||
// 外置句柄是「结果留了指针」非执行失败,不参与失败判定
|
||||
if (offloadHandle.value) return false
|
||||
const r = parsed.value
|
||||
if (tc.name === 'run_command') {
|
||||
if (r && typeof r.exit_code === 'number') return r.exit_code !== 0
|
||||
|
||||
Reference in New Issue
Block a user