'use strict'; const axios = require('axios'); const DOCUMENT_EXTENSIONS = ['pdf', 'doc', 'docx']; const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp']; class AiDify { /** * 构造函数 * @param {object} config * @param {string} config.apiKey - Dify API Key * @param {string} config.chatflowId - ChatFlow ID * @param {string} [config.baseUrl=https://api.dify.ai/v1] - Dify API 接口地址 */ constructor({ apiKey = 'app-fiJr1pNxjdKXpetko8ANawnY', chatflowId = '', baseUrl = 'http://workflow.smartcost.com.cn/v1' }) { if (!apiKey) { throw new Error('apiKey 不能为空'); } this.apiKey = apiKey; this.chatflowId = chatflowId; this.baseUrl = baseUrl; } /** * 向 ChatFlow 发送请求 * @param {string} userInput - 用户输入内容 * @param {string} [userId='user_123'] - 用户 ID * @returns {Promise} - Dify 返回的对象 */ async ask(userInput, userId = 'user_123') { const url = `${this.baseUrl}/chat-messages`; const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', }; const body = { inputs: {}, // 如果你的 ChatFlow 有 schema,可填写 key-value query: userInput, response_mode: 'blocking', user: userId, conversation_id: this.chatflowId, }; try { const res = await axios.post(url, body, { headers }); return res.data; } catch (err) { console.error('Dify 请求失败:', err.response || err.response.data || err.message); throw new Error('Dify 请求失败'); } } async workflows(body) { const url = `${this.baseUrl}/workflows/run`; const headers = { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json', }; try { const res = await axios.post(url, body, { headers }); return res.data.data.outputs; } catch (err) { console.error('Dify 请求失败:', err.response || err.response.data || err.message); throw new Error('Dify 请求失败'); } } async posCalcDetailRead(col, billsName, keyword, files, userId = `user_123`) { const body = { inputs: { header: col.map(x => { return x.title + (x.unit ? `(${x.unit})` : '') }).join(','), billsName, keyword, imgList: [], }, user: userId, }; for (const f of files) { body.inputs.imgList.push({ type: "image", transfer_method: "remote_url", url: f.viewpath, }) } return await this.workflows(body); } async imageTransExcel(files, userId = `user_123`) { const body = { inputs: { imgList: [] }, user: userId, response_mode: 'blocking', }; for (const f of files) { body.inputs.imgList.push({ type: "image", transfer_method: "remote_url", url: f.viewpath, }) } return await this.workflows(body); } async billsIdentify(name, unit, userId = `user_123`) { const body = { inputs: { billsName: name || '' + ( unit ? `(${unit})` : '') }, user: userId, response_mode: 'blocking', }; return await this.workflows(body); } /** * 根据文件名后缀判断 Dify 的 type 字段(image / document) */ resolveDifyFileType(filename = '') { const ext = filename ? filename.split('.').pop().toLowerCase() : ''; if (IMAGE_EXTENSIONS.includes(ext)) return 'image'; if (DOCUMENT_EXTENSIONS.includes(ext)) return 'document'; // 无法判断时默认按document处理,让Dify那边自行兜底 return 'document'; } async contractTrans(file, userId = 'user_123') { const body = { inputs: { fileList: [] }, user: userId, response_mode: 'blocking', }; if (!file.viewpath) { throw new Error('文件缺少viewpath字段'); } const fileType = this.resolveDifyFileType(file.filename); body.inputs.fileList.push({ type: fileType, transfer_method: 'remote_url', url: file.viewpath, }); return await this.workflows(body); } /** 查询助手专用 Chatflow;只返回解析结果,不将业务数据发送给模型。 */ async askAssistant({ question, path = '', user, conversationId = '' }) { let response; try { response = await axios.post(`${this.baseUrl.replace(/\/$/, '')}/chat-messages`, { inputs: { path }, query: question, user, conversation_id: conversationId, response_mode: 'blocking', auto_generate_name: false, }, { headers: { Authorization: `Bearer ${this.apiKey}` }, timeout: 110000, maxRedirects: 0, maxContentLength: 1024 * 1024, }); } catch (err) { // 不透传 Axios 错误对象,其中包含 Authorization 和用户问题。 const status = err.response && err.response.status; if (status === 401 || status === 403) throw new Error('查询助手 Dify 授权失败,请检查服务端配置'); if (status === 404 && conversationId) throw new Error('助手会话已失效,请清空对话后重试'); if (err.code === 'ECONNABORTED') throw new Error('查询助手响应超时,请稍后重试'); console.log(err); throw new Error('查询助手 Dify 调用失败,请检查已发布的 Chatflow 和服务地址'); } const data = response.data || {}; let result; try { // 只兼容包裹整个 JSON 的代码围栏,不猜测或修复损坏的参数。 const text = typeof data.answer === 'string' ? data.answer.trim() : ''; result = JSON.parse(text.replace(/^```(?:json)?\s*\n([\s\S]*?)\n```$/i, '$1')); } catch (err) { throw new Error('查询助手返回的 JSON 无法解析,请检查 Chatflow 的 Answer 配置'); } if (!result || result.version !== '1.1.0' || !['manual', 'business', 'capability'].includes(result.type) || !['answer', 'query', 'clarify', 'error'].includes(result.status) || typeof result.answer !== 'string' || !Array.isArray(result.missing_fields) || !Array.isArray(result.sources) || (result.status === 'query' && (result.type !== 'business' || !result.query || result.answer !== '' || result.missing_fields.length !== 0)) || (result.type !== 'business' && (result.query !== null || !['answer', 'error'].includes(result.status))) || (result.status !== 'query' && !result.answer.trim())) { throw new Error('查询助手返回格式不符合 1.1.0 协议,请检查 Chatflow 配置'); } if (typeof data.conversation_id !== 'string' || !/^[a-f0-9-]{36}$/i.test(data.conversation_id)) { throw new Error('查询助手未返回有效会话 ID'); } return { result, conversationId: data.conversation_id }; } async changeTrans(files, scene, extractScope = 'all', userId = 'user_123') { if (!['apply', 'plan', 'change'].includes(scene)) throw new Error('变更识别场景有误'); extractScope = extractScope || 'all'; if (!['all', 'main', 'items'].includes(extractScope)) throw new Error('变更识别范围有误'); if (!Array.isArray(files) || !files.length) throw new Error('请上传文件'); const body = { inputs: { fileList: [], scene, extract_scope: extractScope }, user: userId, response_mode: 'blocking', }; for (const file of files) { if (!file.viewpath) throw new Error('文件缺少viewpath字段'); body.inputs.fileList.push({ type: this.resolveDifyFileType(file.filename), transfer_method: 'remote_url', url: file.viewpath, }); } const result = await this.workflows(body); if (!result || !result.res) throw new Error('变更识别未返回有效结果'); return result; } } module.exports = AiDify;