mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
feat: 超级无敌帅气的更新和修复
This commit is contained in:
@@ -155,63 +155,7 @@ export class WcdbCore {
|
|||||||
return this.startMonitor(callback)
|
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 路径
|
* 获取 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 }> {
|
async getMessageCount(sessionId: string): Promise<{ success: boolean; count?: number; error?: string }> {
|
||||||
if (!this.ensureReady()) {
|
if (!this.ensureReady()) {
|
||||||
return { success: false, error: 'WCDB 未连接' }
|
return { success: false, error: 'WCDB 未连接' }
|
||||||
|
|||||||
Binary file not shown.
@@ -192,6 +192,7 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
const isLoadingMessagesRef = useRef(false)
|
const isLoadingMessagesRef = useRef(false)
|
||||||
const isLoadingMoreRef = useRef(false)
|
const isLoadingMoreRef = useRef(false)
|
||||||
const isConnectedRef = useRef(false)
|
const isConnectedRef = useRef(false)
|
||||||
|
const isRefreshingRef = useRef(false)
|
||||||
const searchKeywordRef = useRef('')
|
const searchKeywordRef = useRef('')
|
||||||
const preloadImageKeysRef = useRef<Set<string>>(new Set())
|
const preloadImageKeysRef = useRef<Set<string>>(new Set())
|
||||||
const lastPreloadSessionRef = useRef<string | null>(null)
|
const lastPreloadSessionRef = useRef<string | null>(null)
|
||||||
@@ -320,6 +321,7 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setSessions(nextSessions)
|
setSessions(nextSessions)
|
||||||
|
sessionsRef.current = nextSessions
|
||||||
// 立即启动联系人信息加载,不再延迟 500ms
|
// 立即启动联系人信息加载,不再延迟 500ms
|
||||||
void enrichSessionsContactInfo(nextSessions)
|
void enrichSessionsContactInfo(nextSessions)
|
||||||
} else {
|
} else {
|
||||||
@@ -566,10 +568,13 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
* (由用户建议:记住上一条消息时间,自动取之后的并渲染,然后后台兜底全量同步)
|
* (由用户建议:记住上一条消息时间,自动取之后的并渲染,然后后台兜底全量同步)
|
||||||
*/
|
*/
|
||||||
const handleIncrementalRefresh = async () => {
|
const handleIncrementalRefresh = async () => {
|
||||||
if (!currentSessionId || isRefreshingMessages) return
|
if (!currentSessionId || isRefreshingRef.current) return
|
||||||
|
isRefreshingRef.current = true
|
||||||
|
setIsRefreshingMessages(true)
|
||||||
|
|
||||||
// 找出当前已渲染消息中的最大时间戳
|
// 找出当前已渲染消息中的最大时间戳(使用 getState 获取最新状态,避免闭包过时导致重复)
|
||||||
const lastMsg = messages[messages.length - 1]
|
const currentMessages = useChatStore.getState().messages
|
||||||
|
const lastMsg = currentMessages[currentMessages.length - 1]
|
||||||
const minTime = lastMsg?.createTime || 0
|
const minTime = lastMsg?.createTime || 0
|
||||||
|
|
||||||
// 1. 优先执行增量查询并渲染(第一步)
|
// 1. 优先执行增量查询并渲染(第一步)
|
||||||
@@ -581,8 +586,9 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.success && result.messages && result.messages.length > 0) {
|
if (result.success && result.messages && result.messages.length > 0) {
|
||||||
// 过滤去重
|
// 过滤去重:必须对比实时的状态,防止在 handleRefreshMessages 运行期间导致的冲突
|
||||||
const existingKeys = new Set(messages.map(getMessageKey))
|
const latestMessages = useChatStore.getState().messages
|
||||||
|
const existingKeys = new Set(latestMessages.map(getMessageKey))
|
||||||
const newOnes = result.messages.filter(m => !existingKeys.has(getMessageKey(m)))
|
const newOnes = result.messages.filter(m => !existingKeys.has(getMessageKey(m)))
|
||||||
|
|
||||||
if (newOnes.length > 0) {
|
if (newOnes.length > 0) {
|
||||||
@@ -598,18 +604,19 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[IncrementalRefresh] 失败,将依赖全量同步兜底:', e)
|
console.warn('[IncrementalRefresh] 失败,将依赖全量同步兜底:', e)
|
||||||
|
} finally {
|
||||||
|
isRefreshingRef.current = false
|
||||||
|
setIsRefreshingMessages(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 后台兜底:执行之前的完整游标刷新,确保没有遗漏(比如跨库的消息)
|
|
||||||
void handleRefreshMessages()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRefreshMessages = async () => {
|
const handleRefreshMessages = async () => {
|
||||||
if (!currentSessionId || isRefreshingMessages) return
|
if (!currentSessionId || isRefreshingRef.current) return
|
||||||
setJumpStartTime(0)
|
setJumpStartTime(0)
|
||||||
setJumpEndTime(0)
|
setJumpEndTime(0)
|
||||||
setHasMoreLater(false)
|
setHasMoreLater(false)
|
||||||
setIsRefreshingMessages(true)
|
setIsRefreshingMessages(true)
|
||||||
|
isRefreshingRef.current = true
|
||||||
try {
|
try {
|
||||||
// 获取最新消息并增量添加
|
// 获取最新消息并增量添加
|
||||||
const result = await window.electronAPI.chat.getLatestMessages(currentSessionId, 50) as {
|
const result = await window.electronAPI.chat.getLatestMessages(currentSessionId, 50) as {
|
||||||
@@ -620,13 +627,17 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
if (!result.success || !result.messages) {
|
if (!result.success || !result.messages) {
|
||||||
return
|
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 lastTime = lastMsg?.createTime ?? 0
|
||||||
|
|
||||||
const newMessages = result.messages.filter((msg) => {
|
const newMessages = result.messages.filter((msg) => {
|
||||||
const key = getMessageKey(msg)
|
const key = getMessageKey(msg)
|
||||||
if (existing.has(key)) return false
|
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
|
return true
|
||||||
})
|
})
|
||||||
if (newMessages.length > 0) {
|
if (newMessages.length > 0) {
|
||||||
@@ -642,6 +653,7 @@ function ChatPage(_props: ChatPageProps) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('刷新消息失败:', e)
|
console.error('刷新消息失败:', e)
|
||||||
} finally {
|
} finally {
|
||||||
|
isRefreshingRef.current = false
|
||||||
setIsRefreshingMessages(false)
|
setIsRefreshingMessages(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,11 +80,23 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
|||||||
|
|
||||||
setMessages: (messages) => set({ messages }),
|
setMessages: (messages) => set({ messages }),
|
||||||
|
|
||||||
appendMessages: (newMessages, prepend = false) => set((state) => ({
|
appendMessages: (newMessages, prepend = false) => set((state) => {
|
||||||
messages: prepend
|
// 强制去重逻辑
|
||||||
? [...newMessages, ...state.messages]
|
const getMsgKey = (m: Message) => {
|
||||||
: [...state.messages, ...newMessages]
|
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 }),
|
setLoadingMessages: (loading) => set({ isLoadingMessages: loading }),
|
||||||
setLoadingMore: (loading) => set({ isLoadingMore: loading }),
|
setLoadingMore: (loading) => set({ isLoadingMore: loading }),
|
||||||
|
|||||||
Reference in New Issue
Block a user