mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-04 03:01:42 +08:00
feat: merge favorites into export and search_messages
This commit is contained in:
@@ -25,6 +25,7 @@ import { mergeCachedSelfInfo, type CachedSelfInfo } from './services/bootstrap-c
|
||||
import type { VoiceRecognitionUseCase } from './voice-pipeline/voice-recognition-use-case'
|
||||
import { imageFileQuality } from '../shared/image-quality'
|
||||
import { resolveMemberName } from '../shared/member-names'
|
||||
import { FavoritesService } from './favorites-service'
|
||||
import { filesystemSafeName } from '../shared/contact-name'
|
||||
|
||||
const jobs = new Set<string>()
|
||||
@@ -853,6 +854,28 @@ async function runSingleExport(
|
||||
percent: Math.max(1, Math.round(((targetOrder + 1) / targets.length) * 10))
|
||||
})
|
||||
}
|
||||
if (request.includeFavorites) {
|
||||
const wcdb = chat.getChatDb()?.getWcdb4Client()
|
||||
if (wcdb) {
|
||||
try {
|
||||
const favoriteMessages = await new FavoritesService(wcdb).listExportMessages(500)
|
||||
for (const [messageOrder, message] of favoriteMessages.entries()) {
|
||||
if (!request.kinds.includes(kindOf(message))) continue
|
||||
messageEntries.push({
|
||||
message: {
|
||||
...message,
|
||||
exportConversationId: 'favorites',
|
||||
exportConversationName: '收藏'
|
||||
},
|
||||
targetOrder: targets.length,
|
||||
messageOrder
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[export] favorites merge skipped:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
const messages = messageEntries
|
||||
.sort((left, right) => {
|
||||
const byTime = Number(left.message.createTime || 0) - Number(right.message.createTime || 0)
|
||||
|
||||
@@ -30,6 +30,34 @@ export class FavoritesService {
|
||||
} as Message
|
||||
})
|
||||
}
|
||||
|
||||
/** 只读关键词搜索收藏文本(title/desc/正文)。 */
|
||||
async search(query: string, limit = 50): Promise<ParsedContent[]> {
|
||||
const needle = String(query || '').trim().toLowerCase()
|
||||
if (!needle) return []
|
||||
const items = await this.listContents(500)
|
||||
return items.filter((item) => textOf(item).toLowerCase().includes(needle)).slice(0, limit)
|
||||
}
|
||||
|
||||
/** Local Query API 适配:只要文本与时间戳。 */
|
||||
async searchHits(
|
||||
query: string,
|
||||
limit = 20
|
||||
): Promise<Array<{ text: string; timestamp?: number }>> {
|
||||
const rows = await this.wcdb4Client.listFavoriteItems(500)
|
||||
const needle = String(query || '').trim().toLowerCase()
|
||||
if (!needle) return []
|
||||
return rows
|
||||
.map((row) => {
|
||||
const content = favoriteRowToContent(row)
|
||||
return {
|
||||
text: textOf(content),
|
||||
timestamp: Number(row.update_time) || undefined
|
||||
}
|
||||
})
|
||||
.filter((hit) => hit.text.toLowerCase().includes(needle))
|
||||
.slice(0, limit)
|
||||
}
|
||||
}
|
||||
|
||||
function textOf(content: ParsedContent): string {
|
||||
|
||||
@@ -771,6 +771,13 @@ app.whenReady().then(async () => {
|
||||
localQueryApiService.setImageOcrEntryProvider((conversationId, messageId) =>
|
||||
imageTextIndexService.getConversationOcr(conversationId).get(messageId)
|
||||
)
|
||||
// 收藏只读检索:search_messages 合并 favorite.db 命中(无库时返回空)。
|
||||
localQueryApiService.setFavoritesSearchProvider(async (query, limit) => {
|
||||
const wcdb = chat.getChatDb()?.getWcdb4Client()
|
||||
if (!wcdb) return []
|
||||
const { FavoritesService } = await import('./favorites-service')
|
||||
return new FavoritesService(wcdb).searchHits(query, limit)
|
||||
})
|
||||
setLocalQueryApiService(localQueryApiService)
|
||||
// Query Agent:生产 Runtime 只在这里实例化一次,桌面问问微信与 Agent Hub 共用同一个实例。
|
||||
queryAgentService = new QueryAgentService(
|
||||
|
||||
@@ -320,11 +320,23 @@ export class LocalQueryApiService {
|
||||
| ((conversationId: string, messageId: string) => { state: string; text: string } | undefined)
|
||||
| undefined
|
||||
|
||||
/** 收藏关键词检索(favorite.db,只读)。 */
|
||||
private favoritesSearch:
|
||||
| ((query: string, limit: number) => Promise<Array<{ text: string; timestamp?: number }>>)
|
||||
| undefined
|
||||
|
||||
constructor(
|
||||
private readonly knowledge?: KnowledgeSearchService,
|
||||
private readonly nowProvider: () => Date = () => new Date()
|
||||
) {}
|
||||
|
||||
/** 注入收藏检索(`FavoritesService.searchHits`),供 `search_messages` 合并只读命中。 */
|
||||
setFavoritesSearchProvider(
|
||||
provider: (query: string, limit: number) => Promise<Array<{ text: string; timestamp?: number }>>
|
||||
): void {
|
||||
this.favoritesSearch = provider
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入图片文字索引覆盖度提供者。
|
||||
*
|
||||
@@ -507,14 +519,30 @@ export class LocalQueryApiService {
|
||||
enrichmentMs,
|
||||
...(knowledgeTiming ? { knowledge: knowledgeTiming } : {})
|
||||
}
|
||||
let favoriteEvidence: QueryEvidenceItem[] = []
|
||||
if (this.favoritesSearch && !targetView) {
|
||||
try {
|
||||
const hits = await this.favoritesSearch(request.query, Math.min(10, limit))
|
||||
favoriteEvidence = hits.map((hit, index) => ({
|
||||
messageRef: `fav-${index}`,
|
||||
conversationName: '收藏',
|
||||
timestamp: hit.timestamp ?? 0,
|
||||
sender: '收藏',
|
||||
sourceKind: 'other' as QueryMessageType,
|
||||
text: hit.text
|
||||
}))
|
||||
} catch {
|
||||
favoriteEvidence = []
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 'completed' as const,
|
||||
...(targetView ? { target: targetView } : {}),
|
||||
resolvedTimeRange: range,
|
||||
coverage: { state: this.searchCoverage(found, requestedEnd) },
|
||||
probeCount: probes.length,
|
||||
evidenceCount: found.evidence.size,
|
||||
evidence: Array.from(found.evidence.values()).slice(0, limit),
|
||||
evidenceCount: found.evidence.size + favoriteEvidence.length,
|
||||
evidence: [...Array.from(found.evidence.values()).slice(0, limit), ...favoriteEvidence],
|
||||
scope: corpus.scope,
|
||||
indexLatestAt: found.indexLatestAt,
|
||||
sourceLatestAt: found.sourceLatestAt,
|
||||
|
||||
@@ -38,6 +38,8 @@ export interface ExportRequest {
|
||||
kinds: ExportMessageKind[]
|
||||
includeMedia: boolean
|
||||
includeVoiceTranscripts?: boolean
|
||||
/** 合并本机收藏(favorite.db)只读卡片。 */
|
||||
includeFavorites?: boolean
|
||||
preferOriginal?: boolean
|
||||
fallbackThumbnail?: boolean
|
||||
keepMissing?: boolean
|
||||
|
||||
Reference in New Issue
Block a user