From 0ffbf1935c9d091cce22a5583275ac0902f7a693 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 02 Jul 2026 03:04:51 +0800
Subject: [PATCH] Add Cantonese language model support

---
 privatevoice.src/services/engine_service.go |  108 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 98 insertions(+), 10 deletions(-)

diff --git a/privatevoice.src/services/engine_service.go b/privatevoice.src/services/engine_service.go
index ecfb3ac..2a93b00 100755
--- a/privatevoice.src/services/engine_service.go
+++ b/privatevoice.src/services/engine_service.go
@@ -65,6 +65,20 @@
 
 func (s *EngineService) ListModelOptions() []map[string]interface{} {
 	current := s.currentModel()
+	return s.listModelOptionsForCurrent(current)
+}
+
+func (s *EngineService) ListModelOptionsForLanguage(languageID string) []map[string]interface{} {
+	current := s.currentModel()
+	activeLanguageID := current.LanguageSettings.EffectiveLanguageID
+	current.LanguageSettings = language.Resolve(config.LanguageModeManual, model.NormalizeLanguageID(languageID), nil)
+	if current.LanguageSettings.EffectiveLanguageID != activeLanguageID {
+		current.ModelID = ""
+	}
+	return s.listModelOptionsForCurrent(current)
+}
+
+func (s *EngineService) listModelOptionsForCurrent(current modelselection.CurrentModel) []map[string]interface{} {
 	languageProfile, err := model.GetLanguageProfile(current.LanguageSettings.EffectiveLanguageID)
 	if err != nil {
 		return nil
@@ -105,6 +119,25 @@
 	return nil
 }
 
+func (s *EngineService) SelectModelForLanguage(languageID, modelID string) error {
+	languageID = model.NormalizeLanguageID(languageID)
+	profile, languageProfile, err := s.allowedModelProfileForLanguage(languageID, modelID)
+	if err != nil {
+		return err
+	}
+	resolved, err := model.ResolveModel(profile.ID)
+	if err != nil {
+		return err
+	}
+	if !resolved.IsUsable() {
+		return fmt.Errorf("model %s is not installed", profile.ID)
+	}
+
+	s.persistLanguageModelSelection(languageID, profile, languageProfile)
+	s.ReloadCurrentModel()
+	return nil
+}
+
 func (s *EngineService) DownloadModelByID(modelID string) error {
 	profile, err := s.allowedModelProfile(modelID)
 	if err != nil {
@@ -141,6 +174,56 @@
 	config.Save(s.cfg)
 	s.ReloadCurrentModel()
 	return nil
+}
+
+func (s *EngineService) DownloadModelByIDForLanguage(languageID, modelID string) error {
+	languageID = model.NormalizeLanguageID(languageID)
+	profile, languageProfile, err := s.allowedModelProfileForLanguage(languageID, modelID)
+	if err != nil {
+		return err
+	}
+	if len(profile.DownloadURLs) == 0 {
+		return fmt.Errorf("no download URL configured for model %s", profile.ID)
+	}
+
+	ctx, finish, err := s.beginModelDownload(profile.ID)
+	if err != nil {
+		return err
+	}
+	defer finish()
+
+	err = model.DownloadProfileWithContext(ctx, profile, profile.DownloadURLs, paths.ModelsRoot(), func(percent float64, downloaded, total int64) {
+		s.updateModelDownloadProgress(profile.ID, percent, downloaded, total)
+		if s.app != nil {
+			s.app.Event.Emit("model:download-progress", map[string]interface{}{
+				"percent":    percent,
+				"downloaded": downloaded,
+				"total":      total,
+				"modelID":    profile.ID,
+				"modelName":  profile.DisplayName,
+			})
+		}
+	})
+	if err != nil {
+		return err
+	}
+
+	s.persistLanguageModelSelection(languageID, profile, languageProfile)
+	s.ReloadCurrentModel()
+	return nil
+}
+
+func (s *EngineService) persistLanguageModelSelection(languageID string, profile model.ModelProfile, languageProfile model.LanguageProfile) {
+	s.cfg.LanguageMode = config.LanguageModeManual
+	s.cfg.LanguageID = languageID
+	if profile.ID == languageProfile.DefaultModelID {
+		s.cfg.ModelSelectionMode = config.ModelSelectionModeAuto
+		s.cfg.SelectedModelID = ""
+	} else {
+		s.cfg.ModelSelectionMode = config.ModelSelectionModeManual
+		s.cfg.SelectedModelID = profile.ID
+	}
+	config.Save(s.cfg)
 }
 
 func (s *EngineService) CancelModelDownload(modelID string) bool {
@@ -369,32 +452,37 @@
 }
 
 func (s *EngineService) allowedModelProfile(modelID string) (model.ModelProfile, error) {
+	current := s.currentModel()
+	profile, _, err := s.allowedModelProfileForLanguage(current.LanguageSettings.EffectiveLanguageID, modelID)
+	return profile, err
+}
+
+func (s *EngineService) allowedModelProfileForLanguage(languageID, modelID string) (model.ModelProfile, model.LanguageProfile, error) {
 	profile, err := model.GetModelProfile(modelID)
 	if err != nil {
-		return model.ModelProfile{}, err
+		return model.ModelProfile{}, model.LanguageProfile{}, err
 	}
 	switch model.ModelUnsupportedReason(profile.ID) {
 	case "":
 	case model.UnsupportedReasonRequiresMacOS14:
-		return model.ModelProfile{}, fmt.Errorf("model %s requires macOS 14 or later", profile.ID)
+		return model.ModelProfile{}, model.LanguageProfile{}, fmt.Errorf("model %s requires macOS 14 or later", profile.ID)
 	case model.UnsupportedReasonWindowsPreview:
-		return model.ModelProfile{}, fmt.Errorf("model %s is not supported in the Windows preview build; use SenseVoice", profile.ID)
+		return model.ModelProfile{}, model.LanguageProfile{}, fmt.Errorf("model %s is not supported in the Windows preview build; use SenseVoice", profile.ID)
 	default:
-		return model.ModelProfile{}, fmt.Errorf("model %s is not supported in this build", profile.ID)
+		return model.ModelProfile{}, model.LanguageProfile{}, fmt.Errorf("model %s is not supported in this build", profile.ID)
 	}
 
-	current := s.currentModel()
-	languageProfile, err := model.GetLanguageProfile(current.LanguageSettings.EffectiveLanguageID)
+	languageProfile, err := model.GetLanguageProfile(model.NormalizeLanguageID(languageID))
 	if err != nil {
-		return model.ModelProfile{}, err
+		return model.ModelProfile{}, model.LanguageProfile{}, err
 	}
 	if profile.ID == languageProfile.DefaultModelID {
-		return profile, nil
+		return profile, languageProfile, nil
 	}
 	for _, id := range languageProfile.UpgradeModelIDs {
 		if profile.ID == id {
-			return profile, nil
+			return profile, languageProfile, nil
 		}
 	}
-	return model.ModelProfile{}, fmt.Errorf("model %s is not available for language %s", profile.ID, current.LanguageSettings.EffectiveLanguageID)
+	return model.ModelProfile{}, model.LanguageProfile{}, fmt.Errorf("model %s is not available for language %s", profile.ID, languageProfile.ID)
 }

--
Gitblit v1.9.3