Explorar el Código

feat(material): filter schema for PromptEditorWithVariables, useOutputVariables API (#947)

* feat: use output variables

* feat(material): string only for prompt editor with variables
Yiwei Mao hace 2 meses
padre
commit
61d98b52a6

+ 37 - 0
apps/docs/components/form-materials/components/prompt-editor-with-variables.tsx

@@ -15,6 +15,12 @@ const PromptEditorWithVariables = React.lazy(() =>
   }))
   }))
 );
 );
 
 
+const VariableSelectorProvider = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.VariableSelectorProvider,
+  }))
+);
+
 export const BasicStory = () => (
 export const BasicStory = () => (
   <FreeFormMetaStoryBuilder
   <FreeFormMetaStoryBuilder
     filterEndNode
     filterEndNode
@@ -45,3 +51,34 @@ You are a helpful assistant
     }}
     }}
   />
   />
 );
 );
+
+const STRING_ONLY_SCHEMA = { type: 'string' };
+export const StringOnlyStory = () => (
+  <FreeFormMetaStoryBuilder
+    filterEndNode
+    formMeta={{
+      render: () => (
+        <div style={{ width: 400 }}>
+          <FormHeader />
+          <VariableSelectorProvider includeSchema={STRING_ONLY_SCHEMA}>
+            <Field<any | undefined>
+              name="prompt_editor"
+              defaultValue={{
+                type: 'template',
+                content: `# Role
+You are a helpful assistant`,
+              }}
+            >
+              {({ field }) => (
+                <PromptEditorWithVariables
+                  value={field.value}
+                  onChange={(value) => field.onChange(value)}
+                />
+              )}
+            </Field>
+          </VariableSelectorProvider>
+        </div>
+      ),
+    }}
+  />
+);

+ 148 - 72
apps/docs/src/en/guide/variable/variable-consume.mdx

@@ -1,25 +1,24 @@
 ---
 ---
-description: Learn how to consume variables from the variable engine in FlowGram
+description: Introduction to consuming variables output by FlowGram's variable engine
 ---
 ---
 
 
-# Consume Variables
+# Consuming Variables
 
 
-In FlowGram, when a node wants to use variables from a preceding node, it needs to consume them.
+In FlowGram, when a node wants to use variables from preceding nodes, it needs to consume those variables.
 
 
 ## Official Material: `VariableSelector`
 ## Official Material: `VariableSelector`
 
 
-To make it easier for you to integrate variable selection functionality into your application, the official materials provide the `VariableSelector` component.
+To make it easier for you to integrate variable selection functionality into your applications, the official materials provide the `VariableSelector` component.
 
 
-For details, please read the documentation: [VariableSelector](/materials/components/variable-selector)
+See documentation: [VariableSelector](/materials/components/variable-selector)
 
 
+## Getting Accessible Variable Tree
 
 
-## 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.
+In canvas nodes, we often need to get **variables available in the current scope** and display them in a tree structure for users to select and operate.
 
 
 ### `useAvailableVariables`
 ### `useAvailableVariables`
 
 
