7 Commits

Author SHA1 Message Date
848e8a6271 Merge branch 'feature/dsw' of https://git.nianxx.cn/duanshuwen/zn-ai
# Conflicts:
#	src/renderer/views/home/ChatBox.vue
#	src/renderer/views/home/index.vue

合并代码
2026-03-19 19:43:10 +08:00
zoujing
3fc26d6996 feat: 对话的调整 2026-03-06 14:41:59 +08:00
zoujing
ed04eea481 feat: 增加会话改名和删除的接口 2026-03-06 10:51:54 +08:00
zoujing
ef50aae9d0 feat: 会话列表的交互 2026-03-05 17:04:41 +08:00
zoujing
c85f211c9c feat: 重新对接了会话及相关的接口 2026-03-05 16:29:03 +08:00
c7a37e6816 feat: 消息展示的调整 2026-03-04 22:20:53 +08:00
zoujing
3f2a4a506b feat: 消息会话的调整和增加时间戳 2026-03-04 16:59:47 +08:00
9 changed files with 406 additions and 58 deletions

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http://8.138.234.141 https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com; connect-src 'self' http://8.138.234.141 https://api.iconify.design wss://onefeel.brother7.cn"
/>
</head>
<body>

View File

@@ -0,0 +1,122 @@
import { getRequest, postRequest, patchRequest, deleteRequest, ResponseModel } from '@utils/request'
// 创建会话 的请求参数和响应数据结构
export interface CreateSessionRequest {
title?: string
tenant_id_query?: string
}
export interface CreateSessionResponse {
session_id: string
user_id: string
tenant_id: string
title: string
}
export const createSession = async (params: CreateSessionRequest) => {
const res: ResponseModel = await postRequest('/nianxx/api/sessions', params)
return res.data as CreateSessionResponse
}
// 获取会话列表 的请求参数和响应数据结构
export interface SessionListRequest {
tenant_id_query?: string
limit?: number
offset?: number
}
export interface SessionListResponse {
sessions: Array<SessionListRecords>
total: number
}
export interface SessionListRecords {
session_id: string
user_id: string
tenant_id: string
title: string
status: string
created_at: string
updated_at: string
}
export const getSessionList = async (params: SessionListRequest) => {
const res: ResponseModel = await getRequest('/nianxx/api/sessions', params)
return res.data as SessionListResponse
}
/// 获取会话消息历史 的请求参数和响应数据结构
export interface SessionMessagesRequest {
user_id_query?: string
tenant_id_query?: number
limit?: number
offset?: number
session_id: string
}
export interface SessionMessagesResponse {
messages: Array<SessionMessageRecords>
total: number
}
export interface SessionMessageRecords {
id: number
session_id: string
role: string
content: string
source: string
message_id: string | null
created_at: string
timestamp?: number
}
// 获取会话消息历史 的函数实现
export const getSessionMessages = async (params: SessionMessagesRequest) => {
const res: ResponseModel = await getRequest(`/nianxx/api/sessions/${params.session_id}/messages`, {
limit: params.limit,
offset: params.offset,
user_id_query: params.user_id_query,
tenant_id_query: params.tenant_id_query,
})
return res.data as SessionMessagesResponse
}
/// /api/sessions/{session_id} 的请求参数和响应数据结构
export interface UpdateSessionRequest {
session_id: string
title: string
}
export interface UpdateSessionResponse {
success: boolean
}
// 更新会话信息 的函数实现
export const updateSession = async (params: UpdateSessionRequest) => {
const res: ResponseModel = await postRequest(`/nianxx/api/sessions/${params.session_id}/rename`, {
title: params.title,
})
return res.data as UpdateSessionResponse
}
/// /api/sessions/{session_id} 的请求参数和响应数据结构
export interface DeleteSessionRequest {
session_id: string
}
export interface DeleteSessionResponse {
success: boolean
}
// 删除会话 的函数实现
export const deleteSession = async (params: DeleteSessionRequest) => {
const res: ResponseModel = await postRequest(`/nianxx/api/sessions/${params.session_id}/delete`, {})
return res.data as DeleteSessionResponse
}

View File

