Browse Source

非选中会话情况下,点击发送自动创建新会话

kagg886 2 months ago
parent
commit
59f630ff27
2 changed files with 162 additions and 131 deletions
  1. 109 112
      src/api/assist/index.ts
  2. 53 19
      src/views/assistant/index.vue

+ 109 - 112
src/api/assist/index.ts

@@ -9,11 +9,11 @@ import {
 	LmConfigDeleteParams,
 	LmConfigGetParams,
 	SessionMessagesListParams,
-	SessionMessagesSaveReq
+	SessionMessagesSaveReq,
 } from '/@/api/assist/type'
 import { get, post, del, put } from '/@/utils/request'
 import getOrigin from '/@/utils/origin'
-import { getToken } from "/@/utils/auth";
+import { getToken } from '/@/utils/auth'
 
 export default {
 	model: {
@@ -32,60 +32,57 @@ export default {
 	},
 
 	session: {
-		list: (params: any) => get('/system/lmsessions/list',params),
+		list: (params: any) => get('/system/lmsessions/list', params),
 		// 添加大模型会话
-		add: (title: string): Promise<{id: number}> => post('/system/lmsessions/add', {title}),
+		add: (title: string): Promise<{ id: number }> => post('/system/lmsessions/add', { title }),
 		// 删除大模型会话
 		del: (ids: number[]) => del('/system/lmsessions/delete', { ids }),
 
+		edit: (id: number, title: string): Promise<void> => put('/system/lmsessions/edit', { sessionId: id, title }),
+
 		message: {
 			// 获取会话消息列表
-			list: (params: SessionMessagesListParams) =>
-				get('/system/lmsessions/messages/list', params),
+			list: (params: SessionMessagesListParams) => get('/system/lmsessions/messages/list', params),
 			// 保存会话消息
-			save: (data: SessionMessagesSaveReq) =>
-				post('/system/lmsessions/messages/save', data)
-		}
+			save: (data: SessionMessagesSaveReq) => post('/system/lmsessions/messages/save', data),
+		},
 	},
 
 	// SSE聊天方法
-	chat: (
-		data:
-		{
-			chatRequest: ChatRequest,
-			// eslint-disable-next-line no-unused-vars
-			onReceive: (resp: ChatResponse) => void,
-
-			// eslint-disable-next-line no-unused-vars
-			onComplete?: (error: Error|undefined)=> void
-		},
-	) => {
-		const {chatRequest, onReceive,onComplete} = data
+	chat: (data: {
+		chatRequest: ChatRequest
+		// eslint-disable-next-line no-unused-vars
+		onReceive: (resp: ChatResponse) => void
+
+		// eslint-disable-next-line no-unused-vars
+		onComplete?: (error: Error | undefined) => void
+	}) => {
+		const { chatRequest, onReceive, onComplete } = data
 
 		//FIXME 需要抹掉
 		chatRequest.modelMcpId = 1 as unknown as number[]
-		chatRequest["UserId"] = 10
+		chatRequest['UserId'] = 10
 
 		// 构建SSE URL
-		const baseURL = getOrigin();
-		const url = `${baseURL}/ai/chat`;
+		const baseURL = getOrigin()
+		const url = `${baseURL}/ai/chat`
 
 		// 使用fetch API实现SSE POST请求(EventSource不支持POST和自定义headers)
-		const controller = new AbortController();
+		const controller = new AbortController()
 
 		// 使用fetch API实现SSE POST请求
 		const startSSE = async () => {
 			try {
 				const headers: Record<string, string> = {
 					'Content-Type': 'application/json',
-					'Accept': 'text/event-stream',
+					Accept: 'text/event-stream',
 					'Cache-Control': 'no-cache',
-				};
+				}
 
 				// 添加认证token
-				const token = getToken();
+				const token = getToken()
 				if (token) {
-					headers['Authorization'] = `Bearer ${token}`;
+					headers['Authorization'] = `Bearer ${token}`
 				}
 
 				const response = await fetch(url, {
@@ -93,115 +90,115 @@ export default {
 					headers,
 					body: JSON.stringify(chatRequest),
 					signal: controller.signal,
-				}).catch((e:Error)=> e);
+				}).catch((e: Error) => e)
 
 				if (response instanceof Error) {
-					throw new Error('无法连接到服务器');
+					throw new Error('无法连接到服务器')
 				}
 
 				if (!response.ok) {
-					throw new Error(`HTTP error! status: ${response.status}`);
+					throw new Error(`HTTP error! status: ${response.status}`)
 				}
 
-				const reader = response.body?.getReader();
-				const decoder = new TextDecoder();
+				const reader = response.body?.getReader()
+				const decoder = new TextDecoder()
 
 				if (!reader) {
-					throw new Error('Response body is not readable');
+					throw new Error('Response body is not readable')
 				}
 
 				// 读取SSE流
-				let currentEvent = '';
-				let buffer = '';
+				let currentEvent = ''
+				let buffer = ''
 
 				while (true) {
-					const { done, value } = await reader.read();
+					const { done, value } = await reader.read()
 
 					if (done) {
-						break;
+						break
 					}
 
-					buffer += decoder.decode(value, { stream: true });
+					buffer += decoder.decode(value, { stream: true })
 
 					// 按双换行符分割事件块
-					const eventBlocks = buffer.split('\n\n');
+					const eventBlocks = buffer.split('\n\n')
 
 					// 保留最后一个可能不完整的事件块
-					buffer = eventBlocks.pop() || '';
+					buffer = eventBlocks.pop() || ''
 
 					for (const eventBlock of eventBlocks) {
-						if (!eventBlock.trim()) continue;
+						if (!eventBlock.trim()) continue
 
-						const lines = eventBlock.split('\n');
-						let eventType = '';
-						let dataLines: string[] = [];
+						const lines = eventBlock.split('\n')
+						let eventType = ''
+						let dataLines: string[] = []
 
 						// 解析事件块中的每一行
 						for (const line of lines) {
-							const trimmedLine = line.trim();
+							const trimmedLine = line.trim()
 
 							if (trimmedLine.startsWith('event:')) {
-								eventType = trimmedLine.substring(6).trim();
+								eventType = trimmedLine.substring(6).trim()
 							} else if (trimmedLine.startsWith('data:')) {
 								// 提取data后的内容,保留原始格式(包括可能的空格)
-								const dataContent = line.substring(line.indexOf('data:') + 5);
-								dataLines.push(dataContent);
+								const dataContent = line.substring(line.indexOf('data:') + 5)
+								dataLines.push(dataContent)
 							}
 						}
 
 						// 如果有事件类型,更新当前事件
 						if (eventType) {
-							currentEvent = eventType;
+							currentEvent = eventType
 						}
 
 						// 如果有数据行,处理数据
 						if (dataLines.length > 0) {
 							// 将多行data合并,用换行符连接
-							const data = dataLines.join('\n');
+							const data = dataLines.join('\n')
 
 							// 根据事件类型处理数据
 							switch (currentEvent) {
 								case 'message': {
 									const messageResponse: ChatResponse = {
 										type: 'message',
-										message: data
-									};
-									onReceive(messageResponse);
-									break;
+										message: data,
+									}
+									onReceive(messageResponse)
+									break
 								}
 
 								case 'toolcall': {
-									const tools = JSON.parse(data);
+									const tools = JSON.parse(data)
 									const toolcallResponse: ChatResponse = {
 										type: 'toolcall',
 										request: {
-											id: tools["tool_call_id"],
-											name: tools["name"],
-											data: tools["arguments"]
-										}
-									};
-									onReceive(toolcallResponse);
-									break;
+											id: tools['tool_call_id'],
+											name: tools['name'],
+											data: tools['arguments'],
+										},
+									}
+									onReceive(toolcallResponse)
+									break
 								}
 
 								case 'toolres': {
-									const tools = JSON.parse(data);
+									const tools = JSON.parse(data)
 									const toolresResponse: ChatResponse = {
 										type: 'toolres',
 										response: {
-											id: tools["tool_call_id"],
-											name: tools["name"],
-											data: tools["response"]
+											id: tools['tool_call_id'],
+											name: tools['name'],
+											data: tools['response'],
 										},
-									};
-									onReceive(toolresResponse);
-									break;
+									}
+									onReceive(toolresResponse)
+									break
 								}
 
 								case 'error': {
 									const errorResponse: ErrorResponse = {
 										type: 'error',
-										error: data
+										error: data,
 									}
 									onReceive(errorResponse)
 									break
@@ -215,88 +212,88 @@ export default {
 									// 如果没有明确的事件类型,默认作为消息处理
 									const defaultResponse: ChatResponse = {
 										type: 'message',
-										message: data
-									};
-									onReceive(defaultResponse);
-									break;
+										message: data,
+									}
+									onReceive(defaultResponse)
+									break
 								}
 							}
 						}
 					}
 
 					// 处理buffer中剩余的可能完整的单行事件
