feat(flow): 新增流式执行模式与SSE支持

新增流式执行模式,通过SSE实时推送节点执行事件与日志
重构HTTP执行器与中间件,提取通用HTTP客户端组件
优化前端测试面板,支持流式模式切换与实时日志展示
更新依赖版本并修复密码哈希的随机数生成器问题
修复前端节点类型映射问题,确保Code节点表单可用
This commit is contained in:
2025-09-21 01:48:24 +08:00
parent 296f0ae9f6
commit dd7857940f
24 changed files with 1695 additions and 885 deletions

View File

@ -14,9 +14,16 @@ import { Toast } from '@douyinfe/semi-ui';
import { I18n } from '@flowgram.ai/free-layout-editor';
import api, { type ApiResp } from '../../utils/axios';
import { stringifyFlowDoc } from '../utils/yaml';
import { postSSE } from '../../utils/sse';
interface RunResult { ok: boolean; ctx: any; logs: string[] }
// 与后端 StreamEvent 保持一致serde(tag = "type")
export type StreamEvent =
| { type: 'node'; node_id?: string; ctx?: any; logs?: string[] }
| { type: 'done'; ok: boolean; ctx: any; logs: string[] }
| { type: 'error'; message: string };
// 兼容 BrowserRouter 与 HashRouter优先从 search 获取,若无则从 hash 的查询串中获取
function getFlowIdFromUrl(): string {
const searchId = new URLSearchParams(window.location.search).get('id');
@ -116,4 +123,40 @@ export class CustomService {
return null;
}
}
// 新增SSE 流式运行,返回取消函数与完成 Promise
runStream(input: any = {}, handlers?: { onNode?: (e: StreamEvent & { type: 'node' }) => void; onDone?: (e: StreamEvent & { type: 'done' }) => void; onError?: (e: StreamEvent & { type: 'error' }) => void; onFatal?: (err: Error) => void; }) {
const id = getFlowIdFromUrl();
if (!id) {
const err = new Error(I18n.t('Flow ID is missing, cannot run'));
handlers?.onFatal?.(err);
return { cancel: () => {}, done: Promise.resolve<RunResult | null>(null) } as const;
}
const base = (api.defaults.baseURL || '') as string;
const url = base ? `${base}/flows/${id}/run/stream` : `/flows/${id}/run/stream`;
const { cancel, done } = postSSE<RunResult | null>(url, { input }, {
onMessage: (json: any) => {
try {
const evt = json as StreamEvent
if (evt.type === 'node') {
handlers?.onNode?.(evt as any)
return undefined
}
if (evt.type === 'error') {
handlers?.onError?.(evt as any)
return undefined
}
if (evt.type === 'done') {
handlers?.onDone?.(evt as any)
return { ok: evt.ok, ctx: evt.ctx, logs: evt.logs }
}
} catch (_) {}
return undefined
},
onFatal: (e) => handlers?.onFatal?.(e),
})
return { cancel, done } as const;
}
}