diff --git a/openless-all/app/src/components/FloatingShell.tsx b/openless-all/app/src/components/FloatingShell.tsx index 0ff74b2f..96882c48 100644 --- a/openless-all/app/src/components/FloatingShell.tsx +++ b/openless-all/app/src/components/FloatingShell.tsx @@ -15,7 +15,8 @@ import { Vocab } from '../pages/Vocab'; import { Style } from '../pages/Style'; import { Translation } from '../pages/Translation'; import { SelectionAsk } from '../pages/SelectionAsk'; -import { LocalAsr } from '../pages/LocalAsr'; +// LocalAsr 不再作为主 nav tab——本地 ASR 模型管理已合并到 Settings → Advanced 中 +// 通过 渲染。这里之前的 import 与 NAV_BASE 条目都已移除。 import { APP_VERSION_LABEL, IS_BETA_BUILD } from '../lib/appVersion'; import { HOTKEY_MODE_MIGRATION_ACK_KEY, @@ -28,7 +29,7 @@ import { PROVIDER_SETUP_PROMPT_DEFERRED_KEY, shouldShowProviderSetupPrompt, } from '../lib/providerSetup'; -import { NAVIGATE_LOCAL_ASR_EVENT, type SettingsSectionId } from '../pages/Settings'; +import { type SettingsSectionId } from '../pages/Settings'; import { useAppState, type AppTab } from '../state/useAppState'; interface NavItem { @@ -45,7 +46,6 @@ const NAV_BASE: Array> = [ { id: 'style', icon: 'style', cmp: Style }, { id: 'translation', icon: 'translate', cmp: Translation }, { id: 'selectionAsk', icon: 'selectionAsk', cmp: SelectionAsk }, - { id: 'localAsr', icon: 'archive', cmp: LocalAsr }, ]; interface FloatingShellProps { @@ -128,15 +128,9 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia } }, []); - // Settings → ASR 选 local-qwen3 时的"前往模型设置"事件 → 关 modal + 切 tab。 - useEffect(() => { - const onNavigate = () => { - setSettingsOpen(false); - setCurrentTab('localAsr'); - }; - window.addEventListener(NAVIGATE_LOCAL_ASR_EVENT, onNavigate); - return () => window.removeEventListener(NAVIGATE_LOCAL_ASR_EVENT, onNavigate); - }, [setCurrentTab, setSettingsOpen]); + // 之前监听的 NAVIGATE_LOCAL_ASR_EVENT 已无意义——「模型设置」独立 tab 已下线, + // 模型管理 UI 现在通过 Settings → Advanced 的 渲染, + // 用户在 Settings 内即可一站式管理,无需跨页跳转。 const rememberProviderPrompt = () => { window.sessionStorage.setItem(PROVIDER_SETUP_PROMPT_DEFERRED_KEY, '1'); diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index fdfa9ba4..1dea5474 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -294,6 +294,7 @@ export const en: typeof zhCN = { shortcuts: 'Shortcuts', permissions: 'Permissions', language: 'Language', + advanced: 'Advanced', about: 'About', }, recording: { @@ -381,6 +382,7 @@ export const en: typeof zhCN = { volcengineAccessKeyLabel: 'Access Token', volcengineResourceIdLabel: 'Resource ID', volcengineMappingNote: 'Secret Key is not required right now. Resource ID defaults to volc.bigasr.sauc.duration.', + localAsrActiveNotice: 'Local ASR ({{name}}) is currently active. Switch or disable it from the Advanced tab.', localAsrHint: 'Local Qwen3-ASR runs entirely on this machine. No API key needed — just download the model from HuggingFace.', foundryLocalAsrHint: 'Windows local Whisper runs on this device and does not need an ASR API key. First use downloads Foundry Local runtime components and a Whisper model; LLM polishing still uses your configured LLM provider.', localAsrPerformanceWarning: 'Local inference runs on CPU + Apple Silicon Accelerate; each transcription takes **several seconds longer than cloud ASR**, and Chinese / dialect accuracy is **typically lower** than Volcengine or Whisper turbo. Use it for offline, privacy-sensitive, or no-cloud-API scenarios.', @@ -469,6 +471,22 @@ export const en: typeof zhCN = { notWindows: 'Only available on Windows.', }, }, + advanced: { + localAsrTitle: 'Local ASR models (experimental)', + localAsrDesc: 'Move transcription from cloud ASR to on-device inference. Offline / privacy-sensitive use only.', + localAsrWarningShort: 'Local ASR is several seconds slower than cloud, typically less accurate, and on under-spec hardware can drop words (output only part of your speech).', + qwen3Desc: 'Alibaba Qwen3-ASR, cross-platform. Models download from HuggingFace.', + foundryDesc: 'Microsoft Foundry Local Whisper. Windows only.', + enable: 'Enable', + alreadyActive: 'Active', + disableLocalLabel: 'Disable local ASR', + disableLocalDesc: 'Switch back to cloud ASR (defaults to Volcengine bigasr).', + disable: 'Disable', + platformNotSupported: 'Local ASR model integration is not supported on this platform.', + confirmEnableLocalTitle: 'Enable local ASR?', + confirmEnableLocalBody: 'Enabling "{{target}}" makes transcription several seconds slower than cloud and typically less accurate. On under-spec hardware it can drop words (output only part of your speech).', + confirm: 'Enable', + }, language: { title: 'Interface language', desc: 'Switch the UI language. Applies to the current session immediately and persists across launches.', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 1fe51718..31dae950 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -296,6 +296,7 @@ export const ja: typeof zhCN = { shortcuts: 'ショートカット', permissions: '権限', language: '言語', + advanced: '詳細設定', about: '情報', }, recording: { @@ -383,6 +384,7 @@ export const ja: typeof zhCN = { volcengineAccessKeyLabel: 'Access Token', volcengineResourceIdLabel: 'Resource ID', volcengineMappingNote: 'Secret Key は現在不要。Resource ID のデフォルトは volc.bigasr.sauc.duration。', + localAsrActiveNotice: '現在「{{name}}」を使用中。「詳細設定」タブから切り替えまたは無効化できます。', localAsrHint: 'ローカル Qwen3-ASR は本機で実行されるため API Key 不要。HuggingFace からモデルをダウンロードすればすぐに利用できます。', foundryLocalAsrHint: 'Windows ローカル Whisper は本機で実行され、ASR API Key は不要です。初回使用時に Foundry Local ランタイムコンポーネントと Whisper モデルをダウンロードします。LLM 整文は引き続き設定済みの LLM プロバイダーを使用します。', localAsrPerformanceWarning: 'ローカル推論は CPU + Apple Silicon Accelerate で動作するため、1 回の転写時間は **クラウド ASR より数秒長くなります**。中国語認識精度や方言/訛り対応も **通常は** Volcengine / Whisper turbo に劣ります。ネットワーク制限下またはプライバシー重視の場合に選択してください。', @@ -471,6 +473,22 @@ export const ja: typeof zhCN = { notWindows: 'Windows のみ利用可能。', }, }, + advanced: { + localAsrTitle: 'ローカル ASR モデル(実験的)', + localAsrDesc: '転写をクラウドから本機推論に切り替えます。オフライン/プライバシー重視向け。', + localAsrWarningShort: 'ローカル ASR はクラウドより数秒遅く、精度も通常は低め。スペック不足のマシンでは欠字(音声の一部のみ出力)が起きる場合があります。', + qwen3Desc: 'Alibaba Qwen3-ASR、クロスプラットフォーム。モデルは HuggingFace からダウンロード。', + foundryDesc: 'Microsoft Foundry Local Whisper、Windows のみ。', + enable: '有効化', + alreadyActive: '有効', + disableLocalLabel: 'ローカル ASR を無効化', + disableLocalDesc: 'クラウド ASR(既定は Volcengine bigasr)に戻します。', + disable: '無効化', + platformNotSupported: 'このプラットフォームではローカル ASR モデル統合に対応していません。', + confirmEnableLocalTitle: 'ローカル ASR を有効化しますか?', + confirmEnableLocalBody: '「{{target}}」を有効化すると、転写はクラウドより数秒遅くなり、精度も通常は低くなります。スペック不足のマシンでは欠字(音声の一部のみ出力)が起きる場合があります。', + confirm: '有効化する', + }, language: { title: '表示言語', desc: 'UI の表示言語を切り替えます。現在のセッションに即時反映され、次回起動時も維持されます。', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index dec4a534..5974c69e 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -296,6 +296,7 @@ export const ko: typeof zhCN = { shortcuts: '단축키', permissions: '권한', language: '언어', + advanced: '고급', about: '정보', }, recording: { @@ -383,6 +384,7 @@ export const ko: typeof zhCN = { volcengineAccessKeyLabel: 'Access Token', volcengineResourceIdLabel: 'Resource ID', volcengineMappingNote: 'Secret Key 는 현재 입력 불필요. Resource ID 기본값은 volc.bigasr.sauc.duration.', + localAsrActiveNotice: '현재 "{{name}}" 사용 중. "고급" 탭에서 전환 또는 비활성화할 수 있습니다.', localAsrHint: '로컬 Qwen3-ASR 은 본 기기에서 실행되며 API Key 가 필요 없습니다. HuggingFace 에서 모델을 로컬로 다운로드하면 즉시 사용 가능합니다.', foundryLocalAsrHint: 'Windows 로컬 Whisper 는 이 기기에서 실행되며 ASR API Key 가 필요 없습니다. 첫 사용 시 Foundry Local 런타임 구성 요소와 Whisper 모델을 다운로드합니다. LLM 정리는 계속 설정된 LLM 공급자를 사용합니다.', localAsrPerformanceWarning: '로컬 추론은 CPU + Apple Silicon Accelerate 에서 동작하므로, 한 번의 전사 시간이 **클라우드 ASR 보다 몇 초 더 걸립니다**. 중국어 인식 정확도와 방언/억양 대응도 **일반적으로** Volcengine / Whisper turbo 에 미치지 못합니다. 네트워크 제한 또는 프라이버시가 중요한 경우에 사용하세요.', @@ -471,6 +473,22 @@ export const ko: typeof zhCN = { notWindows: 'Windows 만 사용 가능.', }, }, + advanced: { + localAsrTitle: '로컬 ASR 모델 (실험적)', + localAsrDesc: '전사를 클라우드에서 로컬 추론으로 전환합니다. 오프라인 / 프라이버시용에만 권장됩니다.', + localAsrWarningShort: '로컬 ASR 은 클라우드보다 몇 초 느리고 정확도도 일반적으로 낮습니다. 사양이 부족한 기기에서는 글자 누락(음성의 일부만 출력)이 발생할 수 있습니다.', + qwen3Desc: 'Alibaba Qwen3-ASR, 크로스 플랫폼. 모델은 HuggingFace 에서 다운로드.', + foundryDesc: 'Microsoft Foundry Local Whisper, Windows 전용.', + enable: '활성화', + alreadyActive: '활성', + disableLocalLabel: '로컬 ASR 비활성화', + disableLocalDesc: '클라우드 ASR (기본 Volcengine bigasr) 로 돌아갑니다.', + disable: '비활성화', + platformNotSupported: '이 플랫폼에서는 로컬 ASR 모델 통합이 아직 지원되지 않습니다.', + confirmEnableLocalTitle: '로컬 ASR 을 활성화할까요?', + confirmEnableLocalBody: '"{{target}}" 을 활성화하면 전사가 클라우드보다 몇 초 느려지고 정확도도 일반적으로 낮아집니다. 사양이 부족한 기기에서는 글자 누락(음성의 일부만 출력)이 발생할 수 있습니다.', + confirm: '활성화', + }, language: { title: '인터페이스 언어', desc: 'UI 표시 언어를 전환합니다. 현재 세션에 즉시 반영되며 다음 실행에도 유지됩니다.', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index 8379d77d..f9c1fcf5 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -292,6 +292,7 @@ export const zhCN = { shortcuts: '快捷键', permissions: '权限', language: '语言', + advanced: '高级', about: '关于', }, recording: { @@ -379,6 +380,7 @@ export const zhCN = { volcengineAccessKeyLabel: 'Access Token', volcengineResourceIdLabel: 'Resource ID', volcengineMappingNote: 'Secret Key 当前无需填写。Resource ID 默认使用 volc.bigasr.sauc.duration。', + localAsrActiveNotice: '当前已启用「{{name}}」,可在「高级」中切换或禁用。', localAsrHint: '本地 Qwen3-ASR 在本机运行,无需 API Key。模型从 HuggingFace 下载到本地后即可使用。', foundryLocalAsrHint: 'Windows 本地 Whisper 在本机运行,无需 ASR API Key。首次使用会下载 Foundry Local 运行组件和 Whisper 模型;LLM 润色仍按你配置的 LLM 提供商调用。', localAsrPerformanceWarning: '本地推理跑在 CPU + Apple Silicon Accelerate 上,单次转写时间会**比云端 ASR 长几秒**;中文识别准确率与方言/口音表现也**通常不如**火山引擎 / Whisper turbo。请按需取舍:网络受限或对隐私敏感时再用本地。', @@ -467,6 +469,22 @@ export const zhCN = { notWindows: '仅 Windows 可用。', }, }, + advanced: { + localAsrTitle: '本地 ASR 模型(实验性)', + localAsrDesc: '把转写从云端切到本机推理。仅推荐离线 / 隐私敏感场景。', + localAsrWarningShort: '本地 ASR 比云端慢若干秒、准确率更低;配置不足时可能吞字(仅输出部分语音)。', + qwen3Desc: 'Alibaba Qwen3-ASR,跨平台。模型从 HuggingFace 下载。', + foundryDesc: 'Microsoft Foundry Local Whisper,仅 Windows。', + enable: '启用', + alreadyActive: '已启用', + disableLocalLabel: '禁用本地 ASR', + disableLocalDesc: '切回云端 ASR(默认火山引擎 bigasr)。', + disable: '禁用', + platformNotSupported: '该平台暂未支持本地 ASR 模型集成。', + confirmEnableLocalTitle: '启用本地 ASR?', + confirmEnableLocalBody: '启用「{{target}}」后,转写会比云端慢若干秒,准确率更低;如果机器配置不足,可能出现吞字(仅输出部分语音)。', + confirm: '确认启用', + }, language: { title: '界面语言', desc: '切换 UI 显示语言。当前会话即时生效,下次启动自动沿用。', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index c4d8f316..349e2dcc 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -294,6 +294,7 @@ export const zhTW: typeof zhCN = { shortcuts: '快捷鍵', permissions: '權限', language: '語言', + advanced: '高級', about: '關於', }, recording: { @@ -381,6 +382,7 @@ export const zhTW: typeof zhCN = { volcengineAccessKeyLabel: 'Access Token', volcengineResourceIdLabel: 'Resource ID', volcengineMappingNote: 'Secret Key 當前無需填寫。Resource ID 默認使用 volc.bigasr.sauc.duration。', + localAsrActiveNotice: '當前已啓用「{{name}}」,可在「高級」中切換或停用。', localAsrHint: '本地 Qwen3-ASR 在本機運行,無需 API Key。模型從 HuggingFace 下載到本地後即可使用。', foundryLocalAsrHint: 'Windows 本地 Whisper 在本機運行,無需 ASR API Key。首次使用會下載 Foundry Local 運行組件和 Whisper 模型;LLM 潤色仍按你配置的 LLM 提供商調用。', localAsrPerformanceWarning: '本地推理跑在 CPU + Apple Silicon Accelerate 上,**首次轉寫需要加載模型(數秒)**,之後單次轉寫也會比雲端 ASR 慢若干秒;中文識別準確率與方言/口音表現通常不如火山引擎 / Whisper turbo。適用場景:離線 / 隱私敏感 / 不願付費雲 API。', @@ -469,6 +471,22 @@ export const zhTW: typeof zhCN = { notWindows: '僅 Windows 可用。', }, }, + advanced: { + localAsrTitle: '本地 ASR 模型(實驗性)', + localAsrDesc: '把轉寫從雲端切到本機推理。僅推薦離線 / 隱私敏感場景。', + localAsrWarningShort: '本地 ASR 比雲端慢若干秒、準確率更低;配置不足時可能吞字(僅輸出部分語音)。', + qwen3Desc: 'Alibaba Qwen3-ASR,跨平臺。模型從 HuggingFace 下載。', + foundryDesc: 'Microsoft Foundry Local Whisper,僅 Windows。', + enable: '啓用', + alreadyActive: '已啓用', + disableLocalLabel: '停用本地 ASR', + disableLocalDesc: '切回雲端 ASR(默認火山引擎 bigasr)。', + disable: '停用', + platformNotSupported: '該平臺暫未支持本地 ASR 模型集成。', + confirmEnableLocalTitle: '啓用本地 ASR?', + confirmEnableLocalBody: '啓用「{{target}}」後,轉寫會比雲端慢若干秒,準確率更低;如果機器配置不足,可能出現吞字(僅輸出部分語音)。', + confirm: '確認啓用', + }, language: { title: '界面語言', desc: '切換 UI 顯示語言。當前會話即時生效,下次啓動自動沿用。', diff --git a/openless-all/app/src/pages/LocalAsr.tsx b/openless-all/app/src/pages/LocalAsr.tsx index 9d5ace15..135a94a4 100644 --- a/openless-all/app/src/pages/LocalAsr.tsx +++ b/openless-all/app/src/pages/LocalAsr.tsx @@ -7,7 +7,7 @@ // - 监听 `local-asr-download-progress` 事件实时刷新进度 // - Win 端引擎不可用时禁用下载按钮,提示见 issue #256 -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { isTauri, setActiveAsrProvider } from '../lib/ipc'; import { @@ -61,7 +61,16 @@ interface RemoteSize { error: string | null; } -export function LocalAsr() { +interface LocalAsrProps { + /// `embedded=true` 表示作为子组件嵌入「高级」设置页(Settings → Advanced); + /// 此时跳过外层 page padding/height、PageHeader 与独立警告 Card —— 这些由 + /// 宿主 AdvancedSection 决定(包括把警告统一到页面顶部的浮层 popup 上)。 + /// `embedded=false`(默认)保留原全屏页样式,供 v 旧版本的独立「模型设置」 + /// 页面入口使用——但当前代码里该入口已删,本分支会一并移除。 + embedded?: boolean; +} + +export function LocalAsr({ embedded = false }: LocalAsrProps = {}) { const { t } = useTranslation(); const { prefs, updatePrefs } = useHotkeySettings(); const [settings, setSettings] = useState(null); @@ -553,20 +562,35 @@ export function LocalAsr() { ? t('localAsr.foundryRetryPrepare') : t('localAsr.foundryPrepare'); - return ( -
- - - {/* 性能/质量预期警告 —— 用户硬要求要写清楚 */} - -
- ⚠️ {t('localAsr.performanceWarning')} + // embedded=true 嵌入「高级」设置:跳过外层 page padding/height、PageHeader, + // 与独立警告 Card——AdvancedSection 自己负责标题与短警告 + 启用时的浮层 popup, + // LocalAsr 只输出实际功能 Cards(Foundry / Qwen3 模型状态 / 模型列表)。 + const Wrapper = embedded + ? (props: { children: ReactNode }) => <>{props.children} + : (props: { children: ReactNode }) => ( +
+ {props.children}
- + ); + + return ( + + {!embedded && ( + + )} + + {!embedded && ( + /* 性能/质量预期警告 —— embedded 模式下由 AdvancedSection 自己渲染,避免重复。 */ + +
+ ⚠️ {t('localAsr.performanceWarning')} +
+
+ )} {IS_WINDOWS && ( @@ -873,7 +897,7 @@ export function LocalAsr() { /> ))}
-
+ ); } diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 252d2648..e9bac082 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -8,6 +8,7 @@ import { Icon } from '../components/Icon'; import { ShortcutRecorder } from '../components/ShortcutRecorder'; import { isDialogStatus, UpdateDialog, useAutoUpdate } from '../components/AutoUpdate'; import { detectOS } from '../components/WindowChrome'; +import { LocalAsr } from './LocalAsr'; import { APP_VERSION_LABEL } from '../lib/appVersion'; import { isHotkeyModeMigrationNoticeActive } from '../lib/hotkeyMigration'; import { @@ -80,9 +81,12 @@ interface SettingsProps { initialSection?: SettingsSectionId; } // "关于" tab 已移除(内容并入外层 SettingsModal 的 About 页,避免设置内外重复入口)。 -export type SettingsSectionId = 'recording' | 'providers' | 'shortcuts' | 'permissions' | 'language'; +export type SettingsSectionId = 'recording' | 'providers' | 'shortcuts' | 'permissions' | 'language' | 'advanced'; -const SECTION_ORDER: SettingsSectionId[] = ['recording', 'providers', 'shortcuts', 'permissions', 'language']; +// 「高级」放最末——本地推理 / 实验性开关都集中到这一栏,避免新手用户在主流程 +// 里误开 CPU 推理(之前提案:把 local-qwen3 / foundry-local-whisper 从主 ASR +// 下拉藏进高级)。位置末尾也是「实验性」语义在 macOS 系统偏好里的惯用位置。 +const SECTION_ORDER: SettingsSectionId[] = ['recording', 'providers', 'shortcuts', 'permissions', 'language', 'advanced']; async function autostartIsEnabled(): Promise { const { invoke } = await import('@tauri-apps/api/core'); @@ -195,6 +199,7 @@ export function Settings({ embedded = false, initialSection = 'recording' }: Set {section === 'shortcuts' && } {section === 'permissions' && } {section === 'language' && } + {section === 'advanced' && } @@ -1240,8 +1245,10 @@ function ProvidersSection() { const [llmModelRevision, setLlmModelRevision] = useState(0); const [asrModelRevision, setAsrModelRevision] = useState(0); const os = detectOS(); + // 主 ASR 下拉只列云端选项;本地推理(local-qwen3 / foundry-local-whisper) + // 移到「高级」标签页,防止新手误开 CPU 推理。详见 AdvancedSection。 const visibleAsrPresets = ASR_PRESETS.filter( - p => p.id !== 'foundry-local-whisper' || os === 'win', + p => p.id !== 'foundry-local-whisper' && p.id !== 'local-qwen3', ); useEffect(() => { @@ -1250,7 +1257,10 @@ function ProvidersSection() { const llmId = knownLlm ? knownLlm.id : 'custom'; setLlmProvider(llmId); setCommittedLlmProvider(llmId); - const knownAsr = visibleAsrPresets.find(x => x.id === prefs.activeAsrProvider); + // ASR 在 ALL ASR_PRESETS 里查(不是 visibleAsrPresets)——本地选项虽然 + // 从下拉里藏起来了,但若用户曾在「高级」里启用过 local-qwen3,主 Card + // 仍要识别出 active 是本地,并切到「正在使用本地 ASR」的 notice 渲染。 + const knownAsr = ASR_PRESETS.find(x => x.id === prefs.activeAsrProvider); const asrId = knownAsr ? knownAsr.id : 'volcengine'; setAsrProvider(asrId); setCommittedAsrProvider(asrId); @@ -1368,15 +1378,26 @@ function ProvidersSection() {
{t('settings.providers.asrDesc')}
- + {committedAsrProvider === 'local-qwen3' || committedAsrProvider === 'foundry-local-whisper' ? ( + // active 是本地 ASR 时,下拉里没有它的 + ))} + + )} {committedAsrProvider === 'volcengine' ? ( <> @@ -1405,7 +1426,10 @@ function ProvidersSection() { ) : committedAsrProvider === 'local-qwen3' || committedAsrProvider === 'foundry-local-whisper' ? ( - + // 用户已经在用本地 ASR——dropdown 行的 localAsrActiveNotice 已经把 + // "在高级中切换或禁用"讲清楚了,body 不再重复 LocalAsrProviderHint。 + // 模型管理 UI 唯一入口在 AdvancedSection 里的 。 + null ) : ( <> @@ -1436,6 +1460,181 @@ function ProvidersSection() { ); } +/// 「高级」标签页——本地推理 / 实验性开关都集中在这里。 +/// +/// 设计: +/// - 主 Providers 下拉只列云端选项;本地推理 (local-qwen3 / foundry-local-whisper) +/// 完全收纳到此栏,新手不会在主流程里误开 CPU 推理。 +/// - 启用本地推理时弹出**页面顶部**的 React 浮层 popup(不再用 Tauri 系统对话框, +/// 也不再常驻一段长警告文本)——一个框、一段短话、确认/取消。 +/// - 旧的「模型设置」独立页 (LocalAsr.tsx 作为 nav tab) 已下线,模型管理 UI +/// 通过 内嵌在此处统一管理。 +/// +/// 平台可见性: +/// - macOS: 仅 Qwen3-ASR +/// - Windows: Qwen3-ASR + Foundry Local Whisper +/// - Linux: 「该平台暂未支持本地 ASR 模型集成」 +function AdvancedSection() { + const { t } = useTranslation(); + const { prefs, updatePrefs } = useHotkeySettings(); + const os = detectOS(); + const platformSupported = os === 'mac' || os === 'win'; + const switchSeqRef = useRef(0); + const [busy, setBusy] = useState(false); + // 待确认的启用目标。!== null 时浮层 popup 显示在页面顶部;用户点确认 → 真切; + // 点取消 → 回到 null。一次只允许一个 popup(用户的"只一个框"硬要求)。 + const [pendingTarget, setPendingTarget] = useState(null); + + const activeAsrProvider = (prefs?.activeAsrProvider ?? 'volcengine') as AsrPresetId; + const isOnLocalQwen3 = activeAsrProvider === 'local-qwen3'; + const isOnFoundry = activeAsrProvider === 'foundry-local-whisper'; + const isOnAnyLocal = isOnLocalQwen3 || isOnFoundry; + + const requestEnable = (target: AsrPresetId) => { + setPendingTarget(target); + }; + + const performSwitch = async (target: AsrPresetId) => { + setBusy(true); + const seq = ++switchSeqRef.current; + try { + await setActiveAsrProvider(target); + if (seq !== switchSeqRef.current) return; + if (prefs) { + await updatePrefs({ ...prefs, activeAsrProvider: target }); + } + } finally { + if (seq === switchSeqRef.current) { + setBusy(false); + setPendingTarget(null); + } + } + }; + + const pendingNameKey = + pendingTarget === 'local-qwen3' ? 'asrLocalQwen3' + : pendingTarget === 'foundry-local-whisper' ? 'asrFoundryLocalWhisper' + : null; + + return ( + <> + {/* ─── 页面顶部确认浮层 ──────────────────────────────────────────── + 用户硬要求:启用本地推理时**只**弹一个框、直接出现在页面最上方。 + 琥珀边框 + 琥珀字醒目;不阻塞滚动;点取消返回不切。 */} + {pendingTarget && pendingNameKey && ( + +
+ ⚠️ {t('settings.advanced.confirmEnableLocalTitle')} +
+
+ {t('settings.advanced.confirmEnableLocalBody', { + target: t(`settings.providers.presets.${pendingNameKey}`), + })} +
+
+ void performSwitch(pendingTarget)}> + {t('settings.advanced.confirm')} + + setPendingTarget(null)}> + {t('common.cancel')} + +
+
+ )} + + +
+
{t('settings.advanced.localAsrTitle')}
+
+ {t('settings.advanced.localAsrDesc')} +
+
+ + {/* 常驻短警告——一行字够了,详细解释留给点 Enable 后的页面顶部 popup。 */} +
+ ⚠️ {t('settings.advanced.localAsrWarningShort')} +
+ + {!platformSupported ? ( +
+ {t('settings.advanced.platformNotSupported')} +
+ ) : ( + <> + + requestEnable('local-qwen3')}> + {isOnLocalQwen3 ? t('settings.advanced.alreadyActive') : t('settings.advanced.enable')} + + + + {os === 'win' && ( + + requestEnable('foundry-local-whisper')}> + {isOnFoundry ? t('settings.advanced.alreadyActive') : t('settings.advanced.enable')} + + + )} + + )} + + {/* 「禁用本地 ASR」按钮放在 platformSupported 分支之外——若用户在另一台 + 支持本地推理的机器上启用过 local-qwen3 / foundry,preferences 同步到 + 当前 Linux/不支持平台后会卡死(无 enable / disable UI 可点),polish + 走老路径必失败。这里始终在 isOnAnyLocal 时露出 disable 入口,给用户 + 退路(PR #400 pr_agent advisory 的修法)。 */} + {isOnAnyLocal && ( + + void performSwitch('volcengine')}> + {t('settings.advanced.disable')} + + + )} +
+ + {/* 模型管理 UI(镜像源 / 模型列表 / 下载 / 删除 / 设为默认 / Foundry Local) + inline 渲染——「模型设置」独立页已删,这里是唯一入口。 */} + {platformSupported && } + + ); +} + type ProviderToolStatus = 'idle' | 'loading' | 'success' | 'empty' | 'error'; function ProviderTools({ kind, modelAccount, onModelSelected }: { kind: 'llm' | 'asr'; modelAccount: string; onModelSelected: () => void }) {