abstractvoice 0.3.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,398 @@
1
+ """
2
+ Simple Model Manager for AbstractVoice
3
+
4
+ Provides clean, simple APIs for model management that can be used by both
5
+ CLI commands and third-party applications.
6
+ """
7
+
8
+ import os
9
+ import json
10
+ import time
11
+ import threading
12
+ from typing import Dict, List, Optional, Callable, Any
13
+ from pathlib import Path
14
+
15
+
16
+ def _import_tts():
17
+ """Import TTS with helpful error message if dependencies missing."""
18
+ try:
19
+ from TTS.api import TTS
20
+ from TTS.utils.manage import ModelManager
21
+ return TTS, ModelManager
22
+ except ImportError as e:
23
+ raise ImportError(
24
+ "TTS functionality requires coqui-tts. Install with:\n"
25
+ " pip install abstractvoice[tts]\n"
26
+ f"Original error: {e}"
27
+ ) from e
28
+
29
+
30
+ class SimpleModelManager:
31
+ """Simple, clean model manager for AbstractVoice."""
32
+
33
+ # Essential model - guaranteed to work everywhere, reasonable size
34
+ ESSENTIAL_MODEL = "tts_models/en/ljspeech/fast_pitch"
35
+
36
+ # Available models organized by language with metadata
37
+ AVAILABLE_MODELS = {
38
+ "en": {
39
+ "fast_pitch": {
40
+ "model": "tts_models/en/ljspeech/fast_pitch",
41
+ "name": "Fast Pitch (English)",
42
+ "quality": "good",
43
+ "size_mb": 107,
44
+ "description": "Lightweight, reliable English voice",
45
+ "requires_espeak": False,
46
+ "default": True
47
+ },
48
+ "vits": {
49
+ "model": "tts_models/en/ljspeech/vits",
50
+ "name": "VITS (English)",
51
+ "quality": "excellent",
52
+ "size_mb": 328,
53
+ "description": "High-quality English voice with natural prosody",
54
+ "requires_espeak": True,
55
+ "default": False
56
+ },
57
+ "tacotron2": {
58
+ "model": "tts_models/en/ljspeech/tacotron2-DDC",
59
+ "name": "Tacotron2 (English)",
60
+ "quality": "good",
61
+ "size_mb": 362,
62
+ "description": "Classic English voice, reliable",
63
+ "requires_espeak": False,
64
+ "default": False
65
+ }
66
+ },
67
+ "fr": {
68
+ "css10_vits": {
69
+ "model": "tts_models/fr/css10/vits",
70
+ "name": "CSS10 VITS (French)",
71
+ "quality": "excellent",
72
+ "size_mb": 548,
73
+ "description": "High-quality French voice",
74
+ "requires_espeak": True,
75
+ "default": True
76
+ },
77
+ "mai_tacotron2": {
78
+ "model": "tts_models/fr/mai/tacotron2-DDC",
79
+ "name": "MAI Tacotron2 (French)",
80
+ "quality": "good",
81
+ "size_mb": 362,
82
+ "description": "Reliable French voice",
83
+ "requires_espeak": False,
84
+ "default": False
85
+ }
86
+ },
87
+ "es": {
88
+ "mai_tacotron2": {
89
+ "model": "tts_models/es/mai/tacotron2-DDC",
90
+ "name": "MAI Tacotron2 (Spanish)",
91
+ "quality": "good",
92
+ "size_mb": 362,
93
+ "description": "Reliable Spanish voice",
94
+ "requires_espeak": False,
95
+ "default": True
96
+ },
97
+ "css10_vits": {
98
+ "model": "tts_models/es/css10/vits",
99
+ "name": "CSS10 VITS (Spanish)",
100
+ "quality": "excellent",
101
+ "size_mb": 548,
102
+ "description": "High-quality Spanish voice",
103
+ "requires_espeak": True,
104
+ "default": False
105
+ }
106
+ },
107
+ "de": {
108
+ "thorsten_vits": {
109
+ "model": "tts_models/de/thorsten/vits",
110
+ "name": "Thorsten VITS (German)",
111
+ "quality": "excellent",
112
+ "size_mb": 548,
113
+ "description": "High-quality German voice",
114
+ "requires_espeak": True,
115
+ "default": True
116
+ }
117
+ },
118
+ "it": {
119
+ "mai_male_vits": {
120
+ "model": "tts_models/it/mai_male/vits",
121
+ "name": "MAI Male VITS (Italian)",
122
+ "quality": "excellent",
123
+ "size_mb": 548,
124
+ "description": "High-quality Italian male voice",
125
+ "requires_espeak": True,
126
+ "default": True
127
+ },
128
+ "mai_female_vits": {
129
+ "model": "tts_models/it/mai_female/vits",
130
+ "name": "MAI Female VITS (Italian)",
131
+ "quality": "excellent",
132
+ "size_mb": 548,
133
+ "description": "High-quality Italian female voice",
134
+ "requires_espeak": True,
135
+ "default": False
136
+ }
137
+ }
138
+ }
139
+
140
+ def __init__(self, debug_mode: bool = False):
141
+ self.debug_mode = debug_mode
142
+ self._cache_dir = None
143
+
144
+ @property
145
+ def cache_dir(self) -> str:
146
+ """Get the TTS model cache directory."""
147
+ if self._cache_dir is None:
148
+ # Check common cache locations
149
+ import appdirs
150
+ potential_dirs = [
151
+ os.path.expanduser("~/.cache/tts"),
152
+ appdirs.user_data_dir("tts"),
153
+ os.path.expanduser("~/.local/share/tts"),
154
+ os.path.expanduser("~/Library/Application Support/tts"), # macOS
155
+ ]
156
+
157
+ # Find existing cache or use default
158
+ for cache_dir in potential_dirs:
159
+ if os.path.exists(cache_dir):
160
+ self._cache_dir = cache_dir
161
+ break
162
+ else:
163
+ # Use appdirs default
164
+ self._cache_dir = appdirs.user_data_dir("tts")
165
+
166
+ return self._cache_dir
167
+
168
+ def is_model_cached(self, model_name: str) -> bool:
169
+ """Check if a specific model is cached locally."""
170
+ try:
171
+ # Convert model name to cache directory structure
172
+ cache_name = model_name.replace("/", "--")
173
+ model_path = os.path.join(self.cache_dir, cache_name)
174
+
175
+ if not os.path.exists(model_path):
176
+ return False
177
+
178
+ # Check for essential model files
179
+ essential_files = ["model.pth", "config.json"]
180
+ return any(os.path.exists(os.path.join(model_path, f)) for f in essential_files)
181
+ except Exception as e:
182
+ if self.debug_mode:
183
+ print(f"Error checking cache for {model_name}: {e}")
184
+ return False
185
+
186
+ def download_model(self, model_name: str, progress_callback: Optional[Callable[[str, bool], None]] = None) -> bool:
187
+ """Download a specific model.
188
+
189
+ Args:
190
+ model_name: TTS model name (e.g., 'tts_models/en/ljspeech/fast_pitch')
191
+ progress_callback: Optional callback function(model_name, success)
192
+
193
+ Returns:
194
+ bool: True if successful
195
+ """
196
+ if self.is_model_cached(model_name):
197
+ if self.debug_mode:
198
+ print(f"✅ {model_name} already cached")
199
+ if progress_callback:
200
+ progress_callback(model_name, True)
201
+ return True
202
+
203
+ try:
204
+ TTS, _ = _import_tts()
205
+
206
+ if self.debug_mode:
207
+ print(f"📥 Downloading {model_name}...")
208
+
209
+ start_time = time.time()
210
+
211
+ # Initialize TTS to trigger download
212
+ tts = TTS(model_name=model_name, progress_bar=True)
213
+
214
+ download_time = time.time() - start_time
215
+ if self.debug_mode:
216
+ print(f"✅ Downloaded {model_name} in {download_time:.1f}s")
217
+
218
+ if progress_callback:
219
+ progress_callback(model_name, True)
220
+ return True
221
+
222
+ except Exception as e:
223
+ if self.debug_mode:
224
+ print(f"❌ Failed to download {model_name}: {e}")
225
+ if progress_callback:
226
+ progress_callback(model_name, False)
227
+ return False
228
+
229
+ def download_essential_model(self, progress_callback: Optional[Callable[[str, bool], None]] = None) -> bool:
230
+ """Download the essential English model for immediate functionality."""
231
+ return self.download_model(self.ESSENTIAL_MODEL, progress_callback)
232
+
233
+ def list_available_models(self, language: Optional[str] = None) -> Dict[str, Any]:
234
+ """Get list of available models with metadata.
235
+
236
+ Args:
237
+ language: Optional language filter
238
+
239
+ Returns:
240
+ dict: Model information in JSON-serializable format
241
+ """
242
+ if language:
243
+ if language in self.AVAILABLE_MODELS:
244
+ return {language: self.AVAILABLE_MODELS[language]}
245
+ else:
246
+ return {}
247
+
248
+ # Return all models with cache status
249
+ result = {}
250
+ for lang, models in self.AVAILABLE_MODELS.items():
251
+ result[lang] = {}
252
+ for model_id, model_info in models.items():
253
+ # Add cache status to each model
254
+ model_data = model_info.copy()
255
+ model_data["cached"] = self.is_model_cached(model_info["model"])
256
+ result[lang][model_id] = model_data
257
+
258
+ return result
259
+
260
+ def get_cached_models(self) -> List[str]:
261
+ """Get list of model names that are currently cached."""
262
+ if not os.path.exists(self.cache_dir):
263
+ return []
264
+
265
+ cached = []
266
+ try:
267
+ for item in os.listdir(self.cache_dir):
268
+ if item.startswith("tts_models--"):
269
+ # Convert cache name back to model name
270
+ model_name = item.replace("--", "/")
271
+ if self.is_model_cached(model_name):
272
+ cached.append(model_name)
273
+ except Exception as e:
274
+ if self.debug_mode:
275
+ print(f"Error listing cached models: {e}")
276
+
277
+ return cached
278
+
279
+ def get_status(self) -> Dict[str, Any]:
280
+ """Get comprehensive status information."""
281
+ cached_models = self.get_cached_models()
282
+ essential_cached = self.ESSENTIAL_MODEL in cached_models
283
+
284
+ # Calculate total cache size
285
+ total_size_mb = 0
286
+ if os.path.exists(self.cache_dir):
287
+ try:
288
+ for root, dirs, files in os.walk(self.cache_dir):
289
+ for file in files:
290
+ total_size_mb += os.path.getsize(os.path.join(root, file)) / (1024 * 1024)
291
+ except:
292
+ pass
293
+
294
+ return {
295
+ "cache_dir": self.cache_dir,
296
+ "cached_models": cached_models,
297
+ "total_cached": len(cached_models),
298
+ "essential_model_cached": essential_cached,
299
+ "essential_model": self.ESSENTIAL_MODEL,
300
+ "ready_for_offline": essential_cached,
301
+ "total_size_mb": round(total_size_mb, 1),
302
+ "available_languages": list(self.AVAILABLE_MODELS.keys()),
303
+ }
304
+
305
+ def clear_cache(self, confirm: bool = False) -> bool:
306
+ """Clear the model cache."""
307
+ if not confirm:
308
+ return False
309
+
310
+ try:
311
+ import shutil
312
+ if os.path.exists(self.cache_dir):
313
+ shutil.rmtree(self.cache_dir)
314
+ if self.debug_mode:
315
+ print(f"✅ Cleared model cache: {self.cache_dir}")
316
+ return True
317
+ return True
318
+ except Exception as e:
319
+ if self.debug_mode:
320
+ print(f"❌ Failed to clear cache: {e}")
321
+ return False
322
+
323
+ def ensure_essential_model(self, auto_download: bool = True) -> bool:
324
+ """Ensure the essential model is available.
325
+
326
+ Args:
327
+ auto_download: Whether to download if not cached
328
+
329
+ Returns:
330
+ bool: True if essential model is ready
331
+ """
332
+ if self.is_model_cached(self.ESSENTIAL_MODEL):
333
+ return True
334
+
335
+ if not auto_download:
336
+ return False
337
+
338
+ return self.download_essential_model()
339
+
340
+
341
+ # Global instance for easy access
342
+ _model_manager = None
343
+
344
+ def get_model_manager(debug_mode: bool = False) -> SimpleModelManager:
345
+ """Get the global model manager instance."""
346
+ global _model_manager
347
+ if _model_manager is None:
348
+ _model_manager = SimpleModelManager(debug_mode=debug_mode)
349
+ return _model_manager
350
+
351
+
352
+ # Simple API functions for third-party use
353
+ def list_models(language: Optional[str] = None) -> str:
354
+ """Get available models as JSON string.
355
+
356
+ Args:
357
+ language: Optional language filter
358
+
359
+ Returns:
360
+ str: JSON string of available models
361
+ """
362
+ manager = get_model_manager()
363
+ return json.dumps(manager.list_available_models(language), indent=2)
364
+
365
+
366
+ def download_model(model_name: str, progress_callback: Optional[Callable[[str, bool], None]] = None) -> bool:
367
+ """Download a specific model.
368
+
369
+ Args:
370
+ model_name: Model name or voice ID (e.g., 'en.vits' or 'tts_models/en/ljspeech/vits')
371
+ progress_callback: Optional progress callback
372
+
373
+ Returns:
374
+ bool: True if successful
375
+ """
376
+ manager = get_model_manager()
377
+
378
+ # Handle voice ID format (e.g., 'en.vits')
379
+ if '.' in model_name and not model_name.startswith('tts_models'):
380
+ lang, voice_id = model_name.split('.', 1)
381
+ if lang in manager.AVAILABLE_MODELS and voice_id in manager.AVAILABLE_MODELS[lang]:
382
+ model_name = manager.AVAILABLE_MODELS[lang][voice_id]["model"]
383
+ else:
384
+ return False
385
+
386
+ return manager.download_model(model_name, progress_callback)
387
+
388
+
389
+ def get_status() -> str:
390
+ """Get model cache status as JSON string."""
391
+ manager = get_model_manager()
392
+ return json.dumps(manager.get_status(), indent=2)
393
+
394
+
395
+ def is_ready() -> bool:
396
+ """Check if essential model is ready for immediate use."""
397
+ manager = get_model_manager()
398
+ return manager.is_model_cached(manager.ESSENTIAL_MODEL)
@@ -466,34 +466,21 @@ class TTSEngine:
466
466
  try:
