kagg886 3 maanden geleden
bovenliggende
commit
941ab0305b

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

@@ -1,256 +1,268 @@
-import { ChatRequest, ChatResponse, ChatResponseType } from '/@/api/assist/type'
+import { ChatRequest, ChatResponse } 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 () => {
-			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,
-			}).catch((e: Error) => e)
-
-			if (response instanceof Error) {
-				throw response
-			}
-
-			if (!response.ok) {
-				throw new Error(`HTTP error! status: ${response.status}`)
-			}
+			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}`;
+				}
 
-			if (response.headers.get('Content-Type') === 'application/json') {
-				const { message }: { message: string } = await response.json()
+				const response = await fetch(url, {
+					method: 'POST',
+					headers,
+					body: JSON.stringify(chatRequest),
+					signal: controller.signal,
+				});
 
-				throw new Error(message)
-			}
+				if (!response.ok) {
+					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')
-			}
+				if (!reader) {
+					throw new Error('Response body is not readable');
+				}
 
-			// 读取SSE流
-			let currentEvent: ChatResponseType | '' = ''
-			let buffer = ''
+				// 读取SSE流
+				let currentEvent = '';
+				let buffer = '';
 
-			while (true) {
-				const { done, value } = await reader.read()
+				while (true) {
+					const { done, value } = await reader.read();
 
-				if (done) {
-					break
-				}
+					if (done) {
+						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
+					for (const eventBlock of eventBlocks) {
+						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()
+						// 解析事件块中的每一行
+						for (const line of lines) {
+							const trimmedLine = line.trim();
 
-						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 (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 (eventType) {
-						currentEvent = eventType as ChatResponseType
-					}
+						// 如果有事件类型,更新当前事件
+						if (eventType) {
+							currentEvent = eventType;
+						}
 
-					// 如果有数据行,处理数据
-					if (dataLines.length > 0) {
-						// 将多行data合并,用换行符连接
-						const data = dataLines.join('\n')
-
-						// 根据事件类型处理数据
-						switch (currentEvent) {
-							case 'message': {
-								const messageResponse: ChatResponse = {
-									type: 'message',
-									message: data,
+						// 如果有数据行,处理数据
+						if (dataLines.length > 0) {
+							// 将多行data合并,用换行符连接
+							const data = dataLines.join('\n');
+
+							// 根据事件类型处理数据
+							switch (currentEvent) {
+								case 'message': {
+									const messageResponse: ChatResponse = {
+										type: 'message',
+										message: data
+									};
+									onReceive(messageResponse);
+									break;
 								}
-								onReceive(messageResponse)
-								break
-							}
 
-							case 'toolcall': {
-								const tools = JSON.parse(data)
-								const toolcallResponse: ChatResponse = {
-									type: 'toolcall',
-									request: {
-										name: tools['name'],
-										data: tools['arguments'],
-									},
+								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;
 								}
-								onReceive(toolcallResponse)
-								break
-							}
 
-							case 'toolres': {
-								const tools = JSON.parse(data)
-								const toolresResponse: ChatResponse = {
-									type: 'toolres',
-									response: {
-										name: tools['name'],
-										data: tools['response'],
-									},
+								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;
 								}
-								onReceive(toolresResponse)
-								break
-							}
 
-							case 'error': {
-								throw new Error(data)
-							}
-
-							default: {
-								// 如果没有明确的事件类型,默认作为消息处理
-								const defaultResponse: ChatResponse = {
-									type: 'message',
-									message: data,
+								default: {
+									// 如果没有明确的事件类型,默认作为消息处理
+									const defaultResponse: ChatResponse = {
+										type: 'message',
+										message: data
+									};
+									onReceive(defaultResponse);
+									break;
 								}
-								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()
-
-					if (trimmedLine === '') {
-						continue
-					}
+					for (const line of completeLines) {
+						const trimmedLine = line.trim();
 
-					if (trimmedLine.startsWith('event:')) {
-						currentEvent = trimmedLine.substring(6).trim() as ChatResponseType
-						continue
-					}
+						if (trimmedLine === '') {
+							continue;
+						}
 
-					if (trimmedLine.startsWith('data:')) {
-						const data = line.substring(line.indexOf('data:') + 5)
+						if (trimmedLine.startsWith('event:')) {
+							currentEvent = trimmedLine.substring(6).trim();
+							continue;
+						}
 
-						// 根据事件类型处理数据
-						switch (currentEvent) {
-							case 'message': {
-								const messageResponse: ChatResponse = {
-									type: 'message',
-									message: data,
+						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;
 								}
-								onReceive(messageResponse)
-								break
-							}
 
-							case 'toolcall': {
-								const tools = JSON.parse(data)
-								const toolcallResponse: ChatResponse = {
-									type: 'toolcall',
-									request: {
-										name: tools['name'],
-										data: tools['arguments'],
-									},
+								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;
 								}
-								onReceive(toolcallResponse)
-								break
-							}
 
-							case 'toolres': {
-								const tools = JSON.parse(data)
-								const toolresResponse: ChatResponse = {
-									type: 'toolres',
-									response: {
-										name: tools['name'],
-										data: tools['response'],
-									},
+								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;
 								}
-								onReceive(toolresResponse)
-								break
-							}
 
-							default: {
-								// 如果没有明确的事件类型,默认作为消息处理
-								const defaultResponse: ChatResponse = {
-									type: 'message',
-									message: data,
+								default: {
+									// 如果没有明确的事件类型,默认作为消息处理
+									const defaultResponse: ChatResponse = {
+										type: 'message',
+										message: data
+									};
+									onReceive(defaultResponse);
+									break;
 								}
-								onReceive(defaultResponse)
-								break
 							}
 						}
 					}
 				}
+
+				onComplete?.(undefined)
+			} catch (error: any) {
+				if (error.name !== 'AbortError') {
+					console.error('SSE连接错误:', error);
+				}
+				onComplete?.(error)
 			}
-		}
+		};
 
 		// 启动SSE连接
-		startSSE()
-			.then(() => onComplete?.(undefined))
-			.catch((e) => onComplete?.(e))
+		startSSE();
 
 		// 返回关闭连接的函数
 		return () => {
-			controller.abort()
-		}
+			controller.abort();
+		};
 	},
 }

+ 1 - 1
src/api/assist/type.ts

@@ -41,7 +41,7 @@ export type Text = ChatResponseBase<'message'> & {
 export type ToolCallRequest = ChatResponseBase<'toolcall'> & {
 	request: {
 		name: string,
-		data: any
+		data: string
 	}
 }
 

+ 2 - 2
src/components/markdown/Markdown.vue

@@ -5,7 +5,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, defineComponent, h, VNode } from 'vue'
+import { computed, defineComponent, h } from 'vue'
 import MarkdownIt from 'markdown-it';
 import {parseDocument} from 'htmlparser2'
 import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
@@ -55,7 +55,7 @@ const MarkdownNodeRenderer = defineComponent({
 
       for (let i = 0; i < (props.plugins ?? []).length; i++) {
         const plugin = (props.plugins ?? [])[i]
-        if (plugin.tagName === node.tagName) {
+				if (plugin.tagName === node.tagName) {
           return plugin.renderer(node as {attribs: Record<string,string>})
         }
       }

+ 1 - 1
src/components/markdown/plugins/markdown-it-echarts.ts → src/components/markdown/plugins/echarts.ts

@@ -3,7 +3,7 @@ import type {RenderRule} from "markdown-it/lib/renderer.mjs";
 import type Token from "markdown-it/lib/token.mjs";
 import {defineMarkdownPlugin} from "../type/markdown.ts";
 import {h} from "vue";
-import VueCharts from "./VueCharts.vue";
+import VueCharts from "/@/components/markdown/plugins/impl/VueCharts.vue";
 
 
 // 验证JSON格式

+ 0 - 0
src/components/markdown/plugins/impl/ToolsLoadingCard.vue


+ 0 - 0
src/components/markdown/plugins/VueCharts.vue → src/components/markdown/plugins/impl/VueCharts.vue


+ 0 - 0
src/components/markdown/plugins/tools-loading.ts


+ 12 - 20
src/views/assistant/index.vue

@@ -1,9 +1,9 @@
 <script setup lang="ts">
-import { ref, nextTick, onMounted, computed, onUnmounted, reactive, watch } from 'vue'
+import { ref, nextTick, onMounted, computed, onUnmounted, reactive } from 'vue'
 import { Local } from '/@/utils/storage'
 import { User, ChatDotRound, Delete, Edit, Check, Close } from '@element-plus/icons-vue'
 import { MarkdownPlugin } from '/@/components/markdown/type/markdown'
-import EChartsPlugin from '/@/components/markdown/plugins/markdown-it-echarts'
+import EChartsPlugin from '/@/components/markdown/plugins/echarts'
 import Markdown from '/@/components/markdown/Markdown.vue'
 import assist from '/@/api/assist'
 import { ChatResponse, Message } from '/@/api/assist/type'
@@ -118,34 +118,26 @@ const sendMessage = () => {
 					rtn.content += resp.message
 					break
 				case 'toolcall':
-					rtn.content +=`
-> ### Tool Call
->
-> **Name**: \`${resp.request.name}\`
->
-> **Param**: \`${resp.request.data}\`
-`
+					rtn.content += `
+\`\`\`tools-loading
+${resp.request.name}
+${resp.request.data.replace('\n', '')}
+					`
 					break
 				case 'toolres':
 					rtn.content += `
-
->
-> **Value**: \`${resp.response.data}\`
-
+${resp.response.data}
+\`\`\`
 `
 					break
 			}
 		},
-		onComplete: (e)=> {
+		onComplete: (e) => {
 			if (e !== undefined) {
-				rtn.content += `
-
-> **Error**: \`${e.message}\`
-
-`
+				rtn.content += ''
 			}
 			chatInstance.value = undefined
-		}
+		},
 	})
 	messages.value.push(rtn)