feat: 接入聊天历史界面与外链会话恢复

- 新增管理端与用户端聊天历史接口和页面

- 外链聊天支持访问令牌登录、身份保活与当前会话恢复

- 聊天执行链路切到统一 runtime 与 chatlog 查询接口
This commit is contained in:
2026-04-05 11:37:25 +08:00
parent 25e80433a5
commit a4f75a5e4c
48 changed files with 3724 additions and 972 deletions

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { ChatThinkingBlock } from '@easyflow/common-ui';
import { IconifyIcon } from '@easyflow/icons';
import { useUserStore } from '@easyflow/stores';
@@ -48,13 +49,14 @@ function getUserAvatar() {
v-for="(chain, index) in item.chains"
:key="chain.id || index"
>
<ElThinking
<ChatThinkingBlock
v-if="!('id' in chain)"
v-model="chain.thinlCollapse"
v-model:expanded="chain.thinkingExpanded"
:content="chain.reasoning_content"
:status="chain.thinkingStatus"
class="chat-thinking-block-item"
/>
<ElCollapse v-else class="mb-2">
<ElCollapse v-else class="chat-tool-panel">
<ElCollapseItem :title="chain.name" :name="chain.id">
<template #title>
<div class="flex items-center gap-2 pl-5">
@@ -90,41 +92,6 @@ function getUserAvatar() {
</template>
</template>
<!-- <ElThinking
v-if="item.reasoning_content"
v-model="item.thinlCollapse"
:content="item.reasoning_content"
:status="item.thinkingStatus"
/> -->
<!-- <ElCollapse v-if="item.tools" class="mb-2">
<ElCollapseItem
class="mb-2"
v-for="tool in item.tools"
:key="tool.id"
:title="tool.name"
:name="tool.id"
>
<template #title>
<div class="flex items-center gap-2 pl-5">
<ElIcon size="16">
<IconifyIcon icon="svg:wrench" />
</ElIcon>
<span>{{ tool.name }}</span>
<template v-if="tool.status === 'TOOL_CALL'">
<ElIcon size="16">
<IconifyIcon icon="svg:spinner" />
</ElIcon>
</template>
<template v-else>
<ElIcon size="16" color="var(--el-color-success)">
<CircleCheck />
</ElIcon>
</template>
</div>
</template>
<ShowJson :value="tool.result" />
</ElCollapseItem>
</ElCollapse> -->
</div>
</template>
@@ -162,23 +129,30 @@ function getUserAvatar() {
--bubble-content-max-width: 100%;
}
:deep(.el-thinking) {
margin: 0;
}
:deep(.el-thinking .content-wrapper) {
--el-thinking-content-wrapper-width: 100%;
.chat-thinking-block-item {
margin-bottom: 8px;
}
:deep(.el-collapse) {
:deep(.chat-tool-panel.el-collapse) {
overflow: hidden;
border: 1px solid var(--el-collapse-border-color);
border-radius: 8px;
border: 1px solid hsl(var(--divider-faint) / 0.26);
border-radius: 14px;
background: hsl(var(--surface-panel) / 0.7);
box-shadow: inset 0 1px 0 hsl(var(--glass-border) / 0.2);
}
:deep(.el-collapse-item__content) {
:deep(.chat-tool-panel .el-collapse-item__wrap) {
background: transparent;
}
:deep(.chat-tool-panel .el-collapse-item__header) {
min-height: 44px;
padding-right: 14px;
background: transparent;
border-bottom-color: hsl(var(--divider-faint) / 0.16);
}
:deep(.chat-tool-panel .el-collapse-item__content) {
padding-bottom: 0;
}
</style>

View File

@@ -49,14 +49,16 @@ defineExpose({
function getSessionList(resetSession = false) {
api
.get('/userCenter/botConversation/list', {
.get('/userCenter/chatHistory/sessions', {
params: {
botId: props.bot.id,
assistantId: props.bot.id,
pageNumber: 1,
pageSize: 100,
},
})
.then((res) => {
if (res.errorCode === 0) {
sessionList.value = res.data;
sessionList.value = res.data.records || [];
if (resetSession) {
currentSession.value = {};
}
@@ -92,15 +94,27 @@ function clickSession(session: any) {
}
function getMessageList() {
api
.get('/userCenter/botMessage/getMessages', {
params: {
botId: props.bot.id,
conversationId: currentSession.value.id,
},
.get(`/userCenter/chatHistory/sessions/${currentSession.value.id}/messages`, {
params: { pageNumber: 1, pageSize: 100 },
})
.then((res) => {
if (res.errorCode === 0) {
props.onMessageList?.(res.data);
const records = Array.isArray(res.data?.records) ? [...res.data.records] : [];
props.onMessageList?.(
records.reverse().map((item: any) => ({
key: String(item.id),
role: item.senderRole === 'assistant' ? 'assistant' : 'user',
content:
item.senderRole === 'assistant'
? String(item.contentText || '').replace(/^Final Answer:\s*/i, '')
: item.contentText,
placement: item.senderRole === 'assistant' ? 'start' : 'end',
created: item.created,
chains: Array.isArray(item.contentPayload?.chains)
? item.contentPayload.chains
: undefined,
})),
);
}
});
}
@@ -125,12 +139,8 @@ const updateLoading = ref(false);
function updateTitle() {
updateLoading.value = true;
api
.get('/userCenter/botConversation/updateConversation', {
params: {
botId: props.bot.id,
conversationId: currentSession.value.id,
.post(`/userCenter/chatHistory/sessions/${currentSession.value.id}/rename`, {
title: currentSession.value.title,
},
})
.then((res) => {
updateLoading.value = false;
@@ -150,12 +160,7 @@ function remove(row: any) {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
api
.get('/userCenter/botConversation/deleteConversation', {
params: {
botId: props.bot.id,
conversationId: row.id,
},
})
.post(`/userCenter/chatHistory/sessions/${row.id}/delete`)
.then((res) => {
instance.confirmButtonLoading = false;
if (res.errorCode === 0) {
@@ -175,6 +180,11 @@ function remove(row: any) {
},
}).catch(() => {});
}
function openRenameDialog(row: any) {
currentSession.value = { ...row };
dialogVisible.value = true;
}
</script>
<template>
@@ -230,7 +240,7 @@ function remove(row: any) {
)
"
>
{{ formatCreatedTime(conversation.created) }}
{{ formatCreatedTime(conversation.lastMessageAt || conversation.accessAt || conversation.created) }}
</span>
<ElDropdown
:class="
@@ -249,7 +259,7 @@ function remove(row: any) {
@mouseenter="handleMouseEvent(conversation.id)"
@mouseleave="handleMouseEvent()"
>
<ElDropdownItem @click="dialogVisible = true">
<ElDropdownItem @click="openRenameDialog(conversation)">
<ElButton link :icon="Edit">编辑</ElButton>
</ElDropdownItem>
<ElDropdownItem>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { ChatThinkingBlockStatus } from '@easyflow/common-ui';
import type { BubbleProps } from 'vue-element-plus-x/types/Bubble';
import type { ThinkingStatus } from 'vue-element-plus-x/types/Thinking';
import { inject, ref } from 'vue';
@@ -16,8 +16,8 @@ import ChatFileUploader from '#/components/upload/ChatFileUploader.vue';
type Think = {
reasoning_content?: string;
thinkingStatus?: ThinkingStatus;
thinlCollapse?: boolean;
thinkingExpanded?: boolean;
thinkingStatus?: ChatThinkingBlockStatus;
};
type Tool = {
@@ -152,7 +152,7 @@ function sendMessage() {
if (index === -1) {
chains.push({
thinkingStatus: 'thinking',
thinlCollapse: true,
thinkingExpanded: false,
reasoning_content: delta,
});
} else {