重构: 审批交互重设计

This commit is contained in:
lxy
2026-10-05 14:59:12 +08:00
parent 24d6803ecc
commit 2eea0118db
7 changed files with 254 additions and 30 deletions
+86 -5
View File
@@ -5,6 +5,7 @@
'ai-tool-card--' + tc.status,
{ 'ai-tool-card--collapsed': !isExpanded && !shouldKeepOpen(tc), 'ai-tool-card--failed': isFailed },
]"
:data-risk="riskLevel"
>
<!-- 卡片头部:图标 + 名称 + 状态 -->
<div class="ai-tool-header" @click="emit('toggle', tc.id)">
@@ -31,20 +32,35 @@
<!-- Body: 收起态隐藏(running/pending 强制展开) -->
<div v-show="isExpanded || shouldKeepOpen(tc)" class="ai-tool-body">
<!-- Running 骨架屏 -->
<div v-if="tc.status === 'running'" class="ai-tool-skeleton">
<!-- Running 骨架屏(有流式输出时隐藏骨架,显实时行) -->
<div v-if="tc.status === 'running' && !tc.streamLines?.length" class="ai-tool-skeleton">
<div class="ai-tool-skeleton-line" style="width:60%"></div>
<div class="ai-tool-skeleton-line" style="width:85%"></div>
<div class="ai-tool-skeleton-line" style="width:40%"></div>
</div>
<!-- 执行中实时流式输出(run_command shell 行 / ask_uclaw 进度块):
尾部最多 8 行(溢出折叠提示),Completed 后清空走 result 渲染 -->
<div v-if="tc.status === 'running' && tc.streamLines?.length" class="ai-tool-stream">
<div v-for="(ln, i) in tc.streamLines.slice(-8)" :key="tc.streamLines.length - 8 + i" class="ai-tool-stream-line">{{ ln }}</div>
<div v-if="tc.streamLines.length > 8" class="ai-tool-stream-more">{{ $t('aiTool.streamMore', { n: tc.streamLines.length - 8 }) }}</div>
</div>
<!-- 审批:参数内容 + 风险提示 + 按钮 -->
<div v-if="tc.status === 'pending_approval'" class="ai-tool-approval">
<div v-if="argsEntries().length" class="ai-tool-approval-args">
<div v-for="arg in argsEntries()" :key="arg.key" class="ai-tool-arg">
<!-- 风险徽章:High 橙红/Medium 黄,一眼识别操作等级(与左侧着色条同色系) -->
<div v-if="riskLevel !== 'low'" class="ai-tool-risk-badge" :class="'is-' + riskLevel">
{{ riskLevel === 'high' ? $t('aiTool.riskHigh') : $t('aiTool.riskMedium') }}
</div>
<div v-if="visibleArgs.length" class="ai-tool-approval-args">
<div v-for="arg in visibleArgs" :key="arg.key" class="ai-tool-arg">
<span class="ai-tool-arg-key">{{ arg.label }}</span>
<span class="ai-tool-arg-val">{{ displayArgValue(arg) }}</span>
</div>
<!-- args 折叠:多参数默认仅显首条,「更多 N 项」展开(治超长 content/task 刷屏) -->
<button v-if="argsCount > 1" class="ai-tool-args-toggle" @click="argsExpanded = !argsExpanded">
{{ 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>
<!-- BUG-260624-02: 审批到期倒计时(与前端自动拒绝时刻精确对齐,≤60s 红色脉冲);
@@ -156,7 +172,7 @@ import {
} from '@/composables/ai/useToolCard'
import { parseDiffLines } from '@/composables/ai/useToolCardRender'
import { useToolCardHeader } from '@/composables/ai/useToolCardHeader'
import { useToolApproval } from '@/composables/ai/useToolApproval'
import { useToolApproval, toolRiskLevel } from '@/composables/ai/useToolApproval'
import { useTimerOwnership } from '@/composables/useTimerOwnership'
import { callDurationText } from '@/composables/ai/useTurnTrace'
import { formatDate, formatDuration } from '@/utils/time'
@@ -294,6 +310,25 @@ const statusTitle = computed(() => {
/** 头部摘要 hover 回看完整路径(摘要统一缩略尾段,全路径走 title;路径类工具才有值) */
const summaryTitle = computed(() => argString(props.tc.args, 'path'))
/**
* 风险等级(high/medium/low):审批挂起时卡片左侧着色条 + 风险徽章数据源。
* 复用 useToolApproval 注册表投影(权威源=后端 risk_level,启动期 loadToolRisks 同步),
* 仅 pending_approval 态展示——执行中/完成后风险条无操作意义,不抢视觉焦点。
*/
const riskLevel = computed(() =>
props.tc.status === 'pending_approval' ? toolRiskLevel(props.tc.name) : 'low',
)
/** 审批参数折叠态:默认收起仅显首条(args 通常含超长 content/task),点「更多」展开全部 */
const argsExpanded = ref(false)
/** args 条数(供「更多 N 项」文案;无参数时 0) */
const argsCount = computed(() => argsEntries().length)
/** 折叠态展示条数:展开显全部;收起显首条(最重要的参数,如 path/command) */
const visibleArgs = computed(() => {
const all = argsEntries()
return argsExpanded.value ? all : all.slice(0, 1)
})
</script>
<style scoped>
@@ -389,6 +424,23 @@ const summaryTitle = computed(() => argString(props.tc.args, 'path'))
background: linear-gradient(90deg, var(--df-bg) 25%, var(--df-bg-card-hover) 50%, var(--df-bg) 75%);
background-size: 200% 100%; animation: shimmer 1.5s infinite;
}
/* -- 实时流式输出(running 态:run_command 行 / ask_uclaw 进度块) -- */
.ai-tool-stream {
padding: 4px 10px 10px 42px;
display: flex; flex-direction: column; gap: 2px;
max-height: 160px; overflow-y: auto;
}
.ai-tool-stream-line {
font-family: var(--df-font-mono, monospace);
font-size: 11px; line-height: 1.5;
color: var(--df-text-secondary);
white-space: pre-wrap; word-break: break-all;
}
.ai-tool-stream-more {
font-size: 10px; color: var(--df-text-tertiary);
padding-top: 2px; font-style: italic;
}
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
/* -- 审批参数展示 -- */
@@ -474,4 +526,33 @@ const summaryTitle = computed(() => argString(props.tc.args, 'path'))
.ai-tool-toast--warning { background: var(--df-warning-bg); color: var(--df-warning); border: 0.5px solid var(--df-warning); }
.ai-tool-toast-enter-active, .ai-tool-toast-leave-active { transition: opacity 0.2s, transform 0.2s; }
.ai-tool-toast-enter-from, .ai-tool-toast-leave-to { opacity: 0; transform: translate(-50%, -6px); }
/* -- 风险着色条(审批挂起时卡片左缘 2px 竖条,High 橙红/Medium 黄;data-risk 属性驱动) -- */
.ai-tool-card[data-risk='high'] { border-left: 2px solid var(--df-danger); }
.ai-tool-card[data-risk='medium'] { border-left: 2px solid var(--df-warning); }
/* -- 风险徽章(审批区首行,High 橙红底/Medium 黄底,10px 大写字距) -- */
.ai-tool-risk-badge {
display: inline-flex; align-items: center; align-self: flex-start;
padding: 2px 8px; margin: 6px 10px 0;
font-size: 10px; font-weight: 600; letter-spacing: 0.5px;
border-radius: var(--df-radius-sm);
}
.ai-tool-risk-badge.is-high { background: var(--df-danger-bg); color: var(--df-danger); border: 0.5px solid color-mix(in srgb, var(--df-danger) 40%, transparent); }
.ai-tool-risk-badge.is-medium { background: var(--df-warning-bg); color: var(--df-warning); border: 0.5px solid color-mix(in srgb, var(--df-warning) 40%, transparent); }
/* 审批区改纵向 flex(徽章需独立成行,原块级布局不变) */
.ai-tool-approval { display: flex; flex-direction: column; }
.ai-tool-approval-args { margin-bottom: 0; }
/* -- args 折叠切换按钮(「更多 N 项」/「收起」;弱化灰字,hover 变主文字色) -- */
.ai-tool-args-toggle {
display: block; width: 100%; text-align: center;
padding: 3px 0; margin-top: 2px;
font-size: 10px; color: var(--df-text-dim);
background: none; border: none; border-top: 0.5px dashed var(--df-border);
cursor: pointer; transition: color 0.15s var(--df-ease);
font-family: var(--df-font-sans);
}
.ai-tool-args-toggle:hover { color: var(--df-text); }
</style>