fix: 清理导出服务日志并简化whisper接口参数

- 移除exportService中的冗余console日志输出
- 简化whisper API接口,移除downloadModel和getModelStatus的payload参数
- 清理图片、表情、语音导出过程中的调试日志
- 移除数据库查询和媒体处理中的详细日志记录
- 优化代码可读性,减少控制台输出噪音
This commit is contained in:
Forrest
2026-01-17 16:24:18 +08:00
parent 82ba0344b9
commit dc12df0fcf
9 changed files with 33 additions and 104 deletions

View File

@@ -184,10 +184,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
}, },
whisper: { whisper: {
downloadModel: (payload: { modelName: string; downloadDir?: string; source?: string }) => downloadModel: () =>
ipcRenderer.invoke('whisper:downloadModel', payload), ipcRenderer.invoke('whisper:downloadModel'),
getModelStatus: (payload: { modelName: string; downloadDir?: string }) => getModelStatus: () =>
ipcRenderer.invoke('whisper:getModelStatus', payload), ipcRenderer.invoke('whisper:getModelStatus'),
onDownloadProgress: (callback: (payload: { modelName: string; downloadedBytes: number; totalBytes?: number; percent?: number }) => void) => { onDownloadProgress: (callback: (payload: { modelName: string; downloadedBytes: number; totalBytes?: number; percent?: number }) => void) => {
ipcRenderer.on('whisper:downloadProgress', (_, payload) => callback(payload)) ipcRenderer.on('whisper:downloadProgress', (_, payload) => callback(payload))
return () => ipcRenderer.removeAllListeners('whisper:downloadProgress') return () => ipcRenderer.removeAllListeners('whisper:downloadProgress')

View File

@@ -1,4 +1,4 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import * as http from 'http' import * as http from 'http'
import * as https from 'https' import * as https from 'https'
@@ -364,7 +364,6 @@ class ExportService {
return `[${callType}]` return `[${callType}]`
} catch (e) { } catch (e) {
console.error('[ExportService] Failed to parse VOIP message:', e)
return '[通话]' return '[通话]'
} }
} }
@@ -417,7 +416,6 @@ class ExportService {
if (localType === 3 && options.exportImages) { if (localType === 3 && options.exportImages) {
const result = await this.exportImage(msg, sessionId, mediaDir) const result = await this.exportImage(msg, sessionId, mediaDir)
if (result) { if (result) {
console.log('[ExportService] 图片导出成功:', result.relativePath)
} }
return result return result
} }
@@ -438,7 +436,6 @@ class ExportService {
if (localType === 47 && options.exportEmojis) { if (localType === 47 && options.exportEmojis) {
const result = await this.exportEmoji(msg, sessionId, mediaDir) const result = await this.exportEmoji(msg, sessionId, mediaDir)
if (result) { if (result) {
console.log('[ExportService] 表情导出成功:', result.relativePath)
} }
return result return result
} }
@@ -461,12 +458,9 @@ class ExportService {
const imageDatName = msg.imageDatName const imageDatName = msg.imageDatName
if (!imageMd5 && !imageDatName) { if (!imageMd5 && !imageDatName) {
console.log('[ExportService] 图片消息缺少 md5 和 datName:', msg.localId)
return null return null
} }
console.log('[ExportService] 导出图片:', { localId: msg.localId, imageMd5, imageDatName, sessionId })
const result = await imageDecryptService.decryptImage({ const result = await imageDecryptService.decryptImage({
sessionId, sessionId,
imageMd5, imageMd5,
@@ -524,7 +518,6 @@ class ExportService {
return null return null
} catch (e) { } catch (e) {
console.error('[ExportService] 导出图片失败:', e)
return null return null
} }
} }
@@ -545,19 +538,15 @@ class ExportService {
// 如果已存在则跳过 // 如果已存在则跳过
if (fs.existsSync(destPath)) { if (fs.existsSync(destPath)) {
console.log('[ExportService] 语音已存在:', destPath)
return { return {
relativePath: `media/voices/${fileName}`, relativePath: `media/voices/${fileName}`,
kind: 'voice' kind: 'voice'
} }
} }
console.log('[ExportService] 导出语音:', { localId: msg.localId, sessionId })
// 调用 chatService 获取语音数据 // 调用 chatService 获取语音数据
const voiceResult = await chatService.getVoiceData(sessionId, msgId) const voiceResult = await chatService.getVoiceData(sessionId, msgId)
if (!voiceResult.success || !voiceResult.data) { if (!voiceResult.success || !voiceResult.data) {
console.log('[ExportService] 获取语音数据失败:', voiceResult.error)
return null return null
} }
@@ -565,13 +554,11 @@ class ExportService {
const wavBuffer = Buffer.from(voiceResult.data, 'base64') const wavBuffer = Buffer.from(voiceResult.data, 'base64')
fs.writeFileSync(destPath, wavBuffer) fs.writeFileSync(destPath, wavBuffer)
console.log('[ExportService] 语音导出成功:', destPath)
return { return {
relativePath: `media/voices/${fileName}`, relativePath: `media/voices/${fileName}`,
kind: 'voice' kind: 'voice'
} }
} catch (e) { } catch (e) {
console.error('[ExportService] 导出语音失败:', e)
return null return null
} }
} }
@@ -587,7 +574,6 @@ class ExportService {
} }
return '[语音消息 - 转文字失败]' return '[语音消息 - 转文字失败]'
} catch (e) { } catch (e) {
console.error('[ExportService] 语音转文字失败:', e)
return '[语音消息 - 转文字失败]' return '[语音消息 - 转文字失败]'
} }
} }
@@ -625,7 +611,6 @@ class ExportService {
// 如果已存在则跳过 // 如果已存在则跳过
if (fs.existsSync(destPath)) { if (fs.existsSync(destPath)) {
console.log('[ExportService] 表情已存在:', destPath)
return { return {
relativePath: `media/emojis/${fileName}`, relativePath: `media/emojis/${fileName}`,
kind: 'emoji' kind: 'emoji'
@@ -634,22 +619,18 @@ class ExportService {
// 下载表情 // 下载表情
if (emojiUrl) { if (emojiUrl) {
console.log('[ExportService] 开始下载表情:', emojiUrl)
const downloaded = await this.downloadFile(emojiUrl, destPath) const downloaded = await this.downloadFile(emojiUrl, destPath)
if (downloaded) { if (downloaded) {
console.log('[ExportService] 表情下载成功:', destPath)
return { return {
relativePath: `media/emojis/${fileName}`, relativePath: `media/emojis/${fileName}`,
kind: 'emoji' kind: 'emoji'
} }
} else { } else {
console.log('[ExportService] 表情下载失败:', emojiUrl)
} }
} }
return null return null
} catch (e) { } catch (e) {
console.error('[ExportService] 导出表情失败:', e)
return null return null
} }
} }
@@ -823,12 +804,10 @@ class ExportService {
// 图片消息 // 图片消息
imageMd5 = this.extractImageMd5(content) imageMd5 = this.extractImageMd5(content)
imageDatName = this.extractImageDatName(content) imageDatName = this.extractImageDatName(content)
console.log('[ExportService] 提取图片字段:', { localId, imageMd5, imageDatName })
} else if (localType === 47 && content) { } else if (localType === 47 && content) {
// 动画表情 // 动画表情
emojiCdnUrl = this.extractEmojiUrl(content) emojiCdnUrl = this.extractEmojiUrl(content)
emojiMd5 = this.extractEmojiMd5(content) emojiMd5 = this.extractEmojiMd5(content)
console.log('[ExportService] 提取表情字段:', { localId, emojiMd5, emojiCdnUrl: emojiCdnUrl?.substring(0, 100) })
} }
rows.push({ rows.push({
@@ -1233,7 +1212,6 @@ class ExportService {
return { success: true } return { success: true }
} catch (e) { } catch (e) {
console.error('ExportService: 导出失败:', e)
return { success: false, error: String(e) } return { success: false, error: String(e) }
} }
} }
@@ -1348,7 +1326,6 @@ class ExportService {
return { success: true } return { success: true }
} catch (e) { } catch (e) {
console.error('ExportService: 导出失败:', e)
return { success: false, error: String(e) } return { success: false, error: String(e) }
} }
} }
@@ -1570,13 +1547,6 @@ class ExportService {
// 调试日志 // 调试日志
if (msg.localType === 3 || msg.localType === 47) { if (msg.localType === 3 || msg.localType === 47) {
console.log('[ExportService] 媒体消息填充表格:', {
localId: msg.localId,
localType: msg.localType,
hasMediaItem: !!mediaItem,
mediaRelativePath: mediaItem?.relativePath,
contentValue: contentValue?.substring(0, 100)
})
} }
worksheet.getCell(currentRow, 1).value = i + 1 worksheet.getCell(currentRow, 1).value = i + 1
@@ -1628,8 +1598,6 @@ class ExportService {
return { success: true } return { success: true }
} catch (e) { } catch (e) {
console.error('ExportService: 导出 Excel 失败:', e)
// 处理文件被占用的错误 // 处理文件被占用的错误
if (e instanceof Error) { if (e instanceof Error) {
if (e.message.includes('EBUSY') || e.message.includes('resource busy') || e.message.includes('locked')) { if (e.message.includes('EBUSY') || e.message.includes('resource busy') || e.message.includes('locked')) {
@@ -1721,3 +1689,4 @@ class ExportService {
} }
export const exportService = new ExportService() export const exportService = new ExportService()

View File

@@ -1,4 +1,4 @@
import { app } from 'electron' import { app } from 'electron'
import { existsSync, mkdirSync, statSync, unlinkSync, createWriteStream } from 'fs' import { existsSync, mkdirSync, statSync, unlinkSync, createWriteStream } from 'fs'
import { join } from 'path' import { join } from 'path'
import * as https from 'https' import * as https from 'https'
@@ -98,7 +98,6 @@ export class VoiceTranscribeService {
sizeBytes: totalSize sizeBytes: totalSize
} }
} catch (error) { } catch (error) {
console.error('[VoiceTranscribe] getModelStatus error:', error)
return { success: false, error: String(error) } return { success: false, error: String(error) }
} }
} }
@@ -125,7 +124,6 @@ export class VoiceTranscribeService {
const vadPath = this.resolveModelPath((SENSEVOICE_MODEL.files as any).vad) const vadPath = this.resolveModelPath((SENSEVOICE_MODEL.files as any).vad)
// 下载模型文件 (40%) // 下载模型文件 (40%)
console.info('[VoiceTranscribe] 开始下载模型文件...')
await this.downloadToFile( await this.downloadToFile(
MODEL_DOWNLOAD_URLS.model, MODEL_DOWNLOAD_URLS.model,
modelPath, modelPath,
@@ -142,7 +140,6 @@ export class VoiceTranscribeService {
) )
// 下载 tokens 文件 (30%) // 下载 tokens 文件 (30%)
console.info('[VoiceTranscribe] 开始下载 tokens 文件...')
await this.downloadToFile( await this.downloadToFile(
MODEL_DOWNLOAD_URLS.tokens, MODEL_DOWNLOAD_URLS.tokens,
tokensPath, tokensPath,
@@ -160,7 +157,6 @@ export class VoiceTranscribeService {
) )
// 下载 vad 文件 (30%) // 下载 vad 文件 (30%)
console.info('[VoiceTranscribe] 开始下载 VAD 文件...')
await this.downloadToFile( await this.downloadToFile(
(MODEL_DOWNLOAD_URLS as any).vad, (MODEL_DOWNLOAD_URLS as any).vad,
vadPath, vadPath,
@@ -178,10 +174,8 @@ export class VoiceTranscribeService {
} }
) )
console.info('[VoiceTranscribe] 模型下载完成')
return { success: true, modelPath, tokensPath } return { success: true, modelPath, tokensPath }
} catch (error) { } catch (error) {
console.error('[VoiceTranscribe] 下载失败:', error)
const modelPath = this.resolveModelPath(SENSEVOICE_MODEL.files.model) const modelPath = this.resolveModelPath(SENSEVOICE_MODEL.files.model)
const tokensPath = this.resolveModelPath(SENSEVOICE_MODEL.files.tokens) const tokensPath = this.resolveModelPath(SENSEVOICE_MODEL.files.tokens)
const vadPath = this.resolveModelPath((SENSEVOICE_MODEL.files as any).vad) const vadPath = this.resolveModelPath((SENSEVOICE_MODEL.files as any).vad)
@@ -221,8 +215,6 @@ export class VoiceTranscribeService {
// main.js 和 transcribeWorker.js 同在 dist-electron 目录下 // main.js 和 transcribeWorker.js 同在 dist-electron 目录下
const workerPath = join(__dirname, 'transcribeWorker.js') const workerPath = join(__dirname, 'transcribeWorker.js')
console.info('[VoiceTranscribe] 启动后台 Worker 转写...', { workerPath })
const worker = new Worker(workerPath, { const worker = new Worker(workerPath, {
workerData: { workerData: {
modelPath, modelPath,
@@ -248,7 +240,6 @@ export class VoiceTranscribeService {
}) })
worker.on('error', (err: Error) => { worker.on('error', (err: Error) => {
console.error('[VoiceTranscribe] Worker error:', err)
resolve({ success: false, error: String(err) }) resolve({ success: false, error: String(err) })
}) })
@@ -260,7 +251,6 @@ export class VoiceTranscribeService {
}) })
} catch (error) { } catch (error) {
console.error('[VoiceTranscribe] 启动 Worker 失败:', error)
resolve({ success: false, error: String(error) }) resolve({ success: false, error: String(error) })
} }
}) })
@@ -350,10 +340,10 @@ export class VoiceTranscribeService {
// sherpa-onnx 的 recognizer 可能需要手动释放 // sherpa-onnx 的 recognizer 可能需要手动释放
this.recognizer = null this.recognizer = null
} catch (error) { } catch (error) {
console.error('[VoiceTranscribe] 释放识别器失败:', error)
} }
} }
} }
} }
export const voiceTranscribeService = new VoiceTranscribeService() export const voiceTranscribeService = new VoiceTranscribeService()

