From 67b23f687fd364e963436e61a5ff92b61d27a7fb Mon Sep 17 00:00:00 2001 From: crosswk Date: Wed, 29 Jul 2026 22:19:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=B3=E9=97=AD=E5=8E=86?= =?UTF-8?q?=E5=8F=B2=E8=AE=B0=E5=BD=95=E5=BC=80=E5=85=B3=EF=BC=9A=E5=8F=AF?= =?UTF-8?q?=E5=9C=A8=E8=AE=BE=E7=BD=AE=E4=B8=AD=E5=81=9C=E6=AD=A2=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=EF=BC=88=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=BC=80=E5=90=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/settings/GeneralSettingsPage.tsx | 17 ++++- client/src/services/defaults.ts | 1 + .../services/recorder/RecorderOrchestrator.ts | 66 ++++++++++--------- 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/client/src/features/settings/GeneralSettingsPage.tsx b/client/src/features/settings/GeneralSettingsPage.tsx index 79e7273..a5b4506 100644 --- a/client/src/features/settings/GeneralSettingsPage.tsx +++ b/client/src/features/settings/GeneralSettingsPage.tsx @@ -29,6 +29,7 @@ export default function GeneralSettingsPage() { const [protectClipboard, setProtectClipboard] = useState(true) const [pttKey, setPttKey] = useState('AltLeft') const [handsFreeKey, setHandsFreeKey] = useState('Alt+L') + const [historyEnabled, setHistoryEnabled] = useState(true) const [audioRetentionEnabled, setAudioRetentionEnabled] = useState(true) const [audioRetentionDays, setAudioRetentionDays] = useState(30) const [logRetentionDays, setLogRetentionDays] = useState(30) @@ -45,6 +46,7 @@ export default function GeneralSettingsPage() { getSetting('protectClipboard', true).then((value) => setProtectClipboard(Boolean(value))) getSetting('shortcutPTT', 'AltRight').then((value) => setPttKey(value as string)) getSetting('shortcutHandsFree', 'Alt+L').then((value) => setHandsFreeKey(value as string)) + getSetting('historyEnabled', true).then((value) => setHistoryEnabled(Boolean(value))) getSetting('audioRetentionEnabled', true).then((value) => setAudioRetentionEnabled(Boolean(value))) getSetting('readySoundEnabled', true).then((value) => setReadySoundEnabled(Boolean(value))) getSetting('audioRetentionDays', -1).then((value) => { @@ -55,7 +57,7 @@ export default function GeneralSettingsPage() { const v = Number(value) if (v === 7 || v === 15 || v === 30 || v === 90) setLogRetentionDays(v) }) - listMicrophones().then(setMics).catch(() => {}) + listMicrophones().then(setMics).catch(() => { }) }, []) const toggleAutoLaunch = async () => { const next = !autoLaunch; setAutoLaunch(next); await bridge.setAutoLaunch(next) } @@ -63,6 +65,7 @@ export default function GeneralSettingsPage() { const handleMicChange = async (deviceId: string) => { setSelectedMic(deviceId); await setSetting('selectedMic', deviceId); await refreshRecorderSettings() } const toggleMuteSystemAudio = async () => { const next = !muteSystemAudio; setMuteSystemAudio(next); await setSetting('muteSystemAudioWhileRecording', next); await refreshRecorderSettings() } const toggleProtectClipboard = async () => { const next = !protectClipboard; setProtectClipboard(next); await setSetting('protectClipboard', next); await refreshRecorderSettings() } + const toggleHistoryEnabled = async () => { const next = !historyEnabled; setHistoryEnabled(next); await setSetting('historyEnabled', next) } const toggleAudioRetention = async () => { const next = !audioRetentionEnabled; setAudioRetentionEnabled(next); await setSetting('audioRetentionEnabled', next) } const toggleReadySound = async () => { const next = !readySoundEnabled; setReadySoundEnabled(next); await setSetting('readySoundEnabled', next); await refreshRecorderSettings() } const handleAudioRetentionDaysChange = async (value: number) => { setAudioRetentionDays(value); await setSetting('audioRetentionDays', value) } @@ -173,6 +176,18 @@ export default function GeneralSettingsPage() { + + +
+
+

历史记录

+

保存每次转写的文本与录音到本地历史,可随时回看、复制和重新识别。关闭后不再保存新的记录(适合与他人共用的电脑);已有记录不会被删除,可在历史页手动清除。

+
+ void toggleHistoryEnabled()} /> +
+
+
+
diff --git a/client/src/services/defaults.ts b/client/src/services/defaults.ts index 1d0afa5..9adfaaf 100644 --- a/client/src/services/defaults.ts +++ b/client/src/services/defaults.ts @@ -69,6 +69,7 @@ export const DEFAULTS: Record = { // ── 应用设置 ── autoCheckUpdate: true, // 自动检查更新。可选: true | false + historyEnabled: true, // 保存历史记录(文本+录音)。关闭后不再保存新记录,适合共享电脑。可选: true | false audioRetentionEnabled: true, // 保留录音文件。可选: true | false audioRetentionDays: -1, // 录音保留天数。可选: 7 | 30 | 90 | -1(永久) logRetentionDays: 30, // 日志保留天数。可选: 7 | 15 | 30 | 90 diff --git a/client/src/services/recorder/RecorderOrchestrator.ts b/client/src/services/recorder/RecorderOrchestrator.ts index 5b557be..617ff6e 100644 --- a/client/src/services/recorder/RecorderOrchestrator.ts +++ b/client/src/services/recorder/RecorderOrchestrator.ts @@ -603,6 +603,7 @@ export class RecorderOrchestrator { const wallSec = this.wallTimeAtStopSec > 0 ? this.wallTimeAtStopSec : audioDur // Save audio even for empty results const saveAndRecord = async () => { + if (!(await getSetting('historyEnabled', true))) return let audioFilePath: string | undefined const recordId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6) const saveAudioEnabled = await getSetting('audioRetentionEnabled', true) @@ -703,6 +704,7 @@ export class RecorderOrchestrator { const wallSec = this.wallTimeAtStopSec > 0 ? this.wallTimeAtStopSec : audioDur if (audioDur >= 0.5 && this.recordedChunks.length > 0) { const saveErrorHistory = async () => { + if (!(await getSetting('historyEnabled', true))) return let audioFilePath: string | undefined const recordId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6) const saveAudioEnabled = await getSetting('audioRetentionEnabled', true) @@ -1353,6 +1355,7 @@ export class RecorderOrchestrator { this.timedOutProcessingContext = null void (async () => { try { + if (!(await getSetting('historyEnabled', true))) return let audioFilePath: string | undefined const recordId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6) const saveAudioEnabled = await getSetting('audioRetentionEnabled', true) @@ -1614,38 +1617,41 @@ export class RecorderOrchestrator { }) try { - // Save audio file if we have recorded chunks - let audioFilePath: string | undefined - const recordId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6) - const saveAudioEnabled = await getSetting('audioRetentionEnabled', true) - if (saveAudioEnabled && this.recordedChunks.length > 0) { - try { - const savedPath = await saveRecordingAudio(recordId, this.recordedChunks) - if (savedPath) audioFilePath = savedPath - } catch (err) { - addRuntimeEvent('warn', 'recorder', '保存音频文件失败', { error: String(err) }) + // 关闭历史记录时不保存任何记录与音频(共享电脑隐私),但仍完成文本插入 + if (await getSetting('historyEnabled', true)) { + // Save audio file if we have recorded chunks + let audioFilePath: string | undefined + const recordId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6) + const saveAudioEnabled = await getSetting('audioRetentionEnabled', true) + if (saveAudioEnabled && this.recordedChunks.length > 0) { + try { + const savedPath = await saveRecordingAudio(recordId, this.recordedChunks) + if (savedPath) audioFilePath = savedPath + } catch (err) { + addRuntimeEvent('warn', 'recorder', '保存音频文件失败', { error: String(err) }) + } } + + const providerMeta = await this.buildProviderMetadata(result) + + await addHistory({ + id: recordId, + timestamp: Date.now(), + asrText: result.asrText, + llmText: textToPaste, + asrMs: result.asrMs, + llmMs: result.llmMs, + durationSec: wallSec, + audioDurationSec: audioDur > 0 ? audioDur : undefined, + asrDurationSec: result.durationSec > 0 ? result.durationSec : undefined, + charCount: hasText ? textToPaste.length : 0, + isEmpty: !hasText, + audioFilePath, + ...this.buildHistoryMetadata(promptResolution), + ...providerMeta, + }) + void bridge.emit('history-updated') } - - const providerMeta = await this.buildProviderMetadata(result) - - await addHistory({ - id: recordId, - timestamp: Date.now(), - asrText: result.asrText, - llmText: textToPaste, - asrMs: result.asrMs, - llmMs: result.llmMs, - durationSec: wallSec, - audioDurationSec: audioDur > 0 ? audioDur : undefined, - asrDurationSec: result.durationSec > 0 ? result.durationSec : undefined, - charCount: hasText ? textToPaste.length : 0, - isEmpty: !hasText, - audioFilePath, - ...this.buildHistoryMetadata(promptResolution), - ...providerMeta, - }) - void bridge.emit('history-updated') } catch (error) { addRuntimeEvent('warn', 'recorder', '写入历史记录失败', { error: String(error) }) }