mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-04 20:05:38 +08:00
fix: search performance
This commit is contained in:
@@ -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