From fdc5c43b2cf8ebc3d67099cfba9936c838281561 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Tue, 30 Jun 2026 03:36:45 +0800
Subject: [PATCH] Add Windows QA evidence verifier
---
privatevoice.src/frontend/src/components/settings/GeneralPage.svelte | 346 ---------------------------------------------------------
1 files changed, 3 insertions(+), 343 deletions(-)
diff --git a/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte b/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
index bf9186e..d54ca49 100755
--- a/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
+++ b/privatevoice.src/frontend/src/components/settings/GeneralPage.svelte
@@ -1,24 +1,14 @@
<script lang="ts">
- import { Call, Events } from '@wailsio/runtime'
+ import { Call } from '@wailsio/runtime'
import ToggleSwitch from '../shared/ToggleSwitch.svelte'
- import { t, setLocale } from '../../lib/i18n'
- import { autoHide, soundFeedback, copyToClipboard, startAtLogin, hotkeyVK, hotkeyMode, languageMode, languageID, effectiveLanguageID, uiLocale } from '../../lib/stores/config'
+ import { t } from '../../lib/i18n'
+ import { autoHide, soundFeedback, copyToClipboard, startAtLogin, hotkeyVK, hotkeyMode } from '../../lib/stores/config'
import { deviceName, engineStatus, engineHardwareInfo } from '../../lib/stores/app'
import { hotkeyName } from '../../lib/stores/indicator'
interface InputDevice {
name: string
isDefault: boolean
- }
-
- interface ModelOption {
- modelID: string
- displayName: string
- tier: string
- installed: boolean
- isCurrent: boolean
- isDefault: boolean
- downloadSize?: string
}
let autoHideVal = $state(true)
@@ -32,17 +22,10 @@
let hwInfo = $state('')
let currentKeyName = $state('Ctrl')
let hotkeyModeVal: 'tap' | 'hold' = $state('hold')
- let languageModeVal: 'auto' | 'manual' = $state('auto')
- let languageIDVal: 'zh-CN' | 'en' = $state('zh-CN')
- let effectiveLanguageIDVal: 'zh-CN' | 'en' = $state('zh-CN')
let isRecording = $state(false)
let hintText = $state('')
let devices = $state<InputDevice[]>([])
let showDeviceDropdown = $state(false)
- let modelOptions = $state<ModelOption[]>([])
- let modelBusyID = $state('')
- let modelProgress = $state(0)
- let modelError = $state('')
const unsub1 = autoHide.subscribe(v => { autoHideVal = v })
const unsub2 = startAtLogin.subscribe(v => { startAtLoginVal = v })
@@ -53,16 +36,6 @@
const unsub7 = soundFeedback.subscribe(v => { soundFeedbackVal = v })
const unsub8 = copyToClipboard.subscribe(v => { copyToClipboardVal = v })
const unsub9 = hotkeyMode.subscribe(v => { hotkeyModeVal = v })
- const unsub10 = languageMode.subscribe(v => { languageModeVal = v })
- const unsub11 = languageID.subscribe(v => { languageIDVal = v })
- const unsub12 = effectiveLanguageID.subscribe(v => { effectiveLanguageIDVal = v })
-
- Events.On('model:download-progress', (ev: any) => {
- const data = ev?.data
- if (data?.modelID && data.modelID === modelBusyID && typeof data.percent === 'number') {
- modelProgress = data.percent
- }
- })
// Load actual device name from backend on mount
async function loadDeviceName() {
@@ -107,102 +80,9 @@
const hideDock: any = await Call.ByName('voicesnap/services.ConfigService.GetHideDockIcon')
hideDockIconVal = !!hideDock
} catch {}
- try {
- const lang: any = await Call.ByName('voicesnap/services.ConfigService.GetLanguageSettings')
- applyLanguageSettings(lang)
- } catch {}
- await loadModelOptions()
settingsLoaded = true
}
loadSettings()
-
- function applyLanguageSettings(settings: any) {
- const mode = settings?.languageMode === 'manual' ? 'manual' : 'auto'
- const configured = settings?.languageID === 'en' ? 'en' : 'zh-CN'
- const effective = settings?.effectiveLanguageID === 'en' ? 'en' : 'zh-CN'
- const locale = settings?.uiLocale === 'en' ? 'en' : 'zh'
- languageMode.set(mode)
- languageID.set(configured)
- effectiveLanguageID.set(effective)
- uiLocale.set(locale)
- languageModeVal = mode
- languageIDVal = configured
- effectiveLanguageIDVal = effective
- setLocale(locale)
- }
-
- async function syncEngineForCurrentModel() {
- try {
- const current: any = await Call.ByName('voicesnap/services.EngineService.GetCurrentModelStatus')
- if (current?.installed) {
- engineStatus.set('loading')
- } else {
- engineHardwareInfo.set('')
- engineStatus.set('need_model')
- }
- await Call.ByName('voicesnap/services.EngineService.ReloadCurrentModel')
- } catch {}
- }
-
- async function loadModelOptions() {
- try {
- const options: any = await Call.ByName('voicesnap/services.EngineService.ListModelOptions')
- modelOptions = Array.isArray(options) ? options : []
- } catch {
- modelOptions = []
- }
- }
-
- function modelDescription(modelID: string): string {
- switch (modelID) {
- case 'sensevoice-zh': return t('models.sensevoiceDesc')
- case 'moonshine-en': return t('models.moonshineDesc')
- case 'parakeet-en': return t('models.parakeetDesc')
- case 'qwen3-asr-0.6b': return t('models.qwen3Desc')
- default: return ''
- }
- }
-
- function modelTierLabel(option: ModelOption): string {
- if (option.isDefault) return t('settings.modelDefault')
- if (option.tier === 'advanced') return t('settings.modelAdvanced')
- return option.tier || ''
- }
-
- function modelActionLabel(option: ModelOption): string {
- if (modelBusyID === option.modelID) {
- if (!option.installed && modelProgress > 0) {
- return `${Math.min(100, Math.max(0, modelProgress)).toFixed(0)}%`
- }
- return t('settings.modelWorking')
- }
- if (option.isCurrent) return t('settings.modelCurrent')
- if (option.installed) return t('settings.modelUse')
- return t('settings.modelDownload')
- }
-
- async function onModelAction(option: ModelOption) {
- if (option.isCurrent || modelBusyID) return
- modelBusyID = option.modelID
- modelProgress = 0
- modelError = ''
- try {
- if (option.installed) {
- engineStatus.set('loading')
- await Call.ByName('voicesnap/services.EngineService.SelectModel', option.modelID)
- } else {
- await Call.ByName('voicesnap/services.EngineService.DownloadModelByID', option.modelID)
- engineStatus.set('loading')
- }
- await loadModelOptions()
- } catch (err: any) {
- modelError = err?.message || String(err || t('settings.modelActionFailed'))
- await loadModelOptions()
- } finally {
- modelBusyID = ''
- modelProgress = 0
- }
- }
async function onAutoHideChange(checked: boolean) {
autoHide.set(checked)
@@ -231,33 +111,6 @@
try {
await Call.ByName('voicesnap/services.ConfigService.SetHotkeyMode', mode)
} catch {}
- }
-
- async function onLanguageChange(selection: 'auto' | 'zh-CN' | 'en') {
- const prevMode = languageModeVal
- const prevID = languageIDVal
- if (selection === 'auto') {
- languageModeVal = 'auto'
- languageMode.set('auto')
- } else {
- languageModeVal = 'manual'
- languageIDVal = selection
- languageMode.set('manual')
- languageID.set(selection)
- }
- try {
- const settings: any = selection === 'auto'
- ? await Call.ByName('voicesnap/services.ConfigService.SetLanguageAuto')
- : await Call.ByName('voicesnap/services.ConfigService.SetLanguageManual', selection)
- applyLanguageSettings(settings)
- await syncEngineForCurrentModel()
- await loadModelOptions()
- } catch {
- languageModeVal = prevMode
- languageIDVal = prevID
- languageMode.set(prevMode)
- languageID.set(prevID)
- }
}
async function onStartAtLoginChange(checked: boolean) {
@@ -422,90 +275,6 @@
</div>
</div>
-
- {#if settingsLoaded}
- <div class="section">
- <div class="setting-row">
- <div class="setting-info">
- <span class="setting-label">{t('settings.language')}</span>
- <span class="setting-desc">
- {languageModeVal === 'auto'
- ? t('settings.languageAutoDesc')
- : effectiveLanguageIDVal === 'en'
- ? t('settings.languageEnglishDesc')
- : t('settings.languageChineseDesc')}
- </span>
- </div>
- <div class="segmented language" role="group" aria-label={t('settings.language')}>
- <button
- class:active={languageModeVal === 'auto'}
- onclick={() => onLanguageChange('auto')}
- >
- {t('settings.languageAuto')}
- </button>
- <button
- class:active={languageModeVal === 'manual' && languageIDVal === 'zh-CN'}
- onclick={() => onLanguageChange('zh-CN')}
- >
- {t('settings.languageChinese')}
- </button>
- <button
- class:active={languageModeVal === 'manual' && languageIDVal === 'en'}
- onclick={() => onLanguageChange('en')}
- >
- {t('settings.languageEnglish')}
- </button>
- </div>
- </div>
- </div>
- {/if}
-
- {#if settingsLoaded && modelOptions.length > 0}
- <div class="section">
- <div class="setting-row models-header">
- <div class="setting-info">
- <span class="setting-label">{t('settings.recognitionModel')}</span>
- <span class="setting-desc">
- {effectiveLanguageIDVal === 'en' ? t('settings.recognitionModelEnglishDesc') : t('settings.recognitionModelChineseDesc')}
- </span>
- </div>
- </div>
-
- <div class="model-list">
- {#each modelOptions as option}
- <div class="model-card" class:current={option.isCurrent}>
- <div class="model-main">
- <div class="model-title-row">
- <span class="model-title">{option.displayName}</span>
- <span class="model-pill" class:current={option.isCurrent}>
- {option.isCurrent ? t('settings.modelCurrent') : modelTierLabel(option)}
- </span>
- </div>
- <span class="model-desc">{modelDescription(option.modelID)}</span>
- <span class="model-meta">
- {option.installed ? t('settings.modelInstalled') : t('settings.modelNotInstalled')}
- {#if option.downloadSize}
- ยท {option.downloadSize}
- {/if}
- </span>
- </div>
- <button
- class="model-action"
- class:primary={!option.isCurrent}
- disabled={option.isCurrent || !!modelBusyID}
- onclick={() => onModelAction(option)}
- >
- {modelActionLabel(option)}
- </button>
- </div>
- {/each}
- </div>
-
- {#if modelError}
- <p class="model-error">{modelError}</p>
- {/if}
- </div>
- {/if}
<!-- Engine + Device -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
@@ -758,16 +527,6 @@
flex-shrink: 0;
}
- .segmented.language {
- grid-template-columns: repeat(3, 1fr);
- min-width: 246px;
- }
-
- .segmented.language button {
- min-width: 76px;
- padding: 0 10px;
- }
-
.segmented button {
min-width: 66px;
height: 30px;
@@ -791,105 +550,6 @@
height: 1px;
background: var(--color-separator);
margin: var(--spacing-sm) 0;
- }
-
- .models-header {
- align-items: flex-start;
- }
-
- .model-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
-
- .model-card {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: var(--spacing-md);
- min-height: 78px;
- padding: 10px 12px;
- border: 1px solid var(--color-separator);
- border-radius: var(--radius-sm);
- background: var(--color-bg-secondary);
- }
-
- .model-card.current {
- border-color: rgba(0, 122, 255, 0.38);
- }
-
- .model-main {
- display: flex;
- flex-direction: column;
- gap: 3px;
- min-width: 0;
- }
-
- .model-title-row {
- display: flex;
- align-items: center;
- gap: 8px;
- min-width: 0;
- }
-
- .model-title {
- font-size: var(--font-size-base);
- font-weight: 600;
- }
-
- .model-pill {
- display: inline-flex;
- align-items: center;
- height: 20px;
- padding: 0 7px;
- border-radius: 999px;
- background: var(--color-bg-tertiary);
- color: var(--color-secondary-label);
- font-size: 11px;
- font-weight: 500;
- white-space: nowrap;
- }
-
- .model-pill.current {
- background: rgba(52, 199, 89, 0.14);
- color: var(--color-green);
- }
-
- .model-desc,
- .model-meta {
- font-size: var(--font-size-xs);
- color: var(--color-tertiary-label);
- }
-
- .model-action {
- min-width: 76px;
- height: 30px;
- padding: 0 12px;
- border: none;
- border-radius: var(--radius-sm);
- background: var(--color-bg-tertiary);
- color: var(--color-secondary-label);
- font-size: var(--font-size-sm);
- font-weight: 500;
- cursor: pointer;
- white-space: nowrap;
- }
-
- .model-action.primary {
- background: var(--color-blue);
- color: white;
- }
-
- .model-action:disabled {
- cursor: not-allowed;
- opacity: 0.72;
- }
-
- .model-error {
- margin-top: 8px;
- font-size: var(--font-size-xs);
- color: var(--color-red);
}
/* Device selector */
--
Gitblit v1.9.3