import { CHAT_CONFIG, ContentType, SSE_TIMEOUT } from '../config/chat'; import type { Feedbacktype, ThoughtItem, VisionFile, MessageEnd, MessageReplace } from '../types/dify_chat'; import { unicodeToChar } from '../utils/chat-utils'; // 基础请求选项 const baseOptions = { method: 'GET', mode: 'cors' as RequestMode, credentials: 'omit' as RequestCredentials, headers: new Headers({ 'Content-Type': ContentType.json, 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }), redirect: 'follow' as RequestRedirect, }; // 回调接口定义 export type IOnDataMoreInfo = { conversationId?: string; taskId?: string; messageId: string; errorMessage?: string; errorCode?: string; } export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void; export type IOnThought = (thought: ThoughtItem) => void; export type IOnFile = (file: VisionFile) => void; export type IOnMessageEnd = (messageEnd: MessageEnd) => void; export type IOnMessageReplace = (messageReplace: MessageReplace) => void; export type IOnCompleted = (hasError?: boolean) => void; export type IOnError = (msg: string, code?: string) => void; // 工作流相关类型 export type WorkflowStartedResponse = { task_id: string; workflow_run_id: string; event: string; data: { id: string; workflow_id: string; sequence_number: number; created_at: number; }; } export type WorkflowFinishedResponse = { task_id: string; workflow_run_id: string; event: string; data: { id: string; workflow_id: string; status: string; outputs: any; error: string; elapsed_time: number; total_tokens: number; total_steps: number; created_at: number; finished_at: number; }; } export type NodeStartedResponse = { task_id: string; workflow_run_id: string; event: string; data: { id: string; node_id: string; node_type: string; index: number; predecessor_node_id?: string; inputs: any; created_at: number; extras?: any; }; } export type NodeFinishedResponse = { task_id: string; workflow_run_id: string; event: string; data: { id: string; node_id: string; node_type: string; index: number; predecessor_node_id?: string; inputs: any; process_data: any; outputs: any; status: string; error: string; elapsed_time: number; execution_metadata: { total_tokens: number; total_price: number; currency: string; }; created_at: number; }; } export type IOnWorkflowStarted = (workflowStarted: WorkflowStartedResponse) => void; export type IOnWorkflowFinished = (workflowFinished: WorkflowFinishedResponse) => void; export type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => void; export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void; /** * 处理服务器发送事件 (SSE) 流式响应 * * 这是核心的流式响应处理函数,负责: * - 解析 SSE 数据流 * - 处理各种事件类型(消息、思考、文件、工作流等) * - 错误处理和状态管理 * - 实时更新 UI * * @param response - fetch API 返回的 Response 对象 * @param onData - 处理消息数据的回调函数 * @param onCompleted - 流式响应完成时的回调函数 * @param onThought - 处理 Agent 思考过程的回调函数 * @param onMessageEnd - 处理消息结束事件的回调函数 * @param onMessageReplace - 处理消息替换事件的回调函数 * @param onFile - 处理文件事件的回调函数 * @param onWorkflowStarted - 处理工作流开始事件的回调函数 * @param onWorkflowFinished - 处理工作流完成事件的回调函数 * @param onNodeStarted - 处理节点开始事件的回调函数 * @param onNodeFinished - 处理节点完成事件的回调函数 * @param onError - 处理错误的回调函数 * * @example * ```typescript * const response = await fetch('/api/chat-messages', options); * handleStream( * response, * (message, isFirst, info) => console.log('收到消息:', message), * () => console.log('流式响应完成'), * (thought) => console.log('AI思考:', thought), * // ... 其他回调 * ); * ``` */ const handleStream = ( response: Response, onData: IOnData, onCompleted?: IOnCompleted, onThought?: IOnThought, onMessageEnd?: IOnMessageEnd, onMessageReplace?: IOnMessageReplace, onFile?: IOnFile, onWorkflowStarted?: IOnWorkflowStarted, onWorkflowFinished?: IOnWorkflowFinished, onNodeStarted?: IOnNodeStarted, onNodeFinished?: IOnNodeFinished, onError?: IOnError, ) => { if (!response.ok) { console.error('❌ [handleStream] 响应错误:', response.status, response.statusText); onError?.('网络响应错误'); throw new Error('网络响应错误'); } const reader = response.body?.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; let bufferObj: Record; let isFirstMessage = true; let messageCount = 0; function read() { let hasError = false; reader?.read().then((result: any) => { if (result.done) { onCompleted && onCompleted(); return; } const chunk = decoder.decode(result.value, { stream: true }); buffer += chunk; const lines = buffer.split('\n'); try { lines.forEach((message, index) => { if (message.startsWith('data: ')) { const jsonStr = message.substring(6); try { bufferObj = JSON.parse(jsonStr) as Record; } catch (e) { console.warn('⚠️ [handleStream] JSON解析失败:', e, 'JSON:', jsonStr); // 处理消息截断 onData('', isFirstMessage, { conversationId: bufferObj?.conversation_id, messageId: bufferObj?.message_id || bufferObj?.id, }); return; } if (bufferObj.status === 400 || !bufferObj.event) { console.error('❌ [handleStream] 错误响应:', { status: bufferObj.status, event: bufferObj.event, message: bufferObj.message, code: bufferObj.code }); onData('', false, { conversationId: undefined, messageId: '', errorMessage: bufferObj?.message, errorCode: bufferObj?.code, }); hasError = true; onCompleted?.(true); return; } if (bufferObj.event === 'message' || bufferObj.event === 'agent_message') { const answer = unicodeToChar(bufferObj.answer); onData(answer, isFirstMessage, { conversationId: bufferObj.conversation_id, messageId: bufferObj.id || bufferObj.message_id, taskId: bufferObj.task_id, }); isFirstMessage = false; } else if (bufferObj.event === 'agent_thought' && onThought) { // console.log('🤔 [handleStream] 处理思考事件:', bufferObj.event); onThought(bufferObj as ThoughtItem); } else if (bufferObj.event === 'message_file' && onFile) { // console.log('📁 [handleStream] 处理文件事件:', bufferObj.event); onFile(bufferObj as VisionFile); } else if (bufferObj.event === 'message_end' && onMessageEnd) { // console.log('🏁 [handleStream] 处理消息结束事件:', bufferObj.event); onMessageEnd(bufferObj as MessageEnd); } else if (bufferObj.event === 'message_replace' && onMessageReplace) { // console.log('🔄 [handleStream] 处理消息替换事件:', bufferObj.event); onMessageReplace(bufferObj as MessageReplace); } else if (bufferObj.event === 'workflow_started' && onWorkflowStarted) { // console.log('🚀 [handleStream] 处理工作流开始事件:', bufferObj.event); onWorkflowStarted(bufferObj as WorkflowStartedResponse); } else if (bufferObj.event === 'workflow_finished' && onWorkflowFinished) { // console.log('🎯 [handleStream] 处理工作流完成事件:', bufferObj.event); onWorkflowFinished(bufferObj as WorkflowFinishedResponse); } else if (bufferObj.event === 'node_started' && onNodeStarted) { // console.log('🔗 [handleStream] 处理节点开始事件:', bufferObj.event); onNodeStarted(bufferObj as NodeStartedResponse); } else if (bufferObj.event === 'node_finished' && onNodeFinished) { // console.log('✅ [handleStream] 处理节点完成事件:', bufferObj.event); onNodeFinished(bufferObj as NodeFinishedResponse); } else { // console.log('❓ [handleStream] 未知事件类型:', bufferObj.event); } } else if (message.trim()) { // console.log('📝 [handleStream] 非data消息:', message.substring(0, 100)); } }); // 保留最后一行(可能是不完整的消息) const lastLine = lines[lines.length - 1]; buffer = lastLine; } catch (err) { console.error('❌ [handleStream] 解析响应时出错:', err); onData('', false, { conversationId: undefined, messageId: '', errorMessage: `${err}`, }); hasError = true; onCompleted?.(true); return; } if (!hasError) { read(); } else { } }).catch(err => { console.error('❌ [handleStream] 读取流时出错:', err); onError?.(err.message); }); } read(); }; /** * 基础 HTTP 请求函数 * * 提供统一的请求配置和错误处理: * - 自动添加认证头 * - 统一的 URL 处理 * - 错误状态码处理 * - 自动添加用户 ID * * @param url - 请求的 URL 路径(相对于 API 基础 URL) * @param fetchOptions - fetch API 的配置选项 * @param needAllResponseContent - 是否需要返回完整的响应内容而不是 JSON * @returns Promise - 返回解析后的响应数据 * * @throws {Error} 当请求失败时抛出错误 * * @example * ```typescript * // 发送 GET 请求 * const data = await baseFetch('conversations', { method: 'GET' }); * * // 发送 POST 请求 * const result = await baseFetch('chat-messages', { * method: 'POST', * body: { query: 'Hello' } * }); * ``` */ const baseFetch = (url: string, fetchOptions: any, needAllResponseContent: boolean = false) => { const options = Object.assign({}, baseOptions, fetchOptions); // 直接构建Dify API URL const urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`; // 确保Authorization头存在 if (CHAT_CONFIG.API_KEY && options.headers) { options.headers['Authorization'] = `Bearer ${CHAT_CONFIG.API_KEY}`; } const { body } = options; if (body && typeof body === 'object') { // 为所有请求添加user参数 const bodyWithUser = { ...body, user: CHAT_CONFIG.generateUserId(), }; options.body = JSON.stringify(bodyWithUser); } return fetch(urlWithPrefix, options) .then((res: Response) => { if (!res.ok) { console.error('❌ Request failed:', { status: res.status, statusText: res.statusText, url: urlWithPrefix }); if (res.status === 422) { return res.text().then(text => { let errorMessage = text; try { const data = JSON.parse(text); errorMessage = data.message || data.error || text; } catch (e) { // 如果不是JSON,使用原始文本 } throw new Error(errorMessage); }); } throw new Error(`${res.status}: ${res.statusText}`); } if (needAllResponseContent) { return res.text().then(text => { try { return JSON.parse(text); } catch (e) { return text; } }); } const data = res.json(); return data; }) .catch((err) => { console.error('❌ Request error:', err.message); throw err; }); }; /** * 发送 SSE (Server-Sent Events) POST 请求 * * 专门用于处理流式响应的 POST 请求: * - 配置 SSE 相关的请求头 * - 设置 AbortController 用于取消请求 * - 调用 handleStream 处理流式响应 * - 自动添加用户 ID 到请求体 * * @param url - 请求的 URL 路径 * @param fetchOptions - fetch 配置选项 * @param callbacks - 包含各种事件回调的对象 * @param callbacks.onData - 处理消息数据的回调 * @param callbacks.onCompleted - 流式响应完成时的回调 * @param callbacks.onThought - 处理思考过程的回调 * @param callbacks.onFile - 处理文件的回调 * @param callbacks.onMessageEnd - 处理消息结束的回调 * @param callbacks.onMessageReplace - 处理消息替换的回调 * @param callbacks.onError - 处理错误的回调 * @param callbacks.getAbortController - 获取中止控制器的回调 * @param callbacks.onWorkflowStarted - 处理工作流开始的回调 * @param callbacks.onWorkflowFinished - 处理工作流完成的回调 * @param callbacks.onNodeStarted - 处理节点开始的回调 * @param callbacks.onNodeFinished - 处理节点完成的回调 * * @example * ```typescript * ssePost('chat-messages', { * body: { query: 'Hello', response_mode: 'streaming' } * }, { * onData: (message, isFirst, info) => updateUI(message), * onCompleted: () => setLoading(false), * onError: (error) => showError(error) * }); * ``` */ export const ssePost = ( url: string, fetchOptions: any, { onData, onCompleted, onThought, onFile, onMessageEnd, onMessageReplace, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onError, getAbortController, }: { onData: IOnData; onCompleted?: IOnCompleted; onThought?: IOnThought; onFile?: IOnFile; onMessageEnd?: IOnMessageEnd; onMessageReplace?: IOnMessageReplace; onError?: IOnError; getAbortController?: (abortController: AbortController) => void; onWorkflowStarted?: IOnWorkflowStarted; onWorkflowFinished?: IOnWorkflowFinished; onNodeStarted?: IOnNodeStarted; onNodeFinished?: IOnNodeFinished; }, ) => { const options = Object.assign({}, baseOptions, { method: 'POST', }, fetchOptions); // 直接构建Dify API URL const urlWithPrefix = `${CHAT_CONFIG.API_URL}/${url.replace(/^\//, '')}`; const controller = new AbortController(); if (getAbortController) getAbortController(controller); options.headers = { ...options.headers, 'Content-Type': 'application/json', 'Accept': ContentType.stream, 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }; options.signal = controller.signal; const { body } = options; if (body && typeof body === 'object') { // 为SSE请求添加user参数 const bodyWithUser = { ...body, user: CHAT_CONFIG.generateUserId(), }; options.body = JSON.stringify(bodyWithUser); } return fetch(urlWithPrefix, options) .then((res: Response) => { if (!/^(2|3)\d{2}$/.test(res.status.toString())) { res.json().then((data: any) => { console.error('❌ SSE Error:', data.message || 'Server Error'); onError?.(data.message || 'Server Error'); }); return; } handleStream( res, onData, onCompleted, onThought, onMessageEnd, onMessageReplace, onFile, onWorkflowStarted, onWorkflowFinished, onNodeStarted, onNodeFinished, onError ); }) .catch((err) => { console.error('❌ SSE Request Error:', err); onError?.(err.message); }); }; /** * 获取用户的会话列表 * * 从 Dify API 获取当前用户的所有会话: * - 自动分页获取(最多100条) * - 包含会话 ID、名称、输入参数等信息 * - 按时间倒序排列 * * @returns Promise - 包含会话列表的响应对象 * @returns Promise.data - 会话数组 * @returns Promise.has_more - 是否还有更多数据 * @returns Promise.limit - 每页限制数量 * * @throws {Error} 当获取会话列表失败时抛出错误 * * @example * ```typescript * const response = await fetchConversations(); * const conversations = response.data; * console.log('会话数量:', conversations.length); * ``` */ export const fetchConversations = async () => { const user = CHAT_CONFIG.generateUserId(); const params = new URLSearchParams({ user, limit: '100', first_id: '', }); return fetch(`${CHAT_CONFIG.API_URL}/conversations?${params}`, { method: 'GET', headers: { 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, }).then(res => { if (!res.ok) { throw new Error(`Failed to fetch conversations: ${res.status}`); } return res.json(); }); }; /** * 获取指定会话的聊天消息列表 * * 从 Dify API 获取特定会话的消息历史: * - 支持分页加载(最多20条) * - 包含用户消息和 AI 回复 * - 按时间顺序排列 * * @param conversationId - 会话 ID * @returns Promise - 包含消息列表的响应对象 * @returns Promise.data - 消息数组 * @returns Promise.has_more - 是否还有更多历史消息 * @returns Promise.limit - 每页限制数量 * * @throws {Error} 当获取消息列表失败时抛出错误 * * @example * ```typescript * const response = await fetchChatList('conv-123'); * const messages = response.data; * console.log('消息数量:', messages.length); * ``` */ export const fetchChatList = async (conversationId: string) => { const user = CHAT_CONFIG.generateUserId(); const params = new URLSearchParams({ user, conversation_id: conversationId, limit: '20', last_id: '', }); return fetch(`${CHAT_CONFIG.API_URL}/messages?${params}`, { method: 'GET', headers: { 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, }).then(res => { if (!res.ok) { throw new Error(`Failed to fetch chat list: ${res.status}`); } return res.json(); }); }; /** * 获取应用参数配置 * * 从 Dify API 获取应用的配置信息: * - 用户输入表单配置 * - 开场白设置 * - 文件上传配置 * - 其他应用级别设置 * * @returns Promise - 包含应用参数的响应对象 * @returns Promise.user_input_form - 用户输入表单配置 * @returns Promise.opening_statement - 开场白内容 * @returns Promise.file_upload - 文件上传配置 * * @throws {Error} 当获取应用参数失败时抛出错误 * * @example * ```typescript * const params = await fetchAppParams(); * const { user_input_form, opening_statement } = params.data; * console.log('开场白:', opening_statement); * ``` */ export const fetchAppParams = async () => { return fetch(`${CHAT_CONFIG.API_URL}/parameters`, { method: 'GET', headers: { 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, }).then(res => { if (!res.ok) { throw new Error(`Failed to fetch app params: ${res.status}`); } return res.json(); }); }; /** * 更新消息反馈[未使用] * * 向 Dify API 提交用户对 AI 回复的反馈: * - 支持点赞/点踩评价 * - 可添加文字反馈内容 * - 用于改进 AI 回复质量 * * @param params - 反馈参数对象 * @param params.url - 包含消息 ID 的 URL * @param params.body - 反馈内容 * @param params.body.rating - 评分:'like' | 'dislike' | null * @param params.body.content - 文字反馈内容(可选) * @returns Promise - 反馈提交结果 * * @throws {Error} 当提交反馈失败时抛出错误 * * @example * ```typescript * await updateFeedback({ * url: '/messages/msg-123/feedbacks', * body: { rating: 'like', content: '回答很好' } * }); * ``` */ export const updateFeedback = async ({ url, body }: { url: string; body: Feedbacktype }) => { const messageId = url.split('/').pop(); // 从URL中提取messageId return fetch(`${CHAT_CONFIG.API_URL}/messages/${messageId}/feedbacks`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, body: JSON.stringify({ ...body, user: CHAT_CONFIG.generateUserId(), }), }).then(res => { if (!res.ok) { throw new Error(`Failed to update feedback: ${res.status}`); } return res.json(); }); }; /** * 生成会话名称[未使用] * * 让 AI 根据会话内容自动生成合适的会话名称: * - 基于会话中的消息内容 * - 生成简洁有意义的标题 * - 用于替换默认的"新对话"名称 * * @param id - 会话 ID * @returns Promise - 包含生成名称的响应对象 * @returns Promise.name - 生成的会话名称 * * @throws {Error} 当生成名称失败时抛出错误 * * @example * ```typescript * const result = await generateConversationName('conv-123'); * console.log('生成的名称:', result.name); * ``` */ export const generateConversationName = async (id: string) => { return fetch(`${CHAT_CONFIG.API_URL}/conversations/${id}/name`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, body: JSON.stringify({ auto_generate: true, user: CHAT_CONFIG.generateUserId(), }), }).then(res => { if (!res.ok) { throw new Error(`Failed to generate conversation name: ${res.status}`); } return res.json(); }); }; /** * 重命名会话 * * 更新会话的显示名称: * - 支持手动设置名称 * - 支持 AI 自动生成名称 * - 更新后在会话列表中显示新名称 * * @param id - 会话 ID * @param name - 新的会话名称(当 autoGenerate 为 false 时使用) * @param autoGenerate - 是否使用 AI 自动生成名称,默认为 false * @returns Promise - 重命名结果 * @returns Promise.name - 最终的会话名称 * * @throws {Error} 当重命名失败时抛出错误 * * @example * ```typescript * // 手动设置名称 * await renameConversation('conv-123', '关于编程的讨论'); * * // AI 自动生成名称 * await renameConversation('conv-123', '', true); * ``` */ export const renameConversation = async (id: string, name: string, autoGenerate: boolean = false) => { return fetch(`${CHAT_CONFIG.API_URL}/conversations/${id}/name`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, body: JSON.stringify({ name: autoGenerate ? undefined : name, auto_generate: autoGenerate, user: CHAT_CONFIG.generateUserId(), }), }).then(res => { if (!res.ok) { throw new Error(`Failed to rename conversation: ${res.status}`); } return res.json(); }); }; /** * 删除会话 * * 从用户的会话列表中永久删除指定会话: * - 删除会话及其所有消息 * - 操作不可逆 * - 删除后从会话列表中移除 * * @param id - 要删除的会话 ID * @returns Promise - 删除操作结果 * * @throws {Error} 当删除会话失败时抛出错误 * * @example * ```typescript * await deleteConversation('conv-123'); * console.log('会话已删除'); * ``` */ export const deleteConversation = async (id: string) => { return fetch(`${CHAT_CONFIG.API_URL}/conversations/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CHAT_CONFIG.API_KEY}`, }, body: JSON.stringify({ user: CHAT_CONFIG.generateUserId(), }), }).then(res => { if (!res.ok) { throw new Error(`Failed to delete conversation: ${res.status}`); } return res.json(); }); }; /** * 上传文件到 Dify API[未使用] * * 使用 XMLHttpRequest 上传文件: * - 支持文件上传进度监控 * - 自动添加认证头和用户 ID * - 返回文件 ID 用于后续引用 * * @param fetchOptions - 上传配置选项 * @param fetchOptions.method - HTTP 方法(通常为 'POST') * @param fetchOptions.url - 上传 URL(可选,会自动构建) * @param fetchOptions.data - FormData 对象,包含要上传的文件 * @param fetchOptions.headers - 额外的请求头 * @param fetchOptions.xhr - XMLHttpRequest 实例 * @param fetchOptions.onprogress - 上传进度回调函数 * @returns Promise - 返回包含文件 ID 的对象 * @returns Promise.id - 上传后的文件 ID * * @throws {Error} 当文件上传失败时抛出错误 * * @example * ```typescript * const formData = new FormData(); * formData.append('file', fileBlob); * * const xhr = new XMLHttpRequest(); * const result = await upload({ * data: formData, * xhr: xhr, * onprogress: (event) => { * const progress = (event.loaded / event.total) * 100; * console.log('上传进度:', progress + '%'); * } * }); * console.log('文件ID:', result.id); * ``` */ export const upload = (fetchOptions: any): Promise => { const urlWithPrefix = `${CHAT_CONFIG.API_URL}/files/upload`; const defaultOptions = { method: 'POST', url: urlWithPrefix, data: {}, }; const options = { ...defaultOptions, ...fetchOptions, }; return new Promise((resolve, reject) => { const xhr = options.xhr; xhr.open(options.method, options.url); for (const key in options.headers) xhr.setRequestHeader(key, options.headers[key]); if (CHAT_CONFIG.API_KEY) { xhr.setRequestHeader('Authorization', `Bearer ${CHAT_CONFIG.API_KEY}`); } // 添加user参数到formData if (options.data instanceof FormData) { options.data.append('user', CHAT_CONFIG.generateUserId()); } xhr.withCredentials = false; // 改为false,因为直接调用Dify API xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) resolve({ id: xhr.response }); else reject(new Error(xhr.responseText || 'Upload failed')); } }; xhr.upload.onprogress = options.onprogress; xhr.send(options.data); }); }; /** * 通用 HTTP 请求函数 * * 基于 baseFetch 的通用请求封装: * - 合并基础配置和自定义选项 * - 统一的错误处理 * - 支持所有 HTTP 方法 * * @param url - 请求 URL 路径 * @param options - 请求配置选项,默认为空对象 * @param needAllResponseContent - 是否返回完整响应内容,默认为 false * @returns Promise - 请求响应数据 * * @example * ```typescript * const data = await request('conversations', { method: 'GET' }); * ``` */ export const request = (url: string, options = {}, needAllResponseContent = false) => { return baseFetch(url, { ...baseOptions, ...options }, needAllResponseContent); }; /** * 发送 GET 请求 * * @param url - 请求 URL 路径 * @param options - 额外的请求配置选项,默认为空对象 * @returns Promise - 请求响应数据 * * @example * ```typescript * const conversations = await get('conversations'); * ``` */ export const get = (url: string, options = {}) => { return request(url, { ...options, method: 'GET' }); }; /** * 发送 POST 请求 * * @param url - 请求 URL 路径 * @param options - 额外的请求配置选项,默认为空对象 * @returns Promise - 请求响应数据 * * @example * ```typescript * const result = await post('chat-messages', { * body: { query: 'Hello' } * }); * ``` */ export const post = (url: string, options = {}) => { return request(url, { ...options, method: 'POST' }); }; /** * 发送 PUT 请求 * * @param url - 请求 URL 路径 * @param options - 额外的请求配置选项,默认为空对象 * @returns Promise - 请求响应数据 * * @example * ```typescript * const result = await put('conversations/123', { * body: { name: '新名称' } * }); * ``` */ export const put = (url: string, options = {}) => { return request(url, { ...options, method: 'PUT' }); }; /** * 发送 DELETE 请求 * * @param url - 请求 URL 路径 * @param options - 额外的请求配置选项,默认为空对象 * @returns Promise - 请求响应数据 * * @example * ```typescript * await del('conversations/123'); * ``` */ export const del = (url: string, options = {}) => { return request(url, { ...options, method: 'DELETE' }); }; /** * 发送聊天消息 * * 向 Dify API 发送聊天消息并处理流式响应: * - 自动设置流式响应模式 * - 支持文件附件 * - 支持会话输入参数 * - 处理各种类型的响应事件 * * @param body - 消息请求体 * @param body.query - 用户的问题文本 * @param body.conversation_id - 会话 ID(可选,用于继续现有会话) * @param body.files - 附件文件列表(可选) * @param body.inputs - 会话输入参数(可选) * @param callbacks - 事件回调函数集合 * @param callbacks.onData - 处理消息数据的回调,必需 * @param callbacks.onCompleted - 流式响应完成时的回调,必需 * @param callbacks.onFile - 处理文件的回调(可选) * @param callbacks.onThought - 处理思考过程的回调(可选) * @param callbacks.onMessageEnd - 处理消息结束的回调(可选) * @param callbacks.onMessageReplace - 处理消息替换的回调(可选) * @param callbacks.onError - 处理错误的回调(可选) * @param callbacks.getAbortController - 获取中止控制器的回调(可选) * @param callbacks.onWorkflowStarted - 处理工作流开始的回调(可选) * @param callbacks.onNodeStarted - 处理节点开始的回调(可选) * @param callbacks.onNodeFinished - 处理节点完成的回调(可选) * @param callbacks.onWorkflowFinished - 处理工作流完成的回调(可选) * @returns Promise - 异步操作完成 * * @example * ```typescript * await sendChatMessage({ * query: '你好,请介绍一下自己', * conversation_id: 'conv-123' * }, { * onData: (message, isFirst, info) => { * console.log('收到消息:', message); * updateChatUI(message, info.messageId); * }, * onCompleted: (hasError) => { * console.log('对话完成', hasError ? '有错误' : '成功'); * setLoading(false); * }, * onThought: (thought) => { * console.log('AI思考:', thought.thought); * }, * onError: (error) => { * console.error('发送失败:', error); * showErrorMessage(error); * } * }); * ``` */ export const sendChatMessage = async ( body: Record, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace, onWorkflowStarted, onNodeStarted, onNodeFinished, onWorkflowFinished, }: { onData: IOnData; onCompleted: IOnCompleted; onFile?: IOnFile; onThought?: IOnThought; onMessageEnd?: IOnMessageEnd; onMessageReplace?: IOnMessageReplace; onError?: IOnError; getAbortController?: (abortController: AbortController) => void; onWorkflowStarted?: IOnWorkflowStarted; onNodeStarted?: IOnNodeStarted; onNodeFinished?: IOnNodeFinished; onWorkflowFinished?: IOnWorkflowFinished; }, ) => { return ssePost('chat-messages', { body: { ...body, response_mode: 'streaming', }, }, { onData, onCompleted, onThought, onFile, onError, getAbortController, onMessageEnd, onMessageReplace, onNodeStarted, onWorkflowStarted, onWorkflowFinished, onNodeFinished }); };