From 859c14d783dc31fe68550b114fc8adeea09dab2a Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 13 Jun 2026 21:48:32 +0800
Subject: [PATCH] Sync macOS paste clipboard safety to X-ASR branch

---
 privatevoice.src/internal/engine/engine.go |   39 +++++++++++++++++++++++++++++++++------
 1 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/privatevoice.src/internal/engine/engine.go b/privatevoice.src/internal/engine/engine.go
index 8e2c23c..3f939c1 100755
--- a/privatevoice.src/internal/engine/engine.go
+++ b/privatevoice.src/internal/engine/engine.go
@@ -3,8 +3,10 @@
 import (
 	"fmt"
 	"voicesnap/internal/config"
+	"voicesnap/internal/language"
 	"voicesnap/internal/logger"
 	"voicesnap/internal/model"
+	"voicesnap/internal/modelselection"
 	"voicesnap/internal/paths"
 )
 
@@ -15,6 +17,20 @@
 	// HardwareInfo returns a human-readable description of the hardware backend being used.
 	HardwareInfo() string
 	// Close releases engine resources.
+	Close()
+}
+
+// StreamingEngine is implemented by engines that can expose partial results
+// while audio is still being captured.
+type StreamingEngine interface {
+	NewStreamingSession() (StreamingSession, error)
+}
+
+// StreamingSession receives incremental 16kHz mono PCM and returns the current
+// best transcript for the active utterance.
+type StreamingSession interface {
+	Accept(samples []float32) (string, error)
+	Finish() (string, error)
 	Close()
 }
 
@@ -60,9 +76,10 @@
 	cfg, err := config.Load()
 	if err != nil {
 		logger.Error("Failed to load config for model selection: %v", err)
-		return NewWithModelID(model.DefaultModelID)
+		cfg = config.Default()
 	}
-	return NewWithModelID(cfg.SelectedModelID)
+	current := modelselection.Resolve(cfg, language.NewSystemDetector())
+	return NewWithModelID(current.ModelID)
 }
 
 func NewWithModelID(modelID string) (Engine, error) {
@@ -77,18 +94,28 @@
 	if !resolved.IsUsable() {
 		return nil, fmt.Errorf("model %s is not usable: %s missing=%v problems=%v", resolved.ModelID, resolved.Status, resolved.Missing, resolved.Problems)
 	}
-	if resolved.BackendKind != model.BackendSenseVoice {
+	if !isSupportedBackend(resolved.BackendKind) {
 		return nil, fmt.Errorf("unsupported backend kind: %s", resolved.BackendKind)
 	}
 
-	logger.Info("Loading model %s from %s", resolved.ModelID, resolved.RootDir)
+	logger.Info("Loading model %s backend=%s from %s", resolved.ModelID, resolved.BackendKind, resolved.RootDir)
 	return newPlatformEngine(resolved)
+}
+
+func isSupportedBackend(backend string) bool {
+	switch backend {
+	case model.BackendSenseVoice, model.BackendMoonshine, model.BackendNemoTransducer, model.BackendQwen3ASR, model.BackendXASRStreaming:
+		return true
+	default:
+		return false
+	}
 }
 
 func resolveCurrentModel() (model.ResolvedModel, error) {
 	cfg, err := config.Load()
 	if err != nil {
-		return model.ResolveModel(model.DefaultModelID)
+		cfg = config.Default()
 	}
-	return model.ResolveModel(model.NormalizeModelID(cfg.SelectedModelID))
+	current := modelselection.Resolve(cfg, language.NewSystemDetector())
+	return model.ResolveModel(current.ModelID)
 }

--
Gitblit v1.9.3