'use client'; import { useState, useCallback, useRef, useEffect } from 'react'; /** * 搜索结果项类型 */ export interface SearchResult { messageId: string; conversationId: string; conversationTitle: string; role: string; content: string; createdAt: string; } /** * 搜索响应类型 */ interface SearchResponse { success: boolean; data?: { results: SearchResult[]; total: number; page: number; limit: number; totalPages: number; query: string; role: string; }; error?: string; } /** * 搜索状态类型 */ type SearchStatus = 'idle' | 'loading' | 'success' | 'error'; /** * 搜索角色筛选类型 */ export type SearchRole = 'all' | 'user' | 'assistant'; /** * useSearch Hook 返回类型 */ interface UseSearchReturn { // 状态 query: string; results: SearchResult[]; total: number; page: number; totalPages: number; status: SearchStatus; error: string | null; role: SearchRole; // 操作 setQuery: (query: string) => void; setRole: (role: SearchRole) => void; search: (searchQuery?: string) => Promise; loadMore: () => Promise; reset: () => void; } /** * 消息搜索 Hook * * 功能: * - 防抖搜索(300ms) * - 角色筛选 * - 分页加载 * - 结果缓存 * - 状态管理 */ export function useSearch(): UseSearchReturn { // 搜索状态 const [query, setQueryState] = useState(''); const [results, setResults] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(0); const [status, setStatus] = useState('idle'); const [error, setError] = useState(null); const [role, setRoleState] = useState('all'); // 防抖定时器 const debounceTimerRef = useRef(null); // 缓存最后一次搜索的参数,避免重复请求 const lastSearchRef = useRef<{ query: string; role: SearchRole } | null>(null); // AbortController 用于取消请求 const abortControllerRef = useRef(null); /** * 执行搜索请求 */ const doSearch = useCallback(async ( searchQuery: string, searchRole: SearchRole, searchPage: number = 1, append: boolean = false ) => { // 空查询时重置状态 if (!searchQuery.trim()) { setResults([]); setTotal(0); setPage(1); setTotalPages(0); setStatus('idle'); setError(null); return; } // 取消之前的请求 if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); try { setStatus('loading'); setError(null); const params = new URLSearchParams({ q: searchQuery.trim(), role: searchRole, page: searchPage.toString(), limit: '20', }); const response = await fetch(`/api/messages/search?${params}`, { signal: abortControllerRef.current.signal, }); const data: SearchResponse = await response.json(); if (!response.ok || !data.success) { throw new Error(data.error || '搜索失败'); } // 更新状态 if (append && data.data) { setResults(prev => [...prev, ...data.data!.results]); } else if (data.data) { setResults(data.data.results); } if (data.data) { setTotal(data.data.total); setPage(data.data.page); setTotalPages(data.data.totalPages); } setStatus('success'); // 更新缓存 lastSearchRef.current = { query: searchQuery, role: searchRole }; } catch (err) { // 忽略取消的请求 if (err instanceof Error && err.name === 'AbortError') { return; } setStatus('error'); setError(err instanceof Error ? err.message : '搜索失败'); } }, []); /** * 设置搜索关键词(带防抖) */ const setQuery = useCallback((newQuery: string) => { setQueryState(newQuery); // 清除之前的定时器 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // 空查询立即清空结果 if (!newQuery.trim()) { setResults([]); setTotal(0); setPage(1); setTotalPages(0); setStatus('idle'); setError(null); return; } // 设置新的防抖定时器 debounceTimerRef.current = setTimeout(() => { doSearch(newQuery, role, 1, false); }, 300); }, [role, doSearch]); /** * 设置角色筛选 */ const setRole = useCallback((newRole: SearchRole) => { setRoleState(newRole); // 如果有搜索关键词,立即重新搜索 if (query.trim()) { // 清除防抖定时器 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } doSearch(query, newRole, 1, false); } }, [query, doSearch]); /** * 手动触发搜索 */ const search = useCallback(async (searchQuery?: string) => { const q = searchQuery ?? query; if (q.trim()) { // 清除防抖定时器 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } await doSearch(q, role, 1, false); } }, [query, role, doSearch]); /** * 加载更多结果 */ const loadMore = useCallback(async () => { if (status === 'loading' || page >= totalPages) { return; } await doSearch(query, role, page + 1, true); }, [query, role, page, totalPages, status, doSearch]); /** * 重置搜索状态 */ const reset = useCallback(() => { // 清除防抖定时器 if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // 取消进行中的请求 if (abortControllerRef.current) { abortControllerRef.current.abort(); } setQueryState(''); setResults([]); setTotal(0); setPage(1); setTotalPages(0); setStatus('idle'); setError(null); setRoleState('all'); lastSearchRef.current = null; }, []); // 清理定时器和请求 useEffect(() => { return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, []); return { query, results, total, page, totalPages, status, error, role, setQuery, setRole, search, loadMore, reset, }; }