| 1 | import '../constants/app_constants.dart'; |
| 2 | |
| 3 | enum EngineId { system, kitten, piper } |
| 4 | |
| 5 | class EngineMeta { |
| 6 | final String name; |
| 7 | final String tagline; |
| 8 | final int sizeMb; |
| 9 | final int ramMb; |
| 10 | final int voiceCount; |
| 11 | final int accentColor; |
| 12 | |
| 13 | const EngineMeta({ |
| 14 | required this.name, |
| 15 | required this.tagline, |
| 16 | required this.sizeMb, |
| 17 | required this.ramMb, |
| 18 | required this.voiceCount, |
| 19 | required this.accentColor, |
| 20 | }); |
| 21 | |
| 22 | static const system = EngineMeta( |
| 23 | name: 'System TTS', |
| 24 | tagline: 'Built-in device engine', |
| 25 | sizeMb: 0, |
| 26 | ramMb: 0, |
| 27 | voiceCount: 0, |
| 28 | accentColor: 0xFF9E9E9E, |
| 29 | ); |
| 30 | |
| 31 | static const kitten = EngineMeta( |
| 32 | name: 'Kitten TTS', |
| 33 | tagline: 'High-speed neural TTS', |
| 34 | sizeMb: 57, |
| 35 | ramMb: 120, |
| 36 | voiceCount: 8, |
| 37 | accentColor: 0xFFFF9800, |
| 38 | ); |
| 39 | |
| 40 | static const piper = EngineMeta( |
| 41 | name: 'Sherpa ONNX VITS', |
| 42 | tagline: 'Offline Piper voices', |
| 43 | sizeMb: 50, |
| 44 | ramMb: 100, |
| 45 | voiceCount: 2, |
| 46 | accentColor: 0xFF4CAF50, |
| 47 | ); |
| 48 | |
| 49 | static EngineMeta forEngine(EngineId engine) { |
| 50 | switch (engine) { |
| 51 | case EngineId.kitten: |
| 52 | return kitten; |
| 53 | case EngineId.piper: |
| 54 | return piper; |
| 55 | default: |
| 56 | return system; |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | class Voice { |
| 62 | final String id; |
| 63 | final String name; |
| 64 | final EngineId engine; |
| 65 | final String family; |
| 66 | final String? language; |
| 67 | final String gender; |
| 68 | |
| 69 | const Voice({ |
| 70 | required this.id, |
| 71 | required this.name, |
| 72 | required this.engine, |
| 73 | required this.family, |
| 74 | this.language, |
| 75 | required this.gender, |
| 76 | }); |
| 77 | } |
| 78 | |
| 79 | List<Voice> voicesForEngine(EngineId engine) { |
| 80 | switch (engine) { |
| 81 | case EngineId.kitten: |
| 82 | return kittenVoices; |
| 83 | case EngineId.piper: |
| 84 | return piperVoices; |
| 85 | default: |
| 86 | return []; |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | Map<String, List<Voice>> voicesGroupedByFamily(EngineId engine) { |
| 91 | final result = <String, List<Voice>>{}; |
| 92 | for (final voice in voicesForEngine(engine)) { |
| 93 | result.putIfAbsent(voice.family, () => <Voice>[]).add(voice); |
| 94 | } |
| 95 | return result; |
| 96 | } |
| 97 | |
| 98 | const List<Voice> kittenVoices = [ |
| 99 | Voice( |
| 100 | id: 'Jasper', |
| 101 | name: 'Jasper', |
| 102 | engine: EngineId.kitten, |
| 103 | family: 'Kitten', |
| 104 | language: 'en', |
| 105 | gender: 'm', |
| 106 | ), |
| 107 | Voice( |
| 108 | id: 'Bella', |
| 109 | name: 'Bella', |
| 110 | engine: EngineId.kitten, |
| 111 | family: 'Kitten', |
| 112 | language: 'en', |
| 113 | gender: 'f', |
| 114 | ), |
| 115 | Voice( |
| 116 | id: 'Bruno', |
| 117 | name: 'Bruno', |
| 118 | engine: EngineId.kitten, |
| 119 | family: 'Kitten', |
| 120 | language: 'en', |
| 121 | gender: 'm', |
| 122 | ), |
| 123 | Voice( |
| 124 | id: 'Luna', |
| 125 | name: 'Luna', |
| 126 | engine: EngineId.kitten, |
| 127 | family: 'Kitten', |
| 128 | language: 'en', |
| 129 | gender: 'f', |
| 130 | ), |
| 131 | Voice( |
| 132 | id: 'Hugo', |
| 133 | name: 'Hugo', |
| 134 | engine: EngineId.kitten, |
| 135 | family: 'Kitten', |
| 136 | language: 'en', |
| 137 | gender: 'm', |
| 138 | ), |
| 139 | Voice( |
| 140 | id: 'Rosie', |
| 141 | name: 'Rosie', |
| 142 | engine: EngineId.kitten, |
| 143 | family: 'Kitten', |
| 144 | language: 'en', |
| 145 | gender: 'f', |
| 146 | ), |
| 147 | Voice( |
| 148 | id: 'Leo', |
| 149 | name: 'Leo', |
| 150 | engine: EngineId.kitten, |
| 151 | family: 'Kitten', |
| 152 | language: 'en', |
| 153 | gender: 'm', |
| 154 | ), |
| 155 | Voice( |
| 156 | id: 'Kiki', |
| 157 | name: 'Kiki', |
| 158 | engine: EngineId.kitten, |
| 159 | family: 'Kitten', |
| 160 | language: 'en', |
| 161 | gender: 'f', |
| 162 | ), |
| 163 | ]; |
| 164 | |
| 165 | const List<Voice> piperVoices = [ |
| 166 | Voice( |
| 167 | id: 'en_US-lessac-medium', |
| 168 | name: 'Lessac (US)', |
| 169 | engine: EngineId.piper, |
| 170 | family: 'Piper', |
| 171 | language: 'en', |
| 172 | gender: 'f', |
| 173 | ), |
| 174 | Voice( |
| 175 | id: 'en_US-ryan-medium', |
| 176 | name: 'Ryan (US)', |
| 177 | engine: EngineId.piper, |
| 178 | family: 'Piper', |
| 179 | language: 'en', |
| 180 | gender: 'm', |
| 181 | ), |
| 182 | ]; |
| 183 | |
| 184 | EngineId engineIdFromString(String value) { |
| 185 | switch (value) { |
| 186 | case 'kitten': |
| 187 | return EngineId.kitten; |
| 188 | case 'piper': |
| 189 | return EngineId.piper; |
| 190 | default: |
| 191 | return EngineId.system; |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | Voice? voiceFromSettings(String? voiceId, EngineId engine) { |
| 196 | if (voiceId == null || voiceId.isEmpty) return null; |
| 197 | final voices = voicesForEngine(engine); |
| 198 | try { |
| 199 | return voices.firstWhere((v) => v.id == voiceId); |
| 200 | } catch (_) { |
| 201 | return null; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | enum ServerType { |
| 206 | lmStudio, |
| 207 | openAICompatible, |
| 208 | ollama, |
| 209 | ollamaCloud, |
| 210 | openRouter, |
| 211 | onDevice, |
| 212 | requesty, |
| 213 | } |
| 214 | |
| 215 | extension ServerTypeLogoExtension on ServerType { |
| 216 | String? get logoAsset { |
| 217 | switch (this) { |
| 218 | case ServerType.lmStudio: |
| 219 | return AppConstants.lmStudioLogoAsset; |
| 220 | case ServerType.ollama: |
| 221 | case ServerType.ollamaCloud: |
| 222 | return AppConstants.ollamaLogoAsset; |
| 223 | case ServerType.openRouter: |
| 224 | return AppConstants.openRouterLogoAsset; |
| 225 | case ServerType.requesty: |
| 226 | return AppConstants.requestyLogoAsset; |
| 227 | case ServerType.openAICompatible: |
| 228 | case ServerType.onDevice: |
| 229 | return null; |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | bool get hasLogo => logoAsset != null; |
| 234 | } |
| 235 | |
| 236 | enum ConnectionStatus { connected, disconnected, checking, error } |
| 237 | |
| 238 | enum MessageRole { user, assistant, system, tool } |
| 239 | |
| 240 | enum MessageStatus { sending, streaming, complete, error, cancelled } |
| 241 | |
| 242 | enum ModelStatus { unloaded, loading, loaded, preloaded, thinking } |
| 243 | |
| 244 | enum OnDeviceEngineStatus { notLoaded, loading, loaded, error } |
| 245 | |
| 246 | enum OnDeviceModelState { |
| 247 | notDownloaded, |
| 248 | downloading, |
| 249 | downloaded, |
| 250 | loading, |
| 251 | loaded, |
| 252 | error, |
| 253 | } |