feat: 新增mac ocr转文字,增加到问一问微信 图片索引优化速度

- 图片文字索引性能与进度诚实化
- 问问微信:证据卡区分「消息类型」与「派生来源」,派生命中内容自报来源
- 问问微信:回答规则禁止未真实执行的多轮承诺
- 本地图片文字识别:支持 macOS 系统 OCR(Apple Vision)
This commit is contained in:
Wxw-Gu
2026-09-17 13:11:20 +08:00
parent b8f08d54c8
commit 1337bcb7de
53 changed files with 4985 additions and 624 deletions
@@ -0,0 +1,102 @@
/**
* 大会话读取的性能日志与隐私契约。
*
* 这一组锁两件事:
* 1. 大会话必须留下**可归因**的一行(谁读的 / 各阶段耗时 / 行数),
* 否则"图片索引卡住 10 秒"永远只能靠猜;
* 2. 那一行里**不能**出现会话 md5 —— 稳定会话标识不进日志。
*
* 单独一个文件:`chat-service.test.ts` 里会调用 `closeChatDbForQuit()`,
* 那会把进程级的关闭标志置上,后续任何 `setChatDb` 都会被拒。
*/
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { WechatDb } from '../../src/main/wechat-db'
import { listMessagesAsync, setChatDb } from '../../src/main/services/chat-service'
const FIXTURE_MD5 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
const makeMessages = (count: number): Array<Record<string, unknown>> =>
Array.from({ length: count }, (_, index) => ({
messageType: '1',
msgCreateTime: String(1_700_000_000 + index),
mesDes: '0',
mesLocalID: String(index + 1),
msgContent: '普通文本',
sender: 'wxid_fixture',
serverId: String(index + 1)
}))
const installDb = (raw: Array<Record<string, unknown>>): void => {
const fakeDb = {
close: vi.fn(),
md5: () => FIXTURE_MD5,
getWcdb4Client: () => ({
getUsernameByMd5: () => 'fixture@chatroom',
resolveEmoticonCdnUrl: () => ''
}),
getUserMessagesAsync: vi.fn(async () => raw)
} as unknown as WechatDb
setChatDb(fakeDb)
}
const perfLines = (log: ReturnType<typeof vi.spyOn>): string[] =>
log.mock.calls
.map((call) => String(call[0] ?? ''))
.filter((message) => message.startsWith('[ChatServicePerf]'))
describe('chat service listMessages perf log', () => {
afterEach(() => setChatDb(null))
it('leaves one attributable line for a large read, without the conversation md5', async () => {
installDb(makeMessages(20_000))
const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
try {
await listMessagesAsync(
FIXTURE_MD5,
undefined,
undefined,
undefined,
undefined,
'image-text-index'
)
const lines = perfLines(log)
expect(lines).toHaveLength(1)
const line = lines[0]
expect(line).toContain('caller=image-text-index')
expect(line).toContain('rows=20000')
expect(line).toContain('rawRows=20000')
// 拆解字段必须齐全,否则"这几秒花在哪"还是答不出来。
for (const field of [
'totalMs=',
'rawReadMs=',
'formatMs=',
'dateFormatMs=',
'contentParseMs=',
'sortMs=',
'otherMs='
]) {
expect(line).toContain(field)
}
// 隐私:稳定会话标识绝不出现。
expect(line).not.toContain(FIXTURE_MD5)
expect(line).not.toContain('md5')
// 关联用进程内序号(`request-N`),不是稳定标识。
expect(line).toMatch(/request=request-\d+/)
} finally {
log.mockRestore()
}
})
it('stays silent for a small read so normal usage does not spam the log', async () => {
installDb(makeMessages(10))
const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
try {
await listMessagesAsync(FIXTURE_MD5, undefined, undefined, undefined, undefined, 'knowledge')
expect(perfLines(log)).toEqual([])
} finally {
log.mockRestore()
}
})
})