fix: 完成系统向智能体数据链路切换

- 切换工作台、聊天历史、资源候选与公共调用到 Agent

- 加固资源绑定、删除保护及发布运行并发控制

- 隔离旧 Bot 专属服务和组件并保留兼容入口
This commit is contained in:
2026-07-31 14:24:15 +08:00
parent f0aba1eddd
commit f872eac1f9
114 changed files with 5997 additions and 874 deletions

View File

@@ -28,7 +28,8 @@ import ShowJson from '#/components/json/ShowJson.vue';
interface Props {
allowRegenerate?: boolean;
allowVariantSwitch?: boolean;
bot: any;
assistant?: any;
bot?: any;
messages: ChatTimeTimelineItem[];
regenerateDisabled?: boolean;
switchingRoundIds?: string[];
@@ -54,7 +55,7 @@ const emit = defineEmits<{
}>();
function getAssistantAvatar() {
return props.bot.icon || defaultAssistantAvatar;
return props.assistant?.icon || props.bot?.icon || defaultAssistantAvatar;
}
function getUserAvatar() {
return store.userInfo?.avatar || defaultUserAvatar;

View File

@@ -1,3 +1,3 @@
export { default as ChatBubbleList } from './bubbleList.vue';
export { default as ChatBubbleList } from '../chat-timeline/ChatBubbleList.vue';
export { default as ChatContainer } from './container.vue';
export { default as ChatSender } from './sender.vue';

View File

@@ -28,7 +28,7 @@ import {
import { tryit } from 'radash';
import { api } from '#/api/request';
import { ChatBubbleList } from '#/components/chat';
import ChatBubbleList from '#/components/chat-timeline/ChatBubbleList.vue';
const route = useRoute();
const router = useRouter();
@@ -58,7 +58,10 @@ const messagePage = ref({
pageNumber: 1,
pageSize: 20,
});
const variantSwitchController = createChatVariantSwitchController<any, ChatTimeTimelineItem>({
const variantSwitchController = createChatVariantSwitchController<
any,
ChatTimeTimelineItem
>({
mapRecords: (records) => ChatTimeHistoryMapper.fromHistoryRecords(records),
onError: () => ElMessage.error('答案版本切换失败'),
onStateChange: () => {
@@ -78,7 +81,11 @@ const filteredSessions = computed(() => {
const title = String(item.title || '').toLowerCase();
const preview = String(item.lastMessagePreview || '').toLowerCase();
const assistantName = String(item.assistantName || '').toLowerCase();
return title.includes(keyword) || preview.includes(keyword) || assistantName.includes(keyword);
return (
title.includes(keyword) ||
preview.includes(keyword) ||
assistantName.includes(keyword)
);
});
});
@@ -99,19 +106,20 @@ watch(
messageList.value = [];
return;
}
if (!currentSession.value || String(currentSession.value.id) !== String(sessionId)) {
if (
!currentSession.value ||
String(currentSession.value.id) !== String(sessionId)
) {
await openSession(String(sessionId));
}
},
);
async function fetchAssistants() {
const [, res] = await tryit(api.get)('/userCenter/bot/list', {
params: { status: 1 },
});
const [, res] = await tryit(api.get)('/userCenter/agent/list');
if (res?.errorCode === 0) {
assistantList.value = (res.data || []).map((item: any) => ({
label: item.title,
label: item.name,
value: item.id,
}));
}
@@ -135,7 +143,9 @@ async function fetchSessions() {
async function openSession(sessionId: string | number) {
drawerLoading.value = true;
const [, summaryRes] = await tryit(api.get)(`/userCenter/chatHistory/sessions/${sessionId}`);
const [, summaryRes] = await tryit(api.get)(
`/userCenter/chatHistory/sessions/${sessionId}`,
);
if (summaryRes?.errorCode !== 0) {
drawerLoading.value = false;
return;
@@ -306,9 +316,12 @@ async function renameSession(session: any) {
if (!value) {
return;
}
const [, res] = await tryit(api.post)(`/userCenter/chatHistory/sessions/${session.id}/rename`, {
title: value,
});
const [, res] = await tryit(api.post)(
`/userCenter/chatHistory/sessions/${session.id}/rename`,
{
title: value,
},
);
if (res?.errorCode === 0) {
ElMessage.success('重命名成功');
if (currentSession.value?.id === session.id) {
@@ -323,16 +336,21 @@ function deleteSession(session: any) {
type: 'warning',
confirmButtonText: '删除',
cancelButtonText: '取消',
}).then(async () => {
const [, res] = await tryit(api.post)(`/userCenter/chatHistory/sessions/${session.id}/delete`, {});
if (res?.errorCode === 0) {
ElMessage.success('删除成功');
if (currentSession.value?.id === session.id) {
closeDrawer();
})
.then(async () => {
const [, res] = await tryit(api.post)(
`/userCenter/chatHistory/sessions/${session.id}/delete`,
{},
);
if (res?.errorCode === 0) {
ElMessage.success('删除成功');
if (currentSession.value?.id === session.id) {
closeDrawer();
}
await fetchSessions();
}
await fetchSessions();
}
}).catch(() => {});
})
.catch(() => {});
}
function formatTime(value?: string) {
@@ -350,7 +368,12 @@ function formatTime(value?: string) {
<template>
<ElContainer class="bg-background-deep h-full">
<ElHeader class="!h-auto !px-8 !pb-0 !pt-8">
<ElSpace direction="vertical" :size="20" alignment="flex-start" class="w-full">
<ElSpace
direction="vertical"
:size="20"
alignment="flex-start"
class="w-full"
>
<div>
<h1 class="text-2xl font-medium">聊天历史</h1>
<p class="text-foreground/60 mt-2 text-sm">
@@ -377,7 +400,9 @@ function formatTime(value?: string) {
</ElHeader>
<ElMain class="!px-8 !pb-8">
<div class="bg-background border-border min-h-full rounded-2xl border p-5">
<div
class="bg-background border-border min-h-full rounded-2xl border p-5"
>
<div v-if="filteredSessions.length > 0" class="flex flex-col gap-3">
<button
v-for="item in filteredSessions"
@@ -387,27 +412,48 @@ function formatTime(value?: string) {
>
<div class="min-w-0 flex-1">
<div class="flex items-center gap-3">
<span class="truncate text-base font-medium">{{ item.title || '未命名会话' }}</span>
<ElTag size="small" effect="plain">{{ item.assistantName || '聊天助理' }}</ElTag>
<span class="truncate text-base font-medium">{{
item.title || '未命名会话'
}}</span>
<ElTag size="small" effect="plain">{{
item.assistantName || '聊天助理'
}}</ElTag>
</div>
<div class="text-foreground/65 mt-2 line-clamp-2 text-sm">
{{ item.lastMessagePreview || '暂无消息内容' }}
</div>
<div class="text-foreground/50 mt-3 flex flex-wrap items-center gap-4 text-xs">
<div
class="text-foreground/50 mt-3 flex flex-wrap items-center gap-4 text-xs"
>
<span>最近发送人{{ item.lastSenderName || '未知' }}</span>
<span>消息数{{ item.messageCount || 0 }}</span>
<span>活跃时间{{ formatTime(item.lastMessageAt || item.accessAt) }}</span>
<span
>活跃时间{{
formatTime(item.lastMessageAt || item.accessAt)
}}</span
>
</div>
</div>
<div class="flex items-center gap-2">
<ElButton link :icon="Edit" @click.stop="renameSession(item)">重命名</ElButton>
<ElButton link type="danger" :icon="Delete" @click.stop="deleteSession(item)">删除</ElButton>
<ElButton link :icon="Edit" @click.stop="renameSession(item)"
>重命名</ElButton
>
<ElButton
link
type="danger"
:icon="Delete"
@click.stop="deleteSession(item)"
>删除</ElButton
>
</div>
</button>
</div>
<ElEmpty v-else description="暂无聊天历史" />
<div class="mt-6 flex justify-end" v-if="pageState.total > queryParams.pageSize">
<div
class="mt-6 flex justify-end"
v-if="pageState.total > queryParams.pageSize"
>
<ElPagination
background
layout="prev, pager, next"
@@ -429,15 +475,23 @@ function formatTime(value?: string) {
@close="closeDrawer"
>
<div v-loading="drawerLoading" class="flex h-full flex-col">
<div class="border-border mb-4 flex items-center justify-between rounded-2xl border px-4 py-3">
<div
class="border-border mb-4 flex items-center justify-between rounded-2xl border px-4 py-3"
>
<div class="min-w-0">
<div class="truncate text-base font-medium">{{ currentSession?.title || '聊天详情' }}</div>
<div class="truncate text-base font-medium">
{{ currentSession?.title || '聊天详情' }}
</div>
<div class="text-foreground/55 mt-1 text-sm">
{{ currentSession?.assistantName || '聊天助理' }}
</div>
</div>
<div class="text-foreground/50 text-xs">
{{ formatTime(currentSession?.lastMessageAt || currentSession?.accessAt) }}
{{
formatTime(
currentSession?.lastMessageAt || currentSession?.accessAt,
)
}}
</div>
</div>
@@ -453,7 +507,7 @@ function formatTime(value?: string) {
</ElButton>
</div>
<ChatBubbleList
:bot="{ icon: '', title: currentSession?.assistantName || '' }"
:assistant="{ icon: '', title: currentSession?.assistantName || '' }"
:messages="messageList"
allow-variant-switch
:switching-round-ids="currentSwitchingRoundIds()"