diff --git a/common/constants.ts b/common/constants.ts
index 338964be..45843895 100644
--- a/common/constants.ts
+++ b/common/constants.ts
@@ -3,7 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { WORKFLOW_STATE } from './interfaces';
+import { QueryPreset, WORKFLOW_STATE } from './interfaces';
+import { customStringify } from './utils';
export const PLUGIN_ID = 'flow-framework';
export const SEARCH_STUDIO = 'Search Studio';
@@ -59,6 +60,8 @@ export const SEARCH_MODELS_NODE_API_PATH = `${BASE_MODEL_NODE_API_PATH}/search`;
// frontend-specific workflow types, derived from the available preset templates
export enum WORKFLOW_TYPE {
SEMANTIC_SEARCH = 'Semantic search',
+ MULTIMODAL_SEARCH = 'Multimodal search',
+ HYBRID_SEARCH = 'Hybrid search',
CUSTOM = 'Custom',
UNKNOWN = 'Unknown',
}
@@ -142,6 +145,91 @@ export const FIXED_TOKEN_LENGTH_OPTIONAL_FIELDS = [
export const DELIMITER_OPTIONAL_FIELDS = ['delimiter'];
export const SHARED_OPTIONAL_FIELDS = ['max_chunk_limit', 'description', 'tag'];
+/**
+ * QUERIES
+ */
+export const FETCH_ALL_QUERY = {
+ query: {
+ match_all: {},
+ },
+ size: 1000,
+};
+export const SEMANTIC_SEARCH_QUERY = {
+ _source: {
+ excludes: [`{{vector_field}}`],
+ },
+ query: {
+ neural: {
+ [`{{vector_field}}`]: {
+ query_text: `{{query_text}}`,
+ model_id: `{{model_id}}`,
+ k: 100,
+ },
+ },
+ },
+};
+export const MULTIMODAL_SEARCH_QUERY = {
+ _source: {
+ excludes: [`{{vector_field}}`],
+ },
+ query: {
+ neural: {
+ [`{{vector_field}}`]: {
+ query_text: `{{query_text}}`,
+ query_image: `{{query_image}}`,
+ model_id: `{{model_id}}`,
+ k: 100,
+ },
+ },
+ },
+};
+export const HYBRID_SEARCH_QUERY = {
+ _source: {
+ excludes: [`{{vector_field}}`],
+ },
+ query: {
+ hybrid: {
+ queries: [
+ {
+ match: {
+ [`{{text_field}}`]: {
+ query: `{{query_text}}`,
+ },
+ },
+ },
+ {
+ neural: {
+ [`{{vector_field}}`]: {
+ query_text: `{{query_text}}`,
+ model_id: `{{model_id}}`,
+ k: 5,
+ },
+ },
+ },
+ ],
+ },
+ },
+};
+
+export const QUERY_PRESETS = [
+ {
+ name: 'Fetch all',
+ query: customStringify(FETCH_ALL_QUERY),
+ },
+ {
+ name: WORKFLOW_TYPE.SEMANTIC_SEARCH,
+ query: customStringify(SEMANTIC_SEARCH_QUERY),
+ },
+ {
+ name: WORKFLOW_TYPE.MULTIMODAL_SEARCH,
+ query: customStringify(MULTIMODAL_SEARCH_QUERY),
+ },
+ {
+ name: WORKFLOW_TYPE.HYBRID_SEARCH,
+ query: customStringify(HYBRID_SEARCH_QUERY),
+ },
+] as QueryPreset[];
+
/**
* MISCELLANEOUS
*/
@@ -152,12 +240,6 @@ export const DEFAULT_NEW_WORKFLOW_STATE = WORKFLOW_STATE.NOT_STARTED;
export const DEFAULT_NEW_WORKFLOW_STATE_TYPE = ('NOT_STARTED' as any) as typeof WORKFLOW_STATE;
export const DATE_FORMAT_PATTERN = 'MM/DD/YY hh:mm A';
export const EMPTY_FIELD_STRING = '--';
-export const FETCH_ALL_QUERY_BODY = {
- query: {
- match_all: {},
- },
- size: 1000,
-};
export const INDEX_NOT_FOUND_EXCEPTION = 'index_not_found_exception';
export const ERROR_GETTING_WORKFLOW_MSG = 'Failed to retrieve template';
export const NO_MODIFICATIONS_FOUND_TEXT =
diff --git a/common/interfaces.ts b/common/interfaces.ts
index 57481697..6128c6fe 100644
--- a/common/interfaces.ts
+++ b/common/interfaces.ts
@@ -456,6 +456,11 @@ export type WorkflowDict = {
[workflowId: string]: Workflow;
};
+export type QueryPreset = {
+ name: string;
+ query: string;
+};
+
/**
********** OPENSEARCH TYPES/INTERFACES ************
*/
diff --git a/public/pages/workflow_detail/workflow_detail.tsx b/public/pages/workflow_detail/workflow_detail.tsx
index 1b337cca..29b87dab 100644
--- a/public/pages/workflow_detail/workflow_detail.tsx
+++ b/public/pages/workflow_detail/workflow_detail.tsx
@@ -16,7 +16,7 @@ import {
EuiPage,
EuiPageBody,
} from '@elastic/eui';
-import { APP_PATH, BREADCRUMBS, SHOW_ACTIONS_IN_HEADER} from '../../utils';
+import { APP_PATH, BREADCRUMBS, SHOW_ACTIONS_IN_HEADER } from '../../utils';
import { getCore } from '../../services';
import { WorkflowDetailHeader } from './components';
import {
@@ -28,7 +28,7 @@ import {
import { ResizableWorkspace } from './resizable_workspace';
import {
ERROR_GETTING_WORKFLOW_MSG,
- FETCH_ALL_QUERY_BODY,
+ FETCH_ALL_QUERY,
MAX_WORKFLOW_NAME_TO_DISPLAY,
getCharacterLimitedString,
} from '../../../common';
@@ -102,7 +102,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
// - fetch available models as their IDs may be used when building flows
useEffect(() => {
dispatch(getWorkflow({ workflowId, dataSourceId }));
- dispatch(searchModels({ apiBody: FETCH_ALL_QUERY_BODY, dataSourceId }));
+ dispatch(searchModels({ apiBody: FETCH_ALL_QUERY, dataSourceId }));
}, []);
return errorMessage.includes(ERROR_GETTING_WORKFLOW_MSG) ? (
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 92712fc6..528554eb 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
@@ -12,11 +12,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
- EuiModal,
- EuiModalBody,
- EuiModalFooter,
- EuiModalHeader,
- EuiModalHeaderTitle,
EuiSuperSelect,
EuiSuperSelectOption,
EuiText,
@@ -31,6 +26,7 @@ import {
useAppDispatch,
} from '../../../../store';
import { getDataSourceId } from '../../../../utils/utils';
+import { EditQueryModal } from './edit_query_modal';
interface ConfigureSearchRequestProps {
setQuery: (query: string) => void;
@@ -88,33 +84,10 @@ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) {
return (
<>
{isEditModalOpen && (
- setIsEditModalOpen(false)}
- style={{ width: '70vw' }}
- >
-
-
- {`Edit query`}
-
-
-
-
-
-
- setIsEditModalOpen(false)}
- fill={false}
- color="primary"
- >
- Close
-
-
-
+
)}
diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx
new file mode 100644
index 00000000..e6f2cb2e
--- /dev/null
+++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx
@@ -0,0 +1,112 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useState, useEffect } from 'react';
+import { getIn, useFormikContext } from 'formik';
+import {
+ EuiButton,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiSpacer,
+ EuiSuperSelect,
+ EuiSuperSelectOption,
+ EuiText,
+} from '@elastic/eui';
+import { JsonField } from '../input_fields';
+import {
+ QUERY_PRESETS,
+ QueryPreset,
+ WorkflowFormValues,
+} from '../../../../../common';
+
+interface EditQueryModalProps {
+ queryFieldPath: string;
+ setModalOpen(isOpen: boolean): void;
+}
+
+/**
+ * Basic modal for configuring a query. Provides a dropdown to select from
+ * a set of pre-defined queries targeted for different use cases.
+ */
+export function EditQueryModal(props: EditQueryModalProps) {
+ // Form state
+ const { values, setFieldValue } = useFormikContext();
+
+ // selected preset state
+ const [queryPreset, setQueryPreset] = useState(undefined);
+
+ // if the current query matches some preset, display the preset name as the selected
+ // option in the dropdown. only execute when first rendering so it isn't triggered
+ // when users are updating the underlying value in the JSON editor.
+ useEffect(() => {
+ setQueryPreset(
+ QUERY_PRESETS.find(
+ (preset) => preset.query === getIn(values, props.queryFieldPath)
+ )?.name
+ );
+ }, []);
+
+ return (
+ props.setModalOpen(false)}
+ style={{ width: '70vw' }}
+ >
+
+
+ {`Edit query`}
+
+
+
+
+ Start with a preset or enter manually.
+ {' '}
+
+
+ ({
+ value: preset.name,
+ inputDisplay: (
+ <>
+ {preset.name}
+ >
+ ),
+ dropdownDisplay: {preset.name},
+ disabled: false,
+ } as EuiSuperSelectOption)
+ )}
+ valueOfSelected={queryPreset || ''}
+ onChange={(option: string) => {
+ setQueryPreset(option);
+ setFieldValue(
+ props.queryFieldPath,
+ QUERY_PRESETS.find((preset) => preset.name === option)?.query
+ );
+ }}
+ isInvalid={false}
+ />
+
+
+
+
+ props.setModalOpen(false)}
+ fill={false}
+ color="primary"
+ >
+ Close
+
+
+
+ );
+}
diff --git a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx
index 6c178901..2279ccb9 100644
--- a/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx
+++ b/public/pages/workflow_detail/workflow_inputs/workflow_inputs.tsx
@@ -4,7 +4,6 @@
*/
import React, { useCallback, useEffect, useState } from 'react';
-import { useSelector } from 'react-redux';
import { getIn, useFormikContext } from 'formik';
import { debounce, isEmpty, isEqual } from 'lodash';
import {
@@ -814,7 +813,9 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
{
dispatch(
searchWorkflows({
- apiBody: FETCH_ALL_QUERY_BODY,
+ apiBody: FETCH_ALL_QUERY,
dataSourceId: dataSourceId,
})
);
@@ -161,7 +161,7 @@ export function Workflows(props: WorkflowsProps) {
}
dispatch(
searchWorkflows({
- apiBody: FETCH_ALL_QUERY_BODY,
+ apiBody: FETCH_ALL_QUERY,
dataSourceId: dataSourceId,
})
);
diff --git a/server/resources/templates/hybrid_search.json b/server/resources/templates/hybrid_search.json
new file mode 100644
index 00000000..f1768c16
--- /dev/null
+++ b/server/resources/templates/hybrid_search.json
@@ -0,0 +1,14 @@
+{
+ "name": "Hybrid Search",
+ "description": "A basic workflow containing the ingest pipeline and index configurations for performing hybrid search",
+ "version": {
+ "template": "1.0.0",
+ "compatibility": [
+ "2.17.0",
+ "3.0.0"
+ ]
+ },
+ "ui_metadata": {
+ "type": "Hybrid search"
+ }
+}
\ No newline at end of file
diff --git a/server/resources/templates/multimodal_search.json b/server/resources/templates/multimodal_search.json
new file mode 100644
index 00000000..409a303d
--- /dev/null
+++ b/server/resources/templates/multimodal_search.json
@@ -0,0 +1,14 @@
+{
+ "name": "Multimodal Search",
+ "description": "A basic workflow containing the ingest pipeline and index configurations for performing multimodal search",
+ "version": {
+ "template": "1.0.0",
+ "compatibility": [
+ "2.17.0",
+ "3.0.0"
+ ]
+ },
+ "ui_metadata": {
+ "type": "Multimodal search"
+ }
+}
\ No newline at end of file