Преглед изворни кода

docs(material): validate flow value (#953)

* docs: validate flow value

* feat: validate when variable sync
Yiwei Mao пре 2 месеци
родитељ
комит
a1aec01421

+ 77 - 0
apps/docs/components/form-materials/effects/validate-when-variable-sync.tsx

@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React, { useEffect } from 'react';
+
+import { Field } from '@flowgram.ai/free-layout-editor';
+import { validateFlowValue, validateWhenVariableSync } from '@flowgram.ai/form-materials';
+import { Button } from '@douyinfe/semi-ui';
+
+import { FreeFormMetaStoryBuilder, FormHeader } from '../../free-form-meta-story-builder';
+
+const DynamicValueInput = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.DynamicValueInput,
+  }))
+);
+
+export const BasicStory = () => (
+  <FreeFormMetaStoryBuilder
+    filterEndNode
+    transformInitialNode={{
+      custom_0: (node) => {
+        node.data.value = {
+          type: 'ref',
+          content: ['start_0', 'query'],
+        };
+
+        return node;
+      },
+    }}
+    formMeta={{
+      effect: {
+        value: validateWhenVariableSync(),
+      },
+      validate: {
+        value: ({ value, context }) =>
+          validateFlowValue(value, {
+            node: context.node,
+            errorMessages: {
+              unknownVariable: 'Unknown Variable',
+            },
+          }),
+      },
+      render: ({ form }) => {
+        useEffect(() => {
+          form.validate();
+        }, []);
+
+        return (
+          <>
+            <FormHeader />
+
+            <b>{"Add 'query' in Start"}</b>
+            <Field<any> name="value">
+              {({ field, fieldState }) => (
+                <>
+                  <DynamicValueInput
+                    value={field.value}
+                    onChange={(value) => field.onChange(value)}
+                  />
+                  <span style={{ color: 'red' }}>
+                    {fieldState.errors?.map((e) => e.message).join('\n')}
+                  </span>
+                </>
+              )}
+            </Field>
+            <br />
+
+            <Button onClick={() => form.validate()}>Trigger Validate</Button>
+          </>
+        );
+      },
+    }}
+  />
+);

+ 135 - 0
apps/docs/components/form-materials/validate/validate-flow-value.tsx

