diff --git a/common/interfaces.ts b/common/interfaces.ts index aa31c7d2..43fbfa15 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -126,6 +126,10 @@ export type OutputMapEntry = InputMapEntry; export type InputMapFormValue = InputMapEntry[]; export type OutputMapFormValue = OutputMapEntry[]; +export type MapCache = { + [idx: number]: Transform[]; +}; + export type InputMapArrayFormValue = InputMapFormValue[]; export type OutputMapArrayFormValue = OutputMapFormValue[]; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx index 49a2958d..5572a101 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx @@ -31,11 +31,13 @@ import { IndexMappings, InputMapEntry, InputMapFormValue, + Transform, TRANSFORM_TYPE, EMPTY_INPUT_MAP_ENTRY, WorkflowConfig, getCharacterLimitedString, INPUT_TRANSFORM_OPTIONS, + MapCache, } from '../../../../../../common'; import { TextField, @@ -49,6 +51,7 @@ import { sanitizeJSONPath, } from '../../../../../utils'; import { ConfigureExpressionModal, ConfigureTemplateModal } from './modals/'; +import { updateCache } from './utils'; interface ModelInputsProps { config: IProcessorConfig; @@ -98,14 +101,6 @@ export function ModelInputs(props: ModelInputsProps) { ModelInterface | undefined >(undefined); - // various modal states - const [templateModalIdx, setTemplateModalIdx] = useState( - undefined - ); - const [expressionModalIdx, setExpressionModalIdx] = useState< - number | undefined - >(undefined); - // get the model interface based on the selected ID and list of known models useEffect(() => { if (!isEmpty(models)) { @@ -116,6 +111,19 @@ export function ModelInputs(props: ModelInputsProps) { } }, [models, getIn(values, modelFieldPath)?.id]); + // various modal states + const [templateModalIdx, setTemplateModalIdx] = useState( + undefined + ); + const [expressionModalIdx, setExpressionModalIdx] = useState< + number | undefined + >(undefined); + + // Temporarily cache any configured transformations for different transform types. + // For example, if a user configures a prompt, swaps the transform + // type to "Data field", and swaps back to "Prompt", the prompt will be persisted. + const [inputMapCache, setInputMapCache] = useState({}); + // persisting doc/query/index mapping fields to collect a list // of options to display in the dropdowns when configuring input / output maps const [docFields, setDocFields] = useState<{ label: string }[]>([]); @@ -346,6 +354,7 @@ export function ModelInputs(props: ModelInputsProps) { @@ -381,20 +390,32 @@ export function ModelInputs(props: ModelInputsProps) { ) || '' } onChange={(option) => { + // before updating, cache any form values + const updatedCache = updateCache( + inputMapCache, + mapEntry, + idx + ); setFieldValue( `${inputMapFieldPath}.${idx}.value.transformType`, option ); - // If the transform type changes, clear any set value and/or nested vars, - // as it will likely not make sense under other types/contexts. + // Pre-populate with any cached values, if found + const curCacheForOption = updatedCache[ + idx + ]?.find( + (transform: Transform) => + transform.transformType === option + ); setFieldValue( `${inputMapFieldPath}.${idx}.value.value`, - '' + curCacheForOption?.value || '' ); setFieldValue( `${inputMapFieldPath}.${idx}.value.nestedVars`, - [] + curCacheForOption?.nestedVars || [] ); + setInputMapCache(updatedCache); }} /> diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx index e3d443e3..87ce1b76 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx @@ -34,10 +34,13 @@ import { EMPTY_OUTPUT_MAP_ENTRY, ExpressionVar, OUTPUT_TRANSFORM_OPTIONS, + Transform, + MapCache, } from '../../../../../../common'; import { TextField } from '../../input_fields'; import { AppState } from '../../../../../store'; import { ConfigureMultiExpressionModal } from './modals'; +import { updateCache } from './utils'; interface ModelOutputsProps { config: IProcessorConfig; @@ -93,6 +96,11 @@ export function ModelOutputs(props: ModelOutputsProps) { } }, [models, getIn(values, modelFieldPath)?.id]); + // Temporarily cache any configured transformations for different transform types. + // For example, if a user configures a prompt, swaps the transform + // type to "Data field", and swaps back to "Prompt", the prompt will be persisted. + const [outputMapCache, setOutputMapCache] = useState({}); + // Adding a map entry to the end of the existing arr function addMapEntry(curEntries: OutputMapFormValue): void { const updatedEntries = [...curEntries, EMPTY_OUTPUT_MAP_ENTRY]; @@ -211,6 +219,7 @@ export function ModelOutputs(props: ModelOutputsProps) { @@ -246,20 +255,32 @@ export function ModelOutputs(props: ModelOutputsProps) { ) || '' } onChange={(option) => { + // before updating, cache any form values + const updatedCache = updateCache( + outputMapCache, + mapEntry, + idx + ); setFieldValue( `${outputMapFieldPath}.${idx}.value.transformType`, option ); - // If the transform type changes, clear any set value and/or nested vars, - // as it will likely not make sense under other types/contexts. + // Pre-populate with any cached values, if found + const curCacheForOption = updatedCache[ + idx + ]?.find( + (transform: Transform) => + transform.transformType === option + ); setFieldValue( `${outputMapFieldPath}.${idx}.value.value`, - '' + curCacheForOption?.value || '' ); setFieldValue( `${outputMapFieldPath}.${idx}.value.nestedVars`, - [] + curCacheForOption?.nestedVars || [] ); + setOutputMapCache(updatedCache); }} /> diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts new file mode 100644 index 00000000..358ea0d7 --- /dev/null +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { isEmpty } from 'lodash'; +import { + MapCache, + InputMapEntry, + OutputMapEntry, + Transform, +} from '../../../../../../common'; + +// Update a cache of data transform values based on a given form value +export function updateCache( + cache: MapCache, + mapEntry: InputMapEntry | OutputMapEntry, + idx: number // the mapEntry index +): MapCache { + const updatedCache = cache; + const curCache = updatedCache[idx]; + if (curCache === undefined || isEmpty(curCache)) { + // case 1: there is no persisted state for this entry index. create a fresh arr + updatedCache[idx] = [mapEntry.value]; + } else if ( + !curCache.some( + (transform: Transform) => + transform.transformType === mapEntry.value.transformType + ) + ) { + // case 2: there is persisted state for this entry index, but not for the particular + // transform type. append to the arr + updatedCache[idx] = [...updatedCache[idx], mapEntry.value]; + } else { + // case 3: there is persisted state for this entry index, and for the particular transform type. + // Update the cache with the current form value(s) + updatedCache[idx] = updatedCache[idx].map((cachedEntry) => { + if (cachedEntry.transformType === mapEntry.value.transformType) { + return mapEntry.value; + } else { + return cachedEntry; + } + }); + } + return updatedCache; +}