| | |
| | | ) |
| | | |
| | | const ( |
| | | appVersion = "2.1.20" |
| | | appBuild = "20260603.1145" |
| | | appVersion = "2.1.29" |
| | | appBuild = "20260607.0147" |
| | | appDisplayVersion = appVersion + " (build " + appBuild + ")" |
| | | appName = "PrivateVoice Input" |
| | | appName = "PrivateVoice Dictation" |
| | | |
| | | silenceThreshold = 0.05 // RMS below this = silence (matches HasVoiceActivity) |
| | | silenceTimeoutDuration = 3 * time.Second // auto-stop after this much silence in free-talk |
| | |
| | | cfg *config.Config |
| | | recorder *audio.Recorder |
| | | eng engine.Engine |
| | | engineMu sync.Mutex |
| | | hk hotkey.Listener |
| | | paster input.Paster |
| | | history *history.Store |
| | |
| | | // Create services for Wails bindings |
| | | appService := services.NewAppService(app.cfg, appDisplayVersion) |
| | | configService := services.NewConfigService(app.cfg) |
| | | engineService := services.NewEngineService() |
| | | engineService := services.NewEngineService(app.cfg) |
| | | app.engineService = engineService |
| | | hotkeyService := services.NewHotkeyService(app.cfg) |
| | | permissionService := services.NewPermissionService() |
| | |
| | | return |
| | | } |
| | | |
| | | text, err := a.eng.Recognize(samples) |
| | | a.engineMu.Lock() |
| | | eng := a.eng |
| | | if eng == nil { |
| | | a.engineMu.Unlock() |
| | | logger.Error("Recognition skipped: engine not ready") |
| | | a.indicator.SetStatus(overlay.StatusError, "引擎未就绪") |
| | | a.delayedHideIf(autoHide, 2000) |
| | | return |
| | | } |
| | | text, err := eng.Recognize(samples) |
| | | a.engineMu.Unlock() |
| | | if err != nil { |
| | | logger.Error("Recognition failed: %v", err) |
| | | a.indicator.SetStatus(overlay.StatusError, "错误") |
| | |
| | | eng, err := engine.New() |
| | | if err != nil { |
| | | logger.Error("Engine initialization failed: %v", err) |
| | | a.replaceEngine(nil) |
| | | status := "need_model" |
| | | if modelExists { |
| | | status = "error" |
| | |
| | | return |
| | | } |
| | | |
| | | a.mu.Lock() |
| | | a.eng = eng |
| | | a.mu.Unlock() |
| | | a.replaceEngine(eng) |
| | | |
| | | logger.Info("ASR engine ready: %s", eng.HardwareInfo()) |
| | | if a.engineService != nil { |
| | |
| | | a.indicator.SetStatus(overlay.StatusReady, hotkeyReadyText(keyName, hotkeyMode)) |
| | | a.indicator.Show() |
| | | a.delayedHideIf(autoHide, 2000) |
| | | } |
| | | |
| | | func (a *App) replaceEngine(eng engine.Engine) { |
| | | a.engineMu.Lock() |
| | | defer a.engineMu.Unlock() |
| | | |
| | | a.mu.Lock() |
| | | old := a.eng |
| | | a.eng = eng |
| | | a.mu.Unlock() |
| | | |
| | | if old != nil && old != eng { |
| | | old.Close() |
| | | } |
| | | } |
| | | |
| | | func hotkeyReadyText(keyName, mode string) string { |
| | |
| | | if a.recorder != nil { |
| | | a.recorder.Close() |
| | | } |
| | | if a.eng != nil { |
| | | a.eng.Close() |
| | | } |
| | | a.replaceEngine(nil) |
| | | a.wg.Wait() |
| | | logger.Info("Cleanup complete") |
| | | } |
| | |
| | | |
| | | // SetEngine sets the engine after model download. |
| | | func (a *App) SetEngine(eng engine.Engine) { |
| | | a.mu.Lock() |
| | | defer a.mu.Unlock() |
| | | a.eng = eng |
| | | a.replaceEngine(eng) |
| | | } |