-`useAvailableVariables` is a lightweight Hook that directly returns an array of available variables (`VariableDeclaration[]`) in the current scope.
+`useAvailableVariables` is a lightweight Hook that directly returns an array of variables available in the current scope (`VariableDeclaration[]`).
 
 
 ```tsx pure title="use-variable-tree.tsx" {7}
 ```tsx pure title="use-variable-tree.tsx" {7}
 import {
 import {
@@ -27,7 +26,7 @@ import {
   useAvailableVariables,
   useAvailableVariables,
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
-// .... In a React component or Hook
+// .... In React component or Hook
 const availableVariables = useAvailableVariables();
 const availableVariables = useAvailableVariables();
 
 
 const renderVariable = (variable: BaseVariableField) => {
 const renderVariable = (variable: BaseVariableField) => {
@@ -40,9 +39,9 @@ return availableVariables.map(renderVariable);
 // ....
 // ....
 ```
 ```
 
 
-### Drilling Down into Object Type Variables
+### Getting Object Type Variable Drill-down
 
 
-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`.
+When a variable's type is `Object`, we often need to be able to "drill down" into its interior to access its properties. The `ASTMatch.isObject` method can help us determine if a variable type is an object. If it is, we can recursively render its `properties`.
 
 
 ```tsx pure title="use-variable-tree.tsx" {12}
 ```tsx pure title="use-variable-tree.tsx" {12}
 import {
 import {
@@ -60,12 +59,11 @@ const renderVariable = (variable: BaseVariableField) => ({
 });
 });
 
 
 // ....
 // ....
-
 ```
 ```
 
 
-### Drilling Down into Array Type Variables
+### Getting Array Type Variable Drill-down
 
 
-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.
+Similar to `Object` type, when encountering an `Array` type variable, we also want to display its internal structure. For arrays, we usually care about the type of their elements. `ASTMatch.isArray` can determine if a variable type is an array. It's worth noting that the element type of an array can be any type, and it might even be another array. Therefore, we need a recursive helper function `getTypeChildren` to handle this situation.
 
 
 ```tsx pure title="use-variable-tree.tsx" {13,16}
 ```tsx pure title="use-variable-tree.tsx" {13,16}
 import {
 import {
@@ -79,10 +77,10 @@ import {
 const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
 const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
   if (!type) return [];
   if (!type) return [];
 
 
-  // Get the properties of an Object
+  // Get Object properties
   if (ASTMatch.isObject(type)) return type.properties;
   if (ASTMatch.isObject(type)) return type.properties;
 
 
-  // Recursively get the element type of an Array
+  // Recursively get Array element type
   if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
   if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
 
 
   return [];
   return [];
@@ -95,67 +93,85 @@ const renderVariable = (variable: BaseVariableField) => ({
 });
 });
 
 
 // ....
 // ....
-
 ```
 ```
 
 
+## `scope.available`
 
 
-## `ScopeAvailableData`
-
-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**.
+`scope.available` is one of the cores of the variable system, which can perform more advanced variable retrieval and monitoring actions on **variables available within the scope**.
 
 
 ### `useScopeAvailable`
 ### `useScopeAvailable`
 
 
-`useScopeAvailable` 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.
+`useScopeAvailable` can directly return `scope.available` in React
 
 
 ```tsx
 ```tsx
-import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
+import { useScopeAvailable } from '@flowgram.ai/free-layout-editor';
 
 
 const available = useScopeAvailable();
 const available = useScopeAvailable();
 
 
-// The available object contains the variable list and other APIs
+// The available object contains variable list and other APIs
 console.log(available.variables);
 console.log(available.variables);
+
+// Get a single variable
+console.log(available.getByKeyPath(['start_0', 'xxx']));
+
+// Monitor changes in a single variable
+available.trackByKeyPath(['start_0', xxx], () => {
+  // ...
+})
+```
+
+:::info{title="Main Difference from useAvailableVariables"}
+
+*   **Return Value Different**: `useAvailableVariables` directly returns an array of variables, while `useScopeAvailable` returns a `ScopeAvailableData` object that includes a `variables` property and other methods.
+*   **Applicable Scenario**: When you need to perform more complex operations on variables, such as tracking changes in a single variable through `trackByKeyPath`, `useScopeAvailable` is your best choice.
+
+:::
+
+:::warning{title="useScopeAvailable automatically refreshes when available variables change"}
+
+If you don't want automatic refresh, you can turn it off through the autoRefresh parameter:
+
+```tsx
+useScopeAvailable({ autoRefresh: false })
 ```
 ```
+:::
 
 
-### Getting the Variable List
+### `getByKeyPath`
 
 
-The most basic usage is to get all the available variables in the current scope.
+Through `getByKeyPath`, you can get a specific variable field (including variables nested in Object or Array) from the accessible variables in the current scope.
 
 
-```tsx {4,7}
+```tsx {6,13-17}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
+import { useEffect, useState } from 'react';
 
 
-function MyComponent() {
+function VariableDisplay({ keyPath }: { keyPath:string[] }) {
   const available = useScopeAvailable();
   const available = useScopeAvailable();
+  const variableField = available.getByKeyPath(keyPath)
+
+  return <div>{variableField.meta?.title}</div>;
+}
+```
 
 
-  // available.variables is an array containing all available variables
-  console.log(available.variables);
+`getByKeyPath` is often used in variable validation, such as:
 
 
-  return (
-    <ul>
-      {available.variables.map(variable => (
-        <li key={variable.key}>{variable.name}</li>
-      ))}
-    </ul>
-  );
+```tsx
+const validateVariableInNode = (keyPath: string, node: FlowNodeEntity) => {
+  // Validate whether the variable can be accessed by the current node
+  return Boolean(node.scope.available.getByKeyPath(keyPath))
 }
 }
 ```
 ```
 
 
 ### `trackByKeyPath`
 ### `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.
+When you only care about changes to a specific variable field (including variables nested in Object or Array), `trackByKeyPath` allows you to precisely "subscribe" to updates of that variable without causing component re-renders due to changes in other unrelated variables, thus achieving more refined performance optimization.
 
 
-```tsx {13-17}
+```tsx {6,13-17}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 
 
 function UserNameDisplay() {
 function UserNameDisplay() {
-  const available = useScopeAvailable();
+  // Turn off autoRefresh to prevent re-renders triggered by any variable changes
+  const available = useScopeAvailable({ autoRefresh: false });
   const [userName, setUserName] = useState('');
   const [userName, setUserName] = useState('');
 
 
   useEffect(() => {
   useEffect(() => {
@@ -164,12 +180,12 @@ function UserNameDisplay() {
 
 
     // Start tracking!
     // Start tracking!
     const disposable = available.trackByKeyPath(keyPath, (nameField) => {
     const disposable = available.trackByKeyPath(keyPath, (nameField) => {
-      // This callback function will be triggered when user.name changes
+      // When the user.name variable field changes, this callback function will be triggered
       // nameField is the changed variable field, from which we can get the latest default value
       // nameField is the changed variable field, from which we can get the latest default value
       setUserName(nameField?.meta.default || '');
       setUserName(nameField?.meta.default || '');
     });
     });
 
 
-    // When the component unmounts, don't forget to cancel the tracking to avoid memory leaks
+    // Cancel tracking when the component unmounts to avoid memory leaks
     return () => disposable.dispose();
     return () => disposable.dispose();
   }, [available]); // The dependency is the available object
   }, [available]); // The dependency is the available object
 
 
@@ -177,46 +193,44 @@ function UserNameDisplay() {
 }
 }
 ```
 ```
 
 
-### Global Listening API
-
-In addition to `trackByKeyPath`, `ScopeAvailableData` also provides a set of global variable change event listening APIs that allow you to control the response logic to variable changes more finely.
+### Overall Listening API
 
 
-This is very useful when dealing with complex scenarios that require manual subscription management.
+In addition to `trackByKeyPath`, `ScopeAvailableData` also provides a set of event listening APIs for overall variable changes, allowing you to more precisely control the response logic for variable changes.
 
 
-Let's use a table to compare these three core listening APIs in detail:
+This is very useful when dealing with complex scenarios that require manual management of subscriptions.
 
 
-| 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 **type, metadata, and drilldown fields** of **any** variable in the list change. | `(changedVariable: VariableDeclaration) => void` | **Only cares about content updates**. 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 any variable. Suitable for "catch-all" scenarios where you need to respond to any possible changes. |
+Below we use a table to compare these three core listening APIs in detail:
 
 
-#### Code Example
+| API & Callback Parameters | Trigger Timing | Core Difference and Applicable Scenario |
+| :--- | :--- | :--- |
+| `onVariableListChange: (variables: VariableDeclaration[]) => void` | When the **list structure** of available variables changes. | **Only cares about the list itself**. For example, an upstream node added/removed an output variable, causing the total number or members of available variables to change. It doesn't care about changes within variables and drill-downs. Applicable to scenarios where you need to update UI based on the presence or quantity of variable lists. |
+| `onAnyVariableChange: (changedVariable: VariableDeclaration) => void` | When the **type, metadata, and drill-down fields** of **any** variable in the list change. | **Only cares about updates to variable definitions**. For example, a user modified the type of an output variable. It doesn't care about changes to the list structure. Applicable to scenarios where you need to respond to changes in the content of any variable. |
+| `onListOrAnyVarChange: (variables: VariableDeclaration[]) => void` | When **either** of the above two situations occurs. | **The most comprehensive listening**, combining the previous two. Both changes to the list structure and changes to any variable will trigger it. Applicable to "fallback" scenarios where you need to respond to any possible changes. |
 
 
-Let's look at a specific example of how to use these APIs in a component.
+Let's see how to use these APIs in components through a specific example.
 
 
-```tsx {9-11,14-16,19-22}
+```tsx {5,9-11,14-16,19-22}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect } from 'react';
 import { useEffect } from 'react';
 
 
 function AdvancedListenerComponent() {
 function AdvancedListenerComponent() {
-  const available = useScopeAvailable();
+  const available = useScopeAvailable({ autoRefresh: false });
 
 
   useEffect(() => {
   useEffect(() => {
-    // 1. Listen for changes in the list structure
+    // 1. Listen to list structure changes
     const listChangeDisposable = available.onVariableListChange((variables) => {
     const listChangeDisposable = available.onVariableListChange((variables) => {
       console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
       console.log('The structure of the available variable list has changed! The new list length is:', variables.length);
     });
     });
 
 
-    // 2. Listen for changes in any variable
+    // 2. Listen to any variable changes
     const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
     const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
-      console.log(`The type, drilldown, or meta of variable '${changedVariable.keyPath.join('.')}' has changed`);
+      console.log(`The definition of variable '${changedVariable.keyPath.join('.')}' has changed`);
     });
     });
 
 
-    // 3. Listen for all changes (structure or individual variable content)
+    // 3. Listen to all changes (structure or individual variable interior)
     const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
     const allChangesDisposable = available.onListOrAnyVarChange((variables) => {
       console.log('The variable list or one of its variables has changed!');
       console.log('The variable list or one of its variables has changed!');
-      // Note: The callback parameter here is the complete variable list, not the single changed variable
+      // Note: The callback parameter here is the complete variable list, not a single changed variable
     });
     });
 
 
     // When the component unmounts, be sure to clean up all listeners to prevent memory leaks
     // When the component unmounts, be sure to clean up all listeners to prevent memory leaks
@@ -227,11 +241,73 @@ function AdvancedListenerComponent() {
     };
     };
   }, [available]);
   }, [available]);
 
 
-  return <div>Please check the console for logs of variable changes...</div>;
+  return <div>Please check the console for variable change logs...</div>;
 }
 }
 ```
 ```
 
 
