From ad033802dc4a421e2ceb3cc9481524be6fbe3681 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 9 Jul 2024 13:55:24 -0700 Subject: [PATCH] Simplify `ui_metadata`; various cleanup and refactoring (#206) Signed-off-by: Tyler Ohlsen --- common/constants.ts | 72 +- common/interfaces.ts | 50 +- public/configs/ml_processor.ts | 6 +- .../workflow_detail/prototype/ingestor.tsx | 217 ------ .../workflow_detail/prototype/prototype.tsx | 103 --- .../prototype/query_executor.tsx | 307 --------- .../pages/workflow_detail/prototype/utils.ts | 27 - .../workflow_detail/tools/errors/errors.tsx | 17 + .../{utils => tools/errors}/index.ts | 2 +- .../workflow_detail/tools/ingest/index.ts | 6 + .../workflow_detail/tools/ingest/ingest.tsx | 36 + .../{prototype => tools/query}/index.ts | 2 +- .../workflow_detail/tools/query/query.tsx | 36 + .../tools/resources/resources.tsx | 3 +- public/pages/workflow_detail/tools/tools.tsx | 41 +- .../utils/data_extractor_utils.ts | 124 ---- .../pages/workflow_detail/workflow_detail.tsx | 1 - .../workflow_inputs/config_field_list.tsx | 21 +- .../ingest_inputs/advanced_settings.tsx | 6 +- .../ingest_inputs/ingest_data.tsx | 9 +- .../ingest_inputs/ingest_inputs.tsx | 5 +- .../ingest_inputs/source_data.tsx | 9 +- .../workflow_inputs/input_fields/index.ts | 1 - .../input_fields/json_field.tsx | 14 +- .../input_fields/model_field.tsx | 211 ++---- .../input_fields/select_field.tsx | 85 --- .../input_fields/text_field.tsx | 19 +- .../configure_search_request.tsx | 9 +- public/pages/workflows/new_workflow/utils.ts | 645 ------------------ public/utils/config_to_form_utils.ts | 1 - public/utils/config_to_schema_utils.ts | 1 - public/utils/config_to_workspace_utils.ts | 2 +- 32 files changed, 195 insertions(+), 1893 deletions(-) delete mode 100644 public/pages/workflow_detail/prototype/ingestor.tsx delete mode 100644 public/pages/workflow_detail/prototype/prototype.tsx delete mode 100644 public/pages/workflow_detail/prototype/query_executor.tsx delete mode 100644 public/pages/workflow_detail/prototype/utils.ts create mode 100644 public/pages/workflow_detail/tools/errors/errors.tsx rename public/pages/workflow_detail/{utils => tools/errors}/index.ts (67%) create mode 100644 public/pages/workflow_detail/tools/ingest/index.ts create mode 100644 public/pages/workflow_detail/tools/ingest/ingest.tsx rename public/pages/workflow_detail/{prototype => tools/query}/index.ts (71%) create mode 100644 public/pages/workflow_detail/tools/query/query.tsx delete mode 100644 public/pages/workflow_detail/utils/data_extractor_utils.ts delete mode 100644 public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx diff --git a/common/constants.ts b/common/constants.ts index d398a9e1..ffed2a0f 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -3,13 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - MODEL_ALGORITHM, - PRETRAINED_MODEL_FORMAT, - PretrainedSentenceTransformer, - PretrainedSparseEncodingModel, - WORKFLOW_STATE, -} from './interfaces'; +import { WORKFLOW_STATE } from './interfaces'; export const PLUGIN_ID = 'flow-framework'; @@ -55,70 +49,6 @@ export const GET_PRESET_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH export const BASE_MODEL_NODE_API_PATH = `${BASE_NODE_API_PATH}/model`; export const SEARCH_MODELS_NODE_API_PATH = `${BASE_MODEL_NODE_API_PATH}/search`; -/** - * ML PLUGIN PRETRAINED MODELS - * (based off of https://opensearch.org/docs/latest/ml-commons-plugin/pretrained-models) - */ - -// ---- SENTENCE TRANSFORMERS ---- -export const ROBERTA_SENTENCE_TRANSFORMER = { - name: 'huggingface/sentence-transformers/all-distilroberta-v1', - shortenedName: 'all-distilroberta-v1', - description: 'A sentence transformer from Hugging Face', - format: PRETRAINED_MODEL_FORMAT.TORCH_SCRIPT, - algorithm: MODEL_ALGORITHM.TEXT_EMBEDDING, - version: '1.0.1', - vectorDimensions: 768, -} as PretrainedSentenceTransformer; - -export const MPNET_SENTENCE_TRANSFORMER = { - name: 'huggingface/sentence-transformers/all-mpnet-base-v2', - shortenedName: 'all-mpnet-base-v2', - description: 'A sentence transformer from Hugging Face', - format: PRETRAINED_MODEL_FORMAT.TORCH_SCRIPT, - algorithm: MODEL_ALGORITHM.TEXT_EMBEDDING, - version: '1.0.1', - vectorDimensions: 768, -} as PretrainedSentenceTransformer; - -export const BERT_SENTENCE_TRANSFORMER = { - name: 'huggingface/sentence-transformers/msmarco-distilbert-base-tas-b', - shortenedName: 'msmarco-distilbert-base-tas-b', - description: 'A sentence transformer from Hugging Face', - format: PRETRAINED_MODEL_FORMAT.TORCH_SCRIPT, - algorithm: MODEL_ALGORITHM.TEXT_EMBEDDING, - version: '1.0.2', - vectorDimensions: 768, -} as PretrainedSentenceTransformer; - -// ---- SPARSE ENCODERS ---- -export const NEURAL_SPARSE_TRANSFORMER = { - name: 'amazon/neural-sparse/opensearch-neural-sparse-encoding-v1', - shortenedName: 'opensearch-neural-sparse-encoding-v1', - description: 'A general neural sparse encoding model', - format: PRETRAINED_MODEL_FORMAT.TORCH_SCRIPT, - algorithm: MODEL_ALGORITHM.SPARSE_ENCODING, - version: '1.0.1', -} as PretrainedSparseEncodingModel; - -export const NEURAL_SPARSE_DOC_TRANSFORMER = { - name: 'amazon/neural-sparse/opensearch-neural-sparse-encoding-doc-v1', - shortenedName: 'opensearch-neural-sparse-encoding-doc-v1', - description: 'A general neural sparse encoding model', - format: PRETRAINED_MODEL_FORMAT.TORCH_SCRIPT, - algorithm: MODEL_ALGORITHM.SPARSE_ENCODING, - version: '1.0.1', -} as PretrainedSparseEncodingModel; - -export const NEURAL_SPARSE_TOKENIZER_TRANSFORMER = { - name: 'amazon/neural-sparse/opensearch-neural-sparse-tokenizer-v1', - shortenedName: 'opensearch-neural-sparse-tokenizer-v1', - description: 'A neural sparse tokenizer model', - format: PRETRAINED_MODEL_FORMAT.TORCH_SCRIPT, - algorithm: MODEL_ALGORITHM.SPARSE_ENCODING, - version: '1.0.1', -} as PretrainedSparseEncodingModel; - /** * Various constants pertaining to Workflow configs */ diff --git a/common/interfaces.ts b/common/interfaces.ts index 81c88d7c..5d7744c1 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -19,17 +19,18 @@ TODO: over time these can become less generic as the form inputs & UX becomes fi */ export type ConfigFieldType = 'string' | 'json' | 'select' | 'model' | 'map'; -export type ConfigSelectType = 'model'; export type ConfigFieldValue = string | {}; export interface IConfigField { - label: string; type: ConfigFieldType; id: string; value?: ConfigFieldValue; + // TODO: remove below fields out of this interface and directly into the necessary components. + // This is to minimize what we persist here, which is added into ui_metadata and indexed. + // Once the config for ML inference processors is finalized, we can migrate these out. + label?: string; placeholder?: string; helpText?: string; helpLink?: string; - selectType?: ConfigSelectType; } export interface IConfig { id: string; @@ -91,7 +92,6 @@ export type WorkflowSchema = ObjectSchema; */ export type FieldType = 'string' | 'json' | 'select' | 'model'; -export type SelectType = 'model'; export type FieldValue = string | {}; export type ComponentFormValues = FormikValues; export type WorkspaceFormValues = { @@ -125,7 +125,6 @@ export interface IComponentField { placeholder?: string; helpText?: string; helpLink?: string; - selectType?: SelectType; } /** @@ -164,6 +163,7 @@ type ReactFlowViewport = { export type UIState = { config: WorkflowConfig; type: WORKFLOW_TYPE; + // Will be used in future when changing from form-based to flow-based configs via drag-and-drop workspace_flow?: WorkspaceFlowState; }; @@ -271,16 +271,6 @@ export type CreateIndexNode = TemplateNode & { }; }; -export type RegisterPretrainedModelNode = TemplateNode & { - user_inputs: { - name: string; - description: string; - model_format: string; - version: string; - deploy: boolean; - }; -}; - export type TemplateEdge = { source: string; dest: string; @@ -365,30 +355,6 @@ export enum MODEL_ALGORITHM { AGENT = 'Agent', } -export enum MODEL_CATEGORY { - DEPLOYED = 'Deployed', - PRETRAINED = 'Pretrained', -} - -export enum PRETRAINED_MODEL_FORMAT { - TORCH_SCRIPT = 'TORCH_SCRIPT', -} - -export type PretrainedModel = { - name: string; - shortenedName: string; - description: string; - format: PRETRAINED_MODEL_FORMAT; - algorithm: MODEL_ALGORITHM; - version: string; -}; - -export type PretrainedSentenceTransformer = PretrainedModel & { - vectorDimensions: number; -}; - -export type PretrainedSparseEncodingModel = PretrainedModel & {}; - export type ModelConfig = { modelType?: string; embeddingDimension?: number; @@ -408,7 +374,6 @@ export type ModelDict = { export type ModelFormValue = { id: string; - category?: MODEL_CATEGORY; algorithm?: MODEL_ALGORITHM; }; @@ -450,8 +415,6 @@ export enum WORKFLOW_STEP_TYPE { CREATE_INGEST_PIPELINE_STEP_TYPE = 'create_ingest_pipeline', CREATE_SEARCH_PIPELINE_STEP_TYPE = 'create_search_pipeline', CREATE_INDEX_STEP_TYPE = 'create_index', - REGISTER_LOCAL_PRETRAINED_MODEL_STEP_TYPE = 'register_local_pretrained_model', - REGISTER_LOCAL_SPARSE_ENCODING_MODEL_STEP_TYPE = 'register_local_sparse_encoding_model', } // We cannot disambiguate ingest vs. search pipelines based on workflow resource type. To work around @@ -460,9 +423,6 @@ export enum WORKFLOW_STEP_TO_RESOURCE_TYPE_MAP { 'create_ingest_pipeline' = 'Ingest pipeline', 'create_search_pipeline' = 'Search pipeline', 'create_index' = 'Index', - 'register_local_pretrained_model' = 'Model', - 'register_local_sparse_encoding_model' = 'Model', - 'deploy_model' = 'Model', } export type WorkflowDict = { diff --git a/public/configs/ml_processor.ts b/public/configs/ml_processor.ts index 03bd9ae3..ce797307 100644 --- a/public/configs/ml_processor.ts +++ b/public/configs/ml_processor.ts @@ -17,17 +17,14 @@ export abstract class MLProcessor extends Processor { this.name = 'ML Inference Processor'; this.fields = [ { - label: 'Model', id: 'model', type: 'model', - helpText: 'The model ID.', - helpLink: - 'https://opensearch.org/docs/latest/ml-commons-plugin/integrating-ml-models/#choosing-a-model', }, { label: 'Input Map', id: 'inputMap', type: 'map', + // TODO: move these fields directly into the component once design is finalized helpText: `An array specifying how to map fields from the ingested document to the model’s input.`, helpLink: 'https://opensearch.org/docs/latest/ingest-pipelines/processors/ml-inference/#configuration-parameters', @@ -36,6 +33,7 @@ export abstract class MLProcessor extends Processor { label: 'Output Map', id: 'outputMap', type: 'map', + // TODO: move these fields directly into the component once design is finalized helpText: `An array specifying how to map the model’s output to new fields.`, helpLink: 'https://opensearch.org/docs/latest/ingest-pipelines/processors/ml-inference/#configuration-parameters', diff --git a/public/pages/workflow_detail/prototype/ingestor.tsx b/public/pages/workflow_detail/prototype/ingestor.tsx deleted file mode 100644 index a907a88b..00000000 --- a/public/pages/workflow_detail/prototype/ingestor.tsx +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useState, useEffect } from 'react'; -import { - EuiButton, - EuiCodeEditor, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiText, -} from '@elastic/eui'; -import { USE_CASE, Workflow } from '../../../../common'; -import { - getIndexName, - getNeuralSearchValues, -} from '../utils/data_extractor_utils'; -import { ingest, useAppDispatch } from '../../../store'; -import { getCore } from '../../../services'; -import { - NeuralSparseValues, - SemanticSearchValues, - WorkflowValues, - getFormattedJSONString, -} from './utils'; - -interface IngestorProps { - workflow: Workflow; -} - -type DocGeneratorFn = ( - queryText: string, - workflowValues: SemanticSearchValues | NeuralSparseValues -) => {}; - -/** - * A basic and flexible UI for ingesting some documents against an index. Sets up guardrails to limit - * what is customized in the document, and setting readonly values based on the workflow's use case - * and details. - * - * For example, given a semantic search workflow configured on index A, with model B, input field C, and vector field D, - * the UI will enforce the ingested document to include C, and ingest it against A. - */ -export function Ingestor(props: IngestorProps) { - const dispatch = useAppDispatch(); - // query state - const [workflowValues, setWorkflowValues] = useState(); - const [docGeneratorFn, setDocGeneratorFn] = useState(); - const [indexName, setIndexName] = useState(''); - const [docObj, setDocObj] = useState<{}>({}); - const [formattedDoc, setFormattedDoc] = useState(''); - const [userInput, setUserInput] = useState(''); - - // results state - const [response, setResponse] = useState<{}>({}); - const [formattedResponse, setFormattedResponse] = useState(''); - - // hook to set all of the workflow-related fields based on the use case - useEffect(() => { - setWorkflowValues(getWorkflowValues(props.workflow)); - setDocGeneratorFn(getDocGeneratorFn(props.workflow)); - setIndexName(getIndexName(props.workflow)); - }, [props.workflow]); - - // hook to generate the query once all dependent input vars are available - useEffect(() => { - if (docGeneratorFn && workflowValues) { - setDocObj(docGeneratorFn(userInput, workflowValues)); - } - }, [userInput, docGeneratorFn, workflowValues]); - - // hooks to persist the formatted data. this is so we don't - // re-execute the JSON formatting unless necessary - useEffect(() => { - setFormattedResponse(getFormattedJSONString(response)); - }, [response]); - useEffect(() => { - setFormattedDoc(getFormattedJSONString(docObj)); - }, [docObj]); - - // - function onExecuteIngest() { - dispatch(ingest({ index: indexName, doc: docObj })) - .unwrap() - .then(async (result) => { - setResponse(result); - }) - .catch((error: any) => { - getCore().notifications.toasts.addDanger(error); - setResponse({}); - }); - } - - return ( - - - - - Ingest some sample data to get started. - - - - - { - setUserInput(e.target.value); - }} - /> - - - - Ingest - - - - - - {}} - readOnly={true} - setOptions={{ - fontSize: '14px', - }} - aria-label="Code Editor" - tabSize={2} - /> - - - - - - - - Response - - - - - - - - - - {}} - readOnly={true} - setOptions={{ - fontSize: '14px', - }} - aria-label="Code Editor" - tabSize={2} - /> - - - - - ); -} - -// getting the appropriate doc generator function based on the use case -function getDocGeneratorFn(workflow: Workflow): DocGeneratorFn { - let fn; - switch (workflow.use_case) { - case USE_CASE.SEMANTIC_SEARCH: - case USE_CASE.NEURAL_SPARSE_SEARCH: - case USE_CASE.HYBRID_SEARCH: - default: { - fn = () => generateNeuralSearchDoc; - } - } - return fn; -} - -// getting the appropriate static values from the workflow based on the use case -function getWorkflowValues(workflow: Workflow): WorkflowValues { - let values; - switch (workflow.use_case) { - case USE_CASE.SEMANTIC_SEARCH: - case USE_CASE.NEURAL_SPARSE_SEARCH: - case USE_CASE.HYBRID_SEARCH: - default: { - values = getNeuralSearchValues(workflow); - } - } - return values; -} - -// utility fn to generate a document suited for neural search use cases -function generateNeuralSearchDoc( - docValue: string, - workflowValues: SemanticSearchValues | NeuralSparseValues -): {} { - return { - [workflowValues.inputField]: docValue, - }; -} diff --git a/public/pages/workflow_detail/prototype/prototype.tsx b/public/pages/workflow_detail/prototype/prototype.tsx deleted file mode 100644 index dc1ac409..00000000 --- a/public/pages/workflow_detail/prototype/prototype.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useState } from 'react'; -import { - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiPageContent, - EuiSpacer, - EuiTab, - EuiTabs, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { Workflow } from '../../../../common'; -import { QueryExecutor } from './query_executor'; -import { Ingestor } from './ingestor'; - -interface PrototypeProps { - workflow?: Workflow; -} - -enum TAB_ID { - INGEST = 'ingest', - QUERY = 'query', -} - -const inputTabs = [ - { - id: TAB_ID.INGEST, - name: '1. Ingest Data', - disabled: false, - }, - { - id: TAB_ID.QUERY, - name: '2. Query data', - disabled: false, - }, -]; - -/** - * A simple prototyping page to perform ingest and search. - */ -export function Prototype(props: PrototypeProps) { - const [selectedTabId, setSelectedTabId] = useState(TAB_ID.INGEST); - return ( - - -

Prototype

-
- - {props.workflow?.resourcesCreated && - props.workflow?.resourcesCreated.length > 0 ? ( - <> - - {inputTabs.map((tab, idx) => { - return ( - setSelectedTabId(tab.id)} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - key={idx} - > - {tab.name} - - ); - })} - - - - {selectedTabId === TAB_ID.INGEST && ( - - - - )} - {selectedTabId === TAB_ID.QUERY && ( - - - - )} - - - ) : ( - No resources available} - titleSize="s" - body={ - <> - - Provision the workflow to generate resources in order to start - prototyping. - - - } - /> - )} -
- ); -} diff --git a/public/pages/workflow_detail/prototype/query_executor.tsx b/public/pages/workflow_detail/prototype/query_executor.tsx deleted file mode 100644 index 6ee80421..00000000 --- a/public/pages/workflow_detail/prototype/query_executor.tsx +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useState, useEffect } from 'react'; -import { - EuiButton, - EuiCodeEditor, - EuiFieldText, - EuiFlexGroup, - EuiFlexItem, - EuiText, -} from '@elastic/eui'; -import { USE_CASE, Workflow } from '../../../../common'; -import { - getIndexName, - getNeuralSearchValues, -} from '../utils/data_extractor_utils'; -import { searchIndex, useAppDispatch } from '../../../store'; -import { getCore } from '../../../services'; -import { - HybridSearchValues, - NeuralSparseValues, - SemanticSearchValues, - WorkflowValues, - getFormattedJSONString, -} from './utils'; - -interface QueryExecutorProps { - workflow: Workflow; -} - -type QueryGeneratorFn = ( - queryText: string, - workflowValues: SemanticSearchValues | NeuralSparseValues -) => {}; - -/** - * A basic and flexible UI for executing queries against an index. Sets up guardrails to limit - * what is customized in the query, and setting readonly values based on the workflow's use case - * and details. - * - * For example, given a semantic search workflow configured on index A, with model B, input field C, and vector field D, - * the UI will enforce a semantic search neural query configured with B,C,D, and run it against A. - */ -export function QueryExecutor(props: QueryExecutorProps) { - const dispatch = useAppDispatch(); - // query state - const [workflowValues, setWorkflowValues] = useState(); - const [queryGeneratorFn, setQueryGeneratorFn] = useState(); - const [indexName, setIndexName] = useState(''); - const [queryObj, setQueryObj] = useState<{}>({}); - const [formattedQuery, setFormattedQuery] = useState(''); - const [userInput, setUserInput] = useState(''); - - // results state - const [resultHits, setResultHits] = useState<{}[]>([]); - const [formattedHits, setFormattedHits] = useState(''); - - // hook to set all of the workflow-related fields based on the use case - useEffect(() => { - setWorkflowValues(getWorkflowValues(props.workflow)); - setQueryGeneratorFn(getQueryGeneratorFn(props.workflow)); - setIndexName(getIndexName(props.workflow)); - }, [props.workflow]); - - // hook to generate the query once all dependent input vars are available - useEffect(() => { - if (queryGeneratorFn && workflowValues) { - setQueryObj(queryGeneratorFn(userInput, workflowValues)); - } - }, [userInput, queryGeneratorFn, workflowValues]); - - // hooks to persist the formatted data. this is so we don't - // re-execute the JSON formatting unless necessary - useEffect(() => { - setFormattedHits(getFormattedJSONString(processHits(resultHits))); - }, [resultHits]); - useEffect(() => { - setFormattedQuery(getFormattedJSONString(queryObj)); - }, [queryObj]); - - // - function onExecuteSearch() { - dispatch( - searchIndex({ - index: indexName, - body: queryObj, - searchPipeline: workflowValues?.searchPipelineId, - }) - ) - .unwrap() - .then(async (result) => { - setResultHits(result.hits.hits); - }) - .catch((error: any) => { - getCore().notifications.toasts.addDanger(error); - setResultHits([]); - }); - } - - return ( - - - - - Execute queries to test out the results! - - - - - { - setUserInput(e.target.value); - }} - /> - - - - Search - - - - - - {}} - readOnly={true} - setOptions={{ - fontSize: '14px', - }} - aria-label="Code Editor" - tabSize={2} - /> - - - - - - - - Results - - - - - - - - - - {}} - readOnly={true} - setOptions={{ - fontSize: '14px', - }} - aria-label="Code Editor" - tabSize={2} - /> - - - - - ); -} - -// getting the appropriate query generator function based on the use case -function getQueryGeneratorFn(workflow: Workflow): QueryGeneratorFn { - let fn; - switch (workflow.use_case) { - case USE_CASE.SEMANTIC_SEARCH: { - fn = () => generateSemanticSearchQuery; - break; - } - case USE_CASE.NEURAL_SPARSE_SEARCH: { - fn = () => generateNeuralSparseQuery; - break; - } - case USE_CASE.HYBRID_SEARCH: { - fn = () => generateHybridSearchQuery; - break; - } - default: { - fn = () => () => {}; - } - } - return fn; -} - -// getting the appropriate static values from the workflow based on the use case -function getWorkflowValues(workflow: Workflow): WorkflowValues { - let values; - switch (workflow.use_case) { - case USE_CASE.SEMANTIC_SEARCH: - case USE_CASE.NEURAL_SPARSE_SEARCH: - case USE_CASE.HYBRID_SEARCH: - default: { - values = getNeuralSearchValues(workflow); - } - } - return values; -} - -// utility fn to generate a semantic search query -function generateSemanticSearchQuery( - queryText: string, - workflowValues: SemanticSearchValues -): {} { - return { - // TODO: can make this configurable - _source: { - excludes: [`${workflowValues.vectorField}`], - }, - query: { - neural: { - [workflowValues.vectorField]: { - query_text: queryText, - model_id: workflowValues.modelId, - // TODO: expose k as configurable - k: 5, - }, - }, - }, - }; -} - -// utility fn to generate a neural sparse search query -function generateNeuralSparseQuery( - queryText: string, - workflowValues: NeuralSparseValues -): {} { - return { - // TODO: can make this configurable - _source: { - excludes: [`${workflowValues.vectorField}`], - }, - query: { - neural_sparse: { - [workflowValues.vectorField]: { - query_text: queryText, - model_id: workflowValues.modelId, - }, - }, - }, - }; -} - -// utility fn to generate a hybrid search query -function generateHybridSearchQuery( - queryText: string, - workflowValues: HybridSearchValues -): {} { - return { - // TODO: can make this configurable - _source: { - excludes: [`${workflowValues.vectorField}`], - }, - query: { - hybrid: { - queries: [ - { - match: { - [workflowValues.inputField]: { - query: queryText, - }, - }, - }, - { - neural: { - [workflowValues.vectorField]: { - query_text: queryText, - model_id: workflowValues.modelId, - // TODO: expose k as configurable - k: 5, - }, - }, - }, - ], - }, - }, - }; -} - -function processHits(hits: any[]): {}[] { - return hits.map((hit) => hit._source); -} diff --git a/public/pages/workflow_detail/prototype/utils.ts b/public/pages/workflow_detail/prototype/utils.ts deleted file mode 100644 index 11d6fd9a..00000000 --- a/public/pages/workflow_detail/prototype/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Shared utility fns and constants used in the prototyping page. - */ - -// UTILITY FNS -export function getFormattedJSONString(obj: {}): string { - return Object.values(obj).length > 0 ? JSON.stringify(obj, null, '\t') : ''; -} - -// CONSTANTS -export type WorkflowValues = { - modelId: string; -}; - -export type SemanticSearchValues = WorkflowValues & { - inputField: string; - vectorField: string; -}; -export type NeuralSparseValues = SemanticSearchValues; -export type HybridSearchValues = SemanticSearchValues & { - searchPipelineId: string; -}; diff --git a/public/pages/workflow_detail/tools/errors/errors.tsx b/public/pages/workflow_detail/tools/errors/errors.tsx new file mode 100644 index 00000000..81ed584c --- /dev/null +++ b/public/pages/workflow_detail/tools/errors/errors.tsx @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiText } from '@elastic/eui'; + +interface ErrorsProps {} + +/** + * The basic errors component for the Tools panel. + * Displays any errors found while users configure and test their workflow. + */ +export function Errors(props: ErrorsProps) { + return TODO: add errors details here; +} diff --git a/public/pages/workflow_detail/utils/index.ts b/public/pages/workflow_detail/tools/errors/index.ts similarity index 67% rename from public/pages/workflow_detail/utils/index.ts rename to public/pages/workflow_detail/tools/errors/index.ts index 91447dd0..28db1186 100644 --- a/public/pages/workflow_detail/utils/index.ts +++ b/public/pages/workflow_detail/tools/errors/index.ts @@ -3,4 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './data_extractor_utils'; +export { Errors } from './errors'; diff --git a/public/pages/workflow_detail/tools/ingest/index.ts b/public/pages/workflow_detail/tools/ingest/index.ts new file mode 100644 index 00000000..cef6b0f4 --- /dev/null +++ b/public/pages/workflow_detail/tools/ingest/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { Ingest } from './ingest'; diff --git a/public/pages/workflow_detail/tools/ingest/ingest.tsx b/public/pages/workflow_detail/tools/ingest/ingest.tsx new file mode 100644 index 00000000..d4097b16 --- /dev/null +++ b/public/pages/workflow_detail/tools/ingest/ingest.tsx @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiCodeEditor } from '@elastic/eui'; + +interface IngestProps { + ingestResponse: string; +} + +/** + * The basic ingest component for the Tools panel. + * Displays a read-only view of the ingest response after users perform ingest. + */ +export function Ingest(props: IngestProps) { + return ( + // TODO: known issue with the editor where resizing the resizablecontainer does not + // trigger vertical scroll updates. Updating the window, or reloading the component + // by switching tabs etc. will refresh it correctly + + ); +} diff --git a/public/pages/workflow_detail/prototype/index.ts b/public/pages/workflow_detail/tools/query/index.ts similarity index 71% rename from public/pages/workflow_detail/prototype/index.ts rename to public/pages/workflow_detail/tools/query/index.ts index 1021e1a2..d87517bd 100644 --- a/public/pages/workflow_detail/prototype/index.ts +++ b/public/pages/workflow_detail/tools/query/index.ts @@ -3,4 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './prototype'; +export { Query } from './query'; diff --git a/public/pages/workflow_detail/tools/query/query.tsx b/public/pages/workflow_detail/tools/query/query.tsx new file mode 100644 index 00000000..85e81a7c --- /dev/null +++ b/public/pages/workflow_detail/tools/query/query.tsx @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiCodeEditor } from '@elastic/eui'; + +interface QueryProps { + queryResponse: string; +} + +/** + * The basic query component for the Tools panel. + * Displays a read-only view of the query response after users perform search. + */ +export function Query(props: QueryProps) { + return ( + // TODO: known issue with the editor where resizing the resizablecontainer does not + // trigger vertical scroll updates. Updating the window, or reloading the component + // by switching tabs etc. will refresh it correctly + + ); +} diff --git a/public/pages/workflow_detail/tools/resources/resources.tsx b/public/pages/workflow_detail/tools/resources/resources.tsx index 1575f1d5..3a4370bd 100644 --- a/public/pages/workflow_detail/tools/resources/resources.tsx +++ b/public/pages/workflow_detail/tools/resources/resources.tsx @@ -18,7 +18,8 @@ interface ResourcesProps { } /** - * A simple resources page to browse created resources for a given Workflow. + * The basic resources component for the Tools panel. Displays all created + * resources for the particular workflow */ export function Resources(props: ResourcesProps) { return ( diff --git a/public/pages/workflow_detail/tools/tools.tsx b/public/pages/workflow_detail/tools/tools.tsx index 9465a37b..e43282cc 100644 --- a/public/pages/workflow_detail/tools/tools.tsx +++ b/public/pages/workflow_detail/tools/tools.tsx @@ -6,17 +6,18 @@ import React, { useEffect, useState } from 'react'; import { isEmpty } from 'lodash'; import { - EuiCodeEditor, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTab, EuiTabs, - EuiText, EuiTitle, } from '@elastic/eui'; import { Workflow } from '../../../../common'; import { Resources } from './resources'; +import { Query } from './query'; +import { Ingest } from './ingest'; +import { Errors } from './errors'; interface ToolsProps { workflow?: Workflow; @@ -108,42 +109,12 @@ export function Tools(props: ToolsProps) { <> {selectedTabId === TAB_ID.INGEST && ( - // TODO: known issue with the editor where resizing the resizablecontainer does not - // trigger vertical scroll updates. Updating the window, or reloading the component - // by switching tabs etc. will refresh it correctly. This applies to the code editor - // components in both ingest and query below. - + )} {selectedTabId === TAB_ID.QUERY && ( - - )} - {selectedTabId === TAB_ID.ERRORS && ( - TODO: View errors placeholder + )} + {selectedTabId === TAB_ID.ERRORS && } {selectedTabId === TAB_ID.RESOURCES && ( )} diff --git a/public/pages/workflow_detail/utils/data_extractor_utils.ts b/public/pages/workflow_detail/utils/data_extractor_utils.ts deleted file mode 100644 index 596c0ffb..00000000 --- a/public/pages/workflow_detail/utils/data_extractor_utils.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - ReactFlowComponent, - COMPONENT_CLASS, - ModelFormValue, - MODEL_CATEGORY, - WorkspaceFormValues, - Workflow, - WORKFLOW_RESOURCE_TYPE, - NODE_CATEGORY, - WORKFLOW_STEP_TYPE, -} from '../../../../common'; -import { getNodesAndEdgesUnderParent } from './workflow_to_template_utils'; -import { componentDataToFormik } from '../../../utils'; - -/** - * Collection of utility fns to extract - * data fields from a Workflow - */ - -export function getIndexName(workflow: Workflow): string | undefined { - if (workflow?.ui_metadata?.workspace_flow) { - const indexerComponent = getIndexerComponent(workflow); - if (indexerComponent) { - const { indexName } = componentDataToFormik(indexerComponent.data) as { - indexName: string; - }; - return indexName; - } - } -} - -// Returns values for neural search use cases. Note many of them -// persist the same values to use during ingest and search, so we keep the naming general -export function getNeuralSearchValues( - workflow: Workflow -): { - modelId: string; - inputField: string; - vectorField: string; - searchPipelineId?: string; -} { - const modelId = getModelId(workflow) as string; - const transformerComponent = getTransformerComponent( - workflow - ) as ReactFlowComponent; - const { inputField, vectorField } = componentDataToFormik( - transformerComponent.data - ) as { inputField: string; vectorField: string }; - - const searchPipelineId = workflow.resourcesCreated?.find( - (resource) => - resource.stepType === WORKFLOW_STEP_TYPE.CREATE_SEARCH_PIPELINE_STEP_TYPE - )?.id; - - return { modelId, inputField, vectorField, searchPipelineId }; -} - -function getFormValues(workflow: Workflow): WorkspaceFormValues | undefined { - if (workflow?.ui_metadata?.workspace_flow) { - const formValues = {} as WorkspaceFormValues; - workflow.ui_metadata.workspace_flow.nodes.forEach((node) => { - formValues[node.id] = componentDataToFormik(node.data); - }); - return formValues; - } -} - -function getModelId(workflow: Workflow): string | undefined { - if (workflow?.ui_metadata?.workspace_flow) { - const transformerComponent = getTransformerComponent(workflow); - if (transformerComponent) { - const { model } = componentDataToFormik(transformerComponent.data) as { - model: ModelFormValue; - inputField: string; - vectorField: string; - }; - - // if it's a pretrained model, we created a new model ID, parse from resources - if (model.category === MODEL_CATEGORY.PRETRAINED) { - const modelResource = workflow.resourcesCreated?.find( - (resource) => resource.type === WORKFLOW_RESOURCE_TYPE.MODEL_ID - ); - return modelResource?.id; - } else { - return model.id; - } - } - } -} - -function getTransformerComponent( - workflow: Workflow -): ReactFlowComponent | undefined { - if (workflow?.ui_metadata?.workspace_flow) { - const { nodes: ingestNodes } = getNodesAndEdgesUnderParent( - NODE_CATEGORY.INGEST_GROUP, - workflow?.ui_metadata?.workspace_flow?.nodes, - workflow?.ui_metadata?.workspace_flow?.edges - ); - return ingestNodes.find((ingestNode) => - ingestNode.data.baseClasses?.includes(COMPONENT_CLASS.ML_TRANSFORMER) - ); - } -} - -function getIndexerComponent( - workflow: Workflow -): ReactFlowComponent | undefined { - if (workflow?.ui_metadata?.workspace_flow) { - const { nodes: ingestNodes } = getNodesAndEdgesUnderParent( - NODE_CATEGORY.INGEST_GROUP, - workflow?.ui_metadata?.workspace_flow?.nodes, - workflow?.ui_metadata?.workspace_flow?.edges - ); - return ingestNodes.find((ingestNode) => - ingestNode.data.baseClasses?.includes(COMPONENT_CLASS.INDEXER) - ); - } -} diff --git a/public/pages/workflow_detail/workflow_detail.tsx b/public/pages/workflow_detail/workflow_detail.tsx index 6d240ca7..b9035c23 100644 --- a/public/pages/workflow_detail/workflow_detail.tsx +++ b/public/pages/workflow_detail/workflow_detail.tsx @@ -64,7 +64,6 @@ export function WorkflowDetail(props: WorkflowDetailProps) { // - fetch available models as their IDs may be used when building flows useEffect(() => { dispatch(getWorkflow(workflowId)); - dispatch(searchModels(FETCH_ALL_QUERY_BODY)); }, []); diff --git a/public/pages/workflow_detail/workflow_inputs/config_field_list.tsx b/public/pages/workflow_detail/workflow_inputs/config_field_list.tsx index d7d80811..8daf1022 100644 --- a/public/pages/workflow_detail/workflow_inputs/config_field_list.tsx +++ b/public/pages/workflow_detail/workflow_inputs/config_field_list.tsx @@ -29,28 +29,15 @@ export function ConfigFieldList(props: ConfigFieldListProps) { {configFields.map((field, idx) => { let el; switch (field.type) { - case 'string': { - el = ( - - - - - ); - break; - } - // case 'select': { + // case 'string': { // el = ( // - // - // + // // // ); // break; diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/advanced_settings.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/advanced_settings.tsx index 50335b25..d4841358 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/advanced_settings.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/advanced_settings.tsx @@ -10,11 +10,9 @@ import { EuiFlexItem, EuiSpacer, } from '@elastic/eui'; -import { IConfigField, WorkflowConfig } from '../../../../../common'; import { JsonField } from '../input_fields'; interface AdvancedSettingsProps { - uiConfig: WorkflowConfig; onFormChange: () => void; } @@ -30,14 +28,14 @@ export function AdvancedSettings(props: AdvancedSettingsProps) { diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_data.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_data.tsx index 3e975771..cc6ccb52 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_data.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_data.tsx @@ -5,12 +5,10 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import { IConfigField, WorkflowConfig } from '../../../../../common'; import { TextField } from '../input_fields'; import { AdvancedSettings } from './advanced_settings'; interface IngestDataProps { - uiConfig: WorkflowConfig; onFormChange: () => void; } @@ -27,16 +25,13 @@ export function IngestData(props: IngestDataProps) { - + ); diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_inputs.tsx index aad1d29f..e1a96acf 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/ingest_inputs.tsx @@ -43,10 +43,7 @@ export function IngestInputs(props: IngestInputsProps) { - + ); diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx index fce7f7c5..30cbd120 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx @@ -63,14 +63,7 @@ export function SourceData(props: SourceDataProps) { void; + label: string; + helpLink?: string; + helpText?: string; editorHeight?: string; } @@ -41,17 +43,17 @@ export function JsonField(props: JsonFieldProps) { return ( - + Learn more ) : undefined } - helpText={props.field.helpText || undefined} + helpText={props.helpText || undefined} error={getIn(errors, field.name)} isInvalid={getIn(errors, field.name) && getIn(touched, field.name)} > diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx index 26d50698..802d8174 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx @@ -9,24 +9,14 @@ import { Field, FieldProps, getIn, useFormikContext } from 'formik'; import { EuiFormRow, EuiLink, - EuiRadioGroup, - EuiRadioGroupOption, - EuiSpacer, EuiSuperSelect, EuiSuperSelectOption, EuiText, } from '@elastic/eui'; import { - BERT_SENTENCE_TRANSFORMER, MODEL_STATE, - ROBERTA_SENTENCE_TRANSFORMER, WorkspaceFormValues, ModelFormValue, - MODEL_CATEGORY, - MPNET_SENTENCE_TRANSFORMER, - NEURAL_SPARSE_TRANSFORMER, - NEURAL_SPARSE_DOC_TRANSFORMER, - NEURAL_SPARSE_TOKENIZER_TRANSFORMER, IConfigField, } from '../../../../../common'; import { AppState } from '../../../../store'; @@ -41,11 +31,8 @@ type ModelItem = ModelFormValue & { name: string; }; -// TODO: there is no concrete UX for model selection and model provisioning. This component is TBD -// and simply provides the ability to select existing models, or deploy some pretrained ones, -// and persist all of this in form state. /** - * A specific field for selecting existing deployed models, or available pretrained models. + * A specific field for selecting existing deployed models */ export function ModelField(props: ModelFieldProps) { // Initial store is fetched when loading base page. We don't @@ -57,26 +44,8 @@ export function ModelField(props: ModelFieldProps) { // Deployed models state const [deployedModels, setDeployedModels] = useState([]); - const [pretrainedModels, setPretrainedModels] = useState([]); - const [selectableModels, setSelectableModels] = useState([]); - // Radio options state - const radioOptions = [ - { - id: MODEL_CATEGORY.DEPLOYED, - label: 'Existing deployed models', - }, - // TODO: finalize if pretrained models will be supported or not - // { - // id: MODEL_CATEGORY.PRETRAINED, - // label: 'Pretrained models', - // }, - ] as EuiRadioGroupOption[]; - const [selectedRadioId, setSelectedRadioId] = useState< - MODEL_CATEGORY | undefined - >(undefined); - - // Initialize available deployed models + // Hook to update available deployed models useEffect(() => { if (models) { const modelItems = [] as ModelItem[]; @@ -85,7 +54,6 @@ export function ModelField(props: ModelFieldProps) { modelItems.push({ id: modelId, name: models[modelId].name, - category: MODEL_CATEGORY.DEPLOYED, algorithm: models[modelId].algorithm, } as ModelItem); } @@ -94,140 +62,63 @@ export function ModelField(props: ModelFieldProps) { } }, [models]); - // Initialize available pretrained models - useEffect(() => { - const modelItems = [ - { - id: ROBERTA_SENTENCE_TRANSFORMER.name, - name: ROBERTA_SENTENCE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: ROBERTA_SENTENCE_TRANSFORMER.algorithm, - }, - { - id: MPNET_SENTENCE_TRANSFORMER.name, - name: MPNET_SENTENCE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: MPNET_SENTENCE_TRANSFORMER.algorithm, - }, - { - id: BERT_SENTENCE_TRANSFORMER.name, - name: BERT_SENTENCE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: BERT_SENTENCE_TRANSFORMER.algorithm, - }, - { - id: NEURAL_SPARSE_TRANSFORMER.name, - name: NEURAL_SPARSE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: NEURAL_SPARSE_TRANSFORMER.algorithm, - }, - { - id: NEURAL_SPARSE_DOC_TRANSFORMER.name, - name: NEURAL_SPARSE_DOC_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: NEURAL_SPARSE_DOC_TRANSFORMER.algorithm, - }, - { - id: NEURAL_SPARSE_TOKENIZER_TRANSFORMER.name, - name: NEURAL_SPARSE_TOKENIZER_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: NEURAL_SPARSE_TOKENIZER_TRANSFORMER.algorithm, - }, - ]; - setPretrainedModels(modelItems); - }, []); - - // Update the valid available models when the radio selection changes. - // e.g., only show deployed models when 'deployed' button is selected - useEffect(() => { - if (selectedRadioId !== undefined) { - // TODO: add fine-grained filtering so only relevant pretrained and existing models - // are visible based on the use case - if (selectedRadioId === MODEL_CATEGORY.DEPLOYED) { - setSelectableModels(deployedModels); - } else { - setSelectableModels(pretrainedModels); - } - } - }, [selectedRadioId, deployedModels, pretrainedModels]); - return ( {({ field, form }: FieldProps) => { - // a hook to update the model category and trigger reloading - // of valid models to select from - useEffect(() => { - setSelectedRadioId(field.value?.category || MODEL_CATEGORY.DEPLOYED); - }, [field.value?.category]); return ( - - Learn more - - - ) : undefined + + + Learn more + + } - helpText={props.field.helpText || undefined} + helpText={'The model ID.'} > - <> - { - // if user selects a new category: - // 1. clear the saved ID - // 2. update the field category - form.setFieldValue(props.fieldPath, { - id: '', - category: radioId, - } as ModelFormValue); - props.onFormChange(); - }} - > - - - ({ - value: option.id, - inputDisplay: ( - <> - {option.name} - - ), - dropdownDisplay: ( - <> - {option.name} - - {option.category} - - - {option.algorithm} - - - ), - disabled: false, - } as EuiSuperSelectOption) - )} - valueOfSelected={field.value?.id || ''} - onChange={(option: string) => { - form.setFieldValue(props.fieldPath, { - id: option, - category: selectedRadioId, - } as ModelFormValue); - props.onFormChange(); - }} - isInvalid={ - getIn(errors, field.name) && getIn(touched, field.name) - ? true - : undefined - } - /> - + + ({ + value: option.id, + inputDisplay: ( + <> + {option.name} + + ), + dropdownDisplay: ( + <> + {option.name} + + Deployed + + + {option.algorithm} + + + ), + disabled: false, + } as EuiSuperSelectOption) + )} + valueOfSelected={field.value?.id || ''} + onChange={(option: string) => { + form.setFieldValue(props.fieldPath, { + id: option, + } as ModelFormValue); + props.onFormChange(); + }} + isInvalid={ + getIn(errors, field.name) && getIn(touched, field.name) + ? true + : undefined + } + /> ); }} diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx deleted file mode 100644 index 9687989d..00000000 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useEffect, useState } from 'react'; -import { Field, FieldProps, useFormikContext } from 'formik'; -import { - EuiFormRow, - EuiLink, - EuiSuperSelect, - EuiSuperSelectOption, - EuiText, -} from '@elastic/eui'; -import { IComponentField, WorkspaceFormValues } from '../../../../../common'; -import { getInitialValue, isFieldInvalid } from '../../../../utils'; - -interface SelectFieldProps { - field: IComponentField; - componentId: string; - onFormChange: () => void; -} - -/** - * An input field for a component where users select from a list of available - * options. - */ -export function SelectField(props: SelectFieldProps) { - // Options state - const [options, setOptions] = useState([]); - - // Populate options depending on the select type - useEffect(() => { - // TODO: figure out how we want to utilize select types to customize the options - if (props.field.selectType === 'model') { - } - }, []); - - const formField = `${props.componentId}.${props.field.id}`; - const { errors, touched } = useFormikContext(); - - return ( - - {({ field, form }: FieldProps) => { - return ( - - - Learn more - - - ) : undefined - } - helpText={props.field.helpText || undefined} - > - - ({ - value: option, - inputDisplay: {option}, - disabled: false, - } as EuiSuperSelectOption) - )} - valueOfSelected={field.value || getInitialValue(props.field.type)} - onChange={(option) => { - form.setFieldValue(formField, option); - props.onFormChange(); - }} - isInvalid={isFieldInvalid( - props.componentId, - props.field.id, - errors, - touched - )} - /> - - ); - }} - - ); -} diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx index 96185d3b..fce6a91a 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx @@ -6,13 +6,16 @@ import React from 'react'; import { Field, FieldProps, getIn, useFormikContext } from 'formik'; import { EuiFieldText, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; -import { IConfigField, WorkspaceFormValues } from '../../../../../common'; +import { WorkspaceFormValues } from '../../../../../common'; import { getInitialValue } from '../../../../utils'; interface TextFieldProps { - field: IConfigField; fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField') onFormChange: () => void; + label: string; + helpLink?: string; + helpText?: string; + placeholder?: string; } /** @@ -27,25 +30,25 @@ export function TextField(props: TextFieldProps) { return ( - + Learn more ) : undefined } - helpText={props.field.helpText || undefined} + helpText={props.helpText || undefined} error={getIn(errors, field.name)} isInvalid={getIn(errors, field.name) && getIn(touched, field.name)} > { form.setFieldValue(props.fieldPath, e.target.value); props.onFormChange(); diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx index 096267b9..596abb2a 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/configure_search_request.tsx @@ -92,14 +92,7 @@ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) { node.id === ingestId0)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === ingestId1)?.data -// .baseClasses, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId1, -// key: edgeId1, -// source: ingestId1, -// target: ingestId2, -// sourceClasses: ingestNodes.find((node) => node.id === ingestId1)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === ingestId2)?.data -// .baseClasses, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId2, -// key: edgeId2, -// source: searchId0, -// target: searchId1, -// sourceClasses: ingestNodes.find((node) => node.id === searchId0)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === searchId1)?.data -// .baseClasses, -// sourceHandle: COMPONENT_CLASS.QUERY, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId3, -// key: edgeId3, -// source: searchId1, -// target: searchId2, -// sourceClasses: ingestNodes.find((node) => node.id === searchId1)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === searchId2)?.data -// .baseClasses, -// sourceHandle: COMPONENT_CLASS.QUERY, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// ] as ReactFlowEdge[], -// }; -// } - -// function fetchHybridSearchWorkspaceFlow(): WorkspaceFlowState { -// const ingestId0 = generateId(COMPONENT_CLASS.DOCUMENT); -// const ingestId1 = generateId(COMPONENT_CLASS.TEXT_EMBEDDING_TRANSFORMER); -// const ingestId2 = generateId(COMPONENT_CLASS.KNN_INDEXER); -// const ingestGroupId = generateId(COMPONENT_CATEGORY.INGEST); -// const searchGroupId = generateId(COMPONENT_CATEGORY.SEARCH); -// const searchId0 = generateId(COMPONENT_CLASS.MATCH_QUERY); -// const searchId1 = generateId(COMPONENT_CLASS.NEURAL_QUERY); -// const searchId2 = generateId(COMPONENT_CLASS.TEXT_EMBEDDING_TRANSFORMER); -// const searchId3 = generateId(COMPONENT_CLASS.KNN_INDEXER); -// const searchId4 = generateId(COMPONENT_CLASS.NORMALIZATION_TRANSFORMER); -// const edgeId0 = generateId('edge'); -// const edgeId1 = generateId('edge'); -// const edgeId2 = generateId('edge'); -// const edgeId3 = generateId('edge'); -// const edgeId4 = generateId('edge'); -// const edgeId5 = generateId('edge'); - -// const ingestNodes = [ -// { -// id: ingestGroupId, -// position: { x: 400, y: 400 }, -// type: NODE_CATEGORY.INGEST_GROUP, -// data: { label: COMPONENT_CATEGORY.INGEST }, -// style: { -// width: 1300, -// height: 400, -// }, -// className: 'reactflow__group-node__ingest', -// selectable: true, -// draggable: true, -// deletable: false, -// }, -// { -// id: ingestId0, -// position: { x: 100, y: 70 }, -// data: initComponentData(new Document().toObj(), ingestId0), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: ingestGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: ingestId1, -// position: { x: 500, y: 70 }, -// data: initComponentData( -// new TextEmbeddingTransformer().toObj(), -// ingestId1 -// ), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: ingestGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: ingestId2, -// position: { x: 900, y: 70 }, -// data: initComponentData(new KnnIndexer().toObj(), ingestId2), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: ingestGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// ] as ReactFlowComponent[]; - -// const searchNodes = [ -// { -// id: searchGroupId, -// position: { x: 400, y: 1000 }, -// type: NODE_CATEGORY.SEARCH_GROUP, -// data: { label: COMPONENT_CATEGORY.SEARCH }, -// style: { -// width: 1700, -// height: 600, -// }, -// className: 'reactflow__group-node__search', -// selectable: true, -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId0, -// position: { x: 100, y: 70 }, -// data: initComponentData(new NeuralQuery().toObj(), searchId0), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId1, -// position: { x: 100, y: 370 }, -// data: initComponentData(new MatchQuery().toObj(), searchId1), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId2, -// position: { x: 500, y: 70 }, -// data: initComponentData( -// new TextEmbeddingTransformer().toPlaceholderObj(), -// searchId2 -// ), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId3, -// position: { x: 900, y: 200 }, -// data: initComponentData(new KnnIndexer().toPlaceholderObj(), searchId3), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId4, -// position: { x: 1300, y: 200 }, -// data: initComponentData( -// new NormalizationTransformer().toObj(), -// searchId4 -// ), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// ] as ReactFlowComponent[]; - -// return { -// nodes: [...ingestNodes, ...searchNodes], -// edges: [ -// { -// id: edgeId0, -// key: edgeId0, -// source: ingestId0, -// target: ingestId1, -// sourceClasses: ingestNodes.find((node) => node.id === ingestId0)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === ingestId1)?.data -// .baseClasses, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId1, -// key: edgeId1, -// source: ingestId1, -// target: ingestId2, -// sourceClasses: ingestNodes.find((node) => node.id === ingestId1)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === ingestId2)?.data -// .baseClasses, -// targetHandle: COMPONENT_CLASS.DOCUMENT, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId2, -// key: edgeId2, -// source: searchId0, -// target: searchId2, -// sourceClasses: searchNodes.find((node) => node.id === searchId0)?.data -// .baseClasses, -// targetClasses: searchNodes.find((node) => node.id === searchId2)?.data -// .baseClasses, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId3, -// key: edgeId3, -// source: searchId2, -// target: searchId3, -// sourceClasses: searchNodes.find((node) => node.id === searchId2)?.data -// .baseClasses, -// targetClasses: searchNodes.find((node) => node.id === searchId3)?.data -// .baseClasses, -// sourceHandle: COMPONENT_CLASS.QUERY, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId4, -// key: edgeId4, -// source: searchId1, -// target: searchId3, -// sourceClasses: searchNodes.find((node) => node.id === searchId1)?.data -// .baseClasses, -// targetClasses: searchNodes.find((node) => node.id === searchId3)?.data -// .baseClasses, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId5, -// key: edgeId5, -// source: searchId3, -// target: searchId4, -// sourceClasses: searchNodes.find((node) => node.id === searchId3)?.data -// .baseClasses, -// targetClasses: searchNodes.find((node) => node.id === searchId4)?.data -// .baseClasses, -// targetHandle: COMPONENT_CLASS.RESULTS, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// ] as ReactFlowEdge[], -// }; -// } - // Utility fn to process workflow names from their presentable/readable titles // on the UI, to a valid name format. // This leads to less friction if users decide to save the name later on. diff --git a/public/utils/config_to_form_utils.ts b/public/utils/config_to_form_utils.ts index 96f7489f..fa6a3dc3 100644 --- a/public/utils/config_to_form_utils.ts +++ b/public/utils/config_to_form_utils.ts @@ -111,7 +111,6 @@ export function getInitialValue(fieldType: ConfigFieldType): ConfigFieldValue { case 'model': { return { id: '', - category: undefined, algorithm: undefined, } as ModelFormValue; } diff --git a/public/utils/config_to_schema_utils.ts b/public/utils/config_to_schema_utils.ts index 4d06a52c..72d8055a 100644 --- a/public/utils/config_to_schema_utils.ts +++ b/public/utils/config_to_schema_utils.ts @@ -91,7 +91,6 @@ function getFieldSchema(fieldType: ConfigFieldType): Schema { case 'model': { baseSchema = yup.object().shape({ id: yup.string().min(1, 'Too short').max(70, 'Too long').required(), - category: yup.string().required(), }); break; } diff --git a/public/utils/config_to_workspace_utils.ts b/public/utils/config_to_workspace_utils.ts index ac1b84c6..cb9f31b2 100644 --- a/public/utils/config_to_workspace_utils.ts +++ b/public/utils/config_to_workspace_utils.ts @@ -33,7 +33,7 @@ import { generateId } from './utils'; **************** Config -> workspace utils ********************** */ -const PARENT_NODE_HEIGHT = 350; +const PARENT_NODE_HEIGHT = 325; const NODE_HEIGHT_Y = 70; const NODE_WIDTH = 300; // based off of the value set in reactflow-styles.scss const NODE_SPACING = 100; // the margin (in # pixels) between the components