mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-15 03:01:58 +08:00
fix: search performance
This commit is contained in:
48
package-lock.json
generated
48
package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"@replit/codemirror-minimap": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@uiw/react-codemirror": "^4.23.13",
|
||||
"@vscode/ripgrep": "^1.17.1",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
@@ -4797,6 +4798,18 @@
|
||||
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vscode/ripgrep": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.17.1.tgz",
|
||||
"integrity": "sha512-xTs7DGyAO3IsJYOCTBP8LnTvPiYVKEuyv8s0xyJDBXfs8rhBfqnZPvb6xDT+RnwWzcXqW27xLS/aGrkjX7lNWw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"yauzl": "^2.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@xterm/addon-clipboard": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.1.0.tgz",
|
||||
@@ -5629,6 +5642,15 @@
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-crc32": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
@@ -8288,6 +8310,15 @@
|
||||
"walk-up-path": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fd-slicer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@@ -13392,6 +13423,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/perfect-debounce": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz",
|
||||
@@ -13785,7 +13822,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pump": {
|
||||
@@ -18236,6 +18272,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yauzl": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
|
||||
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"@replit/codemirror-minimap": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@uiw/react-codemirror": "^4.23.13",
|
||||
"@vscode/ripgrep": "^1.17.1",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -114,9 +114,9 @@ export function useSidebarController({
|
||||
const [conversationResults, setConversationResults] = useState<ConversationSearchResults | null>(null);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [searchProgress, setSearchProgress] = useState<SearchProgress | null>(null);
|
||||
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
|
||||
const [optimisticStarByProjectId, setOptimisticStarByProjectId] = useState<Map<string, boolean>>(new Map());
|
||||
const [loadingMoreProjects, setLoadingMoreProjects] = useState<Set<string>>(new Set());
|
||||
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const searchSeqRef = useRef(0);
|
||||
const eventSourceRef = useRef<EventSource | null>(null);
|
||||
const starToggleSequenceByProjectRef = useRef<Map<string, number>>(new Map());
|
||||
@@ -248,17 +248,26 @@ export function useSidebarController({
|
||||
});
|
||||
}, [projects]);
|
||||
|
||||
// Debounce search text updates so both project filtering and conversation
|
||||
// SSE requests avoid running on every keypress.
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setDebouncedSearchQuery(searchFilter.trim());
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [searchFilter]);
|
||||
|
||||
// Debounced conversation search with SSE streaming
|
||||
useEffect(() => {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
}
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
eventSourceRef.current = null;
|
||||
}
|
||||
|
||||
const query = searchFilter.trim();
|
||||
const query = debouncedSearchQuery;
|
||||
if (searchMode !== 'conversations' || query.length < 2) {
|
||||
searchSeqRef.current += 1;
|
||||
setConversationResults(null);
|
||||
@@ -270,78 +279,75 @@ export function useSidebarController({
|
||||
setIsSearching(true);
|
||||
const seq = ++searchSeqRef.current;
|
||||
|
||||
searchTimeoutRef.current = setTimeout(() => {
|
||||
if (seq !== searchSeqRef.current) return;
|
||||
if (seq !== searchSeqRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = api.searchConversationsUrl(query);
|
||||
const es = new EventSource(url);
|
||||
eventSourceRef.current = es;
|
||||
const url = api.searchConversationsUrl(query);
|
||||
const es = new EventSource(url);
|
||||
eventSourceRef.current = es;
|
||||
|
||||
const accumulated: ConversationProjectResult[] = [];
|
||||
let totalMatches = 0;
|
||||
const accumulated: ConversationProjectResult[] = [];
|
||||
let totalMatches = 0;
|
||||
|
||||
es.addEventListener('result', (evt) => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
try {
|
||||
const data = JSON.parse(evt.data) as {
|
||||
projectResult: ConversationProjectResult;
|
||||
totalMatches: number;
|
||||
scannedProjects: number;
|
||||
totalProjects: number;
|
||||
};
|
||||
accumulated.push(data.projectResult);
|
||||
totalMatches = data.totalMatches;
|
||||
setConversationResults({ results: [...accumulated], totalMatches, query });
|
||||
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
||||
} catch {
|
||||
// Ignore malformed SSE data
|
||||
}
|
||||
});
|
||||
es.addEventListener('result', (evt) => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
try {
|
||||
const data = JSON.parse(evt.data) as {
|
||||
projectResult: ConversationProjectResult;
|
||||
totalMatches: number;
|
||||
scannedProjects: number;
|
||||
totalProjects: number;
|
||||
};
|
||||
accumulated.push(data.projectResult);
|
||||
totalMatches = data.totalMatches;
|
||||
setConversationResults({ results: [...accumulated], totalMatches, query });
|
||||
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
||||
} catch {
|
||||
// Ignore malformed SSE data
|
||||
}
|
||||
});
|
||||
|
||||
es.addEventListener('progress', (evt) => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
try {
|
||||
const data = JSON.parse(evt.data) as { totalMatches: number; scannedProjects: number; totalProjects: number };
|
||||
totalMatches = data.totalMatches;
|
||||
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
||||
} catch {
|
||||
// Ignore malformed SSE data
|
||||
}
|
||||
});
|
||||
es.addEventListener('progress', (evt) => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
try {
|
||||
const data = JSON.parse(evt.data) as { totalMatches: number; scannedProjects: number; totalProjects: number };
|
||||
totalMatches = data.totalMatches;
|
||||
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
||||
} catch {
|
||||
// Ignore malformed SSE data
|
||||
}
|
||||
});
|
||||
|
||||
es.addEventListener('done', () => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
es.close();
|
||||
eventSourceRef.current = null;
|
||||
setIsSearching(false);
|
||||
setSearchProgress(null);
|
||||
if (accumulated.length === 0) {
|
||||
setConversationResults({ results: [], totalMatches: 0, query });
|
||||
}
|
||||
});
|
||||
es.addEventListener('done', () => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
es.close();
|
||||
eventSourceRef.current = null;
|
||||
setIsSearching(false);
|
||||
setSearchProgress(null);
|
||||
if (accumulated.length === 0) {
|
||||
setConversationResults({ results: [], totalMatches: 0, query });
|
||||
}
|
||||
});
|
||||
|
||||
es.addEventListener('error', () => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
es.close();
|
||||
eventSourceRef.current = null;
|
||||
setIsSearching(false);
|
||||
setSearchProgress(null);
|
||||
if (accumulated.length === 0) {
|
||||
setConversationResults({ results: [], totalMatches: 0, query });
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
es.addEventListener('error', () => {
|
||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||
es.close();
|
||||
eventSourceRef.current = null;
|
||||
setIsSearching(false);
|
||||
setSearchProgress(null);
|
||||
if (accumulated.length === 0) {
|
||||
setConversationResults({ results: [], totalMatches: 0, query });
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (searchTimeoutRef.current) {
|
||||
clearTimeout(searchTimeoutRef.current);
|
||||
}
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
eventSourceRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [searchFilter, searchMode]);
|
||||
}, [debouncedSearchQuery, searchMode]);
|
||||
|
||||
// All sidebar state keys (expanded, starred, loading, etc.) use the DB
|
||||
// `projectId` as their identifier after the migration.
|
||||
@@ -493,8 +499,8 @@ export function useSidebarController({
|
||||
);
|
||||
|
||||
const filteredProjects = useMemo(
|
||||
() => filterProjects(sortedProjects, searchFilter),
|
||||
[searchFilter, sortedProjects],
|
||||
() => filterProjects(sortedProjects, debouncedSearchQuery),
|
||||
[debouncedSearchQuery, sortedProjects],
|
||||
);
|
||||
|
||||
const startEditing = useCallback((project: Project) => {
|
||||
|
||||
Reference in New Issue
Block a user