-**Key Points**:
+:::warning
+
+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 listening.
+
+:::
 
 
-*   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.
+## Getting Output Variables of Current Scope
+
+### `useOutputVariables`
+
+useOutputVariables can get **output variables of the current scope** and **automatically trigger a refresh** when the output variable list or drill-down changes.
+
+```tsx
+const variables = useOutputVariables();
+```
+
+:::tip
+
+useOutputVariables is available in flowgram@0.5.6 and later versions. If using an earlier version, you can implement it with the following code:
+
+```tsx
+const scope = useCurrentScope();
+const refresh = useRefresh();
+
+useEffect(() => {
+  const disposable = scope.output.onListOrAnyVarChange(() => {
+    refresh();
+  });
+
+  return () => disposable.dispose();
+}, [])
+
+const variables = scope.variables;
+```
+:::
+
+## Other APIs
+
+### Getting Current Scope
+
+You can get the current scope through [`useCurrentScope`](https://flowgram.ai/auto-docs/editor/functions/useCurrentScope).
+
+```tsx
+const scope = useCurrentScope()
+
+scope.output.variables
+
+scope.available
+
+```
+
+### Setting Current Scope
+
+You can set the current scope through [`ScopeProvider`](https://flowgram.ai/auto-docs/editor/functions/ScopeProvider)
+
+```tsx
+// set the scope of current node
+<ScopeProvider scope={node.scope}>
+  <YourUI />
+</ScopeProvider>
+
+// set to private scope of current node
+<ScopeProvider scope={node.privateScope}>
+  <YourUI />
+</ScopeProvider>
+```

+ 66 - 90
apps/docs/src/en/guide/variable/variable-output.mdx

@@ -1,30 +1,30 @@
 ---
 ---
-description: Learn how to use the variable engine to output variables in FlowGram
+description: Introduction to using FlowGram's variable engine to output variables
 ---
 ---
 
 
 # Output Variables
 # Output Variables
 
 
-We mainly divide output variables into three categories:
+We primarily categorize output variables into three types:
 
 
-1. **Output Node Variables**: Usually as the output of that node for subsequent nodes to use.
-2. **Output Node Private Variables**: Output variables are limited to the inside of the node (including child nodes) and cannot be accessed by external nodes.
-3. **Output Global Variables**: Runs through the entire process, and any node can read it. It is suitable for storing some public states or configurations.
+1. **Output Node Variables**: Typically produced by the node and available for subsequent nodes to use.
+2. **Output Node Private Variables**: Output variables limited to the node's interior (including child nodes) and not accessible by external nodes.
+3. **Output Global Variables**: Available throughout the entire flow, readable by any node, suitable for storing public states or configurations.
 
 
-## Outputting Node Variables
+## Output Node Variables
 
 
-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.
+Output node variables are bound to the lifecycle of the current node. When a node is created, the variables are born; when a node is deleted, the variables disappear with it.
 
 
-We usually have three ways to output node variables:
+We typically have three ways to output node variables:
 
 
-### Method 1: Sync via Form Side Effects
+### Method 1: Synchronization via Form Side Effects
 
 
-[Form Side Effects](/guide/form/form#side-effects-effect) are usually configured in the node's `form-meta.ts` file and are the most common way to define node output variables.
+[Form side effects](/guide/form/form#side-effects-effect) are usually configured in the node's `form-meta.ts` file and are the most common way to define node output variables.
 
 
 #### `provideJsonSchemaOutputs` Material
 #### `provideJsonSchemaOutputs` Material
 
 
-If the structure of the output variable required by the node matches the [JSON Schema](https://json-schema.org/) structure, you can use the `provideJsonSchemaOutputs` side effect (Effect) material.
+If the structure of the output variables required by a node matches the [JSON Schema](https://json-schema.org/) structure, you can use the `provideJsonSchemaOutputs` side effect (Effect) material.
 
 
-Just add two lines of configuration in the `effect` of `formMeta`:
+Simply add two lines of configuration to the `effect` of `formMeta`:
 
 
 ```tsx pure title="form-meta.ts" {8-9}
 ```tsx pure title="form-meta.ts" {8-9}
 import {
 import {
@@ -34,27 +34,26 @@ import {
 
 
 export const formMeta = {
 export const formMeta = {
   effect: {
   effect: {
-    title: syncVariableTitle, // Variable title is automatically synchronized
+    title: syncVariableTitle, // Variable title auto-sync
     outputs: provideJsonSchemaOutputs,
     outputs: provideJsonSchemaOutputs,
   },
   },
 };
 };
 ```
 ```
 
 
-#### Customizing Output with `createEffectFromVariableProvider`
+#### `createEffectFromVariableProvider` Custom Output
 
 
-
-`provideJsonSchemaOutputs` only adapts to `JsonSchema`. If you want to define your own set of Schema, then you need to customize the side effects of the form.
+`provideJsonSchemaOutputs` only adapts to `JsonSchema`. If you want to define your own set of Schema, you'll need to customize form side effects.
 
 
 :::note
 :::note
 
 
-FlowGram provides `createEffectFromVariableProvider`, you only need to define a `parse` function to customize your own variable synchronization side effects:
-- `parse` will be called when the form value is initialized and updated
-- The input of `parse` is the value of the current field's form
-- The output of `parse` is the variable AST information
+FlowGram provides `createEffectFromVariableProvider`, which only requires defining a `parse` function to customize your variable synchronization side effect:
+- `parse` is called when the form value is initialized and updated
+- The input of `parse` is the current field's form value
+- The output of `parse` is variable AST information
 
 
 :::
 :::
 
 
-In the following example, we create output variables for the two fields of the form `path.to.value` and `path.to.value2` respectively:
+In the following example, we create output variables for two form fields `path.to.value` and `path.to.value2`:
 
 
 ```tsx pure title="form-meta.ts" {27-38,41-52}
 ```tsx pure title="form-meta.ts" {27-38,41-52}
 import {
 import {
@@ -116,10 +115,9 @@ export const formMeta =  {
 }
 }
 ```
 ```
 
 
-#### Syncing multiple form fields to one variable
-
-If multiple fields are synchronized to one variable, you need to use the `namespace` field of `createEffectFromVariableProvider` to synchronize the variable data of multiple fields to the same namespace.
+#### Synchronizing Multiple Form Fields to One Variable
 
 
+If synchronizing multiple fields to one variable, you need to use the `namespace` field of `createEffectFromVariableProvider` to synchronize variable data from multiple fields to the same namespace.
 
 
 ```tsx pure title="form-meta.ts" {11}
 ```tsx pure title="form-meta.ts" {11}
 import {
 import {
@@ -128,14 +126,15 @@ import {
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
 /**
 /**
- * Get information of multiple fields from the form
+ * Get information from multiple form fields
  */
  */
 const variableSyncEffect = createEffectFromVariableProvider({
 const variableSyncEffect = createEffectFromVariableProvider({
-  // Must be added to ensure that the side effects of different fields are synchronized to the same namespace
+  // Must be added to ensure side effects from different fields synchronize to the same namespace
   namespace: 'your_namespace',
   namespace: 'your_namespace',
 
 
-  // Parse the form value into a variable
+  // Parse form value to variable
   parse(_, { form, node }) {
   parse(_, { form, node }) {
+    // Note: The form field requires flowgram version > 0.5.5
     return [{
     return [{
       meta: {
       meta: {
         title: `Title_${form.getValueIn('path.to.value')}_${form.getValueIn('path.to.value2')}`,
         title: `Title_${form.getValueIn('path.to.value')}_${form.getValueIn('path.to.value2')}`,
@@ -157,24 +156,23 @@ export const formMeta = {
 }
 }
 ```
 ```
 
 
-#### Using the `node.scope` API in Side Effects
+#### Using `node.scope` API in Side Effects
 
 
-If `createEffectFromVariableProvider` does not meet your needs, you can also directly use the `node.scope` API in form side effects for more flexible and variable operations.
+If `createEffectFromVariableProvider` doesn't meet your needs, you can also directly use the `node.scope` API in form side effects for more flexible variable operations.
 
 
 :::note
 :::note
 
 
-`node.scope` will return a node's variable scope (Scope) object, which has several core methods:
+`node.scope` returns a variable scope object for a node, which has several core methods mounted on it:
 
 
 - `setVar(variable)`: Set a variable.
 - `setVar(variable)`: Set a variable.
-- `setVar(namespace, variable)`: Set a variable under the specified namespace.
+- `setVar(namespace, variable)`: Set a variable under a specified namespace.
 - `getVar()`: Get all variables.
 - `getVar()`: Get all variables.
-- `getVar(namespace)`: Get the variables under the specified namespace.
+- `getVar(namespace)`: Get variables under a specified namespace.
 - `clearVar()`: Clear all variables.
 - `clearVar()`: Clear all variables.
-- `clearVar(namespace)`: Clear the variables under the specified namespace.
+- `clearVar(namespace)`: Clear variables under a specified namespace.
 
 
 :::
 :::
 
 
-
 ```tsx pure title="form-meta.tsx" {10-18,29-38}
 ```tsx pure title="form-meta.tsx" {10-18,29-38}
 import { Effect } from '@flowgram.ai/editor';
 import { Effect } from '@flowgram.ai/editor';
 
 
@@ -226,15 +224,13 @@ export const formMeta = {
 }
 }
 ```
 ```
 
 
+### Method 2: Synchronizing Variables via Plugins
 
 
-### Method 2: Sync Variables via Plugins
+In addition to static configuration in forms, we can also freely and dynamically manipulate node variables in plugins through `node.scope`.
 
 
-In addition to static configuration in the form, we can also freely and dynamically operate the variables of the node in the plugin (Plugin) through `node.scope`.
+#### Updating via Specified Node's Scope
 
 
-
-#### Update the Scope of the specified node
-
-The following example demonstrates how to get the `Scope` of the start node in the `onInit` life cycle of the plugin and perform a series of operations on its variables.
+The following example demonstrates how to obtain the `Scope` of the start node in the `onInit` lifecycle of a plugin and perform a series of operations on its variables.
 
 
 ```tsx pure title="sync-variable-plugin.tsx" {10-22}
 ```tsx pure title="sync-variable-plugin.tsx" {10-22}
 import {
 import {
@@ -263,9 +259,9 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
   })
   })
 ```
 ```
 
 
-#### Sync variables in onNodeCreate
+#### Synchronizing Variables in onNodeCreate
 
 
-The following example demonstrates how to get the Scope of a newly created node through `onNodeCreate` and synchronize variables by listening to `node.form.onFormValuesChange`.
+The following example demonstrates how to obtain the Scope of a newly created node through `onNodeCreate` and implement variable synchronization by listening to `node.form.onFormValuesChange`.
 
 
 ```tsx pure title="sync-variable-plugin.tsx" {10,29}
 ```tsx pure title="sync-variable-plugin.tsx" {10,29}
 import {
 import {
@@ -308,20 +304,17 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
   })
   })
 ```
 ```
 
 
-### Method 3: Sync Variables in UI (Not Recommended)
+### Method 3: Synchronizing Variables in UI (Not Recommended)
 
 
 :::warning
 :::warning
-Directly synchronizing variables in the UI (Method 3) is a highly discouraged practice.
-
-It breaks the principle of **separation of data and rendering**, leading to tight coupling between data and rendering:
+Directly synchronizing variables in UI (Method 3) is a **strongly discouraged** practice. It breaks the principle of **separation of data and rendering**, leading to tight coupling between data and rendering, which may cause:
 
 
-- **Unable to sync variables without UI**: Variables cannot be updated independently without the UI, leading to inconsistencies between data and rendering.
-- **Increased code complexity**: Directly manipulating variables in the UI increases the complexity of the UI logic, making the code harder to maintain.
-- **Performance issues**: Variable synchronization operations may trigger unnecessary re-rendering of UI components.
+- Closing the node sidebar prevents variable synchronization, resulting in inconsistency between data and rendering.
+- If the canvas enables performance optimization to only render nodes visible in the view, and the node is not in the view, the联动 logic will fail.
 
 
 :::
 :::
 
 
-The following example demonstrates how to synchronize and update variables through the `useCurrentScope` event in `formMeta.render`.
+The following example demonstrates how to synchronously update variables in `formMeta.render` through the `useCurrentScope` event.
 
 
 ```tsx pure title="form-meta.ts" {13}
 ```tsx pure title="form-meta.ts" {13}
 import {
 import {
@@ -330,11 +323,11 @@ import {
 } from '@flowgram.ai/fixed-layout-editor';
 } from '@flowgram.ai/fixed-layout-editor';
 
 
 /**
 /**
- * Get information of multiple fields from the form
+ * Get information from form
  */
  */
 const FormRender = () => {
 const FormRender = () => {
   /**
   /**
-   * Get the current scope for subsequent variable setting
+   * Get current scope for setting variables later
    */
    */
   const scope = useCurrentScope()
   const scope = useCurrentScope()
 
 
@@ -360,22 +353,17 @@ export const formMeta = {
 }
 }
 ```
 ```
 
 
+## Output Node Private Variables
 
 
+Private variables are variables that can only be accessed within the current node and its child nodes. (See: [Node Private Scope](./concept#node-private-scope))
 
 
+Here we only list two methods, and other methods can be inferred from [Output Node Variables](#output-node-variables).
 
 
+### Method 1: `createEffectFromVariableProvider`
 
 
-
-## Outputting Node Private Variables
-
-Private variables are variables that can only be accessed within the current node and its child nodes. (See also: [Node Private Scope](./concept#node-private-scope))
-
-Only two of the methods are listed below, and other methods can be deduced from the [Output Node Variables](#outputting-node-variables) method.
-
-### Method 1: Via `createEffectFromVariableProvider`
-
-`createEffectFromVariableProvider` provides the parameter `scope` to specify the scope of the variable.
-- When `scope` is set to `private`, the scope of the variable is the private scope of the current node `node.privateScope`
-- When `scope` is set to `public`, the scope of the variable is the scope of the current node `node.scope`
+`createEffectFromVariableProvider` provides the parameter `scope` for specifying the variable's scope.
+- When `scope` is set to `private`, the variable's scope is the current node's private scope `node.privateScope`
+- When `scope` is set to `public`, the variable's scope is the current node's scope `node.scope`
 
 
 ```tsx pure title="form-meta.ts" {11}
 ```tsx pure title="form-meta.ts" {11}
 import {
 import {
@@ -407,12 +395,9 @@ export const formMeta =  {
 }
 }
 ```
 ```
 
 
+### Method 2: `node.privateScope`
 
 
-### Method 2: Via `node.privateScope`
-
-
-The API of `node.privateScope` is designed to be almost identical to the node scope (`node.scope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. For details, please refer to [`node.scope`](#using-the-nodescope-api-in-side-effects).
-
+The API design of `node.privateScope` is almost identical to the node scope (`node.scope`), both providing methods like `setVar`, `getVar`, `clearVar`, etc., and both supporting namespaces. For details, please refer to [`node.scope`](#using-nodescope-api-in-side-effects).
 
 
 ```tsx pure title="form-meta.tsx" {10-18}
 ```tsx pure title="form-meta.tsx" {10-18}
 import { Effect } from '@flowgram.ai/editor';
 import { Effect } from '@flowgram.ai/editor';
@@ -445,19 +430,15 @@ export const formMeta = {
 }
 }
 ```
 ```
 
 
+## Output Global Variables
 
 
+Global variables are like the "shared memory" of the entire flow, accessible and modifiable by any node or plugin. They are very suitable for storing states that run through the entire flow, such as user information, environment configurations, etc.
 
 
+Similar to node variables, we also have two main ways to obtain the global variable scope (`GlobalScope`).
 
 
-## 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`:
+### Method 1: Obtaining in Plugins
 
 
+In the plugin's context (`ctx`), we can directly "inject" an instance of `GlobalScope`:
 
 
 ```tsx pure title="global-variable-plugin.tsx" {10-20}
 ```tsx pure title="global-variable-plugin.tsx" {10-20}
 import {
 import {
@@ -482,13 +463,11 @@ export const createGlobalVariablePlugin: PluginCreator<SyncVariablePluginOptions
       )
       )
     }
     }
   })
   })
-
 ```
 ```
 
 
+### Method 2: Obtaining in UI
 
 
-### Method 2: Obtain in UI
-
-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`:
+If you want to interact with global variables in a React component on the canvas, you can use the `useService` Hook to obtain an instance of `GlobalScope`:
 
 
 ```tsx pure title="global-variable-component.tsx" {7}
 ```tsx pure title="global-variable-component.tsx" {7}
 import {
 import {
@@ -515,16 +494,13 @@ function GlobalVariableComponent() {
 
 
   return <Input onChange={handleChange}/>
   return <Input onChange={handleChange}/>
 }
 }
-
 ```
 ```
 
 
+### Global Scope API
 
 
+The API design of `GlobalScope` is almost identical to the node scope (`node.scope`), both providing methods like `setVar`, `getVar`, `clearVar`, etc., and both supporting namespaces. For details, please refer to [`node.scope`](#using-nodescope-api-in-side-effects).
 
 
-### API of Global Scope
-
-The API of `GlobalScope` is designed to be almost identical to the node scope (`node.scope`), providing methods such as `setVar`, `getVar`, `clearVar`, and also supporting namespaces. For details, please refer to [`node.scope`](#using-the-nodescope-api-in-side-effects).
-
-Here is a comprehensive example of operating global variables in a plugin:
+Here's a comprehensive example of operating global variables in a plugin:
 
 
 ```tsx pure title="sync-variable-plugin.tsx" {11-39}
 ```tsx pure title="sync-variable-plugin.tsx" {11-39}
 import {
 import {
@@ -551,7 +527,7 @@ onInit(ctx, options) {
 
 
   globalScope.clearVar()
   globalScope.clearVar()
 
 
-  // 2.  Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
+  // 2. Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
     globalScope.setVar(
     globalScope.setVar(
       'namespace_1',
       'namespace_1',
       ASTFactory.createVariableDeclaration({
       ASTFactory.createVariableDeclaration({
@@ -571,4 +547,4 @@ onInit(ctx, options) {
 }
 }
 ```
 ```
 
 
-See also: [Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)
+See: [Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)

+ 42 - 90
apps/docs/src/en/materials/components/prompt-editor-with-variables.mdx

@@ -1,5 +1,5 @@
 import { SourceCode } from '@theme';
 import { SourceCode } from '@theme';
-import { BasicStory } from 'components/form-materials/components/prompt-editor-with-variables';
+import { BasicStory, StringOnlyStory } from 'components/form-materials/components/prompt-editor-with-variables';
 
 
 # PromptEditorWithVariables
 # PromptEditorWithVariables
 
 
@@ -15,6 +15,15 @@ PromptEditorWithVariables is an enhanced prompt editor that integrates variable
 
 
 ### Basic Usage
 ### Basic Usage
 
 
+:::tip{title="Variable Insertion"}
+
+Enter the `@`, `{` characters in the editor to trigger the variable selector.
+
+After entering `@`, `{`, a list of available variables will be displayed. Selecting a variable will automatically insert it in the `{{variable.path}}` format.
+
+:::
+
+
 <BasicStory />
 <BasicStory />
 
 
 ```tsx pure title="form-meta.tsx"
 ```tsx pure title="form-meta.tsx"
@@ -43,18 +52,38 @@ You are a helpful assistant
 }
 }
 ```
 ```
 
 
-### Variable Insertion
+### String Only Variables
 
 
-Enter the `@`, `{` characters in the editor to trigger the variable selector.
+<StringOnlyStory />
 
 
-After entering `@`, `{`, a list of available variables will be displayed. Selecting a variable will automatically insert it in the `{{variable.path}}` format.
+
+```tsx pure title="form-meta.tsx"
+import { PromptEditorWithVariables, VariableSelectorProvider } from '@flowgram.ai/form-materials';
+
+const STRING_ONLY_SCHEMA = { type: 'string' };
+
+const formMeta = {
+  render: () => (
+    <VariableSelectorProvider includeSchema={STRING_ONLY_SCHEMA}>
+      <Field<any> name="prompt_template">
+        {({ field }) => (
+          <PromptEditorWithVariables
+            value={field.value}
+            onChange={(value) => field.onChange(value)}
+          />
+        )}
+      </Field>
+    </VariableSelectorProvider>
+  ),
+}
+```
 
 
 ## API Reference
 ## API Reference
 
 
 ### PromptEditorWithVariables Props
 ### PromptEditorWithVariables Props
 
 
 | Property | Type | Default | Description |
 | Property | Type | Default | Description |
-|----------|------|---------|-------------|
+|--------|------|--------|------|
 | `value` | `{ type: 'template', content: string }` | - | Prompt template content |
 | `value` | `{ type: 'template', content: string }` | - | Prompt template content |
 | `onChange` | `(value: { type: 'template', content: string }) => void` | - | Callback function when content changes |
 | `onChange` | `(value: { type: 'template', content: string }) => void` | - | Callback function when content changes |
 | `readonly` | `boolean` | `false` | Whether it's read-only mode |
 | `readonly` | `boolean` | `false` | Whether it's read-only mode |
@@ -67,7 +96,7 @@ After entering `@`, `{`, a list of available variables will be displayed. Select
 ## Source Code Guide
 ## Source Code Guide
 
 
 <SourceCode
 <SourceCode
-  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/prompt-editor-with-variables"
+  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/prompt-editor-with-variables/index.tsx"
 />
 />
 
 
 Use CLI command to copy source code locally:
 Use CLI command to copy source code locally:
@@ -80,96 +109,19 @@ npx @flowgram.ai/cli@latest materials components/prompt-editor-with-variables
 
 
 ```
 ```
 prompt-editor-with-variables/
 prompt-editor-with-variables/
-├── index.tsx           # Lazy loading export file
-├── editor.tsx          # Main component implementation
-└── README.md          # Component documentation
-
-prompt-editor/
-├── index.tsx           # Basic prompt editor export
-├── editor.tsx          # Basic prompt editor implementation
-├── types.ts            # Type definitions
-├── styles.ts           # Style components
-└── extensions/         # Editor extensions
-    ├── markdown.tsx    # Markdown highlighting
-    ├── language-support.tsx # Language support
-    └── jinja.tsx       # Jinja template highlighting
+└── index.tsx           # Main component implementation
 ```
 ```
 
 
 ### Core Implementation Explanation
 ### Core Implementation Explanation
 
 
-#### Variable Selector Integration
-PromptEditorWithVariables extends the basic PromptEditor, adding variable management functionality:
-
-```typescript
-export function PromptEditorWithVariables(props: PromptEditorWithVariablesProps) {
-  return (
-    <PromptEditor {...props}>
-      <EditorVariableTree />
-      <EditorVariableTagInject />
-    </PromptEditor>
-  );
-}
-```
-
-#### Variable Tree Selector
-The `EditorVariableTree` component provides a tree-structured variable selector:
-
-- Supports triggering variable selection with `@`
-- Supports tree display of nested variables
-- Supports searching and filtering variables
-- Supports variable type icon display
-
-#### Variable Tag Injection
-The `EditorVariableTagInject` component is responsible for variable tag rendering and management:
-
-- Variable tag style rendering
-- Variable tag interaction handling
-- Variable tag validation and error prompts
-
-### Flowgram APIs Used
+#### Variable Capability Integration
 
 
-#### @flowgram.ai/coze-editor/react
-- `Renderer`: Editor renderer
-- `EditorProvider`: Editor provider
-- `ActiveLinePlaceholder`: Active line placeholder
-- `InferValues`: Type inference tool
+PromptEditorWithVariables extends the basic [PromptEditor](./prompt-editor) and adds variable reference and tag display functionality based on [CozeEditorExtensions](./coze-editor-extensions).
 
 
-#### @flowgram.ai/coze-editor/preset-prompt
-- `preset`: Prompt editor preset configuration
-- `EditorAPI`: Editor API interface
+### Dependent Materials
 
 
-#### coze-editor-extensions materials
-
-See [CozeEditorExtensions](./coze-editor-extensions)
+[**PromptEditor**](./prompt-editor)
 
 
+[**CozeEditorExtensions**](./coze-editor-extensions)
 - `EditorVariableTree`: Variable tree selection trigger
 - `EditorVariableTree`: Variable tree selection trigger
-- `EditorVariableTagInject`: Variable tag display
-
-### Overall Process
-
-```mermaid
-graph TD
-    A[PromptEditorWithVariables] --> B[Render PromptEditor]
-    B --> C[Load preset configuration]
-    C --> D[Integrate extension plugins]
-
-    D --> E[MarkdownHighlight]
-    D --> F[LanguageSupport]
-    D --> G[JinjaHighlight]
-
-    E --> H[Syntax highlighting]
-    F --> I[Language support]
-    G --> J[Template syntax]
-
-    A --> K[Integrate variable extensions]
-    K --> L[EditorVariableTree]
-    K --> M[EditorVariableTagInject]
-
-    L --> N[Variable selector]
-    M --> O[Variable tag rendering]
-
-    N --> P[Variable selection]
-    P --> Q[Insert variable]
-
-    Q --> R[Update template content]
-```
+- `EditorVariableTagInject`: Variable Tag display

+ 2 - 0
apps/docs/src/en/materials/components/variable-selector.mdx

@@ -110,6 +110,8 @@ const formMeta = {
 | Property | Type | Default | Description |
 | Property | Type | Default | Description |
 |----------|------|---------|-------------|
 |----------|------|---------|-------------|
 | `skipVariable` | `(variable?: BaseVariableField) => boolean` | - | Custom variable filter function |
 | `skipVariable` | `(variable?: BaseVariableField) => boolean` | - | Custom variable filter function |
+| `includeSchema` | `IJsonSchema \| IJsonSchema[]` | - | Variable type inclusion filter conditions |
+| `excludeSchema` | `IJsonSchema \| IJsonSchema[]` | - | Variable type exclusion filter conditions |
 | `children` | `React.ReactNode` | - | Child components |
 | `children` | `React.ReactNode` | - | Child components |
 
 
 ## Source Code Guide
 ## Source Code Guide

+ 129 - 45
apps/docs/src/zh/guide/variable/variable-consume.mdx

@@ -13,9 +13,9 @@ description: 介绍如何在 FlowGram 中消费变量引擎输出的变量
 详见文档: [VariableSelector](/materials/components/variable-selector)
 详见文档: [VariableSelector](/materials/components/variable-selector)
 
 
 
 
-## 在节点内获取变量树
+## 获取可访问的变量树
 
 
-在画布的节点中,我们常常需要获取当前作用域下可用的变量,并将它们以树形结构展示出来,方便用户进行选择和操作。
+在画布的节点中,我们常常需要获取**当前作用域下可用的变量**,并将它们以树形结构展示出来,方便用户进行选择和操作。
 
 
 ### `useAvailableVariables`
 ### `useAvailableVariables`
 
 
@@ -98,64 +98,85 @@ const renderVariable = (variable: BaseVariableField) => ({
 
 
 ```
 ```
 
 
+## `scope.available`
 
 
-## `ScopeAvailableData`
-
-`ScopeAvailableData` 对象是变量系统的核心之一,它由 `useScopeAvailable` Hook 返回,是与**作用域内可用变量**进行交互的主要桥梁。
+`scope.available` 是变量系统的核心之一,可以对 **作用域内可用变量** 进行更加高级的变量获取和监听动作。
 
 
 ### `useScopeAvailable`
 ### `useScopeAvailable`
 
 
-`useScopeAvailable` 能够返回一个 `ScopeAvailableData` 对象,其中不仅包含了当前作用域所有可用的变量信息,还提供了一些高级 API,比如 `trackByKeyPath`。
-
-它与 `useAvailableVariables` 的主要区别在于:
-
-*   **返回值不同**:`useAvailableVariables` 直接返回变量数组,而 `useScopeAvailable` 返回的是一个包含了 `variables` 属性以及其他方法的 `ScopeAvailableData` 对象。
-*   **适用场景**:当你需要对变量进行更复杂的操作,比如追踪单个变量的变化时,`useScopeAvailable` 是你的不二之选。
+`useScopeAvailable` 能够在 React 中直接返回 `scope.available`
 
 
 ```tsx
 ```tsx
-import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
+import { useScopeAvailable } from '@flowgram.ai/free-layout-editor';
 
 
 const available = useScopeAvailable();
 const available = useScopeAvailable();
 
 
 // available 对象上包含了变量列表和其他 API
 // available 对象上包含了变量列表和其他 API
 console.log(available.variables);
 console.log(available.variables);
+
+// 获取单个变量
+console.log(available.getByKeyPath(['start_0', 'xxx']));
+
+// 监听单个变量的变化
+available.trackByKeyPath(['start_0', xxx], () => {
+  // ...
+})
+```
+
+:::info{title="与 useAvailableVariables 的主要区别"}
+
+*   **返回值不同**:`useAvailableVariables` 直接返回变量数组,而 `useScopeAvailable` 返回的是一个包含了 `variables` 属性以及其他方法的 `ScopeAvailableData` 对象。
+*   **适用场景**:当你需要对变量进行更复杂的操作,比如通过 `trackByKeyPath` 追踪单个变量的变化时,`useScopeAvailable` 是你的不二之选。
+
+:::
+
+
+:::warning{title="useScopeAvailable 会在可用变量变化时自动刷新"}
+
+如果不想自动刷新,可以通过 autoRefresh 参数关闭:
+
+```tsx
+useScopeAvailable({ autoRefresh: false })
 ```
 ```
+:::
 
 
-### 获取变量列表
+### `getByKeyPath`
 
 
-最基础的用法就是获取当前作用域下所有可用的变量。
+通过`getByKeyPath` 可以在当前作用域的可访问变量中获取特定变量字段(包括嵌套在 Object 或 Array 中的变量)
 
 
-```tsx {4,7}
+```tsx {6,13-17}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
+import { useEffect, useState } from 'react';
 
 
-function MyComponent() {
+function VariableDisplay({ keyPath }: { keyPath:string[] }) {
   const available = useScopeAvailable();
   const available = useScopeAvailable();
+  const variableField = available.getByKeyPath(keyPath)
+
+  return <div>{variableField.meta?.title}</div>;
+}
+```
 
 
-  // available.variables 就是一个包含了所有可用变量的数组
-  console.log(available.variables);
+`getByKeyPath` 也尝尝用于变量校验中,如:
 
 
-  return (
-    <ul>
-      {available.variables.map(variable => (
-        <li key={variable.key}>{variable.name}</li>
-      ))}
-    </ul>
-  );
+```tsx
+const validateVariableInNode = (keyPath: string, node: FlowNodeEntity) => {
+  // 校验变量能否被当前节点所访问
+  return Boolean(node.scope.available.getByKeyPath(keyPath))
 }
 }
 ```
 ```
 
 
-### `trackByKeyPath`
 
 
-当你只关心某个特定变量(尤其是嵌套在 Object 或 Array 中的变量)的变化时,`trackByKeyPath` 就派上用场了。它能让你精准地“订阅”这个变量的更新,而不会因为其他不相关变量的变化导致组件重新渲染,从而实现更精细的性能优化。
+### `trackByKeyPath`
 
 
-假设我们有一个名为 `user` 的 Object 类型变量,它有一个 `name` 属性。我们希望在 `user.name` 变化时更新组件
+当你只关心某个特定变量字段(包括嵌套在 Object 或 Array 中的变量)的变化时,`trackByKeyPath` 能让你精准地“订阅”这个变量的更新,而不会因为其他不相关变量的变化导致组件重新渲染,从而实现更精细的性能优化
 
 
-```tsx {13-17}
+```tsx {6,13-17}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 
 
 function UserNameDisplay() {
 function UserNameDisplay() {
-  const available = useScopeAvailable();
+  // 关闭 autoRefresh 能力,防止任意变量变化触发重渲染
+  const available = useScopeAvailable({ autoRefresh: false });
   const [userName, setUserName] = useState('');
   const [userName, setUserName] = useState('');
 
 
   useEffect(() => {
   useEffect(() => {
@@ -169,7 +190,7 @@ function UserNameDisplay() {
       setUserName(nameField?.meta.default || '');
       setUserName(nameField?.meta.default || '');
     });
     });
 
 
-    // 组件卸载时,别忘了取消追踪,避免内存泄漏
+    // 组件卸载时取消追踪,避免内存泄漏
     return () => disposable.dispose();
     return () => disposable.dispose();
   }, [available]); // 依赖项是 available 对象
   }, [available]); // 依赖项是 available 对象
 
 
@@ -185,32 +206,30 @@ function UserNameDisplay() {
 
 
 下面我们通过一个表格来详细对比这三个核心的监听 API:
 下面我们通过一个表格来详细对比这三个核心的监听 API:
 
 
-| API | 触发时机 | 回调参数 | 核心区别与适用场景 |
-| :--- | :--- | :--- | :--- |
-| `onVariableListChange` | 当可用变量的**列表结构**发生变化时。 | `(variables: VariableDeclaration[]) => void` | **只关心列表本身**。比如,上游节点新增/删除了一个输出变量,导致可用变量的总数或成员发生了变化。它不关心变量内部和下钻的改变。适用于需要根据变量列表的有无或数量来更新 UI 的场景。 |
-| `onAnyVariableChange` | 当列表中**任意一个**变量的**类型,元数据和下钻字段**发生变化时。 | `(changedVariable: VariableDeclaration) => void` | **只关心变量内容的更新**。比如,用户修改了一个输出变量的类型。它不关心列表结构的变化。适用于需要对任何一个变量的内容变化做出反应的场景。 |
-| `onListOrAnyVarChange` | 以上两种情况**任意一种**发生时。 | `(variables: VariableDeclaration[]) => void` | **最全面的监听**,是前两者的结合。无论是列表结构变化,还是任何一个变量的变化,都会触发。适用于需要对任何可能的变化都进行响应的“兜底”场景。 |
-
-#### 代码示例
+| API & 回调参数 | 触发时机 | 核心区别与适用场景 |
+| :--- | :--- | :--- |
+| `onVariableListChange: (variables: VariableDeclaration[]) => void` | 当可用变量的**列表结构**发生变化时。 | **只关心列表本身**。比如,上游节点新增/删除了一个输出变量,导致可用变量的总数或成员发生了变化。它不关心变量内部和下钻的改变。适用于需要根据变量列表的有无或数量来更新 UI 的场景。 |
+| `onAnyVariableChange: (changedVariable: VariableDeclaration) => void` | 当列表中**任意一个**变量的**类型,元数据和下钻字段**发生变化时。 | **只关心变量定义的更新**。比如,用户修改了一个输出变量的类型。它不关心列表结构的变化。适用于需要对任何一个变量的内容变化做出反应的场景。 |
+| `onListOrAnyVarChange: (variables: VariableDeclaration[]) => void` | 以上两种情况**任意一种**发生时。 | **最全面的监听**,是前两者的结合。无论是列表结构变化,还是任何一个变量的变化,都会触发。适用于需要对任何可能的变化都进行响应的“兜底”场景。 |
 
 
 让我们通过一个具体的例子来看看如何在组件中使用这些 API。
 让我们通过一个具体的例子来看看如何在组件中使用这些 API。
 
 
-```tsx {9-11,14-16,19-22}
+```tsx {5,9-11,14-16,19-22}
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useScopeAvailable } from '@flowgram.ai/fixed-layout-editor';
 import { useEffect } from 'react';
 import { useEffect } from 'react';
 
 
 function AdvancedListenerComponent() {
 function AdvancedListenerComponent() {
-  const available = useScopeAvailable();
+  const available = useScopeAvailable({ autoRefresh: false });
 
 
   useEffect(() => {
   useEffect(() => {
     // 1. 监听列表结构变化
     // 1. 监听列表结构变化
     const listChangeDisposable = available.onVariableListChange((variables) => {
     const listChangeDisposable = available.onVariableListChange((variables) => {
-      console.log('可用变量列表的结构变了!新的列表长度是:', variables.length);
+      console.log('可用变量列表的结构变了!新的列表长度是', variables.length);
     });
     });
 
 
     // 2. 监听任意变量的变化
     // 2. 监听任意变量的变化
     const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
     const valueChangeDisposable = available.onAnyVariableChange((changedVariable) => {
-      console.log(`变量 '${changedVariable.keyPath.join('.')}' 的类型、下钻或者 meta 变了`);
+      console.log(`变量 '${changedVariable.keyPath.join('.')}' 的定义变了`);
     });
     });
 
 
     // 3. 监听所有变化(结构或单个变量内部)
     // 3. 监听所有变化(结构或单个变量内部)
@@ -231,7 +250,72 @@ function AdvancedListenerComponent() {
 }
 }
 ```
 ```
 
 
-**关键点**:
+:::warning
+
+这些 API 返回的都是一个 `Disposable` 对象。为了避免内存泄漏和不必要的计算,你必须在 `useEffect` 的清理函数中调用其 `dispose()` 方法来取消监听。
+
+:::
+
 
 
-*   这些 API 返回的都是一个 `Disposable` 对象。
-*   为了避免内存泄漏和不必要的计算,你必须在 `useEffect` 的清理函数中调用其 `dispose()` 方法来取消监听。
+## 获取当前作用域的输出变量
+
+### `useOutputVariables`
+
+
+useOutputVariables 可以获取**当前作用域的输出变量**,并在输出变量列表或者下钻变化时**自动触发刷新**
+
+```tsx
+const variables = useOutputVariables();
+```
+
+:::tip
+
+useOutputVariables 在 flowgram@0.5.6 之后的版本提供,如果版本较早,可以通过以下代码实现获取:
+
+```tsx
+const scope = useCurrentScope();
+const refresh = useRefresh();
+
+useEffect(() => {
+  const disposable = scope.output.onListOrAnyVarChange(() => {
+    refresh();
+  });
+
+  return () => disposable.dispose();
+}, [])
+
+const variables = scope.variables;
+```
+:::
+
+
+## 其余 API
+
+### 获取当前作用域
+
+可以通过 [`useCurrentScope`](https://flowgram.ai/auto-docs/editor/functions/useCurrentScope),获取当前的作用域。
+
+```tsx
+const scope = useCurrentScope()
+
+scope.output.variables
+
+scope.available
+
+```
+
+### 设定当前作用域
+
+可以通过 [`ScopeProvider`](https://flowgram.ai/auto-docs/editor/functions/ScopeProvider) 设定当前作用域
+
+```tsx
+// set the scope of current node
+<ScopeProvider scope={node.scope}>
+  <YourUI />
+</ScopeProvider>
+
+// set to private scope of current node
+<ScopeProvider scope={node.privateScope}>
+  <YourUI />
+</ScopeProvider>
+```

+ 7 - 9
apps/docs/src/zh/guide/variable/variable-output.mdx

@@ -40,7 +40,7 @@ export const formMeta = {
 };
 };
 ```
 ```
 
 
-#### 通过 `createEffectFromVariableProvider` 自定义输出
+#### `createEffectFromVariableProvider` 自定义输出
 
 
 
 
 `provideJsonSchemaOutputs` 只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。
 `provideJsonSchemaOutputs` 只适配 `JsonSchema`。如果你想要定义自己的一套 Schema,那么就需要自定义表单的副作用。
@@ -136,6 +136,7 @@ const variableSyncEffect = createEffectFromVariableProvider({
 
 
   // 将表单值解析为变量
   // 将表单值解析为变量
   parse(_, { form, node }) {
   parse(_, { form, node }) {
+    // 注意:form 字段要求 flowgram 版本 > 0.5.5
     return [{
     return [{
       meta: {
       meta: {
         title: `Title_${form.getValueIn('path.to.value')}_${form.getValueIn('path.to.value2')}`,
         title: `Title_${form.getValueIn('path.to.value')}_${form.getValueIn('path.to.value2')}`,
@@ -311,13 +312,10 @@ export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions>
 ### 方式三:在 UI 中同步变量(不推荐)
 ### 方式三:在 UI 中同步变量(不推荐)
 
 
 :::warning
 :::warning
-直接在 UI 中同步变量(方式三)是一种非常不推荐的做法。
+直接在 UI 中同步变量(方式三)是一种 **非常不推荐** 的做法。它会打破**数据和渲染分离**的原则,会导致数据和渲染之间的紧密耦合,可能会导致:
 
 
-它会打破**数据和渲染分离**的原则,会导致数据和渲染之间的紧密耦合:
-
-- **脱离 UI 无法同步变量**:脱离了 UI 就无法独立更新变量,会导致数据和渲染之间的不一致。
-- **增加代码复杂度**:直接在 UI 中操作变量,会增加 UI 逻辑的复杂性,使代码更难维护。
-- **性能问题**:变量的同步操作可能会触发 UI 组件的不必要的重渲染。
+- 关闭节点侧边栏,就无法触发变量同步,导致数据和渲染之间的不一致。
+- 画布如果开启了只渲染视图内可见节点的性能优化,如果节点不在视图内,则联动逻辑会失效
 
 
 :::
 :::
 
 
@@ -371,7 +369,7 @@ export const formMeta = {
 
 
 下面只列举其中两种方式,其他方式可以根据[输出节点变量](#输出节点变量)的方式类推
 下面只列举其中两种方式,其他方式可以根据[输出节点变量](#输出节点变量)的方式类推
 
 
-### 方式一:通过 `createEffectFromVariableProvider`
+### 方式一:`createEffectFromVariableProvider`
 
 
 `createEffectFromVariableProvider` 提供了参数 `scope`,用于指定变量的作用域。
 `createEffectFromVariableProvider` 提供了参数 `scope`,用于指定变量的作用域。
 - `scope` 设置为 `private` 时,变量的作用域为当前节点的私有作用域 `node.privateScope`
 - `scope` 设置为 `private` 时,变量的作用域为当前节点的私有作用域 `node.privateScope`
@@ -408,7 +406,7 @@ export const formMeta =  {
 ```
 ```
 
 
 
 
-### 方式二:通过 `node.privateScope`
+### 方式二:`node.privateScope`
 
 
 
 
 `node.privateScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar`等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。
 `node.privateScope` 的 API 设计得和节点作用域(`node.scope`)几乎一模一样,都提供了 `setVar`、`getVar`、`clearVar`等方法,并且同样支持命名空间(namespace)。详情可以参考 [`node.scope`](#在副作用中使用-nodescope-api)。

+ 27 - 1
apps/docs/src/zh/materials/components/prompt-editor-with-variables.mdx

@@ -1,5 +1,5 @@
 import { SourceCode } from '@theme';
 import { SourceCode } from '@theme';
-import { BasicStory } from 'components/form-materials/components/prompt-editor-with-variables';
+import { BasicStory, StringOnlyStory } from 'components/form-materials/components/prompt-editor-with-variables';
 
 
 # PromptEditorWithVariables
 # PromptEditorWithVariables
 
 
@@ -54,6 +54,32 @@ You are a helpful assistant
 }
 }
 ```
 ```
 
 
+### 只能选择 String 类型的变量
+
+<StringOnlyStory />
+
+
+```tsx pure title="form-meta.tsx"
+import { PromptEditorWithVariables, VariableSelectorProvider } from '@flowgram.ai/form-materials';
+
+const STRING_ONLY_SCHEMA = { type: 'string' };
+
+const formMeta = {
+  render: () => (
+    <VariableSelectorProvider includeSchema={STRING_ONLY_SCHEMA}>
+      <Field<any> name="prompt_template">
+        {({ field }) => (
+          <PromptEditorWithVariables
+            value={field.value}
+            onChange={(value) => field.onChange(value)}
+          />
+        )}
+      </Field>
+    </VariableSelectorProvider>
+  ),
+}
+```
+
 ## API 参考
 ## API 参考
 
 
 ### PromptEditorWithVariables Props
 ### PromptEditorWithVariables Props

+ 2 - 0
apps/docs/src/zh/materials/components/variable-selector.mdx

@@ -114,6 +114,8 @@ const formMeta = {
 | 属性名 | 类型 | 默认值 | 描述 |
 | 属性名 | 类型 | 默认值 | 描述 |
 |--------|------|--------|------|
 |--------|------|--------|------|
 | `skipVariable` | `(variable?: BaseVariableField) => boolean` | - | 自定义变量过滤函数 |
 | `skipVariable` | `(variable?: BaseVariableField) => boolean` | - | 自定义变量过滤函数 |
+| `includeSchema` | `IJsonSchema \| IJsonSchema[]` | - | 包含的变量类型过滤条件 |
+| `excludeSchema` | `IJsonSchema \| IJsonSchema[]` | - | 排除的变量类型过滤条件 |
 | `children` | `React.ReactNode` | - | 子组件 |
 | `children` | `React.ReactNode` | - | 子组件 |
 
 
 ## 源码导读
 ## 源码导读

+ 22 - 2
packages/materials/form-materials/src/components/variable-selector/context.tsx

@@ -5,10 +5,19 @@
 
 
 import React, { createContext, useContext, useMemo } from 'react';
 import React, { createContext, useContext, useMemo } from 'react';
 
 
+import { IJsonSchema } from '@flowgram.ai/json-schema';
 import { BaseVariableField } from '@flowgram.ai/editor';
 import { BaseVariableField } from '@flowgram.ai/editor';
 
 
+type VariableField = BaseVariableField<{
+  icon?: string | JSX.Element;
+  title?: string;
+  disabled?: boolean;
+}>;
+
 export const VariableSelectorContext = createContext<{
 export const VariableSelectorContext = createContext<{
-  skipVariable?: (variable?: BaseVariableField) => boolean;
+  includeSchema?: IJsonSchema | IJsonSchema[];
+  excludeSchema?: IJsonSchema | IJsonSchema[];
+  skipVariable?: (variable: VariableField) => boolean;
 }>({});
 }>({});
 
 
 export const useVariableSelectorContext = () => useContext(VariableSelectorContext);
 export const useVariableSelectorContext = () => useContext(VariableSelectorContext);
@@ -16,11 +25,22 @@ export const useVariableSelectorContext = () => useContext(VariableSelectorConte
 export const VariableSelectorProvider = ({
 export const VariableSelectorProvider = ({
   children,
   children,
   skipVariable,
   skipVariable,
+  includeSchema,
+  excludeSchema,
 }: {
 }: {
   skipVariable?: (variable?: BaseVariableField) => boolean;
   skipVariable?: (variable?: BaseVariableField) => boolean;
+  includeSchema?: IJsonSchema | IJsonSchema[];
+  excludeSchema?: IJsonSchema | IJsonSchema[];
   children: React.ReactNode;
   children: React.ReactNode;
 }) => {
 }) => {
-  const context = useMemo(() => ({ skipVariable }), [skipVariable]);
+  const context = useMemo(
+    () => ({
+      skipVariable,
+      includeSchema,
+      excludeSchema,
+    }),
+    [skipVariable, includeSchema, excludeSchema]
+  );
 
 
   return (
   return (
     <VariableSelectorContext.Provider value={context}>{children}</VariableSelectorContext.Provider>
     <VariableSelectorContext.Provider value={context}>{children}</VariableSelectorContext.Provider>

+ 9 - 1
packages/materials/form-materials/src/components/variable-selector/use-variable-tree.tsx

@@ -15,6 +15,8 @@ import { ASTMatch, BaseVariableField, useAvailableVariables } from '@flowgram.ai
 import { TreeNodeData } from '@douyinfe/semi-ui/lib/es/tree';
 import { TreeNodeData } from '@douyinfe/semi-ui/lib/es/tree';
 import { Icon } from '@douyinfe/semi-ui';
 import { Icon } from '@douyinfe/semi-ui';
 
 
+import { useVariableSelectorContext } from './context';
+
 type VariableField = BaseVariableField<{
 type VariableField = BaseVariableField<{
   icon?: string | JSX.Element;
   icon?: string | JSX.Element;
   title?: string;
   title?: string;
@@ -26,7 +28,13 @@ export function useVariableTree(params: {
   excludeSchema?: IJsonSchema | IJsonSchema[];
   excludeSchema?: IJsonSchema | IJsonSchema[];
   skipVariable?: (variable: VariableField) => boolean;
   skipVariable?: (variable: VariableField) => boolean;
 }): TreeNodeData[] {
 }): TreeNodeData[] {
-  const { includeSchema, excludeSchema, skipVariable } = params;
+  const context = useVariableSelectorContext();
+
+  const {
+    includeSchema = context.includeSchema,
+    excludeSchema = context.excludeSchema,
+    skipVariable = context.skipVariable,
+  } = params;
 
 
   const typeManager = useTypeManager() as JsonSchemaTypeManager;
   const typeManager = useTypeManager() as JsonSchemaTypeManager;
   const variables = useAvailableVariables();
   const variables = useAvailableVariables();

+ 0 - 0
packages/variable-engine/variable-core/src/react/hooks/useAvailableVariables.ts → packages/variable-engine/variable-core/src/react/hooks/use-available-variables.ts


+ 38 - 0
packages/variable-engine/variable-core/src/react/hooks/use-output-variables.ts

@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import { useEffect } from 'react';
+
+import { useRefresh } from '@flowgram.ai/core';
+
+import { useCurrentScope } from '../context';
+import { VariableDeclaration } from '../../ast';
+
+/**
+ * Get output variable list in the current scope.
+ *
+ * - The hook is reactive to variable list or any variables change.
+ */
+export function useOutputVariables(): VariableDeclaration[] {
+  const scope = useCurrentScope();
+
+  const refresh = useRefresh();
+
+  useEffect(() => {
+    if (!scope) {
+      throw new Error(
+        '[useOutputVariables]: No scope found, useOutputVariables must be used in <ScopeProvider>'
+      );
+    }
+
+    const disposable = scope.output.onListOrAnyVarChange(() => {
+      refresh();
+    });
+
+    return () => disposable.dispose();
+  }, []);
+
+  return scope.output.variables;
+}

+ 0 - 0
packages/variable-engine/variable-core/src/react/hooks/useScopeAvailable.ts → packages/variable-engine/variable-core/src/react/hooks/use-scope-available.ts


+ 3 - 2
packages/variable-engine/variable-core/src/react/index.tsx

@@ -4,5 +4,6 @@
  */
  */
 
 
 export { ScopeProvider, useCurrentScope } from './context';
 export { ScopeProvider, useCurrentScope } from './context';
-export { useScopeAvailable } from './hooks/useScopeAvailable';
-export { useAvailableVariables } from './hooks/useAvailableVariables';
+export { useScopeAvailable } from './hooks/use-scope-available';
+export { useAvailableVariables } from './hooks/use-available-variables';
+export { useOutputVariables } from './hooks/use-output-variables';