diff --git a/client/src/components/Workflow/Editor/Forms/FormDefault.vue b/client/src/components/Workflow/Editor/Forms/FormDefault.vue index d69056932e0..205f9687206 100644 --- a/client/src/components/Workflow/Editor/Forms/FormDefault.vue +++ b/client/src/components/Workflow/Editor/Forms/FormDefault.vue @@ -43,8 +43,12 @@ v-if="isSubworkflow" :step="step" @onUpdateStep="(id, step) => emit('onUpdateStep', id, step)" /> + +import { toRef, watch } from "vue"; + +import type { Step } from "@/stores/workflowStepStore"; + +import { useToolState } from "../composables/useToolState"; + +import FormElement from "@/components/Form/FormElement.vue"; + +interface ToolState { + mode: string; + num_inputs: number; +} + +const props = defineProps<{ + step: Step; +}>(); + +const stepRef = toRef(props, "step"); +const { toolState } = useToolState(stepRef); + +function asToolState(ts: unknown): ToolState { + const raw = ts as Record; + return { + mode: (raw.mode as string) ?? "first_non_null", + num_inputs: (raw.num_inputs as number) ?? 2, + }; +} + +function cleanToolState(): ToolState { + if (toolState.value) { + return asToolState({ ...toolState.value }); + } + return { mode: "first_non_null", num_inputs: 2 }; +} + +const emit = defineEmits(["onChange"]); + +const modeOptions = [ + { value: "first_non_null", label: "First non-null (error if all null)" }, + { value: "first_or_skip", label: "First non-null (skip if all null)" }, + { value: "the_only_non_null", label: "The only non-null (error if != 1)" }, + { value: "all_non_null", label: "All non-null (as collection)" }, +]; + +function onMode(newMode: string) { + const state = cleanToolState(); + state.mode = newMode; + emit("onChange", state); +} + +// Grow-on-connect: watch step connections, add terminal when last empty one gets connected +watch( + () => props.step.input_connections, + (connections) => { + const state = cleanToolState(); + const lastTerminalName = `input_${state.num_inputs}`; + if (connections && connections[lastTerminalName]) { + state.num_inputs = state.num_inputs + 1; + emit("onChange", state); + } + }, + { deep: true } +); + +// Dummy initial emit (same pattern as FormInputCollection — resets initialChange guard) +emit("onChange", cleanToolState()); + + + diff --git a/client/src/components/Workflow/Editor/modules/inputs.ts b/client/src/components/Workflow/Editor/modules/inputs.ts index 4e299843d8a..fb4952d5737 100644 --- a/client/src/components/Workflow/Editor/modules/inputs.ts +++ b/client/src/components/Workflow/Editor/modules/inputs.ts @@ -1,5 +1,5 @@ import { faFile, faFolder } from "@fortawesome/free-regular-svg-icons"; -import { faPencilAlt } from "@fortawesome/free-solid-svg-icons"; +import { faCodeBranch, faPencilAlt } from "@fortawesome/free-solid-svg-icons"; import type { IconDefinition } from "font-awesome-6"; export interface WorkflowInput { @@ -96,5 +96,11 @@ export function getWorkflowInputs(): WorkflowInput[] { parameter_type: "directory_uri", }, }, + { + moduleId: "pick_value", + title: "Pick Value", + description: "Select among conditional branch outputs", + icon: faCodeBranch, + }, ]; } diff --git a/client/src/components/Workflow/Editor/modules/itemIcons.ts b/client/src/components/Workflow/Editor/modules/itemIcons.ts index d21068ba8ab..24ba1dffad8 100644 --- a/client/src/components/Workflow/Editor/modules/itemIcons.ts +++ b/client/src/components/Workflow/Editor/modules/itemIcons.ts @@ -1,6 +1,7 @@ import { faArrowRightFromBracket, faArrowRightToBracket, + faCodeBranch, faComment, faFile, faFolderOpen, @@ -22,6 +23,7 @@ export const iconForType = { parameter_input: faPen, subworkflow: faSitemap, pause: faPause, + pick_value: faCodeBranch, }, input: faArrowRightToBracket, output: faArrowRightFromBracket, diff --git a/client/src/components/Workflow/icons.js b/client/src/components/Workflow/icons.js index 02a5fa35476..02dd15d10c9 100644 --- a/client/src/components/Workflow/icons.js +++ b/client/src/components/Workflow/icons.js @@ -5,4 +5,5 @@ export default { subworkflow: "fa-sitemap fa-rotate-270", parameter_input: "fa-pencil-alt", // fa-pencil for older FontAwesome, fa-pencil-alt for newer pause: "fa-pause", + pick_value: "fa-code-branch", }; diff --git a/client/src/components/WorkflowInvocationState/WorkflowStepIcon.vue b/client/src/components/WorkflowInvocationState/WorkflowStepIcon.vue index 7b103f3d27c..1b7b5a0a5b0 100644 --- a/client/src/components/WorkflowInvocationState/WorkflowStepIcon.vue +++ b/client/src/components/WorkflowInvocationState/WorkflowStepIcon.vue @@ -5,7 +5,7 @@ import { computed } from "vue"; import WorkflowIcons from "@/components/Workflow/icons"; interface WorkflowStepIconProps { - stepType: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause"; + stepType: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause" | "pick_value"; } const props = defineProps(); diff --git a/client/src/stores/workflowNodeInspectorStore.ts b/client/src/stores/workflowNodeInspectorStore.ts index e92683ac853..b1ad8b6dda5 100644 --- a/client/src/stores/workflowNodeInspectorStore.ts +++ b/client/src/stores/workflowNodeInspectorStore.ts @@ -21,6 +21,7 @@ function getContentId(step: Step) { data_input: () => "input", parameter_input: () => "input", pause: () => "pause", + pick_value: () => "pick_value", }); } diff --git a/client/src/stores/workflowStepStore.ts b/client/src/stores/workflowStepStore.ts index 94c5e287bbc..a5de55101ab 100644 --- a/client/src/stores/workflowStepStore.ts +++ b/client/src/stores/workflowStepStore.ts @@ -113,7 +113,7 @@ export interface NewStep { tool_state: Record; tool_version?: string; tooltip?: string | null; - type: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause"; + type: "tool" | "data_input" | "data_collection_input" | "subworkflow" | "parameter_input" | "pause" | "pick_value"; uuid?: string; when?: string | null; workflow_id?: string;