Jelajahi Sumber

feat(material): prompt-editor-with-inputs outside canvas (#950)

Yiwei Mao 2 bulan lalu
induk
melakukan
c6e55e56d5

+ 26 - 1
apps/docs/components/form-materials/components/prompt-editor-with-inputs.tsx

@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: MIT
  */
 
-import React from 'react';
+import React, { useState } from 'react';
 
 import { Field } from '@flowgram.ai/free-layout-editor';
 import { IFlowTemplateValue, IInputsValues, InputsValuesTree } from '@flowgram.ai/form-materials';
@@ -59,3 +59,28 @@ export const BasicStory = () => (
     }}
   />
 );
+
+export const WithoutCanvas = () => {
+  const [value, setValue] = useState<IFlowTemplateValue | undefined>({
+    type: 'template',
+    content: '# Role \nYou are a helpful assistant. \n\n# Query \n{{b.obj2.num}} \n\n',
+  });
+
+  return (
+    <div>
+      <PromptEditorWithInputs
+        value={value}
+        onChange={(value) => setValue(value)}
+        inputsValues={{
+          a: { type: 'constant', content: '123' },
+          b: {
+            c: {
+              d: { type: 'constant', content: 456 },
+            },
+            e: { type: 'constant', content: 789 },
+          },
+        }}
+      />
+    </div>
+  );
+};

+ 47 - 88
apps/docs/src/en/materials/components/prompt-editor-with-inputs.mdx

@@ -1,5 +1,5 @@
 import { SourceCode } from '@theme';
-import { BasicStory } from 'components/form-materials/components/prompt-editor-with-inputs';
+import { BasicStory, WithoutCanvas } from 'components/form-materials/components/prompt-editor-with-inputs';
 
 # PromptEditorWithInputs
 
@@ -59,6 +59,45 @@ Enter the `@`, `{` characters in the editor to trigger the Inputs selector.
 
 After entering `@`, `{`, a list of available variables will be displayed. Selecting a variable will automatically insert it in the `{{inputs.path}}` format.
 
+### Usage Without Canvas
+
+:::warning
+
+When used without canvas, due to inability to access variables, inputsValues does not support value definitions of type: 'ref'.
+
+:::
+
+<WithoutCanvas />
+
+```tsx pure title="with-canvas.tsx"
+export const WithoutCanvas = () => {
+  const [value, setValue] = useState<IFlowTemplateValue | undefined>({
+    type: 'template',
+    content: '# Role \n You are a helpful assistant. \n\n # Query \n {{b.obj2.num}} \n\n',
+  });
+
+  return (
+    <div>
+      <PromptEditorWithInputs
+        value={value}
+        onChange={(value) => setValue(value)}
+        inputsValues={{
+          a: { type: 'constant', content: '123' },
+          b: {
+            c: {
+              d: { type: 'constant', content: 456 },
+            },
+            e: { type: 'constant', content: 789 },
+          },
+        }}
+      />
+    </div>
+  );
+};
+```
+
+
+
 ## API Reference
 
 ### PromptEditorWithInputs Props
