feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -0,0 +1,246 @@
<script setup lang="ts">
import type {
ChatTimelineItem,
ChatTimelineMessageItem,
ChatTimelineMessagePart,
ChatTimelineToolApprovalPayload,
} from './types';
import {computed} from 'vue';
import ChatThinkingBlock from '../chat-thinking/ChatThinkingBlock.vue';
import ChatErrorNotice from './ChatErrorNotice.vue';
import ChatKnowledgeCard from './ChatKnowledgeCard.vue';
import ChatMessageToolbar from './ChatMessageToolbar.vue';
import ChatTextBlock from './ChatTextBlock.vue';
import ChatTimelineStatusRow from './ChatTimelineStatusRow.vue';
import ChatToolCard from './ChatToolCard.vue';
const props = defineProps<{
approvalLoading?: boolean;
copyable?: boolean;
regenerable?: boolean;
regenerateDisabled?: boolean;
assistantActionsVisible?: boolean;
item: ChatTimelineItem;
variantLoading?: boolean;
}>();
const emit = defineEmits<{
approve: [payload: ChatTimelineToolApprovalPayload];
copyMessage: [item: ChatTimelineMessageItem];
regenerateMessage: [item: ChatTimelineMessageItem];
reject: [payload: ChatTimelineToolApprovalPayload];
selectNextVariant: [item: ChatTimelineMessageItem];
selectPreviousVariant: [item: ChatTimelineMessageItem];
thinkingToggle: [];
}>();
const messageItem = computed(() =>
props.item.type === 'message' ? props.item : undefined,
);
const alignmentClass = computed(() => {
if (props.item.type === 'message' && props.item.role === 'user') {
return 'is-user';
}
return 'is-assistant';
});
const variantCurrent = computed(() => {
const item = messageItem.value;
return Number(item?.selectedVariantIndex || item?.variantIndex || 1);
});
const variantTotal = computed(() =>
Number(messageItem.value?.variantCount || 1),
);
const showVariantNavigator = computed(() => {
const item = messageItem.value;
return Boolean(
item?.role === 'assistant' &&
item.roundCompleted &&
item.roundId &&
item.switchable !== false &&
variantTotal.value > 1,
);
});
const disabledVariantPrevious = computed(
() => props.variantLoading || variantCurrent.value <= 1,
);
const disabledVariantNext = computed(
() => props.variantLoading || variantCurrent.value >= variantTotal.value,
);
const showToolbar = computed(() => {
const item = messageItem.value;
return Boolean(
item &&
(item.role === 'assistant'
? (props.assistantActionsVisible ?? true) &&
(props.copyable || props.regenerable || showVariantNavigator.value)
: props.copyable),
);
});
function getMessageParts(item: ChatTimelineMessageItem) {
const textParts = item.parts.filter(
(part): part is Extract<ChatTimelineMessagePart, { type: 'text' }> =>
part.type === 'text',
);
if (textParts.length <= 1) {
return item.parts;
}
return [
...item.parts.filter((part) => part.type !== 'text'),
{
content: textParts.map((part) => part.content).join(''),
id: `${item.id}-merged-text`,
type: 'text' as const,
},
];
}
function updateThinkingExpanded(partId: string, expanded: boolean) {
const item = messageItem.value;
if (!item) {
return;
}
const part = item.parts.find((current) => current.id === partId);
if (part?.type === 'thinking') {
emit('thinkingToggle');
part.expanded = expanded;
}
}
</script>
<template>
<div class="chat-timeline-item" :class="alignmentClass">
<div
v-if="messageItem"
class="chat-timeline-item__message"
:class="[
`is-${messageItem.role}`,
{ 'has-variant-navigator': showVariantNavigator },
]"
>
<template v-for="part in getMessageParts(messageItem)" :key="part.id">
<ChatThinkingBlock
v-if="part.type === 'thinking'"
:content="part.content"
:expanded="part.expanded ?? part.status === 'thinking'"
:status="part.status"
class="chat-timeline-item__thinking"
readonly
@update:expanded="updateThinkingExpanded(part.id, $event)"
/>
<ChatTextBlock
v-else
:content="part.content"
:streaming="messageItem.status === 'streaming'"
/>
</template>
<ChatKnowledgeCard
v-if="messageItem.knowledgeItems?.length"
:items="messageItem.knowledgeItems"
/>
<ChatMessageToolbar
v-if="showToolbar && messageItem"
:align="messageItem.role === 'user' ? 'end' : 'start'"
:allow-copy="copyable"
:allow-regenerate="regenerable"
:disabled-variant-next="disabledVariantNext"
:disabled-variant-previous="disabledVariantPrevious"
:regenerate-disabled="regenerateDisabled"
:show-variant-navigator="showVariantNavigator"
:variant-current="variantCurrent"
:variant-loading="variantLoading"
:variant-total="variantTotal"
@copy="emit('copyMessage', messageItem)"
@regenerate="emit('regenerateMessage', messageItem)"
@select-next-variant="emit('selectNextVariant', messageItem)"
@select-previous-variant="emit('selectPreviousVariant', messageItem)"
/>
</div>
<ChatToolCard
v-else-if="item.type === 'tool'"
:item="item"
:loading="approvalLoading"
@approve="emit('approve', $event)"
@reject="emit('reject', $event)"
/>
<ChatKnowledgeCard
v-else-if="item.type === 'knowledge'"
:items="item.items"
/>
<ChatTimelineStatusRow v-else-if="item.type === 'status'" :item="item" />
<ChatErrorNotice
v-else-if="item.type === 'error'"
:message="item.message"
/>
</div>
</template>
<style scoped>
.chat-timeline-item {
display: flex;
width: 100%;
}
.chat-timeline-item.is-user {
justify-content: flex-end;
}
.chat-timeline-item.is-assistant {
justify-content: flex-start;
}
.chat-timeline-item__bubble {
display: flex;
flex-direction: column;
gap: 8px;
max-width: min(78%, 680px);
min-width: 0;
}
.chat-timeline-item__message {
display: flex;
flex-direction: column;
gap: 8px;
max-width: min(78%, 680px);
min-width: 0;
}
.chat-timeline-item__message :deep(.chat-text-block),
.chat-timeline-item__message :deep(.chat-thinking-block) {
padding: 0;
color: var(--el-text-color-primary);
background: transparent;
border: 0;
}
.chat-timeline-item__message :deep(.chat-text-block) {
font-size: 14px;
line-height: 22px;
}
.chat-timeline-item__message.is-user {
color: var(--el-text-color-primary);
}
.chat-timeline-item__message.is-assistant.has-variant-navigator {
width: min(78%, 680px);
}
.chat-timeline-item__bubble.is-assistant,
.chat-timeline-item__bubble.is-system {
color: var(--el-text-color-primary);
}
.chat-timeline-item__thinking {
margin-bottom: 0;
}
</style>