-					const remainingLines = buffer.split('\n');
-					const completeLines = remainingLines.slice(0, -1);
-					buffer = remainingLines[remainingLines.length - 1] || '';
+					const remainingLines = buffer.split('\n')
+					const completeLines = remainingLines.slice(0, -1)
+					buffer = remainingLines[remainingLines.length - 1] || ''
 
 					for (const line of completeLines) {
-						const trimmedLine = line.trim();
+						const trimmedLine = line.trim()
 
 						if (trimmedLine === '') {
-							continue;
+							continue
 						}
 
 						if (trimmedLine.startsWith('event:')) {
-							currentEvent = trimmedLine.substring(6).trim();
-							continue;
+							currentEvent = trimmedLine.substring(6).trim()
+							continue
 						}
 
 						if (trimmedLine.startsWith('data:')) {
-							const data = line.substring(line.indexOf('data:') + 5);
+							const data = line.substring(line.indexOf('data:') + 5)
 
 							// 根据事件类型处理数据
 							switch (currentEvent) {
 								case 'message': {
 									const messageResponse: ChatResponse = {
 										type: 'message',
-										message: data
-									};
-									onReceive(messageResponse);
-									break;
+										message: data,
+									}
+									onReceive(messageResponse)
+									break
 								}
 
 								case 'toolcall': {
 									try {
-										const tools = JSON.parse(data);
+										const tools = JSON.parse(data)
 										const toolcallResponse: ChatResponse = {
 											type: 'toolcall',
 											request: {
-												name: tools["name"],
-												data: tools["arguments"]
-											}
-										};
-										onReceive(toolcallResponse);
+												name: tools['name'],
+												data: tools['arguments'],
+											},
+										}
+										onReceive(toolcallResponse)
 									} catch (error) {
-										console.error('解析toolcall数据失败:', error, '原始数据:', data);
+										console.error('解析toolcall数据失败:', error, '原始数据:', data)
 									}
-									break;
+									break
 								}
 
 								case 'toolres': {
 									try {
-										const tools = JSON.parse(data);
+										const tools = JSON.parse(data)
 										const toolresResponse: ChatResponse = {
 											type: 'toolres',
 											response: {
-												name: tools["name"],
-												data: tools["response"]
+												name: tools['name'],
+												data: tools['response'],
 											},
-										};
-										onReceive(toolresResponse);
+										}
+										onReceive(toolresResponse)
 									} catch (error) {
-										console.error('解析toolres数据失败:', error, '原始数据:', data);
+										console.error('解析toolres数据失败:', error, '原始数据:', data)
 									}
