mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-20 22:48:38 +08:00
feat: 超级无敌帅气的更新和修复
This commit is contained in:
@@ -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.
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
Reference in New Issue
Block a user