View File

@@ -58,11 +58,13 @@ export class WcdbService {
}) })
this.worker.on('error', (err) => { this.worker.on('error', (err) => {
console.error('WCDB Worker 错误:', err) // Worker error
}) })
this.worker.on('exit', (code) => { this.worker.on('exit', (code) => {
if (code !== 0) console.error(`WCDB Worker 异常退出,退出码: ${code}`) if (code !== 0) {
// Worker exited with error
}
this.worker = null this.worker = null
}) })
@@ -73,7 +75,7 @@ export class WcdbService {
this.setLogEnabled(this.logEnabled) this.setLogEnabled(this.logEnabled)
} catch (e) { } catch (e) {
console.error('创建 WCDB Worker 失败:', e) // Failed to create worker
} }
} }
@@ -97,7 +99,7 @@ export class WcdbService {
setPaths(resourcesPath: string, userDataPath: string): void { setPaths(resourcesPath: string, userDataPath: string): void {
this.resourcesPath = resourcesPath this.resourcesPath = resourcesPath
this.userDataPath = userDataPath this.userDataPath = userDataPath
this.callWorker('setPaths', { resourcesPath, userDataPath }).catch(console.error) this.callWorker('setPaths', { resourcesPath, userDataPath }).catch(() => {})
} }
/** /**
@@ -105,7 +107,7 @@ export class WcdbService {
*/ */
setLogEnabled(enabled: boolean): void { setLogEnabled(enabled: boolean): void {
this.logEnabled = enabled this.logEnabled = enabled
this.callWorker('setLogEnabled', { enabled }).catch(console.error) this.callWorker('setLogEnabled', { enabled }).catch(() => {})
} }
/** /**

View File

@@ -1,4 +1,4 @@
import { parentPort, workerData } from 'worker_threads' import { parentPort, workerData } from 'worker_threads'
import * as fs from 'fs' import * as fs from 'fs'
interface WorkerParams { interface WorkerParams {
@@ -9,37 +9,23 @@ interface WorkerParams {
} }
async function run() { async function run() {
console.info('[TranscribeWorker] Worker process starting...');
if (!parentPort) { if (!parentPort) {
console.error('[TranscribeWorker] Critical Error: parentPort is null');
return; return;
} }
try { try {
console.info('[TranscribeWorker] Loading sherpa-onnx-node...');
// 动态加载以捕获可能的加载错误(如 C++ 运行库缺失等) // 动态加载以捕获可能的加载错误(如 C++ 运行库缺失等)
let sherpa: any; let sherpa: any;
try { try {
sherpa = require('sherpa-onnx-node'); sherpa = require('sherpa-onnx-node');
console.info('[TranscribeWorker] sherpa-onnx-node loaded successfully.');
} catch (requireError) { } catch (requireError) {
console.error('[TranscribeWorker] Failed to load sherpa-onnx-node:', requireError);
parentPort.postMessage({ type: 'error', error: 'Failed to load speech engine: ' + String(requireError) }); parentPort.postMessage({ type: 'error', error: 'Failed to load speech engine: ' + String(requireError) });
return; return;
} }
const { modelPath, tokensPath, wavData: rawWavData, sampleRate } = workerData as WorkerParams const { modelPath, tokensPath, wavData: rawWavData, sampleRate } = workerData as WorkerParams
const wavData = Buffer.from(rawWavData); const wavData = Buffer.from(rawWavData);
console.info('[TranscribeWorker] Params received:', {
modelPath,
tokensPath,
sampleRate,
wavDataLength: wavData?.length
});
// 1. 初始化识别器 (SenseVoiceSmall) // 1. 初始化识别器 (SenseVoiceSmall)
console.info('[TranscribeWorker] Initializing OfflineRecognizer...');
const recognizerConfig = { const recognizerConfig = {
modelConfig: { modelConfig: {
senseVoice: { senseVoice: {
@@ -52,12 +38,8 @@ async function run() {
} }
} }
const recognizer = new sherpa.OfflineRecognizer(recognizerConfig) const recognizer = new sherpa.OfflineRecognizer(recognizerConfig)
console.info('[TranscribeWorker] OfflineRecognizer initialized.');
// 2. 初始化 VAD (用于流式输出效果) // 2. 初始化 VAD (用于流式输出效果)
const vadPath = modelPath.replace('model.int8.onnx', 'silero_vad.onnx'); const vadPath = modelPath.replace('model.int8.onnx', 'silero_vad.onnx');
console.info('[TranscribeWorker] VAD Path:', vadPath);
const vadConfig = { const vadConfig = {
sileroVad: { sileroVad: {
model: vadPath, model: vadPath,
@@ -73,8 +55,6 @@ async function run() {
// 检查 VAD 模型是否存在,如果不存在则退回到全量识别 // 检查 VAD 模型是否存在,如果不存在则退回到全量识别
if (!fs.existsSync(vadPath)) { if (!fs.existsSync(vadPath)) {
console.warn('[TranscribeWorker] VAD model not found, falling back to full transcription.');
const pcmData = wavData.slice(44) const pcmData = wavData.slice(44)
const samples = new Float32Array(pcmData.length / 2) const samples = new Float32Array(pcmData.length / 2)
for (let i = 0; i < samples.length; i++) { for (let i = 0; i < samples.length; i++) {
@@ -86,15 +66,11 @@ async function run() {
recognizer.decode(stream) recognizer.decode(stream)
const result = recognizer.getResult(stream) const result = recognizer.getResult(stream)
console.info('[TranscribeWorker] Full transcription result:', result.text);
parentPort.postMessage({ type: 'final', text: result.text }) parentPort.postMessage({ type: 'final', text: result.text })
return return
} }
console.info('[TranscribeWorker] Initializing Vad...');
const vad = new sherpa.Vad(vadConfig, 60) // 60s max const vad = new sherpa.Vad(vadConfig, 60) // 60s max
console.info('[TranscribeWorker] VAD initialized.');
// 3. 处理音频数据 // 3. 处理音频数据
const pcmData = wavData.slice(44) const pcmData = wavData.slice(44)
const samples = new Float32Array(pcmData.length / 2) const samples = new Float32Array(pcmData.length / 2)
@@ -107,7 +83,6 @@ async function run() {
let offset = 0 let offset = 0
let accumulatedText = '' let accumulatedText = ''
console.info('[TranscribeWorker] Starting processing loop...');
let segmentCount = 0; let segmentCount = 0;
while (offset < samples.length) { while (offset < samples.length) {
@@ -120,9 +95,6 @@ async function run() {
while (!vad.isEmpty()) { while (!vad.isEmpty()) {
const segment = vad.front(false) const segment = vad.front(false)
// Log segment detection
console.info(`[TranscribeWorker] VAD Segment detected. Duration: ${segment.samples.length / sampleRate}s`);
const stream = recognizer.createStream() const stream = recognizer.createStream()
stream.acceptWaveform({ sampleRate, samples: segment.samples }) stream.acceptWaveform({ sampleRate, samples: segment.samples })
recognizer.decode(stream) recognizer.decode(stream)
@@ -133,7 +105,6 @@ async function run() {
if (text.length > 0) { if (text.length > 0) {
accumulatedText += (accumulatedText ? ' ' : '') + text accumulatedText += (accumulatedText ? ' ' : '') + text
segmentCount++; segmentCount++;
console.info(`[TranscribeWorker] Partial update #${segmentCount}: "${text}" -> Total: "${accumulatedText.substring(0, 50)}..."`);
parentPort.postMessage({ type: 'partial', text: accumulatedText }) parentPort.postMessage({ type: 'partial', text: accumulatedText })
} }
} }
@@ -149,26 +120,23 @@ async function run() {
vad.flush(); vad.flush();
while (!vad.isEmpty()) { while (!vad.isEmpty()) {
const segment = vad.front(false); const segment = vad.front(false);
console.info(`[TranscribeWorker] Final VAD Segment detected. Duration: ${segment.samples.length / sampleRate}s`);
const stream = recognizer.createStream() const stream = recognizer.createStream()
stream.acceptWaveform({ sampleRate, samples: segment.samples }) stream.acceptWaveform({ sampleRate, samples: segment.samples })
recognizer.decode(stream) recognizer.decode(stream)
const result = recognizer.getResult(stream) const result = recognizer.getResult(stream)
if (result.text) { if (result.text) {
accumulatedText += (accumulatedText ? ' ' : '') + result.text.trim() accumulatedText += (accumulatedText ? ' ' : '') + result.text.trim()
console.info(`[TranscribeWorker] Final partial update: "${result.text.trim()}"`);
parentPort.postMessage({ type: 'partial', text: accumulatedText }) parentPort.postMessage({ type: 'partial', text: accumulatedText })
} }
vad.pop(); vad.pop();
} }
console.info('[TranscribeWorker] Loop finished. Final text length:', accumulatedText.length);
parentPort.postMessage({ type: 'final', text: accumulatedText }) parentPort.postMessage({ type: 'final', text: accumulatedText })
} catch (error) { } catch (error) {
console.error('[TranscribeWorker] Fatal error:', error);
parentPort.postMessage({ type: 'error', error: String(error) }) parentPort.postMessage({ type: 'error', error: String(error) })
} }
} }
run(); run();

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "weflow", "name": "weflow",
"version": "1.1.2", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "weflow", "name": "weflow",
"version": "1.1.2", "version": "1.2.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"better-sqlite3": "^12.5.0", "better-sqlite3": "^12.5.0",

View File

@@ -1412,7 +1412,7 @@
.voice-transcript.sent { .voice-transcript.sent {
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
color: var(--text-primary); color: #333333;
border-color: transparent; border-color: transparent;
} }

View File

@@ -160,7 +160,7 @@ function ExportPage() {
exportImages: options.exportMedia && options.exportImages, exportImages: options.exportMedia && options.exportImages,
exportVoices: options.exportMedia && options.exportVoices, exportVoices: options.exportMedia && options.exportVoices,
exportEmojis: options.exportMedia && options.exportEmojis, exportEmojis: options.exportMedia && options.exportEmojis,
exportVoiceAsText: options.exportMedia && options.exportVoiceAsText, exportVoiceAsText: options.exportVoiceAsText, // 独立于 exportMedia
dateRange: options.useAllTime ? null : options.dateRange ? { dateRange: options.useAllTime ? null : options.dateRange ? {
start: Math.floor(options.dateRange.start.getTime() / 1000), start: Math.floor(options.dateRange.start.getTime() / 1000),
// 将结束日期设置为当天的 23:59:59,以包含当天的所有消息 // 将结束日期设置为当天的 23:59:59,以包含当天的所有消息
@@ -444,15 +444,14 @@ function ExportPage() {
<div className="media-option-divider"></div> <div className="media-option-divider"></div>
<label className={`media-checkbox-row ${!options.exportMedia ? 'disabled' : ''}`}> <label className="media-checkbox-row">
<div className="media-checkbox-info"> <div className="media-checkbox-info">
<span className="media-checkbox-title"></span> <span className="media-checkbox-title"></span>
<span className="media-checkbox-desc"></span> <span className="media-checkbox-desc"></span>
</div> </div>
<input <input
type="checkbox" type="checkbox"
checked={options.exportVoiceAsText} checked={options.exportVoiceAsText}
disabled={!options.exportMedia}
onChange={e => setOptions({ ...options, exportVoiceAsText: e.target.checked })} onChange={e => setOptions({ ...options, exportVoiceAsText: e.target.checked })}
/> />
</label> </label>

View File

@@ -32,7 +32,8 @@ export default defineConfig({
'koffi', 'koffi',
'fsevents', 'fsevents',
'whisper-node', 'whisper-node',
'shelljs' 'shelljs',
'exceljs'
] ]
} }
} }