@glassly/engine 0.3.3-dev.670 → 0.3.4-beta.685
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.
- package/build/engine.d.ts +38 -14
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +3 -0
- package/build/engine.js.map +1 -1
- package/build/facades/ai.d.ts +32 -0
- package/build/facades/ai.d.ts.map +1 -0
- package/build/facades/ai.js +38 -0
- package/build/facades/ai.js.map +1 -0
- package/build/facades/glasses.d.ts +30 -24
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +49 -30
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/glassesSettings.d.ts.map +1 -1
- package/build/facades/glassesSettings.js +1 -0
- package/build/facades/glassesSettings.js.map +1 -1
- package/build/facades/pairing.d.ts +6 -5
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +14 -14
- package/build/facades/pairing.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +5 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +9 -1
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/GestureDotsService.d.ts +14 -3
- package/build/services/GestureDotsService.d.ts.map +1 -1
- package/build/services/GestureDotsService.js +48 -13
- package/build/services/GestureDotsService.js.map +1 -1
- package/build/services/GlassesTouchInput.d.ts.map +1 -1
- package/build/services/GlassesTouchInput.js +11 -9
- package/build/services/GlassesTouchInput.js.map +1 -1
- package/build/services/LlmRouting.d.ts +35 -0
- package/build/services/LlmRouting.d.ts.map +1 -0
- package/build/services/LlmRouting.js +25 -0
- package/build/services/LlmRouting.js.map +1 -0
- package/build/services/LocalLlmModelManager.d.ts +79 -0
- package/build/services/LocalLlmModelManager.d.ts.map +1 -0
- package/build/services/LocalLlmModelManager.js +278 -0
- package/build/services/LocalLlmModelManager.js.map +1 -0
- package/build/services/LocalLlmPrompt.d.ts +90 -0
- package/build/services/LocalLlmPrompt.d.ts.map +1 -0
- package/build/services/LocalLlmPrompt.js +189 -0
- package/build/services/LocalLlmPrompt.js.map +1 -0
- package/build/services/LocalLlmProvider.d.ts +35 -0
- package/build/services/LocalLlmProvider.d.ts.map +1 -0
- package/build/services/LocalLlmProvider.js +218 -0
- package/build/services/LocalLlmProvider.js.map +1 -0
- package/build/services/LocalMiniappRuntime.d.ts +9 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +91 -17
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/STTModelManager.d.ts +25 -2
- package/build/services/STTModelManager.d.ts.map +1 -1
- package/build/services/STTModelManager.js +137 -6
- package/build/services/STTModelManager.js.map +1 -1
- package/build/services/controllers.d.ts +21 -0
- package/build/services/controllers.d.ts.map +1 -0
- package/build/services/controllers.js +62 -0
- package/build/services/controllers.js.map +1 -0
- package/build/services/effectiveCapabilities.d.ts.map +1 -1
- package/build/services/effectiveCapabilities.js +18 -24
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts +17 -6
- package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.js +40 -23
- package/build/services/neuralBand/NeuralBandInput.js.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandSettingsSync.js +23 -26
- package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
- package/build/services/settingsBridge.d.ts +26 -18
- package/build/services/settingsBridge.d.ts.map +1 -1
- package/build/services/settingsBridge.js +36 -15
- package/build/services/settingsBridge.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +3 -1
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/core.d.ts.map +1 -1
- package/build/stores/core.js +0 -1
- package/build/stores/core.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +2 -6
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +29 -3
- package/build/stores/settings.js.map +1 -1
- package/package.json +12 -8
- package/src/engine.ts +3 -0
- package/src/facades/ai.ts +43 -0
- package/src/facades/glasses.ts +60 -31
- package/src/facades/glassesSettings.ts +1 -0
- package/src/facades/pairing.ts +18 -14
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +16 -2
- package/src/services/DeviceEventRouter.ts +8 -1
- package/src/services/GestureDotsService.ts +52 -15
- package/src/services/GlassesTouchInput.ts +11 -9
- package/src/services/LlmRouting.ts +49 -0
- package/src/services/LocalLlmModelManager.ts +327 -0
- package/src/services/LocalLlmPrompt.ts +246 -0
- package/src/services/LocalLlmProvider.ts +290 -0
- package/src/services/LocalMiniappRuntime.ts +137 -21
- package/src/services/STTModelManager.ts +140 -8
- package/src/services/controllers.ts +77 -0
- package/src/services/effectiveCapabilities.ts +16 -32
- package/src/services/neuralBand/NeuralBandInput.ts +50 -26
- package/src/services/neuralBand/NeuralBandSettingsSync.ts +28 -26
- package/src/services/settingsBridge.ts +60 -22
- package/src/stores/bluetoothSettingKeys.ts +3 -1
- package/src/stores/core.ts +0 -1
- package/src/stores/glasses.ts +2 -6
- 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:
|
|
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:
|
|
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":"AAIA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,YAAY,
|
|
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 &&
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
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
|
-
*
|
|
16
|
-
*
|
|
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
|
|
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) +
|
|
98
|
-
buttons: [...buttons,
|
|
91
|
+
count: (caps.button?.count ?? buttons.length) + added.length,
|
|
92
|
+
buttons: [...buttons, ...added],
|
|
99
93
|
},
|
|
100
94
|
};
|
|
101
95
|
}
|