467
467
  if self.debug_mode:
468
468
  print(f" > Loading TTS model: {model_name}")
469
-
470
- # Try to initialize TTS using lazy import
469
+
470
+ # Try simple, effective initialization strategy
471
471
  try:
472
472
  TTS = _import_tts()
473
- self.tts = TTS(model_name=model_name, progress_bar=self.debug_mode)
473
+ success, final_model = self._load_with_simple_fallback(TTS, model_name, debug_mode)
474
+ if not success:
475
+ # If all fails, provide actionable guidance
476
+ self._handle_model_load_failure(debug_mode)
477
+ elif self.debug_mode and final_model != model_name:
478
+ print(f" > Loaded fallback model: {final_model}")
474
479
  except Exception as e:
475
480
  error_msg = str(e).lower()
476
481
  # Check if this is an espeak-related error
477
482
  if ("espeak" in error_msg or "phoneme" in error_msg):
478
- # Restore stdout to show user-friendly message
479
- if not debug_mode:
480
- sys.stdout = sys.__stdout__
481
-
482
- print("\n" + "="*70)
483
- print("⚠️ VITS Model Requires espeak-ng (Not Found)")
484
- print("="*70)
485
- print("\nFor BEST voice quality, install espeak-ng:")
486
- print(" • macOS: brew install espeak-ng")
487
- print(" • Linux: sudo apt-get install espeak-ng")
488
- print(" • Windows: conda install espeak-ng (or see README)")
489
- print("\nFalling back to fast_pitch (lower quality, but works)")
490
- print("="*70 + "\n")
491
-
492
- if not debug_mode:
493
- sys.stdout = null_out
494
-
495
- # Fallback to fast_pitch
496
- self.tts = TTS(model_name="tts_models/en/ljspeech/fast_pitch", progress_bar=self.debug_mode)
483
+ self._handle_espeak_fallback(debug_mode)
497
484
  else:
