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

Add quick-create fields in the create flow and form pre-population #309

Merged
merged 6 commits into from
Aug 26, 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
38 changes: 22 additions & 16 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,14 @@ export const DELIMITER_OPTIONAL_FIELDS = ['delimiter'];
export const SHARED_OPTIONAL_FIELDS = ['max_chunk_limit', 'description', 'tag'];

/**
* QUERIES
* QUERY PRESETS
*/
export const VECTOR_FIELD_PATTERN = `{{vector_field}}`;
export const TEXT_FIELD_PATTERN = `{{text_field}}`;
export const QUERY_TEXT_PATTERN = `{{query_text}}`;
export const QUERY_IMAGE_PATTERN = `{{query_image}}`;
export const MODEL_ID_PATTERN = `{{model_id}}`;

export const FETCH_ALL_QUERY = {
query: {
match_all: {},
Expand All @@ -156,52 +162,52 @@ export const FETCH_ALL_QUERY = {
};
export const SEMANTIC_SEARCH_QUERY = {
_source: {
excludes: [`{{vector_field}}`],
excludes: [VECTOR_FIELD_PATTERN],
},
query: {
neural: {
[`{{vector_field}}`]: {
query_text: `{{query_text}}`,
model_id: `{{model_id}}`,
[VECTOR_FIELD_PATTERN]: {
query_text: QUERY_TEXT_PATTERN,
model_id: MODEL_ID_PATTERN,
k: 100,
},
},
},
};
export const MULTIMODAL_SEARCH_QUERY = {
_source: {
excludes: [`{{vector_field}}`],
excludes: [VECTOR_FIELD_PATTERN],
},
query: {
neural: {
[`{{vector_field}}`]: {
query_text: `{{query_text}}`,
query_image: `{{query_image}}`,
model_id: `{{model_id}}`,
[VECTOR_FIELD_PATTERN]: {
query_text: QUERY_TEXT_PATTERN,
query_image: QUERY_IMAGE_PATTERN,
model_id: MODEL_ID_PATTERN,
k: 100,
},
},
},
};
export const HYBRID_SEARCH_QUERY = {
_source: {
excludes: [`{{vector_field}}`],
excludes: [VECTOR_FIELD_PATTERN],
},
query: {
hybrid: {
queries: [
{
match: {
[`{{text_field}}`]: {
query: `{{query_text}}`,
[TEXT_FIELD_PATTERN]: {
query: QUERY_TEXT_PATTERN,
},
},
},
{
neural: {
[`{{vector_field}}`]: {
query_text: `{{query_text}}`,
model_id: `{{model_id}}`,
[VECTOR_FIELD_PATTERN]: {
query_text: QUERY_TEXT_PATTERN,
model_id: MODEL_ID_PATTERN,
k: 5,
},
},
Expand Down
7 changes: 7 additions & 0 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,13 @@ export type QueryPreset = {
query: string;
};

export type QuickConfigureFields = {
embeddingModelId?: string;
vectorField?: string;
textField?: string;
embeddingLength?: number;
};

/**
********** OPENSEARCH TYPES/INTERFACES ************
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function EditQueryModal(props: EditQueryModalProps) {
setFieldValue(props.queryFieldPath, preset.query);
setPopoverOpen(false);
},
size: 's',
size: 'full',
})),
},
]}
Expand Down
21 changes: 18 additions & 3 deletions public/pages/workflows/new_workflow/new_workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,19 @@ import {
} from '@elastic/eui';
import { useSelector } from 'react-redux';
import { UseCase } from './use_case';
import { Workflow, WorkflowTemplate } from '../../../../common';
import { AppState, useAppDispatch, getWorkflowPresets } from '../../../store';
import {
FETCH_ALL_QUERY,
Workflow,
WorkflowTemplate,
} from '../../../../common';
import {
AppState,
useAppDispatch,
getWorkflowPresets,
searchModels,
} from '../../../store';
import { enrichPresetWorkflowWithUiMetadata } from './utils';
import { getDataSourceId } from '../../../utils';

interface NewWorkflowProps {}

Expand All @@ -27,6 +37,7 @@ interface NewWorkflowProps {}
*/
export function NewWorkflow(props: NewWorkflowProps) {
const dispatch = useAppDispatch();
const dataSourceId = getDataSourceId();

// workflows state
const { presetWorkflows, loading } = useSelector(
Expand All @@ -43,9 +54,13 @@ export function NewWorkflow(props: NewWorkflowProps) {
setSearchQuery(query);
}, 200);

// initial state
// on initial load:
// 1. fetch the workflow presets persisted on server-side
// 2. fetch the ML models. these may be used in quick-create views when selecting a preset,
// so we optimize by fetching once at the top-level here.
useEffect(() => {
dispatch(getWorkflowPresets());
dispatch(searchModels({ apiBody: FETCH_ALL_QUERY, dataSourceId }));
}, []);

// initial hook to populate all workflows
Expand Down
163 changes: 163 additions & 0 deletions public/pages/workflows/new_workflow/quick_configure_inputs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import {
EuiCompressedFormRow,
EuiText,
EuiSpacer,
EuiCompressedSuperSelect,
EuiSuperSelectOption,
EuiAccordion,
EuiCompressedFieldText,
EuiCompressedFieldNumber,
} from '@elastic/eui';
import {
MODEL_STATE,
Model,
QuickConfigureFields,
WORKFLOW_TYPE,
} from '../../../../common';
import { AppState } from '../../../store';

interface QuickConfigureInputsProps {
workflowType?: WORKFLOW_TYPE;
setFields(fields: QuickConfigureFields): void;
}

// Dynamic component to allow optional input configuration fields for different use cases.
// Hooks back to the parent component with such field values
export function QuickConfigureInputs(props: QuickConfigureInputsProps) {
const models = useSelector((state: AppState) => state.models.models);

// Deployed models state
const [deployedModels, setDeployedModels] = useState<Model[]>([]);

// Hook to update available deployed models
useEffect(() => {
if (models) {
setDeployedModels(
Object.values(models).filter(
(model) => model.state === MODEL_STATE.DEPLOYED
)
);
}
}, [models]);

// Local field values state
const [fieldValues, setFieldValues] = useState<QuickConfigureFields>({});

// Hook to update the parent field values
useEffect(() => {
props.setFields(fieldValues);
}, [fieldValues]);

return (
<>
{(props.workflowType === WORKFLOW_TYPE.SEMANTIC_SEARCH ||
props.workflowType === WORKFLOW_TYPE.MULTIMODAL_SEARCH ||
props.workflowType === WORKFLOW_TYPE.HYBRID_SEARCH) && (
<>
<EuiSpacer size="m" />
<EuiAccordion
id="optionalConfiguration"
buttonContent="Optional configuration"
initialIsOpen={true}
>
<EuiSpacer size="m" />
<EuiCompressedFormRow
label={'Embedding model'}
isInvalid={false}
helpText="The model to generate embeddings"
>
<EuiCompressedSuperSelect
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={fieldValues?.embeddingModelId || ''}
onChange={(option: string) => {
setFieldValues({
...fieldValues,
embeddingModelId: option,
});
}}
isInvalid={false}
/>
</EuiCompressedFormRow>
<EuiSpacer size="s" />
<EuiCompressedFormRow
label={'Text field'}
isInvalid={false}
helpText="The name of the document field containing plaintext"
>
<EuiCompressedFieldText
value={fieldValues?.textField || ''}
onChange={(e) => {
setFieldValues({
...fieldValues,
textField: e.target.value,
});
}}
/>
</EuiCompressedFormRow>
<EuiSpacer size="s" />
<EuiCompressedFormRow
label={'Vector field'}
isInvalid={false}
helpText="The name of the document field containing the vector embedding"
>
<EuiCompressedFieldText
value={fieldValues?.vectorField || ''}
onChange={(e) => {
setFieldValues({
...fieldValues,
vectorField: e.target.value,
});
}}
/>
</EuiCompressedFormRow>
<EuiSpacer size="s" />
<EuiCompressedFormRow
label={'Embedding length'}
isInvalid={false}
helpText="The length / dimension of the generated vector embeddings"
>
<EuiCompressedFieldNumber
value={fieldValues?.embeddingLength || ''}
onChange={(e) => {
setFieldValues({
...fieldValues,
embeddingLength: Number(e.target.value),
});
}}
/>
</EuiCompressedFormRow>
</EuiAccordion>
</>
)}
</>
);
}
Loading
Loading