ソースを参照

feat(runtime): global variable (#823)

Louis Young 4 ヶ月 前
コミット
c386683f5e

+ 1 - 3
apps/demo-free-layout/src/components/testrun/node-status-bar/group/index.module.less

@@ -16,15 +16,13 @@
     transition: transform 0.2s;
     transition: transform 0.2s;
     cursor: pointer;
     cursor: pointer;
     margin-right: 4px;
     margin-right: 4px;
-    opacity: 0;
 
 
     &-expanded {
     &-expanded {
       transform: rotate(0deg);
       transform: rotate(0deg);
-      opacity: 1;
     }
     }
   }
   }
 
 
   &-tag {
   &-tag {
     margin-left: 4px;
     margin-left: 4px;
   }
   }
-}
+}

+ 2 - 1
packages/runtime/interface/src/runtime/state/index.ts

@@ -9,6 +9,7 @@ import {
   WorkflowVariableType,
   WorkflowVariableType,
   IFlowTemplateValue,
   IFlowTemplateValue,
   IJsonSchema,
   IJsonSchema,
+  WorkflowSchema,
 } from '@schema/index';
 } from '@schema/index';
 import { IVariableParseResult, IVariableStore } from '../variable';
 import { IVariableParseResult, IVariableStore } from '../variable';
 import { INode } from '../document';
 import { INode } from '../document';
