Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically populate model inputs/outputs if applicable #254

Merged
merged 6 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,12 +362,33 @@ export type ModelConfig = {
embeddingDimension?: number;
};

// Based off of JSONSchema. For more info, see https://json-schema.org/understanding-json-schema/reference/type
export type ModelInput = {
type: string;
description?: string;
};

export type ModelOutput = ModelInput;

// For rendering options, we extract the name (the key in the input/output obj) and combine into a single obj
export type ModelInputFormField = ModelInput & {
label: string;
};

export type ModelOutputFormField = ModelInputFormField;

export type ModelInterface = {
input: { [key: string]: ModelInput };
output: { [key: string]: ModelOutput };
};

export type Model = {
id: string;
name: string;
algorithm: MODEL_ALGORITHM;
state: MODEL_STATE;
modelConfig?: ModelConfig;
interface?: ModelInterface;
};

export type ModelDict = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function ConfigFieldList(props: ConfigFieldListProps) {
// Default to ID if no optional formatted / prettified label provided
label={field.label || field.id}
fieldPath={`${props.baseConfigPath}.${configId}.${field.id}`}
showError={true}
onFormChange={props.onFormChange}
/>
<EuiSpacer size={CONFIG_FIELD_SPACER_SIZE} />
Expand All @@ -56,19 +57,6 @@ export function ConfigFieldList(props: ConfigFieldListProps) {
);
break;
}
case 'model': {
el = (
<EuiFlexItem key={idx}>
<ModelField
field={field}
fieldPath={`${props.baseConfigPath}.${configId}.${field.id}`}
onFormChange={props.onFormChange}
/>
<EuiSpacer size={CONFIG_FIELD_SPACER_SIZE} />
</EuiFlexItem>
);
break;
}
}
return el;
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ interface MapArrayFieldProps {
onFormChange: () => void;
onMapAdd?: (curArray: MapArrayFormValue) => void;
onMapDelete?: (idxToDelete: number) => void;
keyOptions?: any[];
valueOptions?: any[];
}

