@glassly/engine 0.3.3-dev.670 → 0.3.4-beta.689

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.
Files changed (115) hide show
  1. package/build/engine.d.ts +38 -14
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/engine.js +3 -0
  4. package/build/engine.js.map +1 -1
  5. package/build/facades/ai.d.ts +32 -0
  6. package/build/facades/ai.d.ts.map +1 -0
  7. package/build/facades/ai.js +38 -0
  8. package/build/facades/ai.js.map +1 -0
  9. package/build/facades/glasses.d.ts +30 -24
  10. package/build/facades/glasses.d.ts.map +1 -1
  11. package/build/facades/glasses.js +49 -30
  12. package/build/facades/glasses.js.map +1 -1
  13. package/build/facades/glassesSettings.d.ts.map +1 -1
  14. package/build/facades/glassesSettings.js +1 -0
  15. package/build/facades/glassesSettings.js.map +1 -1
  16. package/build/facades/pairing.d.ts +6 -5
  17. package/build/facades/pairing.d.ts.map +1 -1
  18. package/build/facades/pairing.js +14 -14
  19. package/build/facades/pairing.js.map +1 -1
  20. package/build/generated/releaseMetadata.js +4 -4
  21. package/build/generated/releaseMetadata.js.map +1 -1
  22. package/build/index.d.ts +5 -2
  23. package/build/index.d.ts.map +1 -1
  24. package/build/index.js +2 -0
  25. package/build/index.js.map +1 -1
  26. package/build/services/DeviceEventRouter.d.ts.map +1 -1
  27. package/build/services/DeviceEventRouter.js +9 -1
  28. package/build/services/DeviceEventRouter.js.map +1 -1
  29. package/build/services/GestureDotsService.d.ts +14 -3
  30. package/build/services/GestureDotsService.d.ts.map +1 -1
  31. package/build/services/GestureDotsService.js +48 -13
  32. package/build/services/GestureDotsService.js.map +1 -1
  33. package/build/services/GlassesTouchInput.d.ts.map +1 -1
  34. package/build/services/GlassesTouchInput.js +11 -9
  35. package/build/services/GlassesTouchInput.js.map +1 -1
  36. package/build/services/LlmRouting.d.ts +35 -0
  37. package/build/services/LlmRouting.d.ts.map +1 -0
  38. package/build/services/LlmRouting.js +25 -0
  39. package/build/services/LlmRouting.js.map +1 -0
  40. package/build/services/LocalLlmModelManager.d.ts +79 -0
  41. package/build/services/LocalLlmModelManager.d.ts.map +1 -0
  42. package/build/services/LocalLlmModelManager.js +278 -0
  43. package/build/services/LocalLlmModelManager.js.map +1 -0
  44. package/build/services/LocalLlmPrompt.d.ts +90 -0
  45. package/build/services/LocalLlmPrompt.d.ts.map +1 -0
  46. package/build/services/LocalLlmPrompt.js +189 -0
  47. package/build/services/LocalLlmPrompt.js.map +1 -0
  48. package/build/services/LocalLlmProvider.d.ts +35 -0
  49. package/build/services/LocalLlmProvider.d.ts.map +1 -0
  50. package/build/services/LocalLlmProvider.js +218 -0
  51. package/build/services/LocalLlmProvider.js.map +1 -0
  52. package/build/services/LocalMiniappRuntime.d.ts +9 -1
  53. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  54. package/build/services/LocalMiniappRuntime.js +91 -17
  55. package/build/services/LocalMiniappRuntime.js.map +1 -1
  56. package/build/services/STTModelManager.d.ts +25 -2
  57. package/build/services/STTModelManager.d.ts.map +1 -1
  58. package/build/services/STTModelManager.js +137 -6
  59. package/build/services/STTModelManager.js.map +1 -1
  60. package/build/services/controllers.d.ts +21 -0
  61. package/build/services/controllers.d.ts.map +1 -0
  62. package/build/services/controllers.js +62 -0
  63. package/build/services/controllers.js.map +1 -0
  64. package/build/services/effectiveCapabilities.d.ts.map +1 -1
  65. package/build/services/effectiveCapabilities.js +18 -24
  66. package/build/services/effectiveCapabilities.js.map +1 -1
  67. package/build/services/neuralBand/NeuralBandInput.d.ts +17 -6
  68. package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
  69. package/build/services/neuralBand/NeuralBandInput.js +40 -23
  70. package/build/services/neuralBand/NeuralBandInput.js.map +1 -1
  71. package/build/services/neuralBand/NeuralBandSettingsSync.d.ts.map +1 -1
  72. package/build/services/neuralBand/NeuralBandSettingsSync.js +23 -26
  73. package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
  74. package/build/services/settingsBridge.d.ts +26 -18
  75. package/build/services/settingsBridge.d.ts.map +1 -1
  76. package/build/services/settingsBridge.js +36 -15
  77. package/build/services/settingsBridge.js.map +1 -1
  78. package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
  79. package/build/stores/bluetoothSettingKeys.js +3 -1
  80. package/build/stores/bluetoothSettingKeys.js.map +1 -1
  81. package/build/stores/core.d.ts.map +1 -1
  82. package/build/stores/core.js +0 -1
  83. package/build/stores/core.js.map +1 -1
  84. package/build/stores/glasses.d.ts.map +1 -1
  85. package/build/stores/glasses.js +2 -6
  86. package/build/stores/glasses.js.map +1 -1
  87. package/build/stores/settings.d.ts.map +1 -1
  88. package/build/stores/settings.js +29 -3
  89. package/build/stores/settings.js.map +1 -1
  90. package/package.json +12 -8
  91. package/src/engine.ts +3 -0
  92. package/src/facades/ai.ts +43 -0
  93. package/src/facades/glasses.ts +60 -31
  94. package/src/facades/glassesSettings.ts +1 -0
  95. package/src/facades/pairing.ts +18 -14
  96. package/src/generated/releaseMetadata.ts +4 -4
  97. package/src/index.ts +16 -2
  98. package/src/services/DeviceEventRouter.ts +8 -1
  99. package/src/services/GestureDotsService.ts +52 -15
  100. package/src/services/GlassesTouchInput.ts +11 -9
  101. package/src/services/LlmRouting.ts +49 -0
  102. package/src/services/LocalLlmModelManager.ts +327 -0
  103. package/src/services/LocalLlmPrompt.ts +246 -0
  104. package/src/services/LocalLlmProvider.ts +290 -0
  105. package/src/services/LocalMiniappRuntime.ts +137 -21
  106. package/src/services/STTModelManager.ts +140 -8
  107. package/src/services/controllers.ts +77 -0
  108. package/src/services/effectiveCapabilities.ts +16 -32
  109. package/src/services/neuralBand/NeuralBandInput.ts +50 -26
  110. package/src/services/neuralBand/NeuralBandSettingsSync.ts +28 -26
  111. package/src/services/settingsBridge.ts +60 -22
  112. package/src/stores/bluetoothSettingKeys.ts +3 -1
  113. package/src/stores/core.ts +0 -1
  114. package/src/stores/glasses.ts +2 -6
  115. package/src/stores/settings.ts +29 -3