@@ -17,7 +18,7 @@ import { WorkflowInputs, WorkflowOutputs } from '../base';
 export interface IState {
 export interface IState {
   id: string;
   id: string;
   variableStore: IVariableStore;
   variableStore: IVariableStore;
-  init(): void;
+  init(schema?: WorkflowSchema): void;
   dispose(): void;
   dispose(): void;
   getNodeInputs(node: INode): WorkflowInputs;
   getNodeInputs(node: INode): WorkflowInputs;
   setNodeOutputs(params: { node: INode; outputs: WorkflowOutputs }): void;
   setNodeOutputs(params: { node: INode; outputs: WorkflowOutputs }): void;

+ 2 - 2
packages/runtime/interface/src/runtime/variable/index.ts

@@ -30,14 +30,14 @@ export interface IVariableStore {
     params: {
     params: {
       nodeID: string;
       nodeID: string;
       key: string;
       key: string;
-      value: Object;
+      value: unknown;
     } & VariableTypeInfo
     } & VariableTypeInfo
   ): void;
   ): void;
   setValue(params: {
   setValue(params: {
     nodeID: string;
     nodeID: string;
     variableKey: string;
     variableKey: string;
     variablePath?: string[];
     variablePath?: string[];
-    value: Object;
+    value: unknown;
   }): void;
   }): void;
   getValue<T = unknown>(params: {
   getValue<T = unknown>(params: {
     nodeID: string;
     nodeID: string;

+ 74 - 0
packages/runtime/js-core/src/domain/__tests__/schemas/global-variable.test.ts

@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { describe, expect, it } from 'vitest';
+import { IContainer, IEngine, WorkflowStatus } from '@flowgram.ai/runtime-interface';
+
+import { snapshotsToVOData } from '../utils';
+import { WorkflowRuntimeContainer } from '../../container';
+import { TestSchemas } from '.';
+
+const container: IContainer = WorkflowRuntimeContainer.instance;
+
+describe('WorkflowRuntime global variable schema', () => {
+  it('should execute a workflow', async () => {
+    const engine = container.get<IEngine>(IEngine);
+    const { context, processing } = engine.invoke({
+      schema: TestSchemas.globalVariableSchema,
+      inputs: {},
+    });
+    expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Processing);
+    const result = await processing;
+    expect(context.statusCenter.workflow.status).toBe(WorkflowStatus.Succeeded);
+    expect(result).toStrictEqual({
+      g_str: 'ABC',
+      g_num: 123.123,
+      g_int: 123,
+      g_bool: false,
+      g_obj: {
+        key_str: 'value',
+        key_int: 123,
+        key_bool: true,
+      },
+      g_map: {
+        key: 'value',
+      },
+      g_arr_str: ['AAA', 'BBB', 'CCC'],
+      g_date: '2000-01-01T00:00:00.000Z',
+    });
+    const snapshots = snapshotsToVOData(context.snapshotCenter.exportAll());
+    expect(snapshots).toStrictEqual([
+      { nodeID: 'start_0', inputs: {}, outputs: {}, data: {} },
+      {
+        nodeID: 'end_0',
+        inputs: {
+          g_str: 'ABC',
+          g_num: 123.123,
+          g_int: 123,
+          g_bool: false,
+          g_obj: { key_str: 'value', key_int: 123, key_bool: true },
+          g_arr_str: ['AAA', 'BBB', 'CCC'],
+          g_map: { key: 'value' },
+          g_date: '2000-01-01T00:00:00.000Z',
+        },
+        outputs: {
+          g_str: 'ABC',
+          g_num: 123.123,
+          g_int: 123,
+          g_bool: false,
+          g_obj: { key_str: 'value', key_int: 123, key_bool: true },
+          g_arr_str: ['AAA', 'BBB', 'CCC'],
+          g_map: { key: 'value' },
+          g_date: '2000-01-01T00:00:00.000Z',
+        },
+        data: {},
+      },
+    ]);
+    const report = context.reporter.export();
+    expect(report.workflowStatus.status).toBe(WorkflowStatus.Succeeded);
+    expect(report.reports.start_0.status).toBe(WorkflowStatus.Succeeded);
+    expect(report.reports.end_0.status).toBe(WorkflowStatus.Succeeded);
+  });
+});

+ 179 - 0
packages/runtime/js-core/src/domain/__tests__/schemas/global-variable.ts

@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import type { WorkflowSchema } from '@flowgram.ai/runtime-interface';
+
+export const globalVariableSchema: WorkflowSchema = {
+  nodes: [
+    {
+      id: 'start_0',
+      type: 'start',
+      meta: {
+        position: {
+          x: 180,
+          y: 22.5,
+        },
+      },
+      data: {
+        title: 'Start',
+        outputs: {
+          type: 'object',
+          properties: {},
+          required: [],
+        },
+      },
+    },
+    {
+      id: 'end_0',
+      type: 'end',
+      meta: {
+        position: {
+          x: 640,
+          y: 0,
+        },
+      },
+      data: {
+        title: 'End',
+        inputsValues: {
+          g_str: {
+            type: 'ref',
+            content: ['global', 'str'],
+          },
+          g_num: {
+            type: 'ref',
+            content: ['global', 'num'],
+          },
+          g_int: {
+            type: 'ref',
+            content: ['global', 'int'],
+          },
+          g_bool: {
+            type: 'ref',
+            content: ['global', 'bool'],
+          },
+          g_obj: {
+            type: 'ref',
+            content: ['global', 'obj'],
+          },
+          g_arr_str: {
+            type: 'ref',
+            content: ['global', 'arr_str'],
+          },
+          g_map: {
+            type: 'ref',
+            content: ['global', 'map'],
+          },
+          g_date: {
+            type: 'ref',
+            content: ['global', 'date'],
+          },
+        },
+        inputs: {
+          type: 'object',
+          properties: {
+            g_str: {
+              type: 'string',
+            },
+            g_num: {
+              type: 'number',
+            },
+            g_int: {
+              type: 'integer',
+            },
+            g_bool: {
+              type: 'boolean',
+            },
+            g_obj: {
+              type: 'object',
+              required: [],
+              properties: {
+                key_str: {
+                  type: 'string',
+                },
+                key_int: {
+                  type: 'integer',
+                },
+                key_bool: {
+                  type: 'boolean',
+                },
+              },
+            },
+            g_arr_str: {
+              type: 'array',
+              items: {
+                type: 'string',
+              },
+            },
+            g_map: {
+              type: 'map',
+            },
+            g_date: {
+              type: 'date-time',
+            },
+          },
+        },
+      },
+    },
+  ],
+  edges: [
+    {
+      sourceNodeID: 'start_0',
+      targetNodeID: 'end_0',
+    },
+  ],
+  globalVariable: {
+    type: 'object',
+    required: [],
+    properties: {
+      str: {
+        type: 'string',
+        default: 'ABC',
+      },
+      num: {
+        type: 'number',
+        default: 123.123,
+      },
+      int: {
+        type: 'integer',
+        default: 123,
+      },
+      bool: {
+        type: 'boolean',
+        default: false,
+      },
+      obj: {
+        type: 'object',
+        required: [],
+        properties: {
+          key_str: {
+            type: 'string',
+          },
+          key_int: {
+            type: 'integer',
+          },
+          key_bool: {
+            type: 'boolean',
+          },
+        },
+        default: '{"key_str": "value","key_int": 123,"key_bool": true}',
+      },
+      arr_str: {
+        type: 'array',
+        items: {
+          type: 'string',
+        },
+        default: '["AAA", "BBB", "CCC"]',
+      },
+      map: {
+        type: 'map',
+        default: '{"key": "value"}',
+      },
+      date: {
+        type: 'date-time',
+        default: '2000-01-01T00:00:00.000Z',
+      },
+    },
+  },
+};

+ 2 - 0
packages/runtime/js-core/src/domain/__tests__/schemas/index.ts

@@ -10,6 +10,7 @@ import { loopBreakContinueSchema } from './loop-break-continue';
 import { loopSchema } from './loop';
 import { loopSchema } from './loop';
 import { llmRealSchema } from './llm-real';
 import { llmRealSchema } from './llm-real';
 import { httpSchema } from './http';
 import { httpSchema } from './http';
+import { globalVariableSchema } from './global-variable';
 import { endConstantSchema } from './end-constant';
 import { endConstantSchema } from './end-constant';
 import { codeSchema } from './code';
 import { codeSchema } from './code';
 import { branchTwoLayersSchema } from './branch-two-layers';
 import { branchTwoLayersSchema } from './branch-two-layers';
@@ -29,4 +30,5 @@ export const TestSchemas = {
   codeSchema,
   codeSchema,
   endConstantSchema,
   endConstantSchema,
   startDefaultSchema,
   startDefaultSchema,
+  globalVariableSchema,
 };
 };

+ 1 - 1
packages/runtime/js-core/src/domain/context/index.ts

@@ -70,7 +70,7 @@ export class WorkflowRuntimeContext implements IContext {
     this.cache.init();
     this.cache.init();
     this.document.init(schema);
     this.document.init(schema);
     this.variableStore.init();
     this.variableStore.init();
-    this.state.init();
+    this.state.init(schema);
     this.ioCenter.init(inputs);
     this.ioCenter.init(inputs);
     this.snapshotCenter.init();
     this.snapshotCenter.init();
     this.statusCenter.init();
     this.statusCenter.init();

+ 25 - 1
packages/runtime/js-core/src/domain/state/index.ts

@@ -16,6 +16,7 @@ import {
   WorkflowVariableType,
   WorkflowVariableType,
   IFlowTemplateValue,
   IFlowTemplateValue,
   IJsonSchema,
   IJsonSchema,
+  WorkflowSchema,
 } from '@flowgram.ai/runtime-interface';
 } from '@flowgram.ai/runtime-interface';
 
 
 import { uuid, WorkflowRuntimeType } from '@infra/utils';
 import { uuid, WorkflowRuntimeType } from '@infra/utils';
@@ -29,7 +30,8 @@ export class WorkflowRuntimeState implements IState {
     this.id = uuid();
     this.id = uuid();
   }
   }
 
 
-  public init(): void {
+  public init(schema?: WorkflowSchema): void {
+    this.setGlobalVariable(schema?.globalVariable);
     this.executedNodes = new Set();
     this.executedNodes = new Set();
   }
   }
 
 
@@ -207,4 +209,26 @@ export class WorkflowRuntimeState implements IState {
     }
     }
     return jsonContent as T;
     return jsonContent as T;
   }
   }
+
+  private setGlobalVariable(globalVariableDeclare: IJsonSchema | undefined): void {
+    if (globalVariableDeclare?.type !== 'object' || !globalVariableDeclare.properties) {
+      return;
+    }
+    Object.entries(globalVariableDeclare.properties).forEach(([key, typeInfo]) => {
+      if (!key || !typeInfo) {
+        return;
+      }
+      const type = typeInfo.type as WorkflowVariableType;
+      const itemsType = typeInfo.items?.type as WorkflowVariableType;
+      const defaultValue = this.parseJSONContent(typeInfo.default, type);
+      // create variable
+      this.variableStore.setVariable({
+        nodeID: 'global',
+        key,
+        value: defaultValue,
+        type,
+        itemsType,
+      });
+    });
+  }
 }
 }