feat(chat): 极简模式摘要行改为单行渐变+工具统计总结

This commit is contained in:
JOJO 2026-07-07 16:45:59 +08:00
parent 478d24c4dd
commit 2264a86851

View File

@ -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) {
// intentintentintent
const firstLineEnd = intentText.indexOf('\n');
const textToShow = firstLineEnd > 0 ? intentText.substring(0, firstLineEnd) : intentText;
return truncateSummaryPreview(textToShow);
return getFirstLine(intentText);
}
// intentintent
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 {