mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
Compare commits
6 Commits
4a857ba9a3
...
f2a04c6219
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2a04c6219 | ||
|
|
e5c96344f1 | ||
|
|
2c4a42a772 | ||
|
|
f3a91a4efa | ||
|
|
b0ebe5634d | ||
|
|
1792e5f190 |
@ -99,10 +99,17 @@ export function useEditorProps(
|
|||||||
* 判断是否连线
|
* 判断是否连线
|
||||||
*/
|
*/
|
||||||
canAddLine(ctx, fromPort, toPort) {
|
canAddLine(ctx, fromPort, toPort) {
|
||||||
// not the same node
|
// Cannot be a self-loop on the same node / 不能是同一节点自循环
|
||||||
if (fromPort.node === toPort.node) {
|
if (fromPort.node === toPort.node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Cannot be in different loop containers - 不能在不同 Loop 容器
|
||||||
|
if (
|
||||||
|
toPort.node.parent?.flowNodeType === WorkflowNodeType.Loop &&
|
||||||
|
fromPort.node.parent?.id !== toPort.node.parent?.id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 线条环检测,不允许连接到前面的节点
|
* 线条环检测,不允许连接到前面的节点
|
||||||
* Line loop detection, which is not allowed to connect to the node in front of it
|
* Line loop detection, which is not allowed to connect to the node in front of it
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
|
import { FormRenderProps, FormMeta } from '@flowgram.ai/free-layout-editor';
|
||||||
import { provideJsonSchemaOutputs, syncVariableTitle } from '@flowgram.ai/form-materials';
|
|
||||||
import { Avatar } from '@douyinfe/semi-ui';
|
import { Avatar } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { FlowNodeJSON } from '../../typings';
|
import { FlowNodeJSON } from '../../typings';
|
||||||
@ -38,12 +37,4 @@ export const renderForm = ({ form }: FormRenderProps<FlowNodeJSON>) => (
|
|||||||
|
|
||||||
export const formMeta: FormMeta<FlowNodeJSON> = {
|
export const formMeta: FormMeta<FlowNodeJSON> = {
|
||||||
render: renderForm,
|
render: renderForm,
|
||||||
validateTrigger: ValidateTrigger.onChange,
|
|
||||||
validate: {
|
|
||||||
title: ({ value }: { value: string }) => (value ? undefined : 'Title is required'),
|
|
||||||
},
|
|
||||||
effect: {
|
|
||||||
title: syncVariableTitle,
|
|
||||||
outputs: provideJsonSchemaOutputs,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
|
import { FormRenderProps, FormMeta } from '@flowgram.ai/free-layout-editor';
|
||||||
import { provideJsonSchemaOutputs, syncVariableTitle } from '@flowgram.ai/form-materials';
|
|
||||||
import { Avatar } from '@douyinfe/semi-ui';
|
import { Avatar } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { FlowNodeJSON } from '../../typings';
|
import { FlowNodeJSON } from '../../typings';
|
||||||
@ -38,12 +37,4 @@ export const renderForm = ({ form }: FormRenderProps<FlowNodeJSON>) => (
|
|||||||
|
|
||||||
export const formMeta: FormMeta<FlowNodeJSON> = {
|
export const formMeta: FormMeta<FlowNodeJSON> = {
|
||||||
render: renderForm,
|
render: renderForm,
|
||||||
validateTrigger: ValidateTrigger.onChange,
|
|
||||||
validate: {
|
|
||||||
title: ({ value }: { value: string }) => (value ? undefined : 'Title is required'),
|
|
||||||
},
|
|
||||||
effect: {
|
|
||||||
title: syncVariableTitle,
|
|
||||||
outputs: provideJsonSchemaOutputs,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { delay, WorkflowDocument, WorkflowNodeEntity } from '@flowgram.ai/free-layout-editor';
|
|
||||||
|
|
||||||
import { WorkflowNodeType } from '../constants';
|
|
||||||
|
|
||||||
export const createBuiltInNodes = async (node: WorkflowNodeEntity) => {
|
|
||||||
// wait for node render - 等待节点渲染
|
|
||||||
await delay(16);
|
|
||||||
if (node.blocks.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const document = node.document as WorkflowDocument;
|
|
||||||
document.createWorkflowNode(
|
|
||||||
{
|
|
||||||
id: `block_start_${node.id}`,
|
|
||||||
type: WorkflowNodeType.BlockStart,
|
|
||||||
meta: {
|
|
||||||
position: {
|
|
||||||
x: -80,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
node.id
|
|
||||||
);
|
|
||||||
document.createWorkflowNode(
|
|
||||||
{
|
|
||||||
id: `block_end_${node.id}`,
|
|
||||||
type: WorkflowNodeType.BlockEnd,
|
|
||||||
meta: {
|
|
||||||
position: {
|
|
||||||
x: 80,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
node.id
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -16,7 +16,6 @@ import { FlowNodeRegistry } from '../../typings';
|
|||||||
import iconLoop from '../../assets/icon-loop.jpg';
|
import iconLoop from '../../assets/icon-loop.jpg';
|
||||||
import { LoopFormRender } from './loop-form-render';
|
import { LoopFormRender } from './loop-form-render';
|
||||||
import { WorkflowNodeType } from '../constants';
|
import { WorkflowNodeType } from '../constants';
|
||||||
import { createBuiltInNodes } from './create-built-in-nodes';
|
|
||||||
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
export const LoopNodeRegistry: FlowNodeRegistry = {
|
export const LoopNodeRegistry: FlowNodeRegistry = {
|
||||||
@ -74,10 +73,31 @@ export const LoopNodeRegistry: FlowNodeRegistry = {
|
|||||||
data: {
|
data: {
|
||||||
title: `Loop_${++index}`,
|
title: `Loop_${++index}`,
|
||||||
},
|
},
|
||||||
};
|
blocks: [
|
||||||
|
{
|
||||||
|
id: `block_start_${nanoid(5)}`,
|
||||||
|
type: WorkflowNodeType.BlockStart,
|
||||||
|
meta: {
|
||||||
|
position: {
|
||||||
|
x: -80,
|
||||||
|
y: 0,
|
||||||
},
|
},
|
||||||
onCreate(node, json) {
|
},
|
||||||
createBuiltInNodes(node);
|
data: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: `block_end_${nanoid(5)}`,
|
||||||
|
type: WorkflowNodeType.BlockEnd,
|
||||||
|
meta: {
|
||||||
|
position: {
|
||||||
|
x: 80,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
},
|
},
|
||||||
formMeta: {
|
formMeta: {
|
||||||
...defaultFormMeta,
|
...defaultFormMeta,
|
||||||
|
|||||||
@ -57,6 +57,22 @@ export class CopyShortcut implements ShortcutsHandler {
|
|||||||
await this.write(data);
|
await this.write(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create clipboard data - 转换为剪贴板数据
|
||||||
|
*/
|
||||||
|
public toClipboardData(nodes?: WorkflowNodeEntity[]): WorkflowClipboardData {
|
||||||
|
const validNodes = this.getValidNodes(nodes ? nodes : this.selectedNodes);
|
||||||
|
const source = this.toSource();
|
||||||
|
const json = this.toJSON(validNodes);
|
||||||
|
const bounds = this.getEntireBounds(validNodes);
|
||||||
|
return {
|
||||||
|
type: WorkflowClipboardDataID,
|
||||||
|
source,
|
||||||
|
json,
|
||||||
|
bounds,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* has selected text - 是否有文字被选中
|
* has selected text - 是否有文字被选中
|
||||||
*/
|
*/
|
||||||
@ -93,22 +109,6 @@ export class CopyShortcut implements ShortcutsHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* create clipboard data - 转换为剪贴板数据
|
|
||||||
*/
|
|
||||||
toClipboardData(nodes?: WorkflowNodeEntity[]): WorkflowClipboardData {
|
|
||||||
const validNodes = this.getValidNodes(nodes ? nodes : this.selectedNodes);
|
|
||||||
const source = this.toSource();
|
|
||||||
const json = this.toJSON(validNodes);
|
|
||||||
const bounds = this.getEntireBounds(validNodes);
|
|
||||||
return {
|
|
||||||
type: WorkflowClipboardDataID,
|
|
||||||
source,
|
|
||||||
json,
|
|
||||||
bounds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get valid nodes - 获取有效的节点
|
* get valid nodes - 获取有效的节点
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,74 +1,80 @@
|
|||||||
# Variable Basics
|
# Variable Basics
|
||||||
|
|
||||||
## Business Context
|
## What is a Variable?
|
||||||
|
|
||||||
In workflow orchestration, information needs to be passed between nodes. To achieve this, we use **variables** to store and manage this information.
|
Imagine you're building a complex Lego model where each module needs to connect precisely. In the world of Workflows, **variables** play a similar role as "connectors." They are the "messengers" used to pass information between different nodes.
|
||||||
|
|
||||||
:::warning A variable consists of three main components:
|
Simply put, a variable is a named container where you can store various things, such as user input, calculation results, or data retrieved from somewhere.
|
||||||
|
|
||||||
1. **Unique Identifier**: The name of the variable, used to distinguish between different variables so they can be accurately referenced and used in the program. Examples: `userName` or `totalAmount`.
|
A variable typically consists of three parts:
|
||||||
2. **Value**: The data stored in the variable. Values can be of various types, such as numbers (e.g., `42`), strings (e.g., `"Hello!"`), booleans (e.g., `true`), etc.
|
|
||||||
3. **Type**: The kind of data the variable can store. The type determines what values the variable can accept. For example, a variable can be an integer, float, string, boolean, etc.
|
|
||||||
|
|
||||||
:::
|
- **Name (Unique Identifier)**: Like your name, it allows everyone to find this variable accurately. For example, `userName`, `orderId`.
|
||||||
|
- **Value**: The content inside the container. It can be a number `123`, text `"Hello Flowgram!"`, or a switch state `true` / `false`.
|
||||||
|
- **Type**: Specifies what kind of things this container can hold. For instance, some can only hold numbers, while others can only hold text.
|
||||||
|
|
||||||
Here’s an example of workflow orchestration: The WebSearch node retrieves knowledge and passes it to the LLM node for analysis via `natural_language_desc`.
|
---
|
||||||
|
|
||||||
|
For example, in an "Intelligent Q&A" flow:
|
||||||
|
|
||||||
<div style={{display: 'flex', gap: '20px'}}>
|
<div style={{display: 'flex', gap: '20px'}}>
|
||||||
<img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" />
|
<img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" />
|
||||||
<div>
|
<div>
|
||||||
In this example:
|
<p style={{marginTop: 10}}>1. **`WebSearch` Node**: Responsible for searching the web and putting the found knowledge (e.g., the answer to "What's the weather like today?") into a variable named `searchResult`.</p>
|
||||||
<p style={{marginTop: 10}}>1. The WebSearch node stores the information (value) in a variable with the unique identifier `natural_language_desc`.</p>
|
<p style={{marginTop: 5}}>2. **`LLM` Node**: It takes the `searchResult` "messenger," reads its content, and then answers the user in a more natural and friendly way.</p>
|
||||||
<p style={{marginTop: 5}}>2. The LLM node retrieves the knowledge base information (value) via the `natural_language_desc` identifier and passes it to the LLM node for analysis.</p>
|
<p style={{marginTop: 5}}>3. In this process, the type of `searchResult` is "string" because it contains text content.</p>
|
||||||
<p style={{marginTop: 5}}>3. The `natural_language_desc` variable is of type string, representing the content retrieved from the web, such as "DeepSeek released a new model today."</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## What is the Variable Engine?
|
## Why Do You Need a Variable Engine?
|
||||||
|
|
||||||
The variable engine is an optional built-in feature provided by Flowgram to help streamline **variable information orchestration** in workflow design more efficiently. It enables the following functionalities:
|
As the complexity of workflows increases, so do the number and management difficulty of variables.
|
||||||
|
|
||||||
|
To address this challenge, Flowgram provides a powerful **Variable Engine**.
|
||||||
|
|
||||||
|
It acts like a professional "data steward," systematically managing all variables to ensure the clarity and stability of the data flow.
|
||||||
|
|
||||||
|
Enabling the Variable Engine will bring you the following core advantages:
|
||||||
|
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "25px" }}>
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "25px" }}>
|
||||||
<div style={{ gridColumn: "span 2" }}>
|
<div style={{ gridColumn: "span 2" }}>
|
||||||
<b>Scope Constraint Control</b>
|
<b>Scope Constraints: Precise Data Access Control</b>
|
||||||
<p className="rs-tip">With the variable engine, you can control the scope of variables, ensuring they are available within the appropriate range and avoiding unnecessary conflicts.</p>
|
<p className="rs-tip">The Variable Engine can precisely control the effective range (i.e., scope) of each variable. Like having specific keys for different rooms, it ensures that variables are only accessed within the intended nodes, effectively preventing data pollution and unexpected logical errors.</p>
|
||||||
<div style={{display: "flex", gap: "25px"}}>
|
<div style={{display: "flex", gap: "25px"}}>
|
||||||
<div>
|
<div>
|
||||||
<img loading="lazy" src="/variable/variable-scope-feature-1.png" />
|
<img loading="lazy" src="/variable/variable-scope-feature-1.png" />
|
||||||
<p style={{marginTop: '10px'}}>In the diagram, the `query` variable from the Start node can be accessed by subsequent LLM and End nodes.</p>
|
<p style={{marginTop: '10px'}}>The `query` variable defined in the `Start` node can be easily accessed by the subsequent `LLM` and `End` nodes.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img loading="lazy" src="/variable/variable-scope-feature-2.png" />
|
<img loading="lazy" src="/variable/variable-scope-feature-2.png" />
|
||||||
<p style={{marginTop: '10px'}}>In the diagram, the LLM node is inside a Condition branch, while the End node is outside. Thus, the End node's variable selector cannot access the `result` variable from the LLM node.</p>
|
<p style={{marginTop: '10px'}}>The `LLM` node is in a `Condition` branch, like being in a separate room. The `End` node outside naturally cannot access its `result` variable.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>Variable Information Tree Maintenance</b>
|
<b>Variable Structure Insight: Easily Understand Complex Data</b>
|
||||||
<p className="rs-tip">The variable engine helps you build a clear variable information tree, making it easier to view and manage the state and relationships of all variables.</p>
|
<p className="rs-tip">When a variable becomes complex (e.g., an object with many levels), the Variable Engine allows you to explore its internal structure layer by layer, like peeling an onion, with all details at your fingertips.</p>
|
||||||
<img loading="lazy" src="/variable/variable-tree-management.gif" />
|
<img loading="lazy" src="/variable/variable-tree-management.gif" />
|
||||||
<p style={{marginTop: '10px'}}>The diagram shows output variables from multiple nodes and global configurations, with some variables containing multiple sub-variables forming a tree structure.</p>
|
<p style={{marginTop: '10px'}}>In this diagram, you can see the output variables of all nodes and their hierarchical relationships, like a lush tree.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>Automatic Type Inference</b>
|
<b>Automatic Type Inference: A Spark of Genius</b>
|
||||||
<p className="rs-tip">The variable engine can automatically infer variable types based on context, reducing the need for manual type specification and improving development efficiency.</p>
|
<p className="rs-tip">You no longer need to tell each variable what its type should be. The Variable Engine, like your "soulmate," will automatically infer its type based on the context.</p>
|
||||||
<img loading="lazy" src="/variable/variable-batch-auto-infer.gif" />
|
<img loading="lazy" src="/variable/variable-batch-auto-infer.gif" />
|
||||||
<p style={{marginTop: '10px'}}>In the diagram, the Batch node processes the `arr` variable from the Start node. When the type of the `arr` variable changes, the type of the `item` variable output by the Batch node also changes accordingly.</p>
|
<p style={{marginTop: '10px'}}>For example, when the type of the `arr` variable in the `Start` node changes, the type of the `item` output by the `Batch` node will also be automatically updated to ensure type consistency.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Enabling the Variable Engine
|
## How to Enable the Variable Engine?
|
||||||
|
|
||||||
[> API Detail](https://flowgram.ai/auto-docs/editor/interfaces/VariablePluginOptions.html)
|
You can enable the Variable Engine with a simple configuration to experience its powerful features.
|
||||||
|
|
||||||
|
[> View API Details](https://flowgram.ai/auto-docs/editor/interfaces/VariablePluginOptions.html)
|
||||||
|
|
||||||
```tsx pure title="use-editor-props.ts" {3}
|
```tsx pure title="use-editor-props.ts" {3}
|
||||||
|
// Enable the Variable Engine in EditorProps
|
||||||
// EditorProps
|
|
||||||
{
|
{
|
||||||
variableEngine: {
|
variableEngine: {
|
||||||
/**
|
// Set to true to enable the Variable Engine
|
||||||
* The variable engine must be enabled to use this feature.
|
|
||||||
*/
|
|
||||||
enable: true
|
enable: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +1,39 @@
|
|||||||
# Consume Variables
|
# Consuming Variables
|
||||||
|
|
||||||
## Accessing Variable Tree Within a Node
|
In Flowgram, managing and consuming variables is at the core of building dynamic, interactive applications. Understanding how to effectively consume variables is crucial for developers. This document will guide you through the various ways to consume variables in different scenarios.
|
||||||
|
|
||||||
### Retrieving Variable List
|
## Getting the Variable Tree within a Node
|
||||||
|
|
||||||
|
In the nodes on the canvas, we often need to get the variables available in the current scope and display them in a tree structure for users to select and operate. The `useAvailableVariables` React Hook is designed for this purpose.
|
||||||
|
|
||||||
|
### `useAvailableVariables`
|
||||||
|
|
||||||
|
`useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope. If you only need a simple list of variables and don't need to perform more complex operations, `useAvailableVariables` is a more convenient choice.
|
||||||
|
|
||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
useScopeAvailable,
|
useAvailableVariables,
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
// .... Inside react hooks or component
|
// .... In a React component or Hook
|
||||||
|
|
||||||
const available = useScopeAvailable()
|
const availableVariables = useAvailableVariables();
|
||||||
|
|
||||||
const renderVariable = (variable: BaseVariableField) => {
|
const renderVariable = (variable: BaseVariableField) => {
|
||||||
|
// You can render each variable according to your needs here
|
||||||
// ....
|
// ....
|
||||||
}
|
}
|
||||||
|
|
||||||
return available.variables.map(renderVariable)
|
return availableVariables.map(renderVariable);
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Drilling Down into Object Type Variables
|
### Drilling Down into Object Type Variables
|
||||||
|
|
||||||
|
When a variable's type is `Object`, we often need to "drill down" into it to get its properties. The `ASTMatch.isObject` method can help us determine if a variable's type is an object. If it is, we can recursively render its `properties`.
|
||||||
|
|
||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
@ -37,8 +45,8 @@ import {
|
|||||||
const renderVariable = (variable: BaseVariableField) => ({
|
const renderVariable = (variable: BaseVariableField) => ({
|
||||||
title: variable.meta?.title,
|
title: variable.meta?.title,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
// Only Object Type can drilldown
|
// Only Object type variables can be drilled down
|
||||||
children: ASTMatch.isObject(type) ? type.properties.map(renderVariable) : [],
|
children: ASTMatch.isObject(variable.type) ? variable.type.properties.map(renderVariable) : [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
@ -47,6 +55,8 @@ const renderVariable = (variable: BaseVariableField) => ({
|
|||||||
|
|
||||||
### Drilling Down into Array Type Variables
|
### Drilling Down into Array Type Variables
|
||||||
|
|
||||||
|
Similar to the `Object` type, when we encounter an `Array` type variable, we also want to display its internal structure. For arrays, we are usually concerned with the type of its elements. `ASTMatch.isArray` can determine if a variable's type is an array. It's worth noting that the element type of an array can be anything, even another array. Therefore, we need a recursive helper function `getTypeChildren` to handle this situation.
|
||||||
|
|
||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
@ -59,17 +69,18 @@ import {
|
|||||||
const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
||||||
if (!type) return [];
|
if (!type) return [];
|
||||||
|
|
||||||
// get properties of Object
|
// Get the properties of an Object
|
||||||
if (ASTMatch.isObject(type)) return type.properties;
|
if (ASTMatch.isObject(type)) return type.properties;
|
||||||
|
|
||||||
// get items type of Array
|
// Recursively get the element type of an Array
|
||||||
if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
|
if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
|
||||||
|
|
||||||
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderVariable = (variable: BaseVariableField) => ({
|
const renderVariable = (variable: BaseVariableField) => ({
|
||||||
title: variable.meta?.title,
|
title: variable.meta?.title,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
// Only Object Type can drilldown
|
|
||||||
children: getTypeChildren(variable.type).map(renderVariable),
|
children: getTypeChildren(variable.type).map(renderVariable),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,20 +88,164 @@ const renderVariable = (variable: BaseVariableField) => ({
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Using Official VariableSelector Component
|
## Official Material: `VariableSelector`
|
||||||
|
|
||||||
See: [Official Form Materials](/guide/advanced/form-materials.html)
|
To make it easier for you to integrate variable selection functionality into your application, we have prepared an official material for you - the `VariableSelector` component. It encapsulates all the logic mentioned earlier, allowing you to have a powerful and beautiful variable selector without starting from scratch.
|
||||||
|
|
||||||
<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
|
<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
|
||||||
|
|
||||||
The [VariableSelector](https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/variable-selector/index.tsx) component is used for selecting individual variables.
|
`VariableSelector` not only supports displaying a variable tree but also has built-in advanced features such as search and filtering, which can greatly enhance the user experience. For a more detailed introduction, please refer to the [Official Form Materials](/guide/advanced/form-materials.html) documentation.
|
||||||
|
|
||||||
|
You can use it in the following two ways:
|
||||||
|
|
||||||
|
**1. By Referencing the NPM Package**
|
||||||
|
|
||||||
|
This is the simplest and most direct way. With just one line of code, you can introduce `VariableSelector` into your project.
|
||||||
|
|
||||||
Install via package:
|
|
||||||
```tsx
|
```tsx
|
||||||
import { VariableSelector } from '@flowgram.ai/materials'
|
import { VariableSelector } from '@flowgram.ai/form-materials';
|
||||||
```
|
```
|
||||||
|
|
||||||
Or copy source code via CLI:
|
**2. By Copying the Source Code via CLI**
|
||||||
|
|
||||||
|
If you want to customize `VariableSelector` more deeply, we also provide a way to copy the component's source code directly into your project via the CLI. This way, you can modify it as you wish to meet your unique business needs.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @flowgram.ai/materials components/variable-selector
|
npx @flowgram.ai/materials components/variable-selector
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `ScopeAvailableData`: Your Variable Toolbox
|
||||||
|
|
||||||
|
The `ScopeAvailableData` object is one of the core components of the variable system. It is returned by the `useScopeAvailable` Hook and is your main bridge for interacting with the available variables in the scope. You can think of it as a powerful "variable toolbox."
|
||||||
|
|
||||||
|
### `useScopeAvailable`
|
||||||
|
|
||||||
|
`useScopeAvailable` is a more powerful Hook that returns a `ScopeAvailableData` object, which not only contains all the available variable information in the current scope but also provides some advanced APIs, such as `trackByKeyPath`.
|
||||||
|
|
||||||
|
Its main differences from `useAvailableVariables` are:
|
||||||
|
|
||||||
|
* **Return Value**: `useAvailableVariables` directly returns a variable array, while `useScopeAvailable` returns a `ScopeAvailableData` object that contains the `variables` property and other methods.
|
||||||
|
* **Applicable Scenarios**: When you need to perform more complex operations on variables, such as tracking changes to a single variable, `useScopeAvailable` is your best choice.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
|
||||||
|
// The available object contains the variable list and other APIs
|
||||||
|
console.log(available.variables);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Getting the Variable List
|
||||||
|
|
||||||
|
The most basic usage is to get all the available variables in the current scope.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
|
||||||
|
// available.variables is an array containing all available variables
|
||||||
|
console.log(available.variables);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{available.variables.map(variable => (
|
||||||
|
<li key={variable.key}>{variable.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tracking Changes to a Single Variable: `trackByKeyPath`
|
||||||
|
|
||||||
|
When you are only concerned with the changes of a specific variable (especially one nested in an Object or Array), `trackByKeyPath` comes in handy. It allows you to accurately "subscribe" to the updates of this variable without causing the component to re-render due to changes in other unrelated variables, thus achieving finer performance optimization.
|
||||||
|
|
||||||
|
Suppose we have an Object type variable named `user` with a `name` property. We want to update the component when `user.name` changes.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function UserNameDisplay() {
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
const [userName, setUserName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Define the variable path we want to track
|
||||||
|
const keyPath = ['user', 'name'];
|
||||||
|
|
||||||
|
// Start tracking!
|
||||||
|
const disposable = available.trackByKeyPath(keyPath, (nameField) => {
|
||||||
|
// This callback function will be triggered when user.name changes
|
||||||
|
// nameField is the changed variable field, from which we can get the latest default value
|
||||||
|
setUserName(nameField?.meta.default || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the component unmounts, don't forget to cancel the tracking to avoid memory leaks
|
||||||
|
return () => disposable.dispose();
|
||||||
|
}, [available]); // The dependency is the available object
|
||||||
|
|
||||||
|
return <div>User Name: {userName}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Event Listening API
|
||||||
|
|
||||||
|
In addition to `trackByKeyPath`, `ScopeAvailableData` also provides a set of lower-level event listening APIs that allow you to control the response logic to variable changes more finely. This is very useful when dealing with complex scenarios that require manual subscription management.
|
||||||
|
|
||||||
|
Let's use a table to compare these three core listening APIs in detail:
|
||||||
|
|
||||||
|
| API | Trigger | Callback Parameters | Core Differences and Applicable Scenarios |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `onVariableListChange` | When the **list structure** of available variables changes. | `(variables: VariableDeclaration[]) => void` | **Only cares about the list itself**. For example, an upstream node adds/deletes an output variable, causing the total number or members of available variables to change. It does not care about internal or drilled-down changes to variables. Suitable for scenarios where the UI needs to be updated based on the presence or number of variables in the list. |
|
||||||
|
| `onAnyVariableChange` | When the **content (meta, type, drilldown fields)** of **any** variable in the list changes. | `(changedVariable: VariableDeclaration) => void` | **Only cares about content (type, meta, drilldown) updates to variables**. For example, a user modifies the type of an output variable. It does not care about changes to the list structure. Suitable for scenarios where you need to react to changes in the content of any variable. |
|
||||||
|
| `onListOrAnyVarChange` | When **either** of the above two situations occurs. | `(variables: VariableDeclaration[]) => void` | **The most comprehensive listener**, a combination of the previous two. It is triggered by either a change in the list structure or a change in the value of any variable. Suitable for "catch-all" scenarios where you need to respond to any possible changes. |
|
||||||
|
|
||||||
|
#### Code Example
|
||||||
|
|
||||||
|
Let's look at a specific example of how to use these APIs in a component.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
function AdvancedListenerComponent() {
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 1. Listen for changes in the list structure
|
||||||
|
const listChangeDisposable = available.onVariableListChange((variables) => {
|
||||||
|
console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Listen for changes in the value of any variable
|
||||||
|
const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
|
||||||
|
console.log(`The content of variable '${changedVariable.keyPath.join('.')}' has changed!`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Listen for all changes (structure or content)
|
||||||
|
const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
|
||||||
|
console.log('The variable list or the content of one of its variables has changed!');
|
||||||
|
// Note: The callback parameter here is the complete variable list, not the single changed variable
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the component unmounts, be sure to clean up all listeners to prevent memory leaks
|
||||||
|
return () => {
|
||||||
|
listChangeDisposable.dispose();
|
||||||
|
valueChangeDisposable.dispose();
|
||||||
|
allChangesDisposable.dispose();
|
||||||
|
};
|
||||||
|
}, [available]);
|
||||||
|
|
||||||
|
return <div>Please check the console for logs of variable changes...</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points**:
|
||||||
|
|
||||||
|
* These APIs all return a `Disposable` object.
|
||||||
|
* To avoid memory leaks and unnecessary calculations, you must call its `dispose()` method in the cleanup function of `useEffect` to cancel the listener.
|
||||||
|
|||||||
@ -1,144 +1,54 @@
|
|||||||
# Output Variables
|
# Outputting Variables
|
||||||
|
|
||||||
## Node Output Variables
|
In Flowgram, variables are the key hubs that connect various nodes. The output of one node is often the input of another. Therefore, how to elegantly define and output variables has become a required course. This article will take you to explore the various ways of outputting variables in Flowgram.
|
||||||
|
|
||||||
### FlowNodeVariableData Output Variables
|
We mainly divide variables into two categories:
|
||||||
|
|
||||||
`Flowgram` manages node information based on [`ECS`](https://flowgram.ai/guide/concepts/ecs.html) (Entity-Component-System).
|
- **Node Variables**: The scope is limited to a single node, usually as the output of that node for subsequent nodes to use.
|
||||||
|
- **Global Variables**: The scope runs through the entire process, and any node can read and modify it. It is suitable for storing some public states or configurations.
|
||||||
|
|
||||||
The [`FlowNodeVariableData`](https://flowgram.ai/auto-docs/editor/classes/FlowNodeVariableData.html) is a `Component` on the `FlowNodeEntity` node, specifically designed to handle **variable information** output from nodes.
|
## Outputting Node Variables
|
||||||
|
|
||||||
The following demo shows: How to obtain `FlowNodeVariableData` and use it to output variables from nodes.
|
Outputting a node variable means that this variable is bound to the life cycle of the current node. When the node is created, the variable is born; when the node is deleted, the variable also disappears. We usually have two ways to output node variables:
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
1. **Through form configuration**: In the node's setting panel (Form), declare output variables through some preset configurations or custom logic. This method is very intuitive, what you see is what you get.
|
||||||
|
2. **Through API calls**: In the plugin (Plugin), dynamically add, modify or delete variables for the node by calling the `getNodeScope` API. This method is more flexible and suitable for handling complex and dynamic business scenarios.
|
||||||
|
|
||||||
|
Next, we will explore the specific usage of these two methods in depth.
|
||||||
|
|
||||||
|
### Method 1: Output through Form Configuration
|
||||||
|
|
||||||
|
Configuring in the node's `form-meta.ts` file is the most common way to define node output variables. It can be divided into two ways: one is the "lazy version", using our built-in materials; the other is the "master version", completely customized.
|
||||||
|
|
||||||
|
#### `provideJsonSchemaOutputs` Material
|
||||||
|
|
||||||
|
If the structure of the variable that your node needs to output happens to be consistent with the `JSON Schema` structure of the form, then congratulations, the `provideJsonSchemaOutputs` side effect (Effect) material is tailor-made for you! It is like an automated "variable porter", which can automatically convert the data in the form into the output variable of the node as it is.
|
||||||
|
|
||||||
|
It is very simple to use, just add two lines of configuration in the `effect` of `formMeta`:
|
||||||
|
|
||||||
|
```tsx pure title="form-meta.ts"
|
||||||
import {
|
import {
|
||||||
FlowNodeVariableData,
|
syncVariableTitle,
|
||||||
ASTFactory,
|
provideJsonSchemaOutputs,
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/form-materials';
|
||||||
|
|
||||||
// ....
|
export const formMeta = {
|
||||||
|
effect: {
|
||||||
flowDocument.onNodeCreate(({ node }) => {
|
title: syncVariableTitle, // Variable title is automatically synchronized
|
||||||
const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
|
outputs: provideJsonSchemaOutputs,
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
// 1. Clear VariableData if No value
|
|
||||||
variableData.clearVar()
|
|
||||||
|
|
||||||
// 2. Set a String Variable as output
|
|
||||||
variableData.setVar(
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title`,
|
|
||||||
},
|
},
|
||||||
key: `your_variable_global_unique_key_${node.id}`,
|
};
|
||||||
type: ASTFactory.createString(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// 3. Set a Complicated Variable Data as output
|
|
||||||
variableData.setVar(
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title`,
|
|
||||||
},
|
|
||||||
key: `your_variable_global_unique_key_${node.id}`,
|
|
||||||
type: ASTFactory.createArray({
|
|
||||||
items: ASTFactory.createObject({
|
|
||||||
properties: [
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'stringType',
|
|
||||||
type: ASTFactory.createString(),
|
|
||||||
}),
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'booleanType',
|
|
||||||
type: ASTFactory.createBoolean(),
|
|
||||||
}),
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'numberType',
|
|
||||||
type: ASTFactory.createNumber(),
|
|
||||||
}),
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'integerType',
|
|
||||||
type: ASTFactory.createInteger(),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4. Get Variable for current Node
|
|
||||||
console.log(variableData.getVar())
|
|
||||||
|
|
||||||
// ....
|
|
||||||
})
|
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See: [> Demo Detail](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout/src/plugins/sync-variable-plugin/sync-variable-plugin.ts#L25)
|
#### Custom Output through Form Side Effects
|
||||||
|
|
||||||
### Setting Multiple Output Variables for a Node
|
Although `provideJsonSchemaOutputs` is convenient, it only adapts to JsonSchema.
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
If you want to define your own set of Schema, then you need to customize the side effects of the form.
|
||||||
import {
|
|
||||||
FlowNodeVariableData,
|
|
||||||
ASTFactory,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
|
||||||
|
|
||||||
// ....
|
Flowgram provides a powerful auxiliary function `createEffectFromVariableProvider`, which can help you easily create a side effect for providing variables. You can think of it as a "variable processing factory", the input is the form data, and the output is the variable you have carefully processed.
|
||||||
|
|
||||||
flowDocument.onNodeCreate(({ node }) => {
|
In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2`. When the user fills in these two fields in the form, the `parse` function will be triggered to convert the user's input value (`v`) into a standard variable declaration object.
|
||||||
const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
// 1. Create, Update, Read, Delete Variable in namespace_1
|
|
||||||
variableData.setVar(
|
|
||||||
'namespace_1',
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title`,
|
|
||||||
},
|
|
||||||
key: `your_variable_global_unique_key_${node.id}`,
|
|
||||||
type: ASTFactory.createString(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(variableData.getVar('namespace_1'))
|
|
||||||
|
|
||||||
variableData.clearVar('namespace_1')
|
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
// 2. Create, Update, Read, Delete Variable in namespace_2
|
|
||||||
variableData.setVar(
|
|
||||||
'namespace_2',
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title 2`,
|
|
||||||
},
|
|
||||||
key: `your_variable_global_unique_key_${node.id}_2`,
|
|
||||||
type: ASTFactory.createString(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(variableData.getVar('namespace_2'))
|
|
||||||
|
|
||||||
variableData.clearVar('namespace_2')
|
|
||||||
|
|
||||||
// ....
|
|
||||||
})
|
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
For more usage, see: [Class: FlowNodeVariableData](https://flowgram.ai/auto-docs/editor/classes/FlowNodeVariableData.html)
|
|
||||||
|
|
||||||
### Setting Output Variables via Form Side Effects
|
|
||||||
|
|
||||||
```tsx pure title="node-registries.ts"
|
```tsx pure title="node-registries.ts"
|
||||||
import {
|
import {
|
||||||
@ -207,13 +117,85 @@ export const nodeRegistries: FlowNodeRegistry[] = [
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Global Output Variables
|
### Method 2: Use the `getNodeScope` API
|
||||||
|
|
||||||
### Obtaining Global Variable Scope
|
In addition to static configuration in the form, we can also get the scope in the plugin (Plugin) through the `getNodeScope` API to dynamically operate the variables of the node.
|
||||||
|
|
||||||
The global scope can be obtained via `ctx` in a Plugin:
|
This method gives you a high degree of freedom. You can add, delete, modify, and query the variables of the node at any time and any place.
|
||||||
|
|
||||||
|
`getNodeScope` will return a node's variable scope (Scope) object, which has several core methods:
|
||||||
|
|
||||||
|
- `setVar(variable)`: Set a variable.
|
||||||
|
- `setVar(namespace, variable)`: Set a variable under the specified namespace.
|
||||||
|
- `getVar()`: Get all variables.
|
||||||
|
- `getVar(namespace)`: Get the variables under the specified namespace.
|
||||||
|
- `clearVar()`: Clear all variables.
|
||||||
|
- `clearVar(namespace)`: Clear the variables under the specified namespace.
|
||||||
|
|
||||||
|
The following example demonstrates how to get the `Scope` of the start node and perform a series of operations on its variables in the `onInit` life cycle of the plugin.
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
```tsx pure title="sync-variable-plugin.tsx"
|
||||||
|
import {
|
||||||
|
FlowDocument,
|
||||||
|
definePluginCreator,
|
||||||
|
PluginCreator,
|
||||||
|
getNodeScope,
|
||||||
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
|
||||||
|
export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
||||||
|
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
||||||
|
onInit(ctx, options) {
|
||||||
|
const startNode = ctx.get(FlowDocument).getNode('start_0');
|
||||||
|
const startScope = getNodeScope(startNode)
|
||||||
|
|
||||||
|
// 1. Set Variable For Start Scope
|
||||||
|
startScope.setVar(
|
||||||
|
ASTFactory.createVariableDeclaration({
|
||||||
|
meta: {
|
||||||
|
title: `Your Output Variable Title`,
|
||||||
|
},
|
||||||
|
key: `your_variable_unique_key`,
|
||||||
|
type: ASTFactory.createString(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. Create, Update, Read, Delete Variable in namespace_2
|
||||||
|
startScope.setVar(
|
||||||
|
'namespace_2',
|
||||||
|
ASTFactory.createVariableDeclaration({
|
||||||
|
meta: {
|
||||||
|
title: `Your Output Variable Title 2`,
|
||||||
|
},
|
||||||
|
key: `your_variable_global_unique_key_2`,
|
||||||
|
type: ASTFactory.createString(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(startScope.getVar('namespace_2'))
|
||||||
|
|
||||||
|
// 3. Delete Variable in namespace_2
|
||||||
|
startScope.clearVar('namespace_2')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Outputting Global Variables
|
||||||
|
|
||||||
|
Global variables are like the "shared memory" of the entire process, which can be accessed and modified by any node and any plugin. It is very suitable for storing some states that run through, such as user information, environment configuration, and so on.
|
||||||
|
|
||||||
|
Similar to node variables, we also have two main ways to obtain the scope of global variables (`GlobalScope`).
|
||||||
|
|
||||||
|
### Method 1: Obtain in Plugin
|
||||||
|
|
||||||
|
In the context of the plugin (`ctx`), we can directly "inject" the instance of `GlobalScope`:
|
||||||
|
|
||||||
|
|
||||||
|
```tsx pure title="global-variable-plugin.tsx"
|
||||||
import {
|
import {
|
||||||
GlobalScope,
|
GlobalScope,
|
||||||
definePluginCreator,
|
definePluginCreator,
|
||||||
@ -221,7 +203,7 @@ import {
|
|||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
|
||||||
export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
||||||
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
||||||
onInit(ctx, options) {
|
onInit(ctx, options) {
|
||||||
const globalScope = ctx.get(GlobalScope)
|
const globalScope = ctx.get(GlobalScope)
|
||||||
@ -240,7 +222,10 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It can also be obtained in React components within the canvas via `useService`:
|
|
||||||
|
### Method 2: Obtain in React Component
|
||||||
|
|
||||||
|
If you want to interact with global variables in the React component of the canvas, you can use the `useService` Hook to get the instance of `GlobalScope`:
|
||||||
|
|
||||||
```tsx pure title="global-variable-component.tsx"
|
```tsx pure title="global-variable-component.tsx"
|
||||||
import {
|
import {
|
||||||
@ -271,9 +256,13 @@ function GlobalVariableComponent() {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Outputting Variables in Global Scope
|
|
||||||
|
|
||||||
The API for outputting variables in [`GlobalScope`](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html) is similar to `FlowNodeVariableData`:
|
|
||||||
|
### API of Global Scope
|
||||||
|
|
||||||
|
The API of `GlobalScope` is designed to be almost identical to the node scope (`NodeScope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. This consistent design greatly reduces our learning costs.
|
||||||
|
|
||||||
|
Here is a comprehensive example of operating global variables in a plugin:
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
```tsx pure title="sync-variable-plugin.tsx"
|
||||||
import {
|
import {
|
||||||
@ -321,4 +310,4 @@ onInit(ctx, options) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
See: [Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)
|
See also: [Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)
|
||||||
|
|||||||
@ -1,76 +1,80 @@
|
|||||||
# 变量基础
|
# 变量基础
|
||||||
|
|
||||||
## 业务背景
|
## 什么是变量?
|
||||||
|
|
||||||
在 Workflow 编排中,节点与节点之间需要传递信息。为了实现这一点,我们使用**变量**来存储和管理这些信息。
|
想象一下,你在搭建一个复杂的乐高模型,每个模块都需要精确地连接在一起。在工作流(Workflow)的世界里,**变量**就扮演着类似“连接件”的角色。它们是用来在不同节点之间传递信息的“信使”。
|
||||||
|
|
||||||
:::warning 一个变量由三个主要部分组成:
|
简单来说,变量就是一个带名字的容器,你可以往里面装各种东西,比如用户的输入、计算结果,或者从某个地方获取的数据。
|
||||||
|
|
||||||
1. **唯一标识符**:变量的名字,用于区分不同的变量,以便在程序中可以准确地引用和使用它。如:`userName` 或 `totalAmount`。
|
一个变量通常由三部分组成:
|
||||||
2. **值**:变量存储的数据。值可以是多种类型,比如数字(如 `42`)、字符串(如 `"Hello!"`)、布尔值(如 `true`)等。
|
|
||||||
3. **类型**:变量可以存储的数据种类。类型决定了变量可以接受什么样的值。例如,一个变量可以是整数、浮点数、字符串或布尔值等。
|
|
||||||
|
|
||||||
:::
|
- **名字(唯一标识符)**:就像你的名字一样,它让大家能准确地找到这个变量。例如 `userName`、`orderId`。
|
||||||
|
- **值**:容器里装的东西。它可以是数字 `123`,文字 `"Hello Flowgram!"`,或者一个开关状态 `true` / `false`。
|
||||||
|
- **类型**:规定了这个容器能装哪种东西。比如,有的只能装数字,有的只能装文字。
|
||||||
|
|
||||||
下面是一个流程编排的例子:WebSearch 节点获取到知识,通过 natural_language_desc 传递到 LLM 节点进行分析
|
---
|
||||||
|
|
||||||
|
举个例子,在一个“智能问答”流程中:
|
||||||
|
|
||||||
<div style={{display: 'flex', gap: '20px'}}>
|
<div style={{display: 'flex', gap: '20px'}}>
|
||||||
<img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" />
|
<img style={{width: "50%"}} loading="lazy" src="/variable/variable-biz-context-websearch-llm.png" />
|
||||||
<div>
|
<div>
|
||||||
在该例子中:
|
<p style={{marginTop: 10}}>1. **`WebSearch` 节点**:负责上网搜索,然后把搜到的知识(比如“今天天气怎么样?”的答案)放进一个名为 `searchResult` 的变量里。</p>
|
||||||
<p style={{marginTop: 10}}>1. WebSearch 节点将信息(值)存在 natural_language_desc 为唯一标识符的变量内</p>
|
<p style={{marginTop: 5}}>2. **`LLM` 节点**:它会接过 `searchResult` 这个“信使”,读取里面的内容,然后用更自然、更友好的方式回答用户。</p>
|
||||||
<p style={{marginTop: 5}}>2. LLM 节点通过 natural_language_desc 唯一标识符获取到知识库检索的信息(值),并传入 LLM 节点进行分析</p>
|
<p style={{marginTop: 5}}>3. 在这个过程中,`searchResult` 的类型就是“字符串”,因为它装的是文字内容。</p>
|
||||||
<p style={{marginTop: 5}}>3. natural_language_desc 变量的类型为字符串,代表在网络中检索到的信息内容,例如 "DeepSeek 今日有新模型发布"</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## 为什么需要变量引擎?
|
||||||
|
|
||||||
## 什么是变量引擎?
|
随着工作流复杂度的提升,变量的数量和管理难度也随之增加。
|
||||||
|
|
||||||
变量引擎是 Flowgram 提供的一个可选内置功能,可以帮助 Workflow 设计时更高效地实现**变量信息编排**。它可以实现以下功能:
|
为了应对这一挑战,Flowgram 提供了强大的**变量引擎**。
|
||||||
|
|
||||||
|
它如同一位专业的“数据管家”,能够系统化地管理所有变量,确保数据流的清晰与稳定。
|
||||||
|
|
||||||
|
启用变量引擎将为您带来以下核心优势:
|
||||||
|
|
||||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "25px" }}>
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "25px" }}>
|
||||||
<div style={{ gridColumn: "span 2" }}>
|
<div style={{ gridColumn: "span 2" }}>
|
||||||
<b>作用域约束控制</b>
|
<b>作用域约束:精准的数据访问控制</b>
|
||||||
<p className="rs-tip">通过变量引擎,你可以控制变量的作用域,确保变量在合适的范围内可用,避免不必要的冲突。</p>
|
<p className="rs-tip">变量引擎能够精确控制每个变量的有效范围(即作用域)。如同为不同房间配置专属钥匙,它确保了变量只在预期的节点中被访问,从而有效避免了数据污染和意外的逻辑错误。</p>
|
||||||
<div style={{display: "flex", gap: "25px"}}>
|
<div style={{display: "flex", gap: "25px"}}>
|
||||||
<div>
|
<div>
|
||||||
<img loading="lazy" src="/variable/variable-scope-feature-1.png" />
|
<img loading="lazy" src="/variable/variable-scope-feature-1.png" />
|
||||||
<p style={{marginTop: '10px'}}>图中 Start 节点的 query 变量,可被后续的 LLM 节点和 End 节点访问</p>
|
<p style={{marginTop: '10px'}}>`Start` 节点定义的 `query` 变量,在它后面的 `LLM` 和 `End` 节点都能轻松访问。</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<img loading="lazy" src="/variable/variable-scope-feature-2.png" />
|
<img loading="lazy" src="/variable/variable-scope-feature-2.png" />
|
||||||
<p style={{marginTop: '10px'}}>图中 LLM 节点在 Condition 分支内,End 节点在 Condition 分支外;因此 End 节点的变量选择器无法选择到 LLM 节点上的 result 变量</p>
|
<p style={{marginTop: '10px'}}>`LLM` 节点在一个 `Condition` 分支里,像是在一个独立的房间。外面的 `End` 节点自然就拿不到它的 `result` 变量了。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>变量信息树的维护</b>
|
<b>变量结构透视:轻松洞悉复杂数据</b>
|
||||||
<p className="rs-tip">变量引擎可以帮助你构建一个清晰的变量信息树,方便你查看和管理所有变量的状态和关系。</p>
|
<p className="rs-tip">当变量变得复杂时(例如一个包含许多层级的对象),变量引擎能让你像剥洋葱一样,一层层地深入探索它的内部结构,所有细节都尽在掌握。</p>
|
||||||
<img loading="lazy" src="/variable/variable-tree-management.gif" />
|
<img loading="lazy" src="/variable/variable-tree-management.gif" />
|
||||||
<p style={{marginTop: '10px'}}>图中展示了多个节点 + 全局配置的输出变量;其中部分变量包含了多个子变量,形成了一棵树的结构</p>
|
<p style={{marginTop: '10px'}}>这张图里,你能看到所有节点的输出变量,以及它们之间的层级关系,像一棵枝繁叶茂的树。</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>变量类型自动联动推导</b>
|
<b>类型自动推导:心有灵犀一点通</b>
|
||||||
<p className="rs-tip">变量引擎能够根据上下文自动推导变量的类型,减少手动指定类型的工作量,提高开发效率。</p>
|
<p className="rs-tip">你不用再挨个告诉每个变量它应该是什么类型,变量引擎会像你的“灵魂伴侣”一样,根据上下文自动推导出它的类型。</p>
|
||||||
<img loading="lazy" src="/variable/variable-batch-auto-infer.gif" />
|
<img loading="lazy" src="/variable/variable-batch-auto-infer.gif" />
|
||||||
<p style={{marginTop: '10px'}}>图中的 Batch 节点对 Start 节点的 arr 变量进行了批处理,当 arr 变量的类型变动时,Batch 节点批处理输出的 item 变量类型也随之变动</p>
|
<p style={{marginTop: '10px'}}>例如,当 `Start` 节点中 `arr` 变量的类型发生变更时,`Batch` 节点输出的 `item` 类型也会自动同步更新,确保了类型的一致性。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## 如何开启变量引擎?
|
||||||
|
|
||||||
## 开启变量引擎
|
您可以通过简单的配置来启用变量引擎,以体验其强大的功能。
|
||||||
|
|
||||||
[> API Detail](https://flowgram.ai/auto-docs/editor/interfaces/VariablePluginOptions.html)
|
[> 查看 API 详情](https://flowgram.ai/auto-docs/editor/interfaces/VariablePluginOptions.html)
|
||||||
|
|
||||||
```tsx pure title="use-editor-props.ts" {3}
|
```tsx pure title="use-editor-props.ts" {3}
|
||||||
|
// 在 EditorProps 中开启变量引擎
|
||||||
// EditorProps
|
|
||||||
{
|
{
|
||||||
variableEngine: {
|
variableEngine: {
|
||||||
/**
|
// 设置为 true 即可开启变量引擎
|
||||||
* 需要开启变量引擎才能使用
|
|
||||||
*/
|
|
||||||
enable: true
|
enable: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,39 @@
|
|||||||
# 消费变量
|
# 消费变量
|
||||||
|
|
||||||
|
在 Flowgram 中,变量的管理和消费是构建动态、可交互应用的核心。理解如何有效地消费变量,对于开发者来说至关重要。这篇文档将带你深入了解在不同场景下消费变量的各种方式。
|
||||||
|
|
||||||
## 在节点内获取变量树
|
## 在节点内获取变量树
|
||||||
|
|
||||||
### 获取变量列表
|
在画布的节点中,我们常常需要获取当前作用域下可用的变量,并将它们以树形结构展示出来,方便用户进行选择和操作。`useAvailableVariables` 这个 React Hook 就是为此而生。
|
||||||
|
|
||||||
|
### `useAvailableVariables`
|
||||||
|
|
||||||
|
`useAvailableVariables` 是一个轻量级的 Hook,它直接返回当前作用域可用的变量数组 (`VariableDeclaration[]`)。如果你只需要一个简单的变量列表,并且不需要进行更复杂的操作,那么 `useAvailableVariables` 会是更便捷的选择。
|
||||||
|
|
||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
useScopeAvailable,
|
useAvailableVariables,
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
// .... Inside react hooks or component
|
// .... 在 React 组件或 Hook 中
|
||||||
|
|
||||||
const available = useScopeAvailable()
|
const availableVariables = useAvailableVariables();
|
||||||
|
|
||||||
const renderVariable = (variable: BaseVariableField) => {
|
const renderVariable = (variable: BaseVariableField) => {
|
||||||
|
// 这里可以根据你的需求渲染每个变量
|
||||||
// ....
|
// ....
|
||||||
}
|
}
|
||||||
|
|
||||||
return available.variables.map(renderVariable)
|
return availableVariables.map(renderVariable);
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 获取 Object 类型变量的下钻
|
### 获取 Object 类型变量的下钻
|
||||||
|
|
||||||
|
当变量的类型是 `Object` 时,我们往往需要能够“下钻”到它的内部,获取其属性。`ASTMatch.isObject` 方法可以帮助我们判断一个变量类型是否为对象。如果是,我们就可以递归地渲染它的 `properties`。
|
||||||
|
|
||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
@ -38,8 +45,8 @@ import {
|
|||||||
const renderVariable = (variable: BaseVariableField) => ({
|
const renderVariable = (variable: BaseVariableField) => ({
|
||||||
title: variable.meta?.title,
|
title: variable.meta?.title,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
// Only Object Type can drilldown
|
// 只有 Object 类型的变量才可以下钻
|
||||||
children: ASTMatch.isObject(type) ? type.properties.map(renderVariable) : [],
|
children: ASTMatch.isObject(variable.type) ? variable.type.properties.map(renderVariable) : [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// ....
|
// ....
|
||||||
@ -48,6 +55,8 @@ const renderVariable = (variable: BaseVariableField) => ({
|
|||||||
|
|
||||||
### 获取 Array 类型变量的下钻
|
### 获取 Array 类型变量的下钻
|
||||||
|
|
||||||
|
与 `Object` 类型类似,当遇到 `Array` 类型的变量时,我们也希望能展示它的内部结构。对于数组,我们通常关心的是其元素的类型。`ASTMatch.isArray` 可以判断变量类型是否为数组。值得注意的是,数组的元素类型可能是任意的,甚至可能是另一个数组。因此,我们需要一个递归的辅助函数 `getTypeChildren` 来处理这种情况。
|
||||||
|
|
||||||
```tsx pure title="use-variable-tree.tsx"
|
```tsx pure title="use-variable-tree.tsx"
|
||||||
import {
|
import {
|
||||||
type BaseVariableField,
|
type BaseVariableField,
|
||||||
@ -60,17 +69,18 @@ import {
|
|||||||
const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
|
||||||
if (!type) return [];
|
if (!type) return [];
|
||||||
|
|
||||||
// get properties of Object
|
// 获取 Object 的属性
|
||||||
if (ASTMatch.isObject(type)) return type.properties;
|
if (ASTMatch.isObject(type)) return type.properties;
|
||||||
|
|
||||||
// get items type of Array
|
// 递归获取 Array 的元素类型
|
||||||
if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
|
if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
|
||||||
|
|
||||||
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderVariable = (variable: BaseVariableField) => ({
|
const renderVariable = (variable: BaseVariableField) => ({
|
||||||
title: variable.meta?.title,
|
title: variable.meta?.title,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
// Only Object Type can drilldown
|
|
||||||
children: getTypeChildren(variable.type).map(renderVariable),
|
children: getTypeChildren(variable.type).map(renderVariable),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,22 +88,164 @@ const renderVariable = (variable: BaseVariableField) => ({
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 直接使用 VariableSelector 官方物料
|
## 官方物料:`VariableSelector`
|
||||||
|
|
||||||
详见: [官方表单物料](/guide/advanced/form-materials.html)
|
为了让你能更轻松地在应用中集成变量选择的功能,我们为你准备了官方物料 —— `VariableSelector` 组件。它封装了前面提到的所有逻辑,让你无需从头开始,即可拥有一个功能强大、界面美观的变量选择器。
|
||||||
|
|
||||||
<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
|
<img loading="lazy" src="/materials/variable-selector.png" style={{width:500}}/>
|
||||||
|
|
||||||
[VariableSelector](https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/variable-selector/index.tsx) 组件用于选择单个变量
|
`VariableSelector` 不仅支持展示变量树,还内置了搜索、过滤等高级功能,可以极大地提升用户体验。更详细的介绍,请参考 [官方表单物料](/guide/advanced/form-materials.html) 文档。
|
||||||
|
|
||||||
通过包引用使用:
|
你可以通过以下两种方式来使用它:
|
||||||
|
|
||||||
|
**1. 通过 NPM 包引用**
|
||||||
|
|
||||||
|
这是最简单直接的方式,只需一行代码,即可在你的项目中引入 `VariableSelector`。
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { VariableSelector } from '@flowgram.ai/materials'
|
import { VariableSelector } from '@flowgram.ai/form-materials';
|
||||||
```
|
```
|
||||||
|
|
||||||
通过 CLI 复制源代码使用:
|
**2. 通过 CLI 复制源代码**
|
||||||
|
|
||||||
|
如果你希望对 `VariableSelector` 进行更深度的定制,我们也提供了通过 CLI 将组件源代码直接复制到你的项目中的方式。这样,你就可以随心所欲地修改它,以满足你独特的业务需求。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx @flowgram.ai/materials components/variable-selector
|
npx @flowgram.ai/materials components/variable-selector
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `ScopeAvailableData`:你的变量百宝箱
|
||||||
|
|
||||||
|
`ScopeAvailableData` 对象是变量系统的核心之一,它由 `useScopeAvailable` Hook 返回,是你与作用域内可用变量进行交互的主要桥梁。你可以把它想象成一个功能强大的“变量工具箱”。
|
||||||
|
|
||||||
|
### `useScopeAvailable`
|
||||||
|
|
||||||
|
`useScopeAvailable` 是一个功能更强大的 Hook,它能够返回一个 `ScopeAvailableData` 对象,其中不仅包含了当前作用域所有可用的变量信息,还提供了一些高级 API,比如 `trackByKeyPath`。
|
||||||
|
|
||||||
|
它与 `useAvailableVariables` 的主要区别在于:
|
||||||
|
|
||||||
|
* **返回值不同**:`useAvailableVariables` 直接返回变量数组,而 `useScopeAvailable` 返回的是一个包含了 `variables` 属性以及其他方法的 `ScopeAvailableData` 对象。
|
||||||
|
* **适用场景**:当你需要对变量进行更复杂的操作,比如追踪单个变量的变化时,`useScopeAvailable` 是你的不二之选。
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
|
||||||
|
// available 对象上包含了变量列表和其他 API
|
||||||
|
console.log(available.variables);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 获取变量列表
|
||||||
|
|
||||||
|
最基础的用法就是获取当前作用域下所有可用的变量。
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
function MyComponent() {
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
|
||||||
|
// available.variables 就是一个包含了所有可用变量的数组
|
||||||
|
console.log(available.variables);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{available.variables.map(variable => (
|
||||||
|
<li key={variable.key}>{variable.name}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 追踪单个变量的变化:`trackByKeyPath`
|
||||||
|
|
||||||
|
当你只关心某个特定变量(尤其是嵌套在 Object 或 Array 中的变量)的变化时,`trackByKeyPath` 就派上用场了。它能让你精准地“订阅”这个变量的更新,而不会因为其他不相关变量的变化导致组件重新渲染,从而实现更精细的性能优化。
|
||||||
|
|
||||||
|
假设我们有一个名为 `user` 的 Object 类型变量,它有一个 `name` 属性。我们希望在 `user.name` 变化时更新组件。
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
function UserNameDisplay() {
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
const [userName, setUserName] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 定义我们要追踪的变量路径
|
||||||
|
const keyPath = ['user', 'name'];
|
||||||
|
|
||||||
|
// 开始追踪!
|
||||||
|
const disposable = available.trackByKeyPath(keyPath, (nameField) => {
|
||||||
|
// 当 user.name 变量字段变化时,这个回调函数会被触发
|
||||||
|
// nameField 就是那个变化的变量字段,我们可以从中获取最新的默认值
|
||||||
|
setUserName(nameField?.meta.default || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时,别忘了取消追踪,避免内存泄漏
|
||||||
|
return () => disposable.dispose();
|
||||||
|
}, [available]); // 依赖项是 available 对象
|
||||||
|
|
||||||
|
return <div>User Name: {userName}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 高级事件监听 API
|
||||||
|
|
||||||
|
除了 `trackByKeyPath`,`ScopeAvailableData` 还提供了一套更底层的事件监听 API,让你能够更精细地控制变量变化的响应逻辑。这在处理一些复杂的、需要手动管理订阅的场景时非常有用。
|
||||||
|
|
||||||
|
下面我们通过一个表格来详细对比这三个核心的监听 API:
|
||||||
|
|
||||||
|
| API | 触发时机 | 回调参数 | 核心区别与适用场景 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `onVariableListChange` | 当可用变量的**列表结构**发生变化时。 | `(variables: VariableDeclaration[]) => void` | **只关心列表本身**。比如,上游节点新增/删除了一个输出变量,导致可用变量的总数或成员发生了变化。它不关心变量内部和下钻的改变。适用于需要根据变量列表的有无或数量来更新 UI 的场景。 |
|
||||||
|
| `onAnyVariableChange` | 当列表中**任意一个**变量的**类型,元数据和下钻字段**发生变化时。 | `(changedVariable: VariableDeclaration) => void` | **只关心变量内容的更新**。比如,用户修改了一个输出变量的类型。它不关心列表结构的变化。适用于需要对任何一个变量的内容变化做出反应的场景。 |
|
||||||
|
| `onListOrAnyVarChange` | 以上两种情况**任意一种**发生时。 | `(variables: VariableDeclaration[]) => void` | **最全面的监听**,是前两者的结合。无论是列表结构变化,还是任何一个变量的变化,都会触发。适用于需要对任何可能的变化都进行响应的“兜底”场景。 |
|
||||||
|
|
||||||
|
#### 代码示例
|
||||||
|
|
||||||
|
让我们通过一个具体的例子来看看如何在组件中使用这些 API。
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
function AdvancedListenerComponent() {
|
||||||
|
const available = useScopeAvailable();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// 1. 监听列表结构变化
|
||||||
|
const listChangeDisposable = available.onVariableListChange((variables) => {
|
||||||
|
console.log('可用变量列表的结构变了!新的列表长度是:', variables.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 监听任意变量的变化
|
||||||
|
const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
|
||||||
|
console.log(`变量 '${changedVariable.keyPath.join('.')}' 的类型、下钻或者 meta 变了`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. 监听所有变化(结构或单个变量内部)
|
||||||
|
const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
|
||||||
|
console.log('变量列表或其中某个变量发生了变化!');
|
||||||
|
// 注意:这里的回调参数是完整的变量列表,而不是单个变化的变量
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在组件卸载时,务必清理所有的监听器,防止内存泄漏
|
||||||
|
return () => {
|
||||||
|
listChangeDisposable.dispose();
|
||||||
|
valueChangeDisposable.dispose();
|
||||||
|
allChangesDisposable.dispose();
|
||||||
|
};
|
||||||
|
}, [available]);
|
||||||
|
|
||||||
|
return <div>请在控制台查看变量变化的日志...</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键点**:
|
||||||
|
|
||||||
|
* 这些 API 返回的都是一个 `Disposable` 对象。
|
||||||
|
* 为了避免内存泄漏和不必要的计算,你必须在 `useEffect` 的清理函数中调用其 `dispose()` 方法来取消监听。
|
||||||
|
|||||||
@ -1,146 +1,54 @@
|
|||||||
# 输出变量
|
# 输出变量
|
||||||
|
|
||||||
|
在 Flowgram 中,变量是连接各个节点的关键枢纽。一个节点的输出,往往是另一个节点的输入。因此,如何优雅地定义和输出变量,就成了一门必修课。本文将带你探索在 Flowgram 中输出变量的各种姿势。
|
||||||
|
|
||||||
|
我们主要将变量分为两类:
|
||||||
|
|
||||||
|
- **节点变量**:作用域仅限于单个节点,通常作为该节点的产出,供后续节点使用。
|
||||||
|
- **全局变量**:作用域贯穿整个流程,任何节点都可以读取和修改,适合存放一些公共状态或配置。
|
||||||
|
|
||||||
## 输出节点变量
|
## 输出节点变量
|
||||||
|
|
||||||
### FlowNodeVariableData 输出变量
|
输出节点变量,意味着这个变量和当前节点的生命周期是绑定的。当节点被创建时,变量就诞生了;当节点被删除时,变量也随之消逝。我们通常有两种方式来输出节点变量:
|
||||||
|
|
||||||
`Flowgram` 基于 [`ECS`](https://flowgram.ai/guide/concepts/ecs.html) (Entity-Component-System) 来实现节点信息的管理。
|
1. **通过表单配置**:在节点的设置面板(Form)中,通过一些预设的配置或自定义的逻辑来声明输出变量。这种方式非常直观,所见即所得。
|
||||||
|
2. **通过 API 调用**:在插件(Plugin)中,通过调用 `getNodeScope` API 来动态地为节点添加、修改或删除变量。这种方式更加灵活,适合处理复杂的、动态的业务场景。
|
||||||
|
|
||||||
其中 [`FlowNodeVariableData`](https://flowgram.ai/auto-docs/editor/classes/FlowNodeVariableData.html) 是节点 `FlowNodeEntity` 上的一个 `Component`,专门用于处理节点上输出的 **变量信息**。
|
接下来,我们将深入探讨这两种方式的具体用法。
|
||||||
|
|
||||||
|
### 方式一:通过表单配置输出
|
||||||
|
|
||||||
下面的 Demo 展示了:如何拿到 `FlowNodeVariableData`, 并且通过 `FlowNodeVariableData` 实现在节点上输出变量
|
在节点的 `form-meta.ts` 文件中进行配置,是定义节点输出变量最常见的方式。它又可以分为两种玩法:一种是“懒人版”,使用我们内置的物料;另一种是“大神版”,完全自定义。
|
||||||
|
|
||||||
|
#### `provideJsonSchemaOutputs` 物料
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
如果你的节点需要输出的变量,其结构恰好和表单的 `JSON Schema` 结构一致,那么恭喜你,`provideJsonSchemaOutputs` 这个副作用(Effect)物料就是为你量身定做的!它就像一个自动化的“变量搬运工”,能自动将表单中的数据,原封不动地转换为节点的输出变量。
|
||||||
|
|
||||||
|
使用起来非常简单,只需要在 `formMeta` 的 `effect` 中加上两行配置即可:
|
||||||
|
|
||||||
|
```tsx pure title="form-meta.ts"
|
||||||
import {
|
import {
|
||||||
FlowNodeVariableData,
|
syncVariableTitle,
|
||||||
ASTFactory,
|
provideJsonSchemaOutputs,
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/form-materials';
|
||||||
|
|
||||||
// ....
|
export const formMeta = {
|
||||||
|
effect: {
|
||||||
flowDocument.onNodeCreate(({ node }) => {
|
title: syncVariableTitle, // 变量标题自动同步
|
||||||
const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
|
outputs: provideJsonSchemaOutputs,
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
// 1. Clear VariableData if No value
|
|
||||||
variableData.clearVar()
|
|
||||||
|
|
||||||
// 2. Set a String Variable as output
|
|
||||||
variableData.setVar(
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title`,
|
|
||||||
},
|
},
|
||||||
key: `your_variable_global_unique_key_${node.id}`,
|
};
|
||||||
type: ASTFactory.createString(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// 3. Set a Complicated Variable Data as output
|
|
||||||
variableData.setVar(
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title`,
|
|
||||||
},
|
|
||||||
key: `your_variable_global_unique_key_${node.id}`,
|
|
||||||
type: ASTFactory.createArray({
|
|
||||||
items: ASTFactory.createObject({
|
|
||||||
properties: [
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'stringType',
|
|
||||||
type: ASTFactory.createString(),
|
|
||||||
}),
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'booleanType',
|
|
||||||
type: ASTFactory.createBoolean(),
|
|
||||||
}),
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'numberType',
|
|
||||||
type: ASTFactory.createNumber(),
|
|
||||||
}),
|
|
||||||
ASTFactory.createProperty({
|
|
||||||
key: 'integerType',
|
|
||||||
type: ASTFactory.createInteger(),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// 4. Get Variable for current Node
|
|
||||||
console.log(variableData.getVar())
|
|
||||||
|
|
||||||
// ....
|
|
||||||
})
|
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
详见: [> Demo Detail](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout/src/plugins/sync-variable-plugin/sync-variable-plugin.ts#L25)
|
#### 通过表单副作用自定义输出
|
||||||
|
|
||||||
### 一个节点设置多个输出变量
|
`provideJsonSchemaOutputs` 虽然方便,但它只适配 JsonSchema。
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
如果你想要定义自己的一套 Schema,那么你就需要自定义表单的副作用了。
|
||||||
import {
|
|
||||||
FlowNodeVariableData,
|
|
||||||
ASTFactory,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
|
||||||
|
|
||||||
// ....
|
Flowgram 提供了一个强大的辅助函数 `createEffectFromVariableProvider`,它能帮你轻松地创建一个用于提供变量的副作用。你可以把它想象成一个“变量加工厂”,输入的是表单数据,输出的是你精心加工后的变量。
|
||||||
|
|
||||||
flowDocument.onNodeCreate(({ node }) => {
|
下面这个例子中,我们为表单的两个字段 `path.to.value` 和 `path.to.value2` 分别创建了输出变量。当用户在表单中填写这两个字段时,`parse` 函数就会被触发,将用户输入的值(`v`)转换成一个标准的变量声明对象。
|
||||||
const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
|
|
||||||
|
|
||||||
// ...
|
|
||||||
// 1. Create, Update, Read, Delete Variable in namespace_1
|
|
||||||
variableData.setVar(
|
|
||||||
'namespace_1',
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title`,
|
|
||||||
},
|
|
||||||
key: `your_variable_global_unique_key_${node.id}`,
|
|
||||||
type: ASTFactory.createString(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(variableData.getVar('namespace_1'))
|
|
||||||
|
|
||||||
variableData.clearVar('namespace_1')
|
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
// 2. Create, Update, Read, Delete Variable in namespace_2
|
|
||||||
variableData.setVar(
|
|
||||||
'namespace_2',
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `Your Output Variable Title 2`,
|
|
||||||
},
|
|
||||||
key: `your_variable_global_unique_key_${node.id}_2`,
|
|
||||||
type: ASTFactory.createString(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(variableData.getVar('namespace_2'))
|
|
||||||
|
|
||||||
variableData.clearVar('namespace_2')
|
|
||||||
|
|
||||||
// ....
|
|
||||||
})
|
|
||||||
|
|
||||||
// ....
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
更多用法,详见:[Class: FlowNodeVariableData](https://flowgram.ai/auto-docs/editor/classes/FlowNodeVariableData.html)
|
|
||||||
|
|
||||||
### 表单副作用设置输出变量
|
|
||||||
|
|
||||||
```tsx pure title="node-registries.ts"
|
```tsx pure title="node-registries.ts"
|
||||||
import {
|
import {
|
||||||
@ -209,14 +117,85 @@ export const nodeRegistries: FlowNodeRegistry[] = [
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 输出全局变量
|
### 方式二:使用 `getNodeScope` API
|
||||||
|
|
||||||
### 获取全局变量作用域
|
除了在表单中静态配置,我们还可以在插件(Plugin)中,通过 `getNodeScope` API 来获取作用域,从而动态地操作节点的变量。
|
||||||
|
|
||||||
全局作用域可以在 Plugin 中通过 `ctx` 获取:
|
这种方式赋予了你极高的自由度,你可以在任何时机、任何地点,对节点的变量进行增、删、改、查。
|
||||||
|
|
||||||
|
`getNodeScope` 会返回一个节点的变量作用域(Scope)对象,这个对象上挂载了几个核心方法:
|
||||||
|
|
||||||
|
- `setVar(variable)`: 设置一个变量。
|
||||||
|
- `setVar(namespace, variable)`: 在指定的命名空间下设置一个变量。
|
||||||
|
- `getVar()`: 获取所有变量。
|
||||||
|
- `getVar(namespace)`: 获取指定命名空间下的变量。
|
||||||
|
- `clearVar()`: 清空所有变量。
|
||||||
|
- `clearVar(namespace)`: 清空指定命名空间下的变量。
|
||||||
|
|
||||||
|
下面的例子演示了如何在插件的 `onInit`生命周期中,获取开始节点的 `Scope`,并对它的变量进行一系列操作。
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
```tsx pure title="sync-variable-plugin.tsx"
|
||||||
|
import {
|
||||||
|
FlowDocument,
|
||||||
|
definePluginCreator,
|
||||||
|
PluginCreator,
|
||||||
|
getNodeScope,
|
||||||
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
|
||||||
|
export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
||||||
|
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
||||||
|
onInit(ctx, options) {
|
||||||
|
const startNode = ctx.get(FlowDocument).getNode('start_0');
|
||||||
|
const startScope = getNodeScope(startNode)
|
||||||
|
|
||||||
|
// 1. Set Variable For Start Scope
|
||||||
|
startScope.setVar(
|
||||||
|
ASTFactory.createVariableDeclaration({
|
||||||
|
meta: {
|
||||||
|
title: `Your Output Variable Title`,
|
||||||
|
},
|
||||||
|
key: `your_variable_unique_key`,
|
||||||
|
type: ASTFactory.createString(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. Create, Update, Read, Delete Variable in namespace_2
|
||||||
|
startScope.setVar(
|
||||||
|
'namespace_2',
|
||||||
|
ASTFactory.createVariableDeclaration({
|
||||||
|
meta: {
|
||||||
|
title: `Your Output Variable Title 2`,
|
||||||
|
},
|
||||||
|
key: `your_variable_global_unique_key_2`,
|
||||||
|
type: ASTFactory.createString(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(startScope.getVar('namespace_2'))
|
||||||
|
|
||||||
|
// 3. Delete Variable in namespace_2
|
||||||
|
startScope.clearVar('namespace_2')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 输出全局变量
|
||||||
|
|
||||||
|
全局变量就像是整个流程的“共享内存”,任何节点、任何插件都可以访问和修改它。它非常适合用来存储一些贯穿始终的状态,比如用户信息、环境配置等等。
|
||||||
|
|
||||||
|
和节点变量类似,我们也有两种主要的方式来获取全局变量的作用域(`GlobalScope`)。
|
||||||
|
|
||||||
|
### 方式一:在插件中获取
|
||||||
|
|
||||||
|
在插件的上下文中(`ctx`),我们可以直接“注入”`GlobalScope` 的实例:
|
||||||
|
|
||||||
|
|
||||||
|
```tsx pure title="global-variable-plugin.tsx"
|
||||||
import {
|
import {
|
||||||
GlobalScope,
|
GlobalScope,
|
||||||
definePluginCreator,
|
definePluginCreator,
|
||||||
@ -224,7 +203,7 @@ import {
|
|||||||
} from '@flowgram.ai/fixed-layout-editor';
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
|
||||||
export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
||||||
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
||||||
onInit(ctx, options) {
|
onInit(ctx, options) {
|
||||||
const globalScope = ctx.get(GlobalScope)
|
const globalScope = ctx.get(GlobalScope)
|
||||||
@ -244,7 +223,9 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
也可以在画布中的 React 组件内,通过 `useService` 获取全局作用域:
|
### 方式二:在 React 组件中获取
|
||||||
|
|
||||||
|
如果你想在画布的 React 组件中与全局变量交互,可以使用 `useService` 这个 Hook 来获取 `GlobalScope` 的实例:
|
||||||
|
|
||||||
```tsx pure title="global-variable-component.tsx"
|
```tsx pure title="global-variable-component.tsx"
|
||||||
import {
|
import {
|
||||||
@ -277,9 +258,11 @@ function GlobalVariableComponent() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 全局作用域输出变量
|
### 全局作用域的 API
|
||||||
|
|
||||||
[`GlobalScope`](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html) 输出变量的 API 和 `FlowNodeVariableData` 类似:
|
`GlobalScope` 的 API 设计得和节点作用域(`NodeScope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar` 等方法,并且同样支持命名空间(namespace)。这种一致性设计大大降低了我们的学习成本。
|
||||||
|
|
||||||
|
下面是一个在插件中操作全局变量的综合示例:
|
||||||
|
|
||||||
```tsx pure title="sync-variable-plugin.tsx"
|
```tsx pure title="sync-variable-plugin.tsx"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -646,13 +646,9 @@ export class WorkflowDragService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.setLineColor(line, this.linesManager.lineColor.drawing);
|
this.setLineColor(line, this.linesManager.lineColor.drawing);
|
||||||
if (toNode && !this.isContainer(toNode)) {
|
if (toNode) {
|
||||||
// 如果鼠标 hover 在 node 中的时候,默认连线到这个 node 的初始位置
|
// 如果鼠标 hover 在 node 中的时候,默认连线到这个 node 的初始位置
|
||||||
const portsData = toNode.getData(WorkflowNodePortsData)!;
|
toPort = this.getNearestPort(toNode, dragPos);
|
||||||
const { inputPorts } = portsData;
|
|
||||||
if (inputPorts.length === 1) {
|
|
||||||
toPort = inputPorts[0];
|
|
||||||
}
|
|
||||||
const { hasError } = this.handleDragOnNode(toNode, fromPort, line, toPort, originLine);
|
const { hasError } = this.handleDragOnNode(toNode, fromPort, line, toPort, originLine);
|
||||||
lineErrorReset = hasError;
|
lineErrorReset = hasError;
|
||||||
}
|
}
|
||||||
@ -783,4 +779,15 @@ export class WorkflowDragService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取最近的 port */
|
||||||
|
private getNearestPort(node: WorkflowNodeEntity, mousePos: IPoint): WorkflowPortEntity {
|
||||||
|
const portsData = node.getData(WorkflowNodePortsData)!;
|
||||||
|
const distanceSortedPorts = portsData.inputPorts.sort((a, b) => {
|
||||||
|
const aDistance = Math.abs(mousePos.y - a.point.y);
|
||||||
|
const bDistance = Math.abs(mousePos.y - b.point.y);
|
||||||
|
return aDistance - bDistance;
|
||||||
|
});
|
||||||
|
return distanceSortedPorts[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,11 @@ import { TransformData } from '@flowgram.ai/editor';
|
|||||||
|
|
||||||
type AutoLayoutResetFn = () => void;
|
type AutoLayoutResetFn = () => void;
|
||||||
|
|
||||||
type AutoLayoutFn = (options?: LayoutOptions) => Promise<AutoLayoutResetFn>;
|
export type AutoLayoutOptions = LayoutOptions & {
|
||||||
|
disableFitView?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AutoLayoutFn = (options?: AutoLayoutOptions) => Promise<AutoLayoutResetFn>;
|
||||||
|
|
||||||
type UseAutoLayout = () => AutoLayoutFn;
|
type UseAutoLayout = () => AutoLayoutFn;
|
||||||
|
|
||||||
@ -124,10 +128,15 @@ export const useAutoLayout: UseAutoLayout = () => {
|
|||||||
[document, playground]
|
[document, playground]
|
||||||
);
|
);
|
||||||
const autoLayout: AutoLayoutFn = useCallback(
|
const autoLayout: AutoLayoutFn = useCallback(
|
||||||
async (options?: LayoutOptions): Promise<AutoLayoutResetFn> => {
|
async (options: AutoLayoutOptions = {}): Promise<AutoLayoutResetFn> => {
|
||||||
|
const { disableFitView } = options;
|
||||||
|
if (disableFitView !== true) {
|
||||||
handleFitView();
|
handleFitView();
|
||||||
|
}
|
||||||
const resetFn: AutoLayoutResetFn = await applyLayout(options);
|
const resetFn: AutoLayoutResetFn = await applyLayout(options);
|
||||||
|
if (disableFitView !== true) {
|
||||||
handleFitView();
|
handleFitView();
|
||||||
|
}
|
||||||
return resetFn;
|
return resetFn;
|
||||||
},
|
},
|
||||||
[applyLayout]
|
[applyLayout]
|
||||||
|
|||||||
@ -16,10 +16,9 @@ import {
|
|||||||
usePlayground,
|
usePlayground,
|
||||||
useService,
|
useService,
|
||||||
} from '@flowgram.ai/free-layout-core';
|
} from '@flowgram.ai/free-layout-core';
|
||||||
import { LayoutOptions } from '@flowgram.ai/free-auto-layout-plugin';
|
|
||||||
import { EditorState } from '@flowgram.ai/editor';
|
import { EditorState } from '@flowgram.ai/editor';
|
||||||
|
|
||||||
import { useAutoLayout } from './use-auto-layout';
|
import { useAutoLayout, type AutoLayoutOptions } from './use-auto-layout';
|
||||||
|
|
||||||
interface SetCursorStateCallbackEvent {
|
interface SetCursorStateCallbackEvent {
|
||||||
isPressingSpaceBar: boolean;
|
isPressingSpaceBar: boolean;
|
||||||
@ -31,7 +30,7 @@ export interface PlaygroundTools {
|
|||||||
zoomin: (easing?: boolean) => void;
|
zoomin: (easing?: boolean) => void;
|
||||||
zoomout: (easing?: boolean) => void;
|
zoomout: (easing?: boolean) => void;
|
||||||
fitView: (easing?: boolean) => void;
|
fitView: (easing?: boolean) => void;
|
||||||
autoLayout: (options?: LayoutOptions) => Promise<() => void>;
|
autoLayout: (options?: AutoLayoutOptions) => Promise<() => void>;
|
||||||
/**
|
/**
|
||||||
* 切换线条
|
* 切换线条
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export interface LayoutParams {
|
|||||||
|
|
||||||
export interface LayoutOptions {
|
export interface LayoutOptions {
|
||||||
getFollowNode?: GetFollowNode;
|
getFollowNode?: GetFollowNode;
|
||||||
enableAnimation: boolean;
|
enableAnimation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LayoutConfig {
|
export interface LayoutConfig {
|
||||||
|
|||||||
@ -28,12 +28,11 @@ export const buildLine: IBuildLine = (params) => {
|
|||||||
const shouldBuildFromLine = portsData.inputPorts?.length > 0;
|
const shouldBuildFromLine = portsData.inputPorts?.length > 0;
|
||||||
if (fromPort && shouldBuildFromLine) {
|
if (fromPort && shouldBuildFromLine) {
|
||||||
const toTargetPort = portsData.inputPorts[0];
|
const toTargetPort = portsData.inputPorts[0];
|
||||||
const isSingleInput = portsData.inputPorts.length === 1;
|
|
||||||
linesManager.createLine({
|
linesManager.createLine({
|
||||||
from: fromPort.node.id,
|
from: fromPort.node.id,
|
||||||
fromPort: fromPort.portID,
|
fromPort: fromPort.portID,
|
||||||
to: node.id,
|
to: node.id,
|
||||||
toPort: isSingleInput ? undefined : toTargetPort.id,
|
toPort: toTargetPort.portID,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const shouldBuildToLine = portsData.outputPorts?.length > 0;
|
const shouldBuildToLine = portsData.outputPorts?.length > 0;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user