@@ -91,101 +130,21 @@ npx @flowgram.ai/cli@latest materials components/prompt-editor-with-inputs
 
 ```
 prompt-editor-with-inputs/
-├── 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
 
 #### Input Variable Integration
-PromptEditorWithInputs extends the basic PromptEditor, adding an input variable selector:
-
-```typescript
-export function PromptEditorWithInputs({
-  inputsValues,
-  ...restProps
-}: PromptEditorWithInputsProps) {
-  return (
-    <PromptEditor {...restProps}>
-      <EditorInputsTree inputsValues={inputsValues} />
-    </PromptEditor>
-  );
-}
-```
-
-#### Basic Prompt Editor
-The basic PromptEditor provides complete template editing functionality:
 
-```typescript
-<PromptEditor
-  value={field.value}
-  onChange={(value) => field.onChange(value)}
-  placeholder="Enter prompt template..."
-  activeLinePlaceholder="Press @ to insert variable"
-/>
-```
+PromptEditorWithInputs extends the basic [PromptEditor](./prompt-editor) and adds node inputs reference functionality based on [coze-editor-extensions](./coze-editor-extensions).
 
-#### Editor Extensions
-The basic editor integrates multiple extensions:
-
-- **MarkdownHighlight**: Provides Markdown syntax highlighting
-- **LanguageSupport**: Supports multiple programming languages
-- **JinjaHighlight**: Jinja2 template syntax highlighting
-
-#### Variable Selector
-The `EditorInputsTree` component provides a tree-structured variable selector:
-
-```typescript
-<EditorInputsTree inputsValues={inputsValues} />
-```
+### Dependencies
 
-### Flowgram APIs Used
+[**PromptEditor**](./prompt-editor)
 
-#### @flowgram.ai/coze-editor/react
-- `Renderer`: Editor renderer
-- `EditorProvider`: Editor provider
-- `ActiveLinePlaceholder`: Active line placeholder
-- `InferValues`: Type inference tool
+[**CozeEditorExtensions**](./coze-editor-extensions)
+- `EditorInputsTree`: Input tree selection trigger
 
-#### @flowgram.ai/coze-editor/preset-prompt
-- `preset`: Prompt editor preset configuration
-- `EditorAPI`: Editor API interface
-
-#### @flowgram.ai/shared
+[**FlowValue**](../common/flow-value)
 - `IInputsValues`: Input variable type definition
-
-### Overall Process
-
-```mermaid
-graph TD
-    A[PromptEditorWithInputs] --> B[Pass inputsValues]
-    A --> C[Render PromptEditor]
-    C --> D[Load preset configuration]
-    D --> E[Integrate extension plugins]
-
-    E --> F[MarkdownHighlight]
-    E --> G[LanguageSupport]
-    E --> H[JinjaHighlight]
-
-    F --> I[Syntax highlighting]
-    G --> J[Language support]
-    H --> K[Template syntax]
-
-    B --> L[EditorInputsTree]
-    L --> M[Input tree parsing + display]
-    M --> N[Variable selection]
-    N --> O[Insert variable]
-
-    O --> P[Update template content]
-```

+ 38 - 1
apps/docs/src/zh/materials/components/prompt-editor-with-inputs.mdx

@@ -1,5 +1,5 @@
 import { SourceCode } from '@theme';
-import { BasicStory } from 'components/form-materials/components/prompt-editor-with-inputs';
+import { BasicStory, WithoutCanvas } from 'components/form-materials/components/prompt-editor-with-inputs';
 
 # PromptEditorWithInputs
 
@@ -64,6 +64,43 @@ const formMeta = {
 }
 ```
 
+### 脱离画布使用
+
+:::warning
+
+脱离画布使用时,由于没法访问变量,inputsValues 内不支持 type: 'ref' 类型的值定义
+
+:::
+
+<WithoutCanvas />
+
+```tsx pure title="with-canvas.tsx"
+export const WithoutCanvas = () => {
+  const [value, setValue] = useState<IFlowTemplateValue | undefined>({
+    type: 'template',
+    content: '# Role \n You are a helpful assistant. \n\n # Query \n {{b.obj2.num}} \n\n',
+  });
+
+  return (
+    <div>
+      <PromptEditorWithInputs
+        value={value}
+        onChange={(value) => setValue(value)}
+        inputsValues={{
+          a: { type: 'constant', content: '123' },
+          b: {
+            c: {
+              d: { type: 'constant', content: 456 },
+            },
+            e: { type: 'constant', content: 789 },
+          },
+        }}
+      />
+    </div>
+  );
+};
+```
+
 
 ## API 参考
 

+ 3 - 3
packages/materials/form-materials/src/components/coze-editor-extensions/extensions/inputs-tree.tsx

