From 97ea0e5a033cb4f5a958f2b875875ca67ceecbf6 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Thu, 10 Apr 2025 17:38:57 +0800 Subject: [PATCH 1/4] refactor(ipc): streamline IPC handler definitions and improve import organization - Simplified the registration of IPC handlers for the search window by removing unnecessary async/await syntax. - Improved import organization by removing duplicate import statements for ASRServerService. --- src/main/ipc.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 1bd7318dbd..91b8d71ef1 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -9,6 +9,7 @@ import log from 'electron-log' import { titleBarOverlayDark, titleBarOverlayLight } from './config' import AppUpdater from './services/AppUpdater' +import { asrServerService } from './services/ASRServerService' import BackupManager from './services/BackupManager' import { configManager } from './services/ConfigManager' import CopilotService from './services/CopilotService' @@ -21,7 +22,6 @@ import mcpService from './services/MCPService' import * as NutstoreService from './services/NutstoreService' import ObsidianVaultService from './services/ObsidianVaultService' import { ProxyConfig, proxyManager } from './services/ProxyManager' -import { asrServerService } from './services/ASRServerService' import { searchService } from './services/SearchService' import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' @@ -31,8 +31,6 @@ import { decrypt, encrypt } from './utils/aes' import { getConfigDir, getFilesDir } from './utils/file' import { compress, decompress } from './utils/zip' - - const fileManager = new FileStorage() const backupManager = new BackupManager() const exportService = new ExportService(fileManager) @@ -297,15 +295,11 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ) // search window - ipcMain.handle(IpcChannel.SearchWindow_Open, async (_, uid: string) => { - await searchService.openSearchWindow(uid) - }) - ipcMain.handle(IpcChannel.SearchWindow_Close, async (_, uid: string) => { - await searchService.closeSearchWindow(uid) - }) - ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, async (_, uid: string, url: string) => { - return await searchService.openUrlInSearchWindow(uid, url) - }) + ipcMain.handle(IpcChannel.SearchWindow_Open, (_, uid: string) => searchService.openSearchWindow(uid)) + ipcMain.handle(IpcChannel.SearchWindow_Close, (_, uid: string) => searchService.closeSearchWindow(uid)) + ipcMain.handle(IpcChannel.SearchWindow_OpenUrl, (_, uid: string, url: string) => + searchService.openUrlInSearchWindow(uid, url) + ) // 注册ASR服务器IPC处理程序 asrServerService.registerIpcHandlers() From 77bd17515d173629bb0620308c507cf8d3bb8c14 Mon Sep 17 00:00:00 2001 From: 1600822305 <161661698+1600822305@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:47:23 +0800 Subject: [PATCH 2/4] Update ASRService.ts --- src/renderer/src/services/ASRService.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/services/ASRService.ts b/src/renderer/src/services/ASRService.ts index 252ecd9b1d..bfa2971ce9 100644 --- a/src/renderer/src/services/ASRService.ts +++ b/src/renderer/src/services/ASRService.ts @@ -119,19 +119,19 @@ class ASRService { console.log('[ASRService] 浏览器断开连接') this.browserReady = false window.message.error({ content: '语音识别浏览器断开连接', key: 'browser-status' }) - } - } else if (data.type === 'status' && data.message === 'stopped') { - // 语音识别已停止 - console.log('[ASRService] 语音识别已停止') - this.isRecording = false + } else if (data.message === 'stopped') { + // 语音识别已停止 + console.log('[ASRService] 语音识别已停止') + this.isRecording = false - // 如果没有收到最终结果,显示处理完成消息 - window.message.success({ content: i18n.t('settings.asr.completed'), key: 'asr-processing' }) + // 如果没有收到最终结果,显示处理完成消息 + window.message.success({ content: i18n.t('settings.asr.completed'), key: 'asr-processing' }) - // 如果有回调函数,调用一次空字符串,触发按钮状态重置 - if (this.resultCallback && typeof this.resultCallback === 'function') { - // 使用空字符串调用回调,不会影响输入框,但可以触发按钮状态重置 - this.resultCallback('') + // 如果有回调函数,调用一次空字符串,触发按钮状态重置 + if (this.resultCallback && typeof this.resultCallback === 'function') { + // 使用空字符串调用回调,不会影响输入框,但可以触发按钮状态重置 + this.resultCallback('') + } } } else if (data.type === 'result' && data.data) { // 处理识别结果 From 8e6519d0182b973a5b1bbf76f518313c96a244bb Mon Sep 17 00:00:00 2001 From: 1600822305 <161661698+1600822305@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:48:08 +0800 Subject: [PATCH 3/4] Update ASRServerService.ts --- src/renderer/src/services/ASRServerService.ts | 832 +++++++++++++++--- 1 file changed, 728 insertions(+), 104 deletions(-) diff --git a/src/renderer/src/services/ASRServerService.ts b/src/renderer/src/services/ASRServerService.ts index 506715a4e8..3e105bcf4f 100644 --- a/src/renderer/src/services/ASRServerService.ts +++ b/src/renderer/src/services/ASRServerService.ts @@ -1,129 +1,753 @@ -import i18n from '@renderer/i18n' +import { AudioOutlined, PlusOutlined, ReloadOutlined, SoundOutlined } from '@ant-design/icons' +import { useTheme } from '@renderer/context/ThemeProvider' +import TTSService from '@renderer/services/TTSService' +import store, { useAppDispatch } from '@renderer/store' +import { + addTtsCustomModel, + addTtsCustomVoice, + removeTtsCustomModel, + removeTtsCustomVoice, + resetTtsCustomValues, + setTtsApiKey, + setTtsApiUrl, + setTtsEdgeVoice, + setTtsEnabled, + setTtsFilterOptions, + setTtsModel, + setTtsServiceType, + setTtsVoice +} from '@renderer/store/settings' +import { Button, Form, Input, message, Select, Space, Switch, Tabs, Tag } from 'antd' +import { FC, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import styled from 'styled-components' -// 使用window.electron而不是直接导入electron模块 -// 这样可以避免__dirname不可用的问题 +import { + SettingContainer, + SettingDivider, + SettingGroup, + SettingHelpText, + SettingRow, + SettingRowTitle, + SettingTitle +} from '..' +import ASRSettings from './ASRSettings' -class ASRServerService { - private serverProcess: any = null - private isServerRunning = false +const CustomVoiceInput = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +` - /** - * 启动ASR服务器 - * @returns Promise 是否成功启动 - */ - startServer = async (): Promise => { - if (this.isServerRunning) { - console.log('[ASRServerService] 服务器已经在运行中') - window.message.info({ content: i18n.t('settings.asr.server.already_running'), key: 'asr-server' }) - return true - } +const TagsContainer = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; + margin-bottom: 16px; +` - try { - console.log('[ASRServerService] 正在启动ASR服务器...') - window.message.loading({ content: i18n.t('settings.asr.server.starting'), key: 'asr-server' }) +const EmptyText = styled.div` + color: rgba(0, 0, 0, 0.45); + padding: 4px 0; +` - // 使用IPC调用主进程启动服务器 - const result = await window.api.asrServer.startServer() +const InputGroup = styled.div` + display: flex; + gap: 8px; + margin-bottom: 8px; +` - if (result.success) { - this.isServerRunning = true - this.serverProcess = result.pid - console.log('[ASRServerService] ASR服务器启动成功,PID:', result.pid) - window.message.success({ content: i18n.t('settings.asr.server.started'), key: 'asr-server' }) - return true - } else { - console.error('[ASRServerService] ASR服务器启动失败:', result.error) - window.message.error({ - content: i18n.t('settings.asr.server.start_failed') + ': ' + result.error, - key: 'asr-server' - }) - return false +const FlexContainer = styled.div` + display: flex; + gap: 8px; +` + +const FilterOptionItem = styled.div` + margin-bottom: 16px; + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; +` + +const LengthLabel = styled.span` + margin-right: 8px; +` + +const LoadingText = styled.div` + margin-top: 8px; + color: #999; +` + +const VoiceSelectContainer = styled.div` + display: flex; + gap: 8px; + margin-bottom: 8px; +` + +const TTSSettings: FC = () => { + const { t } = useTranslation() + const { theme } = useTheme() + const dispatch = useAppDispatch() + + // 从Redux获取TTS设置 + const ttsEnabled = useSelector((state: any) => state.settings.ttsEnabled) + const ttsServiceType = useSelector((state: any) => state.settings.ttsServiceType || 'openai') + const ttsApiKey = useSelector((state: any) => state.settings.ttsApiKey) + const ttsApiUrl = useSelector((state: any) => state.settings.ttsApiUrl) + const ttsVoice = useSelector((state: any) => state.settings.ttsVoice) + const ttsModel = useSelector((state: any) => state.settings.ttsModel) + const ttsEdgeVoice = useSelector((state: any) => state.settings.ttsEdgeVoice || 'zh-CN-XiaoxiaoNeural') + const ttsCustomVoices = useSelector((state: any) => state.settings.ttsCustomVoices || []) + const ttsCustomModels = useSelector((state: any) => state.settings.ttsCustomModels || []) + const ttsFilterOptions = useSelector( + (state: any) => + state.settings.ttsFilterOptions || { + filterThinkingProcess: true, + filterMarkdown: true, + filterCodeBlocks: true, + filterHtmlTags: true, + maxTextLength: 4000 } - } catch (error) { - console.error('[ASRServerService] 启动ASR服务器时出错:', error) - window.message.error({ - content: i18n.t('settings.asr.server.start_failed') + ': ' + (error as Error).message, - key: 'asr-server' - }) - return false - } - } + ) - /** - * 停止ASR服务器 - * @returns Promise 是否成功停止 - */ - stopServer = async (): Promise => { - if (!this.isServerRunning || !this.serverProcess) { - console.log('[ASRServerService] 服务器未运行') - window.message.info({ content: i18n.t('settings.asr.server.not_running'), key: 'asr-server' }) - return true - } + // 新增自定义音色和模型的状态 + const [newVoice, setNewVoice] = useState('') + const [newModel, setNewModel] = useState('') - try { - console.log('[ASRServerService] 正在停止ASR服务器...') - window.message.loading({ content: i18n.t('settings.asr.server.stopping'), key: 'asr-server' }) + // 浏览器可用的语音列表 + const [availableVoices, setAvailableVoices] = useState<{ label: string; value: string }[]>([]) - // 使用IPC调用主进程停止服务器 - const result = await window.api.asrServer.stopServer(this.serverProcess) + // 预定义的浏览器 TTS音色列表 + const predefinedVoices = [ + { label: '小晓 (女声, 中文)', value: 'zh-CN-XiaoxiaoNeural' }, + { label: '云扬 (男声, 中文)', value: 'zh-CN-YunyangNeural' }, + { label: '晓晓 (女声, 中文)', value: 'zh-CN-XiaoxiaoNeural' }, + { label: '晓涵 (女声, 中文)', value: 'zh-CN-XiaohanNeural' }, + { label: '晓诗 (女声, 中文)', value: 'zh-CN-XiaoshuangNeural' }, + { label: '晓瑞 (女声, 中文)', value: 'zh-CN-XiaoruiNeural' }, + { label: '晓墨 (女声, 中文)', value: 'zh-CN-XiaomoNeural' }, + { label: '晓然 (男声, 中文)', value: 'zh-CN-XiaoranNeural' }, + { label: '晓坤 (男声, 中文)', value: 'zh-CN-XiaokunNeural' }, + { label: 'Aria (Female, English)', value: 'en-US-AriaNeural' }, + { label: 'Guy (Male, English)', value: 'en-US-GuyNeural' }, + { label: 'Jenny (Female, English)', value: 'en-US-JennyNeural' }, + { label: 'Ana (Female, Spanish)', value: 'es-ES-ElviraNeural' }, + { label: 'Ichiro (Male, Japanese)', value: 'ja-JP-KeitaNeural' }, + { label: 'Nanami (Female, Japanese)', value: 'ja-JP-NanamiNeural' }, + // 添加更多常用的语音 + { label: 'Microsoft David (en-US)', value: 'Microsoft David Desktop - English (United States)' }, + { label: 'Microsoft Zira (en-US)', value: 'Microsoft Zira Desktop - English (United States)' }, + { label: 'Microsoft Mark (en-US)', value: 'Microsoft Mark Online (Natural) - English (United States)' }, + { label: 'Microsoft Aria (en-US)', value: 'Microsoft Aria Online (Natural) - English (United States)' }, + { label: 'Google US English', value: 'Google US English' }, + { label: 'Google UK English Female', value: 'Google UK English Female' }, + { label: 'Google UK English Male', value: 'Google UK English Male' }, + { label: 'Google 日本語', value: 'Google 日本語' }, + { label: 'Google 普通话(中国大陆)', value: 'Google 普通话(中国大陆)' }, + { label: 'Google 粤語(香港)', value: 'Google 粤語(香港)' } + ] - if (result.success) { - this.isServerRunning = false - this.serverProcess = null - console.log('[ASRServerService] ASR服务器已停止') - window.message.success({ content: i18n.t('settings.asr.server.stopped'), key: 'asr-server' }) - return true - } else { - console.error('[ASRServerService] ASR服务器停止失败:', result.error) - window.message.error({ - content: i18n.t('settings.asr.server.stop_failed') + ': ' + result.error, - key: 'asr-server' - }) - return false + // 获取浏览器可用的语音列表 + const getVoices = () => { + if (typeof window !== 'undefined' && 'speechSynthesis' in window) { + // 先触发一下语音合成引擎,确保它已经初始化 + window.speechSynthesis.cancel() + + // 获取浏览器可用的语音列表 + const voices = window.speechSynthesis.getVoices() + console.log('获取到的语音列表:', voices) + console.log('语音列表长度:', voices.length) + + // 转换浏览器语音列表为选项格式 + const browserVoices = voices.map((voice) => ({ + label: `${voice.name} (${voice.lang})${voice.default ? ' - 默认' : ''}`, + value: voice.name, + lang: voice.lang, + isNative: true // 标记为浏览器原生语音 + })) + + // 添加语言信息到预定义语音 + const enhancedPredefinedVoices = predefinedVoices.map((voice) => ({ + ...voice, + lang: voice.value.split('-').slice(0, 2).join('-'), + isNative: false // 标记为非浏览器原生语音 + })) + + // 合并所有语音列表 + let allVoices = [...browserVoices] + + // 如果浏览器语音少于5个,添加预定义语音 + if (browserVoices.length < 5) { + allVoices = [...browserVoices, ...enhancedPredefinedVoices] } - } catch (error) { - console.error('[ASRServerService] 停止ASR服务器时出错:', error) - window.message.error({ - content: i18n.t('settings.asr.server.stop_failed') + ': ' + (error as Error).message, - key: 'asr-server' + + // 去除重复项,优先保留浏览器原生语音 + const uniqueVoices = allVoices.filter((voice, index, self) => { + const firstIndex = self.findIndex((v) => v.value === voice.value) + // 如果是原生语音或者是第一次出现,则保留 + return voice.isNative || firstIndex === index }) - return false + + // 按语言分组并排序 + const groupedVoices = uniqueVoices.sort((a, b) => { + // 先按语言排序 + if (a.lang !== b.lang) { + return a.lang.localeCompare(b.lang) + } + // 同语言下,原生语音优先 + if (a.isNative !== b.isNative) { + return a.isNative ? -1 : 1 + } + // 最后按名称排序 + return a.label.localeCompare(b.label) + }) + + setAvailableVoices(groupedVoices) + console.log('设置可用语音列表:', groupedVoices) + } else { + // 如果浏览器不支持Web Speech API,使用预定义的语音列表 + console.log('浏览器不支持Web Speech API,使用预定义的语音列表') + setAvailableVoices(predefinedVoices) } } - /** - * 检查ASR服务器是否正在运行 - * @returns boolean 是否正在运行 - */ - isRunning = (): boolean => { - return this.isServerRunning + // 刷新语音列表 + const refreshVoices = () => { + console.log('手动刷新语音列表') + message.loading({ + content: t('settings.tts.edge_voice.refreshing', { defaultValue: '正在刷新语音列表...' }), + key: 'refresh-voices' + }) + + // 先清空当前列表 + setAvailableVoices([]) + + // 强制重新加载语音列表 + if (typeof window !== 'undefined' && 'speechSynthesis' in window) { + window.speechSynthesis.cancel() + + // 尝试多次获取语音列表 + setTimeout(() => { + getVoices() + setTimeout(() => { + getVoices() + message.success({ + content: t('settings.tts.edge_voice.refreshed', { defaultValue: '语音列表已刷新' }), + key: 'refresh-voices' + }) + }, 1000) + }, 500) + } else { + // 如果浏览器不支持Web Speech API,使用预定义的语音列表 + setAvailableVoices(predefinedVoices) + message.success({ + content: t('settings.tts.edge_voice.refreshed', { defaultValue: '语音列表已刷新' }), + key: 'refresh-voices' + }) + } } - /** - * 获取ASR服务器网页URL - * @returns string 网页URL - */ - getServerUrl = (): string => { - return 'http://localhost:8080' + useEffect(() => { + // 初始化语音合成引擎 + if (typeof window !== 'undefined' && 'speechSynthesis' in window) { + // 触发语音合成引擎初始化 + window.speechSynthesis.cancel() + + // 设置voiceschanged事件处理程序 + const voicesChangedHandler = () => { + console.log('检测到voiceschanged事件,重新获取语音列表') + getVoices() + } + + // 添加事件监听器 + window.speechSynthesis.onvoiceschanged = voicesChangedHandler + + // 立即获取可用的语音 + getVoices() + + // 创建多个定时器,在不同时间点尝试获取语音列表 + // 这是因为不同浏览器加载语音列表的时间不同 + const timers = [ + setTimeout(() => getVoices(), 500), + setTimeout(() => getVoices(), 1000), + setTimeout(() => getVoices(), 2000) + ] + + return () => { + // 清理事件监听器和定时器 + window.speechSynthesis.onvoiceschanged = null + timers.forEach((timer) => clearTimeout(timer)) + } + } else { + // 如果浏览器不支持Web Speech API,使用预定义的语音列表 + setAvailableVoices(predefinedVoices) + return () => {} + } + }, [getVoices, predefinedVoices]) + + // 测试TTS功能 + const testTTS = async () => { + if (!ttsEnabled) { + window.message.error({ content: t('settings.tts.error.not_enabled'), key: 'tts-test' }) + return + } + + // 获取最新的服务类型设置 + const latestSettings = store.getState().settings + const currentServiceType = latestSettings.ttsServiceType || 'openai' + console.log('测试TTS时使用的服务类型:', currentServiceType) + console.log('测试时完整TTS设置:', { + ttsEnabled: latestSettings.ttsEnabled, + ttsServiceType: latestSettings.ttsServiceType, + ttsApiKey: latestSettings.ttsApiKey ? '已设置' : '未设置', + ttsVoice: latestSettings.ttsVoice, + ttsModel: latestSettings.ttsModel, + ttsEdgeVoice: latestSettings.ttsEdgeVoice + }) + + // 根据服务类型检查必要的参数 + if (currentServiceType === 'openai') { + if (!ttsApiKey) { + window.message.error({ content: t('settings.tts.error.no_api_key'), key: 'tts-test' }) + return + } + + if (!ttsVoice) { + window.message.error({ content: t('settings.tts.error.no_voice'), key: 'tts-test' }) + return + } + + if (!ttsModel) { + window.message.error({ content: t('settings.tts.error.no_model'), key: 'tts-test' }) + return + } + } else if (currentServiceType === 'edge') { + if (!ttsEdgeVoice) { + window.message.error({ content: t('settings.tts.error.no_edge_voice'), key: 'tts-test' }) + return + } + } + + await TTSService.speak('这是一段测试语音,用于测试TTS功能是否正常工作。') } - /** - * 获取ASR服务器文件路径 - * @returns string 服务器文件路径 - */ - getServerFilePath = (): string => { - // 使用相对路径,因为window.electron.app.getAppPath()不可用 - return process.env.NODE_ENV === 'development' - ? 'src/renderer/src/assets/asr-server/server.js' - : 'public/asr-server/server.js' + // 添加自定义音色 + const handleAddVoice = () => { + if (!newVoice) { + window.message.error({ content: '请输入音色', key: 'add-voice' }) + return + } + + // 确保添加的是字符串 + const voiceStr = typeof newVoice === 'string' ? newVoice : String(newVoice) + dispatch(addTtsCustomVoice(voiceStr)) + setNewVoice('') } - /** - * 打开ASR服务器网页 - */ - openServerPage = (): void => { - window.open(this.getServerUrl(), '_blank') + // 添加自定义模型 + const handleAddModel = () => { + if (!newModel) { + window.message.error({ content: '请输入模型', key: 'add-model' }) + return + } + + // 确保添加的是字符串 + const modelStr = typeof newModel === 'string' ? newModel : String(newModel) + dispatch(addTtsCustomModel(modelStr)) + setNewModel('') } + + // 删除自定义音色 + const handleRemoveVoice = (voice: string) => { + // 确保删除的是字符串 + const voiceStr = typeof voice === 'string' ? voice : String(voice) + dispatch(removeTtsCustomVoice(voiceStr)) + } + + // 删除自定义模型 + const handleRemoveModel = (model: string) => { + // 确保删除的是字符串 + const modelStr = typeof model === 'string' ? model : String(model) + dispatch(removeTtsCustomModel(modelStr)) + } + + return ( + + + + + {t('settings.voice.title')} + + + + + {t('settings.tts.tab_title')} + + ), + children: ( +
+ + + {t('settings.tts.enable')} + dispatch(setTtsEnabled(checked))} /> + + {t('settings.tts.enable.help')} + + + {/* 重置按钮 */} + + + {t('settings.tts.reset_title')} + + + {t('settings.tts.reset_help')} + + + {t('settings.tts.api_settings')} +
+ {/* TTS服务类型选择 */} + + + dispatch(setTtsApiUrl(e.target.value))} + placeholder={t('settings.tts.api_url.placeholder')} + disabled={!ttsEnabled} + /> + + + )} + + {/* 浏览器 TTS设置 */} + {ttsServiceType === 'edge' && ( + + + dispatch(setTtsVoice(value))} + options={ttsCustomVoices.map((voice: any) => { + // 确保voice是字符串 + const voiceStr = typeof voice === 'string' ? voice : String(voice) + return { label: voiceStr, value: voiceStr } + })} + disabled={!ttsEnabled} + style={{ width: '100%' }} + placeholder={t('settings.tts.voice.placeholder')} + showSearch + optionFilterProp="label" + allowClear + /> + + + {/* 自定义音色列表 */} + + {ttsCustomVoices && ttsCustomVoices.length > 0 ? ( + ttsCustomVoices.map((voice: any, index: number) => { + // 确保voice是字符串 + const voiceStr = typeof voice === 'string' ? voice : String(voice) + return ( + handleRemoveVoice(voiceStr)} + style={{ padding: '4px 8px' }}> + {voiceStr} + + ) + }) + ) : ( + {t('settings.tts.voice_empty')} + )} + + + {/* 添加自定义音色 */} + + + setNewVoice(e.target.value)} + disabled={!ttsEnabled} + style={{ flex: 1 }} + /> + + + + + {/* 模型选择 */} + + setNewModel(e.target.value)} + disabled={!ttsEnabled} + style={{ flex: 1 }} + /> + + + + + )} + + {/* TTS过滤选项 */} + + + dispatch(setTtsFilterOptions({ filterThinkingProcess: checked }))} + disabled={!ttsEnabled} + />{' '} + {t('settings.tts.filter.thinking_process')} + + + dispatch(setTtsFilterOptions({ filterMarkdown: checked }))} + disabled={!ttsEnabled} + />{' '} + {t('settings.tts.filter.markdown')} + + + dispatch(setTtsFilterOptions({ filterCodeBlocks: checked }))} + disabled={!ttsEnabled} + />{' '} + {t('settings.tts.filter.code_blocks')} + + + dispatch(setTtsFilterOptions({ filterHtmlTags: checked }))} + disabled={!ttsEnabled} + />{' '} + {t('settings.tts.filter.html_tags')} + + + {t('settings.tts.max_text_length')}: + { - console.log('切换TTS服务类型为:', value) - // 先将新的服务类型写入Redux状态 - dispatch(setTtsServiceType(value)) - - // 等待一下,确保状态已更新 - setTimeout(() => { - // 验证状态是否正确更新 - const currentType = store.getState().settings.ttsServiceType - console.log('更新后的TTS服务类型:', currentType) - - // 如果状态没有正确更新,再次尝试 - if (currentType !== value) { - console.log('状态未正确更新,再次尝试') - dispatch(setTtsServiceType(value)) - } - }, 100) - }} - options={[ - { label: t('settings.tts.service_type.openai'), value: 'openai' }, - { label: t('settings.tts.service_type.edge'), value: 'edge' } - ]} - disabled={!ttsEnabled} - style={{ flex: 1 }} - /> - - - - - {/* 模型选择 */} - - setNewModel(e.target.value)} - disabled={!ttsEnabled} - style={{ flex: 1 }} - /> - - - - - )} - - {/* TTS过滤选项 */} - - - dispatch(setTtsFilterOptions({ filterThinkingProcess: checked }))} - disabled={!ttsEnabled} - />{' '} - {t('settings.tts.filter.thinking_process')} - - - dispatch(setTtsFilterOptions({ filterMarkdown: checked }))} - disabled={!ttsEnabled} - />{' '} - {t('settings.tts.filter.markdown')} - - - dispatch(setTtsFilterOptions({ filterCodeBlocks: checked }))} - disabled={!ttsEnabled} - />{' '} - {t('settings.tts.filter.code_blocks')} - - - dispatch(setTtsFilterOptions({ filterHtmlTags: checked }))} - disabled={!ttsEnabled} - />{' '} - {t('settings.tts.filter.html_tags')} - - - {t('settings.tts.max_text_length')}: -