/** * Provider 注册与聚合搜索。 * 并行调用各 Provider 合并结果、打分排序、应用去重。 */ import type { QPItem, QPProvider } from './types' import { appRankFromPath } from './utils' import { HistoryProvider } from './history' import { CommandProvider } from './command' import { CustomCommandProvider } from './customCommand' import { AppProvider } from './app' import { FileProvider } from './file' import { ClipboardProvider } from './clipboard' import { CalcProvider } from './calc' import { UnitProvider } from './unit' import { SpecialProvider } from './special' import { SystemProvider } from './system' import { WebProvider } from './web' let providers: QPProvider[] | null = null export function getProviders(): QPProvider[] { if (!providers) { providers = [ new HistoryProvider(), new CommandProvider(), new CustomCommandProvider(), new AppProvider(), new FileProvider(), new ClipboardProvider(), new CalcProvider(), new UnitProvider(), new SpecialProvider(), new SystemProvider(), new WebProvider(), ] } return providers } /** * 聚合搜索:并行调用各 Provider,合并结果,按 score 降序排序。 * 空查询时返回 command Provider 的快捷入口 + system Provider 的固定项。 */ export async function aggregateSearch(query: string): Promise { const all = getProviders() const results = await Promise.all(all.map(p => Promise.resolve(p.search(query)))) const merged: QPItem[] = [] results.forEach((items, idx) => { items.forEach(item => { // 未打分的项赋予基础分(按 provider 优先级递减) if (item.score === undefined) { item.score = (10 - idx) * 0.01 } merged.push(item) }) }) // 去重:所有来源的「应用」(含文件索引中的 .lnk)按名称归并,保留可靠性最高的来源 // 可靠性:开始菜单(appRank 0) > 桌面(1) > 其他位置(2);同可靠性时保留分数更高的 // (如 "TRAE Work CN" 在开始菜单 + 桌面 + 某索引目录都有 .lnk,只留开始菜单那条) const appKey = (title: string): string => { let t = title.trim().toLowerCase() if (t.endsWith('.lnk')) t = t.slice(0, -4).trim() return t } // 应用候选:应用分组,以及文件分组中的 .lnk 快捷方式 const isAppLike = (item: QPItem): boolean => { if (item.group === '应用') return true if (item.group === '文件' && item.title && item.title.toLowerCase().endsWith('.lnk')) return true return false } const bestAppByKey = new Map() for (const item of merged) { if (!isAppLike(item) || !item.title) continue const key = appKey(item.title) const prev = bestAppByKey.get(key) if (!prev) { bestAppByKey.set(key, item) continue } // 比较可靠性:appRank 越小越可靠;文件分组 .lnk 无 appRank 时按路径推断 const rankOf = (i: QPItem): number => { if (i.appRank !== undefined) return i.appRank if (i.group === '文件') return appRankFromPath(i.subtitle ?? '') return 2 } const rankA = rankOf(item) const rankB = rankOf(prev) if (rankA < rankB || (rankA === rankB && (item.score ?? 0) > (prev.score ?? 0))) { bestAppByKey.set(key, item) } } const keptAppIds = new Set(Array.from(bestAppByKey.values()).map(i => i.id)) const deduped = merged.filter(item => { if (!isAppLike(item)) return true return keptAppIds.has(item.id) }) deduped.sort((a, b) => (b.score ?? 0) - (a.score ?? 0)) return deduped }