@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
+ * SPDX-License-Identifier: MIT
+ */
+
+import React from 'react';
+
+import { Field } from '@flowgram.ai/free-layout-editor';
+import { validateFlowValue } from '@flowgram.ai/form-materials';
+import { Button } from '@douyinfe/semi-ui';
+
+import { FreeFormMetaStoryBuilder, FormHeader } from '../../free-form-meta-story-builder';
+
+const DynamicValueInput = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.DynamicValueInput,
+  }))
+);
+
+const PromptEditorWithVariables = React.lazy(() =>
+  import('@flowgram.ai/form-materials').then((module) => ({
+    default: module.PromptEditorWithVariables,
+  }))
+);
+
+export const BasicStory = () => (
+  <FreeFormMetaStoryBuilder
+    filterEndNode
+    transformInitialNode={{
+      custom_0: (node) => {
+        node.data.dynamic_value_input = {
+          type: 'ref',
+          content: ['start_0', 'unknown_key'],
+        };
+        node.data.required_dynamic_value_input = {
+          type: 'constant',
+          content: '',
+        };
+        node.data.prompt_editor = {
+          type: 'template',
+          content: 'Hello {{start_0.unknown_key}}',
+        };
+        return node;
+      },
+    }}
+    formMeta={{
+      validate: {
+        dynamic_value_input: ({ value, context }) =>
+          validateFlowValue(value, {
+            node: context.node,
+            errorMessages: {
+              required: 'Value is required',
+              unknownVariable: 'Unknown Variable',
+            },
+          }),
+
+        required_dynamic_value_input: ({ value, context }) =>
+          validateFlowValue(value, {
+            node: context.node,
+            required: true,
+            errorMessages: {
+              required: 'Value is required',
+              unknownVariable: 'Unknown Variable',
+            },
+          }),
+
+        prompt_editor: ({ value, context }) =>
+          validateFlowValue(value, {
+            node: context.node,
+            required: true,
+            errorMessages: {
+              required: 'Prompt is required',
+              unknownVariable: 'Unknown Variable In Template',
+            },
+          }),
+      },
+      render: ({ form }) => (
+        <>
+          <FormHeader />
+
+          <b>Validate variable valid</b>
+          <Field<any> name="dynamic_value_input">
+            {({ field, fieldState }) => (
+              <>
+                <DynamicValueInput
+                  value={field.value}
+                  onChange={(value) => field.onChange(value)}
+                />
+                <span style={{ color: 'red' }}>
+                  {fieldState.errors?.map((e) => e.message).join('\n')}
+                </span>
+              </>
+            )}
+          </Field>
+          <br />
+
+          <b>Validate required value</b>
+          <Field<any> name="required_dynamic_value_input">
+            {({ field, fieldState }) => (
+              <>
+                <DynamicValueInput
+                  value={field.value}
+                  onChange={(value) => field.onChange(value)}
+                />
+                <span style={{ color: 'red' }}>
+                  {fieldState.errors?.map((e) => e.message).join('\n')}
+                </span>
+              </>
+            )}
+          </Field>
+          <br />
+
+          <b>Validate required and variables valid in prompt</b>
+          <Field<any> name="prompt_editor">
+            {({ field, fieldState }) => (
+              <>
+                <PromptEditorWithVariables
+                  value={field.value}
+                  onChange={(value) => field.onChange(value)}
+                />
+                <span style={{ color: 'red' }}>
+                  {fieldState.errors?.map((e) => e.message).join('\n')}
+                </span>
+              </>
+            )}
+          </Field>
+
+          <br />
+
+          <Button onClick={() => form.validate()}>Trigger Validate</Button>
+        </>
+      ),
+    }}
+  />
+);

+ 81 - 5
apps/docs/src/en/materials/effects/validate-when-variable-sync.mdx

@@ -1,11 +1,87 @@
 import { SourceCode } from '@theme';
 import { SourceCode } from '@theme';
+import { BasicStory } from 'components/form-materials/effects/validate-when-variable-sync';
+
+# validateWhenVariableSync
+
+When accessible variables of a field change, re-trigger validation for the specified **error fields**.
+
+:::note{title="Why error fields?"}
+
+Error messages for error fields may be derived from the validity of variable references.
+
+If the **accessible variables of a field change, making the variable references of the field change from invalid to valid**, there's a need to re-validate the current field to clear previous error messages.
 
 
-:::warning
-The material has been developed and the documentation is still being improved. Contributions are welcome.
 :::
 :::
 
 
-# validateWhenVariableSync (WIP)
+| Before Introduction | After Introduction |
+| --- | --- |
+| <img loading="lazy" src="/materials/validate-when-variable-sync-without-effect.gif" alt="syncVariableTitle not introduced" style={{ width: 500 }} /> *Variable changes from invalid to valid, error message persists* | <img loading="lazy" src="/materials/validate-when-variable-sync-with-effect.gif" alt="syncVariableTitle introduced" style={{ width: 500 }} /> *Variable changes from invalid to valid, error message automatically cleared* |
+
+## Example Demonstration
+
+### Basic Usage
+
+<BasicStory />
+
+```tsx pure title="form-meta.tsx"
+const formMeta = {
+  effect: {
+    value: validateWhenVariableSync(),
+  },
+  validate: {
+    value: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        errorMessages: {
+          unknownVariable: 'Unknown Variable',
+        },
+      }),
+  },
+};
+```
+
+## API Reference
+
+`validateWhenVariableSync(options?: { scope?: 'private' | 'public' })`
+
+| Parameter | Type | Description | Default Value | Required |
+| --- | --- | --- | --- | --- |
+| `options.scope` | `'private' \| 'public'` | Variable scope type, specifies whether to listen for changes in private or public variables | - | No |
+
+## Source Code Guide
 
 
 <SourceCode
 <SourceCode
