From 97f6971bd01f493ffec90844781fa1a1cadc59fc Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 11 Nov 2025 09:10:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=B4=E6=97=B6=E4=BF=9D=E5=AD=98=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/ai-service.js | 356 ++++++++++++++++++ common/js/config.js | 11 +- .../ai-chat-message/ai-chat-message.vue | 210 ++++++++++- components/hover-nav/hover-nav.vue | 18 +- lang/zh-cn/ai/ai-chat.js | 4 + manifest.json | 2 +- pages.json | 7 - pages_tool/ai-chat/index.vue | 20 +- scripts/iconfontcss-generate-preview.js | 57 +++ 9 files changed, 644 insertions(+), 41 deletions(-) create mode 100644 common/js/ai-service.js create mode 100644 lang/zh-cn/ai/ai-chat.js create mode 100644 scripts/iconfontcss-generate-preview.js diff --git a/common/js/ai-service.js b/common/js/ai-service.js new file mode 100644 index 0000000..7e3c6f2 --- /dev/null +++ b/common/js/ai-service.js @@ -0,0 +1,356 @@ +import http from './http.js' +import store from '@/store/index.js' + +export default { + /** + * 发送消息到Dify API + * @param {string} message 用户消息内容 + * @param {Object} options 配置选项 + * @returns {Promise} + */ + async sendMessage(message, options = {}) { + try { + // 获取AI配置 + const aiConfig = store.getters.globalAIAgentConfig + + // 构建Dify API请求参数 + const params = { + url: '/api/ai/chat', // 后端代理接口 + data: { + message: message, + conversation_id: options.conversationId || this.generateConversationId(), + user_id: store.state.memberInfo?.id || 'anonymous', + stream: options.stream || false, // 是否流式响应 + // Dify API参数 + inputs: {}, + query: message, + response_mode: options.stream ? 'streaming' : 'blocking', + user: store.state.memberInfo?.id || 'anonymous' + }, + header: { + 'Content-Type': 'application/json' + } + } + + // 如果有Dify配置,添加API密钥 + if (aiConfig?.difyApiKey) { + params.header['Authorization'] = `Bearer ${aiConfig.difyApiKey}` + } + + if (aiConfig?.difyBaseUrl) { + params.header['X-Dify-Url'] = aiConfig.difyBaseUrl + } + + // 发送请求 + const response = await http.sendRequest({ + ...params, + async: false // 使用Promise方式 + }) + + return this.handleResponse(response, options) + + } catch (error) { + console.error('Dify API请求失败:', error) + throw new Error('AI服务暂时不可用,请稍后重试') + } + }, + + /** + * 流式消息处理 + * @param {string} message 用户消息 + * @param {Function} onChunk 流式数据回调 + * @param {Function} onComplete 完成回调 + */ + async sendStreamMessage(message, onChunk, onComplete) { + try { + const aiConfig = store.getters.globalAIAgentConfig + + // 检查配置 + if (!aiConfig?.difyBaseUrl || !aiConfig?.difyApiKey) { + throw new Error('未配置Dify服务') + } + + // 创建WebSocket连接或使用Server-Sent Events + if (aiConfig?.difyWsUrl) { + return this.connectWebSocket(message, onChunk, onComplete) + } else { + // 使用HTTP流式请求 + return this.sendHttpStream(message, onChunk, onComplete) + } + + } catch (error) { + console.error('流式消息发送失败:', error) + throw error + } + }, + + /** + * WebSocket连接 + */ + connectWebSocket(message, onChunk, onComplete) { + return new Promise((resolve, reject) => { + const aiConfig = store.getters.globalAIAgentConfig + const wsUrl = aiConfig.difyWsUrl + + if (!wsUrl) { + reject(new Error('未配置WebSocket地址')) + return + } + + // #ifdef H5 + const ws = new WebSocket(wsUrl) + + ws.onopen = () => { + // 发送消息 + ws.send(JSON.stringify({ + message: message, + user_id: store.state.memberInfo?.id || 'anonymous', + conversation_id: this.generateConversationId() + })) + } + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data) + if (data.type === 'chunk' && onChunk) { + onChunk(data.content) + } else if (data.type === 'complete' && onComplete) { + onComplete(data.content) + ws.close() + resolve(data.content) + } + } catch (e) { + console.error('WebSocket消息解析失败:', e) + } + } + + ws.onerror = (error) => { + console.error('WebSocket连接错误:', error) + reject(error) + } + + ws.onclose = () => { + console.log('WebSocket连接关闭') + } + // #endif + + // #ifdef MP-WEIXIN || APP-PLUS + // 小程序和APP使用uni.connectSocket + uni.connectSocket({ + url: wsUrl, + success: () => { + uni.onSocketOpen(() => { + uni.sendSocketMessage({ + data: JSON.stringify({ + message: message, + user_id: store.state.memberInfo?.id || 'anonymous', + conversation_id: this.generateConversationId() + }) + }) + }) + + uni.onSocketMessage((res) => { + try { + const data = JSON.parse(res.data) + if (data.type === 'chunk' && onChunk) { + onChunk(data.content) + } else if (data.type === 'complete' && onComplete) { + onComplete(data.content) + uni.closeSocket() + resolve(data.content) + } + } catch (e) { + console.error('WebSocket消息解析失败:', e) + } + }) + + uni.onSocketError((error) => { + console.error('WebSocket连接错误:', error) + reject(error) + }) + }, + fail: (error) => { + reject(error) + } + }) + // #endif + }) + }, + + /** + * HTTP流式请求 + */ + async sendHttpStream(message, onChunk, onComplete) { + const aiConfig = store.getters.globalAIAgentConfig + + const params = { + url: '/api/ai/chat-stream', + data: { + message: message, + conversation_id: this.generateConversationId(), + user_id: store.state.memberInfo?.id || 'anonymous', + stream: true + }, + header: { + 'Content-Type': 'application/json' + } + } + + if (aiConfig?.difyApiKey) { + params.header['Authorization'] = `Bearer ${aiConfig.difyApiKey}` + } + + // 使用fetch API进行流式请求(H5环境) + // #ifdef H5 + try { + const response = await fetch(`${aiConfig.difyBaseUrl}/chat-messages`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${aiConfig.difyApiKey}` + }, + body: JSON.stringify({ + inputs: {}, + query: message, + response_mode: 'streaming', + user: store.state.memberInfo?.id || 'anonymous' + }) + }) + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let content = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + const chunk = decoder.decode(value) + const lines = chunk.split('\n') + + for (const line of lines) { + if (line.startsWith('data: ')) { + try { + const data = JSON.parse(line.slice(6)) + if (data.event === 'text_message' && data.text) { + content += data.text + if (onChunk) onChunk(data.text) + } + } catch (e) { + // 忽略解析错误 + } + } + } + } + + if (onComplete) onComplete(content) + return content + + } catch (error) { + console.error('HTTP流式请求失败:', error) + throw error + } + // #endif + + // 非H5环境使用普通请求模拟流式效果 + // #ifndef H5 + const response = await http.sendRequest({ + ...params, + async: false + }) + + // 模拟流式效果 + if (response.success && response.data) { + const content = response.data + const chunkSize = 3 + let index = 0 + + const streamInterval = setInterval(() => { + if (index < content.length) { + const chunk = content.substring(index, index + chunkSize) + index += chunkSize + if (onChunk) onChunk(chunk) + } else { + clearInterval(streamInterval) + if (onComplete) onComplete(content) + } + }, 100) + } + // #endif + }, + + /** + * 处理API响应 + */ + handleResponse(response, options) { + if (response.code === 0 || response.success) { + return { + success: true, + content: response.data?.answer || response.data?.content || response.data, + conversationId: response.data?.conversation_id, + messageId: response.data?.message_id + } + } else { + throw new Error(response.message || 'AI服务返回错误') + } + }, + + /** + * 生成会话ID + */ + generateConversationId() { + return 'conv_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9) + }, + + /** + * 获取AI服务状态 + */ + async getServiceStatus() { + try { + const aiConfig = store.getters.globalAIAgentConfig + + if (!aiConfig?.difyBaseUrl || !aiConfig?.difyApiKey) { + return { + available: false, + reason: '未配置Dify服务' + } + } + + // 简单的健康检查 + const response = await http.sendRequest({ + url: '/api/ai/health', + async: false + }) + + return { + available: response.success, + reason: response.success ? '服务正常' : '服务异常' + } + + } catch (error) { + return { + available: false, + reason: '服务检查失败' + } + } + }, + + /** + * 清除会话历史 + */ + async clearConversation(conversationId) { + try { + const response = await http.sendRequest({ + url: '/api/ai/clear-conversation', + data: { conversation_id: conversationId }, + async: false + }) + + return response.success + + } catch (error) { + console.error('清除会话失败:', error) + return false + } + } +} \ No newline at end of file diff --git a/common/js/config.js b/common/js/config.js index e97d99d..b326786 100644 --- a/common/js/config.js +++ b/common/js/config.js @@ -14,16 +14,19 @@ try { // 调试版本,配置说明 const devCfg = { // 商户ID - uniacid: 460, //825 + uniacid: 926, //825 //api请求地址 - baseUrl: 'https://xcx30.5g-quickapp.com/', + baseUrl: 'https://xcx21.5g-quickapp.com/', + // baseUrl: 'http://localhost:8010/', // 图片域名 - imgDomain: 'https://xcx30.5g-quickapp.com/', + imgDomain: 'https://xcx21.5g-quickapp.com/', + //imgDomain: 'http://localhost:8010/', // H5端域名 - h5Domain: 'https://xcx30.5g-quickapp.com/', + h5Domain: 'https://xcx21.5g-quickapp.com/', + // h5Domain: 'http://localhost:8010/', // // api请求地址 // baseUrl: 'https://tsaas.liveplatform.cn/', diff --git a/components/ai-chat-message/ai-chat-message.vue b/components/ai-chat-message/ai-chat-message.vue index 0d7ada9..1ea4cde 100644 --- a/components/ai-chat-message/ai-chat-message.vue +++ b/components/ai-chat-message/ai-chat-message.vue @@ -277,6 +277,7 @@