diff --git a/web/src/components/prompt-editor/constant.ts b/web/src/components/prompt-editor/constant.ts new file mode 100644 index 000000000..b6cf30ed9 --- /dev/null +++ b/web/src/components/prompt-editor/constant.ts @@ -0,0 +1 @@ +export const ProgrammaticTag = 'programmatic'; diff --git a/web/src/components/prompt-editor/index.tsx b/web/src/components/prompt-editor/index.tsx index 695d10047..ba20e62f2 100644 --- a/web/src/components/prompt-editor/index.tsx +++ b/web/src/components/prompt-editor/index.tsx @@ -1,13 +1,10 @@ import { CodeHighlightNode, CodeNode } from '@lexical/code'; -import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'; import { InitialConfigType, LexicalComposer, } from '@lexical/react/LexicalComposer'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; -import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; -import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { HeadingNode, QuoteNode } from '@lexical/rich-text'; import { @@ -18,9 +15,11 @@ import { LexicalNode, } from 'lexical'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import theme from './theme'; import { VariableNode } from './variable-node'; +import { VariableOnChangePlugin } from './variable-on-change-plugin'; import VariablePickerMenuPlugin from './variable-picker-plugin'; // Catch any errors that occur during Lexical updates and log them @@ -52,16 +51,20 @@ export function PromptEditor({ value, onChange }: IProps) { nodes: Nodes, }; - function onValueChange(editorState: EditorState) { - editorState?.read(() => { - const listNodes = $nodesOfType(VariableNode); // to be removed - // const allNodes = $dfs(); - console.log('🚀 ~ onChange ~ allNodes:', listNodes); + const onValueChange = useCallback( + (editorState: EditorState) => { + editorState?.read(() => { + const listNodes = $nodesOfType(VariableNode); // to be removed + // const allNodes = $dfs(); + console.log('🚀 ~ onChange ~ allNodes:', listNodes); - const text = $getRoot().getTextContent(); - onChange?.(text); - }); - } + const text = $getRoot().getTextContent(); + + onChange?.(text); + }); + }, + [onChange], + ); return ( @@ -74,10 +77,8 @@ export function PromptEditor({ value, onChange }: IProps) { } ErrorBoundary={LexicalErrorBoundary} /> - - - + ); } diff --git a/web/src/components/prompt-editor/variable-on-change-plugin.tsx b/web/src/components/prompt-editor/variable-on-change-plugin.tsx new file mode 100644 index 000000000..86fa66db4 --- /dev/null +++ b/web/src/components/prompt-editor/variable-on-change-plugin.tsx @@ -0,0 +1,35 @@ +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; +import { EditorState, LexicalEditor } from 'lexical'; +import { useEffect } from 'react'; +import { ProgrammaticTag } from './constant'; + +interface IProps { + onChange: ( + editorState: EditorState, + editor?: LexicalEditor, + tags?: Set, + ) => void; +} + +export function VariableOnChangePlugin({ onChange }: IProps) { + // Access the editor through the LexicalComposerContext + const [editor] = useLexicalComposerContext(); + // Wrap our listener in useEffect to handle the teardown and avoid stale references. + useEffect(() => { + // most listeners return a teardown function that can be called to clean them up. + return editor.registerUpdateListener( + ({ editorState, tags, dirtyElements }) => { + // Check if there is a "programmatic" tag + const isProgrammaticUpdate = tags.has(ProgrammaticTag); + + // The onchange event is only triggered when the data is manually updated + // Otherwise, the content will be displayed incorrectly. + if (dirtyElements.size > 0 && !isProgrammaticUpdate) { + onChange(editorState); + } + }, + ); + }, [editor, onChange]); + + return null; +} diff --git a/web/src/components/prompt-editor/variable-picker-plugin.tsx b/web/src/components/prompt-editor/variable-picker-plugin.tsx index 4a30ad3bc..b50e540ee 100644 --- a/web/src/components/prompt-editor/variable-picker-plugin.tsx +++ b/web/src/components/prompt-editor/variable-picker-plugin.tsx @@ -33,6 +33,7 @@ import { FlowFormContext } from '@/pages/flow/context'; import { useBuildComponentIdSelectOptions } from '@/pages/flow/hooks/use-get-begin-query'; import { $createVariableNode } from './variable-node'; +import { ProgrammaticTag } from './constant'; import './index.css'; class VariableInnerOption extends MenuOption { label: string; @@ -215,9 +216,12 @@ export default function VariablePickerMenuPlugin({ useEffect(() => { if (editor && value && isFirstRender.current) { isFirstRender.current = false; - editor.update(() => { - parseTextToVariableNodes(value); - }); + editor.update( + () => { + parseTextToVariableNodes(value); + }, + { tag: ProgrammaticTag }, + ); } }, [parseTextToVariableNodes, editor, value]); diff --git a/web/src/hooks/flow-hooks.ts b/web/src/hooks/flow-hooks.ts index e733e9be1..5a0964dc0 100644 --- a/web/src/hooks/flow-hooks.ts +++ b/web/src/hooks/flow-hooks.ts @@ -3,6 +3,7 @@ import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; import { IDebugSingleRequestBody } from '@/interfaces/request/flow'; import i18n from '@/locales/config'; import { useGetSharedChatSearchParams } from '@/pages/chat/shared-hooks'; +import { BeginId } from '@/pages/flow/constant'; import flowService from '@/services/flow-service'; import { buildMessageListWithUuid } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; @@ -16,7 +17,7 @@ export const EmptyDsl = { graph: { nodes: [ { - id: 'begin', + id: BeginId, type: 'beginNode', position: { x: 50, diff --git a/web/src/pages/flow/form/generate-form/index.tsx b/web/src/pages/flow/form/generate-form/index.tsx index 076ed2518..f21ce1035 100644 --- a/web/src/pages/flow/form/generate-form/index.tsx +++ b/web/src/pages/flow/form/generate-form/index.tsx @@ -35,6 +35,7 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => { }, ]} > + {/* */} { useDebounceEffect( () => { - saveAgent(); + // saveAgent(); }, [nodes, edges], {