feat(flow): 新增分组执行与异步模式支持

refactor(executors): 将 Rhai 引擎评估逻辑迁移至 script_rhai 模块
docs: 添加 Flow 架构文档与示例 JSON
feat(i18n): 新增前端多语言支持
perf(axios): 优化 token 刷新与 401 处理逻辑
style: 统一代码格式化与简化条件判断
This commit is contained in:
2025-12-03 20:51:22 +08:00
parent a1b21e87b3
commit 75c6974a35
20 changed files with 1830 additions and 299 deletions

View File

@ -11,6 +11,7 @@ import {
WorkflowDocument,
} from '@flowgram.ai/free-layout-editor';
import { Toast } from '@douyinfe/semi-ui';
import { tr } from '../../utils/i18n';
import { I18n } from '@flowgram.ai/free-layout-editor';
import api, { type ApiResp } from '../../utils/axios';
import { stringifyFlowDoc } from '../utils/yaml';
@ -73,22 +74,29 @@ export class CustomService {
@inject(WorkflowDocument) document!: WorkflowDocument;
// 新增可选参数,用于静默保存时不弹出 Toast并返回是否保存成功
async save(opts?: { silent?: boolean }): Promise<boolean> {
async save(opts?: { silent?: boolean; executionMode?: 'sync'|'async'|'queued'|'bounded'; concurrencyLimit?: number }): Promise<boolean> {
const silent = !!opts?.silent;
try {
const id = getFlowIdFromUrl();
if (!id) {
if (!silent) Toast.error(I18n.t('Flow ID is missing, cannot save'));
if (!silent) Toast.error(tr('Flow ID is missing, cannot save'));
return false;
}
const json = this.document.toJSON() as any;
// 在根级写入执行配置与后端契约保持一致executionMode/concurrencyLimit
if (opts?.executionMode) {
try { json.executionMode = opts.executionMode; } catch {}
}
if (typeof opts?.concurrencyLimit === 'number') {
try { json.concurrencyLimit = opts.concurrencyLimit; } catch {}
}
const yaml = stringifyFlowDoc(json);
// 使用转换后的 design_json以便后端根据语言选择正确的执行器
const designForBackend = transformDesignJsonForBackend(json);
const design_json = JSON.stringify(designForBackend);
const { data } = await api.put<ApiResp<{ saved: boolean }>>(`/flows/${id}`, { yaml, design_json });
if (data?.code === 0) {
if (!silent) Toast.success(I18n.t('Saved'));
if (!silent) Toast.success(tr('Saved'));
try {
const key = (() => {
const hash = window.location.hash || '';
@ -101,12 +109,12 @@ export class CustomService {
} catch {}
return true;
} else {
const msg = data?.message || I18n.t('Save failed');
const msg = data?.message || tr('Save failed');
if (!silent) Toast.error(msg);
return false;
}
} catch (e: any) {
const msg = e?.message || I18n.t('Save failed');
const msg = e?.message || tr('Save failed');
if (!silent) Toast.error(msg);
return false;
}
@ -116,16 +124,16 @@ export class CustomService {
try {
const id = getFlowIdFromUrl();
if (!id) {
Toast.error(I18n.t('Flow ID is missing, cannot run'));
Toast.error(tr('Flow ID is missing, cannot run'));
return null;
}
const { data } = await api.post<ApiResp<RunResult>>(`/flows/${id}/run`, { input });
if (data?.code === 0) {
return data.data;
}
throw new Error(data?.message || I18n.t('Run failed'));
throw new Error(data?.message || tr('Run failed'));
} catch (e: any) {
Toast.error(e?.message || I18n.t('Run failed'));
Toast.error(e?.message || tr('Run failed'));
return null;
}
}
@ -142,7 +150,7 @@ export class CustomService {
) {
const id = getFlowIdFromUrl();
if (!id) {
const err = new Error(I18n.t('Flow ID is missing, cannot run'));
const err = new Error(tr('Flow ID is missing, cannot run'));
handlers?.onFatal?.(err);
return { cancel: () => {}, done: Promise.resolve<RunResult | null>(null) } as const;
}