498
485
  # Different error, re-raise
499
486
  raise
@@ -520,6 +507,136 @@ class TTSEngine:
520
507
  # Pause/resume state
521
508
  self.pause_lock = threading.Lock() # Thread-safe pause operations
522
509
  self.is_paused_state = False # Explicit paused state tracking
510
+
511
+ def _load_with_simple_fallback(self, TTS, preferred_model: str, debug_mode: bool) -> tuple[bool, str]:
512
+ """Load TTS model with simple, effective strategy."""
513
+ from ..simple_model_manager import get_model_manager
514
+
515
+ model_manager = get_model_manager(debug_mode=debug_mode)
516
+
517
+ # Strategy 1: Try preferred model if cached
518
+ if model_manager.is_model_cached(preferred_model):
519
+ try:
520
+ if debug_mode:
521
+ print(f" > Using cached model: {preferred_model}")
522
+ self.tts = TTS(model_name=preferred_model, progress_bar=self.debug_mode)
523
+ return True, preferred_model
524
+ except Exception as e:
525
+ if debug_mode:
526
+ print(f" > Cached model failed: {e}")
527
+
528
+ # Strategy 2: Try essential model if cached
529
+ essential_model = model_manager.ESSENTIAL_MODEL
530
+ if essential_model != preferred_model and model_manager.is_model_cached(essential_model):
531
+ try:
532
+ if debug_mode:
533
+ print(f" > Using cached essential model: {essential_model}")
534
+ self.tts = TTS(model_name=essential_model, progress_bar=self.debug_mode)
535
+ return True, essential_model
536
+ except Exception as e:
537
+ if debug_mode:
538
+ print(f" > Essential model failed: {e}")
539
+
540
+ # Strategy 3: Download essential model (guaranteed to work)
541
+ try:
542
+ if debug_mode:
543
+ print(f" > Downloading essential model: {essential_model}")
544
+ success = model_manager.download_model(essential_model)
545
+ if success:
546
+ self.tts = TTS(model_name=essential_model, progress_bar=self.debug_mode)
547
+ return True, essential_model
548
+ except Exception as e:
549
+ if debug_mode:
550
+ print(f" > Essential model download failed: {e}")
551
+
552
+ # Strategy 4: Try downloading preferred model
553
+ try:
554
+ if debug_mode:
555
+ print(f" > Attempting preferred model download: {preferred_model}")
556
+ self.tts = TTS(model_name=preferred_model, progress_bar=self.debug_mode)
557
+ return True, preferred_model
558
+ except Exception as e:
559
+ if debug_mode:
560
+ print(f" > Preferred model download failed: {e}")
561
+
562
+ return False, None
563
+
564
+ def _handle_espeak_fallback(self, debug_mode: bool):
565
+ """Handle espeak-related errors with fallback to non-phoneme models."""
566
+ # Restore stdout to show user-friendly message
567
+ if not debug_mode:
568
+ sys.stdout = sys.__stdout__
569
+
570
+ print("\n" + "="*70)
571
+ print("⚠️ VITS Model Requires espeak-ng (Not Found)")
572
+ print("="*70)
573
+ print("\nFor BEST voice quality, install espeak-ng:")
574
+ print(" • macOS: brew install espeak-ng")
575
+ print(" • Linux: sudo apt-get install espeak-ng")
576
+ print(" • Windows: conda install espeak-ng (or see README)")
577
+ print("\nFalling back to fast_pitch (no espeak dependency)")
578
+ print("="*70 + "\n")
579
+
580
+ if not debug_mode:
581
+ import os
582
+ null_out = open(os.devnull, 'w')
583
+ sys.stdout = null_out
584
+
585
+ # Try non-phoneme models that don't require espeak
586
+ from TTS.api import TTS
587
+ fallback_models = [
588
+ "tts_models/en/ljspeech/fast_pitch",
589
+ "tts_models/en/ljspeech/tacotron2-DDC",
590
+ "tts_models/en/ljspeech/glow-tts"
591
+ ]
592
+
593
+ tts_loaded = False
594
+ for fallback_model in fallback_models:
595
+ try:
596
+ if debug_mode:
597
+ print(f"Trying fallback model: {fallback_model}")
598
+ self.tts = TTS(model_name=fallback_model, progress_bar=self.debug_mode)
599
+ tts_loaded = True
600
+ break
601
+ except Exception as fallback_error:
602
+ if debug_mode:
603
+ print(f"Fallback {fallback_model} failed: {fallback_error}")
604
+ continue
605
+
606
+ if not tts_loaded:
607
+ self._handle_model_load_failure(debug_mode)
608
+
609
+ def _handle_model_load_failure(self, debug_mode: bool):
610
+ """Handle complete model loading failure with actionable guidance."""
611
+ # Restore stdout to show user-friendly message
612
+ if not debug_mode:
613
+ sys.stdout = sys.__stdout__
614
+
615
+ print("\n" + "="*70)
616
+ print("❌ TTS Model Loading Failed")
617
+ print("="*70)
618
+ print("\nNo TTS models could be loaded (offline or online).")
619
+ print("\nQuick fixes:")
620
+ print(" 1. Download essential models:")
621
+ print(" abstractvoice download-models")
622
+ print(" 2. Check internet connectivity")
623
+ print(" 3. Clear corrupted cache:")
624
+ print(" rm -rf ~/.cache/tts ~/.local/share/tts")
625
+ print(" 4. Reinstall TTS:")
626
+ print(" pip install --force-reinstall coqui-tts")
627
+ print(" 5. Use text-only mode:")
628
+ print(" abstractvoice --no-tts")
629
+ print("="*70)
630
+
631
+ raise RuntimeError(
632
+ "❌ Failed to load any TTS model.\n"
633
+ "This typically means:\n"
634
+ " • No models cached locally AND no internet connection\n"
635
+ " • Corrupted model cache\n"
636
+ " • Insufficient disk space\n"
637
+ " • Network firewall blocking downloads\n\n"
638
+ "Run 'abstractvoice download-models' when you have internet access."
639
+ )
523
640
 
