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> </span>
<template v-else> <template v-else>
<span <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}`" :key="`${group.id}-${idx}`"
class="summary-char" class="summary-char"
:style="{ animationDelay: `${(idx + 1) * 0.12}s` }" :style="{ animationDelay: `${(idx + 1) * 0.12}s` }"
@ -44,7 +46,7 @@
</template> </template>
</template> </template>
<template v-else> <template v-else>
{{ getSummaryPreview(group.actions) }} {{ getSummaryLineText(group.actions, group.id) }}
</template> </template>
</span> </span>
</div> </div>
@ -208,7 +210,6 @@ const personalizationStore = usePersonalizationStore();
const expandedGroups = ref(new Set<string>()); const expandedGroups = ref(new Set<string>());
const thinkingRefs = new Map<string, HTMLElement>(); const thinkingRefs = new Map<string, HTMLElement>();
const summaryLoaders = new Map<string, Component>(); const summaryLoaders = new Map<string, Component>();
const SUMMARY_PREVIEW_MAX_CHARS = 25;
const TOOL_REEL_ITEM_HEIGHT = 26; const TOOL_REEL_ITEM_HEIGHT = 26;
const TOOL_REEL_INTERVAL_MS = 1450; const TOOL_REEL_INTERVAL_MS = 1450;
const TOOL_REEL_ROLL_MS = 520; const TOOL_REEL_ROLL_MS = 520;
@ -230,14 +231,6 @@ const toolReelStates = reactive<Record<string, ToolReelState>>({});
const toolReelIntervals = new Map<string, number>(); const toolReelIntervals = new Map<string, number>();
const toolReelTimeouts = 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 getAnimatedSummaryChars = (text: string) => Array.from(text || '');
const getFirstLine = (raw: string) => { const getFirstLine = (raw: string) => {
@ -254,7 +247,7 @@ const getToolSummaryText = (action: Action) => {
const intentText = tool.intent_rendered || tool.intent_full || ''; const intentText = tool.intent_rendered || tool.intent_full || '';
if (intentEnabled && intentText) { if (intentEnabled && intentText) {
return truncateSummaryPreview(getFirstLine(intentText)); return getFirstLine(intentText);
} }
if (tool.status === 'preparing') { if (tool.status === 'preparing') {
@ -426,10 +419,13 @@ const finishToolReel = (groupId: string, actions: Action[]) => {
}, TOOL_REEL_ROLL_MS); }, TOOL_REEL_ROLL_MS);
pushToolReelTimeout(groupId, settleTimeout); pushToolReelTimeout(groupId, settleTimeout);
const cleanupTimeout = window.setTimeout(() => { const cleanupTimeout = window.setTimeout(
clearToolReelTimers(groupId); () => {
delete toolReelStates[groupId]; clearToolReelTimers(groupId);
}, TOOL_REEL_ROLL_MS + TOOL_REEL_SETTLE_MS + 260); delete toolReelStates[groupId];
},
TOOL_REEL_ROLL_MS + TOOL_REEL_SETTLE_MS + 260
);
pushToolReelTimeout(groupId, cleanupTimeout); pushToolReelTimeout(groupId, cleanupTimeout);
}); });
}; };
@ -438,7 +434,11 @@ const syncToolReels = () => {
const activeGroups = new Set<string>(); const activeGroups = new Set<string>();
blockGroups.value.forEach((group) => { 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; return;
} }
@ -584,22 +584,64 @@ const blockGroups = computed(() => {
return groups; return groups;
}); });
// / type ToolCategory = 'read' | 'command' | 'edit' | 'search' | 'webpage' | 'other';
const getSummaryPreview = (actions: Action[]) => {
// streaming 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); const streamingActions = actions.filter((a) => a.streaming);
let currentStep; let currentStep;
if (streamingActions.length > 0) { if (streamingActions.length > 0) {
// streaming streaming
currentStep = streamingActions[streamingActions.length - 1]; currentStep = streamingActions[streamingActions.length - 1];
} else { } else {
// streaming
const toolActions = actions.filter((a) => a.type === 'tool'); const toolActions = actions.filter((a) => a.type === 'tool');
if (toolActions.length > 0) { if (toolActions.length > 0) {
currentStep = toolActions[toolActions.length - 1]; currentStep = toolActions[toolActions.length - 1];
} else { } else {
//
currentStep = actions[actions.length - 1]; currentStep = actions[actions.length - 1];
} }
} }
@ -607,13 +649,11 @@ const getSummaryPreview = (actions: Action[]) => {
if (!currentStep) return ''; if (!currentStep) return '';
if (currentStep.type === 'thinking') { if (currentStep.type === 'thinking') {
// 25...
const content = currentStep.content || ''; const content = currentStep.content || '';
const firstLineEnd = content.indexOf('\n'); return getFirstLine(content);
const textToShow = firstLineEnd > 0 ? content.substring(0, firstLineEnd) : content; }
return truncateSummaryPreview(textToShow);
} else if (currentStep.type === 'tool') { if (currentStep.type === 'tool') {
// intent_rendered intent_full
const tool = currentStep.tool; const tool = currentStep.tool;
if (!tool) return ''; if (!tool) return '';
@ -621,13 +661,9 @@ const getSummaryPreview = (actions: Action[]) => {
const intentText = tool.intent_rendered || tool.intent_full || ''; const intentText = tool.intent_rendered || tool.intent_full || '';
if (intentEnabled && intentText) { if (intentEnabled && intentText) {
// intentintentintent return getFirstLine(intentText);
const firstLineEnd = intentText.indexOf('\n');
const textToShow = firstLineEnd > 0 ? intentText.substring(0, firstLineEnd) : intentText;
return truncateSummaryPreview(textToShow);
} }
// intentintent
if (tool.status === 'preparing') { if (tool.status === 'preparing') {
return `准备调用 ${tool.name || '工具'}...`; return `准备调用 ${tool.name || '工具'}...`;
} }
@ -635,15 +671,64 @@ const getSummaryPreview = (actions: Action[]) => {
return `正在调用 ${tool.name || '工具'}...`; return `正在调用 ${tool.name || '工具'}...`;
} }
if (tool.status === 'completed') { if (tool.status === 'completed') {
return truncateSummaryPreview(tool.display_name || tool.name || '工具执行完成'); return tool.display_name || tool.name || '工具执行完成';
} }
return currentStep.streaming return currentStep.streaming ? '正在执行工具...' : tool.display_name || tool.name || '执行工具';
? '正在执行工具...'
: truncateSummaryPreview(tool.display_name || tool.name || '工具执行完成');
} }
return ''; 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) => { const isSummaryRunning = (actions: Action[], groupId: string) => {
if (!props.conversationRunning || !props.isLatestMessage) { if (!props.conversationRunning || !props.isLatestMessage) {
@ -890,10 +975,11 @@ onBeforeUnmount(() => {
.summary-preview { .summary-preview {
display: block; display: block;
white-space: normal; white-space: nowrap;
overflow-wrap: anywhere; overflow: hidden;
word-break: break-word;
color: inherit; 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 { .summary-tool-reel-window {
@ -1090,8 +1176,7 @@ onBeforeUnmount(() => {
margin-top: calc((1em - 1.7em) / 2); margin-top: calc((1em - 1.7em) / 2);
/* 滚动条样式(参考 git 状态侧边栏文件列表,滚动槽透明贴合灰色底色) */ /* 滚动条样式(参考 git 状态侧边栏文件列表,滚动槽透明贴合灰色底色) */
scrollbar-width: thin; /* Firefox */ scrollbar-width: thin; /* Firefox */
scrollbar-color: color-mix(in srgb, var(--claude-text-secondary) 55%, transparent) scrollbar-color: color-mix(in srgb, var(--claude-text-secondary) 55%, transparent) transparent;
transparent;
} }
.thinking-content::-webkit-scrollbar { .thinking-content::-webkit-scrollbar {