feat(editor): Add node popularity scores to improve search ranking (#19561)

This commit is contained in:
Eugene
2025-09-17 10:25:55 +03:00
committed by GitHub
parent 69c81a6437
commit ae1af1101b
11 changed files with 332 additions and 9 deletions

View File

@@ -61,6 +61,8 @@ import { useUIStore } from '@/stores/ui.store';
import { type NodeIconSource } from '@/utils/nodeIcon';
import { getThemedValue } from '@/utils/nodeTypesUtils';
import nodePopularity from 'virtual:node-popularity-data';
export interface ViewStack {
uuid?: string;
title?: string;
@@ -88,6 +90,13 @@ export interface ViewStack {
communityNodeDetails?: CommunityNodeDetails;
}
const nodePopularityMap = Object.values(nodePopularity).reduce((acc, node) => {
return {
...acc,
[node.id]: node.popularity * 100, // Scale the popularity score
};
}, {});
export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
const nodeCreatorStore = useNodeCreatorStore();
const { getActiveItemIndex } = useKeyboardNavigation();
@@ -121,7 +130,11 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
searchBase = filterOutAiNodes(searchBase);
}
const searchResults = extendItemsWithUUID(searchNodes(stack.search || '', searchBase));
const searchResults = extendItemsWithUUID(
searchNodes(stack.search || '', searchBase, {
popularity: nodePopularityMap,
}),
);
const groupedNodes = groupIfAiNodes(searchResults, stack.title, false) ?? searchResults;
// Set the active index to the second item if there's a section
@@ -181,8 +194,11 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
const filteredNodes = isAiRootView(stack) ? allNodes : filterOutAiNodes(allNodes);
let globalSearchResult: INodeCreateElement[] = extendItemsWithUUID(
searchNodes(stack.search || '', filteredNodes),
searchNodes(stack.search || '', filteredNodes, {
popularity: nodePopularityMap,
}),
);
if (isAiRootView(stack)) {
globalSearchResult = groupIfAiNodes(globalSearchResult, stack.title, false);
}

View File

@@ -29,6 +29,7 @@ import {
import { v4 as uuidv4 } from 'uuid';
import { sublimeSearch } from '@n8n/utils/search/sublimeSearch';
import { reRankSearchResults } from '@n8n/utils/search/reRankSearchResults';
import type { NodeViewItemSection } from './viewsData';
import { i18n } from '@n8n/i18n';
import sortBy from 'lodash/sortBy';
@@ -122,7 +123,11 @@ export function removeTrailingTrigger(searchFilter: string) {
return searchFilter;
}
export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
export function searchNodes(
searchFilter: string,
items: INodeCreateElement[],
additionalFactors = {},
) {
const askAiEnabled = useSettingsStore().isAskAiEnabled;
if (!askAiEnabled) {
items = items.filter((item) => item.key !== AI_TRANSFORM_NODE_TYPE);
@@ -131,12 +136,12 @@ export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
const trimmedFilter = removeTrailingTrigger(searchFilter).toLowerCase();
// We have a snapshot of this call in sublimeSearch.test.ts to assert practical order for some cases
// Please update the snapshots per the README next to the the snapshots if you modify items significantly.
const result = (sublimeSearch<INodeCreateElement>(trimmedFilter, items) || []).map(
({ item }) => item,
);
// Please update the snapshots per the README next to the snapshots if you modify items significantly.
const searchResults = sublimeSearch<INodeCreateElement>(trimmedFilter, items) || [];
return result;
const reRankedResults = reRankSearchResults(searchResults, additionalFactors);
return reRankedResults.map(({ item }) => item);
}
export function flattenCreateElements(items: INodeCreateElement[]): INodeCreateElement[] {

View File

@@ -0,0 +1,9 @@
/// <reference types="vite/client" />
declare module 'virtual:node-popularity-data' {
const data: Array<{
id: string;
popularity: number;
}>;
export default data;
}