kagg886 3 mesiacov pred
rodič
commit
e6ce491c1a
3 zmenil súbory, kde vykonal 222 pridanie a 227 odobranie
  1. 194 206
      src/api/assist/index.ts
  2. 7 3
      src/api/assist/type.ts
  3. 21 18
      src/views/assistant/index.vue

+ 194 - 206
src/api/assist/index.ts

@@ -1,268 +1,256 @@
-import { ChatRequest, ChatResponse } from '/@/api/assist/type'
+import { ChatRequest, ChatResponse, ChatResponseType } from '/@/api/assist/type'
 import getOrigin from '/@/utils/origin'
-import { getToken } from "/@/utils/auth";
+import { getToken } from '/@/utils/auth'
 
 export default {
 	// 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
-
-		//FIXME 需要抹掉
-		chatRequest.modelMcpId = 1 as unknown as number[]
-		chatRequest.modelClassId = 1
-		chatRequest["UserId"] = 10
+	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.modelClassId = 1
+		// 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',
-					'Cache-Control': 'no-cache',
-				};
-
-				// 添加认证token
-				const token = getToken();
-				if (token) {
-					headers['Authorization'] = `Bearer ${token}`;
-				}
+			const headers: Record<string, string> = {
+				'Content-Type': 'application/json',
+				Accept: 'text/event-stream',
+				'Cache-Control': 'no-cache',
+			}
+
+			// 添加认证token
+			const token = getToken()
+			if (token) {
+				headers['Authorization'] = `Bearer ${token}`
+			}
 
-				const response = await fetch(url, {
-					method: 'POST',
-					headers,
-					body: JSON.stringify(chatRequest),
-					signal: controller.signal,
-				});
+			const response = await fetch(url, {
+				method: 'POST',
+				headers,
+				body: JSON.stringify(chatRequest),
+				signal: controller.signal,
+			}).catch((e: Error) => e)
 
-				if (!response.ok) {
-					throw new Error(`HTTP error! status: ${response.status}`);
-				}
+			if (response instanceof Error) {
+				throw response
+			}
 
-				const reader = response.body?.getReader();
-				const decoder = new TextDecoder();
+			if (!response.ok) {
+				throw new Error(`HTTP error! status: ${response.status}`)
+			}
 
-				if (!reader) {
-					throw new Error('Response body is not readable');
-				}
+			if (response.headers.get('Content-Type') === 'application/json') {
+				const { message }: { message: string } = await response.json()
 
-				// 读取SSE流
-				let currentEvent = '';
-				let buffer = '';
+				throw new Error(message)
+			}
 
