11import type { UseMutationResult } from "@tanstack/react-query" ;
22import { cloneDeep , debounce } from "lodash" ;
33import { SAVE_DEBOUNCE_TIME } from "@/constants/constants" ;
4+ import useFlowStore from "@/stores/flowStore" ;
45import type { APIClassType , ResponseErrorDetailAPI } from "@/types/api" ;
56import i18n from "../../i18n" ;
67import { updateHiddenOutputs } from "./update-hidden-outputs" ;
78
8- // Map to store debounced functions for each node ID + parameter combination
99const debouncedFunctions = new Map < string , ReturnType < typeof debounce > > ( ) ;
1010
11+ const getNodeCode = ( nodeId : string ) : unknown => {
12+ const currentNode = useFlowStore
13+ . getState ( )
14+ . nodes . find ( ( flowNode ) => flowNode . id === nodeId ) ;
15+ return currentNode ?. data ?. node ?. template ?. code ?. value ;
16+ } ;
17+
18+ // A refresh answers for the code that was current when it left, and applying it
19+ // replaces the whole template — a code save landing meanwhile would be reverted.
20+ const isStaleForNode = (
21+ nodeId : string ,
22+ requestedNode : APIClassType ,
23+ ) : boolean => {
24+ const requestedCode = requestedNode . template ?. code ?. value ;
25+ if ( requestedCode === undefined ) return false ;
26+ const currentCode = getNodeCode ( nodeId ) ;
27+ if ( currentCode === undefined ) return false ;
28+ return currentCode !== requestedCode ;
29+ } ;
30+
1131export const mutateTemplate = async (
1232 newValue ,
1333 nodeId : string ,
@@ -25,8 +45,7 @@ export const mutateTemplate = async (
2545 toolMode ?: boolean ,
2646 isRefresh ?: boolean ,
2747) => {
28- // Different parameters must debounce independently to avoid one field's
29- // refresh cancelling another's during concurrent mount calls.
48+ // Per-parameter keys keep one field's refresh from cancelling another's on mount.
3049 const debounceKey = parameterName ? `${ nodeId } -${ parameterName } ` : nodeId ;
3150 if ( ! debouncedFunctions . has ( debounceKey ) ) {
3251 debouncedFunctions . set (
@@ -56,7 +75,7 @@ export const mutateTemplate = async (
5675 tool_mode : toolMode ?? node . tool_mode ,
5776 is_refresh : isRefresh ?? false ,
5877 } ) ;
59- if ( newTemplate ) {
78+ if ( newTemplate && ! isStaleForNode ( nodeId , node ) ) {
6079 newNode . template = newTemplate . template ;
6180 newNode . outputs = updateHiddenOutputs (
6281 newNode . outputs ?? [ ] ,
@@ -97,11 +116,8 @@ export const mutateTemplate = async (
97116 ) ;
98117 }
99118
100- // Enabling Tool Mode mounts the tools_metadata field, which queues its own
101- // debounced refresh. If the user turns Tool Mode off before that refresh
102- // runs, the queued request still carries tool_mode=true and can restore the
103- // Toolset output after the off response. The explicit toggle supersedes that
104- // pending metadata refresh.
119+ // A queued tools_metadata refresh still carries tool_mode=true and would restore
120+ // the Toolset output after an off response, so the explicit toggle supersedes it.
105121 if ( parameterName === "tool_mode" ) {
106122 debouncedFunctions . get ( `${ nodeId } -tools_metadata` ) ?. cancel ( ) ;
107123 }
@@ -119,9 +135,8 @@ export const mutateTemplate = async (
119135 isRefresh ,
120136 ) ;
121137
122- // Tool Mode is a discrete toggle, so delaying it like a text input leaves
123- // the node in its previous output shape and gives slower refresh responses
124- // a chance to repaint the toggle with stale state.
138+ // Debouncing a discrete toggle like a text input lets slower refresh responses
139+ // repaint it with stale state, so Tool Mode is flushed immediately.
125140 if ( parameterName === "tool_mode" ) {
126141 await debouncedFunction ?. flush ( ) ;
127142 }
0 commit comments