| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- import { useState, useEffect, useRef, useCallback } from "react";
- import { ErrorBoundary } from "./error";
- import styles from "./mask.module.scss";
- import { useNavigate } from "react-router-dom";
- import { IconButton } from "./button";
- import CloseIcon from "../icons/close.svg";
- import EyeIcon from "../icons/eye.svg";
- import Locale from "../locales";
- import { Path } from "../constant";
- import { useChatStore } from "../store";
- type Item = {
- id: number;
- name: string;
- content: string;
- };
- export function SearchChatPage() {
- const navigate = useNavigate();
- const chatStore = useChatStore();
- const sessions = chatStore.sessions;
- const selectSession = chatStore.selectSession;
- const [searchResults, setSearchResults] = useState<Item[]>([]);
- const previousValueRef = useRef<string>("");
- const searchInputRef = useRef<HTMLInputElement>(null);
- const doSearch = useCallback((text: string) => {
- const lowerCaseText = text.toLowerCase();
- const results: Item[] = [];
- sessions.forEach((session, index) => {
- const fullTextContents: string[] = [];
- session.messages.forEach((message) => {
- const content = message.content as string;
- if (!content.toLowerCase || content === "") return;
- const lowerCaseContent = content.toLowerCase();
- // full text search
- let pos = lowerCaseContent.indexOf(lowerCaseText);
- while (pos !== -1) {
- const start = Math.max(0, pos - 35);
- const end = Math.min(content.length, pos + lowerCaseText.length + 35);
- fullTextContents.push(content.substring(start, end));
- pos = lowerCaseContent.indexOf(
- lowerCaseText,
- pos + lowerCaseText.length,
- );
- }
- });
- if (fullTextContents.length > 0) {
- results.push({
- id: index,
- name: session.topic,
- content: fullTextContents.join("... "), // concat content with...
- });
- }
- });
- // sort by length of matching content
- results.sort((a, b) => b.content.length - a.content.length);
- return results;
- }, []);
- useEffect(() => {
- const intervalId = setInterval(() => {
- if (searchInputRef.current) {
- const currentValue = searchInputRef.current.value;
- if (currentValue !== previousValueRef.current) {
- if (currentValue.length > 0) {
- const result = doSearch(currentValue);
- setSearchResults(result);
- }
- previousValueRef.current = currentValue;
- }
- }
- }, 1000);
- // Cleanup the interval on component unmount
- return () => clearInterval(intervalId);
- }, [doSearch]);
- return (
- <ErrorBoundary>
- <div className={styles["mask-page"]}>
- {/* header */}
- <div className="window-header">
- <div className="window-header-title">
- <div className="window-header-main-title">
- {Locale.SearchChat.Page.Title}
- </div>
- <div className="window-header-submai-title">
- {Locale.SearchChat.Page.SubTitle(searchResults.length)}
- </div>
- </div>
- <div className="window-actions">
- <div className="window-action-button">
- <IconButton
- icon={<CloseIcon />}
- bordered
- onClick={() => navigate(-1)}
- />
- </div>
- </div>
- </div>
- <div className={styles["mask-page-body"]}>
- <div className={styles["mask-filter"]}>
- {/**搜索输入框 */}
- <input
- type="text"
- className={styles["search-bar"]}
- placeholder={Locale.SearchChat.Page.Search}
- autoFocus
- ref={searchInputRef}
- onKeyDown={(e) => {
- if (e.key === "Enter") {
- e.preventDefault();
- const searchText = e.currentTarget.value;
- if (searchText.length > 0) {
- const result = doSearch(searchText);
- setSearchResults(result);
- }
- }
- }}
- />
- </div>
- <div>
- {searchResults.map((item) => (
- <div
- className={styles["mask-item"]}
- key={item.id}
- onClick={() => {
- navigate(Path.Chat);
- selectSession(item.id);
- }}
- style={{ cursor: "pointer" }}
- >
- {/** 搜索匹配的文本 */}
- <div className={styles["mask-header"]}>
- <div className={styles["mask-title"]}>
- <div className={styles["mask-name"]}>{item.name}</div>
- {item.content.slice(0, 70)}
- </div>
- </div>
- {/** 操作按钮 */}
- <div className={styles["mask-actions"]}>
- <IconButton
- icon={<EyeIcon />}
- text={Locale.SearchChat.Item.View}
- />
- </div>
- </div>
- ))}
- </div>
- </div>
- </div>
- </ErrorBoundary>
- );
- }
|