feat(chat): 极简模式摘要行改为单行渐变+工具统计总结
This commit is contained in:
parent
478d24c4dd
commit
2264a86851
@ -34,7 +34,9 @@
|
||||
</span>
|
||||
<template v-else>
|
||||
<span
|
||||
v-for="(char, idx) in getAnimatedSummaryChars(getSummaryPreview(group.actions))"
|
||||
v-for="(char, idx) in getAnimatedSummaryChars(
|
||||
getSummaryLineText(group.actions, group.id)
|
||||
)"
|
||||
:key="`${group.id}-${idx}`"
|
||||
class="summary-char"
|
||||
:style="{ animationDelay: `${(idx + 1) * 0.12}s` }"
|
||||
@ -44,7 +46,7 @@
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ getSummaryPreview(group.actions) }}
|
||||
{{ getSummaryLineText(group.actions, group.id) }}
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
@ -208,7 +210,6 @@ const personalizationStore = usePersonalizationStore();
|
||||
const expandedGroups = ref(new Set<string>());
|
||||
const thinkingRefs = new Map<string, HTMLElement>();
|
||||
const summaryLoaders = new Map<string, Component>();
|
||||
const SUMMARY_PREVIEW_MAX_CHARS = 25;
|
||||
const TOOL_REEL_ITEM_HEIGHT = 26;
|
||||
const TOOL_REEL_INTERVAL_MS = 1450;
|
||||
const TOOL_REEL_ROLL_MS = 520;
|
||||
@ -230,14 +231,6 @@ const toolReelStates = reactive<Record<string, ToolReelState>>({});
|
||||
const toolReelIntervals = new Map<string, number>();
|
||||
const toolReelTimeouts = new Map<string, number[]>();
|
||||
|
||||
const truncateSummaryPreview = (raw: string) => {
|
||||
const text = typeof raw === 'string' ? raw : '';
|
||||
if (text.length > SUMMARY_PREVIEW_MAX_CHARS) {
|
||||
return `${text.slice(0, SUMMARY_PREVIEW_MAX_CHARS)}...`;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const getAnimatedSummaryChars = (text: string) => Array.from(text || '');
|
||||
|
||||
const getFirstLine = (raw: string) => {
|
||||
@ -254,7 +247,7 @@ const getToolSummaryText = (action: Action) => {
|
||||
const intentText = tool.intent_rendered || tool.intent_full || '';
|
||||
|
||||
if (intentEnabled && intentText) {
|
||||
return truncateSummaryPreview(getFirstLine(intentText));
|
||||
return getFirstLine(intentText);
|
||||
}
|
||||
|
||||
if (tool.status === 'preparing') {
|
||||
@ -426,10 +419,13 @@ const finishToolReel = (groupId: string, actions: Action[]) => {
|
||||
}, TOOL_REEL_ROLL_MS);
|
||||
pushToolReelTimeout(groupId, settleTimeout);
|
||||
|
||||
const cleanupTimeout = window.setTimeout(() => {
|
||||
clearToolReelTimers(groupId);
|
||||
delete toolReelStates[groupId];
|
||||
}, TOOL_REEL_ROLL_MS + TOOL_REEL_SETTLE_MS + 260);
|
||||
const cleanupTimeout = window.setTimeout(
|
||||
() => {
|
||||
clearToolReelTimers(groupId);
|
||||
delete toolReelStates[groupId];
|
||||
},
|
||||
TOOL_REEL_ROLL_MS + TOOL_REEL_SETTLE_MS + 260
|
||||
);
|
||||
pushToolReelTimeout(groupId, cleanupTimeout);
|
||||
});
|
||||
};
|
||||
@ -438,7 +434,11 @@ const syncToolReels = () => {
|
||||
const activeGroups = new Set<string>();
|
||||
|
||||
blockGroups.value.forEach((group) => {
|
||||
if (group.type !== 'summary' || !group.actions || !shouldShowToolReel(group.actions, group.id)) {
|
||||
if (
|
||||
group.type !== 'summary' ||
|
||||
!group.actions ||
|
||||
!shouldShowToolReel(group.actions, group.id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -584,22 +584,64 @@ const blockGroups = computed(() => {
|
||||
return groups;
|
||||
});
|
||||
|
||||
// 获取摘要组的预览文本(始终显示最新的思考/工具)
|
||||
const getSummaryPreview = (actions: Action[]) => {
|
||||
// 优先返回正在 streaming 的最后一个
|
||||
type ToolCategory = 'read' | 'command' | 'edit' | 'search' | 'webpage' | 'other';
|
||||
|
||||
const TOOL_CATEGORY_MAP: Record<string, ToolCategory> = {
|
||||
// 读取文件
|
||||
read_file: 'read',
|
||||
read_skill: 'read',
|
||||
|
||||
// 运行指令
|
||||
run_command: 'command',
|
||||
terminal_session: 'command',
|
||||
terminal_input: 'command',
|
||||
terminal_snapshot: 'command',
|
||||
|
||||
// 编辑文件
|
||||
create_file: 'edit',
|
||||
write_file: 'edit',
|
||||
edit_file: 'edit',
|
||||
delete_file: 'edit',
|
||||
rename_file: 'edit',
|
||||
create_folder: 'edit',
|
||||
save_webpage: 'edit',
|
||||
|
||||
// 搜索
|
||||
web_search: 'search',
|
||||
|
||||
// 查看网页
|
||||
extract_webpage: 'webpage'
|
||||
};
|
||||
|
||||
const CATEGORY_LABELS: Record<ToolCategory, (count: number) => string> = {
|
||||
read: (n) => `读取了 ${n} 个文件`,
|
||||
command: (n) => `运行了 ${n} 个指令`,
|
||||
edit: (n) => `编辑了 ${n} 次文件`,
|
||||
search: (n) => `搜索了 ${n} 次`,
|
||||
webpage: (n) => `查看了 ${n} 次网页`,
|
||||
other: (n) => `执行了 ${n} 次其他操作`
|
||||
};
|
||||
|
||||
const CATEGORY_ORDER: ToolCategory[] = ['read', 'command', 'edit', 'search', 'webpage', 'other'];
|
||||
|
||||
const getToolCategory = (action: Action): ToolCategory => {
|
||||
const name = action.tool?.name;
|
||||
if (typeof name !== 'string') return 'other';
|
||||
return TOOL_CATEGORY_MAP[name] || 'other';
|
||||
};
|
||||
|
||||
// 运行中:显示当前最新步骤的意图或状态(单行,由 CSS 截断)
|
||||
const getRunningSummaryText = (actions: Action[]): string => {
|
||||
const streamingActions = actions.filter((a) => a.streaming);
|
||||
|
||||
let currentStep;
|
||||
if (streamingActions.length > 0) {
|
||||
// 如果有 streaming 的,返回最后一个 streaming 的
|
||||
currentStep = streamingActions[streamingActions.length - 1];
|
||||
} else {
|
||||
// 如果没有 streaming 的(全部完成了),优先返回最后一个工具
|
||||
const toolActions = actions.filter((a) => a.type === 'tool');
|
||||
if (toolActions.length > 0) {
|
||||
currentStep = toolActions[toolActions.length - 1];
|
||||
} else {
|
||||
// 如果没有工具,返回最后一个思考
|
||||
currentStep = actions[actions.length - 1];
|
||||
}
|
||||
}
|
||||
@ -607,13 +649,11 @@ const getSummaryPreview = (actions: Action[]) => {
|
||||
if (!currentStep) return '';
|
||||
|
||||
if (currentStep.type === 'thinking') {
|
||||
// 思考内容:最多显示到第一个换行,且不超过25字符(不含...)
|
||||
const content = currentStep.content || '';
|
||||
const firstLineEnd = content.indexOf('\n');
|
||||
const textToShow = firstLineEnd > 0 ? content.substring(0, firstLineEnd) : content;
|
||||
return truncateSummaryPreview(textToShow);
|
||||
} else if (currentStep.type === 'tool') {
|
||||
// 工具:优先显示 intent_rendered 或 intent_full
|
||||
return getFirstLine(content);
|
||||
}
|
||||
|
||||
if (currentStep.type === 'tool') {
|
||||
const tool = currentStep.tool;
|
||||
if (!tool) return '';
|
||||
|
||||
@ -621,13 +661,9 @@ const getSummaryPreview = (actions: Action[]) => {
|
||||
const intentText = tool.intent_rendered || tool.intent_full || '';
|
||||
|
||||
if (intentEnabled && intentText) {
|
||||
// 开启intent模式且有intent时,只显示intent
|
||||
const firstLineEnd = intentText.indexOf('\n');
|
||||
const textToShow = firstLineEnd > 0 ? intentText.substring(0, firstLineEnd) : intentText;
|
||||
return truncateSummaryPreview(textToShow);
|
||||
return getFirstLine(intentText);
|
||||
}
|
||||
|
||||
// 没有intent或未开启intent模式时显示状态
|
||||
if (tool.status === 'preparing') {
|
||||
return `准备调用 ${tool.name || '工具'}...`;
|
||||
}
|
||||
@ -635,15 +671,64 @@ const getSummaryPreview = (actions: Action[]) => {
|
||||
return `正在调用 ${tool.name || '工具'}...`;
|
||||
}
|
||||
if (tool.status === 'completed') {
|
||||
return truncateSummaryPreview(tool.display_name || tool.name || '工具执行完成');
|
||||
return tool.display_name || tool.name || '工具执行完成';
|
||||
}
|
||||
return currentStep.streaming
|
||||
? '正在执行工具...'
|
||||
: truncateSummaryPreview(tool.display_name || tool.name || '工具执行完成');
|
||||
return currentStep.streaming ? '正在执行工具...' : tool.display_name || tool.name || '执行工具';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
// 完成后:汇总本组所有工具的执行次数
|
||||
const getCompletedSummaryText = (actions: Action[]): string => {
|
||||
const toolActions = actions.filter((a) => a.type === 'tool');
|
||||
|
||||
if (toolActions.length === 0) {
|
||||
// 没有工具时回退到最后一条思考的单行内容
|
||||
const lastThinking = actions.filter((a) => a.type === 'thinking').pop();
|
||||
if (lastThinking) {
|
||||
return getFirstLine(lastThinking.content || '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// 只有一个工具时,直接显示该工具的 intent/名称
|
||||
if (toolActions.length === 1) {
|
||||
return getToolSummaryText(toolActions[0]);
|
||||
}
|
||||
|
||||
const counts: Record<ToolCategory, number> = {
|
||||
read: 0,
|
||||
command: 0,
|
||||
edit: 0,
|
||||
search: 0,
|
||||
webpage: 0,
|
||||
other: 0
|
||||
};
|
||||
|
||||
toolActions.forEach((action) => {
|
||||
counts[getToolCategory(action)]++;
|
||||
});
|
||||
|
||||
const parts: string[] = [];
|
||||
CATEGORY_ORDER.forEach((category) => {
|
||||
const count = counts[category];
|
||||
if (count > 0) {
|
||||
parts.push(CATEGORY_LABELS[category](count));
|
||||
}
|
||||
});
|
||||
|
||||
return parts.join(',');
|
||||
};
|
||||
|
||||
// 摘要行统一入口:运行中显示实时进度,完成后显示统计总结
|
||||
const getSummaryLineText = (actions: Action[], groupId: string): string => {
|
||||
if (isSummaryRunning(actions, groupId)) {
|
||||
return getRunningSummaryText(actions);
|
||||
}
|
||||
return getCompletedSummaryText(actions);
|
||||
};
|
||||
|
||||
// 判断摘要组是否正在运行(显示加载动画还是对勾)
|
||||
const isSummaryRunning = (actions: Action[], groupId: string) => {
|
||||
if (!props.conversationRunning || !props.isLatestMessage) {
|
||||
@ -890,10 +975,11 @@ onBeforeUnmount(() => {
|
||||
|
||||
.summary-preview {
|
||||
display: block;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
mask-image: linear-gradient(to right, black 85%, transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(to right, black 85%, transparent 100%);
|
||||
}
|
||||
|
||||
.summary-tool-reel-window {
|
||||
@ -1090,8 +1176,7 @@ onBeforeUnmount(() => {
|
||||
margin-top: calc((1em - 1.7em) / 2);
|
||||
/* 滚动条样式(参考 git 状态侧边栏文件列表,滚动槽透明贴合灰色底色) */
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: color-mix(in srgb, var(--claude-text-secondary) 55%, transparent)
|
||||
transparent;
|
||||
scrollbar-color: color-mix(in srgb, var(--claude-text-secondary) 55%, transparent) transparent;
|
||||
}
|
||||
|
||||
.thinking-content::-webkit-scrollbar {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user