feat: 全新智能体功能
- 基于先进智能体框架,增加智能体编排功能 - 增加智能体聊天,并对接持久化
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user