diff --git a/src/components/nodes/ConditionalSwitchNode.tsx b/src/components/nodes/ConditionalSwitchNode.tsx index 6dc7c0b2..0ab9726b 100644 --- a/src/components/nodes/ConditionalSwitchNode.tsx +++ b/src/components/nodes/ConditionalSwitchNode.tsx @@ -4,44 +4,9 @@ import { memo, useMemo, useEffect, useLayoutEffect, useState, useCallback, useRe import { Handle, Position, useUpdateNodeInternals, useReactFlow, NodeProps, useEdges } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { useWorkflowStore } from "@/store/workflowStore"; +import { evaluateRule } from "@/store/utils/ruleEvaluation"; import type { WorkflowNode, ConditionalSwitchNodeData, ConditionalSwitchRule, MatchMode } from "@/types"; -/** - * Evaluates a rule against incoming text with the specified match mode. - * Returns true if any comma-separated value matches using OR logic. - */ -function evaluateRule(text: string | null, ruleValue: string, mode: MatchMode): boolean { - // Guard: no match if text or ruleValue is empty - if (!text || !ruleValue) return false; - - // Normalize text - const normalizedText = text.toLowerCase().trim(); - - // Split and normalize rule values (comma-separated) - const values = ruleValue - .split(',') - .map(v => v.toLowerCase().trim()) - .filter(v => v.length > 0); - - if (values.length === 0) return false; - - // OR logic: match if ANY value matches - return values.some(value => { - switch (mode) { - case "exact": - return normalizedText === value; - case "contains": - return normalizedText.includes(value); - case "starts-with": - return normalizedText.startsWith(value); - case "ends-with": - return normalizedText.endsWith(value); - default: - return false; - } - }); -} - export const ConditionalSwitchNode = memo(({ id, data, selected }: NodeProps) => { const nodeData = data as ConditionalSwitchNodeData; const edges = useEdges(); diff --git a/src/store/utils/ruleEvaluation.ts b/src/store/utils/ruleEvaluation.ts new file mode 100644 index 00000000..ec4d2a95 --- /dev/null +++ b/src/store/utils/ruleEvaluation.ts @@ -0,0 +1,37 @@ +import type { MatchMode } from "@/types"; + +/** + * Evaluates a rule against incoming text with the specified match mode. + * Returns true if any comma-separated value matches using OR logic. + */ +export function evaluateRule(text: string | null, ruleValue: string, mode: MatchMode): boolean { + // Guard: no match if text or ruleValue is empty + if (!text || !ruleValue) return false; + + // Normalize text + const normalizedText = text.toLowerCase().trim(); + + // Split and normalize rule values (comma-separated) + const values = ruleValue + .split(',') + .map(v => v.toLowerCase().trim()) + .filter(v => v.length > 0); + + if (values.length === 0) return false; + + // OR logic: match if ANY value matches + return values.some(value => { + switch (mode) { + case "exact": + return normalizedText === value; + case "contains": + return normalizedText.includes(value); + case "starts-with": + return normalizedText.startsWith(value); + case "ends-with": + return normalizedText.endsWith(value); + default: + return false; + } + }); +} diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index 0cbca79e..6bcf727c 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -23,6 +23,7 @@ import { ProviderSettings, RecentModel, CanvasNavigationSettings, + MatchMode, MODEL_DISPLAY_NAMES, } from "@/types"; import { useToast } from "@/components/Toast"; @@ -59,6 +60,7 @@ import { clearNodeImageRefs, } from "./utils/executionUtils"; import { getConnectedInputsPure, validateWorkflowPure } from "./utils/connectedInputs"; +import { evaluateRule } from "./utils/ruleEvaluation"; import { computeDimmedNodes } from "./utils/dimmingUtils"; import { executeAnnotation, @@ -87,42 +89,6 @@ import type { NodeExecutionContext } from "./execution"; export type { LevelGroup } from "./utils/executionUtils"; export { CONCURRENCY_SETTINGS_KEY } from "./utils/executionUtils"; -/** - * Evaluates a rule against incoming text with the specified match mode. - * Returns true if any comma-separated value matches using OR logic. - */ -function evaluateRuleMatch(text: string | null, ruleValue: string, mode: "exact" | "contains" | "starts-with" | "ends-with"): boolean { - // Guard: no match if text or ruleValue is empty - if (!text || !ruleValue) return false; - - // Normalize text - const normalizedText = text.toLowerCase().trim(); - - // Split and normalize rule values (comma-separated) - const values = ruleValue - .split(',') - .map(v => v.toLowerCase().trim()) - .filter(v => v.length > 0); - - if (values.length === 0) return false; - - // OR logic: match if ANY value matches - return values.some(value => { - switch (mode) { - case "exact": - return normalizedText === value; - case "contains": - return normalizedText.includes(value); - case "starts-with": - return normalizedText.startsWith(value); - case "ends-with": - return normalizedText.endsWith(value); - default: - return false; - } - }); -} - function saveLogSession(): void { const session = logger.getCurrentSession(); if (session) { @@ -1099,7 +1065,7 @@ const workflowStoreImpl: StateCreator = (set, get) => ({ // Evaluate each rule against incoming text const updatedRules = nodeData.rules.map(rule => { - const isMatched = evaluateRuleMatch(incomingText, rule.value, rule.mode as "exact" | "contains" | "starts-with" | "ends-with"); + const isMatched = evaluateRule(incomingText, rule.value, rule.mode as MatchMode); return { ...rule, isMatched }; }); @@ -1445,7 +1411,7 @@ const workflowStoreImpl: StateCreator = (set, get) => ({ // Evaluate each rule against incoming text const updatedRules = nodeData.rules.map(rule => { - const isMatched = evaluateRuleMatch(incomingText, rule.value, rule.mode as "exact" | "contains" | "starts-with" | "ends-with"); + const isMatched = evaluateRule(incomingText, rule.value, rule.mode as MatchMode); return { ...rule, isMatched }; });