mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 11:37:06 +08:00
feat: 完善群聊日报语音转写缓存
支持空内容的微信语音消息并兼容历史缓存转写结果 将系统通知标记为微信系统消息,排除活跃成员与发言排行
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseGroupDailyReport } from '../../src/renderer/src/utils/group-report'
|
||||
import {
|
||||
buildGroupReportInput,
|
||||
parseGroupDailyReport
|
||||
} from '../../src/renderer/src/utils/group-report'
|
||||
import { summaryContent } from '../../src/renderer/src/utils/group-report-facts'
|
||||
import { summarySender } from '../../src/renderer/src/utils/group-report-facts'
|
||||
import { selectHeroParticipantNames } from '../../src/shared/group-report'
|
||||
import type { GroupReportMetadata } from '../../src/shared/group-report'
|
||||
import type { Message } from '../../src/shared/types'
|
||||
|
||||
@@ -25,6 +30,12 @@ const media = {
|
||||
}
|
||||
|
||||
describe('group report parsing', () => {
|
||||
it('keeps only distinct real participants in the hero avatar list', () => {
|
||||
expect(
|
||||
selectHeroParticipantNames(['濑岛田井卫', '测试群昵称', '濑岛田井卫', '', ' '])
|
||||
).toEqual(['濑岛田井卫', '测试群昵称'])
|
||||
})
|
||||
|
||||
it('includes a cached voice transcript in the report input content', () => {
|
||||
const message: Message = {
|
||||
id: 'voice-1',
|
||||
@@ -40,6 +51,68 @@ describe('group report parsing', () => {
|
||||
expect(summaryContent(message)).toContain('今晚八点确认发布。')
|
||||
})
|
||||
|
||||
it('includes a voice transcript when legacy cached messages have no contentData', () => {
|
||||
const message: Message = {
|
||||
id: 'voice-legacy',
|
||||
from: 'member',
|
||||
type: '语音',
|
||||
datetime: '2026-08-11 10:00:00',
|
||||
content: '[语音消息]',
|
||||
isSender: false,
|
||||
voiceTranscript: '能不能听见这个语音?'
|
||||
}
|
||||
|
||||
expect(summaryContent(message)).toBe('[语音] 能不能听见这个语音?')
|
||||
})
|
||||
|
||||
it('passes legacy voice transcripts into the daily-report model prompt', async () => {
|
||||
const message: Message = {
|
||||
id: 'voice-prompt',
|
||||
from: 'member',
|
||||
type: '语音',
|
||||
datetime: '2026-08-11 10:00:00',
|
||||
content: '[语音消息]',
|
||||
isSender: false,
|
||||
name: '测试成员',
|
||||
voiceTranscript: '试一下好不好使?'
|
||||
}
|
||||
|
||||
const input = await buildGroupReportInput([message], null, true, 'full')
|
||||
|
||||
expect(input.prompt).toContain('[语音] 试一下好不好使?')
|
||||
})
|
||||
|
||||
it('labels system notices separately and excludes them from active members', async () => {
|
||||
const systemMessage: Message = {
|
||||
id: 'system-1',
|
||||
from: 'system',
|
||||
type: '系统消息',
|
||||
datetime: '2026-08-11 09:59:00',
|
||||
content: '由于账号安全原因,无法加入当前群聊。',
|
||||
isSender: false,
|
||||
contentData: {
|
||||
type: 'system',
|
||||
content: '由于账号安全原因,无法加入当前群聊。'
|
||||
}
|
||||
}
|
||||
const memberMessage: Message = {
|
||||
id: 'member-1',
|
||||
from: 'member',
|
||||
type: '普通文本',
|
||||
datetime: '2026-08-11 10:00:00',
|
||||
content: '收到',
|
||||
name: '测试成员',
|
||||
isSender: false
|
||||
}
|
||||
|
||||
expect(summarySender(systemMessage, null, true)).toBe('微信系统消息')
|
||||
const input = await buildGroupReportInput([systemMessage, memberMessage], null, true, 'full')
|
||||
|
||||
expect(input.metadata.activeUsers).toBe(1)
|
||||
expect(input.topSpeakers).toEqual([{ name: '测试成员', count: 1 }])
|
||||
expect(input.prompt).toContain('微信系统消息:由于账号安全原因,无法加入当前群聊。')
|
||||
})
|
||||
|
||||
it('falls back to topic keywords when the model omits top-level keywords', () => {
|
||||
const report = parseGroupDailyReport(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('message parser', () => {
|
||||
md5: '0123456789abcdef0123456789abcdef'
|
||||
})
|
||||
expect(parseMessageContent('voice fixture', 34)).toEqual({ type: 'voice' })
|
||||
expect(parseMessageContent('', 34)).toEqual({ type: 'voice' })
|
||||
expect(
|
||||
parseMessageContent(
|
||||
'<emoji md5="abcdefabcdefabcdefabcdefabcdefab" cdnurl="https://fixture.invalid/a" />',
|
||||
|
||||
@@ -74,6 +74,20 @@ describe('daily report voice transcription', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('normalizes legacy voice messages without contentData for report facts', async () => {
|
||||
const legacy = { ...voice('legacy', 4), contentData: undefined }
|
||||
const result = await transcribeVoiceMessages([legacy], {
|
||||
getModelStatus: vi.fn(async () => status('ready')),
|
||||
recognize: vi.fn(async () => ({ success: true, transcript: '日报语音内容' })),
|
||||
onProgress: vi.fn()
|
||||
})
|
||||
|
||||
expect(result[0]).toMatchObject({
|
||||
voiceTranscript: '日报语音内容',
|
||||
contentData: { type: 'voice' }
|
||||
})
|
||||
})
|
||||
|
||||
it('does not require the model when every transcript is cached', async () => {
|
||||
const getModelStatus = vi.fn(async () => status('missing'))
|
||||
const recognize = vi.fn()
|
||||
@@ -88,6 +102,26 @@ describe('daily report voice transcription', () => {
|
||||
expect(recognize).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('hydrates persisted transcript cache before invoking recognition', async () => {
|
||||
const getModelStatus = vi.fn(async () => status('missing'))
|
||||
const getCachedTranscript = vi.fn(async () => ({
|
||||
state: 'transcribed' as const,
|
||||
transcript: '持久化缓存内容'
|
||||
}))
|
||||
const recognize = vi.fn()
|
||||
const result = await transcribeVoiceMessages([voice('persisted', 8)], {
|
||||
getModelStatus,
|
||||
getCachedTranscript,
|
||||
recognize,
|
||||
onProgress: vi.fn()
|
||||
})
|
||||
|
||||
expect(result[0].voiceTranscript).toBe('持久化缓存内容')
|
||||
expect(getCachedTranscript).toHaveBeenCalledOnce()
|
||||
expect(getModelStatus).not.toHaveBeenCalled()
|
||||
expect(recognize).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('stops before recognition when the local model is not ready', async () => {
|
||||
const recognize = vi.fn()
|
||||
await expect(
|
||||
|
||||
Reference in New Issue
Block a user