feat: 收口工作流对话最终结果展示

- 主时间线只展示顶级工作流最终输出和有限运行进度

- 补齐结构化结果、人工审核交互与实时详情回归

- 优化数组层级和复制操作
This commit is contained in:
2026-07-31 14:41:43 +08:00
parent f92707dac8
commit 0754ad0792
13 changed files with 1531 additions and 330 deletions

View File

@@ -149,27 +149,33 @@ watch(
</div>
</div>
<template v-else>
<ChatTimelineItem
v-for="item in items"
:key="item.id"
:assistant-actions-visible="isAssistantActionAnchor(item)"
:item="item"
:document-loader="documentLoader"
:image-loader="imageLoader"
:approval-loading="approvalLoading"
:copy-action="copyAction"
:copyable="canCopyMessage(item)"
:regenerable="canRegenerateMessage(item)"
:regenerate-disabled="regenerateDisabled"
:variant-loading="isVariantLoading(item)"
@approve="emit('approve', $event)"
@copy-message="emit('copyMessage', $event)"
@regenerate-message="emit('regenerateMessage', $event)"
@reject="emit('reject', $event)"
@select-next-variant="emit('selectNextVariant', $event)"
@select-previous-variant="emit('selectPreviousVariant', $event)"
@thinking-toggle="handleThinkingToggle"
/>
<template v-for="item in items" :key="item.id">
<slot
v-if="item.type === 'custom'"
name="custom-item"
:item="item"
></slot>
<ChatTimelineItem
v-else
:assistant-actions-visible="isAssistantActionAnchor(item)"
:item="item"
:document-loader="documentLoader"
:image-loader="imageLoader"
:approval-loading="approvalLoading"
:copy-action="copyAction"
:copyable="canCopyMessage(item)"
:regenerable="canRegenerateMessage(item)"
:regenerate-disabled="regenerateDisabled"
:variant-loading="isVariantLoading(item)"
@approve="emit('approve', $event)"
@copy-message="emit('copyMessage', $event)"
@regenerate-message="emit('regenerateMessage', $event)"
@reject="emit('reject', $event)"
@select-next-variant="emit('selectNextVariant', $event)"
@select-previous-variant="emit('selectPreviousVariant', $event)"
@thinking-toggle="handleThinkingToggle"
/>
</template>
</template>
</div>
</template>

View File

@@ -0,0 +1,29 @@
import { mount } from '@vue/test-utils';
import { h } from 'vue';
import { describe, expect, it } from 'vitest';
import ChatTimeline from '../ChatTimeline.vue';
describe('chat timeline custom item', () => {
it('renders business content through the custom item slot', () => {
const wrapper = mount(ChatTimeline, {
props: {
items: [
{
customType: 'workflow-final-output',
data: { answer: '完成' },
id: 'final-result',
type: 'custom',
},
],
},
slots: {
'custom-item': ({ item }: any) =>
h('div', { class: 'custom-result' }, item.customType),
},
});
expect(wrapper.get('.custom-result').text()).toBe('workflow-final-output');
});
});

View File

@@ -16,6 +16,7 @@ export type {
ChatDocumentLoader,
ChatImageAttachment,
ChatImageLoader,
ChatTimelineCustomItem,
ChatTimelineErrorItem,
ChatTimelineItem,
ChatTimelineItemStatus,

View File

@@ -142,7 +142,14 @@ export interface ChatTimelineErrorItem extends ChatTimelineItemBase {
type: 'error';
}
export interface ChatTimelineCustomItem extends ChatTimelineItemBase {
customType: string;
data: unknown;
type: 'custom';
}
export type ChatTimelineItem =
| ChatTimelineCustomItem
| ChatTimelineErrorItem
| ChatTimelineKnowledgeItem
| ChatTimelineMessageItem