feat(quickdock): 子智能体时间线支持压缩提示与状态图标右置

上下文压缩发生时在时间线对应位置显示提示条(layers 图标 + 轮次);工具栏去掉右侧完成/进行中文字,状态图标(转圈/对勾/叉)移至行尾。

Co-authored-by: Astrion powered by DeepSeek-V4-Flash <astrion-agent@users.noreply.github.com>
This commit is contained in:
JOJO 2026-08-29 23:12:05 +08:00
parent 10a2763f00
commit af01030424
4 changed files with 64 additions and 31 deletions

View File

@ -30,15 +30,23 @@
v-for="item in timelineItems"
:key="item.key"
class="qd-feed-row"
:class="[item.kind === 'tool' ? 'feed-tool' : 'feed-text', { 'is-new': animatedKeys.has(item.key) }]"
:class="[item.kind === 'tool' ? 'feed-tool' : item.kind === 'compression' ? 'feed-compressed' : 'feed-text', { 'is-new': animatedKeys.has(item.key) }]"
>
<template v-if="item.kind === 'tool'">
<span v-if="item.state === 'running' || item.state === 'calling'" class="qd-tool-spinner"></span>
<span v-else class="qd-tool-done">{{ item.state === 'failed' ? '✕' : '✓' }}</span>
<span class="tool-name">{{ item.toolName }}</span>
<span class="tool-param" :title="item.text">{{ item.text }}</span>
<span class="tool-result" :class="{ 'is-error': item.state === 'failed' }">
{{ item.stateLabel }}
<span class="tool-status" :class="{ 'is-error': item.state === 'failed' }">
<span v-if="item.state === 'running' || item.state === 'calling'" class="qd-tool-spinner"></span>
<span v-else class="qd-tool-done">{{ item.state === 'failed' ? '✕' : '✓' }}</span>
</span>
</template>
<template v-else-if="item.kind === 'compression'">
<span class="feed-compressed__label">
<svg class="feed-compressed__icon" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M8 1.5 13.5 4v4.5c0 3-2.2 5-5.5 6-3.3-1-5.5-3-5.5-6V4L8 1.5Z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
<path d="M5.5 8.2 7.2 9.9 11 5.8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ t('quickdock.contextCompressed', { round: item.round }) }}
</span>
</template>
<template v-else>{{ item.content }}</template>
@ -283,14 +291,6 @@ function isEntryTerminal(status?: string) {
].includes(normalized);
}
/** 工具条目状态标签:映射表存 key、使用处解析规范 §3.3,模块顶层禁止调 t() */
const TOOL_STATE_LABEL_KEYS: Record<string, string> = {
completed: 'quickdock.toolStateDone',
failed: 'quickdock.toolStateFailed',
calling: 'quickdock.toolStateCalling',
running: 'quickdock.toolStateProgress'
};
function buildText(entry: any): string {
const tool = entry.tool || '';
const args = entry.args || {};
@ -309,7 +309,6 @@ interface ToolTimelineItem {
kind: 'tool';
key: string;
state: string;
stateLabel: string;
toolName: string;
text: string;
}
@ -320,10 +319,16 @@ interface OutputTimelineItem {
content: string;
}
const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() => {
interface CompressionTimelineItem {
kind: 'compression';
key: string;
round: number;
}
const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem | CompressionTimelineItem)[]>(() => {
void currentLocale.value;
const entries = activityEntries.value || [];
const rawItems: ({ kind: 'tool'; key: string; entry: any } | OutputTimelineItem)[] = [];
const rawItems: ({ kind: 'tool'; key: string; entry: any } | OutputTimelineItem | CompressionTimelineItem)[] = [];
let currentToolGroup: { kind: 'tool'; key: string; entry: any } | null = null;
const flushToolGroup = () => {
@ -361,6 +366,17 @@ const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() =>
return;
}
//
if (entry?.type === 'progress' && entry?.subtype === 'compression') {
flushToolGroup();
rawItems.push({
kind: 'compression',
key: `compression-${entry.ts || index}`,
round: Number(entry.round) || 0,
});
return;
}
if (!entry || entry.type !== 'progress' || !entry.tool) {
return;
}
@ -401,7 +417,7 @@ const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() =>
flushToolGroup();
return rawItems.map((item) => {
if (item.kind === 'output') {
if (item.kind === 'output' || item.kind === 'compression') {
return item;
}
const state = normalizeStatus(item.entry.status);
@ -409,7 +425,6 @@ const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() =>
kind: 'tool' as const,
key: item.key,
state,
stateLabel: t(TOOL_STATE_LABEL_KEYS[state] || 'quickdock.toolStateProgress'),
toolName: item.entry.tool || t('common.tool'),
text: buildText(item.entry)
};

View File

@ -697,15 +697,16 @@
min-width: 0;
}
.qd-feed-row.feed-tool .tool-result {
.qd-feed-row.feed-tool .tool-status {
flex: none;
margin-left: auto;
font-size: 11.5px;
color: var(--text-tertiary);
font-variant-numeric: tabular-nums;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 14px;
}
.qd-feed-row.feed-tool .tool-result.is-error {
.qd-feed-row.feed-tool .tool-status.is-error .qd-tool-done {
color: var(--state-danger);
}
@ -733,6 +734,25 @@
color: var(--accent);
}
.qd-feed-row.feed-compressed {
justify-content: center;
padding: 2px 14px;
}
.qd-feed-row.feed-compressed .feed-compressed__label {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11.5px;
color: var(--text-tertiary);
}
.qd-feed-row.feed-compressed .feed-compressed__icon {
width: 12px;
height: 12px;
flex: none;
}
.qd-feed-row.feed-term {
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
font-size: 12px;

View File

@ -39,10 +39,9 @@ export default {
toolRunCommand: 'Run command {command}',
toolEditFile: 'Edit {path}',
toolReadMedia: 'Read media file {path}',
toolStateDone: 'Done',
toolStateFailed: 'Failed',
toolStateCalling: 'Calling...',
toolStateProgress: 'In progress',
// —— Sub-agent detail panel: context compression notice ——
contextCompressed: 'Context compressed (round {round})',
// —— File preview panel ——
resizeWidthHint: 'Drag to resize width',

View File

@ -46,10 +46,9 @@ export default {
toolRunCommand: '运行命令 {command}',
toolEditFile: '编辑 {path}',
toolReadMedia: '读取媒体文件 {path}',
toolStateDone: '完成',
toolStateFailed: '失败',
toolStateCalling: '调用中',
toolStateProgress: '进行中',
// —— 子智能体详情面板:上下文压缩提示 ——
contextCompressed: '上下文已压缩(第 {round} 轮)',
// —— 文件预览面板FilePreviewPanel ——
resizeWidthHint: '拖拽调整宽度',