mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-28 01:09:17 +08:00
⚡️ perf(frontend): 优化长时运行下的搜索与缓存占用
- 为 V2 cmd+k 搜索预建索引并限制初始/宽泛结果数量 - 清理冷数据库树和 DataViewer 长生命周期快照缓存 - 收紧运行时 SQL 日志预算并在 hydration 时压缩旧缓存
This commit is contained in:
@@ -415,6 +415,13 @@ export type V2CommandSearchItem =
|
||||
dbName?: string;
|
||||
};
|
||||
|
||||
export interface V2CommandSearchTreeIndexEntry {
|
||||
item: Extract<V2CommandSearchItem, { kind: 'node' }>;
|
||||
normalizedSearchText: string;
|
||||
normalizedObjectText: string;
|
||||
objectNode: boolean;
|
||||
}
|
||||
|
||||
export type V2CommandSearchMode = 'default' | 'object' | 'ai';
|
||||
|
||||
export interface V2CommandSearchQuery {
|
||||
@@ -467,40 +474,69 @@ const isV2CommandSearchObjectNode = (node: SidebarTreeNode): boolean => {
|
||||
|| node.type === 'materialized-view';
|
||||
};
|
||||
|
||||
const V2_COMMAND_SEARCH_INITIAL_TREE_LIMIT = 24;
|
||||
export const V2_COMMAND_SEARCH_INITIAL_TREE_LIMIT = 24;
|
||||
export const V2_COMMAND_SEARCH_MAX_TREE_RESULTS = 120;
|
||||
|
||||
export const buildV2CommandSearchTreeIndex = (
|
||||
items: V2CommandSearchItem[],
|
||||
): V2CommandSearchTreeIndexEntry[] => {
|
||||
return items.flatMap((item) => {
|
||||
if (item.kind !== 'node') {
|
||||
return [];
|
||||
}
|
||||
const dataRef = item.node.dataRef || {};
|
||||
const normalizedTitle = String(item.title || '').toLowerCase();
|
||||
const normalizedPrimaryObjectText = String(
|
||||
dataRef.tableName || dataRef.viewName || item.title || '',
|
||||
).toLowerCase();
|
||||
|
||||
return [{
|
||||
item,
|
||||
normalizedSearchText: [
|
||||
item.title,
|
||||
item.meta,
|
||||
dataRef.tableName,
|
||||
dataRef.viewName,
|
||||
dataRef.dbName,
|
||||
dataRef.name,
|
||||
dataRef.config?.host,
|
||||
].filter(Boolean).join(' ').toLowerCase(),
|
||||
normalizedObjectText: `${normalizedPrimaryObjectText} ${normalizedTitle}`.trim(),
|
||||
objectNode: isV2CommandSearchObjectNode(item.node),
|
||||
}];
|
||||
});
|
||||
};
|
||||
|
||||
export const filterV2CommandSearchTreeItems = (
|
||||
items: V2CommandSearchItem[],
|
||||
items: V2CommandSearchItem[] | V2CommandSearchTreeIndexEntry[],
|
||||
query: V2CommandSearchQuery,
|
||||
): V2CommandSearchItem[] => {
|
||||
if (query.mode === 'ai') return [];
|
||||
const index = items.length > 0 && 'item' in items[0]
|
||||
? items as V2CommandSearchTreeIndexEntry[]
|
||||
: buildV2CommandSearchTreeIndex(items as V2CommandSearchItem[]);
|
||||
const normalizedKeyword = query.normalizedKeyword;
|
||||
const objectMode = query.mode === 'object';
|
||||
const matchedItems = items.filter((item) => {
|
||||
if (item.kind !== 'node') return false;
|
||||
const node = item.node;
|
||||
const dataRef = node.dataRef || {};
|
||||
if (objectMode && !isV2CommandSearchObjectNode(node)) {
|
||||
return false;
|
||||
const result: V2CommandSearchItem[] = [];
|
||||
const maxResults = normalizedKeyword
|
||||
? V2_COMMAND_SEARCH_MAX_TREE_RESULTS
|
||||
: V2_COMMAND_SEARCH_INITIAL_TREE_LIMIT;
|
||||
|
||||
for (const entry of index) {
|
||||
if (objectMode && !entry.objectNode) {
|
||||
continue;
|
||||
}
|
||||
if (!normalizedKeyword) return true;
|
||||
const objectName = String(dataRef.tableName || dataRef.viewName || item.title || '').toLowerCase();
|
||||
if (objectMode) {
|
||||
return objectName.includes(normalizedKeyword)
|
||||
|| String(item.title || '').toLowerCase().includes(normalizedKeyword);
|
||||
if (!normalizedKeyword) {
|
||||
result.push(entry.item);
|
||||
} else if (objectMode ? entry.normalizedObjectText.includes(normalizedKeyword) : entry.normalizedSearchText.includes(normalizedKeyword)) {
|
||||
result.push(entry.item);
|
||||
}
|
||||
const haystack = [
|
||||
item.title,
|
||||
item.meta,
|
||||
dataRef.tableName,
|
||||
dataRef.viewName,
|
||||
dataRef.dbName,
|
||||
dataRef.name,
|
||||
dataRef.config?.host,
|
||||
].filter(Boolean).join(' ').toLowerCase();
|
||||
return haystack.includes(normalizedKeyword);
|
||||
});
|
||||
return normalizedKeyword ? matchedItems : matchedItems.slice(0, V2_COMMAND_SEARCH_INITIAL_TREE_LIMIT);
|
||||
if (result.length >= maxResults) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export interface V2CommandSearchEnterState {
|
||||
@@ -765,4 +801,63 @@ export const resolveV2ActiveConnectionId = ({
|
||||
|| '';
|
||||
};
|
||||
|
||||
export const resolveSidebarDatabaseTreePruneKeys = ({
|
||||
treeData,
|
||||
expandedKeys,
|
||||
selectedKeys,
|
||||
activeDatabaseKey,
|
||||
touchedAtByDatabaseKey,
|
||||
maxLoadedDatabases,
|
||||
}: {
|
||||
treeData: SidebarTreeNode[];
|
||||
expandedKeys: React.Key[];
|
||||
selectedKeys: React.Key[];
|
||||
activeDatabaseKey?: string;
|
||||
touchedAtByDatabaseKey?: Record<string, number>;
|
||||
maxLoadedDatabases: number;
|
||||
}): string[] => {
|
||||
if (!Number.isFinite(maxLoadedDatabases) || maxLoadedDatabases <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const loadedDatabaseKeys: string[] = [];
|
||||
const visit = (nodes: SidebarTreeNode[]) => {
|
||||
nodes.forEach((node) => {
|
||||
if (node.type === 'database' && Array.isArray(node.children) && node.children.length > 0) {
|
||||
loadedDatabaseKeys.push(String(node.key || '').trim());
|
||||
return;
|
||||
}
|
||||
if (node.children?.length) {
|
||||
visit(node.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
visit(treeData);
|
||||
|
||||
if (loadedDatabaseKeys.length <= maxLoadedDatabases) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const expandedKeySet = new Set(expandedKeys.map((key) => String(key || '').trim()).filter(Boolean));
|
||||
const selectedKeySet = new Set(selectedKeys.map((key) => String(key || '').trim()).filter(Boolean));
|
||||
const protectedDatabaseKeys = new Set<string>();
|
||||
if (activeDatabaseKey) {
|
||||
protectedDatabaseKeys.add(String(activeDatabaseKey).trim());
|
||||
}
|
||||
|
||||
const candidates = loadedDatabaseKeys
|
||||
.filter((key) => key && !expandedKeySet.has(key) && !selectedKeySet.has(key) && !protectedDatabaseKeys.has(key))
|
||||
.sort((left, right) => {
|
||||
const leftTouchedAt = Number(touchedAtByDatabaseKey?.[left] || 0);
|
||||
const rightTouchedAt = Number(touchedAtByDatabaseKey?.[right] || 0);
|
||||
if (leftTouchedAt !== rightTouchedAt) {
|
||||
return leftTouchedAt - rightTouchedAt;
|
||||
}
|
||||
return left.localeCompare(right);
|
||||
});
|
||||
|
||||
const pruneCount = loadedDatabaseKeys.length - maxLoadedDatabases;
|
||||
return candidates.slice(0, pruneCount);
|
||||
};
|
||||
|
||||
export const shouldClearSidebarActiveContextOnEmptySelect = (isV2Ui: boolean): boolean => !isV2Ui;
|
||||
|
||||
Reference in New Issue
Block a user