/**
Expand Down Expand Up @@ -122,6 +124,8 @@ export function MapArrayField(props: MapArrayFieldProps) {
keyPlaceholder={props.keyPlaceholder}
valuePlaceholder={props.valuePlaceholder}
onFormChange={props.onFormChange}
keyOptions={props.keyOptions}
valueOptions={props.valueOptions}
/>
</EuiPanel>
</EuiAccordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiFormControlLayoutDelimited,
EuiFormRow,
EuiIcon,
EuiLink,
EuiText,
} from '@elastic/eui';
import { Field, FieldProps, getIn, useFormikContext } from 'formik';
import { isEmpty } from 'lodash';
import {
MapEntry,
MapFormValue,
WorkflowFormValues,
} from '../../../../../common';
import { SelectWithCustomOptions } from './select_with_custom_options';
import { TextField } from './text_field';

interface MapFieldProps {
fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField')
Expand All @@ -29,10 +32,14 @@ interface MapFieldProps {
keyPlaceholder?: string;
valuePlaceholder?: string;
onFormChange: () => void;
keyOptions?: any[];
valueOptions?: any[];
}

/**
* Input component for configuring field mappings
* Input component for configuring field mappings. Input forms are defaulted to text fields. If
* keyOptions or valueOptions are set, set the respective input form as a select field, with those options.
* Allow custom options as a backup/default to ensure flexibility.
*/
export function MapField(props: MapFieldProps) {
const { setFieldValue, setFieldTouched, errors, touched } = useFormikContext<
Expand Down Expand Up @@ -96,47 +103,56 @@ export function MapField(props: MapFieldProps) {
<EuiFlexItem key={idx}>
<EuiFlexGroup direction="row">
<EuiFlexItem grow={true}>
<EuiFormControlLayoutDelimited
fullWidth={true}
startControl={
<input
type="string"
placeholder={props.keyPlaceholder || 'Input'}
className="euiFieldText"
value={mapping.key}
onChange={(e) => {
form.setFieldTouched(
`${props.fieldPath}.${idx}.key`,
true
);
form.setFieldValue(
`${props.fieldPath}.${idx}.key`,
e.target.value
);
props.onFormChange();
}}
/>
}
endControl={
<input
type="string"
placeholder={props.valuePlaceholder || 'Output'}
className="euiFieldText"
value={mapping.value}
onChange={(e) => {
form.setFieldTouched(
`${props.fieldPath}.${idx}.value`,
true
);
form.setFieldValue(
`${props.fieldPath}.${idx}.value`,
e.target.value
);
props.onFormChange();
}}
/>
}
/>
<EuiFlexGroup direction="row" gutterSize="xs">
<EuiFlexItem>
<>
{!isEmpty(props.keyOptions) ? (
<SelectWithCustomOptions
fieldPath={`${props.fieldPath}.${idx}.key`}
options={props.keyOptions as any[]}
placeholder={props.keyPlaceholder || 'Input'}
onFormChange={props.onFormChange}
/>
) : (
<TextField
fieldPath={`${props.fieldPath}.${idx}.key`}
placeholder={props.keyPlaceholder || 'Input'}
showError={false}
onFormChange={props.onFormChange}
/>
)}
</>
</EuiFlexItem>
<EuiFlexItem
grow={false}
style={{ marginTop: '14px' }}
>
<EuiIcon type="sortRight" />
</EuiFlexItem>
<EuiFlexItem>
<>
{!isEmpty(props.valueOptions) ? (
<SelectWithCustomOptions
fieldPath={`${props.fieldPath}.${idx}.value`}
options={props.valueOptions || []}
placeholder={
props.valuePlaceholder || 'Output'
}
onFormChange={props.onFormChange}
/>
) : (
<TextField
fieldPath={`${props.fieldPath}.${idx}.value`}
placeholder={
props.valuePlaceholder || 'Output'
}
showError={false}
onFormChange={props.onFormChange}
/>
)}
</>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Field, FieldProps, getIn, useFormikContext } from 'formik';
import {
EuiCallOut,
EuiFormRow,
EuiLink,
EuiSpacer,
EuiSuperSelect,
EuiSuperSelectOption,
EuiText,
Expand All @@ -25,11 +27,14 @@ import { AppState } from '../../../../store';
interface ModelFieldProps {
field: IConfigField;
fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField')
hasModelInterface: boolean;
onModelChange: (modelId: string) => void;
onFormChange: () => void;
}

type ModelItem = ModelFormValue & {
name: string;
interface?: {};
};

/**
Expand All @@ -56,6 +61,7 @@ export function ModelField(props: ModelFieldProps) {
id: modelId,
name: models[modelId].name,
algorithm: models[modelId].algorithm,
interface: models[modelId].interface,
} as ModelItem);
}
});
Expand All @@ -64,61 +70,75 @@ export function ModelField(props: ModelFieldProps) {
}, [models]);

return (
<Field name={props.fieldPath}>
{({ field, form }: FieldProps) => {
return (
<EuiFormRow
label={'Model'}
labelAppend={
<EuiText size="xs">
<EuiLink href={ML_CHOOSE_MODEL_LINK} target="_blank">
Learn more
</EuiLink>
</EuiText>
}
helpText={'The model ID.'}
>
<EuiSuperSelect
options={deployedModels.map(
(option) =>
({
value: option.id,
inputDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
</>
),
dropdownDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
<EuiText size="xs" color="subdued">
Deployed
</EuiText>
<EuiText size="xs" color="subdued">
{option.algorithm}
</EuiText>
</>
),
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={field.value?.id || ''}
onChange={(option: string) => {
form.setFieldTouched(props.fieldPath, true);
form.setFieldValue(props.fieldPath, {
id: option,
} as ModelFormValue);
props.onFormChange();
}}
isInvalid={
getIn(errors, field.name) && getIn(touched, field.name)
? true
: undefined
<>
{!props.hasModelInterface && (
<>
<EuiCallOut
size="s"
title="The selected model does not have a model interface. Cannot automatically determine model inputs and outputs."
iconType={'alert'}
color="warning"
/>
<EuiSpacer size="s" />
</>
)}
<Field name={props.fieldPath}>
{({ field, form }: FieldProps) => {
return (
<EuiFormRow
label={'Model'}
labelAppend={
<EuiText size="xs">
<EuiLink href={ML_CHOOSE_MODEL_LINK} target="_blank">
Learn more
</EuiLink>
</EuiText>
}
/>
</EuiFormRow>
);
}}
</Field>
helpText={'The model ID.'}
>
<EuiSuperSelect
options={deployedModels.map(
(option) =>
({
value: option.id,
inputDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
</>
),
dropdownDisplay: (
<>
<EuiText size="s">{option.name}</EuiText>
<EuiText size="xs" color="subdued">
Deployed
</EuiText>
<EuiText size="xs" color="subdued">
{option.algorithm}
</EuiText>
</>
),
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={field.value?.id || ''}
onChange={(option: string) => {
form.setFieldTouched(props.fieldPath, true);
form.setFieldValue(props.fieldPath, {
id: option,
} as ModelFormValue);
props.onFormChange();
props.onModelChange(option);
}}
isInvalid={
getIn(errors, field.name) && getIn(touched, field.name)
? true
: undefined
}
/>
</EuiFormRow>
);
}}
</Field>
</>
);
}
Loading
Loading