@@ -1,3 +1,4 @@
1
+ export type SttModelType = "transducer" | "ctc" | "nemotron" | "apple";
1
2
  export interface LanguageInfo {
2
3
  code: string;
3
4
  displayName: string;
@@ -5,7 +6,7 @@ export interface LanguageInfo {
5
6
  language: string;
6
7
  downloaded: boolean;
7
8
  path?: string;
8
- type: "transducer" | "ctc" | "nemotron";
9
+ type: SttModelType;
9
10
  }
10
11
  export interface DownloadProgress {
11
12
  jobId: number;
@@ -23,16 +24,29 @@ export interface LanguageConfig {
23
24
  fileName: string;
24
25
  downloadUrl?: string;
25
26
  size: number;
26
- type: "transducer" | "ctc" | "nemotron";
27
+ type: SttModelType;
27
28
  requiredFiles: string[];
28
29
  languageCode: string;
29
30
  }
31
+ /**
32
+ * Apple SpeechTranscriber entries are keyed `apple:<bcp47>`; the same string is
33
+ * what native stores as the model path, since the OS owns the assets and there
34
+ * is no directory to point at.
35
+ */
36
+ export declare const APPLE_STT_PREFIX = "apple:";
37
+ export declare function isAppleSttCode(code: string): boolean;
38
+ /** SpeechTranscriber ships with iOS 26. */
39
+ export declare function appleSttSupported(): boolean;
30
40
  declare class STTModelManager {
31
41
  private static instance;
32
42
  private downloadJobId?;
33
43
  private currentLanguage;
34
44
  private modelReadyListeners;
35
45
  private modelBaseUrl;
46
+ /** Apple locales reported by the OS, refreshed on each list load. */
47
+ private appleLanguages;
48
+ private appleInstalled;
49
+ private appleRefresh;
36
50
  private languages;
37
51
  private constructor();
38
52
  static getInstance(): STTModelManager;
@@ -40,7 +54,14 @@ declare class STTModelManager {
40
54
  getCurrentLanguage(): string;
41
55
  setCurrentLanguage(code: string): void;
42
56
  getBcp47ForLanguage(code?: string): string;
57
+ /** Sherpa models first, then whatever Apple locales the last refresh reported. */
43
58
  getAvailableLanguages(): LanguageConfig[];
59
+ private config;
60
+ /**
61
+ * Re-read the Apple locale list and install states from the OS. A no-op below
62
+ * iOS 26 and on Android, where the Apple section simply never appears.
63
+ */
64
+ refreshAppleLanguages(): Promise<void>;
44
65
  getModelDirectory(): string;
45
66
  getModelPath(code?: string): string;
46
67
  /** Last path segment is the language code (the on-disk folder name). */
@@ -48,6 +69,8 @@ declare class STTModelManager {
48
69
  isModelAvailable(code?: string): Promise<boolean>;
49
70
  getLanguageInfo(code?: string): Promise<LanguageInfo>;
50
71
  getAllLanguageInfo(): Promise<LanguageInfo[]>;
72
+ /** Downloads the Apple locale's assets through the OS, surfacing its progress as a download. */
73
+ private installAppleModel;
51
74
  downloadModel(code?: string, onProgress?: (progress: DownloadProgress) => void, onExtractionProgress?: (progress: ExtractionProgress) => void): Promise<void>;
52
75
  cancelDownload(): Promise<void>;
53
76
  deleteModel(code?: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"STTModelManager.d.ts","sourceRoot":"","sources":["../../src/services/STTModelManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"STTModelManager.d.ts","sourceRoot":"","sources":["../../src/services/STTModelManager.ts"],"names":[],"mappings":"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"}
@@ -2,12 +2,29 @@ import BluetoothSdk from "@glassly/bluetooth-sdk";
2
2
  import { Platform } from "react-native";
3
3
  import * as RNFS from "@dr.pogodin/react-native-fs";
4
4
  const DEFAULT_LANGUAGE = "en";
5
+ /**
6
+ * Apple SpeechTranscriber entries are keyed `apple:<bcp47>`; the same string is
7
+ * what native stores as the model path, since the OS owns the assets and there
8
+ * is no directory to point at.
9
+ */
10
+ export const APPLE_STT_PREFIX = "apple:";
11
+ export function isAppleSttCode(code) {
12
+ return code.startsWith(APPLE_STT_PREFIX);
13
+ }
14
+ /** SpeechTranscriber ships with iOS 26. */
15
+ export function appleSttSupported() {
16
+ return Platform.OS === "ios" && parseInt(String(Platform.Version), 10) >= 26;
17
+ }
5
18
  class STTModelManager {
6
19
  static instance;
7
20
  downloadJobId;
8
21
  currentLanguage = DEFAULT_LANGUAGE;
9
22
  modelReadyListeners = new Set();
10
23
  modelBaseUrl = "https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/";
24
+ /** Apple locales reported by the OS, refreshed on each list load. */
25
+ appleLanguages = {};
26
+ appleInstalled = new Set();
27
+ appleRefresh = null;
11
28
  languages = {
12
29
  "en": {
13
30
  code: "en",
@@ -149,7 +166,10 @@ class STTModelManager {
149
166
  try {
150
167
  const path = await BluetoothSdk.getSttModelPath();
151
168
  const code = path && path.length > 0 ? this.getLanguageFromPath(path) : "";
152
- if (code && this.languages[code]) {
169
+ if (code && isAppleSttCode(code)) {
170
+ await this.refreshAppleLanguages();
171
+ }
172
+ if (code && this.config(code)) {
153
173
  this.currentLanguage = code;
154
174
  return code;
155
175
  }
@@ -164,16 +184,60 @@ class STTModelManager {
164
184
  return this.currentLanguage;
165
185
  }
166
186
  setCurrentLanguage(code) {
167
- if (this.languages[code]) {
187
+ if (this.config(code)) {
168
188
  this.currentLanguage = code;
169
189
  }
170
190
  }
171
191
  getBcp47ForLanguage(code) {
172
192
  const id = code || this.currentLanguage;
173
- return this.languages[id]?.languageCode ?? "en-US";
193
+ return this.config(id)?.languageCode ?? "en-US";
174
194
  }
195
+ /** Sherpa models first, then whatever Apple locales the last refresh reported. */
175
196
  getAvailableLanguages() {
176
- return Object.values(this.languages);
197
+ return [...Object.values(this.languages), ...Object.values(this.appleLanguages)];
198
+ }
199
+ config(code) {
200
+ return this.languages[code] ?? this.appleLanguages[code];
201
+ }
202
+ /**
203
+ * Re-read the Apple locale list and install states from the OS. A no-op below
204
+ * iOS 26 and on Android, where the Apple section simply never appears.
205
+ */
206
+ async refreshAppleLanguages() {
207
+ if (!appleSttSupported())
208
+ return;
209
+ if (this.appleRefresh)
210
+ return this.appleRefresh;
211
+ this.appleRefresh = (async () => {
212
+ try {
213
+ const rows = await BluetoothSdk.getAppleSpeechLocales();
214
+ const next = {};
215
+ const installed = new Set();
216
+ for (const row of rows) {
217
+ const code = APPLE_STT_PREFIX + row.code;
218
+ next[code] = {
219
+ code,
220
+ displayName: row.name,
221
+ fileName: "",
222
+ size: 0,
223
+ type: "apple",
224
+ requiredFiles: [],
225
+ languageCode: row.code,
226
+ };
227
+ if (row.status === "installed")
228
+ installed.add(code);
229
+ }
230
+ this.appleLanguages = next;
231
+ this.appleInstalled = installed;
232
+ }
233
+ catch (error) {
234
+ console.warn("STTModelManager: Apple locale refresh failed:", error);
235
+ }
236
+ finally {
237
+ this.appleRefresh = null;
238
+ }
239
+ })();
240
+ return this.appleRefresh;
177
241
  }
178
242
  getModelDirectory() {
179
243
  const baseDir = Platform.OS === "ios" ? RNFS.DocumentDirectoryPath : RNFS.DocumentDirectoryPath;
@@ -181,6 +245,8 @@ class STTModelManager {
181
245
  }
182
246
  getModelPath(code) {
183
247
  const id = code || this.currentLanguage;
248
+ if (isAppleSttCode(id))
249
+ return id;
184
250
  return `${this.getModelDirectory()}/${id}`;
185
251
  }
186
252
  /** Last path segment is the language code (the on-disk folder name). */
@@ -190,6 +256,17 @@ class STTModelManager {
190
256
  async isModelAvailable(code) {
191
257
  try {
192
258
  const id = code || this.currentLanguage;
259
+ if (isAppleSttCode(id)) {
260
+ if (!appleSttSupported())
261
+ return false;
262
+ const status = await BluetoothSdk.getAppleSpeechLocaleStatus(id.slice(APPLE_STT_PREFIX.length));
263
+ const installed = status === "installed";
264
+ if (installed)
265
+ this.appleInstalled.add(id);
266
+ else
267
+ this.appleInstalled.delete(id);
268
+ return installed;
269
+ }
193
270
  const language = this.languages[id];
194
271
  if (!language)
195
272
  return false;
@@ -211,7 +288,7 @@ class STTModelManager {
211
288
  }
212
289
  async getLanguageInfo(code) {
213
290
  const id = code || this.currentLanguage;
214
- const language = this.languages[id];
291
+ const language = this.config(id);
215
292
  if (!language) {
216
293
  throw new Error(`Language ${id} not found`);
217
294
  }
@@ -232,10 +309,53 @@ class STTModelManager {
232
309
  for (const code of Object.keys(this.languages)) {
233
310
  infos.push(await this.getLanguageInfo(code));
234
311
  }
312
+ // One OS round-trip covers every Apple row's install state.
313
+ await this.refreshAppleLanguages();
314
+ for (const language of Object.values(this.appleLanguages)) {
315
+ const downloaded = this.appleInstalled.has(language.code);
316
+ infos.push({
317
+ code: language.code,
318
+ displayName: language.displayName,
319
+ size: 0,
320
+ language: language.languageCode,
321
+ downloaded,
322
+ path: downloaded ? language.code : undefined,
323
+ type: "apple",
324
+ });
325
+ }
235
326
  return infos;
236
327
  }
328
+ /** Downloads the Apple locale's assets through the OS, surfacing its progress as a download. */
329
+ async installAppleModel(code, onProgress, onExtractionProgress) {
330
+ if (!appleSttSupported()) {
331
+ throw new Error("Apple on-device speech requires iOS 26");
332
+ }
333
+ const locale = code.slice(APPLE_STT_PREFIX.length);
334
+ const subscription = BluetoothSdk.addListener("apple_speech_install_progress", (event) => {
335
+ if (event.locale !== locale)
336
+ return;
337
+ onProgress?.({ jobId: 0, bytesWritten: 0, contentLength: 0, percentage: event.percentage });
338
+ });
339
+ try {
340
+ const status = await BluetoothSdk.installAppleSpeechLocale(locale);
341
+ if (status !== "installed") {
342
+ throw new Error(`Apple speech assets for ${locale} are ${status}`);
343
+ }
344
+ }
345
+ finally {
346
+ subscription.remove();
347
+ }
348
+ onExtractionProgress?.({ percentage: 100 });
349
+ this.appleInstalled.add(code);
350
+ if (code === this.currentLanguage) {
351
+ await this.setNativeModelPath(code, locale);
352
+ }
353
+ }
237
354
  async downloadModel(code, onProgress, onExtractionProgress) {
238
355
  const id = code || this.currentLanguage;
356
+ if (isAppleSttCode(id)) {
357
+ return this.installAppleModel(id, onProgress, onExtractionProgress);
358
+ }
239
359
  const language = this.languages[id];
240
360
  if (!language) {
241
361
  throw new Error(`Language ${id} not found`);
@@ -321,13 +441,24 @@ class STTModelManager {
321
441
  }
322
442
  async deleteModel(code) {
323
443
  const id = code || this.currentLanguage;
444
+ if (isAppleSttCode(id)) {
445
+ // The OS owns the assets; only the app's reservation can be dropped.
446
+ this.appleInstalled.delete(id);
447
+ if (appleSttSupported()) {
448
+ await BluetoothSdk.releaseAppleSpeechLocale(id.slice(APPLE_STT_PREFIX.length));
449
+ }
450
+ return;
451
+ }
324
452
  const modelPath = this.getModelPath(id);
325
453
  if (await RNFS.exists(modelPath)) {
326
454
  await RNFS.unlink(modelPath);
327
455
  }
328
456
  }
329
457
  async activateLanguage(code) {
330
- const language = this.languages[code];
458
+ if (isAppleSttCode(code) && !this.appleLanguages[code]) {
459
+ await this.refreshAppleLanguages();
460
+ }
461
+ const language = this.config(code);
331
462
  if (!language) {
332
463
  throw new Error(`Language ${code} not found`);
333
464
  }
@@ -1 +1 @@
1
- {"version":3,"file":"STTModelManager.js","sourceRoot":"","sources":["../../src/services/STTModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk\"\nimport {Platform} from \"react-native\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\nexport interface LanguageInfo {\n code: string\n displayName: string\n size: number\n language: string\n downloaded: boolean\n path?: string\n type: \"transducer\" | \"ctc\" | \"nemotron\"\n}\n\nexport interface DownloadProgress {\n jobId: number\n bytesWritten: number\n contentLength: number\n percentage: number\n}\n\nexport interface ExtractionProgress {\n percentage: number\n currentFile?: string\n}\n\nexport interface LanguageConfig {\n code: string\n displayName: string\n fileName: string\n downloadUrl?: string\n size: number\n type: \"transducer\" | \"ctc\" | \"nemotron\"\n requiredFiles: string[]\n languageCode: string\n}\n\nconst DEFAULT_LANGUAGE = \"en\"\n\nclass STTModelManager {\n private static instance: STTModelManager\n private downloadJobId?: number\n private currentLanguage = DEFAULT_LANGUAGE\n private modelReadyListeners = new Set<() => void>()\n private modelBaseUrl = \"https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/\"\n\n private languages: Record<string, LanguageConfig> = {\n \"en\": {\n code: \"en\",\n displayName: \"English\",\n fileName: \"sherpa-onnx-streaming-zipformer-en-2023-06-21-mobile\",\n size: 349 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n \"fr\": {\n code: \"fr\",\n displayName: \"Français\",\n fileName: \"sherpa-onnx-streaming-zipformer-fr-kroko-2025-08-06\",\n size: 57 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"fr-FR\",\n },\n \"de\": {\n code: \"de\",\n displayName: \"Deutsch\",\n fileName: \"sherpa-onnx-streaming-zipformer-de-kroko-2025-08-06\",\n size: 58 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"de-DE\",\n },\n \"es\": {\n code: \"es\",\n displayName: \"Español\",\n fileName: \"sherpa-onnx-streaming-zipformer-es-kroko-2025-08-06\",\n size: 124 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"es-ES\",\n },\n \"zh\": {\n code: \"zh\",\n displayName: \"中文\",\n fileName: \"sherpa-onnx-streaming-zipformer-zh-2025-06-30\",\n size: 150 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"zh-CN\",\n },\n \"ko\": {\n code: \"ko\",\n displayName: \"한국어\",\n fileName: \"sherpa-onnx-streaming-zipformer-korean-2024-06-16\",\n size: 200 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"ko-KR\",\n },\n // Alternative English streaming models, kept alongside \"en\" so the three can\n // be compared on-device. Both are newer than the 2023 Zipformer used for\n // \"en\" and are a fraction of its 349 MB.\n //\n // Kroko: same family as the fr/de/es entries above, and the smallest English\n // option here. Ships bare encoder/decoder/joiner .onnx (no int8 variants).\n \"en-kroko\": {\n code: \"en-kroko\",\n displayName: \"English (Kroko, 55 MB)\",\n fileName: \"sherpa-onnx-streaming-zipformer-en-kroko-2025-08-06\",\n size: 55 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n // NeMo streaming fast-conformer, 480 ms chunk. Upstream publishes 80/480/1040 ms;\n // 480 ms is the middle latency/accuracy point. Unlike Kroko this ships only\n // int8 weights, so requiredFiles uses the .int8.onnx names — the archive has\n // no bare .onnx files at all.\n //\n // sherpa-onnx loads it through the plain transducer config (type \"transducer\",\n // not \"nemotron\"): the nemotron branch in SherpaOnnxTranscriber exists for the\n // Nemotron models below, which need a different modelType string to decode\n // correctly. This one is a stock NeMo transducer.\n \"en-nemo-fast-conformer\": {\n code: \"en-nemo-fast-conformer\",\n displayName: \"English (NeMo Fast-Conformer 480ms, 101 MB)\",\n fileName: \"sherpa-onnx-nemo-streaming-fast-conformer-transducer-en-480ms-int8\",\n size: 101 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\n \"encoder.int8.onnx\",\n \"decoder.int8.onnx\",\n \"joiner.int8.onnx\",\n \"tokens.txt\",\n ],\n languageCode: \"en-US\",\n },\n // NVIDIA Nemotron streaming ASR. These are model choices rather than plain\n // languages, so they get their own codes and sort after the per-language\n // Zipformers above.\n //\n // Both ship int8 encoder/decoder/joiner — the same file shape as a Zipformer\n // transducer — and are loaded through the identical transducer config;\n // sherpa-onnx dispatches to its NeMo implementation from the ONNX metadata.\n // Requires sherpa-onnx >= 1.13.4 (see the pins in the Android build.gradle\n // and scripts/setup-sherpa-onnx-optional.sh).\n //\n // The 560ms chunk variant is chosen for both: upstream publishes\n // 80/160/320/560/1120ms, trading latency against accuracy, and 560ms is the\n // middle ground that stays usable for live captions. These are ~450 MB\n // downloads — substantially larger than every other model here.\n \"nemotron-en\": {\n code: \"nemotron-en\",\n displayName: \"Nemotron English (high accuracy)\",\n fileName: \"sherpa-onnx-nemotron-speech-streaming-en-0.6b-560ms-int8-2026-04-25\",\n size: 442 * 1024 * 1024,\n type: \"nemotron\",\n requiredFiles: [\"encoder.int8.onnx\", \"decoder.int8.onnx\", \"joiner.int8.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n // Nemotron-3.5 covers 40 language-locales with automatic language detection,\n // so there is no single BCP-47 tag for it. We report en-US as the nominal\n // tag because downstream consumers require one; the model itself is not\n // restricted to English.\n \"nemotron-3.5\": {\n code: \"nemotron-3.5\",\n displayName: \"Nemotron 3.5 Multilingual (40 languages)\",\n fileName: \"sherpa-onnx-nemotron-3.5-asr-streaming-0.6b-560ms-int8-2026-06-11\",\n size: 453 * 1024 * 1024,\n type: \"nemotron\",\n requiredFiles: [\"encoder.int8.onnx\", \"decoder.int8.onnx\", \"joiner.int8.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n }\n\n private constructor() {}\n\n static getInstance(): STTModelManager {\n if (!STTModelManager.instance) {\n STTModelManager.instance = new STTModelManager()\n }\n return STTModelManager.instance\n }\n\n async getCurrentLanguageFromPreferences(): Promise<string> {\n try {\n const path = await BluetoothSdk.getSttModelPath()\n const code = path && path.length > 0 ? this.getLanguageFromPath(path) : \"\"\n if (code && this.languages[code]) {\n this.currentLanguage = code\n return code\n }\n return \"\"\n } catch (error) {\n console.error(\"Error getting current STT language from preferences:\", error)\n return \"\"\n }\n }\n\n getCurrentLanguage(): string {\n return this.currentLanguage\n }\n\n setCurrentLanguage(code: string): void {\n if (this.languages[code]) {\n this.currentLanguage = code\n }\n }\n\n getBcp47ForLanguage(code?: string): string {\n const id = code || this.currentLanguage\n return this.languages[id]?.languageCode ?? \"en-US\"\n }\n\n getAvailableLanguages(): LanguageConfig[] {\n return Object.values(this.languages)\n }\n\n getModelDirectory(): string {\n const baseDir = Platform.OS === \"ios\" ? RNFS.DocumentDirectoryPath : RNFS.DocumentDirectoryPath\n return `${baseDir}/stt_models`\n }\n\n getModelPath(code?: string): string {\n const id = code || this.currentLanguage\n return `${this.getModelDirectory()}/${id}`\n }\n\n /** Last path segment is the language code (the on-disk folder name). */\n getLanguageFromPath(path: string): string {\n return path.split(\"/\").pop() || \"\"\n }\n\n async isModelAvailable(code?: string): Promise<boolean> {\n try {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) return false\n\n const modelPath = this.getModelPath(id)\n for (const file of language.requiredFiles) {\n const filePath = `${modelPath}/${file}`\n const exists = await RNFS.exists(filePath)\n if (!exists) {\n console.log(`Missing required STT file: ${file} at ${filePath}`)\n return false\n }\n }\n\n return await BluetoothSdk.validateSttModel(modelPath)\n } catch (error) {\n console.error(\"Error checking STT model availability:\", error)\n return false\n }\n }\n\n async getLanguageInfo(code?: string): Promise<LanguageInfo> {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`Language ${id} not found`)\n }\n\n const downloaded = await this.isModelAvailable(id)\n const path = downloaded ? this.getModelPath(id) : undefined\n\n return {\n code: id,\n displayName: language.displayName,\n size: language.size,\n language: language.languageCode,\n downloaded,\n path,\n type: language.type,\n }\n }\n\n async getAllLanguageInfo(): Promise<LanguageInfo[]> {\n const infos: LanguageInfo[] = []\n for (const code of Object.keys(this.languages)) {\n infos.push(await this.getLanguageInfo(code))\n }\n return infos\n }\n\n async downloadModel(\n code?: string,\n onProgress?: (progress: DownloadProgress) => void,\n onExtractionProgress?: (progress: ExtractionProgress) => void,\n ): Promise<void> {\n const id = code || this.currentLanguage\n const language = this.languages[id]\n if (!language) {\n throw new Error(`Language ${id} not found`)\n }\n\n const modelUrl = language.downloadUrl ?? `${this.modelBaseUrl}${language.fileName}.tar.bz2`\n const tempPath = `${RNFS.TemporaryDirectoryPath}/${language.fileName}.tar.bz2`\n const modelDir = this.getModelDirectory()\n const finalPath = this.getModelPath(id)\n\n try {\n await RNFS.mkdir(modelDir, {NSURLIsExcludedFromBackupKey: true})\n\n const downloadOptions = {\n fromUrl: modelUrl,\n toFile: tempPath,\n progress: (res: RNFS.DownloadProgressCallbackResultT) => {\n const percentage = Math.round((res.bytesWritten / res.contentLength) * 100)\n onProgress?.({\n jobId: res.jobId,\n bytesWritten: res.bytesWritten,\n contentLength: res.contentLength,\n percentage,\n })\n },\n progressDivider: 10,\n begin: (res: RNFS.DownloadBeginCallbackResultT) => {\n console.log(\"Download started:\", res)\n },\n connectionTimeout: 30000,\n readTimeout: 30000,\n }\n\n const result = await RNFS.downloadFile(downloadOptions)\n this.downloadJobId = result.jobId\n\n const downloadResult = await result.promise\n\n if (downloadResult.statusCode !== 200) {\n throw new Error(`Download failed with status code: ${downloadResult.statusCode}`)\n }\n\n onExtractionProgress?.({percentage: 0})\n\n const subscription = BluetoothSdk.addListener(\"extraction_progress\", (event) => {\n onExtractionProgress?.({percentage: event.percentage})\n })\n\n try {\n const extractionResult = await BluetoothSdk.extractTarBz2(tempPath, finalPath)\n if (!extractionResult) {\n throw new Error(\"Native extraction returned failure status\")\n }\n } catch (extractError) {\n console.error(\"Native extraction failed:\", extractError)\n throw extractError\n } finally {\n subscription.remove()\n }\n\n onExtractionProgress?.({percentage: 100})\n\n await RNFS.unlink(tempPath)\n\n if (id === this.currentLanguage) {\n await this.setNativeModelPath(finalPath, language.languageCode)\n }\n } catch (error) {\n // Best-effort cleanup; never let it mask the original error.\n try {\n if (await RNFS.exists(tempPath)) await RNFS.unlink(tempPath)\n } catch (cleanupError) {\n console.warn(\"STTModelManager: temp cleanup failed:\", cleanupError)\n }\n try {\n if (await RNFS.exists(finalPath)) await RNFS.unlink(finalPath)\n } catch (cleanupError) {\n console.warn(\"STTModelManager: final cleanup failed:\", cleanupError)\n }\n throw error\n }\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId !== undefined) {\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n }\n\n async deleteModel(code?: string): Promise<void> {\n const id = code || this.currentLanguage\n const modelPath = this.getModelPath(id)\n if (await RNFS.exists(modelPath)) {\n await RNFS.unlink(modelPath)\n }\n }\n\n async activateLanguage(code: string): Promise<void> {\n const language = this.languages[code]\n if (!language) {\n throw new Error(`Language ${code} not found`)\n }\n\n const isAvailable = await this.isModelAvailable(code)\n if (!isAvailable) {\n throw new Error(`Language ${code} model is not downloaded`)\n }\n\n this.currentLanguage = code\n const modelPath = this.getModelPath(code)\n await this.setNativeModelPath(modelPath, language.languageCode)\n }\n\n private async setNativeModelPath(path: string, languageCode: string): Promise<void> {\n await BluetoothSdk.setSttModelDetails(path, languageCode)\n for (const listener of this.modelReadyListeners) {\n try {\n listener()\n } catch (error) {\n console.warn(\"STTModelManager: model-ready listener failed:\", error)\n }\n }\n }\n\n /** Fired after a downloaded/selected model's path has reached native. */\n onModelReady(listener: () => void): () => void {\n this.modelReadyListeners.add(listener)\n return () => this.modelReadyListeners.delete(listener)\n }\n\n async getStorageInfo(): Promise<{free: number; total: number}> {\n const fsInfo = await RNFS.getFSInfo()\n return {\n free: fsInfo.freeSpace,\n total: fsInfo.totalSpace,\n }\n }\n\n formatBytes(bytes: number): string {\n return STTModelManager.formatBytes(bytes)\n }\n\n static formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\"\n const k = 1024\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"]\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i]\n }\n}\n\nconst instance = STTModelManager.getInstance()\nexport {STTModelManager}\nexport default instance\n"]}
1
+ {"version":3,"file":"STTModelManager.js","sourceRoot":"","sources":["../../src/services/STTModelManager.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk\"\nimport {Platform} from \"react-native\"\nimport * as RNFS from \"@dr.pogodin/react-native-fs\"\n\nexport type SttModelType = \"transducer\" | \"ctc\" | \"nemotron\" | \"apple\"\n\nexport interface LanguageInfo {\n code: string\n displayName: string\n size: number\n language: string\n downloaded: boolean\n path?: string\n type: SttModelType\n}\n\nexport interface DownloadProgress {\n jobId: number\n bytesWritten: number\n contentLength: number\n percentage: number\n}\n\nexport interface ExtractionProgress {\n percentage: number\n currentFile?: string\n}\n\nexport interface LanguageConfig {\n code: string\n displayName: string\n fileName: string\n downloadUrl?: string\n size: number\n type: SttModelType\n requiredFiles: string[]\n languageCode: string\n}\n\nconst DEFAULT_LANGUAGE = \"en\"\n\n/**\n * Apple SpeechTranscriber entries are keyed `apple:<bcp47>`; the same string is\n * what native stores as the model path, since the OS owns the assets and there\n * is no directory to point at.\n */\nexport const APPLE_STT_PREFIX = \"apple:\"\n\nexport function isAppleSttCode(code: string): boolean {\n return code.startsWith(APPLE_STT_PREFIX)\n}\n\n/** SpeechTranscriber ships with iOS 26. */\nexport function appleSttSupported(): boolean {\n return Platform.OS === \"ios\" && parseInt(String(Platform.Version), 10) >= 26\n}\n\nclass STTModelManager {\n private static instance: STTModelManager\n private downloadJobId?: number\n private currentLanguage = DEFAULT_LANGUAGE\n private modelReadyListeners = new Set<() => void>()\n private modelBaseUrl = \"https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/\"\n /** Apple locales reported by the OS, refreshed on each list load. */\n private appleLanguages: Record<string, LanguageConfig> = {}\n private appleInstalled = new Set<string>()\n private appleRefresh: Promise<void> | null = null\n\n private languages: Record<string, LanguageConfig> = {\n \"en\": {\n code: \"en\",\n displayName: \"English\",\n fileName: \"sherpa-onnx-streaming-zipformer-en-2023-06-21-mobile\",\n size: 349 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n \"fr\": {\n code: \"fr\",\n displayName: \"Français\",\n fileName: \"sherpa-onnx-streaming-zipformer-fr-kroko-2025-08-06\",\n size: 57 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"fr-FR\",\n },\n \"de\": {\n code: \"de\",\n displayName: \"Deutsch\",\n fileName: \"sherpa-onnx-streaming-zipformer-de-kroko-2025-08-06\",\n size: 58 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"de-DE\",\n },\n \"es\": {\n code: \"es\",\n displayName: \"Español\",\n fileName: \"sherpa-onnx-streaming-zipformer-es-kroko-2025-08-06\",\n size: 124 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"es-ES\",\n },\n \"zh\": {\n code: \"zh\",\n displayName: \"中文\",\n fileName: \"sherpa-onnx-streaming-zipformer-zh-2025-06-30\",\n size: 150 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"zh-CN\",\n },\n \"ko\": {\n code: \"ko\",\n displayName: \"한국어\",\n fileName: \"sherpa-onnx-streaming-zipformer-korean-2024-06-16\",\n size: 200 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"ko-KR\",\n },\n // Alternative English streaming models, kept alongside \"en\" so the three can\n // be compared on-device. Both are newer than the 2023 Zipformer used for\n // \"en\" and are a fraction of its 349 MB.\n //\n // Kroko: same family as the fr/de/es entries above, and the smallest English\n // option here. Ships bare encoder/decoder/joiner .onnx (no int8 variants).\n \"en-kroko\": {\n code: \"en-kroko\",\n displayName: \"English (Kroko, 55 MB)\",\n fileName: \"sherpa-onnx-streaming-zipformer-en-kroko-2025-08-06\",\n size: 55 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\"encoder.onnx\", \"decoder.onnx\", \"joiner.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n // NeMo streaming fast-conformer, 480 ms chunk. Upstream publishes 80/480/1040 ms;\n // 480 ms is the middle latency/accuracy point. Unlike Kroko this ships only\n // int8 weights, so requiredFiles uses the .int8.onnx names — the archive has\n // no bare .onnx files at all.\n //\n // sherpa-onnx loads it through the plain transducer config (type \"transducer\",\n // not \"nemotron\"): the nemotron branch in SherpaOnnxTranscriber exists for the\n // Nemotron models below, which need a different modelType string to decode\n // correctly. This one is a stock NeMo transducer.\n \"en-nemo-fast-conformer\": {\n code: \"en-nemo-fast-conformer\",\n displayName: \"English (NeMo Fast-Conformer 480ms, 101 MB)\",\n fileName: \"sherpa-onnx-nemo-streaming-fast-conformer-transducer-en-480ms-int8\",\n size: 101 * 1024 * 1024,\n type: \"transducer\",\n requiredFiles: [\n \"encoder.int8.onnx\",\n \"decoder.int8.onnx\",\n \"joiner.int8.onnx\",\n \"tokens.txt\",\n ],\n languageCode: \"en-US\",\n },\n // NVIDIA Nemotron streaming ASR. These are model choices rather than plain\n // languages, so they get their own codes and sort after the per-language\n // Zipformers above.\n //\n // Both ship int8 encoder/decoder/joiner — the same file shape as a Zipformer\n // transducer — and are loaded through the identical transducer config;\n // sherpa-onnx dispatches to its NeMo implementation from the ONNX metadata.\n // Requires sherpa-onnx >= 1.13.4 (see the pins in the Android build.gradle\n // and scripts/setup-sherpa-onnx-optional.sh).\n //\n // The 560ms chunk variant is chosen for both: upstream publishes\n // 80/160/320/560/1120ms, trading latency against accuracy, and 560ms is the\n // middle ground that stays usable for live captions. These are ~450 MB\n // downloads — substantially larger than every other model here.\n \"nemotron-en\": {\n code: \"nemotron-en\",\n displayName: \"Nemotron English (high accuracy)\",\n fileName: \"sherpa-onnx-nemotron-speech-streaming-en-0.6b-560ms-int8-2026-04-25\",\n size: 442 * 1024 * 1024,\n type: \"nemotron\",\n requiredFiles: [\"encoder.int8.onnx\", \"decoder.int8.onnx\", \"joiner.int8.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n // Nemotron-3.5 covers 40 language-locales with automatic language detection,\n // so there is no single BCP-47 tag for it. We report en-US as the nominal\n // tag because downstream consumers require one; the model itself is not\n // restricted to English.\n \"nemotron-3.5\": {\n code: \"nemotron-3.5\",\n displayName: \"Nemotron 3.5 Multilingual (40 languages)\",\n fileName: \"sherpa-onnx-nemotron-3.5-asr-streaming-0.6b-560ms-int8-2026-06-11\",\n size: 453 * 1024 * 1024,\n type: \"nemotron\",\n requiredFiles: [\"encoder.int8.onnx\", \"decoder.int8.onnx\", \"joiner.int8.onnx\", \"tokens.txt\"],\n languageCode: \"en-US\",\n },\n }\n\n private constructor() {}\n\n static getInstance(): STTModelManager {\n if (!STTModelManager.instance) {\n STTModelManager.instance = new STTModelManager()\n }\n return STTModelManager.instance\n }\n\n async getCurrentLanguageFromPreferences(): Promise<string> {\n try {\n const path = await BluetoothSdk.getSttModelPath()\n const code = path && path.length > 0 ? this.getLanguageFromPath(path) : \"\"\n if (code && isAppleSttCode(code)) {\n await this.refreshAppleLanguages()\n }\n if (code && this.config(code)) {\n this.currentLanguage = code\n return code\n }\n return \"\"\n } catch (error) {\n console.error(\"Error getting current STT language from preferences:\", error)\n return \"\"\n }\n }\n\n getCurrentLanguage(): string {\n return this.currentLanguage\n }\n\n setCurrentLanguage(code: string): void {\n if (this.config(code)) {\n this.currentLanguage = code\n }\n }\n\n getBcp47ForLanguage(code?: string): string {\n const id = code || this.currentLanguage\n return this.config(id)?.languageCode ?? \"en-US\"\n }\n\n /** Sherpa models first, then whatever Apple locales the last refresh reported. */\n getAvailableLanguages(): LanguageConfig[] {\n return [...Object.values(this.languages), ...Object.values(this.appleLanguages)]\n }\n\n private config(code: string): LanguageConfig | undefined {\n return this.languages[code] ?? this.appleLanguages[code]\n }\n\n /**\n * Re-read the Apple locale list and install states from the OS. A no-op below\n * iOS 26 and on Android, where the Apple section simply never appears.\n */\n async refreshAppleLanguages(): Promise<void> {\n if (!appleSttSupported()) return\n if (this.appleRefresh) return this.appleRefresh\n this.appleRefresh = (async () => {\n try {\n const rows = await BluetoothSdk.getAppleSpeechLocales()\n const next: Record<string, LanguageConfig> = {}\n const installed = new Set<string>()\n for (const row of rows) {\n const code = APPLE_STT_PREFIX + row.code\n next[code] = {\n code,\n displayName: row.name,\n fileName: \"\",\n size: 0,\n type: \"apple\",\n requiredFiles: [],\n languageCode: row.code,\n }\n if (row.status === \"installed\") installed.add(code)\n }\n this.appleLanguages = next\n this.appleInstalled = installed\n } catch (error) {\n console.warn(\"STTModelManager: Apple locale refresh failed:\", error)\n } finally {\n this.appleRefresh = null\n }\n })()\n return this.appleRefresh\n }\n\n getModelDirectory(): string {\n const baseDir = Platform.OS === \"ios\" ? RNFS.DocumentDirectoryPath : RNFS.DocumentDirectoryPath\n return `${baseDir}/stt_models`\n }\n\n getModelPath(code?: string): string {\n const id = code || this.currentLanguage\n if (isAppleSttCode(id)) return id\n return `${this.getModelDirectory()}/${id}`\n }\n\n /** Last path segment is the language code (the on-disk folder name). */\n getLanguageFromPath(path: string): string {\n return path.split(\"/\").pop() || \"\"\n }\n\n async isModelAvailable(code?: string): Promise<boolean> {\n try {\n const id = code || this.currentLanguage\n if (isAppleSttCode(id)) {\n if (!appleSttSupported()) return false\n const status = await BluetoothSdk.getAppleSpeechLocaleStatus(id.slice(APPLE_STT_PREFIX.length))\n const installed = status === \"installed\"\n if (installed) this.appleInstalled.add(id)\n else this.appleInstalled.delete(id)\n return installed\n }\n const language = this.languages[id]\n if (!language) return false\n\n const modelPath = this.getModelPath(id)\n for (const file of language.requiredFiles) {\n const filePath = `${modelPath}/${file}`\n const exists = await RNFS.exists(filePath)\n if (!exists) {\n console.log(`Missing required STT file: ${file} at ${filePath}`)\n return false\n }\n }\n\n return await BluetoothSdk.validateSttModel(modelPath)\n } catch (error) {\n console.error(\"Error checking STT model availability:\", error)\n return false\n }\n }\n\n async getLanguageInfo(code?: string): Promise<LanguageInfo> {\n const id = code || this.currentLanguage\n const language = this.config(id)\n if (!language) {\n throw new Error(`Language ${id} not found`)\n }\n\n const downloaded = await this.isModelAvailable(id)\n const path = downloaded ? this.getModelPath(id) : undefined\n\n return {\n code: id,\n displayName: language.displayName,\n size: language.size,\n language: language.languageCode,\n downloaded,\n path,\n type: language.type,\n }\n }\n\n async getAllLanguageInfo(): Promise<LanguageInfo[]> {\n const infos: LanguageInfo[] = []\n for (const code of Object.keys(this.languages)) {\n infos.push(await this.getLanguageInfo(code))\n }\n // One OS round-trip covers every Apple row's install state.\n await this.refreshAppleLanguages()\n for (const language of Object.values(this.appleLanguages)) {\n const downloaded = this.appleInstalled.has(language.code)\n infos.push({\n code: language.code,\n displayName: language.displayName,\n size: 0,\n language: language.languageCode,\n downloaded,\n path: downloaded ? language.code : undefined,\n type: \"apple\",\n })\n }\n return infos\n }\n\n /** Downloads the Apple locale's assets through the OS, surfacing its progress as a download. */\n private async installAppleModel(\n code: string,\n onProgress?: (progress: DownloadProgress) => void,\n onExtractionProgress?: (progress: ExtractionProgress) => void,\n ): Promise<void> {\n if (!appleSttSupported()) {\n throw new Error(\"Apple on-device speech requires iOS 26\")\n }\n const locale = code.slice(APPLE_STT_PREFIX.length)\n const subscription = BluetoothSdk.addListener(\"apple_speech_install_progress\", (event) => {\n if (event.locale !== locale) return\n onProgress?.({jobId: 0, bytesWritten: 0, contentLength: 0, percentage: event.percentage})\n })\n try {\n const status = await BluetoothSdk.installAppleSpeechLocale(locale)\n if (status !== \"installed\") {\n throw new Error(`Apple speech assets for ${locale} are ${status}`)\n }\n } finally {\n subscription.remove()\n }\n onExtractionProgress?.({percentage: 100})\n this.appleInstalled.add(code)\n if (code === this.currentLanguage) {\n await this.setNativeModelPath(code, locale)\n }\n }\n\n async downloadModel(\n code?: string,\n onProgress?: (progress: DownloadProgress) => void,\n onExtractionProgress?: (progress: ExtractionProgress) => void,\n ): Promise<void> {\n const id = code || this.currentLanguage\n if (isAppleSttCode(id)) {\n return this.installAppleModel(id, onProgress, onExtractionProgress)\n }\n const language = this.languages[id]\n if (!language) {\n throw new Error(`Language ${id} not found`)\n }\n\n const modelUrl = language.downloadUrl ?? `${this.modelBaseUrl}${language.fileName}.tar.bz2`\n const tempPath = `${RNFS.TemporaryDirectoryPath}/${language.fileName}.tar.bz2`\n const modelDir = this.getModelDirectory()\n const finalPath = this.getModelPath(id)\n\n try {\n await RNFS.mkdir(modelDir, {NSURLIsExcludedFromBackupKey: true})\n\n const downloadOptions = {\n fromUrl: modelUrl,\n toFile: tempPath,\n progress: (res: RNFS.DownloadProgressCallbackResultT) => {\n const percentage = Math.round((res.bytesWritten / res.contentLength) * 100)\n onProgress?.({\n jobId: res.jobId,\n bytesWritten: res.bytesWritten,\n contentLength: res.contentLength,\n percentage,\n })\n },\n progressDivider: 10,\n begin: (res: RNFS.DownloadBeginCallbackResultT) => {\n console.log(\"Download started:\", res)\n },\n connectionTimeout: 30000,\n readTimeout: 30000,\n }\n\n const result = await RNFS.downloadFile(downloadOptions)\n this.downloadJobId = result.jobId\n\n const downloadResult = await result.promise\n\n if (downloadResult.statusCode !== 200) {\n throw new Error(`Download failed with status code: ${downloadResult.statusCode}`)\n }\n\n onExtractionProgress?.({percentage: 0})\n\n const subscription = BluetoothSdk.addListener(\"extraction_progress\", (event) => {\n onExtractionProgress?.({percentage: event.percentage})\n })\n\n try {\n const extractionResult = await BluetoothSdk.extractTarBz2(tempPath, finalPath)\n if (!extractionResult) {\n throw new Error(\"Native extraction returned failure status\")\n }\n } catch (extractError) {\n console.error(\"Native extraction failed:\", extractError)\n throw extractError\n } finally {\n subscription.remove()\n }\n\n onExtractionProgress?.({percentage: 100})\n\n await RNFS.unlink(tempPath)\n\n if (id === this.currentLanguage) {\n await this.setNativeModelPath(finalPath, language.languageCode)\n }\n } catch (error) {\n // Best-effort cleanup; never let it mask the original error.\n try {\n if (await RNFS.exists(tempPath)) await RNFS.unlink(tempPath)\n } catch (cleanupError) {\n console.warn(\"STTModelManager: temp cleanup failed:\", cleanupError)\n }\n try {\n if (await RNFS.exists(finalPath)) await RNFS.unlink(finalPath)\n } catch (cleanupError) {\n console.warn(\"STTModelManager: final cleanup failed:\", cleanupError)\n }\n throw error\n }\n }\n\n async cancelDownload(): Promise<void> {\n if (this.downloadJobId !== undefined) {\n await RNFS.stopDownload(this.downloadJobId)\n this.downloadJobId = undefined\n }\n }\n\n async deleteModel(code?: string): Promise<void> {\n const id = code || this.currentLanguage\n if (isAppleSttCode(id)) {\n // The OS owns the assets; only the app's reservation can be dropped.\n this.appleInstalled.delete(id)\n if (appleSttSupported()) {\n await BluetoothSdk.releaseAppleSpeechLocale(id.slice(APPLE_STT_PREFIX.length))\n }\n return\n }\n const modelPath = this.getModelPath(id)\n if (await RNFS.exists(modelPath)) {\n await RNFS.unlink(modelPath)\n }\n }\n\n async activateLanguage(code: string): Promise<void> {\n if (isAppleSttCode(code) && !this.appleLanguages[code]) {\n await this.refreshAppleLanguages()\n }\n const language = this.config(code)\n if (!language) {\n throw new Error(`Language ${code} not found`)\n }\n\n const isAvailable = await this.isModelAvailable(code)\n if (!isAvailable) {\n throw new Error(`Language ${code} model is not downloaded`)\n }\n\n this.currentLanguage = code\n const modelPath = this.getModelPath(code)\n await this.setNativeModelPath(modelPath, language.languageCode)\n }\n\n private async setNativeModelPath(path: string, languageCode: string): Promise<void> {\n await BluetoothSdk.setSttModelDetails(path, languageCode)\n for (const listener of this.modelReadyListeners) {\n try {\n listener()\n } catch (error) {\n console.warn(\"STTModelManager: model-ready listener failed:\", error)\n }\n }\n }\n\n /** Fired after a downloaded/selected model's path has reached native. */\n onModelReady(listener: () => void): () => void {\n this.modelReadyListeners.add(listener)\n return () => this.modelReadyListeners.delete(listener)\n }\n\n async getStorageInfo(): Promise<{free: number; total: number}> {\n const fsInfo = await RNFS.getFSInfo()\n return {\n free: fsInfo.freeSpace,\n total: fsInfo.totalSpace,\n }\n }\n\n formatBytes(bytes: number): string {\n return STTModelManager.formatBytes(bytes)\n }\n\n static formatBytes(bytes: number): string {\n if (bytes === 0) return \"0 Bytes\"\n const k = 1024\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\"]\n const i = Math.floor(Math.log(bytes) / Math.log(k))\n return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \" \" + sizes[i]\n }\n}\n\nconst instance = STTModelManager.getInstance()\nexport {STTModelManager}\nexport default instance\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Controller registry read-model: the paired records (the native-authoritative
3
+ * `controllers` setting) and the live driver statuses (glasses status
4
+ * `controllers`), plus what each model adds to the input surfaces. Pure.
5
+ */
6
+ import type { ControllerStatus, PairedController } from "@glassly/bluetooth-sdk";
7
+ import type { ButtonCapabilities } from "../types";
8
+ export type InputSurface = NonNullable<ButtonCapabilities["buttons"]>[number];
9
+ /** The input surfaces a connected controller of `model` adds to the glasses'. */
10
+ export declare function controllerInputSurfaces(model: string): InputSurface[];
11
+ /** The model swipes in two dimensions. */
12
+ export declare function hasInput2d(model: string): boolean;
13
+ export declare function isControllerModel(model: string | undefined): boolean;
14
+ /** The `controllers` setting as records; malformed entries are dropped. */
15
+ export declare function pairedControllersFrom(value: unknown): PairedController[];
16
+ /** Delivering input: link up and the route or claim done. */
17
+ export declare function isControllerReady(status: ControllerStatus | undefined): boolean;
18
+ export declare function readyControllers(statuses: Record<string, ControllerStatus> | undefined): ControllerStatus[];
19
+ /** Change key over the controllers delivering input. */
20
+ export declare function readyControllersKey(statuses: Record<string, ControllerStatus> | undefined): string;
21
+ //# sourceMappingURL=controllers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controllers.d.ts","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAC,gBAAgB,EAAE,gBAAgB,EAAC,MAAM,wBAAwB,CAAA;AAE9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,UAAU,CAAA;AAGhD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAwB7E,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAErE;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,EAAE,CAUxE;AAED,6DAA6D;AAC7D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAE/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,gBAAgB,EAAE,CAE3G;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,CAKlG"}
@@ -0,0 +1,62 @@
1
+ import { ControllerTypes } from "../types/enums";
2
+ /** The Neural Band's thumb swipes, pinches and dial. */
3
+ const NEURAL_BAND_SURFACE = {
4
+ type: "swipe2d",
5
+ events: [
6
+ "TAP",
7
+ "DOUBLE_TAP",
8
+ "PRESS_HOLD",
9
+ "SWIPE_UP",
10
+ "SWIPE_DOWN",
11
+ "SWIPE_LEFT",
12
+ "SWIPE_RIGHT",
13
+ "DIAL_UP",
14
+ "DIAL_DOWN",
15
+ ],
16
+ };
17
+ // The R1 swipes up/down like the temples it stands in for, so it adds nothing.
18
+ const INPUT_SURFACES = {
19
+ [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],
20
+ [ControllerTypes.R1]: [],
21
+ };
22
+ /** The input surfaces a connected controller of `model` adds to the glasses'. */
23
+ export function controllerInputSurfaces(model) {
24
+ return (INPUT_SURFACES[model] ?? []).map((surface) => ({ ...surface, events: [...surface.events] }));
25
+ }
26
+ /** The model swipes in two dimensions. */
27
+ export function hasInput2d(model) {
28
+ return (INPUT_SURFACES[model] ?? []).some((surface) => surface.type === "swipe2d");
29
+ }
30
+ export function isControllerModel(model) {
31
+ return model !== undefined && Object.values(ControllerTypes).includes(model);
32
+ }
33
+ /** The `controllers` setting as records; malformed entries are dropped. */
34
+ export function pairedControllersFrom(value) {
35
+ if (!Array.isArray(value))
36
+ return [];
37
+ const records = [];
38
+ for (const entry of value) {
39
+ if (!entry || typeof entry !== "object")
40
+ continue;
41
+ const { id, model, name } = entry;
42
+ if (typeof id !== "string" || !id || typeof model !== "string")
43
+ continue;
44
+ records.push({ id, model, name: typeof name === "string" ? name : id });
45
+ }
46
+ return records;
47
+ }
48
+ /** Delivering input: link up and the route or claim done. */
49
+ export function isControllerReady(status) {
50
+ return status?.connected === true && status.fullyBooted === true;
51
+ }
52
+ export function readyControllers(statuses) {
53
+ return Object.values(statuses ?? {}).filter(isControllerReady);
54
+ }
55
+ /** Change key over the controllers delivering input. */
56
+ export function readyControllersKey(statuses) {
57
+ return readyControllers(statuses)
58
+ .map((status) => `${status.id}=${status.model}`)
59
+ .sort()
60
+ .join(",");
61
+ }
62
+ //# sourceMappingURL=controllers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controllers.js","sourceRoot":"","sources":["../../src/services/controllers.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,eAAe,EAAC,MAAM,gBAAgB,CAAA;AAI9C,wDAAwD;AACxD,MAAM,mBAAmB,GAAiB;IACxC,IAAI,EAAE,SAAS;IACf,MAAM,EAAE;QACN,KAAK;QACL,YAAY;QACZ,YAAY;QACZ,UAAU;QACV,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,SAAS;QACT,WAAW;KACZ;CACF,CAAA;AAED,+EAA+E;AAC/E,MAAM,cAAc,GAAsD;IACxE,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC,mBAAmB,CAAC;IACpD,CAAC,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE;CACzB,CAAA;AAED,iFAAiF;AACjF,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAC,GAAG,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,EAAC,CAAC,CAAC,CAAA;AACpG,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAA;AACpF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,OAAO,KAAK,KAAK,SAAS,IAAK,MAAM,CAAC,MAAM,CAAC,eAAe,CAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AAC5F,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,OAAO,GAAuB,EAAE,CAAA;IACtC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACjD,MAAM,EAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAC,GAAG,KAAgC,CAAA;QAC1D,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACxE,OAAO,CAAC,IAAI,CAAC,EAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAC,CAAC,CAAA;IACvE,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,MAAoC;IACpE,OAAO,MAAM,EAAE,SAAS,KAAK,IAAI,IAAI,MAAM,CAAC,WAAW,KAAK,IAAI,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAsD;IACrF,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAA;AAChE,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,mBAAmB,CAAC,QAAsD;IACxF,OAAO,gBAAgB,CAAC,QAAQ,CAAC;SAC9B,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;SAC/C,IAAI,EAAE;SACN,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["/**\n * Controller registry read-model: the paired records (the native-authoritative\n * `controllers` setting) and the live driver statuses (glasses status\n * `controllers`), plus what each model adds to the input surfaces. Pure.\n */\nimport type {ControllerStatus, PairedController} from \"@glassly/bluetooth-sdk\"\n\nimport type {ButtonCapabilities} from \"../types\"\nimport {ControllerTypes} from \"../types/enums\"\n\nexport type InputSurface = NonNullable<ButtonCapabilities[\"buttons\"]>[number]\n\n/** The Neural Band's thumb swipes, pinches and dial. */\nconst NEURAL_BAND_SURFACE: InputSurface = {\n type: \"swipe2d\",\n events: [\n \"TAP\",\n \"DOUBLE_TAP\",\n \"PRESS_HOLD\",\n \"SWIPE_UP\",\n \"SWIPE_DOWN\",\n \"SWIPE_LEFT\",\n \"SWIPE_RIGHT\",\n \"DIAL_UP\",\n \"DIAL_DOWN\",\n ],\n}\n\n// The R1 swipes up/down like the temples it stands in for, so it adds nothing.\nconst INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {\n [ControllerTypes.NEURAL_BAND]: [NEURAL_BAND_SURFACE],\n [ControllerTypes.R1]: [],\n}\n\n/** The input surfaces a connected controller of `model` adds to the glasses'. */\nexport function controllerInputSurfaces(model: string): InputSurface[] {\n return (INPUT_SURFACES[model] ?? []).map((surface) => ({...surface, events: [...surface.events]}))\n}\n\n/** The model swipes in two dimensions. */\nexport function hasInput2d(model: string): boolean {\n return (INPUT_SURFACES[model] ?? []).some((surface) => surface.type === \"swipe2d\")\n}\n\nexport function isControllerModel(model: string | undefined): boolean {\n return model !== undefined && (Object.values(ControllerTypes) as string[]).includes(model)\n}\n\n/** The `controllers` setting as records; malformed entries are dropped. */\nexport function pairedControllersFrom(value: unknown): PairedController[] {\n if (!Array.isArray(value)) return []\n const records: PairedController[] = []\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue\n const {id, model, name} = entry as Record<string, unknown>\n if (typeof id !== \"string\" || !id || typeof model !== \"string\") continue\n records.push({id, model, name: typeof name === \"string\" ? name : id})\n }\n return records\n}\n\n/** Delivering input: link up and the route or claim done. */\nexport function isControllerReady(status: ControllerStatus | undefined): boolean {\n return status?.connected === true && status.fullyBooted === true\n}\n\nexport function readyControllers(statuses: Record<string, ControllerStatus> | undefined): ControllerStatus[] {\n return Object.values(statuses ?? {}).filter(isControllerReady)\n}\n\n/** Change key over the controllers delivering input. */\nexport function readyControllersKey(statuses: Record<string, ControllerStatus> | undefined): string {\n return readyControllers(statuses)\n .map((status) => `${status.id}=${status.model}`)\n .sort()\n .join(\",\")\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"effectiveCapabilities.d.ts","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAoBA,OAAO,EAEL,WAAW,EAGX,KAAK,YAAY,EAClB,MAAM,UAAU,CAAA;AAQjB,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAI3D;AAMD,iGAAiG;AACjG,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAEzE"}
1
+ {"version":3,"file":"effectiveCapabilities.d.ts","sourceRoot":"","sources":["../../src/services/effectiveCapabilities.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAqB,WAAW,EAAwB,KAAK,YAAY,EAAC,MAAM,UAAU,CAAA;AAQjG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,IAAI,CAI3D;AAMD,iGAAiG;AACjG,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAEzE"}
@@ -12,15 +12,15 @@
12
12
  * that draws vector shapes keeps 128 scene slots, so shape-heavy frames
13
13
  * (a game board, a chart) get a larger shared text/shape budget.
14
14
  *
15
- * A connected controller that swipes in two dimensions (the Meta Neural Band)
16
- * adds its surface to `button.buttons` as a `swipe2d` entry, so a miniapp can
15
+ * Every connected controller adds its model's input surfaces to
16
+ * `button.buttons` (the Meta Neural Band a `swipe2d` entry), so a miniapp can
17
17
  * tell four-way swipes from the temples' and the R1 ring's up/down.
18
18
  */
19
19
  import { useGlassesStore } from "../stores/glasses";
20
20
  import { SETTINGS, useSettingsStore } from "../stores/settings";
21
- import { ALL_DISPLAY_SHAPES, getModelCapabilities, } from "../types";
21
+ import { ALL_DISPLAY_SHAPES, getModelCapabilities } from "../types";
22
+ import { controllerInputSurfaces, readyControllers } from "./controllers";
22
23
  import { resolveNativeNavigation } from "./nativeNavigationGate";
23
- import { input2dFrom } from "./settingsBridge";
24
24
  const CFW_MAX_TEXT_ELEMENTS = 24;
25
25
  const CFW_MAX_IMAGE_ELEMENTS = 16;
26
26
  const CFW_VECTOR_MAX_ELEMENTS = 120;
@@ -69,33 +69,27 @@ function glassesCapabilities(model) {
69
69
  },
70
70
  };
71
71
  }
72
- /** The Neural Band's thumb swipes, pinches and dial. */
73
- const NEURAL_BAND_SURFACE = {
74
- type: "swipe2d",
75
- events: [
76
- "TAP",
77
- "DOUBLE_TAP",
78
- "PRESS_HOLD",
79
- "SWIPE_UP",
80
- "SWIPE_DOWN",
81
- "SWIPE_LEFT",
82
- "SWIPE_RIGHT",
83
- "DIAL_UP",
84
- "DIAL_DOWN",
85
- ],
86
- };
87
72
  function withControllerInput(caps) {
88
- const controller = useSettingsStore.getState().getSetting(SETTINGS.default_controller.key);
89
- if (!input2dFrom(useGlassesStore.getState(), controller))
90
- return caps;
91
73
  const buttons = caps.button?.buttons ?? [];
74
+ const types = new Set(buttons.map((surface) => surface.type));
75
+ const added = [];
76
+ for (const status of readyControllers(useGlassesStore.getState().controllers)) {
77
+ for (const surface of controllerInputSurfaces(status.model)) {
78
+ if (types.has(surface.type))
79
+ continue;
80
+ types.add(surface.type);
81
+ added.push(surface);
82
+ }
83
+ }
84
+ if (added.length === 0)
85
+ return caps;
92
86
  return {
93
87
  ...caps,
94
88
  hasButton: true,
95
89
  button: {
96
90
  ...caps.button,
97
- count: (caps.button?.count ?? buttons.length) + 1,
98
- buttons: [...buttons, NEURAL_BAND_SURFACE],
91
+ count: (caps.button?.count ?? buttons.length) + added.length,
92
+ buttons: [...buttons, ...added],
99
93
  },
100
94
  };
101
95
  }