Просмотр исходного кода

feat(material): when dep material exists, not overwrite (#504)

* feat(materials): PromptEditorWithVariables react 16,17 compat

* feat(material): not overwrite for dep materials

* fix(material): prompt editor with inputs controlled by value

* feat: add placeholder in code-editor
Yiwei Mao 6 месяцев назад
Родитель
Сommit
d1133fb041

+ 6 - 1
packages/materials/form-materials/bin/index.ts

@@ -85,9 +85,14 @@ program
     // 5. Copy the materials to the project
     console.log(chalk.bold('These Materials will be added to your project'));
     console.log(allMaterials);
+    copyMaterial(material, projectInfo, { overwrite: true });
+
     allMaterials.forEach((mat: Material) => {
+      if (mat === material) {
+        return;
+      }
       // Add type for mat
-      copyMaterial(mat, projectInfo);
+      copyMaterial(mat, projectInfo, { overwrite: false });
     });
   });
 

+ 15 - 1
packages/materials/form-materials/bin/materials.ts

@@ -115,7 +115,15 @@ export function bfsMaterials(material: Material, _materials: Material[] = []): B
   };
 }
 
-export const copyMaterial = (material: Material, projectInfo: ProjectInfo): void => {
+export const copyMaterial = (
+  material: Material,
+  projectInfo: ProjectInfo,
+  {
+    overwrite,
+  }: {
+    overwrite?: boolean;
+  } = {}
+): void => {
   const sourceDir: string = material.path;
   const materialRoot: string = path.join(
     projectInfo.projectPath,
@@ -124,6 +132,12 @@ export const copyMaterial = (material: Material, projectInfo: ProjectInfo): void
     `${material.type}`
   );
   const targetDir = path.join(materialRoot, material.name);
+
+  if (!overwrite && fs.readdirSync(targetDir)?.length > 0) {
+    console.log(`Material ${material.name} already exists in ${materialRoot}, skip copying.`);
+    return;
+  }
+
   fs.cpSync(sourceDir, targetDir, { recursive: true });
 
   let materialRootIndexTs: string = '';

+ 17 - 2
packages/materials/form-materials/src/components/code-editor/index.tsx

@@ -3,9 +3,9 @@
  * SPDX-License-Identifier: MIT
  */
 
-import React, { useRef } from 'react';
+import React, { useEffect, useRef } from 'react';
 
-import { createRenderer, EditorProvider } from '@coze-editor/editor/react';
+import { ActiveLinePlaceholder, createRenderer, EditorProvider } from '@coze-editor/editor/react';
 import preset, { type EditorAPI } from '@coze-editor/editor/preset-code';
 import { EditorView } from '@codemirror/view';
 
@@ -27,6 +27,8 @@ export interface CodeEditorPropsType extends React.PropsWithChildren<{}> {
   onChange?: (value: string) => void;
   languageId: 'python' | 'typescript' | 'shell' | 'json';
   theme?: 'dark' | 'light';
+  placeholder?: string;
+  activeLinePlaceholder?: string;
 }
 
 export function CodeEditor({
@@ -35,9 +37,18 @@ export function CodeEditor({
   languageId = 'python',
   theme = 'light',
   children,
+  placeholder,
+  activeLinePlaceholder,
 }: CodeEditorPropsType) {
   const editorRef = useRef<EditorAPI | null>(null);
 
+  useEffect(() => {
+    // listen to value change
+    if (editorRef.current?.getValue() !== value) {
+      editorRef.current?.setValue(String(value || ''));
+    }
+  }, [value]);
+
   return (
     <EditorProvider>
       <OriginCodeEditor
@@ -46,12 +57,16 @@ export function CodeEditor({
           uri: `file:///untitled${getSuffixByLanguageId(languageId)}`,
           languageId,
           theme,
+          placeholder,
         }}
         didMount={(editor: EditorAPI) => {
           editorRef.current = editor;
         }}
         onChange={(e) => onChange?.(e.value)}
       >
+        {activeLinePlaceholder && (
+          <ActiveLinePlaceholder>{activeLinePlaceholder}</ActiveLinePlaceholder>
+        )}
         {children}
       </OriginCodeEditor>
     </EditorProvider>

+ 1 - 1
packages/materials/form-materials/src/components/json-editor-with-variables/index.tsx

@@ -11,7 +11,7 @@ import { CodeEditor, type CodeEditorPropsType } from '../code-editor';
 
 export function JsonEditorWithVariables(props: Omit<CodeEditorPropsType, 'languageId'>) {
   return (
-    <CodeEditor languageId="json" {...props}>
+    <CodeEditor languageId="json" activeLinePlaceholder="Press '@' to Select variable" {...props}>
       <VariableTree />
       <VariableTagInject />
     </CodeEditor>

+ 10 - 2
packages/materials/form-materials/src/components/json-schema-editor/config.json

@@ -1,5 +1,13 @@
 {
   "name": "json-schema-editor",
-  "depMaterials": ["type-selector", "typings/json-schema"],
-  "depPackages": ["@douyinfe/semi-ui", "@douyinfe/semi-icons", "styled-components"]
+  "depMaterials": [
+    "type-selector",
+    "typings/json-schema",
+    "constant-inputs"
+  ],
+  "depPackages": [
+    "@douyinfe/semi-ui",
+    "@douyinfe/semi-icons",
+    "styled-components"
+  ]
 }

+ 12 - 6
packages/materials/form-materials/src/components/prompt-editor-with-variables/extensions/variable-tag.tsx

@@ -3,9 +3,9 @@
  * SPDX-License-Identifier: MIT
  */
 
+import ReactDOM from 'react-dom';
 import React, { useLayoutEffect } from 'react';
 
-import { createRoot, Root } from 'react-dom/client';
 import { isEqual, last } from 'lodash';
 import {
   BaseVariableField,
@@ -35,7 +35,7 @@ class VariableTagWidget extends WidgetType {
 
   scope: Scope;
 
-  root: Root;
+  rootDOM?: HTMLSpanElement;
 
   constructor({ keyPath, scope }: { keyPath?: string[]; scope: Scope }) {
     super();
@@ -52,9 +52,14 @@ class VariableTagWidget extends WidgetType {
     return icon;
   };
 
+  renderReact(jsx: React.ReactElement) {
+    // NOTICE: For React 19, upgrade to 'react-dom/client'
+    ReactDOM.render(jsx, this.rootDOM!);
+  }
+
   renderVariable(v?: BaseVariableField) {
     if (!v) {
-      this.root.render(
+      this.renderReact(
         <UITag prefixIcon={<IconIssueStroked />} color="amber">
           Unknown
         </UITag>
@@ -69,7 +74,7 @@ class VariableTagWidget extends WidgetType {
     );
     const rootIcon = this.renderIcon(rootField?.meta.icon);
 
-    this.root.render(
+    this.renderReact(
       <Popover
         content={
           <UIPopoverContent>
@@ -90,11 +95,12 @@ class VariableTagWidget extends WidgetType {
   toDOM(view: EditorView): HTMLElement {
     const dom = document.createElement('span');
 
-    this.root = createRoot(dom);
+    this.rootDOM = dom;
 
     this.toDispose.push(
       Disposable.create(() => {
-        this.root.unmount();
+        // NOTICE: For React 19, upgrade to 'react-dom/client'
+        ReactDOM.unmountComponentAtNode(this.rootDOM!);
       })
     );
 

+ 14 - 2
packages/materials/form-materials/src/components/prompt-editor/index.tsx

@@ -3,10 +3,10 @@
  * SPDX-License-Identifier: MIT
  */
 
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
 
 import { Renderer, EditorProvider, ActiveLinePlaceholder } from '@coze-editor/editor/react';
-import preset from '@coze-editor/editor/preset-prompt';
+import preset, { EditorAPI } from '@coze-editor/editor/preset-prompt';
 
 import { PropsType } from './types';
 import { UIContainer } from './styles';
@@ -28,10 +28,22 @@ export function PromptEditor(props: PropsType) {
     children,
   } = props || {};
 
+  const editorRef = useRef<EditorAPI | null>(null);
+
+  useEffect(() => {
+    // listen to value change
+    if (editorRef.current?.getValue() !== value?.content) {
+      editorRef.current?.setValue(String(value?.content || ''));
+    }
+  }, [value]);
+
   return (
     <UIContainer $hasError={hasError} style={style}>
       <EditorProvider>
         <Renderer
+          didMount={(editor: EditorAPI) => {
+            editorRef.current = editor;
+          }}
           plugins={preset}
           defaultValue={String(value?.content)}
           options={{