search-chat.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { useState, useEffect } from "react";
  2. import { ErrorBoundary } from "./error";
  3. import styles from "./mask.module.scss";
  4. import { useNavigate } from "react-router-dom";
  5. import { IconButton } from "./button";
  6. import CloseIcon from "../icons/close.svg";
  7. import EyeIcon from "../icons/eye.svg";
  8. import Locale from "../locales";
  9. import { Path } from "../constant";
  10. import { useChatStore } from "../store";
  11. type Item = {
  12. id: number;
  13. name: string;
  14. content: string;
  15. };
  16. export function SearchChatPage() {
  17. const navigate = useNavigate();
  18. const chatStore = useChatStore();
  19. const sessions = chatStore.sessions;
  20. const selectSession = chatStore.selectSession;
  21. const [searchResults, setSearchResults] = useState<Item[]>([]);
  22. const setDefaultItems = () => {
  23. setSearchResults(
  24. sessions.slice(1, 7).map((session, index) => {
  25. console.log(session.messages[0]);
  26. return {
  27. id: index,
  28. name: session.topic,
  29. content: session.messages[0].content as string, //.map((m) => m.content).join("\n")
  30. };
  31. }),
  32. );
  33. };
  34. useEffect(() => {
  35. setDefaultItems();
  36. }, []);
  37. const doSearch = (text: string) => {
  38. // 分割关键词
  39. const keywords = text.split(" ");
  40. // 存储每个会话的匹配结果
  41. const searchResults: Item[] = [];
  42. sessions.forEach((session, index) => {
  43. let matchCount = 0;
  44. const contents: string[] = [];
  45. session.messages.forEach((message) => {
  46. const content = message.content as string;
  47. const lowerCaseContent = content.toLowerCase();
  48. keywords.forEach((keyword) => {
  49. const pos = lowerCaseContent.indexOf(keyword.toLowerCase());
  50. if (pos !== -1) {
  51. matchCount++;
  52. // 提取关键词前后70个字符的内容
  53. const start = Math.max(0, pos - 35);
  54. const end = Math.min(content.length, pos + keyword.length + 35);
  55. contents.push(content.substring(start, end));
  56. }
  57. });
  58. });
  59. if (matchCount > 0) {
  60. searchResults.push({
  61. id: index,
  62. name: session.topic,
  63. content: contents.join("... "), // 使用...连接不同消息中的内容
  64. });
  65. }
  66. });
  67. // 按匹配数量排序,取前10个结果
  68. return searchResults
  69. .sort((a, b) => b.content.length - a.content.length)
  70. .slice(0, 10);
  71. };
  72. return (
  73. <ErrorBoundary>
  74. <div className={styles["mask-page"]}>
  75. {/* header */}
  76. <div className="window-header">
  77. <div className="window-header-title">
  78. <div className="window-header-main-title">
  79. {Locale.SearchChat.Page.Title}
  80. </div>
  81. <div className="window-header-submai-title">
  82. {Locale.SearchChat.Page.SubTitle(searchResults.length)}
  83. </div>
  84. </div>
  85. <div className="window-actions">
  86. <div className="window-action-button">
  87. <IconButton
  88. icon={<CloseIcon />}
  89. bordered
  90. onClick={() => navigate(-1)}
  91. />
  92. </div>
  93. </div>
  94. </div>
  95. <div className={styles["mask-page-body"]}>
  96. <div className={styles["mask-filter"]}>
  97. {/**搜索输入框 */}
  98. <input
  99. type="text"
  100. className={styles["search-bar"]}
  101. placeholder={Locale.SearchChat.Page.Search}
  102. autoFocus
  103. onKeyDown={(e) => {
  104. if (e.key === "Enter") {
  105. e.preventDefault();
  106. const searchText = e.currentTarget.value;
  107. if (searchText.length > 0) {
  108. const result = doSearch(searchText);
  109. setSearchResults(result);
  110. }
  111. }
  112. }}
  113. />
  114. </div>
  115. <div>
  116. {searchResults.map((item) => (
  117. <div className={styles["mask-item"]} key={item.id}>
  118. {/** 搜索匹配的文本 */}
  119. <div className={styles["mask-header"]}>
  120. <div className={styles["mask-title"]}>
  121. <div className={styles["mask-name"]}>{item.name}</div>
  122. {item.content.slice(0, 70)}
  123. </div>
  124. </div>
  125. {/** 操作按钮 */}
  126. <div className={styles["mask-actions"]}>
  127. <IconButton
  128. icon={<EyeIcon />}
  129. text={Locale.SearchChat.Item.View}
  130. onClick={() => {
  131. navigate(Path.Chat);
  132. selectSession(item.id);
  133. }}
  134. />
  135. </div>
  136. </div>
  137. ))}
  138. </div>
  139. </div>
  140. </div>
  141. </ErrorBoundary>
  142. );
  143. }