mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(editor): Node Creator AI nodes improvements (#9484)
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<n8n-node-creator-node
|
||||
:class="$style.creatorLink"
|
||||
:title="link.title"
|
||||
:is-trigger="false"
|
||||
:description="link.description"
|
||||
:tag="link.tag"
|
||||
:show-action-arrow="true"
|
||||
>
|
||||
<template #icon>
|
||||
<n8n-node-icon type="icon" :name="link.icon" :circle="false" :show-tooltip="false" />
|
||||
</template>
|
||||
</n8n-node-creator-node>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LinkItemProps } from '@/Interface';
|
||||
|
||||
export interface Props {
|
||||
link: LinkItemProps;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.creatorLink {
|
||||
--action-arrow-color: var(--color-text-light);
|
||||
margin-left: var(--spacing-s);
|
||||
margin-right: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
||||
@@ -8,6 +8,7 @@
|
||||
:show-action-arrow="showActionArrow"
|
||||
:is-trigger="isTrigger"
|
||||
:data-test-id="dataTestId"
|
||||
:tag="nodeType.tag"
|
||||
@dragstart="onDragStart"
|
||||
@dragend="onDragEnd"
|
||||
>
|
||||
|
||||
@@ -139,6 +139,13 @@ function onSelected(item: INodeCreateElement) {
|
||||
searchItems: mergedNodes,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === 'link') {
|
||||
window.open(item.properties.url, '_blank');
|
||||
telemetry.trackNodesPanel('nodeCreateList.onLinkSelected', {
|
||||
link: item.properties.url,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function subcategoriesMapper(item: INodeCreateElement) {
|
||||
@@ -195,13 +202,13 @@ function onKeySelect(activeItemId: string) {
|
||||
|
||||
registerKeyHook('MainViewArrowRight', {
|
||||
keyboardKeys: ['ArrowRight', 'Enter'],
|
||||
condition: (type) => ['subcategory', 'node', 'view'].includes(type),
|
||||
condition: (type) => ['subcategory', 'node', 'link', 'view'].includes(type),
|
||||
handler: onKeySelect,
|
||||
});
|
||||
|
||||
registerKeyHook('MainViewArrowLeft', {
|
||||
keyboardKeys: ['ArrowLeft'],
|
||||
condition: (type) => ['subcategory', 'node', 'view'].includes(type),
|
||||
condition: (type) => ['subcategory', 'node', 'link', 'view'].includes(type),
|
||||
handler: arrowLeft,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -8,6 +8,7 @@ import SubcategoryItem from '../ItemTypes/SubcategoryItem.vue';
|
||||
import LabelItem from '../ItemTypes/LabelItem.vue';
|
||||
import ActionItem from '../ItemTypes/ActionItem.vue';
|
||||
import ViewItem from '../ItemTypes/ViewItem.vue';
|
||||
import LinkItem from '../ItemTypes/LinkItem.vue';
|
||||
import CategorizedItemsRenderer from './CategorizedItemsRenderer.vue';
|
||||
|
||||
export interface Props {
|
||||
@@ -147,6 +148,8 @@ watch(
|
||||
[$style.active]: activeItemId === item.uuid,
|
||||
[$style.iteratorItem]: true,
|
||||
[$style[item.type]]: true,
|
||||
// Borderless is only applied to views
|
||||
[$style.borderless]: item.type === 'view' && item.properties.borderless === true,
|
||||
}"
|
||||
data-test-id="item-iterator-item"
|
||||
:data-keyboard-nav-type="item.type !== 'label' ? item.type : undefined"
|
||||
@@ -175,6 +178,12 @@ watch(
|
||||
:view="item.properties"
|
||||
:class="$style.viewItem"
|
||||
/>
|
||||
|
||||
<LinkItem
|
||||
v-else-if="item.type === 'link'"
|
||||
:link="item.properties"
|
||||
:class="$style.linkItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<n8n-loading v-else :loading="true" :rows="1" variant="p" :class="$style.itemSkeleton" />
|
||||
@@ -223,12 +232,14 @@ watch(
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.view {
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
margin-top: var(--spacing-s);
|
||||
padding-top: var(--spacing-xs);
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
@@ -241,4 +252,34 @@ watch(
|
||||
}
|
||||
}
|
||||
}
|
||||
.link {
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: var(--spacing-s);
|
||||
padding-bottom: var(--spacing-xs);
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: var(--spacing-s);
|
||||
right: var(--spacing-s);
|
||||
top: 0;
|
||||
margin: auto;
|
||||
bottom: 0;
|
||||
border-bottom: 1px solid var(--color-foreground-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.borderless {
|
||||
&:last-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
|
||||
&:after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -76,7 +76,7 @@ describe('NodesListPanel', () => {
|
||||
await fireEvent.click(container.querySelector('.backButton')!);
|
||||
await nextTick();
|
||||
|
||||
expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(7);
|
||||
expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(8);
|
||||
});
|
||||
|
||||
it('should render regular nodes', async () => {
|
||||
@@ -136,7 +136,7 @@ describe('NodesListPanel', () => {
|
||||
|
||||
await nextTick();
|
||||
expect(screen.getByText('What happens next?')).toBeInTheDocument();
|
||||
expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(6);
|
||||
expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(5);
|
||||
|
||||
screen.getByText('Action in an app').click();
|
||||
await nextTick();
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
import type { INodeCreateElement, NodeFilterType, SimplifiedNodeType } from '@/Interface';
|
||||
import type {
|
||||
INodeCreateElement,
|
||||
NodeCreateElement,
|
||||
NodeFilterType,
|
||||
SimplifiedNodeType,
|
||||
} from '@/Interface';
|
||||
import {
|
||||
AI_CATEGORY_ROOT_NODES,
|
||||
AI_CODE_NODE_TYPE,
|
||||
AI_NODE_CREATOR_VIEW,
|
||||
AI_OTHERS_NODE_CREATOR_VIEW,
|
||||
AI_SUBCATEGORY,
|
||||
DEFAULT_SUBCATEGORY,
|
||||
TRIGGER_NODE_CREATOR_VIEW,
|
||||
} from '@/constants';
|
||||
import { defineStore } from 'pinia';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import difference from 'lodash-es/difference';
|
||||
|
||||
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
|
||||
|
||||
import {
|
||||
flattenCreateElements,
|
||||
groupItemsInSections,
|
||||
isAINode,
|
||||
searchNodes,
|
||||
sortNodeCreateElements,
|
||||
subcategorizeItems,
|
||||
@@ -27,6 +37,7 @@ import { useKeyboardNavigation } from './useKeyboardNavigation';
|
||||
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import type { INodeInputFilter, NodeConnectionType } from 'n8n-workflow';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
|
||||
interface ViewStack {
|
||||
uuid?: string;
|
||||
@@ -60,11 +71,12 @@ interface ViewStack {
|
||||
export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||
const nodeCreatorStore = useNodeCreatorStore();
|
||||
const { getActiveItemIndex } = useKeyboardNavigation();
|
||||
const i18n = useI18n();
|
||||
|
||||
const viewStacks = ref<ViewStack[]>([]);
|
||||
|
||||
const activeStackItems = computed<INodeCreateElement[]>(() => {
|
||||
const stack = viewStacks.value[viewStacks.value.length - 1];
|
||||
const stack = getLastActiveStack();
|
||||
|
||||
if (!stack?.baselineItems) {
|
||||
return stack.items ? extendItemsWithUUID(stack.items) : [];
|
||||
@@ -76,13 +88,24 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||
? searchBaseItems.value
|
||||
: flattenCreateElements(stack.baselineItems ?? []);
|
||||
|
||||
return extendItemsWithUUID(searchNodes(stack.search || '', searchBase));
|
||||
const canvasHasAINodes = useCanvasStore().aiNodes.length > 0;
|
||||
const filteredNodes =
|
||||
isAiRootView(stack) || canvasHasAINodes ? searchBase : filterOutAiNodes(searchBase);
|
||||
|
||||
const searchResults = extendItemsWithUUID(searchNodes(stack.search || '', filteredNodes));
|
||||
|
||||
const groupedNodes = groupIfAiNodes(searchResults, false) ?? searchResults;
|
||||
// Set the active index to the second item if there's a section
|
||||
// as the first item is collapsable
|
||||
stack.activeIndex = groupedNodes.some((node) => node.type === 'section') ? 1 : 0;
|
||||
|
||||
return groupedNodes;
|
||||
}
|
||||
return extendItemsWithUUID(stack.baselineItems);
|
||||
return extendItemsWithUUID(groupIfAiNodes(stack.baselineItems, true));
|
||||
});
|
||||
|
||||
const activeViewStack = computed<ViewStack>(() => {
|
||||
const stack = viewStacks.value[viewStacks.value.length - 1];
|
||||
const stack = getLastActiveStack();
|
||||
if (!stack) return {};
|
||||
|
||||
const flatBaselineItems = flattenCreateElements(stack.baselineItems ?? []);
|
||||
@@ -99,34 +122,148 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||
);
|
||||
|
||||
const searchBaseItems = computed<INodeCreateElement[]>(() => {
|
||||
const stack = viewStacks.value[viewStacks.value.length - 1];
|
||||
const stack = getLastActiveStack();
|
||||
if (!stack?.searchItems) return [];
|
||||
|
||||
return stack.searchItems.map((item) => transformNodeType(item, stack.subcategory));
|
||||
});
|
||||
|
||||
function getLastActiveStack() {
|
||||
return viewStacks.value[viewStacks.value.length - 1];
|
||||
}
|
||||
|
||||
// Generate a delta between the global search results(all nodes) and the stack search results
|
||||
const globalSearchItemsDiff = computed<INodeCreateElement[]>(() => {
|
||||
const stack = viewStacks.value[viewStacks.value.length - 1];
|
||||
const stack = getLastActiveStack();
|
||||
if (!stack?.search) return [];
|
||||
|
||||
const allNodes = nodeCreatorStore.mergedNodes.map((item) => transformNodeType(item));
|
||||
const globalSearchResult = extendItemsWithUUID(searchNodes(stack.search || '', allNodes));
|
||||
// Apply filtering for AI nodes if the current view is not the AI root view
|
||||
const filteredNodes = isAiRootView(stack) ? allNodes : filterOutAiNodes(allNodes);
|
||||
|
||||
return globalSearchResult.filter((item) => {
|
||||
return !activeStackItems.value.find((activeItem) => activeItem.key === item.key);
|
||||
let globalSearchResult: INodeCreateElement[] = extendItemsWithUUID(
|
||||
searchNodes(stack.search || '', filteredNodes),
|
||||
);
|
||||
if (isAiRootView(stack)) {
|
||||
globalSearchResult = groupIfAiNodes(globalSearchResult);
|
||||
}
|
||||
|
||||
const filteredItems = globalSearchResult.filter((item) => {
|
||||
return !activeStackItems.value.find((activeItem) => {
|
||||
if (activeItem.type === 'section') {
|
||||
const matchingSectionItem = activeItem.children.some(
|
||||
(sectionItem) => sectionItem.key === item.key,
|
||||
);
|
||||
return matchingSectionItem;
|
||||
}
|
||||
|
||||
return activeItem.key === item.key;
|
||||
});
|
||||
});
|
||||
|
||||
// Filter out empty sections if all of their children are filtered out
|
||||
const filteredSections = filteredItems.filter((item) => {
|
||||
if (item.type === 'section') {
|
||||
const hasVisibleChildren = item.children.some((child) =>
|
||||
activeStackItems.value.some((filteredItem) => filteredItem.key === child.key),
|
||||
);
|
||||
|
||||
return hasVisibleChildren;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return filteredSections;
|
||||
});
|
||||
|
||||
const itemsBySubcategory = computed(() => subcategorizeItems(nodeCreatorStore.mergedNodes));
|
||||
|
||||
function isAiRootView(stack: ViewStack) {
|
||||
return stack.rootView === AI_NODE_CREATOR_VIEW;
|
||||
}
|
||||
|
||||
function groupIfAiNodes(items: INodeCreateElement[], sortAlphabetically = true) {
|
||||
const aiNodes = items.filter((node): node is NodeCreateElement => isAINode(node));
|
||||
|
||||
if (aiNodes.length > 0) {
|
||||
const sectionsMap = new Map<string, NodeViewItemSection>();
|
||||
aiNodes.forEach((node) => {
|
||||
const section = node.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.[0];
|
||||
|
||||
if (section) {
|
||||
const currentItems = sectionsMap.get(section)?.items ?? [];
|
||||
const isSubnodesSection =
|
||||
!node.properties.codex?.subcategories?.[AI_SUBCATEGORY].includes(
|
||||
AI_CATEGORY_ROOT_NODES,
|
||||
);
|
||||
|
||||
sectionsMap.set(section, {
|
||||
key: section,
|
||||
title: isSubnodesSection
|
||||
? `${section} (${i18n.baseText('nodeCreator.subnodes')})`
|
||||
: section,
|
||||
items: [...currentItems, node.key],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const nonAiNodes = difference(items, aiNodes);
|
||||
const nonAiTriggerNodes = nonAiNodes.filter(
|
||||
(item) => item.type === 'node' && useNodeTypesStore().isTriggerNode(item.properties.name),
|
||||
);
|
||||
|
||||
const nonAiRegularNodes = difference(nonAiNodes, nonAiTriggerNodes);
|
||||
|
||||
if (nonAiNodes.length > 0) {
|
||||
let sectionKey = '';
|
||||
if (nonAiRegularNodes.length && nonAiTriggerNodes.length) {
|
||||
sectionKey = i18n.baseText('nodeCreator.actionsCategory.regularAndTriggers');
|
||||
} else {
|
||||
sectionKey = nonAiRegularNodes.length
|
||||
? i18n.baseText('nodeCreator.actionsCategory.regularNodes')
|
||||
: i18n.baseText('nodeCreator.actionsCategory.triggerNodes');
|
||||
}
|
||||
|
||||
const nodesKeys = nonAiNodes.map((node) => node.key);
|
||||
|
||||
sectionsMap.set(sectionKey, {
|
||||
key: sectionKey,
|
||||
title: sectionKey,
|
||||
items: [...nodesKeys],
|
||||
});
|
||||
}
|
||||
// Convert sectionsMap to array of sections
|
||||
const sections = Array.from(sectionsMap.values());
|
||||
|
||||
return groupItemsInSections(items, sections, sortAlphabetically);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function filterOutAiNodes(items: INodeCreateElement[]) {
|
||||
const filteredSearchBase = items.filter((item) => {
|
||||
if (item.type === 'node') {
|
||||
const isAICategory = item.properties.codex?.categories?.includes(AI_SUBCATEGORY) === true;
|
||||
|
||||
if (!isAICategory) return true;
|
||||
|
||||
const isRootNodeSubcategory =
|
||||
item.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.includes(AI_CATEGORY_ROOT_NODES);
|
||||
|
||||
return isRootNodeSubcategory;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return filteredSearchBase;
|
||||
}
|
||||
|
||||
async function gotoCompatibleConnectionView(
|
||||
connectionType: NodeConnectionType,
|
||||
isOutput?: boolean,
|
||||
filter?: INodeInputFilter,
|
||||
) {
|
||||
const i18n = useI18n();
|
||||
|
||||
let nodesByConnectionType: { [key: string]: string[] };
|
||||
let relatedAIView: { properties: NodeViewItem['properties'] } | undefined;
|
||||
|
||||
@@ -185,7 +322,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||
}
|
||||
|
||||
function setStackBaselineItems() {
|
||||
const stack = viewStacks.value[viewStacks.value.length - 1];
|
||||
const stack = getLastActiveStack();
|
||||
if (!stack || !activeViewStack.value.uuid) return;
|
||||
|
||||
let stackItems = stack?.items ?? [];
|
||||
@@ -258,7 +395,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
|
||||
}
|
||||
|
||||
function updateCurrentViewStack(stack: Partial<ViewStack>) {
|
||||
const currentStack = viewStacks.value[viewStacks.value.length - 1];
|
||||
const currentStack = getLastActiveStack();
|
||||
const matchedIndex = viewStacks.value.findIndex((s) => s.uuid === currentStack.uuid);
|
||||
if (!currentStack) return;
|
||||
|
||||
|
||||
@@ -6,12 +6,18 @@ import type {
|
||||
INodeCreateElement,
|
||||
SectionCreateElement,
|
||||
} from '@/Interface';
|
||||
import { AI_SUBCATEGORY, CORE_NODES_CATEGORY, DEFAULT_SUBCATEGORY } from '@/constants';
|
||||
import {
|
||||
AI_CATEGORY_AGENTS,
|
||||
AI_SUBCATEGORY,
|
||||
CORE_NODES_CATEGORY,
|
||||
DEFAULT_SUBCATEGORY,
|
||||
} from '@/constants';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { sublimeSearch } from '@/utils/sortUtils';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
import type { NodeViewItemSection } from './viewsData';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
import { sortBy } from 'lodash-es';
|
||||
|
||||
export function transformNodeType(
|
||||
node: SimplifiedNodeType,
|
||||
@@ -70,6 +76,7 @@ export function sortNodeCreateElements(nodes: INodeCreateElement[]) {
|
||||
export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
|
||||
// In order to support the old search we need to remove the 'trigger' part
|
||||
const trimmedFilter = searchFilter.toLowerCase().replace('trigger', '').trimEnd();
|
||||
|
||||
const result = (
|
||||
sublimeSearch<INodeCreateElement>(trimmedFilter, items, [
|
||||
{ key: 'properties.displayName', weight: 1.3 },
|
||||
@@ -83,38 +90,72 @@ export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
|
||||
export function flattenCreateElements(items: INodeCreateElement[]): INodeCreateElement[] {
|
||||
return items.map((item) => (item.type === 'section' ? item.children : item)).flat();
|
||||
}
|
||||
export function isAINode(node: INodeCreateElement) {
|
||||
const isNode = node.type === 'node';
|
||||
if (!isNode) return false;
|
||||
|
||||
if (node.properties.codex?.categories?.includes(AI_SUBCATEGORY)) {
|
||||
const isAgentSubcategory =
|
||||
node.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.includes(AI_CATEGORY_AGENTS);
|
||||
|
||||
return !isAgentSubcategory;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
export function groupItemsInSections(
|
||||
items: INodeCreateElement[],
|
||||
sections: string[] | NodeViewItemSection[],
|
||||
sortAlphabetically = true,
|
||||
): INodeCreateElement[] {
|
||||
const filteredSections = sections.filter(
|
||||
(section): section is NodeViewItemSection => typeof section === 'object',
|
||||
);
|
||||
|
||||
const itemsBySection = items.reduce((acc: Record<string, INodeCreateElement[]>, item) => {
|
||||
const section = filteredSections.find((s) => s.items.includes(item.key));
|
||||
const key = section?.key ?? 'other';
|
||||
acc[key] = [...(acc[key] ?? []), item];
|
||||
return acc;
|
||||
}, {});
|
||||
const itemsBySection = (items2: INodeCreateElement[]) =>
|
||||
items2.reduce((acc: Record<string, INodeCreateElement[]>, item) => {
|
||||
const section = filteredSections.find((s) => s.items.includes(item.key));
|
||||
|
||||
const result: SectionCreateElement[] = filteredSections
|
||||
.map(
|
||||
const key = section?.key ?? 'other';
|
||||
if (key) {
|
||||
acc[key] = [...(acc[key] ?? []), item];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const mapNewSections = (
|
||||
newSections: NodeViewItemSection[],
|
||||
children: Record<string, INodeCreateElement[]>,
|
||||
) =>
|
||||
newSections.map(
|
||||
(section): SectionCreateElement => ({
|
||||
type: 'section',
|
||||
key: section.key,
|
||||
title: section.title,
|
||||
children: sortNodeCreateElements(itemsBySection[section.key] ?? []),
|
||||
children: sortAlphabetically
|
||||
? sortNodeCreateElements(children[section.key] ?? [])
|
||||
: children[section.key] ?? [],
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
const nonAINodes = items.filter((item) => !isAINode(item));
|
||||
const AINodes = items.filter((item) => isAINode(item));
|
||||
|
||||
const nonAINodesBySection = itemsBySection(nonAINodes);
|
||||
const nonAINodesSections = mapNewSections(filteredSections, nonAINodesBySection);
|
||||
|
||||
const AINodesBySection = itemsBySection(AINodes);
|
||||
|
||||
const AINodesSections = mapNewSections(sortBy(filteredSections, ['title']), AINodesBySection);
|
||||
|
||||
const result = [...nonAINodesSections, ...AINodesSections]
|
||||
.concat({
|
||||
type: 'section',
|
||||
key: 'other',
|
||||
title: i18n.baseText('nodeCreator.sectionNames.other'),
|
||||
children: sortNodeCreateElements(itemsBySection.other ?? []),
|
||||
children: sortNodeCreateElements(nonAINodesBySection.other ?? []),
|
||||
})
|
||||
.filter((section) => section.children.length > 0);
|
||||
.filter((section) => section.type !== 'section' || section.children.length > 0);
|
||||
|
||||
if (result.length <= 1) {
|
||||
return items;
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
|
||||
FORM_TRIGGER_NODE_TYPE,
|
||||
MANUAL_TRIGGER_NODE_TYPE,
|
||||
MANUAL_CHAT_TRIGGER_NODE_TYPE,
|
||||
SCHEDULE_TRIGGER_NODE_TYPE,
|
||||
REGULAR_NODE_CREATOR_VIEW,
|
||||
TRANSFORM_DATA_SUBCATEGORY,
|
||||
FILES_SUBCATEGORY,
|
||||
FLOWS_CONTROL_SUBCATEGORY,
|
||||
TRIGGER_NODE_CREATOR_VIEW,
|
||||
EMAIL_IMAP_NODE_TYPE,
|
||||
@@ -52,6 +52,8 @@ import {
|
||||
EMAIL_SEND_NODE_TYPE,
|
||||
EDIT_IMAGE_NODE_TYPE,
|
||||
COMPRESSION_NODE_TYPE,
|
||||
AI_CODE_TOOL_LANGCHAIN_NODE_TYPE,
|
||||
AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
|
||||
} from '@/constants';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
@@ -76,13 +78,17 @@ export interface NodeViewItem {
|
||||
iconProps?: {
|
||||
color?: string;
|
||||
};
|
||||
url?: string;
|
||||
connectionType?: NodeConnectionType;
|
||||
panelClass?: string;
|
||||
group?: string[];
|
||||
sections?: NodeViewItemSection[];
|
||||
description?: string;
|
||||
displayName?: string;
|
||||
tag?: string;
|
||||
tag?: {
|
||||
type: string;
|
||||
text: string;
|
||||
};
|
||||
forceIncludeNodes?: string[];
|
||||
iconData?: {
|
||||
type: string;
|
||||
@@ -141,12 +147,24 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||
value: AI_NODE_CREATOR_VIEW,
|
||||
title: i18n.baseText('nodeCreator.aiPanel.aiNodes'),
|
||||
subtitle: i18n.baseText('nodeCreator.aiPanel.selectAiNode'),
|
||||
info: i18n.baseText('nodeCreator.aiPanel.infoBox', {
|
||||
interpolate: { link: templatesStore.getWebsiteCategoryURL('ai') },
|
||||
}),
|
||||
items: [
|
||||
...chainNodes,
|
||||
{
|
||||
key: 'ai_templates_root',
|
||||
type: 'link',
|
||||
properties: {
|
||||
title: i18n.baseText('nodeCreator.aiPanel.linkItem.title'),
|
||||
icon: 'box-open',
|
||||
description: i18n.baseText('nodeCreator.aiPanel.linkItem.description'),
|
||||
name: 'ai_templates_root',
|
||||
url: templatesStore.getWebsiteCategoryURL(undefined, 'AdvancedAI'),
|
||||
tag: {
|
||||
type: 'info',
|
||||
text: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerTag'),
|
||||
},
|
||||
},
|
||||
},
|
||||
...agentNodes,
|
||||
...chainNodes,
|
||||
{
|
||||
key: AI_OTHERS_NODE_CREATOR_VIEW,
|
||||
type: 'view',
|
||||
@@ -159,6 +177,7 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||
const i18n = useI18n();
|
||||
|
||||
@@ -232,12 +251,20 @@ export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: AI_CATEGORY_TOOLS,
|
||||
type: 'subcategory',
|
||||
key: AI_CATEGORY_TOOLS,
|
||||
category: CORE_NODES_CATEGORY,
|
||||
properties: {
|
||||
title: AI_CATEGORY_TOOLS,
|
||||
icon: 'tools',
|
||||
...getAISubcategoryProperties(NodeConnectionType.AiTool),
|
||||
sections: [
|
||||
{
|
||||
key: 'popular',
|
||||
title: i18n.baseText('nodeCreator.sectionNames.popular'),
|
||||
items: [AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE, AI_CODE_TOOL_LANGCHAIN_NODE_TYPE],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -278,6 +305,18 @@ export function TriggerView() {
|
||||
title: i18n.baseText('nodeCreator.triggerHelperPanel.selectATrigger'),
|
||||
subtitle: i18n.baseText('nodeCreator.triggerHelperPanel.selectATriggerDescription'),
|
||||
items: [
|
||||
{
|
||||
key: MANUAL_TRIGGER_NODE_TYPE,
|
||||
type: 'node',
|
||||
category: [CORE_NODES_CATEGORY],
|
||||
properties: {
|
||||
group: [],
|
||||
name: MANUAL_TRIGGER_NODE_TYPE,
|
||||
displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
|
||||
description: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
|
||||
icon: 'fa:mouse-pointer',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: DEFAULT_SUBCATEGORY,
|
||||
type: 'subcategory',
|
||||
@@ -331,18 +370,6 @@ export function TriggerView() {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: MANUAL_TRIGGER_NODE_TYPE,
|
||||
type: 'node',
|
||||
category: [CORE_NODES_CATEGORY],
|
||||
properties: {
|
||||
group: [],
|
||||
name: MANUAL_TRIGGER_NODE_TYPE,
|
||||
displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
|
||||
description: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
|
||||
icon: 'fa:mouse-pointer',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
|
||||
type: 'node',
|
||||
@@ -355,6 +382,18 @@ export function TriggerView() {
|
||||
icon: 'fa:sign-out-alt',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: MANUAL_CHAT_TRIGGER_NODE_TYPE,
|
||||
type: 'node',
|
||||
category: [CORE_NODES_CATEGORY],
|
||||
properties: {
|
||||
group: [],
|
||||
name: MANUAL_CHAT_TRIGGER_NODE_TYPE,
|
||||
displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualChatTriggerDisplayName'),
|
||||
description: i18n.baseText('nodeCreator.triggerHelperPanel.manualChatTriggerDescription'),
|
||||
icon: 'fa:comments',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'subcategory',
|
||||
key: OTHER_TRIGGER_NODES_SUBCATEGORY,
|
||||
@@ -447,22 +486,6 @@ export function RegularView(nodes: SimplifiedNodeType[]) {
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'subcategory',
|
||||
key: FILES_SUBCATEGORY,
|
||||
category: CORE_NODES_CATEGORY,
|
||||
properties: {
|
||||
title: FILES_SUBCATEGORY,
|
||||
icon: 'file-alt',
|
||||
sections: [
|
||||
{
|
||||
key: 'popular',
|
||||
title: i18n.baseText('nodeCreator.sectionNames.popular'),
|
||||
items: [CONVERT_TO_FILE_NODE_TYPE, EXTRACT_FROM_FILE_NODE_TYPE],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'subcategory',
|
||||
key: HELPERS_SUBCATEGORY,
|
||||
@@ -491,9 +514,13 @@ export function RegularView(nodes: SimplifiedNodeType[]) {
|
||||
title: i18n.baseText('nodeCreator.aiPanel.langchainAiNodes'),
|
||||
icon: 'robot',
|
||||
description: i18n.baseText('nodeCreator.aiPanel.nodesForAi'),
|
||||
tag: i18n.baseText('nodeCreator.aiPanel.newTag'),
|
||||
tag: {
|
||||
type: 'success',
|
||||
text: i18n.baseText('nodeCreator.aiPanel.newTag'),
|
||||
},
|
||||
borderless: true,
|
||||
},
|
||||
});
|
||||
} as NodeViewItem);
|
||||
|
||||
view.items.push({
|
||||
key: TRIGGER_NODE_CREATOR_VIEW,
|
||||
|
||||
Reference in New Issue
Block a user