import { forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react"; import { ChatMessage, useChatStore } from "../store"; import styles from "./home.module.scss"; import SearchIcon from "../icons/search.svg"; import { IconButton } from "./button"; import CloseIcon from "../icons/close.svg"; import { Markdown } from "./markdown"; import { useNavigate } from "react-router-dom"; import { Path } from "@/app/constant"; import Locale from "../locales"; import { getMessageTextContent } from "../utils"; interface SearchResult { sessionId: string; topic: string; lastUpdate: number; message: ChatMessage[]; } interface SearchBarProps { setIsSearching: (isSearching: boolean) => void; className?: string; } export interface SearchInputRef { setInput: (value: string) => void; clearInput: () => void; inputElement: HTMLInputElement | null; } function highlightAndShorten(str: string, search: string) { const index = str.toLowerCase().indexOf(search.toLowerCase()); const head = Math.max(0, index - 10); const tail = Math.min(str.length, index + search.length + 40); // Remove code block syntax let result = str.slice(head, tail); // Use ** to highlight the search result result = result.replace(new RegExp(`(${search})`), "**$1**"); if (head > 0) { result = "..." + result; } if (tail < str.length) { result = result + "..."; } return result; } function HighlightedMessage({ message, search, }: { message: ChatMessage; search: string; }) { const highlightedMessage = useMemo( () => highlightAndShorten(getMessageTextContent(message), search), [getMessageTextContent(message), search], ); const ref = useRef(null); return (
); } function SearchResultItem({ result, input, selectSession, index, }: { result: SearchResult; input: string; selectSession: (id: number) => void; index: number; }) { const navigate = useNavigate(); return (
{ navigate(Path.Chat); selectSession(index); }} >
{result.topic}
{result.message.map((message) => ( ))}
{result.message.length} messages found
{new Date(result.lastUpdate).toLocaleString()}
); } function SearchBarComponent( { setIsSearching, className }: SearchBarProps, ref: Ref, ) { const [sessions, selectSession] = useChatStore((state) => [ state.sessions, state.selectSession, ]); const [input, setInput] = useState(""); const [results, setResults] = useState([]); const inputRef = useRef(null); useImperativeHandle(ref, () => ({ setInput, clearInput: handleClearInput, inputElement: inputRef.current, })); const handleClearInput = useCallback(() => { setInput(""); setResults([]); setIsSearching(false); }, [setIsSearching]); const handleChange = useCallback( (value: string) => { setIsSearching(true); setInput(value); }, [setIsSearching], ); const handleFocus = useCallback(() => { if (input && input.trim().length > 0) setIsSearching(true); }, [setIsSearching]); const handleBlur = useCallback(() => { if ( (inputRef as React.RefObject).current && (inputRef as React.RefObject)?.current?.value.trim() === "" ) { setIsSearching(false); } }, [setIsSearching]); // 当用户输入变化时,执行搜索操作 useEffect(() => { if (input.trim().length === 0) { setResults([]); setIsSearching(false); return; } const newResults: SearchResult[] = []; for (const session of sessions) { const matchingMessages: ChatMessage[] = []; for (const message of session.messages) { if ( getMessageTextContent(message) .toLowerCase() .includes(input.toLowerCase()) ) { matchingMessages.push(message!); } } if (matchingMessages.length > 0) { newResults.push({ topic: session.topic, sessionId: session.id, lastUpdate: session.lastUpdate, message: matchingMessages, }); } } setResults(newResults); }, [input, sessions]); const displayedResults = useMemo(() => results, [results]); return ( <>
handleChange(e.target.value)} onFocus={handleFocus} onBlur={handleBlur} placeholder={Locale.Home.Search} /> {input.trim().length > 0 && ( } onClick={handleClearInput} /> )}
{input.trim().length > 0 && (
{displayedResults.length} chats found
)}
{displayedResults.map((result) => ( session.id === result.sessionId, )} /> ))}
); } export const SearchBar = forwardRef( SearchBarComponent, );