import {defHttp} from "@/utils/http/axios"; import {useMessage} from '/@/hooks/web/useMessage'; import dayjs from "dayjs"; enum Api { // 运行流程(需要保存) run = '/airag/flow/run', // 调试流程(不需要保存) debug = '/airag/flow/debug', // invoke = '/airag/flow/invoke', // 获取流程列表 list = '/airag/flow/list', // 添加流程 add = '/airag/flow/add', // 编辑流程(不包含design数据) edit = '/airag/flow/edit', // 仅保存流程设计 designSave = '/airag/flow/design/save', // 通过id删除流程 deleteById = '/airag/flow/delete', // 批量删除流程 deleteBatch = '/airag/flow/deleteBatch', // 获取子流程列表(包括入参) subflowList = '/airag/flow/subflowList', // 根据ID获取单个子流程 querySubflowById = '/airag/flow/querySubflowById', } const {createMessage: $message} = useMessage(); // 运行流程超时时间(5分钟) const runTimeout = 5 * 60 * 1000; /** * 阻塞式运行流程 * @param flowId * @param inputParams */ export async function blockRun(flowId: string, inputParams: Recordable) { return defHttp.post({ url: Api.run, params: { flowId: flowId, inputParams: inputParams, responseMode: 'blocking' }, timeout: runTimeout, }); } /** * 流式运行流程 // * @param flowId NY5LzSY2VW1BSthYSnJArCFqbgwtZqSuyPQ/OD1n1twWJGU2RN/wkzf+kBVO5DztN85Ca9keeuaRAiwcatr8N0M15+Wv2SmRw82lMwawE2naX5tpJMpkxrUhUUcjnC+BSBL4+PV2JUXBFW8/oOG8HLqYvmPxoP7MMBhMi9D7lRY= * @param flowRecord * @param inputParams */ // export function createStreamRun(flowId: string, inputParams: Recordable) { export function createStreamRun(/*flowId: string, */ flowRecord: Recordable, inputParams: Recordable) { type RunEventType = 'FLOW_STARTED' | 'NODE_STARTED' | 'NODE_FINISHED' | 'FLOW_FINISHED' | 'MESSAGE'; const logColor = (text: string, color: string) => [`%c${text}`, `color: ${color};`]; const lcInfo = () => logColor('[INFO]', '#2196F3'); const lcWarn = () => logColor('[WARN]', '#FFC107'); const lcError = () => logColor('[ERRO]', '#F44336'); const colorTextRun = logColor('[stream-run]', '#999999'); const debugStreamRun = (lcFn: Fn, ...args: any[]) => { const colorTextType: string[] = lcFn(); const colorText = [ `${colorTextRun[0]} ${colorTextType[0]}`, colorTextRun[1], colorTextType[1], ]; const dateText = dayjs().format('HH:mm:ss.SSS'); console.debug(...colorText, `[${dateText}]`, ...args); }; // 记录解析失败的数据 let failChunkText = ''; // 发送请求 async function send() { const readableStream = await defHttp.post({ url: Api.debug, params: { // flowId: flowId, flow: flowRecord, inputParams: inputParams, responseMode: 'streaming', }, adapter: 'fetch', responseType: 'stream', timeout: runTimeout, }, { isTransformResponse: false, }); const reader = readableStream.getReader(); const decoder = new TextDecoder(); failChunkText = ''; while (true) { const {done, value} = await reader.read(); if (done) { break; } const chunkText = decoder.decode(value, {stream: true}); try { debugStreamRun(lcInfo, `收到 chunkText:`, {chunkText}); handleChunkText(chunkText); } catch (error) { console.error('Error parsing update:', error); } } } /** * 处理 chunkText * @param chunkText */ function handleChunkText(chunkText: string) { if (!chunkText) { debugStreamRun(lcError, 'chunkText 为空:', {chunkText}); return; } // 如果包含解析失败的数据,则合并再解析 const hasFailChunkText = failChunkText.length > 0; if (hasFailChunkText) { chunkText = failChunkText + chunkText; debugStreamRun(lcInfo, '合并解析失败的数据:', {chunkText}); failChunkText = ''; } let hasFailChunk = false; const chunks = chunkText.split('\n').flatMap((chunk: string) => { chunk = chunk ? chunk.trim() : ''; if (!chunk) { return [] } if (chunk.startsWith('data:')) { chunk = chunk.slice(5); } if (!chunk) { debugStreamRun(lcError, 'chunk 为空:', {chunk, chunkText}); return []; } try { return [ JSON.parse(chunk) ]; } catch (e) { hasFailChunk = true; debugStreamRun(lcError, 'chunk 解析失败:', {chunk, chunkText}); console.error(e); } return []; }); if (hasFailChunk) { // 解析失败是因为数据量太大,一次性传输的 chunk 不完整,需要合并再解析 // 记录解析失败的数据 failChunkText += chunkText; } chunks.forEach(handleChunkData); } const cbMap = new Map(); const chunkHandled = new Set(); function handleChunkData(data: { data: Recordable, event: RunEventType, flowId?: string, requestId: string, success?: boolean, message?: string, }) { //update-begin---author:wangshuai---date:2025-03-25---for:【QQYUN-11724】调试流程时,如果直接失败,调试界面会卡主--- if (data.success == false) { let cb = cbMap.get("FLOW_FINISHED"); if (typeof cb === 'function') { cb(data); return; } } //update-end---author:wangshuai---date:2025-03-25---for:【QQYUN-11724】调试流程时,如果直接失败,调试界面会卡主--- const key = `${data.event}-${data.data?.id || data.data?.fromNodeId || ''}`; if (chunkHandled.has(key)) { debugStreamRun(lcWarn, 'chunk 重复执行:', {key, data}); return; } chunkHandled.add(key); const cb = cbMap.get(data.event); if (typeof cb === 'function') { debugStreamRun(lcInfo, ` ------ 处理 ${data.event} 事件:`, {key, data}); cb(data.data, data); } else { debugStreamRun(lcWarn, `${data.event} 事件对应的回调不存在:`, {key, data}); } } function setCB(type: RunEventType, cb: Fn) { cbMap.set(type, cb); } return { run: send, onFlowStarted: (cb: Fn) => setCB('FLOW_STARTED', cb), onFlowFinished: (cb: Fn) => setCB('FLOW_FINISHED', cb), onNodeStarted: (cb: Fn) => setCB('NODE_STARTED', cb), onNodeFinished: (cb: Fn) => setCB('NODE_FINISHED', cb), onMessage: (cb: Fn) => setCB('MESSAGE', cb), } } export async function getProcessList(params?: any) { return defHttp.get({url: Api.list, params}); } export async function addProcess(data: Recordable, opt?: Recordable) { const silent = opt?.silent ?? false; return defHttp.post({url: Api.add, params: data}, { successMessageMode: silent ? 'none' : 'success', }); } export async function updateProcess(data: Recordable) { return defHttp.put({url: Api.edit, params: data}); } // 发布流程 export async function releaseProcess(flowId: string, un = false) { const msg = un ? '取消发布' : '发布' const res = await defHttp.put({ url: Api.edit, params: { id: flowId, status: un ? 'enable' : 'release', } }, {successMessageMode: 'none', isTransformResponse: false}); if (res.success) { $message.success(`${msg}成功`) return true; } $message.warn(res.message || `${msg}失败`) return false; } export async function updateDesign(data: Recordable, opt?: Recordable) { const silent = opt?.silent ?? false; return defHttp.put({ url: Api.designSave, params: { id: data.id, name: data.name, chain: data.chain, design: data.design, } }, { successMessageMode: silent ? 'none' : 'success', }); } export async function deleteProcess(id: string) { return defHttp.delete({ url: Api.deleteById, data: {id} }, { joinParamsToUrl: true }); } export async function deleteBatchProcess(idList: string[]) { return defHttp.delete({ url: Api.deleteBatch, data: { ids: idList.join(',') } }, { joinParamsToUrl: true }); } export async function querySubflowList( params: Recordable, ) { return defHttp.get({ url: Api.subflowList, params: params, }); } export async function querySubflowById(subflowId: string) { return defHttp.get({ url: Api.querySubflowById, params: {subflowId} }); }