fix: 优化消息搜索体验

- 去掉切换按钮,搜索框直接同时搜索会话名和消息内容
- 消息搜索加 400ms 防抖,输入停止后再请求
- 全局消息结果显示会话 displayName,点击跳转并清空搜索框
- 修复跨会话搜索 meta 为 null 导致无结果的问题(C++ 层)
This commit is contained in:
hicccc77
2026-03-15 19:40:47 +08:00
parent 053e2cdc64
commit 0eac4e2a44

View File

@@ -2646,12 +2646,17 @@ function ChatPage(props: ChatPageProps) {
}, []) }, [])
// 全局消息搜索 // 全局消息搜索
const globalMsgSearchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const handleGlobalMsgSearch = useCallback(async (keyword: string) => { const handleGlobalMsgSearch = useCallback(async (keyword: string) => {
setGlobalMsgQuery(keyword) setGlobalMsgQuery(keyword)
if (globalMsgSearchTimerRef.current) clearTimeout(globalMsgSearchTimerRef.current)
if (!keyword.trim()) { if (!keyword.trim()) {
setGlobalMsgResults([]) setGlobalMsgResults([])
setShowGlobalMsgSearch(false)
return return
} }
setShowGlobalMsgSearch(true)
globalMsgSearchTimerRef.current = setTimeout(async () => {
setGlobalMsgSearching(true) setGlobalMsgSearching(true)
try { try {
const res = await window.electronAPI.chat.searchMessages(keyword.trim(), undefined, 50, 0) const res = await window.electronAPI.chat.searchMessages(keyword.trim(), undefined, 50, 0)
@@ -2661,12 +2666,14 @@ function ChatPage(props: ChatPageProps) {
} finally { } finally {
setGlobalMsgSearching(false) setGlobalMsgSearching(false)
} }
}, 400)
}, []) }, [])
const handleCloseGlobalMsgSearch = useCallback(() => { const handleCloseGlobalMsgSearch = useCallback(() => {
setShowGlobalMsgSearch(false) setShowGlobalMsgSearch(false)
setGlobalMsgQuery('') setGlobalMsgQuery('')
setGlobalMsgResults([]) setGlobalMsgResults([])
if (globalMsgSearchTimerRef.current) clearTimeout(globalMsgSearchTimerRef.current)
}, []) }, [])
// 滚动加载更多 + 显示/隐藏回到底部按钮(优化:节流,避免频繁执行) // 滚动加载更多 + 显示/隐藏回到底部按钮(优化:节流,避免频繁执行)
@@ -3956,26 +3963,20 @@ function ChatPage(props: ChatPageProps) {
<input <input
ref={searchInputRef} ref={searchInputRef}
type="text" type="text"
placeholder={showGlobalMsgSearch ? '搜索消息内容...' : '搜索'} placeholder="搜索"
value={showGlobalMsgSearch ? globalMsgQuery : searchKeyword} value={searchKeyword}
onChange={(e) => showGlobalMsgSearch ? handleGlobalMsgSearch(e.target.value) : handleSearch(e.target.value)} onChange={(e) => {
handleSearch(e.target.value)
handleGlobalMsgSearch(e.target.value)
}}
/> />
{(showGlobalMsgSearch ? globalMsgQuery : searchKeyword) && ( {searchKeyword && (
<button className="close-search" onClick={showGlobalMsgSearch ? handleCloseGlobalMsgSearch : handleCloseSearch}> <button className="close-search" onClick={() => { handleCloseSearch(); handleCloseGlobalMsgSearch() }}>
<X size={12} /> <X size={12} />
</button> </button>
)} )}
{globalMsgSearching && <Loader2 size={12} className="spin" style={{ flexShrink: 0 }} />}
</div> </div>
<button
className={`icon-btn msg-search-toggle-btn ${showGlobalMsgSearch ? 'active' : ''}`}
onClick={() => {
if (showGlobalMsgSearch) handleCloseGlobalMsgSearch()
else { setShowGlobalMsgSearch(true); setTimeout(() => searchInputRef.current?.focus(), 50) }
}}
title={showGlobalMsgSearch ? '退出消息搜索' : '搜索消息内容'}
>
<MessageSquare size={15} />
</button>
<button className="icon-btn refresh-btn" onClick={handleRefresh} disabled={isLoadingSessions || isRefreshingSessions}> <button className="icon-btn refresh-btn" onClick={handleRefresh} disabled={isLoadingSessions || isRefreshingSessions}>
<RefreshCw size={16} className={(isLoadingSessions || isRefreshingSessions) ? 'spin' : ''} /> <RefreshCw size={16} className={(isLoadingSessions || isRefreshingSessions) ? 'spin' : ''} />
</button> </button>
@@ -3989,22 +3990,27 @@ function ChatPage(props: ChatPageProps) {
{!globalMsgSearching && globalMsgQuery && globalMsgResults.length === 0 && ( {!globalMsgSearching && globalMsgQuery && globalMsgResults.length === 0 && (
<div className="global-msg-empty"></div> <div className="global-msg-empty"></div>
)} )}
{globalMsgResults.map((msg, i) => ( {globalMsgResults.map((msg, i) => {
const sid = msg._session_id || msg.username || ''
const sessionObj = sessions.find(s => s.username === sid)
const sessionName = sessionObj?.displayName || sid || '未知会话'
const content = (msg.content || msg.strContent || msg.message_content || '').slice(0, 60)
const ts = msg.createTime || msg.create_time
const timeStr = ts ? new Date(ts * 1000).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : ''
return (
<div key={i} className="global-msg-result-item" onClick={() => { <div key={i} className="global-msg-result-item" onClick={() => {
const sid = msg._session_id || msg.username if (sessionObj) {
if (sid) { handleSelectSession(sessionObj)
const target = sessions.find(s => s.username === sid)
if (target) {
handleSelectSession(target)
handleCloseGlobalMsgSearch() handleCloseGlobalMsgSearch()
} setSearchKeyword('')
} }
}}> }}>
<div className="global-msg-result-session">{msg._session_id || msg.username || '未知会话'}</div> <div className="global-msg-result-session">{sessionName}</div>
<div className="global-msg-result-content">{(msg.content || msg.strContent || msg.message_content || '').slice(0, 60)}</div> <div className="global-msg-result-content">{content}</div>
<div className="global-msg-result-time">{(msg.createTime || msg.create_time) ? new Date((msg.createTime || msg.create_time) * 1000).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : ''}</div> <div className="global-msg-result-time">{timeStr}</div>
</div> </div>
))} )
})}
</div> </div>
)} )}
</div> </div>