@@ -11,7 +11,7 @@ import {
   ASTMatch,
   type BaseType,
   type BaseVariableField,
-  useScopeAvailable,
+  useCurrentScope,
 } from '@flowgram.ai/editor';
 import {
   Mention,
@@ -36,7 +36,7 @@ export function InputsPicker({
   inputsValues: IInputsValues;
   onSelect: (v: string) => void;
 }) {
-  const available = useScopeAvailable();
+  const scope = useCurrentScope();
 
   const getArrayDrilldown = (type: ArrayType, depth = 1): { type: BaseType; depth: number } => {
     if (ASTMatch.isArray(type.items)) {
@@ -90,7 +90,7 @@ export function InputsPicker({
 
     if (FlowValueUtils.isFlowValue(value)) {
       if (FlowValueUtils.isRef(value)) {
-        const variable = available.getByKeyPath(value.content || []);
+        const variable = scope?.available?.getByKeyPath(value.content || []);
         if (variable) {
           return renderVariable(variable, keyPath);
         }

+ 1 - 1
packages/materials/form-materials/src/components/coze-editor-extensions/extensions/variable-tag.tsx

@@ -138,7 +138,7 @@ class VariableTagWidget extends WidgetType {
 export function VariableTagInject() {
   const injector = useInjector();
 
-  const scope = useCurrentScope();
+  const scope = useCurrentScope({ strict: true });
 
   // 基于 {{var}} 的正则进行匹配,匹配后进行自定义渲染
   useLayoutEffect(() => {

+ 2 - 2
packages/materials/form-materials/src/components/display-outputs/index.tsx

@@ -24,7 +24,7 @@ export function DisplayOutputs({ value, showIconInTree, displayFromScope }: Prop
   const refresh = useRefresh();
 
   useEffect(() => {
-    if (!displayFromScope) {
+    if (!displayFromScope || !scope) {
       return () => null;
     }
 
@@ -38,7 +38,7 @@ export function DisplayOutputs({ value, showIconInTree, displayFromScope }: Prop
   }, [displayFromScope]);
 
   const properties: IJsonSchema['properties'] = displayFromScope
-    ? scope.output.variables?.reduce((acm, curr) => {
+    ? (scope?.output.variables || []).reduce((acm, curr) => {
         acm = {
           ...acm,
           ...(JsonSchemaUtils.astToSchema(curr.type)?.properties || {}),

+ 1 - 1
packages/materials/form-materials/src/shared/inject-material/index.tsx

@@ -62,7 +62,7 @@ export function createInjectMaterial<Props>(
     const container = usePlaygroundContainer();
 
     // Check if renderer registry is bound in container
-    if (!container?.isBound(FlowRendererRegistry)) {
+    if (!container?.isBound?.(FlowRendererRegistry)) {
       // If no registry, use default component directly
       return React.createElement(Component as (props?: any) => any, { ...props });
     }

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

@@ -45,12 +45,12 @@ export const ScopeProvider = (
  * useCurrentScope returns the scope provided by ScopeProvider.
  * @returns
  */
-export const useCurrentScope = (params?: {
+export const useCurrentScope = <Strict extends boolean = false>(params?: {
   /**
    * whether to throw error when no scope in ScopeProvider is found
    */
-  strict?: boolean;
-}): Scope => {
+  strict: Strict;
+}): Strict extends true ? Scope : Scope | undefined => {
   const { strict = false } = params || {};
 
   const context = useContext(ScopeContext);

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

@@ -34,5 +34,5 @@ export function useOutputVariables(): VariableDeclaration[] {
     return () => disposable.dispose();
   }, []);
 
-  return scope.output.variables;
+  return scope?.output.variables || [];
 }

+ 1 - 1
packages/variable-engine/variable-core/src/react/hooks/use-scope-available.ts

@@ -19,7 +19,7 @@ import { ScopeAvailableData } from '../../scope/datas';
 export function useScopeAvailable(params?: { autoRefresh?: boolean }): ScopeAvailableData {
   const { autoRefresh = true } = params || {};
 
-  const scope = useCurrentScope();
+  const scope = useCurrentScope({ strict: true });
   const refresh = useRefresh();
 
   useEffect(() => {