-
Notifications
You must be signed in to change notification settings - Fork 59.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add a page to search chat history #5274
Merged
lloydzhou
merged 18 commits into
ChatGPTNextWeb:main
from
Movelocity:feat/search-history
Aug 20, 2024
Merged
Changes from 2 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
cd92036
Add page to search chat history
3da5284
优化搜索算法,更新图标
00990dc
use yarn instead of npm
98093a1
make search result item easier to click
65ed6b0
Merge branch 'main' into feat/search-history
8622057
Update yarn.lock to match origin version
Movelocity e3f499b
hide search button text
b84bb72
add null check for search content
82298a7
Merge branch 'main' into feat/search-history
fd1c656
add all translations for SearchChat
7ce2e8f
resolve a warning
fcd55df
wrap doSearch with useCallback
39d7d9f
migrate the search button to plugins discovery
b529118
Merge branch 'ChatGPTNextWeb:main' into feat/search-history
Movelocity 64a0ffe
Merge branch 'main' into feat/search-history
6649fbd
remove an empty line
09a9066
Merge branch 'feat/search-history' of https://github.com/Movelocity/C…
e275abd
match the origin format
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
旁观,使用定时器来每秒判断值变化会不会不太好?使用useState绑定input的value,添加到deps中,并做防抖?doSearch函数也可以用useCallBack包裹一下? 仅旁观建议,如有说错请忽略🫣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
好建议!我有空更新一下
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1. 防抖方案
新建分支尝试实现了一下你提到的方案,https://github.com/Movelocity/ChatGPT-Next-Web/tree/test/search-history
使用防抖(debounce)机制时,防抖函数会在每次输入时重置计时器,只有在输入停止并且超过防抖时间后,才会执行搜索操作。
我把防抖时间设为1000毫秒。现在每隔500毫秒输入一个字符,最终只会在输入完成并且没有进一步输入的情况下(即等待时间超过防抖时间)触发一次搜索操作。
举个例子,当用户想搜索
javascript
,输入javas
后就大概率已经能搜索到完整的列表了。而每隔500毫秒输入 j a v a s c r i p t 的情况下,最终只会在输入完成(javascript 整个单词)并且等待超过1000毫秒后,才会执行一次以 javascript 为关键词的搜索相比之下,使用定时器方案可以频繁地检查输入框的值,并立即触发搜索操作。这样可以显著提高搜索功能的响应速度,用户在快速输入时,不必等到整个单词输入完成再等待一次防抖的延时。
所以我还是打算保留原方案。
2. useCallback
doSearch 函数被 useEffect 依赖了,useCallback 确实需要加上
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
算了,每个人都有自己的想法