-  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/effects/validate-when-variable-sync"
-/>
+  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/effects/validate-when-variable-sync/index.ts"
+/>
+
+Use the CLI command to copy the source code to local:
+
+```bash
+npx @flowgram.ai/cli@latest materials effects/validate-when-variable-sync
+```
+
+### Directory Structure
+
+```
+packages/materials/form-materials/src/effects/validate-when-variable-sync/
+└── index.ts           # Core implementation and export
+```
+
+### Core Implementation Explanation
+
+This effect automatically triggers validation for error fields by listening to changes in accessible variables of the field, ensuring that error messages are cleared promptly when variable references change from invalid to valid.
+
+Main process:
+1. Listen for form initialization event `DataEvent.onValueInit`
+2. Get the corresponding node scope (private or public) based on configuration
+3. Listen to the variable change event in the scope `available.onListOrAnyVarChange`
+4. When variables change, filter out error fields that include the current field name
+5. Re-validate the filtered error fields
+6. Return a cleanup function to release event listeners
+
+### Dependency Overview
+
+#### flowgram API
+
+[**@flowgram.ai/variable-core**](https://github.com/bytedance/flowgram.ai/tree/main/packages/variable-engine/variable-core)
+- `scope.available.onListOrAnyVarChange`: Listen for changes in available variables in the scope

+ 216 - 6
apps/docs/src/en/materials/validate/validate-flow-value.mdx

@@ -1,11 +1,221 @@
 import { SourceCode } from '@theme';
 import { SourceCode } from '@theme';
+import { BasicStory } from 'components/form-materials/validate/validate-flow-value';
 
 
-:::warning
-The material has been developed and the documentation is still being improved. Contributions are welcome.
-:::
+# validateFlowValue
 
 
-# validateFlowValue (WIP)
+validateFlowValue is a validation function for verifying the **requiredness and variable reference validity** of [`FlowValue`](../common/flow-value).
+
+## Case Demonstration
+
+### Basic Usage
+
+<BasicStory />
+
+```tsx pure title="form-meta.tsx"
+import { validateFlowValue } from '@flowgram.ai/form-materials';
+
+const formMeta = {
+  validate: {
+    dynamic_value_input: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        errorMessages: {
+          required: 'Value is required',
+          unknownVariable: 'Unknown Variable',
+        },
+      }),
+    required_dynamic_value_input: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        required: true,
+        errorMessages: {
+          required: 'Value is required',
+          unknownVariable: 'Unknown Variable',
+        },
+      }),
+    prompt_editor: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        required: true,
+        errorMessages: {
+          required: 'Prompt is required',
+          unknownVariable: 'Unknown Variable In Template',
+        },
+      }),
+  },
+  render: ({ form }) => (
+    <>
+      <FormHeader />
+      <b>Validate variable valid</b>
+      <Field<any> name="dynamic_value_input">
+        {({ field, fieldState }) => (
+          <>
+            <DynamicValueInput
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+            <span style={{ color: 'red' }}>
+              {fieldState.errors?.map((e) => e.message).join('\n')}
+            </span>
+          </>
+        )}
+      </Field>
+      <br />
+      <b>Validate required value</b>
+      <Field<any> name="required_dynamic_value_input">
+        {({ field, fieldState }) => (
+          <>
+            <DynamicValueInput
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+            <span style={{ color: 'red' }}>
+              {fieldState.errors?.map((e) => e.message).join('\n')}
+            </span>
+          </>
+        )}
+      </Field>
+      <br />
+      <b>Validate required and variables valid in prompt</b>
+      <Field<any> name="prompt_editor">
+        {({ field, fieldState }) => (
+          <>
+            <PromptEditorWithVariables
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+            <span style={{ color: 'red' }}>
+              {fieldState.errors?.map((e) => e.message).join('\n')}
+            </span>
+          </>
+        )}
+      </Field>
+      <br />
+      <Button onClick={() => form.validate()}>Trigger Validate</Button>
+    </>
+  ),
+};
+```
+
+## API Reference
+
+### validateFlowValue Function
+
+```typescript
+export function validateFlowValue(value: IFlowValue | undefined, ctx: Context): {
+  level: FeedbackLevel.Error;
+  message: string;
+} | undefined;
+```
+
+#### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `value` | `IFlowValue \| undefined` | The FlowValue to validate |
+| `ctx` | `Context` | Validation context |
+
+#### Context Interface
+
+```typescript
+interface Context {
+  node: FlowNodeEntity;
+  required?: boolean; // Whether required
+  errorMessages?: {
+    required?: string; // Required error message
+    unknownVariable?: string; // Unknown variable error message
+  };
+}
+```
+
+#### Return Value
+
+- If validation passes, returns `undefined`
+- If validation fails, returns an object containing error level and error message
+
+### Supported Validation Types
+
+1. **Required Validation**: When `required` is set to `true`, verifies if the value exists and is not empty
+2. **Reference Variable Validation**: For values of type `ref`, verifies if the referenced variable exists
+3. **Template Variable Validation**: For values of type `template`, verifies if all variables referenced in the template exist
+
+## Source Code Guide
 
 
 <SourceCode
 <SourceCode
-  href="https.://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/validate/validate-flow-value"
-/>
+  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/validate/validate-flow-value/index.ts"
+/>
+
+Use the CLI command to copy the source code locally:
+
+```bash
+npx @flowgram.ai/cli@latest materials validate/validate-flow-value
+```
+
+### Directory Structure
+
+```
+validate-flow-value/
+└── index.tsx           # Main function implementation, containing validateFlowValue core logic
+```
+
+### Core Implementation
+
+#### Required Validation Logic
+
+```typescript
+if (required && (isNil(value) || isNil(value?.content) || value?.content === '')) {
+  return {
+    level: FeedbackLevel.Error,
+    message: requiredMessage,
+  };
+}
+```
+
+#### Reference Variable Validation Logic
+
+```typescript
+if (value?.type === 'ref') {
+  const variable = node.scope.available.getByKeyPath(value?.content || []);
+  if (!variable) {
+    return {
+      level: FeedbackLevel.Error,
+      message: unknownVariableMessage,
+    };
+  }
+}
+```
+
+#### Template Variable Validation Logic
+
+```typescript
+if (value?.type === 'template') {
+  const allRefs = FlowValueUtils.getTemplateKeyPaths(value);
+
+  for (const ref of allRefs) {
+    const variable = node.scope.available.getByKeyPath(ref);
+    if (!variable) {
+      return {
+        level: FeedbackLevel.Error,
+        message: unknownVariableMessage,
+      };
+    }
+  }
+}
+```
+
+### Flowgram APIs Used
+
+[**@flowgram.ai/editor**](https://github.com/bytedance/flowgram.ai/tree/main/packages/client/editor)
+- [`FeedbackLevel`](https://flowgram.ai/auto-docs/editor/enums/FeedbackLevel): Feedback level enum
+
+### Dependencies on Other Materials
+
+[**FlowValue**](../common/flow-value)
+- `IFlowValue`: FlowValue type definition
+- `FlowValueUtils`: FlowValue utility class
+  - `getTemplateKeyPaths`: Method to extract all variable reference paths from templates
+
+### Third-party Libraries
+
+[**lodash-es**](https://lodash.com/)
+- `isNil`: Checks if a value is null or undefined

BIN
apps/docs/src/public/materials/validate-when-variable-sync-with-effect.gif


BIN
apps/docs/src/public/materials/validate-when-variable-sync-without-effect.gif


+ 80 - 4
apps/docs/src/zh/materials/effects/validate-when-variable-sync.mdx

@@ -1,11 +1,87 @@
 import { SourceCode } from '@theme';
 import { SourceCode } from '@theme';
+import { BasicStory } from 'components/form-materials/effects/validate-when-variable-sync';
+
+# validateWhenVariableSync
+
+当字段可访问的变量发生了变化时,则重新触发指定**错误字段**的校验。
+
+:::note{title="为什么是错误字段?"}
+
+错误字段的**错误信息可能源于变量引用的有效性**。
+
+如果字段的**可访问变量发生了变化,使得字段的变量引用从无效变为有效**,就需要需要重新校验当前字段,清空之前的错误信息。
 
 
-:::warning
-物料已完成开发,文档还在完善中,欢迎参与贡献
 :::
 :::
 
 
-# validateWhenVariableSync (WIP)
+| 引入前 | 引入后 |
+| --- | --- |
+| <img loading="lazy" src="/materials/validate-when-variable-sync-without-effect.gif" alt="syncVariableTitle 没引入" style={{ width: 500 }} /> *变量从无效变有效,错误信息还在* | <img loading="lazy" src="/materials/validate-when-variable-sync-with-effect.gif" alt="syncVariableTitle 引入" style={{ width: 500 }} /> *变量从无效变有效,错误信息自动清空* |
+
+## 案例演示
+
+### 基本使用
+
+<BasicStory />
+
+```tsx pure title="form-meta.tsx"
+const formMeta = {
+  effect: {
+    value: validateWhenVariableSync(),
+  },
+  validate: {
+    value: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        errorMessages: {
+          unknownVariable: 'Unknown Variable',
+        },
+      }),
+  },
+};
+```
+
+## API 参考
+
+`validateWhenVariableSync(options?: { scope?: 'private' | 'public' })`
+
+| 参数 | 类型 | 说明 | 默认值 | 是否必选 |
+| --- | --- | --- | --- | --- |
+| `options.scope` | `'private' \| 'public'` | 变量作用域类型,指定监听私有还是公共变量的变化 | - | 否 |
+
+## 源码导读
 
 
 <SourceCode
 <SourceCode
-  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/effects/validate-when-variable-sync"
+  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/effects/validate-when-variable-sync/index.ts"
 />
 />
+
+使用 CLI 命令可以复制源代码到本地:
+
+```bash
+npx @flowgram.ai/cli@latest materials effects/validate-when-variable-sync
+```
+
+### 目录结构讲解
+
+```
+packages/materials/form-materials/src/effects/validate-when-variable-sync/
+└── index.ts           # 核心实现和导出
+```
+
+### 核心实现说明
+
+该效果通过监听字段可访问的变量变化,当变量发生变化时自动触发错误字段的校验,确保当变量引用从无效变为有效时能及时清除错误信息。
+
+主要流程:
+1. 监听表单初始化事件 `DataEvent.onValueInit`
+2. 根据配置获取对应的节点作用域(私有或公共)
+3. 监听作用域中可用变量的变化事件 `available.onListOrAnyVarChange`
+4. 当变量变化时,筛选出包含当前字段名称的错误字段
+5. 对筛选出的错误字段重新进行校验
+6. 返回清理函数以释放事件监听器
+
+### 依赖梳理
+
+#### flowgram API
+
+[**@flowgram.ai/variable-core**](https://github.com/bytedance/flowgram.ai/tree/main/packages/variable-engine/variable-core)
+- `scope.available.onListOrAnyVarChange`: 监听作用域中可用变量的变化事件

+ 215 - 5
apps/docs/src/zh/materials/validate/validate-flow-value.mdx

@@ -1,11 +1,221 @@
 import { SourceCode } from '@theme';
 import { SourceCode } from '@theme';
+import { BasicStory } from 'components/form-materials/validate/validate-flow-value';
 
 
-:::warning
-物料已完成开发,文档还在完善中,欢迎参与贡献
-:::
+# validateFlowValue
 
 
-# validateFlowValue (WIP)
+validateFlowValue 是一个用于验证 [`FlowValue`](../common/flow-value) **必填性和变量引用有效性** 的验证函数。
+
+## 案例演示
+
+### 基本使用
+
+<BasicStory />
+
+```tsx pure title="form-meta.tsx"
+import { validateFlowValue } from '@flowgram.ai/form-materials';
+
+const formMeta = {
+  validate: {
+    dynamic_value_input: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        errorMessages: {
+          required: 'Value is required',
+          unknownVariable: 'Unknown Variable',
+        },
+      }),
+    required_dynamic_value_input: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        required: true,
+        errorMessages: {
+          required: 'Value is required',
+          unknownVariable: 'Unknown Variable',
+        },
+      }),
+    prompt_editor: ({ value, context }) =>
+      validateFlowValue(value, {
+        node: context.node,
+        required: true,
+        errorMessages: {
+          required: 'Prompt is required',
+          unknownVariable: 'Unknown Variable In Template',
+        },
+      }),
+  },
+  render: ({ form }) => (
+    <>
+      <FormHeader />
+      <b>Validate variable valid</b>
+      <Field<any> name="dynamic_value_input">
+        {({ field, fieldState }) => (
+          <>
+            <DynamicValueInput
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+            <span style={{ color: 'red' }}>
+              {fieldState.errors?.map((e) => e.message).join('\n')}
+            </span>
+          </>
+        )}
+      </Field>
+      <br />
+      <b>Validate required value</b>
+      <Field<any> name="required_dynamic_value_input">
+        {({ field, fieldState }) => (
+          <>
+            <DynamicValueInput
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+            <span style={{ color: 'red' }}>
+              {fieldState.errors?.map((e) => e.message).join('\n')}
+            </span>
+          </>
+        )}
+      </Field>
+      <br />
+      <b>Validate required and variables valid in prompt</b>
+      <Field<any> name="prompt_editor">
+        {({ field, fieldState }) => (
+          <>
+            <PromptEditorWithVariables
+              value={field.value}
+              onChange={(value) => field.onChange(value)}
+            />
+            <span style={{ color: 'red' }}>
+              {fieldState.errors?.map((e) => e.message).join('\n')}
+            </span>
+          </>
+        )}
+      </Field>
+      <br />
+      <Button onClick={() => form.validate()}>Trigger Validate</Button>
+    </>
+  ),
+};
+```
+
+## API 参考
+
+### validateFlowValue 函数
+
+```typescript
+export function validateFlowValue(value: IFlowValue | undefined, ctx: Context): {
+  level: FeedbackLevel.Error;
+  message: string;
+} | undefined;
+```
+
+#### 参数
+
+| 参数名 | 类型 | 描述 |
+|--------|------|------|
+| `value` | `IFlowValue \| undefined` | 要验证的 FlowValue 值 |
+| `ctx` | `Context` | 验证上下文 |
+
+#### Context 接口
+
+```typescript
+interface Context {
+  node: FlowNodeEntity;
+  required?: boolean; // 是否必填
+  errorMessages?: {
+    required?: string; // 必填错误消息
+    unknownVariable?: string; // 未知变量错误消息
+  };
+}
+```
+
+#### 返回值
+
+- 如果验证通过,返回 `undefined`
+- 如果验证失败,返回包含错误级别和错误消息的对象
+
+### 支持的验证类型
+
+1. **必填验证**:当 `required` 设置为 `true` 时,验证值是否存在且内容不为空
+2. **引用变量验证**:对于 `ref` 类型的值,验证引用的变量是否存在
+3. **模板变量验证**:对于 `template` 类型的值,验证模板中引用的所有变量是否存在
+
+## 源码导读
 
 
 <SourceCode
 <SourceCode
-  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/validate/validate-flow-value"
+  href="https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/validate/validate-flow-value/index.ts"
 />
 />
+
+使用 CLI 命令可以复制源代码到本地:
+
+```bash
+npx @flowgram.ai/cli@latest materials validate/validate-flow-value
+```
+
+### 目录结构讲解
+
+```
+validate-flow-value/
+└── index.tsx           # 主函数实现,包含 validateFlowValue 核心逻辑
+```
+
+### 核心实现说明
+
+#### 必填验证逻辑
+
+```typescript
+if (required && (isNil(value) || isNil(value?.content) || value?.content === '')) {
+  return {
+    level: FeedbackLevel.Error,
+    message: requiredMessage,
+  };
+}
+```
+
+#### 引用变量验证逻辑
+
+```typescript
+if (value?.type === 'ref') {
+  const variable = node.scope.available.getByKeyPath(value?.content || []);
+  if (!variable) {
+    return {
+      level: FeedbackLevel.Error,
+      message: unknownVariableMessage,
+    };
+  }
+}
+```
+
+#### 模板内变量验证逻辑
+
+```typescript
+if (value?.type === 'template') {
+  const allRefs = FlowValueUtils.getTemplateKeyPaths(value);
+
+  for (const ref of allRefs) {
+    const variable = node.scope.available.getByKeyPath(ref);
+    if (!variable) {
+      return {
+        level: FeedbackLevel.Error,
+        message: unknownVariableMessage,
+      };
+    }
+  }
+}
+```
+
+### 使用到的 flowgram API
+
+[**@flowgram.ai/editor**](https://github.com/bytedance/flowgram.ai/tree/main/packages/client/editor)
+- [`FeedbackLevel`](https://flowgram.ai/auto-docs/editor/enums/FeedbackLevel): 反馈级别枚举
+
+### 依赖的其他物料
+
+[**FlowValue**](../common/flow-value)
+- `IFlowValue`: FlowValue 类型定义
+- `FlowValueUtils`: FlowValue 工具类
+  - `getTemplateKeyPaths`: 从模板中提取所有变量引用路径的方法
+
+### 第三方库
+
+[**lodash-es**](https://lodash.com/)
+- `isNil`: 检查值是否为 null 或 undefined

+ 7 - 3
packages/materials/form-materials/src/effects/validate-when-variable-sync/index.ts

@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: MIT
  * SPDX-License-Identifier: MIT
  */
  */
 
 
-import { isEmpty } from 'lodash-es';
 import {
 import {
   DataEvent,
   DataEvent,
   Effect,
   Effect,
@@ -19,12 +18,17 @@ export const validateWhenVariableSync = ({
 } = {}): EffectOptions[] => [
 } = {}): EffectOptions[] => [
   {
   {
     event: DataEvent.onValueInit,
     event: DataEvent.onValueInit,
-    effect: (({ context, form }) => {
+    effect: (({ context, form, name }) => {
       const nodeScope =
       const nodeScope =
         scope === 'private' ? getNodePrivateScope(context.node) : getNodeScope(context.node);
         scope === 'private' ? getNodePrivateScope(context.node) : getNodeScope(context.node);
 
 
       const disposable = nodeScope.available.onListOrAnyVarChange(() => {
       const disposable = nodeScope.available.onListOrAnyVarChange(() => {
-        if (!isEmpty(form.state.errors)) {
+        const errorKeys = Object.entries(form.state.errors || {})
+          .filter(([_, errors]) => errors?.length > 0)
+          .filter(([key]) => key.startsWith(name) || name.startsWith(key))
+          .map(([key]) => key);
+
+        if (errorKeys.length > 0) {
           form.validate();
           form.validate();
         }
         }
       });
       });