diff --git a/public/component_types/base_interfaces.ts b/public/component_types/base_interfaces.ts index 5f761c42..76127dd1 100644 --- a/public/component_types/base_interfaces.ts +++ b/public/component_types/base_interfaces.ts @@ -9,12 +9,10 @@ import { COMPONENT_CATEGORY } from '../utils'; * ************ Types ************************** */ -// TODO: may change to enums later +// TODO: may change some/all of these to enums later export type BaseClass = string; export type UIFlow = string; - -// will expand later on -export type FieldType = 'string' | 'json'; +export type FieldType = 'string' | 'json' | 'select'; /** * ************ Base interfaces **************** diff --git a/public/component_types/indices/knn_index.ts b/public/component_types/indices/knn_index.ts index 8b239180..70978071 100644 --- a/public/component_types/indices/knn_index.ts +++ b/public/component_types/indices/knn_index.ts @@ -13,6 +13,9 @@ import { BaseClass, } from '../base_interfaces'; +/** + * A k-NN index UI component + */ export class KnnIndex implements IComponent { id: string; type: BaseClass; @@ -25,7 +28,7 @@ export class KnnIndex implements IComponent { baseClasses: BaseClass[]; inputs: IComponentInput[]; fields: IComponentField[]; - inputFields: IComponentField[]; + createFields: IComponentField[]; outputs: IComponentOutput[]; constructor() { @@ -44,15 +47,15 @@ export class KnnIndex implements IComponent { this.inputs = []; this.fields = [ { - label: 'Name', - type: 'string', + label: 'Index Name', + type: 'select', optional: false, advanced: false, }, ]; - this.inputFields = [ + this.createFields = [ { - label: 'Name', + label: 'Index Name', type: 'string', optional: false, advanced: false, @@ -64,6 +67,7 @@ export class KnnIndex implements IComponent { { label: 'Mappings', type: 'json', + placeholder: 'Enter an index mappings JSON blob...', optional: false, advanced: false, }, diff --git a/public/component_types/processors/text_embedding_processor.ts b/public/component_types/processors/text_embedding_processor.ts index 939b0efa..1000bdbe 100644 --- a/public/component_types/processors/text_embedding_processor.ts +++ b/public/component_types/processors/text_embedding_processor.ts @@ -13,6 +13,9 @@ import { BaseClass, } from '../base_interfaces'; +/** + * A text embedding processor UI component + */ export class TextEmbeddingProcessor implements IComponent { id: string; type: BaseClass; diff --git a/public/pages/workflow_builder/components/index.ts b/public/pages/workflow_builder/components/index.ts new file mode 100644 index 00000000..e2edf8bd --- /dev/null +++ b/public/pages/workflow_builder/components/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { TextField } from './text_field'; +export { JsonField } from './json_field'; +export { SelectField } from './select_field'; diff --git a/public/pages/workflow_builder/components/json_field.tsx b/public/pages/workflow_builder/components/json_field.tsx new file mode 100644 index 00000000..1ff51305 --- /dev/null +++ b/public/pages/workflow_builder/components/json_field.tsx @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiText, EuiTextArea } from '@elastic/eui'; + +interface JsonFieldProps { + label: string; + placeholder: string; +} + +/** + * An input field for a component where users select manually enter + * in some custom JSON + */ +export function JsonField(props: JsonFieldProps) { + return ( + <> + + {props.label} + + + + ); +} diff --git a/public/pages/workflow_builder/components/select_field.tsx b/public/pages/workflow_builder/components/select_field.tsx new file mode 100644 index 00000000..eaec1c0f --- /dev/null +++ b/public/pages/workflow_builder/components/select_field.tsx @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { EuiSuperSelect, EuiSuperSelectOption, EuiText } from '@elastic/eui'; + +// TODO: Should be fetched from global state. +// Need to have a way to determine where to fetch this dynamic data. +const existingIndices = [ + { + value: 'index-1', + inputDisplay: my-index-1, + disabled: false, + }, + { + value: 'index-2', + inputDisplay: my-index-2, + disabled: false, + }, +] as Array>; + +/** + * An input field for a component where users select from a list of available + * options. + */ +export function SelectField() { + const options = existingIndices; + + const [selectedOption, setSelectedOption] = useState( + options[0].value + ); + + const onChange = (option: string) => { + setSelectedOption(option); + }; + + return ( + onChange(option)} + /> + ); +} diff --git a/public/pages/workflow_builder/components/text_field.tsx b/public/pages/workflow_builder/components/text_field.tsx new file mode 100644 index 00000000..b22635db --- /dev/null +++ b/public/pages/workflow_builder/components/text_field.tsx @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiFieldText } from '@elastic/eui'; + +interface TextFieldProps { + label: string; + placeholder: string; +} + +/** + * An input field for a component where users input plaintext + */ +export function TextField(props: TextFieldProps) { + return ( + + ); +} diff --git a/public/pages/workflow_builder/workflow_builder.tsx b/public/pages/workflow_builder/workflow_builder.tsx index 22f97225..645f9129 100644 --- a/public/pages/workflow_builder/workflow_builder.tsx +++ b/public/pages/workflow_builder/workflow_builder.tsx @@ -9,7 +9,6 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, - EuiText, EuiSpacer, } from '@elastic/eui'; import { BREADCRUMBS } from '../../utils'; @@ -29,6 +28,7 @@ export function WorkflowBuilder() { ]); }); + // TODO: Should be fetched from global state. Using some defaults for testing purposes const curComponents = [ new TextEmbeddingProcessor(), new KnnIndex(), diff --git a/public/pages/workflow_builder/workflow_component.tsx b/public/pages/workflow_builder/workflow_component.tsx index cdffe13b..11cb5e20 100644 --- a/public/pages/workflow_builder/workflow_component.tsx +++ b/public/pages/workflow_builder/workflow_component.tsx @@ -3,52 +3,112 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiFieldText, EuiText, EuiSpacer, EuiCard, + EuiTab, + EuiTabs, } from '@elastic/eui'; import { IComponent } from '../../component_types'; +import { JsonField, SelectField, TextField } from './components'; + +interface WorkflowComponentProps { + component: IComponent; +} + +const inputTabs = [ + { + id: 'existing', + name: 'Existing', + disabled: false, + }, + { + id: 'new', + name: 'New', + disabled: false, + }, +]; /** - * This will be the ReactFlow node in the drag-and-drop workspace. It will take in a component + * TODO: This will be the ReactFlow node in the drag-and-drop workspace. It will take in a component * from the global workflow state and render it appropriately (inputs / params / outputs / etc.) * Similar to Flowise's CanvasNode - see * https://github.com/FlowiseAI/Flowise/blob/main/packages/ui/src/views/canvas/CanvasNode.js */ +export function WorkflowComponent(props: WorkflowComponentProps) { + const { component } = props; -interface WorkflowComponentProps { - component: IComponent; -} + const [selectedTabId, setSelectedTabId] = useState('existing'); -// TODO: convert this to a ReactFlow node -export const WorkflowComponent = (props: WorkflowComponentProps) => { - const { component } = props; + const onSelectedTabChanged = (id: string) => { + setSelectedTabId(id); + }; + + const isCreatingNew = component.allowsCreation && selectedTabId === 'new'; + const fieldsToDisplay = isCreatingNew + ? component.createFields + : component.fields; return ( - - {component.fields?.map((field, idx) => { + + {component.allowsCreation ? ( + + {inputTabs.map((tab, idx) => { + return ( + onSelectedTabChanged(tab.id)} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + key={idx} + > + {tab.name} + + ); + })} + + ) : undefined} + + + {fieldsToDisplay?.map((field, idx) => { if (field.type === 'string') { return ( - ); + } else if (field.type === 'json') { + return ( + + + + ); + } else if (field.type === 'select') { + return ( + + + + ); } })} - + {/** + * Hardcoding the interfaced inputs/outputs for readability + * TODO: remove when moving this into the context of a ReactFlow node with Handles. + */} + <> Inputs: @@ -68,4 +128,4 @@ export const WorkflowComponent = (props: WorkflowComponentProps) => { ); -}; +}