mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat(export): prioritize tab counts via lightweight api
This commit is contained in:
@@ -912,6 +912,10 @@ function registerIpcHandlers() {
|
|||||||
return chatService.getSessions()
|
return chatService.getSessions()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('chat:getExportTabCounts', async () => {
|
||||||
|
return chatService.getExportTabCounts()
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle('chat:enrichSessionsContactInfo', async (_, usernames: string[]) => {
|
ipcMain.handle('chat:enrichSessionsContactInfo', async (_, usernames: string[]) => {
|
||||||
return chatService.enrichSessionsContactInfo(usernames)
|
return chatService.enrichSessionsContactInfo(usernames)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
chat: {
|
chat: {
|
||||||
connect: () => ipcRenderer.invoke('chat:connect'),
|
connect: () => ipcRenderer.invoke('chat:connect'),
|
||||||
getSessions: () => ipcRenderer.invoke('chat:getSessions'),
|
getSessions: () => ipcRenderer.invoke('chat:getSessions'),
|
||||||
|
getExportTabCounts: () => ipcRenderer.invoke('chat:getExportTabCounts'),
|
||||||
enrichSessionsContactInfo: (usernames: string[]) =>
|
enrichSessionsContactInfo: (usernames: string[]) =>
|
||||||
ipcRenderer.invoke('chat:enrichSessionsContactInfo', usernames),
|
ipcRenderer.invoke('chat:enrichSessionsContactInfo', usernames),
|
||||||
getMessages: (sessionId: string, offset?: number, limit?: number, startTime?: number, endTime?: number, ascending?: boolean) =>
|
getMessages: (sessionId: string, offset?: number, limit?: number, startTime?: number, endTime?: number, ascending?: boolean) =>
|
||||||
|
|||||||
@@ -151,6 +151,13 @@ interface ExportSessionStats {
|
|||||||
groupMutualFriends?: number
|
groupMutualFriends?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExportTabCounts {
|
||||||
|
private: number
|
||||||
|
group: number
|
||||||
|
official: number
|
||||||
|
former_friend: number
|
||||||
|
}
|
||||||
|
|
||||||
// 表情包缓存
|
// 表情包缓存
|
||||||
const emojiCache: Map<string, string> = new Map()
|
const emojiCache: Map<string, string> = new Map()
|
||||||
const emojiDownloading: Map<string, Promise<string | null>> = new Map()
|
const emojiDownloading: Map<string, Promise<string | null>> = new Map()
|
||||||
@@ -657,6 +664,112 @@ class ChatService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取导出页会话分类数量(轻量接口,优先用于顶部 Tab 数量展示)
|
||||||
|
*/
|
||||||
|
async getExportTabCounts(): Promise<{ success: boolean; counts?: ExportTabCounts; error?: string }> {
|
||||||
|
try {
|
||||||
|
const connectResult = await this.ensureConnected()
|
||||||
|
if (!connectResult.success) {
|
||||||
|
return { success: false, error: connectResult.error }
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionResult = await wcdbService.getSessions()
|
||||||
|
if (!sessionResult.success || !sessionResult.sessions) {
|
||||||
|
return { success: false, error: sessionResult.error || '获取会话失败' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const counts: ExportTabCounts = {
|
||||||
|
private: 0,
|
||||||
|
group: 0,
|
||||||
|
official: 0,
|
||||||
|
former_friend: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonGroupUsernames: string[] = []
|
||||||
|
const usernameSet = new Set<string>()
|
||||||
|
|
||||||
|
for (const row of sessionResult.sessions as Record<string, any>[]) {
|
||||||
|
const username =
|
||||||
|
row.username ||
|
||||||
|
row.user_name ||
|
||||||
|
row.userName ||
|
||||||
|
row.usrName ||
|
||||||
|
row.UsrName ||
|
||||||
|
row.talker ||
|
||||||
|
row.talker_id ||
|
||||||
|
row.talkerId ||
|
||||||
|
''
|
||||||
|
|
||||||
|
if (!this.shouldKeepSession(username)) continue
|
||||||
|
if (usernameSet.has(username)) continue
|
||||||
|
usernameSet.add(username)
|
||||||
|
|
||||||
|
if (username.endsWith('@chatroom')) {
|
||||||
|
counts.group += 1
|
||||||
|
} else {
|
||||||
|
nonGroupUsernames.push(username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonGroupUsernames.length === 0) {
|
||||||
|
return { success: true, counts }
|
||||||
|
}
|
||||||
|
|
||||||
|
const contactTypeMap = new Map<string, 'official' | 'former_friend'>()
|
||||||
|
const chunkSize = 400
|
||||||
|
|
||||||
|
for (let i = 0; i < nonGroupUsernames.length; i += chunkSize) {
|
||||||
|
const chunk = nonGroupUsernames.slice(i, i + chunkSize)
|
||||||
|
if (chunk.length === 0) continue
|
||||||
|
|
||||||
|
const usernamesExpr = chunk.map((name) => `'${this.escapeSqlString(name)}'`).join(',')
|
||||||
|
const contactSql = `
|
||||||
|
SELECT username, local_type, quan_pin
|
||||||
|
FROM contact
|
||||||
|
WHERE username IN (${usernamesExpr})
|
||||||
|
`
|
||||||
|
|
||||||
|
const contactResult = await wcdbService.execQuery('contact', null, contactSql)
|
||||||
|
if (!contactResult.success || !contactResult.rows) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of contactResult.rows as Record<string, any>[]) {
|
||||||
|
const username = String(row.username || '').trim()
|
||||||
|
if (!username) continue
|
||||||
|
|
||||||
|
if (username.startsWith('gh_')) {
|
||||||
|
contactTypeMap.set(username, 'official')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const localType = this.getRowInt(row, ['local_type', 'localType', 'WCDB_CT_local_type'], 0)
|
||||||
|
const quanPin = String(this.getRowField(row, ['quan_pin', 'quanPin', 'WCDB_CT_quan_pin']) || '').trim()
|
||||||
|
if (localType === 0 && quanPin) {
|
||||||
|
contactTypeMap.set(username, 'former_friend')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const username of nonGroupUsernames) {
|
||||||
|
const type = contactTypeMap.get(username)
|
||||||
|
if (type === 'official') {
|
||||||
|
counts.official += 1
|
||||||
|
} else if (type === 'former_friend') {
|
||||||
|
counts.former_friend += 1
|
||||||
|
} else {
|
||||||
|
counts.private += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, counts }
|
||||||
|
} catch (e) {
|
||||||
|
console.error('ChatService: 获取导出页会话分类数量失败:', e)
|
||||||
|
return { success: false, error: String(e) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取通讯录列表
|
* 获取通讯录列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -242,10 +242,12 @@ function ExportPage() {
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const [isSessionEnriching, setIsSessionEnriching] = useState(false)
|
const [isSessionEnriching, setIsSessionEnriching] = useState(false)
|
||||||
|
const [isTabCountsLoading, setIsTabCountsLoading] = useState(true)
|
||||||
const [isSnsStatsLoading, setIsSnsStatsLoading] = useState(true)
|
const [isSnsStatsLoading, setIsSnsStatsLoading] = useState(true)
|
||||||
const [isBaseConfigLoading, setIsBaseConfigLoading] = useState(true)
|
const [isBaseConfigLoading, setIsBaseConfigLoading] = useState(true)
|
||||||
const [isTaskCenterExpanded, setIsTaskCenterExpanded] = useState(false)
|
const [isTaskCenterExpanded, setIsTaskCenterExpanded] = useState(false)
|
||||||
const [sessions, setSessions] = useState<SessionRow[]>([])
|
const [sessions, setSessions] = useState<SessionRow[]>([])
|
||||||
|
const [prefetchedTabCounts, setPrefetchedTabCounts] = useState<Record<ConversationTab, number> | null>(null)
|
||||||
const [sessionMetrics, setSessionMetrics] = useState<Record<string, SessionMetrics>>({})
|
const [sessionMetrics, setSessionMetrics] = useState<Record<string, SessionMetrics>>({})
|
||||||
const [searchKeyword, setSearchKeyword] = useState('')
|
const [searchKeyword, setSearchKeyword] = useState('')
|
||||||
const [activeTab, setActiveTab] = useState<ConversationTab>('private')
|
const [activeTab, setActiveTab] = useState<ConversationTab>('private')
|
||||||
@@ -373,6 +375,20 @@ function ExportPage() {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const loadTabCounts = useCallback(async () => {
|
||||||
|
setIsTabCountsLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.chat.getExportTabCounts()
|
||||||
|
if (result.success && result.counts) {
|
||||||
|
setPrefetchedTabCounts(result.counts)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载导出页会话分类数量失败:', error)
|
||||||
|
} finally {
|
||||||
|
setIsTabCountsLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const loadSnsStats = useCallback(async () => {
|
const loadSnsStats = useCallback(async () => {
|
||||||
setIsSnsStatsLoading(true)
|
setIsSnsStatsLoading(true)
|
||||||
try {
|
try {
|
||||||
@@ -493,7 +509,10 @@ function ExportPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadBaseConfig()
|
void loadBaseConfig()
|
||||||
void loadSessions()
|
void (async () => {
|
||||||
|
await loadTabCounts()
|
||||||
|
await loadSessions()
|
||||||
|
})()
|
||||||
|
|
||||||
// 朋友圈统计延后一点加载,避免与首屏会话初始化抢占。
|
// 朋友圈统计延后一点加载,避免与首屏会话初始化抢占。
|
||||||
const timer = window.setTimeout(() => {
|
const timer = window.setTimeout(() => {
|
||||||
@@ -501,7 +520,7 @@ function ExportPage() {
|
|||||||
}, 180)
|
}, 180)
|
||||||
|
|
||||||
return () => window.clearTimeout(timer)
|
return () => window.clearTimeout(timer)
|
||||||
}, [loadBaseConfig, loadSessions, loadSnsStats])
|
}, [loadTabCounts, loadBaseConfig, loadSessions, loadSnsStats])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
preselectAppliedRef.current = false
|
preselectAppliedRef.current = false
|
||||||
@@ -1057,7 +1076,7 @@ function ExportPage() {
|
|||||||
return set
|
return set
|
||||||
}, [tasks])
|
}, [tasks])
|
||||||
|
|
||||||
const tabCounts = useMemo(() => {
|
const sessionTabCounts = useMemo(() => {
|
||||||
const counts: Record<ConversationTab, number> = {
|
const counts: Record<ConversationTab, number> = {
|
||||||
private: 0,
|
private: 0,
|
||||||
group: 0,
|
group: 0,
|
||||||
@@ -1070,6 +1089,16 @@ function ExportPage() {
|
|||||||
return counts
|
return counts
|
||||||
}, [sessions])
|
}, [sessions])
|
||||||
|
|
||||||
|
const tabCounts = useMemo(() => {
|
||||||
|
if (sessions.length > 0) {
|
||||||
|
return sessionTabCounts
|
||||||
|
}
|
||||||
|
if (prefetchedTabCounts) {
|
||||||
|
return prefetchedTabCounts
|
||||||
|
}
|
||||||
|
return sessionTabCounts
|
||||||
|
}, [sessions.length, sessionTabCounts, prefetchedTabCounts])
|
||||||
|
|
||||||
const contentCards = useMemo(() => {
|
const contentCards = useMemo(() => {
|
||||||
const scopeSessions = sessions.filter(session => session.kind === 'private' || session.kind === 'group')
|
const scopeSessions = sessions.filter(session => session.kind === 'private' || session.kind === 'group')
|
||||||
const totalSessions = scopeSessions.length
|
const totalSessions = scopeSessions.length
|
||||||
@@ -1295,7 +1324,8 @@ function ExportPage() {
|
|||||||
const formatCandidateOptions = exportDialog.scope === 'sns'
|
const formatCandidateOptions = exportDialog.scope === 'sns'
|
||||||
? formatOptions.filter(option => option.value === 'html' || option.value === 'json')
|
? formatOptions.filter(option => option.value === 'html' || option.value === 'json')
|
||||||
: formatOptions
|
: formatOptions
|
||||||
const isTabCountComputing = isLoading || isSessionEnriching
|
const hasTabCountsSource = prefetchedTabCounts !== null || sessions.length > 0
|
||||||
|
const isTabCountComputing = isTabCountsLoading && !hasTabCountsSource
|
||||||
const isSessionCardStatsLoading = isLoading || isBaseConfigLoading
|
const isSessionCardStatsLoading = isLoading || isBaseConfigLoading
|
||||||
const taskRunningCount = tasks.filter(task => task.status === 'running').length
|
const taskRunningCount = tasks.filter(task => task.status === 'running').length
|
||||||
const taskQueuedCount = tasks.filter(task => task.status === 'queued').length
|
const taskQueuedCount = tasks.filter(task => task.status === 'queued').length
|
||||||
|
|||||||
10
src/types/electron.d.ts
vendored
10
src/types/electron.d.ts
vendored
@@ -74,6 +74,16 @@ export interface ElectronAPI {
|
|||||||
chat: {
|
chat: {
|
||||||
connect: () => Promise<{ success: boolean; error?: string }>
|
connect: () => Promise<{ success: boolean; error?: string }>
|
||||||
getSessions: () => Promise<{ success: boolean; sessions?: ChatSession[]; error?: string }>
|
getSessions: () => Promise<{ success: boolean; sessions?: ChatSession[]; error?: string }>
|
||||||
|
getExportTabCounts: () => Promise<{
|
||||||
|
success: boolean
|
||||||
|
counts?: {
|
||||||
|
private: number
|
||||||
|
group: number
|
||||||
|
official: number
|
||||||
|
former_friend: number
|
||||||
|
}
|
||||||
|
error?: string
|
||||||
|
}>
|
||||||
enrichSessionsContactInfo: (usernames: string[]) => Promise<{
|
enrichSessionsContactInfo: (usernames: string[]) => Promise<{
|
||||||
success: boolean
|
success: boolean
|
||||||
contacts?: Record<string, { displayName?: string; avatarUrl?: string }>
|
contacts?: Record<string, { displayName?: string; avatarUrl?: string }>
|
||||||
|
|||||||
Reference in New Issue
Block a user