From f3041f682f691c0caf1cb855cd24711ff0e6f584 Mon Sep 17 00:00:00 2001 From: digua Date: Tue, 18 Aug 2026 18:44:28 +0800 Subject: [PATCH] feat(ai): refine chat process experience --- src/components/AIChat/ChatExplorer.vue | 46 ++++---- src/components/AIChat/chat/ChatMessage.vue | 110 +++++++++++++++--- .../chat/chatMessageProcessSegments.test.ts | 18 +-- .../AIChat/chat/chatMessageProcessSegments.ts | 23 ++-- .../AIChat/input/AIChatComposer.vue | 15 ++- src/components/AIChat/input/AIChatInput.vue | 4 +- src/i18n/locales/en-US/ai.json | 4 +- src/i18n/locales/ja-JP/ai.json | 4 +- src/i18n/locales/zh-CN/ai.json | 4 +- src/i18n/locales/zh-TW/ai.json | 4 +- src/stores/aiChatChartBlocks.test.ts | 31 ++++- src/stores/aiChatChartBlocks.ts | 29 ++++- 12 files changed, 224 insertions(+), 68 deletions(-) diff --git a/src/components/AIChat/ChatExplorer.vue b/src/components/AIChat/ChatExplorer.vue index fbb843a5..f11720af 100644 --- a/src/components/AIChat/ChatExplorer.vue +++ b/src/components/AIChat/ChatExplorer.vue @@ -564,27 +564,33 @@ watch(
- +
+ - - + +
diff --git a/src/components/AIChat/chat/ChatMessage.vue b/src/components/AIChat/chat/ChatMessage.vue index 1d2f6244..766fabcf 100644 --- a/src/components/AIChat/chat/ChatMessage.vue +++ b/src/components/AIChat/chat/ChatMessage.vue @@ -167,7 +167,14 @@ const visibleBlocks = computed(() => { }) function isFoldableProcessBlock(block: ContentBlock): boolean { - return block.type === 'think' || block.type === 'tool' || block.type === 'plan' || block.type === 'plan_draft' + return ( + block.type === 'think' || + block.type === 'tool' || + block.type === 'skill' || + block.type === 'plan' || + block.type === 'plan_draft' || + block.type === 'error' + ) } function isTextBlock(block: ContentBlock): boolean { @@ -197,7 +204,7 @@ function isProcessSegmentOpen(segmentIndex: number): boolean { const key = getProcessSegmentKey(segmentIndex) const override = processSegmentOpenOverrides.value[key] if (override !== undefined) return override - return isProcessingProcessSegment(segmentIndex) + return hasProcessSegmentError(renderSegments.value[segmentIndex]) } function toggleProcessSegment(segmentIndex: number): void { @@ -213,7 +220,47 @@ function isLastVisibleBlock(block: ContentBlock): boolean { } function isProcessingProcessSegment(segmentIndex: number): boolean { - return !!props.isStreaming && segmentIndex === renderSegments.value.length - 1 + return !!props.isStreaming && renderSegments.value[segmentIndex]?.type === 'process' +} + +function getProcessSegmentStepCount(segment: ProcessSegment): number { + if (segment.type !== 'process') return 0 + return segment.blocks.filter(isFoldableProcessBlock).length +} + +function getProcessSegmentStepLabel(segment: ProcessSegment): string { + const count = getProcessSegmentStepCount(segment) + const key = count === 1 ? 'ai.chat.message.process.step' : 'ai.chat.message.process.steps' + return t(key, { count }) +} + +function getSingleLineText(text: string): string { + return text.replace(/\s+/g, ' ').trim() +} + +function getProcessSegmentPreview(segment: ProcessSegment): string { + if (segment.type !== 'process') return '' + + for (let index = segment.blocks.length - 1; index >= 0; index -= 1) { + const block = segment.blocks[index] + if (block.type === 'text' || block.type === 'think' || block.type === 'plan_draft') { + const text = getSingleLineText(block.text) + if (text) return text + } + if (block.type === 'tool') return getToolDisplayName(block.tool) + if (block.type === 'skill') return block.skillName + if (block.type === 'plan') return block.plan.title + if (block.type === 'error') return block.error.message ?? block.error.name ?? '' + } + + return '' +} + +function hasProcessSegmentError(segment: ProcessSegment | undefined): boolean { + if (segment?.type !== 'process') return false + return segment.blocks.some( + (block) => block.type === 'error' || (block.type === 'tool' && block.tool.status === 'error') + ) } function getBlockDurationMs(block: ContentBlock): number { @@ -651,7 +698,7 @@ async function handleCopyMarkdown() {