| 27 | } |
| 28 | |
| 29 | export const useMessageSearch = (messages: ChatMessage[]): UseMessageSearchResult => { |
| 30 | const [searchQuery, setSearchQuery] = useState('') |
| 31 | const [currentMatchIndex, setCurrentMatchIndex] = useState(0) |
| 32 | const [isSearchVisible, setIsSearchVisible] = useState(false) |
| 33 | |
| 34 | // 计算所有匹配项 |
| 35 | const matches = useMemo(() => { |
| 36 | if (!searchQuery.trim()) return [] |
| 37 | |
| 38 | const allMatches: SearchMatch[] = [] |
| 39 | const query = searchQuery.toLowerCase() |
| 40 | let globalIndex = 0 |
| 41 | |
| 42 | // 按消息顺序遍历,保持匹配结果的顺序 |
| 43 | messages.forEach((message, messageIndex) => { |
| 44 | const content = message.content.toLowerCase() |
| 45 | let startIndex = 0 |
| 46 | let matchIndexInMessage = 0 |
| 47 | |
| 48 | while (true) { |
| 49 | const index = content.indexOf(query, startIndex) |
| 50 | if (index === -1) break |
| 51 | |
| 52 | allMatches.push({ |
| 53 | messageId: message.id, |
| 54 | matchIndex: matchIndexInMessage, // 在消息内的索引 |
| 55 | startIndex: index, |
| 56 | endIndex: index + query.length, |
| 57 | content: message.content.slice(index, index + query.length), |
| 58 | globalIndex: globalIndex++ // 全局唯一索引 |
| 59 | }) |
| 60 | |
| 61 | matchIndexInMessage++ |
| 62 | startIndex = index + 1 |
| 63 | } |
| 64 | }) |
| 65 | |
| 66 | return allMatches |
| 67 | }, [searchQuery, messages]) |
| 68 | |
| 69 | const totalMatches = matches.length |
| 70 | |
| 71 | // 显示搜索框 |
| 72 | const showSearch = useCallback(() => { |
| 73 | setIsSearchVisible(true) |
| 74 | }, []) |
| 75 | |
| 76 | // 隐藏搜索框 |
| 77 | const hideSearch = useCallback(() => { |
| 78 | setIsSearchVisible(false) |
| 79 | setSearchQuery('') |
| 80 | setCurrentMatchIndex(0) |
| 81 | }, []) |
| 82 | |
| 83 | // 搜索函数 |
| 84 | const search = useCallback( |
| 85 | (query: string, currentIndex: number, direction: 'next' | 'previous') => { |
| 86 | const isNewQuery = query !== searchQuery |