// API服务文件 import { useUserStore } from '@/stores/user.js'; import { API_CONFIG, getApiUrl } from '@/utils/config.js'; // 图片URL处理函数 - 处理小程序中图片路径问题 export const getResourceUrl = (url) => { if (!url || typeof url !== 'string') { return '/static/default-avatar.png'; } // 如果是完整的http/https URL,直接返回 if (url.startsWith('http://') || url.startsWith('https://')) { return url; } // 如果是相对路径,拼接完整的服务器地址 if (url.startsWith('/file/')) { return API_CONFIG.BASE_URL + url; } // 如果是其他相对路径,也拼接服务器地址 if (url.startsWith('/')) { return API_CONFIG.BASE_URL + url; } // 默认返回原路径 return url; }; // 文本清理函数 - 只保留文字和标点符号 export const cleanText = (text) => { if (!text || typeof text !== 'string') { return ''; } // 去除所有HTML标签 let cleaned = text.replace(/<[^>]*>/g, ''); // 去除Markdown格式标记 cleaned = cleaned // 去除粗体标记 **text** 或 __text__ .replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/__([^_]+)__/g, '$1') // 去除斜体标记 *text* 或 _text_ .replace(/\*([^*]+)\*/g, '$1') .replace(/_([^_]+)_/g, '$1') // 去除删除线标记 ~~text~~ .replace(/~~([^~]+)~~/g, '$1') // 去除代码标记 `code` .replace(/`([^`]+)`/g, '$1') // 去除链接标记 [text](url) .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 去除图片标记 ![alt](url) .replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1') // 去除标题标记 # ## ### .replace(/^#{1,6}\s*/gm, '') // 去除列表标记 - * + .replace(/^[\s]*[-*+]\s*/gm, '') // 去除引用标记 > .replace(/^>\s*/gm, '') // 去除水平线标记 --- 或 *** .replace(/^[-*]{3,}$/gm, ''); // 去除多余的空白字符和换行 cleaned = cleaned // 将多个连续空格和换行替换为单个空格 .replace(/\s+/g, ' ') // 去除行首行尾空格 .trim(); // 去除特殊字符(保留中文、英文、数字、标点符号,以及分隔符 &) cleaned = cleaned.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s\.,;:!?()()【】""''""''、,。!?;:&]/g, ''); return cleaned; }; // 检查用户登录状态 const checkLoginStatus = () => { const userStore = useUserStore(); const customToken = uni.getStorageSync('custom_token'); const userToken = uni.getStorageSync('user_token'); const userInfo = uni.getStorageSync('userInfo'); // 解析userInfo,检查是否有有效的token let userInfoToken = ''; if (userInfo) { try { const parsedUserInfo = JSON.parse(userInfo); userInfoToken = parsedUserInfo.token || ''; } catch (e) { console.warn('解析userInfo失败:', e); } } // 只有存在有效token才认为已登录 const hasValidToken = !!(userStore.token || customToken || userToken || userInfoToken); return { isLoggedIn: hasValidToken, token: userStore.token || customToken || userToken || userInfoToken }; }; // 请求拦截器 const request = (options) => { return new Promise((resolve, reject) => { const userStore = useUserStore(); const loginStatus = checkLoginStatus(); // 默认配置 const defaultOptions = { url: getApiUrl(options.url), method: options.method || 'GET', header: { 'Content-Type': 'application/json', 'Authorization': loginStatus.token ? (loginStatus.token.startsWith('Bearer ') ? loginStatus.token : 'Bearer ' + loginStatus.token) : '', ...options.header }, data: options.data || {}, timeout: options.timeout || API_CONFIG.TIMEOUT }; // 发送请求 uni.request({ ...defaultOptions, success: (res) => { console.log('API请求成功:', res); console.log('响应状态码:', res.statusCode); console.log('响应数据:', res.data); // 处理响应 if (res.statusCode === 200) { resolve(res.data); } else { reject({ code: res.statusCode, message: res.data?.message || '请求失败', data: res.data }); } }, fail: (err) => { console.error('API请求失败:', err); reject({ code: -1, message: '网络请求失败', error: err }); } }); }); }; // AI聊天API export const chatAPI = { // 同步聊天接口 syncChat: async (params) => { const loginStatus = checkLoginStatus(); // 如果用户未登录,直接返回失败,让前端使用本地模拟 if (!loginStatus.isLoggedIn) { console.log('用户未登录,跳过API调用,使用本地模拟'); return { success: false, error: { message: '用户未登录,使用本地模拟回复' }, isAnonymous: true }; } try { const requestData = { message: params.message, useFunctionCall: false, modelId: params.modelId || null, // 支持传入modelId,默认为null使用后端默认 templateId: params.templateId || params.characterId, // 支持templateId参数 sessionId: params.sessionId || params.conversationId || null // 支持sessionId参数,conversationId作为备选 }; console.log('发送AI聊天请求,参数:', requestData); const response = await request({ url: API_CONFIG.ENDPOINTS.CHAT_SYNC, method: 'POST', data: requestData }); console.log('API原始响应:', response); // 处理不同的响应格式 - 参考chat.vue的实现 let processedResponse = null; // 如果响应是字符串,直接返回 if (typeof response === 'string') { processedResponse = response; } // 如果响应是对象,尝试提取AI回复 else if (typeof response === 'object' && response !== null) { // 优先处理根级别的response字段(根据实际后端响应结构) if (response.response && typeof response.response === 'string') { processedResponse = response.response; console.log('从根级别字段 response 提取回复:', processedResponse); } // 备用:处理嵌套结构:res.data.data.response else if (response.data && response.data.data && response.data.data.response) { processedResponse = response.data.data.response; console.log('从嵌套结构 data.data.response 提取回复:', processedResponse); } // 备用:直接使用 data.response else if (response.data && response.data.response) { processedResponse = response.data.response; console.log('从字段 data.response 提取回复:', processedResponse); } // 检查是否为状态消息 else if (response.message) { if (response.message === '对话成功') { // 后端返回成功消息,使用默认回复 processedResponse = '我收到了你的消息,很高兴和你聊天!'; console.log('检测到对话成功状态,使用默认回复'); } else { // 如果后端返回了错误信息,抛出错误 throw new Error(response.message); } } // 检查data中的message字段 else if (response.data && response.data.message) { if (response.data.message === '对话成功') { // 后端返回成功消息,使用默认回复 processedResponse = '我收到了你的消息,很高兴和你聊天!'; console.log('检测到对话成功状态,使用默认回复'); } else { // 如果后端返回了错误信息,抛出错误 throw new Error(response.data.message); } } // 尝试其他可能的字段名 else { const possibleFields = ['message', 'response', 'content', 'reply', 'answer', 'text']; for (const field of possibleFields) { if (response[field] && typeof response[field] === 'string') { processedResponse = response[field]; console.log(`从字段 ${field} 提取回复:`, processedResponse); break; } } // 如果还是没找到,尝试在data中查找 if (!processedResponse && response.data) { for (const field of possibleFields) { if (response.data[field] && typeof response.data[field] === 'string') { processedResponse = response.data[field]; console.log(`从字段 data.${field} 提取回复:`, processedResponse); break; } } } } } // 如果仍然没有找到有效回复,使用默认回复 if (!processedResponse) { processedResponse = '我收到了你的消息,很高兴和你聊天!'; console.log('未找到有效回复,使用默认回复'); } // 清理文本,只保留文字和标点符号 const cleanedResponse = cleanText(processedResponse); console.log('原始回复:', processedResponse); console.log('清理后回复:', cleanedResponse); return { success: true, data: cleanedResponse, originalResponse: response }; } catch (error) { console.error('AI聊天API调用失败:', error); return { success: false, error: error }; } }, // 异步聊天接口(如果需要) asyncChat: async (params) => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CHAT_ASYNC, method: 'POST', data: { message: params.message, characterId: params.characterId, conversationId: params.conversationId || null, ...params } }); return { success: true, data: response }; } catch (error) { console.error('AI异步聊天API调用失败:', error); return { success: false, error: error }; } }, // 获取聊天历史 getChatHistory: async (conversationId) => { try { const response = await request({ url: `${API_CONFIG.ENDPOINTS.CHAT_HISTORY}/${conversationId}`, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取聊天历史失败:', error); return { success: false, error: error }; } }, // 创建新对话 createConversation: async (characterId) => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CHAT_CONVERSATION, method: 'POST', data: { characterId: characterId } }); return { success: true, data: response }; } catch (error) { console.error('创建对话失败:', error); return { success: false, error: error }; } }, // 清空会话上下文 clearSession: async (sessionId) => { try { const response = await request({ url: `${API_CONFIG.ENDPOINTS.CHAT_SESSION}/${sessionId}`, method: 'DELETE' }); return { success: true, data: response }; } catch (error) { console.error('清空会话API调用失败:', error); return { success: false, error: error }; } }, // 获取历史消息(根据sessionId查询全部) getHistoryMessages: async (sessionId) => { const loginStatus = checkLoginStatus(); // 如果用户未登录,直接返回空数组 if (!loginStatus.isLoggedIn) { console.log('用户未登录,无法获取历史消息'); return { success: true, data: [], isAnonymous: true }; } try { const response = await request({ url: API_CONFIG.ENDPOINTS.MESSAGE_HISTORY, method: 'GET', data: { sessionId: sessionId } }); console.log('历史消息API响应:', response); // 处理响应数据 let messageList = []; if (response && response.data) { // 直接是数组 if (Array.isArray(response.data)) { messageList = response.data; } // 可能嵌套在data字段中 else if (response.data.data && Array.isArray(response.data.data)) { messageList = response.data.data; } } else if (Array.isArray(response)) { messageList = response; } console.log('解析后的历史消息数量:', messageList.length); return { success: true, data: messageList }; } catch (error) { console.error('获取历史消息失败:', error); return { success: false, error: error, data: [] }; } } }; // 语音相关API export const voiceAPI = { // 1. 文本转语音 - TTS功能 textToSpeech: async (text, options = {}) => { try { const loginStatus = checkLoginStatus(); if (!loginStatus.isLoggedIn) { return { success: false, error: { message: '用户未登录' } }; } console.log('开始文本转语音:', text); const response = await request({ url: API_CONFIG.ENDPOINTS.TTS, method: 'POST', data: { text: text, voiceStyle: options.voiceStyle || 'default', modelId: options.modelId || null, templateId: options.templateId || null } }); console.log('文本转语音响应:', response); // 处理响应数据 let audioUrl = null; if (response.data && response.data.audioUrl) { audioUrl = response.data.audioUrl; } else if (response.audioUrl) { audioUrl = response.audioUrl; } else if (response.data && response.data.url) { audioUrl = response.data.url; } else if (response.url) { audioUrl = response.url; } return { success: true, data: { audioUrl: audioUrl, originalResponse: response } }; } catch (error) { console.error('文本转语音失败:', error); return { success: false, error: error }; } }, // 2. 对话+语音合成 - 对话后转语音 chatWithTTS: async (message, options = {}) => { try { const loginStatus = checkLoginStatus(); if (!loginStatus.isLoggedIn) { return { success: false, error: { message: '用户未登录' } }; } console.log('开始对话+语音合成:', message); const response = await request({ url: API_CONFIG.ENDPOINTS.ANSWER_TTS, method: 'POST', data: { message: message, modelId: options.modelId || null, templateId: options.templateId || null, voiceStyle: options.voiceStyle || 'default' } }); console.log('对话+语音合成响应:', response); // 处理响应数据 let aiResponse = null; let audioUrl = null; if (response.data) { aiResponse = response.data.response || response.data.text || response.data.message; audioUrl = response.data.audioUrl || response.data.url; } return { success: true, data: { aiResponse: aiResponse, audioUrl: audioUrl, originalResponse: response } }; } catch (error) { console.error('对话+语音合成失败:', error); return { success: false, error: error }; } }, // 3. 语音对话 - 完整语音交互流程(前端发送aac格式,后端转换为wav处理) voiceChat: async (filePath, options = {}) => { try { const loginStatus = checkLoginStatus(); // 如果用户未登录,直接返回失败,让前端使用降级处理 if (!loginStatus.isLoggedIn) { console.log('用户未登录,语音对话跳过API调用'); return { success: false, error: { message: '用户未登录,使用降级处理' } }; } console.log('开始语音对话,文件路径:', filePath); console.log('注意:前端发送aac格式音频,后端需要转换为wav格式进行处理'); // 构建认证头 let authHeader = ''; if (loginStatus.token) { authHeader = loginStatus.token.startsWith('Bearer ') ? loginStatus.token : 'Bearer ' + loginStatus.token; } return new Promise((resolve) => { uni.uploadFile({ url: getApiUrl(API_CONFIG.ENDPOINTS.VOICE_CHAT), filePath: filePath, name: 'audio', header: authHeader ? { 'Authorization': authHeader } : {}, formData: { modelId: options.modelId || null, templateId: options.templateId || null, voiceStyle: options.voiceStyle || 'default' }, success: (res) => { console.log('语音对话上传成功:', res); try { const data = JSON.parse(res.data); console.log('语音对话响应数据:', data); if (data.code === 200) { // 根据后端实际返回结构提取字段 let aiResponse = null; let userText = null; let audioUrl = null; // 从 data.llmResult.response 提取AI回复 if (data.data && data.data.llmResult && data.data.llmResult.response) { aiResponse = data.data.llmResult.response; } // 从 data.sttResult.text 提取用户文本(语音转文字) if (data.data && data.data.sttResult && data.data.sttResult.text) { userText = data.data.sttResult.text; } // 从 data.ttsResult.audioPath 提取音频路径 if (data.data && data.data.ttsResult && data.data.ttsResult.audioPath) { audioUrl = data.data.ttsResult.audioPath; } // 备用字段提取(保持向后兼容) if (!aiResponse) { if (data.response && typeof data.response === 'string') { aiResponse = data.response; } else if (data.data && data.data.response) { aiResponse = data.data.response; } } if (!userText) { userText = data.userText || data.data?.userText || data.data?.text || data.data?.user_text || data.data?.recognizedText || data.data?.transcription; } if (!audioUrl) { audioUrl = data.audioPath || data.audioUrl || data.data?.audioUrl || data.data?.url || data.data?.audio_url || data.data?.speechUrl || data.data?.ttsUrl || data.data?.audioPath; } // 清理AI回复文本 const cleanedAiResponse = cleanText(aiResponse); console.log('原始AI回复:', aiResponse); console.log('清理后AI回复:', cleanedAiResponse); resolve({ success: true, data: { userText: userText, aiResponse: cleanedAiResponse, audioUrl: audioUrl } }); } else { resolve({ success: false, error: { message: data.message || '语音对话失败' } }); } } catch (parseError) { console.error('解析语音对话响应失败:', parseError); resolve({ success: false, error: { message: '解析响应失败' } }); } }, fail: (err) => { console.error('语音对话上传失败:', err); resolve({ success: false, error: err }); } }); }); } catch (error) { console.error('语音对话失败:', error); return { success: false, error: error }; } }, // 4. 音频文件上传语音对话(前端发送aac格式,后端转换为wav处理) uploadVoiceChat: async (filePath, options = {}) => { try { const loginStatus = checkLoginStatus(); // 如果用户未登录,直接返回失败,让前端使用降级处理 if (!loginStatus.isLoggedIn) { console.log('用户未登录,上传音频文件语音对话跳过API调用'); return { success: false, error: { message: '用户未登录,使用降级处理' } }; } console.log('开始上传音频文件语音对话,文件路径:', filePath); console.log('注意:前端发送aac格式音频,后端需要转换为wav格式进行处理'); // 构建认证头 let authHeader = ''; if (loginStatus.token) { authHeader = loginStatus.token.startsWith('Bearer ') ? loginStatus.token : 'Bearer ' + loginStatus.token; } return new Promise((resolve) => { uni.uploadFile({ url: getApiUrl(API_CONFIG.ENDPOINTS.UPLOAD_VOICE_CHAT), filePath: filePath, name: 'audio', header: authHeader ? { 'Authorization': authHeader } : {}, formData: { modelId: options.modelId || null, templateId: options.templateId || null, voiceStyle: options.voiceStyle || 'default' }, success: (res) => { console.log('上传音频文件语音对话成功:', res); try { const data = JSON.parse(res.data); console.log('上传音频文件语音对话响应数据:', data); if (data.code === 200) { // 根据后端实际返回结构提取字段 let aiResponse = null; let userText = null; let audioUrl = null; // 从 data.llmResult.response 提取AI回复 if (data.data && data.data.llmResult && data.data.llmResult.response) { aiResponse = data.data.llmResult.response; } // 从 data.sttResult.text 提取用户文本(语音转文字) if (data.data && data.data.sttResult && data.data.sttResult.text) { userText = data.data.sttResult.text; } // 从 data.ttsResult.audioPath 提取音频路径 if (data.data && data.data.ttsResult && data.data.ttsResult.audioPath) { audioUrl = data.data.ttsResult.audioPath; } // 备用字段提取(保持向后兼容) if (!aiResponse) { if (data.response && typeof data.response === 'string') { aiResponse = data.response; } else if (data.data && data.data.response) { aiResponse = data.data.response; } } if (!userText) { userText = data.userText || data.data?.userText || data.data?.text || data.data?.user_text || data.data?.recognizedText || data.data?.transcription; } if (!audioUrl) { audioUrl = data.audioPath || data.audioUrl || data.data?.audioUrl || data.data?.url || data.data?.audio_url || data.data?.speechUrl || data.data?.ttsUrl || data.data?.audioPath; } // 清理AI回复文本 const cleanedAiResponse = cleanText(aiResponse); console.log('原始AI回复:', aiResponse); console.log('清理后AI回复:', cleanedAiResponse); resolve({ success: true, data: { userText: userText, aiResponse: cleanedAiResponse, audioUrl: audioUrl } }); } else { resolve({ success: false, error: { message: data.message || '上传音频文件语音对话失败' } }); } } catch (parseError) { console.error('解析上传音频文件语音对话响应失败:', parseError); resolve({ success: false, error: { message: '解析响应失败' } }); } }, fail: (err) => { console.error('上传音频文件语音对话失败:', err); resolve({ success: false, error: err }); } }); }); } catch (error) { console.error('上传音频文件语音对话失败:', error); return { success: false, error: error }; } }, // 兼容性方法:语音识别(降级处理) speechToText: async (filePath) => { console.warn('speechToText 方法已废弃,请使用 voiceChat 或 uploadVoiceChat'); // 降级处理:使用语音对话接口 const result = await voiceAPI.voiceChat(filePath); if (result.success) { return { success: true, data: result.data.userText }; } return result; } }; // 充值相关API export const rechargeAPI = { // 获取用户余额 getUserBalance: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.RECHARGE_BALANCE, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取用户余额失败:', error); return { success: false, error: error }; } }, // 创建充值订单 createRechargeOrder: async (orderData) => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.RECHARGE_CREATE_ORDER, method: 'POST', data: { amount: orderData.amount, paymentMethod: orderData.paymentMethod || 'wechat', bonusAmount: 0 // 已取消赠送服务 } }); return { success: true, data: response }; } catch (error) { console.error('创建充值订单失败:', error); return { success: false, error: error }; } }, // 查询订单状态 getOrderStatus: async (orderId) => { try { const response = await request({ url: `${API_CONFIG.ENDPOINTS.RECHARGE_ORDER_STATUS}/${orderId}`, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('查询订单状态失败:', error); return { success: false, error: error }; } }, // 获取充值记录 getRechargeHistory: async (params = {}) => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.RECHARGE_HISTORY, method: 'GET', data: { page: params.page || 1, pageSize: params.pageSize || 10, startDate: params.startDate, endDate: params.endDate } }); return { success: true, data: response }; } catch (error) { console.error('获取充值记录失败:', error); return { success: false, error: error }; } } }; // 角色相关API export const roleAPI = { // 获取角色列表 getRoles: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.ROLE_QUERY, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取角色列表失败:', error); return { success: false, error: error }; } }, // 获取角色详情 getRoleById: async (roleId) => { try { const response = await request({ url: `${API_CONFIG.ENDPOINTS.ROLE_DETAIL}?roleId=${roleId}`, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取角色详情失败:', error); return { success: false, error: error }; } } }; // 配置相关API export const configAPI = { // 获取所有配置 getAllConfigs: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CONFIG_QUERY, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取所有配置失败:', error); return { success: false, error: error }; } }, // 获取LLM模型配置 getModels: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CONFIG_MODELS, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取LLM模型配置失败:', error); return { success: false, error: error }; } }, // 获取STT配置 getSTTConfigs: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CONFIG_STT, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取STT配置失败:', error); return { success: false, error: error }; } }, // 获取模板配置 getTemplates: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CONFIG_TEMPLATES, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取模板配置失败:', error); return { success: false, error: error }; } }, // 获取TTS配置 getTTSConfigs: async () => { try { const response = await request({ url: API_CONFIG.ENDPOINTS.CONFIG_TTS, method: 'GET' }); return { success: true, data: response }; } catch (error) { console.error('获取TTS配置失败:', error); return { success: false, error: error }; } } }; // 导出默认请求方法 export default request;