524
641
  def _on_playback_complete(self):
525
642
  """Callback when audio playback completes."""
@@ -823,14 +823,95 @@ class VoiceManager:
823
823
  return self.voice_recognizer.change_vad_aggressiveness(aggressiveness)
824
824
  return False
825
825
 
826
+ # ===== SIMPLE MODEL MANAGEMENT METHODS =====
827
+ # Clean, simple APIs for both CLI and third-party applications
828
+
829
+ def list_available_models(self, language: str = None) -> dict:
830
+ """Get available models with metadata.
831
+
832
+ Args:
833
+ language: Optional language filter
834
+
835
+ Returns:
836
+ dict: Model information with cache status
837
+
838
+ Example:
839
+ >>> vm = VoiceManager()
840
+ >>> models = vm.list_available_models('en')
841
+ >>> print(json.dumps(models, indent=2))
842
+ """
843
+ from .simple_model_manager import get_model_manager
844
+ manager = get_model_manager(self.debug_mode)
845
+ return manager.list_available_models(language)
846
+
847
+ def download_model(self, model_name: str, progress_callback=None) -> bool:
848
+ """Download a specific model.
849
+
850
+ Args:
851
+ model_name: Model name or voice ID (e.g., 'en.vits' or full model path)
852
+ progress_callback: Optional function(model_name, success)
853
+
854
+ Returns:
855
+ bool: True if successful
856
+
857
+ Example:
858
+ >>> vm = VoiceManager()
859
+ >>> vm.download_model('en.vits') # or 'tts_models/en/ljspeech/vits'
860
+ """
861
+ from .simple_model_manager import download_model
862
+ return download_model(model_name, progress_callback)
863
+
864
+ def is_model_ready(self) -> bool:
865
+ """Check if essential model is ready for immediate use.
866
+
867
+ Returns:
868
+ bool: True if can speak immediately without download
869
+ """
870
+ from .simple_model_manager import is_ready
871
+ return is_ready()
872
+
873
+ def ensure_ready(self, auto_download: bool = True) -> bool:
874
+ """Ensure TTS is ready for immediate use.
875
+
876
+ Args:
877
+ auto_download: Whether to download essential model if needed
878
+
879
+ Returns:
880
+ bool: True if TTS is ready
881
+
882
+ Example:
883
+ >>> vm = VoiceManager()
884
+ >>> if vm.ensure_ready():
885
+ ... vm.speak("Ready to go!")
886
+ """
887
+ if self.is_model_ready():
888
+ return True
889
+
890
+ if not auto_download:
891
+ return False
892
+
893
+ from .simple_model_manager import get_model_manager
894
+ manager = get_model_manager(self.debug_mode)
895
+ return manager.download_essential_model()
896
+
897
+ def get_cache_status(self) -> dict:
898
+ """Get model cache status.
899
+
900
+ Returns:
901
+ dict: Cache information including total models, sizes, etc.
902
+ """
903
+ from .simple_model_manager import get_model_manager
904
+ manager = get_model_manager(self.debug_mode)
905
+ return manager.get_status()
906
+
826
907
  def cleanup(self):
827
908
  """Clean up resources.
828
-
909
+
829
910
  Returns:
830
911
  True if cleanup successful
831
912
  """
832
913
  if self.voice_recognizer:
833
914
  self.voice_recognizer.stop()
834
-
915
+
835
916
  self.stop_speaking()
836
917
  return True