重构: 审批交互重设计
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user