-				while (true) {
-					const { done, value } = await reader.read();
+			const reader = response.body?.getReader()
+			const decoder = new TextDecoder()
 
-					if (done) {
-						break;
-					}
+			if (!reader) {
+				throw new Error('Response body is not readable')
+			}
 
-					buffer += decoder.decode(value, { stream: true });
+			// 读取SSE流
+			let currentEvent: ChatResponseType | '' = ''
+			let buffer = ''
 
-					// 按双换行符分割事件块
-					const eventBlocks = buffer.split('\n\n');
+			while (true) {
+				const { done, value } = await reader.read()
 
-					// 保留最后一个可能不完整的事件块
-					buffer = eventBlocks.pop() || '';
+				if (done) {
+					break
+				}
 
-					for (const eventBlock of eventBlocks) {
-						if (!eventBlock.trim()) continue;
+				buffer += decoder.decode(value, { stream: true })
 
-						const lines = eventBlock.split('\n');
-						let eventType = '';
-						let dataLines: string[] = [];
+				// 按双换行符分割事件块
+				const eventBlocks = buffer.split('\n\n')
 
-						// 解析事件块中的每一行
-						for (const line of lines) {
-							const trimmedLine = line.trim();
+				// 保留最后一个可能不完整的事件块
+				buffer = eventBlocks.pop() || ''
 
-							if (trimmedLine.startsWith('event:')) {
-								eventType = trimmedLine.substring(6).trim();
-							} else if (trimmedLine.startsWith('data:')) {
-								// 提取data后的内容,保留原始格式(包括可能的空格)
-								const dataContent = line.substring(line.indexOf('data:') + 5);
-								dataLines.push(dataContent);
-							}
-						}
+				for (const eventBlock of eventBlocks) {
+					if (!eventBlock.trim()) continue
+
+					const lines = eventBlock.split('\n')
+					let eventType = ''
+					let dataLines: string[] = []
+
+					// 解析事件块中的每一行
+					for (const line of lines) {
+						const trimmedLine = line.trim()
 
-						// 如果有事件类型,更新当前事件
-						if (eventType) {
-							currentEvent = eventType;
+						if (trimmedLine.startsWith('event:')) {
+							eventType = trimmedLine.substring(6).trim()
+						} else if (trimmedLine.startsWith('data:')) {
+							// 提取data后的内容,保留原始格式(包括可能的空格)
+							const dataContent = line.substring(line.indexOf('data:') + 5)
+							dataLines.push(dataContent)
 						}
+					}
 
-						// 如果有数据行,处理数据
-						if (dataLines.length > 0) {
-							// 将多行data合并,用换行符连接
-							const data = dataLines.join('\n');
-
-							// 根据事件类型处理数据
-							switch (currentEvent) {
-								case 'message': {
-									const messageResponse: ChatResponse = {
-										type: 'message',
-										message: data
-									};
-									onReceive(messageResponse);
-									break;
+					// 如果有事件类型,更新当前事件
+					if (eventType) {
+						currentEvent = eventType as ChatResponseType
+					}
+
+					// 如果有数据行,处理数据
+					if (dataLines.length > 0) {
+						// 将多行data合并,用换行符连接
+						const data = dataLines.join('\n')
+
+						// 根据事件类型处理数据
+						switch (currentEvent) {
+							case 'message': {
+								const messageResponse: ChatResponse = {
+									type: 'message',
+									message: data,
 								}
+								onReceive(messageResponse)
+								break
+							}
 
-								case 'toolcall': {
-									try {
-										const tools = JSON.parse(data);
-										const toolcallResponse: ChatResponse = {
-											type: 'toolcall',
-											request: {
-												name: tools["name"],
-												data: tools["arguments"]
-											}
-										};
-										onReceive(toolcallResponse);
-									} catch (error) {
-										console.error('解析toolcall数据失败:', error, '原始数据:', data);
-									}
-									break;
+							case 'toolcall': {
+								const tools = JSON.parse(data)
+								const toolcallResponse: ChatResponse = {
+									type: 'toolcall',
+									request: {
+										name: tools['name'],
+										data: tools['arguments'],
+									},
 								}
+								onReceive(toolcallResponse)
+								break
+							}
 
-								case 'toolres': {
-									try {
-										const tools = JSON.parse(data);
-										const toolresResponse: ChatResponse = {
-											type: 'toolres',
-											response: {
-												name: tools["name"],
-												data: tools["response"]
-											},
-										};
-										onReceive(toolresResponse);
-									} catch (error) {
-										console.error('解析toolres数据失败:', error, '原始数据:', data);
-									}
-									break;
+							case 'toolres': {
+								const tools = JSON.parse(data)
+								const toolresResponse: ChatResponse = {
+									type: 'toolres',
+									response: {
+										name: tools['name'],
+										data: tools['response'],
+									},
 								}
+								onReceive(toolresResponse)
+								break
+							}
 
-								default: {
-									// 如果没有明确的事件类型,默认作为消息处理
-									const defaultResponse: ChatResponse = {
-										type: 'message',
-										message: data
-									};
-									onReceive(defaultResponse);
-									break;
+							case 'error': {
+								throw new Error(data)
+							}
+
+							default: {
+								// 如果没有明确的事件类型,默认作为消息处理
+								const defaultResponse: ChatResponse = {
+									type: 'message',
+									message: data,
 								}
+								onReceive(defaultResponse)
+								break
 							}
 						}
 					}
+				}
 
-					// 处理buffer中剩余的可能完整的单行事件
-					const remainingLines = buffer.split('\n');
-					const completeLines = remainingLines.slice(0, -1);
-					buffer = remainingLines[remainingLines.length - 1] || '';
+				// 处理buffer中剩余的可能完整的单行事件
+				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();
+				for (const line of completeLines) {
+					const trimmedLine = line.trim()
 
-						if (trimmedLine === '') {
-							continue;
-						}
+					if (trimmedLine === '') {
+						continue
+					}
 
-						if (trimmedLine.startsWith('event:')) {
-							currentEvent = trimmedLine.substring(6).trim();
-							continue;
-						}
+					if (trimmedLine.startsWith('event:')) {
+						currentEvent = trimmedLine.substring(6).trim() as ChatResponseType
+						continue
+					}
 
-						if (trimmedLine.startsWith('data:')) {
-							const data = line.substring(line.indexOf('data:') + 5);
-
-							// 根据事件类型处理数据
-							switch (currentEvent) {
-								case 'message': {
-									const messageResponse: ChatResponse = {
-										type: 'message',
-										message: data
-									};
-									onReceive(messageResponse);
-									break;
+					if (trimmedLine.startsWith('data:')) {
+						const data = line.substring(line.indexOf('data:') + 5)
+
+						// 根据事件类型处理数据
+						switch (currentEvent) {
+							case 'message': {
+								const messageResponse: ChatResponse = {
+									type: 'message',
+									message: data,
 								}
+								onReceive(messageResponse)
+								break
+							}
 
-								case 'toolcall': {
-									try {
-										const tools = JSON.parse(data);
-										const toolcallResponse: ChatResponse = {
-											type: 'toolcall',
-											request: {
-												name: tools["name"],
-												data: tools["arguments"]
-											}
-										};
-										onReceive(toolcallResponse);
-									} catch (error) {
-										console.error('解析toolcall数据失败:', error, '原始数据:', data);
-									}
-									break;
+							case 'toolcall': {
+								const tools = JSON.parse(data)
+								const toolcallResponse: ChatResponse = {
+									type: 'toolcall',
+									request: {
+										name: tools['name'],
+										data: tools['arguments'],
+									},
 								}
+								onReceive(toolcallResponse)
+								break
+							}
 
-								case 'toolres': {
-									try {
-										const tools = JSON.parse(data);
-										const toolresResponse: ChatResponse = {
-											type: 'toolres',
-											response: {
-												name: tools["name"],
-												data: tools["response"]
-											},
-										};
-										onReceive(toolresResponse);
-									} catch (error) {
-										console.error('解析toolres数据失败:', error, '原始数据:', data);
-									}
-									break;
+							case 'toolres': {
+								const tools = JSON.parse(data)
+								const toolresResponse: ChatResponse = {
+									type: 'toolres',
+									response: {
+										name: tools['name'],
+										data: tools['response'],
+									},
 								}
+								onReceive(toolresResponse)
+								break
+							}
 
-								default: {
-									// 如果没有明确的事件类型,默认作为消息处理
-									const defaultResponse: ChatResponse = {
-										type: 'message',
-										message: data
-									};
-									onReceive(defaultResponse);
-									break;
+							default: {
+								// 如果没有明确的事件类型,默认作为消息处理
+								const defaultResponse: ChatResponse = {
+									type: 'message',
+									message: data,
 								}
+								onReceive(defaultResponse)
+								break
 							}
 						}
 					}
 				}
-
-				onComplete?.(undefined)
-			} catch (error: any) {
-				if (error.name !== 'AbortError') {
-					console.error('SSE连接错误:', error);
-				}
-				onComplete?.(error)
 			}
-		};
+		}
 
 		// 启动SSE连接
-		startSSE();
+		startSSE()
+			.then(() => onComplete?.(undefined))
+			.catch((e) => onComplete?.(e))
 
 		// 返回关闭连接的函数
 		return () => {
-			controller.abort();
-		};
+			controller.abort()
+		}
 	},
 }

+ 7 - 3
src/api/assist/type.ts

@@ -28,7 +28,7 @@ export type ChatRequest = {
 	message: Message[]
 }
 
-export type ChatResponseType = 'message' | 'toolcall' | 'toolres'
+export type ChatResponseType = 'message' | 'toolcall' | 'toolres' | 'error'
 
 export type ChatResponseBase<T = ChatResponseType> = {
 	type: T
@@ -48,8 +48,12 @@ export type ToolCallRequest = ChatResponseBase<'toolcall'> & {
 export type ToolCallResponse = ChatResponseBase<'toolres'> & {
 	response: {
 		name: string,
-		data: any
+		data: string
 	}
 }
 
-export type ChatResponse = Text | ToolCallRequest | ToolCallResponse
+export type ErrorResponse = ChatResponseBase<'error'> & {
+	error: string
+}
+
+export type ChatResponse = Text | ToolCallRequest | ToolCallResponse | ErrorResponse

+ 21 - 18
src/views/assistant/index.vue

@@ -102,10 +102,12 @@ const sendMessage = () => {
 		content: '',
 		timestamp: Date.now(),
 	})
-	const fe = watch(()=> rtn.content,()=>scrollToBottom())
 
+	inputMessage.value = ''
 
-	let toolcall: { name: string; param?: string; value?: string } | undefined = undefined
+	scrollToBottom()
+	//
+	// let toolcall: { name: string; param?: string; value?: string } | undefined = undefined
 	chatInstance.value = assist.chat({
 		chatRequest: {
 			message: messages.value,
@@ -116,32 +118,33 @@ const sendMessage = () => {
 					rtn.content += resp.message
 					break
 				case 'toolcall':
-					toolcall = {
-						name: resp.request.name,
-						param: resp.request.data,
-					}
+					rtn.content +=`
+> ### Tool Call
+>
+> **Name**: \`${resp.request.name}\`
+>
+> **Param**: \`${resp.request.data}\`
+`
 					break
 				case 'toolres':
-					if (toolcall !== undefined) {
-						toolcall.value = resp.response.data
-					}
-
 					rtn.content += `
-> ### Tool Call
->
-> **Params**: \`${toolcall?.name}\`
+
 >
-> **Value**: \`${toolcall?.value}\`
+> **Value**: \`${resp.response.data}\`
 
 `
-					toolcall = undefined
 					break
 			}
 		},
-		onComplete: ()=> {
+		onComplete: (e)=> {
+			if (e !== undefined) {
+				rtn.content += `
+
+> **Error**: \`${e.message}\`
+
+`
+			}
 			chatInstance.value = undefined
-			scrollToBottom()
-			fe()
 		}
 	})
 	messages.value.push(rtn)