feat(editor): Support node-creator actions for vector store nodes (#11032)

This commit is contained in:
oleg 2024-10-01 14:10:08 +02:00 committed by GitHub
parent 3a9c65e1cb
commit 72b70d9d98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 8 deletions

View file

@ -144,6 +144,12 @@ export function addToolNodeToParent(nodeName: string, parentNodeName: string) {
export function addOutputParserNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_outputParser', parentNodeName);
}
export function addVectorStoreNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_vectorStore', parentNodeName);
}
export function addRetrieverNodeToParent(nodeName: string, parentNodeName: string) {
addSupplementalNodeToParent(nodeName, 'ai_retriever', parentNodeName);
}
export function clickExecuteWorkflowButton() {
getExecuteWorkflowButton().click();

View file

@ -1,3 +1,9 @@
import {
addNodeToCanvas,
addRetrieverNodeToParent,
addVectorStoreNodeToParent,
getNodeCreatorItems,
} from '../composables/workflow';
import { IF_NODE_NAME } from '../constants';
import { NodeCreator } from '../pages/features/node-creator';
import { NDV } from '../pages/ndv';
@ -504,4 +510,38 @@ describe('Node Creator', () => {
nodeCreatorFeature.getters.searchBar().find('input').clear().type('gith');
nodeCreatorFeature.getters.nodeItemName().first().should('have.text', 'GitHub');
});
it('should show vector stores actions', () => {
const actions = [
'Get ranked documents from vector store',
'Add documents to vector store',
'Retrieve documents for AI processing',
];
nodeCreatorFeature.actions.openNodeCreator();
nodeCreatorFeature.getters.searchBar().find('input').clear().type('Vector Store');
getNodeCreatorItems().then((items) => {
const vectorStores = items.map((_i, el) => el.innerText);
// Loop over all vector stores and check if they have the three actions
vectorStores.each((_i, vectorStore) => {
nodeCreatorFeature.getters.getCreatorItem(vectorStore).click();
actions.forEach((action) => {
nodeCreatorFeature.getters.getCreatorItem(action).should('be.visible');
});
cy.realPress('ArrowLeft');
});
});
});
it('should add node directly for sub-connection', () => {
addNodeToCanvas('Question and Answer Chain', true);
addRetrieverNodeToParent('Vector Store Retriever', 'Question and Answer Chain');
cy.realPress('Escape');
addVectorStoreNodeToParent('In-Memory Vector Store', 'Vector Store Retriever');
cy.realPress('Escape');
WorkflowPage.getters.canvasNodes().should('have.length', 4);
});
});

View file

@ -88,25 +88,25 @@ function getOperationModeOptions(args: VectorStoreNodeConstructorArgs): INodePro
name: 'Get Many',
value: 'load',
description: 'Get many ranked documents from vector store for query',
action: 'Get many ranked documents from vector store for query',
action: 'Get ranked documents from vector store',
},
{
name: 'Insert Documents',
value: 'insert',
description: 'Insert documents into vector store',
action: 'Insert documents into vector store',
action: 'Add documents to vector store',
},
{
name: 'Retrieve Documents (For Agent/Chain)',
value: 'retrieve',
description: 'Retrieve documents from vector store to be used with AI nodes',
action: 'Retrieve documents from vector store to be used with AI nodes',
action: 'Retrieve documents for AI processing',
},
{
name: 'Update Documents',
value: 'update',
description: 'Update documents in vector store by ID',
action: 'Update documents in vector store by ID',
action: 'Update vector store documents',
},
];

View file

@ -14,6 +14,7 @@ import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import NodeIcon from '@/components/NodeIcon.vue';
import { useActions } from '../composables/useActions';
import { useViewStacks } from '../composables/useViewStacks';
import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
import { useNodeType } from '@/composables/useNodeType';
@ -34,6 +35,7 @@ const telemetry = useTelemetry();
const { actions } = useNodeCreatorStore();
const { getAddedNodesAndConnections } = useActions();
const { activeViewStack } = useViewStacks();
const { isSubNodeType } = useNodeType({
nodeType: props.nodeType,
});
@ -61,7 +63,7 @@ const dataTestId = computed(() =>
);
const hasActions = computed(() => {
return nodeActions.value.length > 1;
return nodeActions.value.length > 1 && !activeViewStack.hideActions;
});
const nodeActions = computed(() => {

View file

@ -90,7 +90,8 @@ function onSelected(item: INodeCreateElement) {
if (item.type === 'node') {
const nodeActions = actions?.[item.key] || [];
if (nodeActions.length <= 1) {
// Only show actions if there are more than one or if the view is not an AI subcategory
if (nodeActions.length <= 1 || activeViewStack.value.hideActions) {
selectNodeType([item.key]);
return;
}

View file

@ -1,5 +1,5 @@
import type { ActionTypeDescription, ActionsRecord, SimplifiedNodeType } from '@/Interface';
import { CUSTOM_API_CALL_KEY, HTTP_REQUEST_NODE_TYPE } from '@/constants';
import { AI_SUBCATEGORY, CUSTOM_API_CALL_KEY, HTTP_REQUEST_NODE_TYPE } from '@/constants';
import { memoize, startCase } from 'lodash-es';
import type {
ICredentialType,
@ -97,6 +97,36 @@ function operationsCategory(nodeTypeDescription: INodeTypeDescription): ActionTy
return items;
}
function modeCategory(nodeTypeDescription: INodeTypeDescription): ActionTypeDescription[] {
// Mode actions should only be available for AI nodes
const isAINode = nodeTypeDescription.codex?.categories?.includes(AI_SUBCATEGORY);
if (!isAINode) return [];
const matchedProperty = nodeTypeDescription.properties.find(
(property) => property.name?.toLowerCase() === 'mode',
);
if (!matchedProperty?.options) return [];
const modeOptions = matchedProperty.options as INodePropertyOptions[];
const items = modeOptions.map((item: INodePropertyOptions) => ({
...getNodeTypeBase(nodeTypeDescription),
actionKey: item.value as string,
displayName: item.action ?? startCase(item.name),
description: item.description ?? '',
displayOptions: matchedProperty.displayOptions,
values: {
[matchedProperty.name]: item.value,
},
}));
// Do not return empty category
if (items.length === 0) return [];
return items;
}
function triggersCategory(nodeTypeDescription: INodeTypeDescription): ActionTypeDescription[] {
const matchingKeys = ['event', 'events', 'trigger on'];
const isTrigger = nodeTypeDescription.displayName?.toLowerCase().includes('trigger');
@ -231,7 +261,12 @@ export function useActionsGenerator() {
function generateNodeActions(node: INodeTypeDescription | undefined) {
if (!node) return [];
if (node.codex?.subcategories?.AI?.includes('Tools')) return [];
return [...triggersCategory(node), ...operationsCategory(node), ...resourceCategories(node)];
return [
...triggersCategory(node),
...operationsCategory(node),
...resourceCategories(node),
...modeCategory(node),
];
}
function filterActions(actions: ActionTypeDescription[]) {
// Do not show single action nodes

View file

@ -68,6 +68,7 @@ interface ViewStack {
searchItems?: SimplifiedNodeType[];
forceIncludeNodes?: string[];
mode?: 'actions' | 'nodes';
hideActions?: boolean;
baseFilter?: (item: INodeCreateElement) => boolean;
itemsMapper?: (item: INodeCreateElement) => INodeCreateElement;
panelClass?: string;
@ -344,6 +345,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
subcategory: connectionType,
};
},
hideActions: true,
preventBack: true,
});
}