diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/map_array_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/map_array_field.tsx index 196f0077..6e935f6f 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/map_array_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/map_array_field.tsx @@ -37,6 +37,7 @@ interface MapArrayFieldProps { valueOptions?: { label: string }[]; addMapEntryButtonText?: string; addMapButtonText?: string; + mappingDirection?: 'sortRight' | 'sortLeft' | undefined; } /** @@ -130,6 +131,7 @@ export function MapArrayField(props: MapArrayFieldProps) { keyOptions={props.keyOptions} valueOptions={props.valueOptions} addEntryButtonText={props.addMapEntryButtonText} + mappingDirection={props.mappingDirection} /> @@ -150,6 +152,7 @@ export function MapArrayField(props: MapArrayFieldProps) { keyOptions={props.keyOptions} valueOptions={props.valueOptions} addEntryButtonText={props.addMapEntryButtonText} + mappingDirection={props.mappingDirection} /> @@ -172,14 +175,20 @@ export function MapArrayField(props: MapArrayFieldProps) { {'Configure'} ) : ( - { - addMap(field.value); - }} - > - {props.addMapButtonText || `(Advanced) Add another map`} - + + + { + addMap(field.value); + }} + > + {props.addMapButtonText || + `Add another map (Advanced)`} + + + )} diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/map_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/map_field.tsx index be8fd2a4..fbc545ea 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/map_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/map_field.tsx @@ -12,8 +12,8 @@ import { EuiIcon, EuiLink, EuiText, - EuiSmallButton, EuiIconTip, + EuiSmallButtonEmpty, } from '@elastic/eui'; import { Field, FieldProps, getIn, useFormikContext } from 'formik'; import { isEmpty } from 'lodash'; @@ -37,6 +37,7 @@ interface MapFieldProps { keyOptions?: { label: string }[]; valueOptions?: { label: string }[]; addEntryButtonText?: string; + mappingDirection?: 'sortRight' | 'sortLeft' | undefined; } // The keys will be more static in general. Give more space for values where users @@ -50,9 +51,13 @@ const VALUE_FLEX_RATIO = 6; * Allow custom options as a backup/default to ensure flexibility. */ export function MapField(props: MapFieldProps) { - const { setFieldValue, setFieldTouched, errors, touched } = useFormikContext< - WorkflowFormValues - >(); + const { + setFieldValue, + setFieldTouched, + errors, + touched, + values, + } = useFormikContext(); // Adding a map entry to the end of the existing arr function addMapEntry(curEntries: MapFormValue): void { @@ -139,7 +144,28 @@ export function MapField(props: MapFieldProps) { <> <> - {!isEmpty(props.keyOptions) ? ( + {/** + * We determine if there is an interface based on if there are key options or not, + * as the options would be derived from the underlying interface. + * And if so, these values should be static. + * So, we only display the static text with no mechanism to change it's value. + * Note we still allow more entries, if a user wants to override / add custom + * keys if there is some gaps in the model interface. + */} + {!isEmpty(props.keyOptions) && + !isEmpty( + getIn(values, `${props.fieldPath}.${idx}.key`) + ) ? ( + + {getIn( + values, + `${props.fieldPath}.${idx}.key` + )} + + ) : !isEmpty(props.keyOptions) ? ( - + @@ -212,13 +240,16 @@ export function MapField(props: MapFieldProps) { })}
- { addMapEntry(field.value); }} > {props.addEntryButtonText || 'Add more'} - +
diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx index 20a6c936..70e78cc0 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx @@ -29,6 +29,9 @@ import { ModelInterface, IndexMappings, PROMPT_FIELD, + MapArrayFormValue, + MapEntry, + MapFormValue, } from '../../../../../common'; import { MapArrayField, ModelField } from '../input_fields'; import { @@ -132,9 +135,27 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { // 1: update model interface states // 2. clear out any persisted input_map/output_map form values, as those would now be invalid function onModelChange(modelId: string) { - setModelInterface(models[modelId]?.interface); - setFieldValue(inputMapFieldPath, []); - setFieldValue(outputMapFieldPath, []); + const newModelInterface = models[modelId]?.interface; + setModelInterface(newModelInterface); + const modelInputsAsForm = [ + parseModelInputs(newModelInterface).map((modelInput) => { + return { + key: modelInput.label, + value: '', + } as MapEntry; + }) as MapFormValue, + ] as MapArrayFormValue; + const modelOutputsAsForm = [ + parseModelOutputs(newModelInterface).map((modelOutput) => { + return { + key: modelOutput.label, + value: '', + } as MapEntry; + }) as MapFormValue, + ] as MapArrayFormValue; + + setFieldValue(inputMapFieldPath, modelInputsAsForm); + setFieldValue(outputMapFieldPath, modelOutputsAsForm); setFieldTouched(inputMapFieldPath, false); setFieldTouched(outputMapFieldPath, false); } @@ -380,7 +401,8 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { : indexMappingFields } addMapEntryButtonText="Add input" - addMapButtonText="(Advanced) Add input group" + addMapButtonText="Add input group (Advanced)" + mappingDirection="sortLeft" /> @@ -415,25 +437,26 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { fieldPath={outputMapFieldPath} helpText={`An array specifying how to map the model’s output to new document fields. Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the root object selector "${JSONPATH_ROOT_SELECTOR}"`} - keyTitle={ + keyTitle="Name" + keyPlaceholder="Name" + keyOptions={ + fullResponsePath + ? undefined + : parseModelOutputs(modelInterface, false) + } + valueTitle={ props.context === PROCESSOR_CONTEXT.SEARCH_REQUEST ? 'Query field' : 'New document field' } - keyPlaceholder={ + valuePlaceholder={ props.context === PROCESSOR_CONTEXT.SEARCH_REQUEST ? 'Specify a query field' : 'Define a document field' } - valueTitle="Name" - valuePlaceholder="Name" - valueOptions={ - fullResponsePath - ? undefined - : parseModelOutputs(modelInterface, false) - } addMapEntryButtonText="Add output" - addMapButtonText="(Advanced) Add output group" + addMapButtonText="Add output group (Advanced)" + mappingDirection="sortRight" /> {inputMapValue.length !== outputMapValue.length && diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx index 53d1ed47..01342c89 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx @@ -322,7 +322,8 @@ export function InputTransformModal(props: InputTransformModalProps) { } }} addMapEntryButtonText="Add input" - addMapButtonText="(Advanced) Add input group" + addMapButtonText="Add input group (Advanced)" + mappingDirection="sortLeft" /> ); diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx index 50b5d37c..a7afe605 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx @@ -215,23 +215,23 @@ export function OutputTransformModal(props: OutputTransformModalProps) { fieldPath={'output_map'} helpText={`An array specifying how to map the model’s output to new fields. Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the root object selector "${JSONPATH_ROOT_SELECTOR}"`} - keyTitle={ + keyTitle="Name" + keyPlaceholder="Name" + keyOptions={ + tempFullResponsePath + ? undefined + : parseModelOutputs(props.modelInterface, false) + } + valueTitle={ props.context === PROCESSOR_CONTEXT.SEARCH_REQUEST ? 'Query field' : 'New document field' } - keyPlaceholder={ + valuePlaceholder={ props.context === PROCESSOR_CONTEXT.SEARCH_REQUEST ? 'Specify a query field' : 'Define a document field' } - valueTitle="Name" - valuePlaceholder="Name" - valueOptions={ - tempFullResponsePath - ? undefined - : parseModelOutputs(props.modelInterface, false) - } // If the map we are adding is the first one, populate the selected option to index 0 onMapAdd={(curArray) => { if (isEmpty(curArray)) { @@ -247,7 +247,8 @@ root object selector "${JSONPATH_ROOT_SELECTOR}"`} } }} addMapEntryButtonText="Add output" - addMapButtonText="(Advanced) Add output group" + addMapButtonText="Add output group (Advanced)" + mappingDirection="sortRight" /> ); diff --git a/public/pages/workflow_detail/workflow_inputs/processors_list.tsx b/public/pages/workflow_detail/workflow_inputs/processors_list.tsx index 89ea1b17..ec774211 100644 --- a/public/pages/workflow_detail/workflow_inputs/processors_list.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processors_list.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; import { - EuiSmallButton, + EuiSmallButtonEmpty, EuiSmallButtonIcon, EuiContextMenu, EuiFlexGroup, @@ -140,172 +140,184 @@ export function ProcessorsList(props: ProcessorsListProps) { } return ( - + {processors.map((processor: IProcessorConfig, processorIndex) => { return ( - { - deleteProcessor(processor.id); - }} - /> - } - > - - - - - + + { + deleteProcessor(processor.id); + }} + /> + } + > + + + + + + ); })} - -
- { - setPopover(!isPopoverOpen); - }} - data-testid="addProcessorButton" - > - {processors.length > 0 - ? 'Add another processor' - : 'Add processor'} - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - panelPaddingSize="none" - anchorPosition="downLeft" + + + + - { - closePopover(); - addProcessor(new MLIngestProcessor().toObj()); - }, - }, - { - name: 'Split Processor', - onClick: () => { - closePopover(); - addProcessor(new SplitIngestProcessor().toObj()); - }, - }, - { - name: 'Sort Processor', - onClick: () => { - closePopover(); - addProcessor(new SortIngestProcessor().toObj()); - }, - }, - { - name: 'Text Chunking Processor', - onClick: () => { - closePopover(); - addProcessor( - new TextChunkingIngestProcessor().toObj() - ); - }, - }, - ] - : props.context === PROCESSOR_CONTEXT.SEARCH_REQUEST - ? [ - { - name: 'ML Inference Processor', - onClick: () => { - closePopover(); - addProcessor( - new MLSearchRequestProcessor().toObj() - ); - }, - }, - ] - : [ - { - name: 'ML Inference Processor', - onClick: () => { - closePopover(); - addProcessor( - new MLSearchResponseProcessor().toObj() - ); - }, - }, - { - name: 'Split Processor', - onClick: () => { - closePopover(); - addProcessor( - new SplitSearchResponseProcessor().toObj() - ); - }, - }, - { - name: 'Sort Processor', - onClick: () => { - closePopover(); - addProcessor( - new SortSearchResponseProcessor().toObj() - ); - }, - }, - { - name: 'Normalization Processor', - onClick: () => { - closePopover(); - addProcessor( - new NormalizationProcessor().toObj() - ); - }, - }, - { - name: 'Collapse Processor', - onClick: () => { - closePopover(); - addProcessor(new CollapseProcessor().toObj()); - }, - }, - ], - }, - ]} - /> - -
+ + { + setPopover(!isPopoverOpen); + }} + data-testid="addProcessorButton" + > + {`Add processor`} + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + { + closePopover(); + addProcessor(new MLIngestProcessor().toObj()); + }, + }, + { + name: 'Split Processor', + onClick: () => { + closePopover(); + addProcessor( + new SplitIngestProcessor().toObj() + ); + }, + }, + { + name: 'Sort Processor', + onClick: () => { + closePopover(); + addProcessor( + new SortIngestProcessor().toObj() + ); + }, + }, + { + name: 'Text Chunking Processor', + onClick: () => { + closePopover(); + addProcessor( + new TextChunkingIngestProcessor().toObj() + ); + }, + }, + ] + : props.context === PROCESSOR_CONTEXT.SEARCH_REQUEST + ? [ + { + name: 'ML Inference Processor', + onClick: () => { + closePopover(); + addProcessor( + new MLSearchRequestProcessor().toObj() + ); + }, + }, + ] + : [ + { + name: 'ML Inference Processor', + onClick: () => { + closePopover(); + addProcessor( + new MLSearchResponseProcessor().toObj() + ); + }, + }, + { + name: 'Split Processor', + onClick: () => { + closePopover(); + addProcessor( + new SplitSearchResponseProcessor().toObj() + ); + }, + }, + { + name: 'Sort Processor', + onClick: () => { + closePopover(); + addProcessor( + new SortSearchResponseProcessor().toObj() + ); + }, + }, + { + name: 'Normalization Processor', + onClick: () => { + closePopover(); + addProcessor( + new NormalizationProcessor().toObj() + ); + }, + }, + { + name: 'Collapse Processor', + onClick: () => { + closePopover(); + addProcessor(new CollapseProcessor().toObj()); + }, + }, + ], + }, + ]} + /> + + +
+
); diff --git a/public/utils/config_to_template_utils.ts b/public/utils/config_to_template_utils.ts index e0924461..09873a26 100644 --- a/public/utils/config_to_template_utils.ts +++ b/public/utils/config_to_template_utils.ts @@ -189,7 +189,8 @@ export function processorConfigsToTemplateProcessors( if (output_map?.length > 0) { processor.ml_inference.output_map = output_map.map( - (mapFormValue: MapFormValue) => mergeMapIntoSingleObj(mapFormValue) + (mapFormValue: MapFormValue) => + mergeMapIntoSingleObj(mapFormValue, true) // we reverse the form inputs for the output map, so reverse back when converting back to the underlying template configuration ); } @@ -420,13 +421,21 @@ export function reduceToTemplate(workflow: Workflow): WorkflowTemplate { // Helper fn to merge the form map (an arr of objs) into a single obj, such that each key // is an obj property, and each value is a property value. Used to format into the // expected inputs for input_maps and output_maps of the ML inference processors. -function mergeMapIntoSingleObj(mapFormValue: MapFormValue): {} { +function mergeMapIntoSingleObj( + mapFormValue: MapFormValue, + reverse: boolean = false +): {} { let curMap = {} as MapEntry; mapFormValue.forEach((mapEntry) => { - curMap = { - ...curMap, - [mapEntry.key]: mapEntry.value, - }; + curMap = reverse + ? { + ...curMap, + [mapEntry.value]: mapEntry.key, + } + : { + ...curMap, + [mapEntry.key]: mapEntry.value, + }; }); return curMap; }