94 lines
2.6 KiB
JavaScript
94 lines
2.6 KiB
JavaScript
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
// MiniSearch Index Manager
|
|||
|
|
// 全文搜索索引管理
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
import MiniSearch from '../../../libs/minisearch.mjs';
|
|||
|
|
|
|||
|
|
// 索引缓存:chatId -> { index, updatedAt }
|
|||
|
|
const indexCache = new Map();
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取或创建搜索索引
|
|||
|
|
* @param {string} chatId
|
|||
|
|
* @param {Array} events - L2 事件
|
|||
|
|
* @param {number} storeUpdatedAt - store.updatedAt 时间戳
|
|||
|
|
* @returns {MiniSearch}
|
|||
|
|
*/
|
|||
|
|
export function getSearchIndex(chatId, events, storeUpdatedAt) {
|
|||
|
|
const cached = indexCache.get(chatId);
|
|||
|
|
|
|||
|
|
// 缓存有效
|
|||
|
|
if (cached && cached.updatedAt >= storeUpdatedAt) {
|
|||
|
|
return cached.index;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重建索引
|
|||
|
|
const index = new MiniSearch({
|
|||
|
|
fields: ['title', 'summary', 'participants'],
|
|||
|
|
storeFields: ['id'],
|
|||
|
|
searchOptions: {
|
|||
|
|
boost: { title: 2, participants: 1.5, summary: 1 },
|
|||
|
|
fuzzy: 0.2,
|
|||
|
|
prefix: true,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 索引事件
|
|||
|
|
const docs = events.map(e => ({
|
|||
|
|
id: e.id,
|
|||
|
|
title: e.title || '',
|
|||
|
|
summary: e.summary || '',
|
|||
|
|
participants: (e.participants || []).join(' '),
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
index.addAll(docs);
|
|||
|
|
|
|||
|
|
// 缓存
|
|||
|
|
indexCache.set(chatId, { index, updatedAt: storeUpdatedAt });
|
|||
|
|
|
|||
|
|
// 限制缓存数量
|
|||
|
|
if (indexCache.size > 5) {
|
|||
|
|
const firstKey = indexCache.keys().next().value;
|
|||
|
|
indexCache.delete(firstKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return index;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 关键词搜索
|
|||
|
|
* @returns {Map<string, number>} - eventId -> 归一化分数
|
|||
|
|
*/
|
|||
|
|
export function searchByKeywords(index, queryText, limit = 20) {
|
|||
|
|
if (!queryText?.trim()) return new Map();
|
|||
|
|
|
|||
|
|
const results = index.search(queryText, { limit });
|
|||
|
|
|
|||
|
|
if (results.length === 0) return new Map();
|
|||
|
|
|
|||
|
|
// 归一化分数到 0-1
|
|||
|
|
const maxScore = results[0].score;
|
|||
|
|
const scores = new Map();
|
|||
|
|
|
|||
|
|
for (const r of results) {
|
|||
|
|
scores.set(r.id, r.score / maxScore);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return scores;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除指定聊天的索引缓存
|
|||
|
|
*/
|
|||
|
|
export function invalidateIndex(chatId) {
|
|||
|
|
indexCache.delete(chatId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除所有索引缓存
|
|||
|
|
*/
|
|||
|
|
export function clearAllIndexes() {
|
|||
|
|
indexCache.clear();
|
|||
|
|
}
|