package services
|
|
import (
|
"fmt"
|
"sync"
|
"voicesnap/internal/config"
|
"voicesnap/internal/engine"
|
"voicesnap/internal/language"
|
"voicesnap/internal/model"
|
"voicesnap/internal/modelselection"
|
"voicesnap/internal/paths"
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
)
|
|
// EngineService provides engine status and model management to the frontend.
|
type EngineService struct {
|
app *application.App
|
initCallback func()
|
mu sync.RWMutex
|
status string
|
hardwareInfo string
|
lastError string
|
}
|
|
func NewEngineService() *EngineService {
|
return &EngineService{status: "loading"}
|
}
|
|
// ModelExists returns true if the ASR model files are present.
|
func (s *EngineService) ModelExists() bool {
|
return engine.ModelExists()
|
}
|
|
func (s *EngineService) GetStatus() map[string]interface{} {
|
s.mu.RLock()
|
defer s.mu.RUnlock()
|
return map[string]interface{}{
|
"status": s.status,
|
"hardwareInfo": s.hardwareInfo,
|
"error": s.lastError,
|
}
|
}
|
|
func (s *EngineService) GetCurrentModelStatus() map[string]interface{} {
|
current := s.currentModel()
|
resolved, err := model.ResolveModel(current.ModelID)
|
installed := err == nil && resolved.IsUsable()
|
status := model.ModelNotInstalled
|
var missing []string
|
var problems []string
|
if err == nil {
|
status = resolved.Status
|
missing = resolved.Missing
|
problems = resolved.Problems
|
}
|
|
return map[string]interface{}{
|
"modelID": current.ModelID,
|
"displayName": current.Profile.DisplayName,
|
"backendKind": current.Profile.BackendKind,
|
"languageID": current.LanguageSettings.EffectiveLanguageID,
|
"selectionMode": current.SelectionMode,
|
"installed": installed,
|
"installStatus": status,
|
"missing": missing,
|
"problems": problems,
|
"fallbackReason": current.FallbackReason,
|
"downloadSize": current.Profile.ApproxSize,
|
"supportedLanguages": current.Profile.SupportedLanguageIDs,
|
"recommendedLanguage": current.Profile.RecommendedFor,
|
}
|
}
|
|
func (s *EngineService) SetStatus(status, hardwareInfo, lastError string) {
|
s.mu.Lock()
|
defer s.mu.Unlock()
|
s.status = status
|
s.hardwareInfo = hardwareInfo
|
s.lastError = lastError
|
}
|
|
// DownloadModel downloads the ASR model with progress events.
|
func (s *EngineService) DownloadModel(primaryURL, fallbackURL string) error {
|
err := model.Download(primaryURL, fallbackURL, paths.ModelsRoot(), func(percent float64, downloaded, total int64) {
|
if s.app != nil {
|
s.app.Event.Emit("model:download-progress", map[string]interface{}{
|
"percent": percent,
|
"downloaded": downloaded,
|
"total": total,
|
})
|
}
|
})
|
if err != nil {
|
return err
|
}
|
|
if s.initCallback != nil {
|
go s.initCallback()
|
}
|
return nil
|
}
|
|
func (s *EngineService) DownloadCurrentModel() error {
|
current := s.currentModel()
|
if len(current.Profile.DownloadURLs) == 0 {
|
return fmt.Errorf("no download URL configured for model %s", current.ModelID)
|
}
|
|
err := model.DownloadProfile(current.Profile, current.Profile.DownloadURLs, paths.ModelsRoot(), func(percent float64, downloaded, total int64) {
|
if s.app != nil {
|
s.app.Event.Emit("model:download-progress", map[string]interface{}{
|
"percent": percent,
|
"downloaded": downloaded,
|
"total": total,
|
"modelID": current.ModelID,
|
"modelName": current.Profile.DisplayName,
|
})
|
}
|
})
|
if err != nil {
|
return err
|
}
|
|
if s.initCallback != nil {
|
go s.initCallback()
|
}
|
return nil
|
}
|
|
func (s *EngineService) ReloadCurrentModel() {
|
if s.initCallback != nil {
|
go s.initCallback()
|
}
|
}
|
|
// SetInitCallback sets the callback to re-initialize the engine after model download.
|
func (s *EngineService) SetInitCallback(cb func()) {
|
s.initCallback = cb
|
}
|
|
// SetApp sets the Wails app reference for event emission.
|
func (s *EngineService) SetApp(app *application.App) {
|
s.app = app
|
}
|
|
func (s *EngineService) currentModel() modelselection.CurrentModel {
|
cfg, err := config.Load()
|
if err != nil {
|
cfg = config.Default()
|
}
|
return modelselection.Resolve(cfg, language.NewSystemDetector())
|
}
|