-									break;
+									break
 								}
 
 								default: {
 									// 如果没有明确的事件类型,默认作为消息处理
 									const defaultResponse: ChatResponse = {
 										type: 'message',
-										message: data
-									};
-									onReceive(defaultResponse);
-									break;
+										message: data,
+									}
+									onReceive(defaultResponse)
+									break
 								}
 							}
 						}
@@ -306,18 +303,18 @@ export default {
 				onComplete?.(undefined)
 			} catch (error: any) {
 				if (error.name !== 'AbortError') {
-					console.error('SSE连接错误:', error);
+					console.error('SSE连接错误:', error)
 				}
 				onComplete?.(error)
 			}
-		};
+		}
 
 		// 启动SSE连接
-		startSSE();
+		startSSE()
 
 		// 返回关闭连接的函数
 		return () => {
-			controller.abort();
-		};
+			controller.abort()
+		}
 	},
 }

+ 53 - 19
src/views/assistant/index.vue

@@ -101,16 +101,28 @@ onUnmounted(() => chatInstance.value?.())
 // 是否正在对话
 const isConversationActive = computed(() => chatInstance.value !== undefined)
 
-const clearMessage = () => {
+const { loading: loadingClearMessage, doLoading: clearMessage } = useLoading(async () => {
 	stopConversation()
+	if (messages.value.length !== 0 && activeConversationId.value !== undefined) {
+		const res = await assist.session.message
+			.save({
+				sessionId: activeConversationId.value,
+				messages: [],
+			})
+			.then(() => true)
+			.catch(() => false)
+		if (!res) {
+			return
+		}
+	}
 	messages.value = []
-}
-
+})
 // 发送消息
 const sendMessage = async () => {
 	if (!inputMessage.value.trim()) return
 
-	if (activeConversationId.value === undefined) { //未选中任何会话则创建新会话
+	if (activeConversationId.value === undefined) {
+		//未选中任何会话则创建新会话
 		await createConversationAndSetItActive()
 	}
 	await nextTick()
@@ -306,12 +318,17 @@ const scrollToBottom = () => {
 // 所有会话
 const conversations = ref<LmSession[]>([])
 const { loading: loadConversations, doLoading: doLoadConversations } = useLoading(async () => {
-	const data: { list: LmSession[]; total: number } = await assist.session.list({ pageNum: 1, pageSize: 30 }).catch(() => {
-		return {
-			list: [],
-			total: 0,
-		}
-	})
+	const data: { list: LmSession[]; total: number } = await assist.session
+		.list({
+			pageNum: 1,
+			pageSize: 30,
+		})
+		.catch(() => {
+			return {
+				list: [],
+				total: 0,
+			}
+		})
 
 	conversations.value = data.list
 })
@@ -329,7 +346,10 @@ const activeConversationId = ref<number | undefined>(undefined)
 // })
 
 const { loading: loadingMessage, doLoading: doLoadingMessage } = useLoading(async (id: number) => {
-	const data: { messages: Message[]; total: number } = await assist.session.message.list({ sessionId: id }).catch(() => {
+	const data: {
+		messages: Message[]
+		total: number
+	} = await assist.session.message.list({ sessionId: id }).catch(() => {
 		return {
 			list: [],
 			total: 0,
@@ -400,15 +420,22 @@ const editSummary = (id: number) => {
 	}
 }
 // 确认编辑
-const confirmEdit = (id: number) => {
+const { doLoading: confirmEdit,loading: loadingConfirmEdit } = useLoading(async (id: number) => {
 	const conversation = conversations.value.find((conv) => conv.session_id === id)
 	if (conversation && editingTitle.value.trim()) {
+		const edit = await assist.session
+			.edit(id, editingTitle.value.trim())
+			.then(() => true)
+			.catch(() => false)
+		if (!edit) {
+			return
+		}
 		conversation.title = editingTitle.value.trim()
 		// 清除编辑状态
 		editingConversationId.value = undefined
 		editingTitle.value = ''
 	}
-}
+})
 
 // 取消编辑
 const cancelEdit = () => {
@@ -498,6 +525,7 @@ const redirectToModelManager = () => router.push('manage/model')
 								type="success"
 								size="small"
 								@click.stop="confirmEdit(conv.session_id)"
+								:loading="loadingConfirmEdit"
 								class="action-btn confirm-btn"
 								title="确认修改"
 								plain
@@ -524,9 +552,15 @@ const redirectToModelManager = () => router.push('manage/model')
 					</div>
 				</div>
 			</el-scrollbar>
-			<el-button type="primary" size="large" class="create-conversation-btn" @click="createConversationAndSetItActive" :loading="creatingConversation"
-				>创建对话</el-button
-			>
+			<el-button
+				type="primary"
+				size="large"
+				class="create-conversation-btn"
+				@click="createConversationAndSetItActive"
+				:loading="creatingConversation"
+				:disabled="isConversationActive"
+				>创建对话
+			</el-button>
 		</el-aside>
 
 		<!-- 右侧聊天区域 -->
@@ -635,8 +669,8 @@ const redirectToModelManager = () => router.push('manage/model')
 					</div>
 
 					<!-- 模型选择 -->
-					<div class="model-selector" v-loading="loadingModels">
-						<el-select v-model="selectedModel" placeholder="选择模型" size="small" style="width: 200px">
+					<div class="model-selector">
+						<el-select v-loading="loadingModels" v-model="selectedModel" placeholder="选择模型" size="small" style="width: 200px">
 							<el-option v-for="item in modelOptions" :key="item.id" :value="item.id" :label="item.modelName" />
 						</el-select>
 					</div>
@@ -665,7 +699,7 @@ const redirectToModelManager = () => router.push('manage/model')
 
 					<!-- 按钮组 -->
 					<div class="button-group">
-						<el-button type="warning" size="small" @click="clearMessage" style="margin-left: 12px"> 清空</el-button>
+						<el-button type="warning" size="small" @click="clearMessage" style="margin-left: 12px" :loading="loadingClearMessage"> 清空 </el-button>
 						<el-button
 							v-if="!isConversationActive"
 							type="primary"