feat(quickdock): 子智能体时间线支持压缩提示与状态图标右置
上下文压缩发生时在时间线对应位置显示提示条(layers 图标 + 轮次);工具栏去掉右侧完成/进行中文字,状态图标(转圈/对勾/叉)移至行尾。 Co-authored-by: Astrion powered by DeepSeek-V4-Flash <astrion-agent@users.noreply.github.com>
This commit is contained in:
parent
10a2763f00
commit
af01030424
@ -30,15 +30,23 @@
|
|||||||
v-for="item in timelineItems"
|
v-for="item in timelineItems"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
class="qd-feed-row"
|
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'">
|
<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-name">{{ item.toolName }}</span>
|
||||||
<span class="tool-param" :title="item.text">{{ item.text }}</span>
|
<span class="tool-param" :title="item.text">{{ item.text }}</span>
|
||||||
<span class="tool-result" :class="{ 'is-error': item.state === 'failed' }">
|
<span class="tool-status" :class="{ 'is-error': item.state === 'failed' }">
|
||||||
{{ item.stateLabel }}
|
<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>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>{{ item.content }}</template>
|
<template v-else>{{ item.content }}</template>
|
||||||
@ -283,14 +291,6 @@ function isEntryTerminal(status?: string) {
|
|||||||
].includes(normalized);
|
].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 {
|
function buildText(entry: any): string {
|
||||||
const tool = entry.tool || '';
|
const tool = entry.tool || '';
|
||||||
const args = entry.args || {};
|
const args = entry.args || {};
|
||||||
@ -309,7 +309,6 @@ interface ToolTimelineItem {
|
|||||||
kind: 'tool';
|
kind: 'tool';
|
||||||
key: string;
|
key: string;
|
||||||
state: string;
|
state: string;
|
||||||
stateLabel: string;
|
|
||||||
toolName: string;
|
toolName: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
@ -320,10 +319,16 @@ interface OutputTimelineItem {
|
|||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() => {
|
interface CompressionTimelineItem {
|
||||||
|
kind: 'compression';
|
||||||
|
key: string;
|
||||||
|
round: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem | CompressionTimelineItem)[]>(() => {
|
||||||
void currentLocale.value;
|
void currentLocale.value;
|
||||||
const entries = activityEntries.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;
|
let currentToolGroup: { kind: 'tool'; key: string; entry: any } | null = null;
|
||||||
|
|
||||||
const flushToolGroup = () => {
|
const flushToolGroup = () => {
|
||||||
@ -361,6 +366,17 @@ const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() =>
|
|||||||
return;
|
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) {
|
if (!entry || entry.type !== 'progress' || !entry.tool) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -401,7 +417,7 @@ const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() =>
|
|||||||
flushToolGroup();
|
flushToolGroup();
|
||||||
|
|
||||||
return rawItems.map((item) => {
|
return rawItems.map((item) => {
|
||||||
if (item.kind === 'output') {
|
if (item.kind === 'output' || item.kind === 'compression') {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
const state = normalizeStatus(item.entry.status);
|
const state = normalizeStatus(item.entry.status);
|
||||||
@ -409,7 +425,6 @@ const timelineItems = computed<(ToolTimelineItem | OutputTimelineItem)[]>(() =>
|
|||||||
kind: 'tool' as const,
|
kind: 'tool' as const,
|
||||||
key: item.key,
|
key: item.key,
|
||||||
state,
|
state,
|
||||||
stateLabel: t(TOOL_STATE_LABEL_KEYS[state] || 'quickdock.toolStateProgress'),
|
|
||||||
toolName: item.entry.tool || t('common.tool'),
|
toolName: item.entry.tool || t('common.tool'),
|
||||||
text: buildText(item.entry)
|
text: buildText(item.entry)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -697,15 +697,16 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qd-feed-row.feed-tool .tool-result {
|
.qd-feed-row.feed-tool .tool-status {
|
||||||
flex: none;
|
flex: none;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
font-size: 11.5px;
|
display: inline-flex;
|
||||||
color: var(--text-tertiary);
|
align-items: center;
|
||||||
font-variant-numeric: tabular-nums;
|
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);
|
color: var(--state-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,6 +734,25 @@
|
|||||||
color: var(--accent);
|
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 {
|
.qd-feed-row.feed-term {
|
||||||
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@ -39,10 +39,9 @@ export default {
|
|||||||
toolRunCommand: 'Run command {command}',
|
toolRunCommand: 'Run command {command}',
|
||||||
toolEditFile: 'Edit {path}',
|
toolEditFile: 'Edit {path}',
|
||||||
toolReadMedia: 'Read media file {path}',
|
toolReadMedia: 'Read media file {path}',
|
||||||
toolStateDone: 'Done',
|
|
||||||
toolStateFailed: 'Failed',
|
// —— Sub-agent detail panel: context compression notice ——
|
||||||
toolStateCalling: 'Calling...',
|
contextCompressed: 'Context compressed (round {round})',
|
||||||
toolStateProgress: 'In progress',
|
|
||||||
|
|
||||||
// —— File preview panel ——
|
// —— File preview panel ——
|
||||||
resizeWidthHint: 'Drag to resize width',
|
resizeWidthHint: 'Drag to resize width',
|
||||||
|
|||||||
@ -46,10 +46,9 @@ export default {
|
|||||||
toolRunCommand: '运行命令 {command}',
|
toolRunCommand: '运行命令 {command}',
|
||||||
toolEditFile: '编辑 {path}',
|
toolEditFile: '编辑 {path}',
|
||||||
toolReadMedia: '读取媒体文件 {path}',
|
toolReadMedia: '读取媒体文件 {path}',
|
||||||
toolStateDone: '完成',
|
|
||||||
toolStateFailed: '失败',
|
// —— 子智能体详情面板:上下文压缩提示 ——
|
||||||
toolStateCalling: '调用中',
|
contextCompressed: '上下文已压缩(第 {round} 轮)',
|
||||||
toolStateProgress: '进行中',
|
|
||||||
|
|
||||||
// —— 文件预览面板(FilePreviewPanel) ——
|
// —— 文件预览面板(FilePreviewPanel) ——
|
||||||
resizeWidthHint: '拖拽调整宽度',
|
resizeWidthHint: '拖拽调整宽度',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user