feat(flows): 新增流程编辑器基础功能与相关组件
feat(backend): 添加流程模型与服务支持 feat(frontend): 实现流程编辑器UI与交互 feat(assets): 添加流程节点图标资源 feat(plugins): 实现上下文菜单和运行时插件 feat(components): 新增基础节点和侧边栏组件 feat(routes): 添加流程相关路由配置 feat(models): 创建流程和运行日志数据模型 feat(services): 实现流程服务层逻辑 feat(migration): 添加流程相关数据库迁移 feat(config): 更新前端配置支持流程编辑器 feat(utils): 增强axios错误处理和工具函数
This commit is contained in:
99
frontend/src/flows/services/custom-service.ts
Normal file
99
frontend/src/flows/services/custom-service.ts
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { injectable, inject } from '@flowgram.ai/free-layout-editor';
|
||||
import {
|
||||
FreeLayoutPluginContext,
|
||||
SelectionService,
|
||||
Playground,
|
||||
WorkflowDocument,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
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';
|
||||
|
||||
interface RunResult { ok: boolean; ctx: any; logs: string[] }
|
||||
|
||||
// 兼容 BrowserRouter 与 HashRouter:优先从 search 获取,若无则从 hash 的查询串中获取
|
||||
function getFlowIdFromUrl(): string {
|
||||
const searchId = new URLSearchParams(window.location.search).get('id');
|
||||
if (searchId) return searchId;
|
||||
const hash = window.location.hash || '';
|
||||
const qIndex = hash.indexOf('?');
|
||||
if (qIndex >= 0) {
|
||||
const qs = hash.substring(qIndex + 1);
|
||||
const hashId = new URLSearchParams(qs).get('id');
|
||||
if (hashId) return hashId;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class CustomService {
|
||||
@inject(FreeLayoutPluginContext) ctx!: FreeLayoutPluginContext;
|
||||
|
||||
@inject(SelectionService) selectionService!: SelectionService;
|
||||
|
||||
@inject(Playground) playground!: Playground;
|
||||
|
||||
@inject(WorkflowDocument) document!: WorkflowDocument;
|
||||
|
||||
// 新增可选参数,用于静默保存时不弹出 Toast,并返回是否保存成功
|
||||
async save(opts?: { silent?: boolean }): Promise<boolean> {
|
||||
const silent = !!opts?.silent;
|
||||
try {
|
||||
const id = getFlowIdFromUrl();
|
||||
if (!id) {
|
||||
if (!silent) Toast.error(I18n.t('Flow ID is missing, cannot save'));
|
||||
return false;
|
||||
}
|
||||
const json = this.document.toJSON() as any;
|
||||
const yaml = stringifyFlowDoc(json);
|
||||
const design_json = JSON.stringify(json);
|
||||
const { data } = await api.put<ApiResp<{ saved: boolean }>>(`/flows/${id}`, { yaml, design_json });
|
||||
if (data?.code === 0) {
|
||||
if (!silent) Toast.success(I18n.t('Saved'));
|
||||
try {
|
||||
const key = (() => {
|
||||
const hash = window.location.hash || '';
|
||||
if (hash.startsWith('#/')) {
|
||||
return hash.slice(1);
|
||||
}
|
||||
return window.location.pathname + (window.location.search || '');
|
||||
})();
|
||||
window.dispatchEvent(new CustomEvent('flows:doc-dirty', { detail: { key, dirty: false } }));
|
||||
} catch {}
|
||||
return true;
|
||||
} else {
|
||||
const msg = data?.message || I18n.t('Save failed');
|
||||
if (!silent) Toast.error(msg);
|
||||
return false;
|
||||
}
|
||||
} catch (e: any) {
|
||||
const msg = e?.message || I18n.t('Save failed');
|
||||
if (!silent) Toast.error(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async run(input: any = {}) {
|
||||
try {
|
||||
const id = getFlowIdFromUrl();
|
||||
if (!id) {
|
||||
Toast.error(I18n.t('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'));
|
||||
} catch (e: any) {
|
||||
Toast.error(e?.message || I18n.t('Run failed'));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user