feat: 超级无敌帅气的更新和修复

This commit is contained in:
cc
2026-02-01 23:25:19 +08:00
parent b7339b6a35
commit f9bb18d97f
4 changed files with 72 additions and 73 deletions

View File

@@ -155,63 +155,7 @@ export class WcdbCore {
return this.startMonitor(callback)
}
/**
* 获取指定时间之后的新消息(增量更新)
*/
getNewMessages(sessionId: string, minTime: number, limit: number = 1000): { success: boolean; messages?: any[]; error?: string } {
if (!this.handle || !this.wcdbOpenMessageCursorLite || !this.wcdbFetchMessageBatch || !this.wcdbCloseMessageCursor) {
return { success: false, error: 'Database not handled or functions missing' }
}
// 1. Open Cursor
const cursorPtr = Buffer.alloc(8) // int64*
// wcdb_open_message_cursor_lite(handle, sessionId, batchSize, ascending, beginTime, endTime, outCursor)
// ascending=1 (ASC) to get messages AFTER minTime ordered by time
// beginTime = minTime + 1 (to avoid duplicate of the last message)
// Actually, let's use minTime, user logic might handle duplication or we just pass strictly greater
// C++ logic: create_time >= beginTimestamp. So if we want new messages, passing lastTimestamp + 1 is safer.
const openRes = this.wcdbOpenMessageCursorLite(this.handle, sessionId, limit, 1, minTime, 0, cursorPtr)
if (openRes !== 0) {
return { success: false, error: `Open cursor failed: ${openRes}` }
}
// Read int64 from buffer
const cursor = cursorPtr.readBigInt64LE(0)
// 2. Fetch Batch
const outJsonPtr = Buffer.alloc(8) // void**
const outHasMorePtr = Buffer.alloc(4) // int32*
// fetch_message_batch(handle, cursor, outJson, outHasMore)
const fetchRes = this.wcdbFetchMessageBatch(this.handle, cursor, outJsonPtr, outHasMorePtr)
let messages: any[] = []
if (fetchRes === 0) {
const jsonPtr = outJsonPtr.readBigInt64LE(0) // void* address
if (jsonPtr !== 0n) {
// koffi decode string
const jsonStr = this.koffi.decode(jsonPtr, 'string')
this.wcdbFreeString(jsonPtr) // Must free
if (jsonStr) {
try {
messages = JSON.parse(jsonStr)
} catch (e) {
console.error('Parse messages failed', e)
}
}
}
}
// 3. Close Cursor
this.wcdbCloseMessageCursor(this.handle, cursor)
if (fetchRes !== 0) {
return { success: false, error: `Fetch batch failed: ${fetchRes}` }
}
return { success: true, messages }
}
/**
* 获取 DLL 路径
@@ -999,6 +943,37 @@ export class WcdbCore {
}
}
/**
* 获取指定时间之后的新消息
*/
async getNewMessages(sessionId: string, minTime: number, limit: number = 1000): Promise<{ success: boolean; messages?: any[]; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }
}
try {
// 1. 打开游标 (使用 Ascending=1 从指定时间往后查)
const openRes = await this.openMessageCursorLite(sessionId, limit, true, minTime, 0)
if (!openRes.success || !openRes.cursor) {
return { success: false, error: openRes.error }
}
const cursor = openRes.cursor
try {
// 2. 获取批次
const fetchRes = await this.fetchMessageBatch(cursor)
if (!fetchRes.success) {
return { success: false, error: fetchRes.error }
}
return { success: true, messages: fetchRes.rows }
} finally {
// 3. 关闭游标
await this.closeMessageCursor(cursor)
}
} catch (e) {
return { success: false, error: String(e) }
}
}
async getMessageCount(sessionId: string): Promise<{ success: boolean; count?: number; error?: string }> {
if (!this.ensureReady()) {
return { success: false, error: 'WCDB 未连接' }

Binary file not shown.

View File

@@ -192,6 +192,7 @@ function ChatPage(_props: ChatPageProps) {
const isLoadingMessagesRef = useRef(false)
const isLoadingMoreRef = useRef(false)
const isConnectedRef = useRef(false)
const isRefreshingRef = useRef(false)
const searchKeywordRef = useRef('')
const preloadImageKeysRef = useRef<Set<string>>(new Set())
const lastPreloadSessionRef = useRef<string | null>(null)
@@ -320,6 +321,7 @@ function ChatPage(_props: ChatPageProps) {
}
setSessions(nextSessions)
sessionsRef.current = nextSessions
// 立即启动联系人信息加载,不再延迟 500ms
void enrichSessionsContactInfo(nextSessions)
} else {
@@ -566,10 +568,13 @@ function ChatPage(_props: ChatPageProps) {
* (由用户建议:记住上一条消息时间,自动取之后的并渲染,然后后台兜底全量同步)
*/
const handleIncrementalRefresh = async () => {
if (!currentSessionId || isRefreshingMessages) return
if (!currentSessionId || isRefreshingRef.current) return
isRefreshingRef.current = true
setIsRefreshingMessages(true)
// 找出当前已渲染消息中的最大时间戳
const lastMsg = messages[messages.length - 1]
// 找出当前已渲染消息中的最大时间戳(使用 getState 获取最新状态,避免闭包过时导致重复)
const currentMessages = useChatStore.getState().messages
const lastMsg = currentMessages[currentMessages.length - 1]
const minTime = lastMsg?.createTime || 0
// 1. 优先执行增量查询并渲染(第一步)
@@ -581,8 +586,9 @@ function ChatPage(_props: ChatPageProps) {
}
if (result.success && result.messages && result.messages.length > 0) {
// 过滤去重
const existingKeys = new Set(messages.map(getMessageKey))
// 过滤去重:必须对比实时的状态,防止在 handleRefreshMessages 运行期间导致的冲突
const latestMessages = useChatStore.getState().messages
const existingKeys = new Set(latestMessages.map(getMessageKey))
const newOnes = result.messages.filter(m => !existingKeys.has(getMessageKey(m)))
if (newOnes.length > 0) {
@@ -598,18 +604,19 @@ function ChatPage(_props: ChatPageProps) {
}
} catch (e) {
console.warn('[IncrementalRefresh] 失败,将依赖全量同步兜底:', e)
} finally {
isRefreshingRef.current = false
setIsRefreshingMessages(false)
}
// 2. 后台兜底:执行之前的完整游标刷新,确保没有遗漏(比如跨库的消息)
void handleRefreshMessages()
}
const handleRefreshMessages = async () => {
if (!currentSessionId || isRefreshingMessages) return
if (!currentSessionId || isRefreshingRef.current) return
setJumpStartTime(0)
setJumpEndTime(0)
setHasMoreLater(false)
setIsRefreshingMessages(true)
isRefreshingRef.current = true
try {
// 获取最新消息并增量添加
const result = await window.electronAPI.chat.getLatestMessages(currentSessionId, 50) as {
@@ -620,13 +627,17 @@ function ChatPage(_props: ChatPageProps) {
if (!result.success || !result.messages) {
return
}
const existing = new Set(messages.map(getMessageKey))
const lastMsg = messages[messages.length - 1]
// 使用实时状态进行去重对比
const latestMessages = useChatStore.getState().messages
const existing = new Set(latestMessages.map(getMessageKey))
const lastMsg = latestMessages[latestMessages.length - 1]
const lastTime = lastMsg?.createTime ?? 0
const newMessages = result.messages.filter((msg) => {
const key = getMessageKey(msg)
if (existing.has(key)) return false
if (lastTime > 0 && msg.createTime < lastTime) return false
// 这里的 lastTime 仅作参考过滤,主要的去重靠 key
if (lastTime > 0 && msg.createTime < lastTime - 3600) return false // 仅过滤 1 小时之前的冗余请求
return true
})
if (newMessages.length > 0) {
@@ -642,6 +653,7 @@ function ChatPage(_props: ChatPageProps) {
} catch (e) {
console.error('刷新消息失败:', e)
} finally {
isRefreshingRef.current = false
setIsRefreshingMessages(false)
}
}

View File

@@ -80,11 +80,23 @@ export const useChatStore = create<ChatState>((set, get) => ({
setMessages: (messages) => set({ messages }),
appendMessages: (newMessages, prepend = false) => set((state) => ({
messages: prepend
? [...newMessages, ...state.messages]
: [...state.messages, ...newMessages]
})),
appendMessages: (newMessages, prepend = false) => set((state) => {
// 强制去重逻辑
const getMsgKey = (m: Message) => {
if (m.localId && m.localId > 0) return `l:${m.localId}`
return `t:${m.createTime}:${m.sortSeq || 0}:${m.serverId || 0}`
}
const existingKeys = new Set(state.messages.map(getMsgKey))
const filtered = newMessages.filter(m => !existingKeys.has(getMsgKey(m)))
if (filtered.length === 0) return state
return {
messages: prepend
? [...filtered, ...state.messages]
: [...state.messages, ...filtered]
}
}),
setLoadingMessages: (loading) => set({ isLoadingMessages: loading }),
setLoadingMore: (loading) => set({ isLoadingMore: loading }),