@glassly/engine 0.3.7-dev.974 → 0.3.8-beta.1013
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 +10 -0
- package/build/engine.d.ts.map +1 -1
- package/build/facades/ai.d.ts +3 -0
- package/build/facades/ai.d.ts.map +1 -1
- package/build/facades/ai.js +3 -0
- package/build/facades/ai.js.map +1 -1
- package/build/facades/glasses.d.ts +9 -1
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +13 -0
- package/build/facades/glasses.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/services/CloudClientService.d.ts +2 -1
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +8 -0
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/DeviceEventRouter.d.ts.map +1 -1
- package/build/services/DeviceEventRouter.js +1 -0
- package/build/services/DeviceEventRouter.js.map +1 -1
- package/build/services/DisplayDiagnostics.d.ts.map +1 -1
- package/build/services/DisplayDiagnostics.js +4 -2
- package/build/services/DisplayDiagnostics.js.map +1 -1
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +5 -2
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +7 -2
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
- package/build/services/LocalSttFallbackCoordinator.js +2 -0
- package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
- package/build/services/LocalTranslationService.d.ts +2 -0
- package/build/services/LocalTranslationService.d.ts.map +1 -1
- package/build/services/LocalTranslationService.js +22 -0
- package/build/services/LocalTranslationService.js.map +1 -1
- package/build/services/OfflineActivity.d.ts +54 -0
- package/build/services/OfflineActivity.d.ts.map +1 -0
- package/build/services/OfflineActivity.js +86 -0
- package/build/services/OfflineActivity.js.map +1 -0
- package/build/services/STTModelManager.d.ts +1 -0
- package/build/services/STTModelManager.d.ts.map +1 -1
- package/build/services/STTModelManager.js +6 -0
- package/build/services/STTModelManager.js.map +1 -1
- package/build/services/controllers.d.ts.map +1 -1
- package/build/services/controllers.js +2 -0
- package/build/services/controllers.js.map +1 -1
- package/build/services/effectiveCapabilities.js +3 -1
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts +12 -1
- package/build/services/neuralBand/NeuralBandInput.d.ts.map +1 -1
- package/build/services/neuralBand/NeuralBandInput.js +27 -0
- 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 +6 -0
- package/build/services/neuralBand/NeuralBandSettingsSync.js.map +1 -1
- package/build/stores/appVisibility.d.ts.map +1 -1
- package/build/stores/appVisibility.js +0 -1
- package/build/stores/appVisibility.js.map +1 -1
- package/build/stores/apps.js +9 -9
- package/build/stores/apps.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +26 -3
- package/build/stores/settings.js.map +1 -1
- package/build/types/capabilities/memomind-one.d.ts.map +1 -1
- package/build/types/capabilities/memomind-one.js +1 -0
- package/build/types/capabilities/memomind-one.js.map +1 -1
- package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
- package/build/types/capabilities/rayneo-io.js +3 -2
- package/build/types/capabilities/rayneo-io.js.map +1 -1
- package/build/types/enums.d.ts +3 -1
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +2 -0
- package/build/types/enums.js.map +1 -1
- package/package.json +7 -7
- package/src/facades/ai.ts +4 -0
- package/src/facades/glasses.ts +17 -0
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +9 -0
- package/src/services/CloudClientService.ts +8 -0
- package/src/services/DeviceEventRouter.ts +1 -0
- package/src/services/DisplayDiagnostics.ts +4 -2
- package/src/services/LocalDisplayManager.ts +5 -2
- package/src/services/LocalMiniappRuntime.ts +6 -2
- package/src/services/LocalSttFallbackCoordinator.ts +4 -0
- package/src/services/LocalTranslationService.ts +20 -0
- package/src/services/OfflineActivity.ts +131 -0
- package/src/services/STTModelManager.ts +6 -0
- package/src/services/controllers.ts +2 -0
- package/src/services/effectiveCapabilities.ts +3 -1
- package/src/services/neuralBand/NeuralBandInput.ts +37 -1
- package/src/services/neuralBand/NeuralBandSettingsSync.ts +12 -0
- package/src/stores/appVisibility.ts +0 -1
- package/src/stores/apps.ts +11 -11
- package/src/stores/settings.ts +26 -3
- package/src/types/capabilities/memomind-one.ts +1 -0
- package/src/types/capabilities/rayneo-io.ts +3 -2
- package/src/types/enums.ts +2 -0
package/build/types/enums.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export declare enum ControllerTypes {
|
|
|
20
20
|
APPLE_WATCH = "Apple Watch",
|
|
21
21
|
WEAR_OS = "Wear OS Watch",
|
|
22
22
|
GAMEBRICK = "IINE Gamebrick",
|
|
23
|
-
IINE_ANANKE = "IINE Ananke Gen 2"
|
|
23
|
+
IINE_ANANKE = "IINE Ananke Gen 2",
|
|
24
|
+
ZERO2 = "8BitDo Zero 2",
|
|
25
|
+
MICRO = "8BitDo Micro"
|
|
24
26
|
}
|
|
25
27
|
//# sourceMappingURL=enums.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAC,MAAM,2BAA2B,CAAA;AAEhF,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,SAAS,sBAAsB;IAC/B,IAAI,SAAS;IACb,EAAE,sBAAsB;IACxB,EAAE,sBAAsB;IACxB,IAAI,gBAAgB;IACpB,KAAK,iBAAiB;IACtB,IAAI,eAAe;IACnB,GAAG,mBAAmB;IACtB,IAAI,SAAS;IACb,KAAK,oBAAoB;IACzB,SAAS,cAAc;IACvB,YAAY,iBAAiB;CAC9B;AAED,oBAAY,eAAe;IACzB,EAAE,sBAAsB;IACxB,WAAW,qBAAqB;IAChC,WAAW,gBAAgB;IAC3B,OAAO,kBAAkB;IACzB,SAAS,mBAAmB;IAC5B,WAAW,sBAAsB;
|
|
1
|
+
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAC,MAAM,2BAA2B,CAAA;AAEhF,oBAAY,WAAW;IACrB,IAAI,SAAS;IACb,SAAS,sBAAsB;IAC/B,IAAI,SAAS;IACb,EAAE,sBAAsB;IACxB,EAAE,sBAAsB;IACxB,IAAI,gBAAgB;IACpB,KAAK,iBAAiB;IACtB,IAAI,eAAe;IACnB,GAAG,mBAAmB;IACtB,IAAI,SAAS;IACb,KAAK,oBAAoB;IACzB,SAAS,cAAc;IACvB,YAAY,iBAAiB;CAC9B;AAED,oBAAY,eAAe;IACzB,EAAE,sBAAsB;IACxB,WAAW,qBAAqB;IAChC,WAAW,gBAAgB;IAC3B,OAAO,kBAAkB;IACzB,SAAS,mBAAmB;IAC5B,WAAW,sBAAsB;IACjC,KAAK,kBAAkB;IACvB,KAAK,iBAAiB;CACvB"}
|
package/build/types/enums.js
CHANGED
|
@@ -23,5 +23,7 @@ export var ControllerTypes;
|
|
|
23
23
|
ControllerTypes["WEAR_OS"] = "Wear OS Watch";
|
|
24
24
|
ControllerTypes["GAMEBRICK"] = "IINE Gamebrick";
|
|
25
25
|
ControllerTypes["IINE_ANANKE"] = "IINE Ananke Gen 2";
|
|
26
|
+
ControllerTypes["ZERO2"] = "8BitDo Zero 2";
|
|
27
|
+
ControllerTypes["MICRO"] = "8BitDo Micro";
|
|
26
28
|
})(ControllerTypes || (ControllerTypes = {}));
|
|
27
29
|
//# sourceMappingURL=enums.js.map
|
package/build/types/enums.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAC,MAAM,2BAA2B,CAAA;AAEhF,MAAM,CAAN,IAAY,WAcX;AAdD,WAAY,WAAW;IACrB,4BAAa,CAAA;IACb,8CAA+B,CAAA;IAC/B,4BAAa,CAAA;IACb,uCAAwB,CAAA;IACxB,uCAAwB,CAAA;IACxB,mCAAoB,CAAA;IACpB,qCAAsB,CAAA;IACtB,kCAAmB,CAAA;IACnB,qCAAsB,CAAA;IACtB,4BAAa,CAAA;IACb,wCAAyB,CAAA;IACzB,sCAAuB,CAAA;IACvB,4CAA6B,CAAA;AAC/B,CAAC,EAdW,WAAW,KAAX,WAAW,QActB;AAED,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,wBAAwB,EAAC,MAAM,2BAA2B,CAAA;AAEhF,MAAM,CAAN,IAAY,WAcX;AAdD,WAAY,WAAW;IACrB,4BAAa,CAAA;IACb,8CAA+B,CAAA;IAC/B,4BAAa,CAAA;IACb,uCAAwB,CAAA;IACxB,uCAAwB,CAAA;IACxB,mCAAoB,CAAA;IACpB,qCAAsB,CAAA;IACtB,kCAAmB,CAAA;IACnB,qCAAsB,CAAA;IACtB,4BAAa,CAAA;IACb,wCAAyB,CAAA;IACzB,sCAAuB,CAAA;IACvB,4CAA6B,CAAA;AAC/B,CAAC,EAdW,WAAW,KAAX,WAAW,QActB;AAED,MAAM,CAAN,IAAY,eASX;AATD,WAAY,eAAe;IACzB,2CAAwB,CAAA;IACxB,mDAAgC,CAAA;IAChC,8CAA2B,CAAA;IAC3B,4CAAyB,CAAA;IACzB,+CAA4B,CAAA;IAC5B,oDAAiC,CAAA;IACjC,0CAAuB,CAAA;IACvB,yCAAsB,CAAA;AACxB,CAAC,EATW,eAAe,KAAf,eAAe,QAS1B","sourcesContent":["export {HardwareType, HardwareRequirementLevel} from \"@glassly/miniapp/hardware\"\n\nexport enum DeviceTypes {\n NONE = \"None\",\n SIMULATED = \"Simulated Glasses\",\n AR99 = \"AR99\",\n G1 = \"Even Realities G1\",\n G2 = \"Even Realities G2\",\n LIVE = \"Mentra Live\",\n MACH1 = \"Mentra Mach1\",\n Z100 = \"Vuzix Z100\",\n NEX = \"Mentra Display\",\n NIMO = \"NIMO\",\n FRAME = \"Brilliant Frame\",\n RAYNEO_IO = \"RayNeo iO\",\n MEMOMIND_ONE = \"MemoMind One\",\n}\n\nexport enum ControllerTypes {\n R1 = \"Even Realities R1\",\n NEURAL_BAND = \"Meta Neural Band\",\n APPLE_WATCH = \"Apple Watch\",\n WEAR_OS = \"Wear OS Watch\",\n GAMEBRICK = \"IINE Gamebrick\",\n IINE_ANANKE = \"IINE Ananke Gen 2\",\n ZERO2 = \"8BitDo Zero 2\",\n MICRO = \"8BitDo Micro\",\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glassly/engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8-beta.1013",
|
|
4
4
|
"description": "Glassly Engine — miniapp registry, WebView bridge, and miniapp store",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -97,17 +97,17 @@
|
|
|
97
97
|
"registry": "https://registry.npmjs.org/"
|
|
98
98
|
},
|
|
99
99
|
"dependencies": {
|
|
100
|
-
"@glassly/bluetooth-sdk": "0.3.
|
|
101
|
-
"@glassly/cloud-client": "0.3.
|
|
102
|
-
"@glassly/cloud-protocol": "0.3.
|
|
103
|
-
"@glassly/crust": "0.3.
|
|
104
|
-
"@glassly/miniapp": "0.3.
|
|
100
|
+
"@glassly/bluetooth-sdk": "0.3.8-beta.1013",
|
|
101
|
+
"@glassly/cloud-client": "0.3.8-beta.1013",
|
|
102
|
+
"@glassly/cloud-protocol": "0.3.8-beta.1013",
|
|
103
|
+
"@glassly/crust": "0.3.8-beta.1013",
|
|
104
|
+
"@glassly/miniapp": "0.3.8-beta.1013",
|
|
105
105
|
"buffer": "^6.0.3",
|
|
106
106
|
"events": "^3.3.0",
|
|
107
107
|
"react-native-marked": "8.3.2",
|
|
108
108
|
"semver": "^7.7.4",
|
|
109
109
|
"typesafe-ts": "^1.7.1",
|
|
110
|
-
"@glassly/llm-providers": "0.3.
|
|
110
|
+
"@glassly/llm-providers": "0.3.8-beta.1013",
|
|
111
111
|
"@huggingface/jinja": "^0.5.9",
|
|
112
112
|
"jsonrepair": "^3.12.0"
|
|
113
113
|
},
|
package/src/facades/ai.ts
CHANGED
|
@@ -12,6 +12,7 @@ import localLlmModelManager, {
|
|
|
12
12
|
type LocalLlmModelId,
|
|
13
13
|
} from "../services/LocalLlmModelManager"
|
|
14
14
|
import localLlmProvider from "../services/LocalLlmProvider"
|
|
15
|
+
import offlineActivity, {type OfflineActivity} from "../services/OfflineActivity"
|
|
15
16
|
|
|
16
17
|
export const ai = {
|
|
17
18
|
local: {
|
|
@@ -40,4 +41,7 @@ export const ai = {
|
|
|
40
41
|
unload: (): Promise<void> => localLlmProvider.unload(),
|
|
41
42
|
formatBytes: (bytes: number): string => LocalLlmModelManager.formatBytes(bytes),
|
|
42
43
|
},
|
|
44
|
+
offlineActivity: (): OfflineActivity => offlineActivity.current(),
|
|
45
|
+
onOfflineActivity: (listener: (activity: OfflineActivity) => void): (() => void) =>
|
|
46
|
+
offlineActivity.subscribe(listener),
|
|
43
47
|
}
|
package/src/facades/glasses.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
Device,
|
|
20
20
|
KeyboardAdvertEvent,
|
|
21
21
|
KeyboardPairingEvent,
|
|
22
|
+
NeuralBandFirmwareEvent,
|
|
22
23
|
PairedController,
|
|
23
24
|
TouchEvent,
|
|
24
25
|
} from "@glassly/bluetooth-sdk"
|
|
@@ -39,6 +40,7 @@ import {pushAllBluetoothSettings} from "../services/GlassesSettingsSync"
|
|
|
39
40
|
import {isControllerReady, pairedControllersFrom, readyControllers} from "../services/controllers"
|
|
40
41
|
import neuralBandInput, {
|
|
41
42
|
type NeuralBandClaimStatus,
|
|
43
|
+
type NeuralBandFirmwareStatus,
|
|
42
44
|
type NeuralBandHandwritingStatus,
|
|
43
45
|
type NeuralBandHandwritingUpdate,
|
|
44
46
|
} from "../services/neuralBand/NeuralBandInput"
|
|
@@ -344,6 +346,21 @@ export const glasses = {
|
|
|
344
346
|
/** The band's air mouse; movement reaches miniapps on the pointer stream. */
|
|
345
347
|
setPointer: (id: string, enabled: boolean): Promise<void> => BluetoothSdk.neuralBandSetPointer(id, enabled),
|
|
346
348
|
setHand: (id: string, hand: "left" | "right"): Promise<void> => BluetoothSdk.neuralBandSetHand(id, hand),
|
|
349
|
+
firmwareStatus: (id: string): NeuralBandFirmwareStatus => ({...neuralBandInput.firmwareStatus(id)}),
|
|
350
|
+
onFirmwareStatus: (cb: (update: {id: string; status: NeuralBandFirmwareStatus}) => void): (() => void) =>
|
|
351
|
+
neuralBandInput.onFirmwareStatus(cb),
|
|
352
|
+
refreshFirmware: async (id: string): Promise<NeuralBandFirmwareStatus> => {
|
|
353
|
+
const info = await BluetoothSdk.neuralBandFirmwareInfo(id)
|
|
354
|
+
neuralBandInput.handleFirmware({
|
|
355
|
+
...(info as object),
|
|
356
|
+
type: "neural_band_firmware",
|
|
357
|
+
controllerId: id,
|
|
358
|
+
} as NeuralBandFirmwareEvent)
|
|
359
|
+
return neuralBandInput.firmwareStatus(id)
|
|
360
|
+
},
|
|
361
|
+
checkFirmware: (id: string): Promise<void> => BluetoothSdk.neuralBandCheckFirmware(id),
|
|
362
|
+
updateFirmware: (id: string, mode: "update" | "cancel" | "key" | "send" | "trial" | "install"): Promise<void> =>
|
|
363
|
+
BluetoothSdk.neuralBandUpdateFirmware(id, mode),
|
|
347
364
|
},
|
|
348
365
|
|
|
349
366
|
// --- watch: an Apple Watch over WatchConnectivity (iOS), a Wear OS watch over the Data Layer (Android) ---
|
|
@@ -11,10 +11,10 @@ export interface EngineReleaseMetadata {
|
|
|
11
11
|
|
|
12
12
|
export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
|
-
"familyBaseVersion": "0.3.
|
|
15
|
-
"releaseIdentity": "0.3.
|
|
16
|
-
"releaseSetId": "glassly-0.3.
|
|
17
|
-
"sourceCommit": "
|
|
14
|
+
"familyBaseVersion": "0.3.8",
|
|
15
|
+
"releaseIdentity": "0.3.8-beta.1013",
|
|
16
|
+
"releaseSetId": "glassly-0.3.8-beta.1013",
|
|
17
|
+
"sourceCommit": "35a6df924b5f3a9f3b15e5f25d4a263c6d0b82ee",
|
|
18
18
|
"otaManifestUrl": null,
|
|
19
19
|
"otaManifestSha256": null
|
|
20
20
|
})
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,14 @@ export type {
|
|
|
64
64
|
LocalLlmModelId,
|
|
65
65
|
} from "./services/LocalLlmModelManager"
|
|
66
66
|
export {DEFAULT_LOCAL_LLM_MODEL, isLocalLlmModelId} from "./services/LocalLlmModelManager"
|
|
67
|
+
export type {
|
|
68
|
+
OfflineActivity,
|
|
69
|
+
OfflineActivityReason,
|
|
70
|
+
OfflineLlmActivity,
|
|
71
|
+
OfflineTranscriptionActivity,
|
|
72
|
+
OfflineTranslationActivity,
|
|
73
|
+
OfflineTranslationPair,
|
|
74
|
+
} from "./services/OfflineActivity"
|
|
67
75
|
|
|
68
76
|
// Shared user memory timeline (engine.memory / Settings > Memory). Entry and
|
|
69
77
|
// session shapes are canonical in @glassly/miniapp (the session.memory
|
|
@@ -231,6 +239,7 @@ export {
|
|
|
231
239
|
} from "./services/neuralBand/MetaAuth"
|
|
232
240
|
export type {
|
|
233
241
|
NeuralBandClaimStatus,
|
|
242
|
+
NeuralBandFirmwareStatus,
|
|
234
243
|
NeuralBandHandwritingStatus,
|
|
235
244
|
NeuralBandHandwritingUpdate,
|
|
236
245
|
} from "./services/neuralBand/NeuralBandInput"
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
RuntimeSnapshot,
|
|
20
20
|
StoreCatalogResponse,
|
|
21
21
|
StoreListing,
|
|
22
|
+
StoreReleasesResponse,
|
|
22
23
|
} from "@glassly/cloud-client/react-native"
|
|
23
24
|
import type {SubjectTokenType} from "@glassly/cloud-client"
|
|
24
25
|
import {AppState, Platform} from "react-native"
|
|
@@ -778,6 +779,13 @@ export const cloudClientService = {
|
|
|
778
779
|
return c.core.store.get(packageName)
|
|
779
780
|
},
|
|
780
781
|
|
|
782
|
+
async getStoreReleases(packageName: string): Promise<StoreReleasesResponse> {
|
|
783
|
+
if (!client) this.init()
|
|
784
|
+
const c = client
|
|
785
|
+
if (!c?.core) throw new Error("cloud client core is unavailable")
|
|
786
|
+
return c.core.store.releases(packageName)
|
|
787
|
+
},
|
|
788
|
+
|
|
781
789
|
/** Bytes of a store icon or screenshot; these routes need the user Bearer. */
|
|
782
790
|
async fetchStoreAsset(url: string): Promise<Uint8Array> {
|
|
783
791
|
if (!client) this.init()
|
|
@@ -245,6 +245,7 @@ export function startDeviceEventRouter(): void {
|
|
|
245
245
|
subs.push(BluetoothSdk.addListener("neural_band_claim", (event) => neuralBandInput.handleClaim(event)))
|
|
246
246
|
subs.push(BluetoothSdk.addListener("neural_band_handwriting", (event) => neuralBandInput.handleHandwriting(event)))
|
|
247
247
|
subs.push(BluetoothSdk.addListener("neural_band_pointer", (event) => neuralBandInput.handlePointer(event)))
|
|
248
|
+
subs.push(BluetoothSdk.addListener("neural_band_firmware", (event) => neuralBandInput.handleFirmware(event)))
|
|
248
249
|
subs.push({
|
|
249
250
|
remove: useGlassesStore.subscribe(
|
|
250
251
|
(s) => s.keyboardConnected === true,
|
|
@@ -4,6 +4,7 @@ import type {DisplayDiagnostic} from "@glassly/miniapp"
|
|
|
4
4
|
// messages, packet bytes, or arbitrary application content to another miniapp.
|
|
5
5
|
const fields = new Set([
|
|
6
6
|
"v",
|
|
7
|
+
"device",
|
|
7
8
|
"run",
|
|
8
9
|
"id",
|
|
9
10
|
"t",
|
|
@@ -72,10 +73,11 @@ export function collectDisplayDiagnostics(
|
|
|
72
73
|
const seen = new Set<string>()
|
|
73
74
|
for (let i = entries.length - 1; i >= 0 && records.length < 180; i--) {
|
|
74
75
|
const message = entries[i].message
|
|
75
|
-
const
|
|
76
|
+
const prefix = message.includes("MEMOMINDPERF ") ? "MEMOMINDPERF " : "G2PERF "
|
|
77
|
+
const offset = message.indexOf(prefix)
|
|
76
78
|
if (offset < 0 || message.length > 8192) continue
|
|
77
79
|
try {
|
|
78
|
-
const raw = JSON.parse(message.slice(offset +
|
|
80
|
+
const raw = JSON.parse(message.slice(offset + prefix.length))
|
|
79
81
|
if (
|
|
80
82
|
!raw ||
|
|
81
83
|
raw.v !== 1 ||
|
|
@@ -627,6 +627,9 @@ class LocalDisplayManager {
|
|
|
627
627
|
this.currentDisplay &&
|
|
628
628
|
this.currentDisplay.packageName === this.backgroundLock.packageName
|
|
629
629
|
if (bgHoldsAndDisplays) {
|
|
630
|
+
// The retained scene is now older than the saved frame; forget it so
|
|
631
|
+
// the restore paints this frame rather than replaying the stale one.
|
|
632
|
+
sceneRenderer.clearApp(packageName)
|
|
630
633
|
resolve?.({status: "blocked", reason: "a background app holds the display"})
|
|
631
634
|
return
|
|
632
635
|
}
|
|
@@ -974,8 +977,8 @@ class LocalDisplayManager {
|
|
|
974
977
|
}, delay)
|
|
975
978
|
}
|
|
976
979
|
} else {
|
|
977
|
-
// Retained state gone (app stopped
|
|
978
|
-
// elements as a fresh scene instead.
|
|
980
|
+
// Retained state gone (app stopped, or its newer frame was blocked) —
|
|
981
|
+
// re-render the saved elements as a fresh scene instead.
|
|
979
982
|
this.sendNow(saved.packageName, {view: "main", scene: saved.scene, durationMs: remaining})
|
|
980
983
|
}
|
|
981
984
|
return
|
|
@@ -57,6 +57,7 @@ import type {AiTier, AiTierProvider, SavedLlmKeys} from "./AiTierResolution"
|
|
|
57
57
|
import {decideLlmRoute, localLlmReason, type LocalLlmReason} from "./LlmRouting"
|
|
58
58
|
import localLlmModelManager from "./LocalLlmModelManager"
|
|
59
59
|
import localLlmProvider from "./LocalLlmProvider"
|
|
60
|
+
import offlineActivity from "./OfflineActivity"
|
|
60
61
|
import {HttpError} from "@glassly/cloud-client"
|
|
61
62
|
import type {
|
|
62
63
|
QuotaStatusPayload,
|
|
@@ -1680,11 +1681,11 @@ class LocalMiniappRuntime {
|
|
|
1680
1681
|
this.handleOpenUrl(packageName, payload)
|
|
1681
1682
|
break
|
|
1682
1683
|
case MiniappRequestType.DISPLAY_DIAGNOSTICS: {
|
|
1683
|
-
// The records are parsed out of the G2PERF lines, which the SDK only logs while
|
|
1684
|
+
// The records are parsed out of the G2PERF lines, which the SDK only logs while perf_logs is on.
|
|
1684
1685
|
const settings = useSettingsStore.getState()
|
|
1685
1686
|
const enabled =
|
|
1686
1687
|
settings.getSetting(SETTINGS.debug_console.key) === true &&
|
|
1687
|
-
settings.getSetting(SETTINGS.
|
|
1688
|
+
settings.getSetting(SETTINGS.perf_logs.key) === true
|
|
1688
1689
|
this.sendResult(packageName, requestId, true, {
|
|
1689
1690
|
enabled,
|
|
1690
1691
|
generatedAt: Date.now(),
|
|
@@ -4343,6 +4344,7 @@ class LocalMiniappRuntime {
|
|
|
4343
4344
|
return
|
|
4344
4345
|
}
|
|
4345
4346
|
|
|
4347
|
+
const endActivity = offlineActivity.beginLlm(reason, localLlmModelManager.model(modelId).label)
|
|
4346
4348
|
try {
|
|
4347
4349
|
// webSearch is a vendor server-side grant; the phone has none.
|
|
4348
4350
|
const {webSearch: _webSearch, ...localRequest} = request
|
|
@@ -4367,6 +4369,8 @@ class LocalMiniappRuntime {
|
|
|
4367
4369
|
undefined,
|
|
4368
4370
|
primaryError ?? {code: MiniappErrorCode.AI_LOCAL_UNAVAILABLE, message},
|
|
4369
4371
|
)
|
|
4372
|
+
} finally {
|
|
4373
|
+
endActivity()
|
|
4370
4374
|
}
|
|
4371
4375
|
}
|
|
4372
4376
|
|
|
@@ -2,6 +2,7 @@ import BluetoothSdk from "@glassly/bluetooth-sdk/internal"
|
|
|
2
2
|
import {ISLAND_SETTINGS_KEYS} from "../runtime/config"
|
|
3
3
|
import {useSettingsStore} from "../stores/settings"
|
|
4
4
|
import {cloudClientService} from "./CloudClientService"
|
|
5
|
+
import offlineActivity from "./OfflineActivity"
|
|
5
6
|
import sttModelManager from "./STTModelManager"
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -223,6 +224,9 @@ class LocalSttFallbackCoordinator {
|
|
|
223
224
|
|
|
224
225
|
private publishReason(): void {
|
|
225
226
|
const reason = this.localActive ? this.currentReason() : ""
|
|
227
|
+
offlineActivity.setTranscription(
|
|
228
|
+
this.localActive ? {reason: this.currentReason(), model: sttModelManager.currentModelLabel()} : null,
|
|
229
|
+
)
|
|
226
230
|
const store = useSettingsStore.getState()
|
|
227
231
|
if (store.getSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason) !== reason) {
|
|
228
232
|
store.setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason, reason)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import offlineActivity, {type ServedTranslationPair} from "./OfflineActivity"
|
|
1
2
|
import {offlineTranslationManager} from "./OfflineTranslationManager"
|
|
2
3
|
|
|
3
4
|
/** One concrete-target `translation:<source>:<target>` stream and who listens to it. */
|
|
@@ -67,6 +68,7 @@ export class LocalTranslationService {
|
|
|
67
68
|
|
|
68
69
|
attach(host: Host): void {
|
|
69
70
|
this.host = host
|
|
71
|
+
offlineActivity.setTranslationSource(() => this.servedPairs())
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
setSubscriptions(subscriptions: LocalTranslationSubscription[]): void {
|
|
@@ -75,6 +77,24 @@ export class LocalTranslationService {
|
|
|
75
77
|
for (const key of this.lanes.keys()) {
|
|
76
78
|
if (!live.has(key.substring(key.indexOf(">") + 1))) this.lanes.delete(key)
|
|
77
79
|
}
|
|
80
|
+
offlineActivity.refresh()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
servedPairs(): ServedTranslationPair[] {
|
|
84
|
+
if (!this.host || !offlineTranslationManager.isSupported()) return []
|
|
85
|
+
const pairs = new Map<string, ServedTranslationPair>()
|
|
86
|
+
let primaryUp: boolean | null = null
|
|
87
|
+
for (const sub of this.subscriptions) {
|
|
88
|
+
if (baseLanguage(sub.source) === baseLanguage(sub.target)) continue
|
|
89
|
+
if (!sub.forceLocal) {
|
|
90
|
+
primaryUp ??= this.host.primaryUp()
|
|
91
|
+
if (primaryUp || !sub.cloud) continue
|
|
92
|
+
}
|
|
93
|
+
const key = `${sub.source}>${sub.target}`
|
|
94
|
+
const existing = pairs.get(key)
|
|
95
|
+
pairs.set(key, {source: sub.source, target: sub.target, forceLocal: sub.forceLocal || !!existing?.forceLocal})
|
|
96
|
+
}
|
|
97
|
+
return [...pairs.values()]
|
|
78
98
|
}
|
|
79
99
|
|
|
80
100
|
onLocalTranscript(transcript: LocalTranscript, sourceLanguage: string): void {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the phone is running on-device right now (transcription, translation,
|
|
3
|
+
* LLM) and why, for the home offline card. Producers push in; it imports
|
|
4
|
+
* nothing at runtime so their test stubs stay unaffected.
|
|
5
|
+
*/
|
|
6
|
+
import type {LocalLlmReason} from "./LlmRouting"
|
|
7
|
+
import type {LocalSttFallbackReason} from "./LocalSttFallbackCoordinator"
|
|
8
|
+
|
|
9
|
+
export type OfflineActivityReason = LocalSttFallbackReason | LocalLlmReason
|
|
10
|
+
|
|
11
|
+
export interface OfflineTranscriptionActivity {
|
|
12
|
+
reason: LocalSttFallbackReason
|
|
13
|
+
model: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface OfflineTranslationPair {
|
|
17
|
+
source: string
|
|
18
|
+
target: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OfflineTranslationActivity {
|
|
22
|
+
reason: OfflineActivityReason
|
|
23
|
+
pairs: OfflineTranslationPair[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OfflineLlmActivity {
|
|
27
|
+
reason: LocalLlmReason
|
|
28
|
+
model: string
|
|
29
|
+
requests: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OfflineActivity {
|
|
33
|
+
transcription: OfflineTranscriptionActivity | null
|
|
34
|
+
translation: OfflineTranslationActivity | null
|
|
35
|
+
llm: OfflineLlmActivity | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ServedTranslationPair extends OfflineTranslationPair {
|
|
39
|
+
forceLocal: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const EMPTY: OfflineActivity = {transcription: null, translation: null, llm: null}
|
|
43
|
+
|
|
44
|
+
class OfflineActivityTracker {
|
|
45
|
+
private transcription: OfflineTranscriptionActivity | null = null
|
|
46
|
+
private llmRuns: Array<{reason: LocalLlmReason; model: string}> = []
|
|
47
|
+
private translationSource: (() => ServedTranslationPair[]) | null = null
|
|
48
|
+
private snapshot: OfflineActivity = EMPTY
|
|
49
|
+
private snapshotKey = JSON.stringify(EMPTY)
|
|
50
|
+
private listeners = new Set<(activity: OfflineActivity) => void>()
|
|
51
|
+
|
|
52
|
+
current(): OfflineActivity {
|
|
53
|
+
return this.snapshot
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
subscribe(listener: (activity: OfflineActivity) => void): () => void {
|
|
57
|
+
this.listeners.add(listener)
|
|
58
|
+
return () => {
|
|
59
|
+
this.listeners.delete(listener)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setTranscription(transcription: OfflineTranscriptionActivity | null): void {
|
|
64
|
+
this.transcription = transcription
|
|
65
|
+
this.refresh()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
beginLlm(reason: LocalLlmReason, model: string): () => void {
|
|
69
|
+
const run = {reason, model}
|
|
70
|
+
this.llmRuns.push(run)
|
|
71
|
+
this.refresh()
|
|
72
|
+
let ended = false
|
|
73
|
+
return () => {
|
|
74
|
+
if (ended) return
|
|
75
|
+
ended = true
|
|
76
|
+
this.llmRuns = this.llmRuns.filter((r) => r !== run)
|
|
77
|
+
this.refresh()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setTranslationSource(source: (() => ServedTranslationPair[]) | null): void {
|
|
82
|
+
this.translationSource = source
|
|
83
|
+
this.refresh()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
refresh(): void {
|
|
87
|
+
const next: OfflineActivity = {
|
|
88
|
+
transcription: this.transcription,
|
|
89
|
+
translation: this.translation(),
|
|
90
|
+
llm: this.llm(),
|
|
91
|
+
}
|
|
92
|
+
const key = JSON.stringify(next)
|
|
93
|
+
if (key === this.snapshotKey) return
|
|
94
|
+
this.snapshotKey = key
|
|
95
|
+
this.snapshot = next
|
|
96
|
+
for (const listener of this.listeners) {
|
|
97
|
+
try {
|
|
98
|
+
listener(next)
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn("OfflineActivity: listener failed:", error)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
reset(): void {
|
|
106
|
+
this.transcription = null
|
|
107
|
+
this.llmRuns = []
|
|
108
|
+
this.translationSource = null
|
|
109
|
+
this.refresh()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private translation(): OfflineTranslationActivity | null {
|
|
113
|
+
if (!this.transcription || !this.translationSource) return null
|
|
114
|
+
const pairs = this.translationSource()
|
|
115
|
+
if (pairs.length === 0) return null
|
|
116
|
+
return {
|
|
117
|
+
reason: pairs.some((p) => p.forceLocal) ? "force_local" : this.transcription.reason,
|
|
118
|
+
pairs: pairs.map(({source, target}) => ({source, target})),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private llm(): OfflineLlmActivity | null {
|
|
123
|
+
const latest = this.llmRuns[this.llmRuns.length - 1]
|
|
124
|
+
if (!latest) return null
|
|
125
|
+
return {reason: latest.reason, model: latest.model, requests: this.llmRuns.length}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export {OfflineActivityTracker}
|
|
130
|
+
const offlineActivity = new OfflineActivityTracker()
|
|
131
|
+
export default offlineActivity
|
|
@@ -228,6 +228,12 @@ class STTModelManager {
|
|
|
228
228
|
return this.currentLanguage
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
currentModelLabel(): string {
|
|
232
|
+
const language = this.config(this.currentLanguage)
|
|
233
|
+
if (!language) return this.currentLanguage
|
|
234
|
+
return language.type === "apple" ? `Apple · ${language.displayName}` : language.displayName
|
|
235
|
+
}
|
|
236
|
+
|
|
231
237
|
setCurrentLanguage(code: string): void {
|
|
232
238
|
if (this.config(code)) {
|
|
233
239
|
this.currentLanguage = code
|
|
@@ -55,6 +55,8 @@ const INPUT_SURFACES: Readonly<Record<string, readonly InputSurface[]>> = {
|
|
|
55
55
|
[ControllerTypes.WEAR_OS]: [WATCH_SURFACE],
|
|
56
56
|
[ControllerTypes.GAMEBRICK]: [GAMEBRICK_SURFACE],
|
|
57
57
|
[ControllerTypes.IINE_ANANKE]: [GAMEBRICK_SURFACE],
|
|
58
|
+
[ControllerTypes.ZERO2]: [GAMEBRICK_SURFACE],
|
|
59
|
+
[ControllerTypes.MICRO]: [GAMEBRICK_SURFACE],
|
|
58
60
|
[ControllerTypes.R1]: [],
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -61,6 +61,8 @@ function glassesCapabilities(model: DeviceTypes): Capabilities {
|
|
|
61
61
|
// Glassly CFW supports the complete vector contract, including SVG and rotation.
|
|
62
62
|
// Upstream g2flash publishes a raster canvas with sceneVector=false.
|
|
63
63
|
const vector = useGlassesStore.getState().sceneVector === true
|
|
64
|
+
// Depth is a disparity between the lenses; one panel (MemoMind One) draws every element flat.
|
|
65
|
+
const stereo = (base.display.count ?? 1) >= 2
|
|
64
66
|
return {
|
|
65
67
|
...(volumeGate ? withVolumeGate(base) : base),
|
|
66
68
|
hasNativeNavigation: resolveNativeNavigation(base.hasNativeNavigation, true, forceNativeNavigationOnCfw()),
|
|
@@ -81,7 +83,7 @@ function glassesCapabilities(model: DeviceTypes): Capabilities {
|
|
|
81
83
|
vector ? CFW_VECTOR_MAX_ELEMENTS : CFW_MAX_IMAGE_ELEMENTS,
|
|
82
84
|
),
|
|
83
85
|
...(vector
|
|
84
|
-
? {shapes: [...ALL_DISPLAY_SHAPES, "svg" as const], animation: true, rotation: true, depth:
|
|
86
|
+
? {shapes: [...ALL_DISPLAY_SHAPES, "svg" as const], animation: true, rotation: true, depth: stereo}
|
|
85
87
|
: {}),
|
|
86
88
|
},
|
|
87
89
|
}
|
|
@@ -6,7 +6,12 @@
|
|
|
6
6
|
* latest claim is also kept on its own, since the pairing screen runs before
|
|
7
7
|
* the band's id reaches JS.
|
|
8
8
|
*/
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
NeuralBandClaimEvent,
|
|
11
|
+
NeuralBandFirmwareEvent,
|
|
12
|
+
NeuralBandHandwritingEvent,
|
|
13
|
+
NeuralBandPointerEvent,
|
|
14
|
+
} from "@glassly/bluetooth-sdk"
|
|
10
15
|
import {MiniappStreamType} from "@glassly/miniapp"
|
|
11
16
|
|
|
12
17
|
import type {KeyboardEventPayload} from "../GlassesKeyboardInput"
|
|
@@ -34,6 +39,8 @@ export interface NeuralBandHandwritingUpdate {
|
|
|
34
39
|
status: NeuralBandHandwritingStatus
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
export type NeuralBandFirmwareStatus = Omit<NeuralBandFirmwareEvent, "type" | "controllerId"> & {known: boolean}
|
|
43
|
+
|
|
37
44
|
export interface NeuralBandInputOptions {
|
|
38
45
|
forward?: (stream: string, payload: unknown) => void
|
|
39
46
|
now?: () => number
|
|
@@ -67,13 +74,25 @@ function codeFor(char: string): string {
|
|
|
67
74
|
|
|
68
75
|
const IDLE_CLAIM: NeuralBandClaimStatus = {stage: "idle", sessionExpired: false}
|
|
69
76
|
const IDLE_HANDWRITING: NeuralBandHandwritingStatus = {phase: "idle", message: "", text: ""}
|
|
77
|
+
const IDLE_FIRMWARE: NeuralBandFirmwareStatus = {
|
|
78
|
+
known: false,
|
|
79
|
+
connected: false,
|
|
80
|
+
hasMetaSession: false,
|
|
81
|
+
checked: false,
|
|
82
|
+
updateAvailable: false,
|
|
83
|
+
phase: "idle",
|
|
84
|
+
progress: 0,
|
|
85
|
+
message: "",
|
|
86
|
+
}
|
|
70
87
|
|
|
71
88
|
export class NeuralBandInput {
|
|
72
89
|
private latestClaim: NeuralBandClaimStatus = IDLE_CLAIM
|
|
73
90
|
private claims = new Map<string, NeuralBandClaimStatus>()
|
|
74
91
|
private handwriting = new Map<string, NeuralBandHandwritingStatus>()
|
|
92
|
+
private firmware = new Map<string, NeuralBandFirmwareStatus>()
|
|
75
93
|
private claimListeners = new Set<(status: NeuralBandClaimStatus) => void>()
|
|
76
94
|
private handwritingListeners = new Set<(update: NeuralBandHandwritingUpdate) => void>()
|
|
95
|
+
private firmwareListeners = new Set<(update: {id: string; status: NeuralBandFirmwareStatus}) => void>()
|
|
77
96
|
private readonly forward: (stream: string, payload: unknown) => void
|
|
78
97
|
private readonly now: () => number
|
|
79
98
|
|
|
@@ -102,6 +121,15 @@ export class NeuralBandInput {
|
|
|
102
121
|
return () => this.handwritingListeners.delete(cb)
|
|
103
122
|
}
|
|
104
123
|
|
|
124
|
+
firmwareStatus(controllerId: string): NeuralBandFirmwareStatus {
|
|
125
|
+
return this.firmware.get(controllerId) ?? IDLE_FIRMWARE
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onFirmwareStatus(cb: (update: {id: string; status: NeuralBandFirmwareStatus}) => void): () => void {
|
|
129
|
+
this.firmwareListeners.add(cb)
|
|
130
|
+
return () => this.firmwareListeners.delete(cb)
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
/** A new pairing attempt starts from a clean slate. */
|
|
106
134
|
resetClaim(): void {
|
|
107
135
|
this.latestClaim = IDLE_CLAIM
|
|
@@ -112,6 +140,14 @@ export class NeuralBandInput {
|
|
|
112
140
|
forget(controllerId: string): void {
|
|
113
141
|
this.claims.delete(controllerId)
|
|
114
142
|
this.handwriting.delete(controllerId)
|
|
143
|
+
this.firmware.delete(controllerId)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleFirmware(event: NeuralBandFirmwareEvent): void {
|
|
147
|
+
const {type: _type, controllerId, ...rest} = event
|
|
148
|
+
const status: NeuralBandFirmwareStatus = {...rest, known: true}
|
|
149
|
+
this.firmware.set(controllerId, status)
|
|
150
|
+
for (const cb of this.firmwareListeners) cb({id: controllerId, status})
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
handleClaim(event: NeuralBandClaimEvent): void {
|
|
@@ -32,6 +32,13 @@ function pushHand(id: string): void {
|
|
|
32
32
|
)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function pushVibration(id: string): void {
|
|
36
|
+
const enabled = useSettingsStore.getState().getSetting(SETTINGS.neural_band_vibration.key) !== false
|
|
37
|
+
void BluetoothSdk.neuralBandSetGestureHaptics(id, enabled).catch((err) =>
|
|
38
|
+
console.warn("NeuralBandSettingsSync: vibration push failed:", err),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
export function startNeuralBandSettingsSync(): void {
|
|
36
43
|
if (unsubscribers.length > 0) return
|
|
37
44
|
let connected = new Set(connectedBandIds())
|
|
@@ -44,6 +51,7 @@ export function startNeuralBandSettingsSync(): void {
|
|
|
44
51
|
if (connected.has(id)) continue
|
|
45
52
|
pushAirMouse(id)
|
|
46
53
|
pushHand(id)
|
|
54
|
+
pushVibration(id)
|
|
47
55
|
}
|
|
48
56
|
connected = next
|
|
49
57
|
},
|
|
@@ -56,6 +64,10 @@ export function startNeuralBandSettingsSync(): void {
|
|
|
56
64
|
(s) => s.getSetting(SETTINGS.neural_band_hand.key),
|
|
57
65
|
() => connectedBandIds().forEach(pushHand),
|
|
58
66
|
),
|
|
67
|
+
useSettingsStore.subscribe(
|
|
68
|
+
(s) => s.getSetting(SETTINGS.neural_band_vibration.key),
|
|
69
|
+
() => connectedBandIds().forEach(pushVibration),
|
|
70
|
+
),
|
|
59
71
|
]
|
|
60
72
|
}
|
|
61
73
|
|