|
@@ -11,6 +11,12 @@ import {
|
|
|
SessionMessagesListParams,
|
|
|
SessionMessagesSaveReq,
|
|
|
LmDashboard,
|
|
|
+ Prompt,
|
|
|
+ PromptListParams,
|
|
|
+ PromptAddReq,
|
|
|
+ PromptEditReq,
|
|
|
+ PromptDeleteParams,
|
|
|
+ PromptGetParams,
|
|
|
} from '/@/api/assist/type'
|
|
|
import { get, post, del, put } from '/@/utils/request'
|
|
|
import getOrigin from '/@/utils/origin'
|
|
@@ -70,284 +76,297 @@ export default {
|
|
|
},
|
|
|
},
|
|
|
|
|
|
- struct: (uuid: string, refresh?: boolean) => post('/ai/chat/structdata', { uuid,refresh }),
|
|
|
-
|
|
|
- // 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['UserId'] = 10
|
|
|
-
|
|
|
- // 构建SSE URL
|
|
|
- const baseURL = getOrigin()
|
|
|
- const url = `${baseURL}/ai/chat`
|
|
|
-
|
|
|
- // 使用fetch API实现SSE POST请求(EventSource不支持POST和自定义headers)
|
|
|
- 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 response = await fetch(url, {
|
|
|
- method: 'POST',
|
|
|
- headers,
|
|
|
- body: JSON.stringify(chatRequest),
|
|
|
- signal: controller.signal,
|
|
|
- }).catch((e: Error) => e)
|
|
|
-
|
|
|
- if (response instanceof Error) {
|
|
|
- throw new Error('无法连接到服务器')
|
|
|
- }
|
|
|
|
|
|
- if (!response.ok) {
|
|
|
- throw new Error(`HTTP error! status: ${response.status}`)
|
|
|
- }
|
|
|
-
|
|
|
- const reader = response.body?.getReader()
|
|
|
- const decoder = new TextDecoder()
|
|
|
-
|
|
|
- if (!reader) {
|
|
|
- throw new Error('Response body is not readable')
|
|
|
- }
|
|
|
+ chat: {
|
|
|
+ prompt: {
|
|
|
+ // Prompt列表
|
|
|
+ list: (params?: PromptListParams) => get('/system/lmprompt/list', params),
|
|
|
+ // 获取Prompt详情
|
|
|
+ detail: (params: PromptGetParams) => get('/system/lmprompt/get', params),
|
|
|
+ // 添加Prompt
|
|
|
+ add: (data: PromptAddReq) => post('/system/lmprompt/add', data),
|
|
|
+ // 编辑Prompt
|
|
|
+ edit: (data: PromptEditReq) => put('/system/lmprompt/edit', data),
|
|
|
+ // 删除Prompt
|
|
|
+ del: (params: PromptDeleteParams) => del('/system/lmprompt/delete', params),
|
|
|
+ },
|
|
|
+ struct: (uuid: string, refresh?: boolean) => post('/ai/chat/structdata', { uuid,refresh }),
|
|
|
+ sse: (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
|
|
|
+
|
|
|
+ // 构建SSE URL
|
|
|
+ const baseURL = getOrigin()
|
|
|
+ const url = `${baseURL}/ai/chat`
|
|
|
+
|
|
|
+ // 使用fetch API实现SSE POST请求(EventSource不支持POST和自定义headers)
|
|
|
+ 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',
|
|
|
+ }
|
|
|
|
|
|
- // 读取SSE流
|
|
|
- let currentEvent = ''
|
|
|
- let buffer = ''
|
|
|
+ // 添加认证token
|
|
|
+ const token = getToken()
|
|
|
+ if (token) {
|
|
|
+ headers['Authorization'] = `Bearer ${token}`
|
|
|
+ }
|
|
|
|
|
|
- while (true) {
|
|
|
- const { done, value } = await reader.read()
|
|
|
+ const response = await fetch(url, {
|
|
|
+ method: 'POST',
|
|
|
+ headers,
|
|
|
+ body: JSON.stringify(chatRequest),
|
|
|
+ signal: controller.signal,
|
|
|
+ }).catch((e: Error) => e)
|
|
|
|
|
|
- if (done) {
|
|
|
- break
|
|
|
+ if (response instanceof Error) {
|
|
|
+ throw new Error('无法连接到服务器')
|
|
|
}
|
|
|
|
|
|
- buffer += decoder.decode(value, { stream: true })
|
|
|
-
|
|
|
- // 按双换行符分割事件块
|
|
|
- const eventBlocks = buffer.split('\n\n')
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP error! status: ${response.status}`)
|
|
|
+ }
|
|
|
|
|
|
- // 保留最后一个可能不完整的事件块
|
|
|
- buffer = eventBlocks.pop() || ''
|
|
|
+ const reader = response.body?.getReader()
|
|
|
+ const decoder = new TextDecoder()
|
|
|
|
|
|
- for (const eventBlock of eventBlocks) {
|
|
|
- if (!eventBlock.trim()) continue
|
|
|
+ if (!reader) {
|
|
|
+ throw new Error('Response body is not readable')
|
|
|
+ }
|
|
|
|
|
|
- const lines = eventBlock.split('\n')
|
|
|
- let eventType = ''
|
|
|
- let dataLines: string[] = []
|
|
|
+ // 读取SSE流
|
|
|
+ let currentEvent = ''
|
|
|
+ let buffer = ''
|
|
|
|
|
|
- // 解析事件块中的每一行
|
|
|
- for (const line of lines) {
|
|
|
- const trimmedLine = line.trim()
|
|
|
+ while (true) {
|
|
|
+ const { done, value } = await reader.read()
|
|
|
|
|
|
- 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 (done) {
|
|
|
+ break
|
|
|
}
|
|
|
|
|
|
- // 如果有事件类型,更新当前事件
|
|
|
- if (eventType) {
|
|
|
- currentEvent = eventType
|
|
|
- }
|
|
|
+ buffer += decoder.decode(value, { stream: true })
|
|
|
|
|
|
- // 如果有数据行,处理数据
|
|
|
- if (dataLines.length > 0) {
|
|
|
- // 将多行data合并,用换行符连接
|
|
|
- const data = dataLines.join('\n')
|
|
|
-
|
|
|
- // 根据事件类型处理数据
|
|
|
- switch (currentEvent) {
|
|
|
- case 'message': {
|
|
|
- const messageResponse: ChatResponse = {
|
|
|
- type: 'message',
|
|
|
- message: data,
|
|
|
- }
|
|
|
- onReceive(messageResponse)
|
|
|
- break
|
|
|
- }
|
|
|
+ // 按双换行符分割事件块
|
|
|
+ const eventBlocks = buffer.split('\n\n')
|
|
|
|
|
|
- case 'toolcall': {
|
|
|
- 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
|
|
|
- }
|
|
|
-
|
|
|
- case 'structdata': {
|
|
|
- const structDataResponse: ChatResponse = {
|
|
|
- type: 'structdata',
|
|
|
- uuid: data,
|
|
|
- }
|
|
|
- onReceive(structDataResponse)
|
|
|
- break
|
|
|
- }
|
|
|
+ // 保留最后一个可能不完整的事件块
|
|
|
+ buffer = eventBlocks.pop() || ''
|
|
|
|
|
|
- case 'toolres': {
|
|
|
- const tools = JSON.parse(data)
|
|
|
- const toolresResponse: ChatResponse = {
|
|
|
- type: 'toolres',
|
|
|
- response: {
|
|
|
- id: tools['tool_call_id'],
|
|
|
- name: tools['name'],
|
|
|
- data: tools['response'],
|
|
|
- },
|
|
|
- }
|
|
|
- onReceive(toolresResponse)
|
|
|
- break
|
|
|
- }
|
|
|
+ for (const eventBlock of eventBlocks) {
|
|
|
+ if (!eventBlock.trim()) continue
|
|
|
|
|
|
- case 'error': {
|
|
|
- const errorResponse: ErrorResponse = {
|
|
|
- type: 'error',
|
|
|
- error: data,
|
|
|
- }
|
|
|
- onReceive(errorResponse)
|
|
|
- break
|
|
|
- }
|
|
|
+ const lines = eventBlock.split('\n')
|
|
|
+ let eventType = ''
|
|
|
+ let dataLines: string[] = []
|
|
|
|
|
|
- case 'meta': {
|
|
|
- break
|
|
|
- }
|
|
|
+ // 解析事件块中的每一行
|
|
|
+ for (const line of lines) {
|
|
|
+ const trimmedLine = line.trim()
|
|
|
|
|
|
- default: {
|
|
|
- // 如果没有明确的事件类型,默认作为消息处理
|
|
|
- const defaultResponse: ChatResponse = {
|
|
|
- type: 'message',
|
|
|
- message: data,
|
|
|
- }
|
|
|
- onReceive(defaultResponse)
|
|
|
- break
|
|
|
+ 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)
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 处理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
|
|
|
- }
|
|
|
-
|
|
|
- if (trimmedLine.startsWith('event:')) {
|
|
|
- currentEvent = trimmedLine.substring(6).trim()
|
|
|
- continue
|
|
|
- }
|
|
|
|
|
|
- if (trimmedLine.startsWith('data:')) {
|
|
|
- const data = line.substring(line.indexOf('data:') + 5)
|
|
|
+ // 如果有事件类型,更新当前事件
|
|
|
+ if (eventType) {
|
|
|
+ currentEvent = eventType
|
|
|
+ }
|
|
|
|
|
|
- // 根据事件类型处理数据
|
|
|
- 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': {
|
|
|
- try {
|
|
|
+ case 'toolcall': {
|
|
|
const tools = JSON.parse(data)
|
|
|
const toolcallResponse: ChatResponse = {
|
|
|
type: 'toolcall',
|
|
|
request: {
|
|
|
+ id: tools['tool_call_id'],
|
|
|
name: tools['name'],
|
|
|
data: tools['arguments'],
|
|
|
},
|
|
|
}
|
|
|
onReceive(toolcallResponse)
|
|
|
- } catch (error) {
|
|
|
- console.error('解析toolcall数据失败:', error, '原始数据:', data)
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ case 'structdata': {
|
|
|
+ const structDataResponse: ChatResponse = {
|
|
|
+ type: 'structdata',
|
|
|
+ uuid: data,
|
|
|
+ }
|
|
|
+ onReceive(structDataResponse)
|
|
|
+ break
|
|
|
}
|
|
|
- break
|
|
|
- }
|
|
|
|
|
|
- case 'toolres': {
|
|
|
- try {
|
|
|
+ case 'toolres': {
|
|
|
const tools = JSON.parse(data)
|
|
|
const toolresResponse: ChatResponse = {
|
|
|
type: 'toolres',
|
|
|
response: {
|
|
|
+ id: tools['tool_call_id'],
|
|
|
name: tools['name'],
|
|
|
data: tools['response'],
|
|
|
},
|
|
|
}
|
|
|
onReceive(toolresResponse)
|
|
|
- } catch (error) {
|
|
|
- console.error('解析toolres数据失败:', error, '原始数据:', data)
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ case 'error': {
|
|
|
+ const errorResponse: ErrorResponse = {
|
|
|
+ type: 'error',
|
|
|
+ error: data,
|
|
|
+ }
|
|
|
+ onReceive(errorResponse)
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ case 'meta': {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ default: {
|
|
|
+ // 如果没有明确的事件类型,默认作为消息处理
|
|
|
+ const defaultResponse: ChatResponse = {
|
|
|
+ type: 'message',
|
|
|
+ message: data,
|
|
|
+ }
|
|
|
+ onReceive(defaultResponse)
|
|
|
+ break
|
|
|
}
|
|
|
- break
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理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()
|
|
|
|
|
|
- default: {
|
|
|
- // 如果没有明确的事件类型,默认作为消息处理
|
|
|
- const defaultResponse: ChatResponse = {
|
|
|
- type: 'message',
|
|
|
- message: data,
|
|
|
+ if (trimmedLine === '') {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if (trimmedLine.startsWith('event:')) {
|
|
|
+ currentEvent = trimmedLine.substring(6).trim()
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 '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
|
|
|
+ }
|
|
|
+
|
|
|
+ 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?.(undefined)
|
|
|
+ } catch (error: any) {
|
|
|
+ if (error.name !== 'AbortError') {
|
|
|
+ console.error('SSE连接错误:', error)
|
|
|
+ }
|
|
|
+ onComplete?.(error)
|
|
|
}
|
|
|
- onComplete?.(error)
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // 启动SSE连接
|
|
|
- startSSE()
|
|
|
+ // 启动SSE连接
|
|
|
+ startSSE()
|
|
|
|
|
|
- // 返回关闭连接的函数
|
|
|
- return () => {
|
|
|
- controller.abort()
|
|
|
+ // 返回关闭连接的函数
|
|
|
+ return () => {
|
|
|
+ controller.abort()
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
}
|