diff --git a/app/w/[id]/components/workflow-loop/components/loop-input/loop-input.tsx b/app/w/[id]/components/workflow-loop/components/loop-input/loop-input.tsx index 721ec37d2b..4e72f38ae2 100644 --- a/app/w/[id]/components/workflow-loop/components/loop-input/loop-input.tsx +++ b/app/w/[id]/components/workflow-loop/components/loop-input/loop-input.tsx @@ -13,34 +13,61 @@ export function LoopInput({ id }: NodeProps) { // Get the max iterations from the store for this loop const maxIterations = useWorkflowStore((state) => state.loops[loopId]?.maxIterations ?? 5) + const minIterations = useWorkflowStore((state) => state.loops[loopId]?.minIterations ?? 0) const updateLoopMaxIterations = useWorkflowStore((state) => state.updateLoopMaxIterations) + const updateLoopMinIterations = useWorkflowStore((state) => state.updateLoopMinIterations) - // Local state for input value - const [inputValue, setInputValue] = useState(maxIterations.toString()) + // Local state for input values + const [maxInputValue, setMaxInputValue] = useState(maxIterations.toString()) + const [minInputValue, setMinInputValue] = useState(minIterations.toString()) const [open, setOpen] = useState(false) - const handleChange = (e: React.ChangeEvent) => { + const handleMaxChange = (e: React.ChangeEvent) => { const sanitizedValue = e.target.value.replace(/[^0-9]/g, '') const numValue = parseInt(sanitizedValue) // Only update if it's a valid number and <= 50 if (!isNaN(numValue)) { - setInputValue(Math.min(50, numValue).toString()) + setMaxInputValue(Math.min(50, numValue).toString()) } else { - setInputValue(sanitizedValue) + setMaxInputValue(sanitizedValue) + } + } + + const handleMinChange = (e: React.ChangeEvent) => { + const sanitizedValue = e.target.value.replace(/[^0-9]/g, '') + const numValue = parseInt(sanitizedValue) + + // Only update if it's a valid number and <= max + if (!isNaN(numValue)) { + setMinInputValue(Math.min(parseInt(maxInputValue) || 50, numValue).toString()) + } else { + setMinInputValue(sanitizedValue) } } const handleSave = () => { - const value = parseInt(inputValue) - if (!isNaN(value)) { - const newValue = Math.min(50, value) - updateLoopMaxIterations(loopId, newValue) + const maxValue = parseInt(maxInputValue) + const minValue = parseInt(minInputValue) + + if (!isNaN(maxValue)) { + const newMaxValue = Math.min(50, Math.max(minValue, maxValue)) + updateLoopMaxIterations(loopId, newMaxValue) // Sync input with store value - setInputValue(newValue.toString()) + setMaxInputValue(newMaxValue.toString()) } else { // Reset to current store value if invalid - setInputValue(maxIterations.toString()) + setMaxInputValue(maxIterations.toString()) + } + + if (!isNaN(minValue)) { + const newMinValue = Math.min(maxValue, Math.max(0, minValue)) + updateLoopMinIterations(loopId, newMinValue) + // Sync input with store value + setMinInputValue(newMinValue.toString()) + } else { + // Reset to current store value if invalid + setMinInputValue(minIterations.toString()) } } @@ -63,24 +90,41 @@ export function LoopInput({ id }: NodeProps) { 'flex items-center gap-1' )} > - Max Iterations: {maxIterations} + Iterations: {minIterations}-{maxIterations} e.stopPropagation()}>
-
Max Iterations
+
Min Iterations
-
Enter a number between 1 and 50
+
+ Enter a number between 0 and {maxInputValue} +
+ +
Max Iterations
+
+ +
+
+ Enter a number between {minInputValue || 1} and 50 +
diff --git a/executor/loops.ts b/executor/loops.ts index 7dfd098b15..2b28266f44 100644 --- a/executor/loops.ts +++ b/executor/loops.ts @@ -7,7 +7,8 @@ import { ExecutionContext } from './types' export class LoopManager { constructor( private loops: Record, - private defaultMaxIterations: number = 5 + private defaultMaxIterations: number = 5, + private defaultMinIterations: number = 0 ) {} /** @@ -34,8 +35,12 @@ export class LoopManager { continue } + // Check if we need to force iteration due to minimum iterations requirement + const minIterations = loop.minIterations || this.defaultMinIterations + const forceIteration = currentIteration < minIterations + // Check if loop should iterate again - const shouldIterate = this.shouldIterateLoop(loopId, context) + const shouldIterate = forceIteration || this.shouldIterateLoop(loopId, context) if (shouldIterate) { // Increment iteration counter @@ -175,4 +180,14 @@ export class LoopManager { getMaxIterations(loopId: string): number { return this.loops[loopId]?.maxIterations || this.defaultMaxIterations } + + /** + * Gets the minimum iterations for a loop. + * + * @param loopId - ID of the loop + * @returns Minimum iterations for the loop + */ + getMinIterations(loopId: string): number { + return this.loops[loopId]?.minIterations || this.defaultMinIterations + } } diff --git a/serializer/types.ts b/serializer/types.ts index 7d2f9c1215..49add0fb71 100644 --- a/serializer/types.ts +++ b/serializer/types.ts @@ -43,4 +43,5 @@ export interface SerializedLoop { id: string nodes: string[] maxIterations: number + minIterations: number } diff --git a/stores/workflows/workflow/store.ts b/stores/workflows/workflow/store.ts index 603361effd..b74dc3e0f0 100644 --- a/stores/workflows/workflow/store.ts +++ b/stores/workflows/workflow/store.ts @@ -191,6 +191,7 @@ export const useWorkflowStore = create()( id: loopId, nodes: path, maxIterations: 5, + minIterations: 0, } processedPaths.add(canonicalPath) } @@ -229,6 +230,7 @@ export const useWorkflowStore = create()( id: loopId, nodes: path, maxIterations: 5, + minIterations: 0, } processedPaths.add(canonicalPath) } @@ -467,6 +469,27 @@ export const useWorkflowStore = create()( workflowSync.sync() }, + updateLoopMinIterations: (loopId: string, minIterations: number) => { + const newState = { + blocks: { ...get().blocks }, + edges: [...get().edges], + loops: { + ...get().loops, + [loopId]: { + ...get().loops[loopId], + minIterations: Math.max( + 0, + Math.min(get().loops[loopId].maxIterations, minIterations) + ), // Clamp between 0 and maxIterations + }, + }, + } + + set(newState) + pushHistory(set, get, newState, 'Update loop min iterations') + get().updateLastSaved() + }, + triggerUpdate: () => { set((state) => ({ ...state, diff --git a/stores/workflows/workflow/types.ts b/stores/workflows/workflow/types.ts index a76207861e..748d68fbf6 100644 --- a/stores/workflows/workflow/types.ts +++ b/stores/workflows/workflow/types.ts @@ -29,6 +29,7 @@ export interface Loop { id: string nodes: string[] maxIterations: number + minIterations: number } export interface WorkflowState { @@ -56,6 +57,7 @@ export interface WorkflowActions { toggleBlockWide: (id: string) => void updateBlockHeight: (id: string, height: number) => void updateLoopMaxIterations: (loopId: string, maxIterations: number) => void + updateLoopMinIterations: (loopId: string, minIterations: number) => void triggerUpdate: () => void setDeploymentStatus: (isDeployed: boolean, deployedAt?: Date) => void }