@@ -77,6 +77,7 @@ instance.interceptors.request.use(
}
}
console.log(`🚀 ~ request: \n url:${config.url} \n params:${JSON.stringify(config.data)} \n`)
return config
},
(error) => {
@@ -87,7 +88,7 @@ instance.interceptors.request.use(
// 添加响应拦截器
instance.interceptors.response.use(
(res) => {
console.log(`🚀 ~ response: \n url:${res.config.url} \n params:${JSON.stringify(res.config.params)} \n data:\n ${JSON.stringify(res.data)}`)
console.log(`🚀 ~ response: \n url:${res.config.url} \n params:${JSON.stringify(res.config.data)} \n data:\n ${JSON.stringify(res.data)}`)
// 未设置状态码则默认成功状态
const code = res.data.code || 200
// 获取错误信息
@@ -143,7 +144,7 @@ instance.interceptors.response.use(
export const postRequest = <ResponseModel>(url: string, data?: any, options?: any): Promise<ResponseModel> => {
return instance.request({
url,
method: 'POST',
method: 'post',
headers: {
'Content-Type': 'application/json',
},
@@ -156,12 +157,38 @@ export const postRequest = <ResponseModel>(url: string, data?: any, options?: an
export const getRequest = <ResponseModel>(url: string, params?: any, options?: any): Promise<ResponseModel> => {
return instance.request({
url,
method: 'GET',
method: 'get',
params,
...(options || {}),
}) as Promise<ResponseModel>
}
// 封装基于 request 的 PATCH 请求
export const patchRequest = <ResponseModel>(url: string, data?: any, options?: any): Promise<ResponseModel> => {
return instance.request({
url,
method: 'patch',
headers: {
'Content-Type': 'application/json',
},
data,
...(options || {}),
}) as Promise<ResponseModel>
}
// 封装基于 request 的 DELETE 请求
export const deleteRequest = <ResponseModel>(url: string, data?: any, options?: any): Promise<ResponseModel> => {
return instance.request({
url,
method: 'delete',
headers: {
'Content-Type': 'application/json',
},
data,
...(options || {}),
}) as Promise<ResponseModel>
}
export default instance
/// 响应模型

View File

@@ -23,7 +23,7 @@
<ChatRoleMe v-if="msg.messageRole === MessageRole.ME" :msg="msg">
<template #header>
<!-- 名字和时间 -->
<ChatNameTime :showReverse="true" />
<ChatNameTime :showReverse="true" :msg="msg" />
</template>
</ChatRoleMe>
@@ -31,7 +31,7 @@
<ChatRoleAI v-if="msg.messageRole === MessageRole.AI" :msg="msg">
<template #header>
<!-- 名字和时间 -->
<ChatNameTime :showReverse="false" />
<ChatNameTime :showReverse="false" :msg="msg" />
</template>
<template #footer>
@@ -69,7 +69,8 @@
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, nextTick } from "vue";
import { ref, defineProps, defineEmits, watch, nextTick } from 'vue'
import { onMounted, onUnmounted } from "vue";
import { WebSocketManager } from "@common/WebSocketManager";
import { MessageRole, ChatMessage } from "./model/ChatModel";
import { IdUtils } from "@common/index";
@@ -83,11 +84,11 @@ import ChatAttach from './components/ChatAttach.vue';
import ChatInputArea from './components/ChatInputArea.vue';
import TaskCenter from './TaskCenter.vue';
import { Session } from '@utils/storage';
import { Session } from '../../utils/storage';
import userAvatar from '@assets/images/login/user_icon.png';
import aiAvatar from '@assets/images/login/blue_logo.png';
import { createConversation, conversationMessageList } from '@api/ConversationApi';
import { createSession, getSessionMessages } from '../../api/SessionsApi';
import { ElMessage, ElLoading } from 'element-plus'
// 支持外部通过 prop 控制是否为引导页
@@ -109,8 +110,8 @@ watch(isGuidePage, (v) => {
emit('update:guide', v);
if (v) {
// 当切换到引导页时,重置/清理会话状态
conversationId.value = '';
resetConversation();
createConversationRequest();
}
});
@@ -128,11 +129,15 @@ const isSendingMessage = ref(false);
const agentId = ref("1953462165250859011");
/// 会话ID 历史数据接口中获取
const conversationId = ref(props.conversationId);
// 标记 conversationId 是否来自历史消息(由 props.conversationId 提供)
const conversationIdFromHistory = ref(!!props.conversationId);
// 监听 conversationId prop 变化,只有当有值时(选择历史消息)才请求消息列表
watch(() => props.conversationId, (newId) => {
if (newId) {
conversationId.value = newId;
console.log("外部 conversationId 变化,加载对应消息:", newId);
conversationIdFromHistory.value = true;
loadConversationMessages(newId);
}
});
@@ -274,10 +279,9 @@ onMounted(() => {
// token存在初始化数据
const initHandler = async () => {
console.log("initHandler");
console.log("initHandler:检查 token 并初始化数据");
const token = getAccessToken();
if (!token) return;
await createConversationRequest();
await initWebSocket();
};
@@ -295,11 +299,13 @@ const checkToken = async () => {
// 调用接口创建新会话
const createConversationRequest = async (): Promise<string | null> => {
const res = await createConversation();
if (res && res.conversationId) {
conversationId.value = res.conversationId;
const res = await createSession({});
if (res && res.session_id) {
conversationId.value = res.session_id;
// 新创建的 session 不是来源于历史
conversationIdFromHistory.value = false;
console.log("创建新会话ID:", conversationId.value);
return res.conversationId;
return res.session_id;
} else {
console.log("创建会话失败,接口返回异常");
return null;
@@ -309,12 +315,14 @@ const createConversationRequest = async (): Promise<string | null> => {
// 加载历史会话消息
const loadConversationMessages = async (convId: string) => {
try {
const res = await conversationMessageList({ conversationId: convId, pageSize: 50, pageNum: 1 });
const res = await getSessionMessages({ session_id: convId, limit: 50, offset: 0 });
// 将消息转换为 ChatMessage 格式
chatMsgList.value = res.records.map((msg: any) => ({
messageId: msg.messageId,
messageRole: msg.messageSenderRole === 'user' ? MessageRole.ME : MessageRole.AI,
messageContent: msg.messageContent,
chatMsgList.value = res.messages.map((msg: any) => ({
messageId: msg.message_id,
messageRole: msg.role === 'user' ? MessageRole.ME : MessageRole.AI,
messageContent: msg.content,
messageContentList: [msg.content],
timestamp: msg.created_at_ts,
finished: true, // 历史消息已完成
}));
console.log("加载历史消息:", chatMsgList.value);
@@ -347,7 +355,8 @@ const initWebSocket = async () => {
// 使用配置的WebSocket服务器地址
const token = getAccessToken();
const wsUrl = `wss://onefeel.brother7.cn/ingress/agent/ws/chat?access_token=${token}`;
// const wsUrl = `wss://onefeel.brother7.cn/ingress/agent/ws/chat?access_token=${token}`;
const wsUrl = `wss://onefeel.brother7.cn/ingress/nianxx/ws?token=${token}`;
// 初始化WebSocket管理器
webSocketManager = new WebSocketManager({
wsUrl: wsUrl,
@@ -407,6 +416,16 @@ const initWebSocket = async () => {
// 处理WebSocket消息
const handleWebSocketMessage = (data: any) => {
console.log("收到WebSocket消息:", data);
if (data.type === 'notification' && data.event === 'connected') {
console.log("WebSocket连接已建立服务器消息:", data);
return;
}
if (data.type === 'heartbeat') {
return;
}
// 验证关键字段(若服务端传回 conversationId/agentId则校验是否属于当前会话
if (data.conversationId && data.conversationId !== conversationId.value) {
console.warn("收到不属于当前会话的消息,忽略", data.conversationId);
@@ -427,7 +446,7 @@ const handleWebSocketMessage = (data: any) => {
}
// 优先使用 messageId 进行匹配
const msgId = data.messageId || data.id || data.msgId;
const msgId = data.messageId || data.reply_message_id || data.id || data.msgId;
let aiMsgIndex = -1;
if (msgId && pendingMap.has(msgId)) {
aiMsgIndex = pendingMap.get(msgId);
@@ -453,16 +472,24 @@ const handleWebSocketMessage = (data: any) => {
if (chatMsgList.value[aiMsgIndex].isLoading) {
// 首次收到内容:替换“加载中”文案并取消 loading 状态(恢复原始渲染逻辑)
chatMsgList.value[aiMsgIndex].messageContent = data.content;
chatMsgList.value[aiMsgIndex].messageContentList = [data.content];
chatMsgList.value[aiMsgIndex].isLoading = false;
} else {
// 后续流式内容追加
chatMsgList.value[aiMsgIndex].messageContent += data.content;
chatMsgList.value[aiMsgIndex].messageContentList.push(data.content);
}
nextTick(() => scrollToBottom());
}
/// 对于通知类消息,如果没有明确的完成状态,默认视为已完成,触发后续处理逻辑(例如心跳、连接建立等事件)
if (data.type === 'notification') {
data.finish = data.finish || true; // 确保 finish 字段存在
}
// 处理完成状态
if (data.finish) {
chatMsgList.value[aiMsgIndex].timestamp = Date.now();
chatMsgList.value[aiMsgIndex].finished = data.finish;
const msg = chatMsgList.value[aiMsgIndex].messageContent;
if (!msg || chatMsgList.value[aiMsgIndex].isLoading) {
@@ -516,6 +543,16 @@ const sendMessage = async (message: string, isInstruct: boolean = false) => {
await checkToken();
// 如果没有 conversationId且非历史来源在发送时按需创建会话
if (!conversationId.value) {
const sid = await createConversationRequest();
if (!sid) {
ElMessage({ message: '创建会话失败,请稍后重试', type: 'error' });
console.error('createConversationRequest failed before send');
return;
}
}
// 检查WebSocket连接状态如果未连接尝试重新连接
if (!isWsConnected()) {
console.log("WebSocket未连接尝试重新连接...");
@@ -553,6 +590,8 @@ const sendMessage = async (message: string, isInstruct: boolean = false) => {
messageId: IdUtils.generateMessageId(),
messageRole: MessageRole.ME,
messageContent: message,
messageContentList: [],
timestamp: Date.now(),
};
chatMsgList.value.push(newMsg);
inputMessage.value = "";
@@ -686,6 +725,8 @@ const sendChat = async (message: string, isInstruct = false) => {
messageId: currentSessionMessageId,
messageRole: MessageRole.AI,
messageContent: "加载中",
messageContentList: [],
timestamp: Date.now(),
isLoading: true,
};
@@ -823,6 +864,10 @@ const resetConversation = () => {
pendingTimeouts.clear();
pendingMap.clear();
// 如果 conversationId 不是来自历史,重置 conversationId
if (!conversationIdFromHistory.value) {
conversationId.value = '';
}
// 清理消息与状态
chatMsgList.value = [];
inputMessage.value = '';

View File

@@ -19,20 +19,62 @@
]">
<span class="w-2 h-2 rounded-full bg-[#BEDBFF] flex-none"></span>
<div class="flex-1 min-w-0">
<div class="truncate text-sm">{{ item.conversationId }}</div>
<div class="truncate text-sm">{{ item.conversationTitle }}</div>
</div>
<button v-if="item.conversationId === selectedConversationId"
class="bg-transparent border-0 text-gray-500 text-lg px-1 py-0"></button>
<el-dropdown v-if="item.conversationId === selectedConversationId" placement="bottom-end">
<el-icon class="el-icon--right">
...
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="renameHistoryMessage(item.conversationId)">重命名</el-dropdown-item>
<el-dropdown-item @click="deleteHistoryMessage(item.conversationId)">删除</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</li>
</ul>
</div>
</aside>
<!-- 重命名对话框 -->
<el-dialog v-model="renameDialogFormVisible" title="重命名对话" width="500">
<el-form :model="newMessageName">
<el-form-item label="对话名称" :label-width="formLabelWidth">
<el-input v-model="newMessageName" autocomplete="off" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="renameDialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="submitNameChange">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 删除确认对话框 -->
<el-dialog v-model="deleteDialogVisible" title="温馨提示" width="500">
<span>您确定删除该会话吗删除后将无法恢复</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="deleteDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitDelete">确定</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted, defineEmits } from 'vue'
import { RiAddLine, RiArrowRightSLine, RiArrowDownSLine } from '@remixicon/vue'
import { getConversationList } from '../../api/ConversationApi';
import { getSessionList, deleteSession, updateSession } from '../../api/SessionsApi';
const deleteDialogVisible = ref(false)
const renameDialogFormVisible = ref(false)
const newMessageName = ref('')
const formLabelWidth = '100px'
interface HistoryMessage {
conversationId: string;
@@ -51,6 +93,10 @@ const emit = defineEmits(['new-chat', 'select-chat'])
/// 添加新对话
const addNewChat = () => {
console.log('add new chat')
updateNewChat()
}
const updateNewChat = () => {
// 触发新对话事件
emit('new-chat')
// 清空选择的历史消息ID
@@ -65,6 +111,43 @@ const selectedHistoryMessage = (conversationId: string) => {
emit('select-chat', conversationId)
}
/// 重命名历史消息
const renameHistoryMessage = (conversationId: string) => {
console.log('rename message', conversationId)
renameDialogFormVisible.value = true
}
/// 删除历史消息
const deleteHistoryMessage = (conversationId: string) => {
console.log('delete message', conversationId)
deleteDialogVisible.value = true
}
/// 提交重命名
const submitNameChange = async () => {
console.log('submit name change', newMessageName.value)
renameDialogFormVisible.value = false
const res = await updateSession({
session_id: selectedConversationId.value,
title: newMessageName.value
})
if (res && res.success) {
updateNewChat()
}
}
/// 提交删除
const submitDelete = async () => {
console.log('submit delete')
deleteDialogVisible.value = false
const res = await deleteSession({
session_id: selectedConversationId.value
})
if (res && res.success) {
updateNewChat()
}
}
/// 页面加载时获取历史会话列表
onMounted(() => {
getHistoryConversationList()
@@ -72,12 +155,13 @@ onMounted(() => {
/// 获取历史会话列表
const getHistoryConversationList = async () => {
const list = await getConversationList({ pageSize: 20, pageNum: 1 })
if (!list || !list.records) return;
groups.value.push(...list.records.map((item: any) => ({
conversationId: item.conversationId,
conversationTitle: item.conversationTitle
})))
const list = await getSessionList({ limit: 50, offset: 0 })
if (!list || !list.sessions) return;
// 使用整体赋值替换 push避免重复累加
groups.value = list.sessions.map((item: any) => ({
conversationId: item.session_id,
conversationTitle: item.title
}))
}
</script>

View File

@@ -1,17 +1,65 @@
<template>
<div class="flex items-start gap-2 pt-0.5 mb-2" :class="showReverse ? 'flex-row-reverse' : 'flex-row'">
<span class="text-xs text-[#4E5969]"> ZHINIAN</span>
<span class="text-xs text-[#86909C]"> 20:30</span>
<div class="flex items-start gap-2 pt-0.5 mb-2" :class="props.showReverse ? 'flex-row-reverse' : 'flex-row'">
<span class="text-xs text-[#4E5969]">{{ props.msg?.messageRole === MessageRole.AI ? 'NIANXX' : '我' }}</span>
<span class="text-xs text-[#86909C]">{{ formattedTime }}</span>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { ChatMessage, MessageRole } from '../model/ChatModel'
interface Props {
showReverse: boolean
msg?: ChatMessage
showReverse?: boolean
}
withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
showReverse: false
})
const formattedTime = computed(() => {
const tsRaw = props.msg?.timestamp
if (tsRaw == null) return ''
let ts = Number(tsRaw)
if (isNaN(ts)) return ''
const pad = (n: number) => String(n).padStart(2, '0')
// Heuristic:
// - If ts < 1e9, treat as a duration in seconds and convert to dd-hh-mm (legacy)
// - If ts looks like an epoch (seconds or ms) format to YYYY年MM月DD日 HH:mm:ss
if (ts < 1e9) {
const totalSeconds = Math.floor(ts)
const days = Math.floor(totalSeconds / 86400)
const hours = Math.floor((totalSeconds % 86400) / 3600)
const minutes = Math.floor((totalSeconds % 3600) / 60)
return `${String(days).padStart(2, '0')}-${pad(hours)}-${pad(minutes)}`
}
// epoch handling: convert seconds -> ms when appropriate
if (ts < 1e12) ts = ts * 1000
const d = new Date(ts)
if (isNaN(d.getTime())) return ''
const Y = d.getFullYear()
const M = pad(d.getMonth() + 1)
const D = pad(d.getDate())
const h = pad(d.getHours())
const m = pad(d.getMinutes())
const s = pad(d.getSeconds())
// If the timestamp is the same calendar day as today, show only time HH:mm:ss
const now = new Date()
const sameDay = now.getFullYear() === d.getFullYear()
&& now.getMonth() === d.getMonth()
&& now.getDate() === d.getDate()
if (sameDay) {
return `${h}:${m}:${s}`
}
// otherwise show YYYY-MM-DD HH:mm:ss
return `${Y}-${M}-${D} ${h}:${m}:${s}`
})
</script>

View File

@@ -1,10 +1,17 @@
<template>
<div class="max-w-[75%] flex flex-col">
<slot name="header"></slot>
<div class="text-sm text-gray-700 flex flex-row">
<div v-if="!msg.messageContentList || msg.messageContentList.length === 0"
class="flex flex-row text-sm text-gray-700">
<div v-html="compiledMarkdown"></div>
<ChatLoading v-if="msg.isLoading" />
</div>
<div v-else class="flex flex-col p-2 mb-2 text-sm text-gray-700 bg-[#f7f9fc] rounded-md"
v-for="(_, index) in msg.messageContentList" :key="index">
<div v-html="compiledAt(index)"></div>
</div>
<slot name="footer"></slot>
</div>
</template>
@@ -23,6 +30,9 @@ interface Props {
const { msg } = defineProps<Props>()
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
highlight: function (str: string, lang: string) {
if (lang && hljs.getLanguage(lang)) {
try {
@@ -33,6 +43,18 @@ const md = new MarkdownIt({
return hljs.highlightAuto(str).value;
}
});
const compiledMarkdown = computed(() => md.render(msg.messageContent))
const compiledList = computed(() => {
return (msg.messageContentList || []).map((m: string) => md.render(m || ''))
})
const compiledAt = (index: number): string => {
const list: string[] = (compiledList as any).value || []
if (list[index]) return list[index]
const raw = msg?.messageContentList?.[index] || ''
return md.render(raw || '')
}
</script>

View File

@@ -1,43 +1,35 @@
<template>
<layout>
<div class="flex h-full w-full flex-col md:flex-row">
<ChatHistory class="flex-none w-50" @new-chat="guide = true" @select-chat="handleSelectChat" />
<ChatHistory class="flex-none w-50" @new-chat="handleNewChat" @select-chat="handleSelectChat" />
<div class="flex-1 mr-2 overflow-hidden bg-white rounded-xl">
<ChatBox v-model:guide="guide" :conversationId="selectedConversationId" />
</div>
<TaskList />
</div>
<TaskOperationDialog ref="taskOperationDialogRef" />
</layout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TaskList from '@renderer/components/TaskList/index.vue'
import TaskOperationDialog from '@renderer/views/home/components/TaskOperationDialog.vue'
import ChatHistory from './ChatHistory.vue'
import ChatBox from './ChatBox.vue'
import emitter from '@utils/emitter'
// 是否显示引导页
import { ref } from 'vue'
/// 是否显示引导页
const guide = ref(true)
// 选择的历史会话ID
/// 选择的历史会话ID
const selectedConversationId = ref('')
// 任务操作弹窗引用
const taskOperationDialogRef = ref()
/// 处理新对话事件切换到引导页并清空选中的历史会话ID
const handleNewChat = () => {
guide.value = true;
selectedConversationId.value = '';
};
// 选择历史会话
/// 选择历史会话
const handleSelectChat = (conversationId: string) => {
guide.value = false;
selectedConversationId.value = conversationId;
};
// 监听任务操作弹窗关闭事件
emitter.on('OPERATION_CHANNEL', (item) => {
taskOperationDialogRef.value?.open(item);
});
</script>

View File

@@ -16,6 +16,8 @@ export class ChatMessage {
messageRole: MessageRole;
// 消息内容
messageContent: string;
// 消息内容列表(用于流式更新)
messageContentList: string[];
// 是否加载中
isLoading?: boolean;
// 是否完成
@@ -24,22 +26,28 @@ export class ChatMessage {
toolCall?: any;
// 问题信息
question?: string;
// 时间戳
timestamp?: number;
constructor(
messageId: string,
messageRole: MessageRole,
messageContent: string,
messageContentList: string[] = [],
isLoading: boolean = false,
finished: boolean = false,
toolCall?: any,
question?: any
question?: any,
timestamp?: number
) {
this.messageId = messageId;
this.messageRole = messageRole;
this.messageContent = messageContent;
this.messageContentList = messageContentList;
this.isLoading = isLoading;
this.finished = finished;
this.toolCall = toolCall;
this.question = question;
this.timestamp = timestamp || Date.now();
}
}