mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-09 23:25:51 +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",
|
"@replit/codemirror-minimap": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@uiw/react-codemirror": "^4.23.13",
|
"@uiw/react-codemirror": "^4.23.13",
|
||||||
|
"@vscode/ripgrep": "^1.17.1",
|
||||||
"@xterm/addon-clipboard": "^0.1.0",
|
"@xterm/addon-clipboard": "^0.1.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.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"
|
"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": {
|
"node_modules/@xterm/addon-clipboard": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@xterm/addon-clipboard/-/addon-clipboard-0.1.0.tgz",
|
||||||
@@ -5629,6 +5642,15 @@
|
|||||||
"ieee754": "^1.1.13"
|
"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": {
|
"node_modules/buffer-equal-constant-time": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
"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"
|
"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": {
|
"node_modules/file-entry-cache": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||||
@@ -13392,6 +13423,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/perfect-debounce": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.0.0.tgz",
|
||||||
@@ -13785,7 +13822,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
@@ -18236,6 +18272,16 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
"@replit/codemirror-minimap": "^0.5.2",
|
"@replit/codemirror-minimap": "^0.5.2",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@uiw/react-codemirror": "^4.23.13",
|
"@uiw/react-codemirror": "^4.23.13",
|
||||||
|
"@vscode/ripgrep": "^1.17.1",
|
||||||
"@xterm/addon-clipboard": "^0.1.0",
|
"@xterm/addon-clipboard": "^0.1.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/addon-web-links": "^0.11.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 [conversationResults, setConversationResults] = useState<ConversationSearchResults | null>(null);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const [searchProgress, setSearchProgress] = useState<SearchProgress | null>(null);
|
const [searchProgress, setSearchProgress] = useState<SearchProgress | null>(null);
|
||||||
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
|
||||||
const [optimisticStarByProjectId, setOptimisticStarByProjectId] = useState<Map<string, boolean>>(new Map());
|
const [optimisticStarByProjectId, setOptimisticStarByProjectId] = useState<Map<string, boolean>>(new Map());
|
||||||
const [loadingMoreProjects, setLoadingMoreProjects] = useState<Set<string>>(new Set());
|
const [loadingMoreProjects, setLoadingMoreProjects] = useState<Set<string>>(new Set());
|
||||||
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
||||||
const searchSeqRef = useRef(0);
|
const searchSeqRef = useRef(0);
|
||||||
const eventSourceRef = useRef<EventSource | null>(null);
|
const eventSourceRef = useRef<EventSource | null>(null);
|
||||||
const starToggleSequenceByProjectRef = useRef<Map<string, number>>(new Map());
|
const starToggleSequenceByProjectRef = useRef<Map<string, number>>(new Map());
|
||||||
@@ -248,17 +248,26 @@ export function useSidebarController({
|
|||||||
});
|
});
|
||||||
}, [projects]);
|
}, [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
|
// Debounced conversation search with SSE streaming
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchTimeoutRef.current) {
|
|
||||||
clearTimeout(searchTimeoutRef.current);
|
|
||||||
}
|
|
||||||
if (eventSourceRef.current) {
|
if (eventSourceRef.current) {
|
||||||
eventSourceRef.current.close();
|
eventSourceRef.current.close();
|
||||||
eventSourceRef.current = null;
|
eventSourceRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = searchFilter.trim();
|
const query = debouncedSearchQuery;
|
||||||
if (searchMode !== 'conversations' || query.length < 2) {
|
if (searchMode !== 'conversations' || query.length < 2) {
|
||||||
searchSeqRef.current += 1;
|
searchSeqRef.current += 1;
|
||||||
setConversationResults(null);
|
setConversationResults(null);
|
||||||
@@ -270,78 +279,75 @@ export function useSidebarController({
|
|||||||
setIsSearching(true);
|
setIsSearching(true);
|
||||||
const seq = ++searchSeqRef.current;
|
const seq = ++searchSeqRef.current;
|
||||||
|
|
||||||
searchTimeoutRef.current = setTimeout(() => {
|
if (seq !== searchSeqRef.current) {
|
||||||
if (seq !== searchSeqRef.current) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const url = api.searchConversationsUrl(query);
|
const url = api.searchConversationsUrl(query);
|
||||||
const es = new EventSource(url);
|
const es = new EventSource(url);
|
||||||
eventSourceRef.current = es;
|
eventSourceRef.current = es;
|
||||||
|
|
||||||
const accumulated: ConversationProjectResult[] = [];
|
const accumulated: ConversationProjectResult[] = [];
|
||||||
let totalMatches = 0;
|
let totalMatches = 0;
|
||||||
|
|
||||||
es.addEventListener('result', (evt) => {
|
es.addEventListener('result', (evt) => {
|
||||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(evt.data) as {
|
const data = JSON.parse(evt.data) as {
|
||||||
projectResult: ConversationProjectResult;
|
projectResult: ConversationProjectResult;
|
||||||
totalMatches: number;
|
totalMatches: number;
|
||||||
scannedProjects: number;
|
scannedProjects: number;
|
||||||
totalProjects: number;
|
totalProjects: number;
|
||||||
};
|
};
|
||||||
accumulated.push(data.projectResult);
|
accumulated.push(data.projectResult);
|
||||||
totalMatches = data.totalMatches;
|
totalMatches = data.totalMatches;
|
||||||
setConversationResults({ results: [...accumulated], totalMatches, query });
|
setConversationResults({ results: [...accumulated], totalMatches, query });
|
||||||
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore malformed SSE data
|
// Ignore malformed SSE data
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
es.addEventListener('progress', (evt) => {
|
es.addEventListener('progress', (evt) => {
|
||||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(evt.data) as { totalMatches: number; scannedProjects: number; totalProjects: number };
|
const data = JSON.parse(evt.data) as { totalMatches: number; scannedProjects: number; totalProjects: number };
|
||||||
totalMatches = data.totalMatches;
|
totalMatches = data.totalMatches;
|
||||||
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
setSearchProgress({ scannedProjects: data.scannedProjects, totalProjects: data.totalProjects });
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore malformed SSE data
|
// Ignore malformed SSE data
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
es.addEventListener('done', () => {
|
es.addEventListener('done', () => {
|
||||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||||
es.close();
|
es.close();
|
||||||
eventSourceRef.current = null;
|
eventSourceRef.current = null;
|
||||||
setIsSearching(false);
|
setIsSearching(false);
|
||||||
setSearchProgress(null);
|
setSearchProgress(null);
|
||||||
if (accumulated.length === 0) {
|
if (accumulated.length === 0) {
|
||||||
setConversationResults({ results: [], totalMatches: 0, query });
|
setConversationResults({ results: [], totalMatches: 0, query });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
es.addEventListener('error', () => {
|
es.addEventListener('error', () => {
|
||||||
if (seq !== searchSeqRef.current) { es.close(); return; }
|
if (seq !== searchSeqRef.current) { es.close(); return; }
|
||||||
es.close();
|
es.close();
|
||||||
eventSourceRef.current = null;
|
eventSourceRef.current = null;
|
||||||
setIsSearching(false);
|
setIsSearching(false);
|
||||||
setSearchProgress(null);
|
setSearchProgress(null);
|
||||||
if (accumulated.length === 0) {
|
if (accumulated.length === 0) {
|
||||||
setConversationResults({ results: [], totalMatches: 0, query });
|
setConversationResults({ results: [], totalMatches: 0, query });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 400);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (searchTimeoutRef.current) {
|
|
||||||
clearTimeout(searchTimeoutRef.current);
|
|
||||||
}
|
|
||||||
if (eventSourceRef.current) {
|
if (eventSourceRef.current) {
|
||||||
eventSourceRef.current.close();
|
eventSourceRef.current.close();
|
||||||
eventSourceRef.current = null;
|
eventSourceRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [searchFilter, searchMode]);
|
}, [debouncedSearchQuery, searchMode]);
|
||||||
|
|
||||||
// All sidebar state keys (expanded, starred, loading, etc.) use the DB
|
// All sidebar state keys (expanded, starred, loading, etc.) use the DB
|
||||||
// `projectId` as their identifier after the migration.
|
// `projectId` as their identifier after the migration.
|
||||||
@@ -493,8 +499,8 @@ export function useSidebarController({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const filteredProjects = useMemo(
|
const filteredProjects = useMemo(
|
||||||
() => filterProjects(sortedProjects, searchFilter),
|
() => filterProjects(sortedProjects, debouncedSearchQuery),
|
||||||
[searchFilter, sortedProjects],
|
[debouncedSearchQuery, sortedProjects],
|
||||||
);
|
);
|
||||||
|
|
||||||
const startEditing = useCallback((project: Project) => {
|
const startEditing = useCallback((project: Project) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user