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

@@ -1,11 +1,15 @@
<script setup lang="ts">
import type { ChatTimeTimelineItem } from '@easyflow/types';
import type {
ChatTimeAssistantSegment,
ChatTimeAssistantTextSegment,
ChatTimeTimelineItem,
} from '@easyflow/types';
import { computed, ref } from 'vue';
import {computed, ref} from 'vue';
import { ChatThinkingBlock, ChatTimeMarkdown } from '@easyflow/common-ui';
import { IconifyIcon } from '@easyflow/icons';
import { useUserStore } from '@easyflow/stores';
import {ChatThinkingBlock, ChatTimeMarkdown} from '@easyflow/common-ui';
import {IconifyIcon} from '@easyflow/icons';
import {useUserStore} from '@easyflow/stores';
import {
ArrowLeft,
@@ -15,7 +19,7 @@ import {
Loading,
RefreshRight,
} from '@element-plus/icons-vue';
import { ElAvatar, ElIcon, ElMessage } from 'element-plus';
import {ElAvatar, ElIcon, ElMessage} from 'element-plus';
import defaultAssistantAvatar from '#/assets/defaultAssistantAvatar.svg';
import defaultUserAvatar from '#/assets/defaultUserAvatar.png';
@@ -85,6 +89,27 @@ function toggleToolExpanded(item: ChatTimeTimelineItem) {
};
}
function getRenderableSegments(item: ChatTimeTimelineItem) {
if (item.role !== 'assistant') {
return [] as ChatTimeAssistantSegment[];
}
const textSegments = item.segments.filter(
(segment): segment is ChatTimeAssistantTextSegment =>
segment.type === 'text',
);
if (textSegments.length <= 1) {
return item.segments;
}
return [
...item.segments.filter((segment) => segment.type !== 'text'),
{
content: textSegments.map((segment) => segment.content).join(''),
id: `${item.id}-merged-text`,
type: 'text' as const,
},
];
}
function canCopy(item: ChatTimeTimelineItem) {
return item.role !== 'tool' && Boolean(String(item.content || '').trim());
}
@@ -190,7 +215,7 @@ async function handleCopy(item: ChatTimeTimelineItem) {
<template #content="{ item }">
<template v-if="item.role === 'assistant'">
<div class="flex flex-col gap-2">
<template v-for="segment in item.segments" :key="segment.id">
<template v-for="segment in getRenderableSegments(item)" :key="segment.id">
<ChatThinkingBlock
v-if="segment.type === 'thinking'"
v-model:expanded="segment.expanded"
@@ -198,7 +223,11 @@ async function handleCopy(item: ChatTimeTimelineItem) {
:status="segment.status"
class="chat-thinking-block-item"
/>
<ChatTimeMarkdown v-else :content="segment.content" />
<ChatTimeMarkdown
v-else
:content="segment.content"
:streaming="item.typing"
/>
</template>
</div>
</template>
@@ -211,8 +240,8 @@ async function handleCopy(item: ChatTimeTimelineItem) {
@click="toggleToolExpanded(item)"
>
<div class="chat-tool-title">
<ElIcon size="14">
<IconifyIcon icon="svg:wrench" />
<ElIcon class="chat-tool-title-icon" size="14" aria-hidden="true">
<IconifyIcon icon="mdi:hammer" />
</ElIcon>
<span class="chat-tool-title-text">{{ getToolName(item) }}</span>
</div>
@@ -254,7 +283,11 @@ async function handleCopy(item: ChatTimeTimelineItem) {
</div>
</div>
</template>
<ChatTimeMarkdown v-else :content="item.content" />
<ChatTimeMarkdown
v-else
:content="item.content"
:streaming="item.typing"
/>
</template>
<template #footer="{ item }">
@@ -369,6 +402,11 @@ async function handleCopy(item: ChatTimeTimelineItem) {
color: hsl(var(--text-strong));
}
.chat-tool-title-icon {
flex-shrink: 0;
color: hsl(var(--primary));
}
.chat-tool-title-text {
overflow: hidden;
font-size: 12px;