fix: 修复媒体导出与 HTML 聊天档案体验

- 修复打包版图片、语音解析不可用,内置 FFmpeg 与必要运行时依赖
- 修复 HTML 导出原图查找、缩略图回退及增量档案媒体解析问题
- 修复本人昵称在软件和 HTML 导出中显示为微信号的问题,并兼容旧档案昵称迁移
- 修复 HTML 语音播放器超出消息气泡的问题
- 优化 HTML 双向滚动加载,大量消息时最多渲染 240 条,避免页面卡顿
- 移除图片解密页面中手动配置 FFmpeg 的相关提示
- 补充图片解密、语音运行时、打包资源和 HTML 导出相关测试
This commit is contained in:
Wxw-Gu
2026-08-04 17:03:07 +08:00
parent c6587c517a
commit 0a3d930298
24 changed files with 949 additions and 515 deletions
+52 -21
View File
@@ -1,9 +1,52 @@
import { app } from 'electron'
import { join } from 'path'
import { existsSync } from 'fs'
import { createRequire } from 'module'
import { Wcdb4Client } from './wcdb4-client'
import { isPackagedRuntime } from './runtime-mode'
const nodeRequire = createRequire(import.meta.url)
export type SilkWasmRuntimeLocation = {
packagePath: string
wasmPath: string
source: 'unpacked' | 'resources' | 'asar' | 'development'
}
export function getSilkWasmRuntimeLocations(options?: {
packaged?: boolean
resourcesPath?: string
appPath?: string
}): SilkWasmRuntimeLocation[] {
const packaged = options?.packaged ?? isPackagedRuntime()
const resourcesPath = options?.resourcesPath ?? process.resourcesPath
const appPath = options?.appPath ?? app.getAppPath()
const location = (
packagePath: string,
source: SilkWasmRuntimeLocation['source']
): SilkWasmRuntimeLocation => ({
packagePath,
wasmPath: join(packagePath, 'lib', 'silk.wasm'),
source
})
if (!packaged) {
return [location(join(appPath, 'node_modules', 'silk-wasm'), 'development')]
}
return [
location(join(resourcesPath, 'app.asar.unpacked', 'node_modules', 'silk-wasm'), 'unpacked'),
location(join(resourcesPath, 'node_modules', 'silk-wasm'), 'resources'),
location(join(appPath, 'node_modules', 'silk-wasm'), 'asar')
]
}
export function findSilkWasmRuntimeLocation(
locations: SilkWasmRuntimeLocation[]
): SilkWasmRuntimeLocation | null {
return locations.find((location) => existsSync(location.wasmPath)) || null
}
export class VoiceService {
private wcdb4Client: Wcdb4Client
private voiceCache = new Map<string, string>()
@@ -101,35 +144,23 @@ export class VoiceService {
private async decodeSilkToPcm(silkData: Buffer, sampleRate: number): Promise<Buffer | null> {
try {
let wasmPath: string
if (isPackagedRuntime()) {
wasmPath = join(
process.resourcesPath,
'app.asar.unpacked',
'node_modules',
'silk-wasm',
'lib',
'silk.wasm'
const locations = getSilkWasmRuntimeLocations()
const runtime = findSilkWasmRuntimeLocation(locations)
if (!runtime) {
console.error(
'[VoiceService] silk.wasm not found. checked:',
locations.map((location) => location.wasmPath)
)
if (!existsSync(wasmPath)) {
wasmPath = join(process.resourcesPath, 'node_modules', 'silk-wasm', 'lib', 'silk.wasm')
}
} else {
wasmPath = join(app.getAppPath(), 'node_modules', 'silk-wasm', 'lib', 'silk.wasm')
}
if (!existsSync(wasmPath)) {
console.error('[VoiceService] silk.wasm not found at:', wasmPath)
return null
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
const silkWasm = require('silk-wasm')
const silkWasm = nodeRequire(runtime.packagePath)
if (!silkWasm || !silkWasm.decode) {
console.error('[VoiceService] silk-wasm module invalid')
console.error('[VoiceService] silk-wasm module invalid:', runtime.packagePath)
return null
}
console.log('[VoiceService] using silk-wasm runtime:', runtime.source)
const result = await silkWasm.decode(silkData, sampleRate)
return Buffer.from(result.data)
} catch (e) {