178 lines
5.3 KiB
TypeScript
178 lines
5.3 KiB
TypeScript
import { useState, useEffect, useRef } 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 setDefaultItems = () => {
|
|
// setSearchResults(
|
|
// sessions.slice(1, 7).map((session, index) => {
|
|
// return {
|
|
// id: index,
|
|
// name: session.topic,
|
|
// content: session.messages[0].content as string, //.map((m) => m.content).join("\n")
|
|
// };
|
|
// }),
|
|
// );
|
|
// };
|
|
// useEffect(() => {
|
|
// setDefaultItems();
|
|
// }, []);
|
|
|
|
const previousValueRef = useRef<string>("");
|
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
const doSearch = (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;
|
|
const lowerCaseContent = content.toLowerCase();
|
|
|
|
// 全文搜索
|
|
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("... "), // 使用...连接不同消息中的内容
|
|
});
|
|
}
|
|
});
|
|
|
|
// 按内容长度排序
|
|
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);
|
|
}, []);
|
|
|
|
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}>
|
|
{/** 搜索匹配的文本 */}
|
|
<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}
|
|
onClick={() => {
|
|
navigate(Path.Chat);
|
|
selectSession(item.id);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ErrorBoundary>
|
|
);
|
|
}
|