@glassly/engine 0.3.8-dev.996 → 0.3.8-dev.998

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/build/engine.d.ts +2 -0
  2. package/build/engine.d.ts.map +1 -1
  3. package/build/facades/ai.d.ts +3 -0
  4. package/build/facades/ai.d.ts.map +1 -1
  5. package/build/facades/ai.js +3 -0
  6. package/build/facades/ai.js.map +1 -1
  7. package/build/generated/releaseMetadata.js +3 -3
  8. package/build/generated/releaseMetadata.js.map +1 -1
  9. package/build/index.d.ts +1 -0
  10. package/build/index.d.ts.map +1 -1
  11. package/build/index.js.map +1 -1
  12. package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
  13. package/build/services/LocalMiniappRuntime.js +5 -0
  14. package/build/services/LocalMiniappRuntime.js.map +1 -1
  15. package/build/services/LocalSttFallbackCoordinator.d.ts.map +1 -1
  16. package/build/services/LocalSttFallbackCoordinator.js +2 -0
  17. package/build/services/LocalSttFallbackCoordinator.js.map +1 -1
  18. package/build/services/LocalTranslationService.d.ts +2 -0
  19. package/build/services/LocalTranslationService.d.ts.map +1 -1
  20. package/build/services/LocalTranslationService.js +22 -0
  21. package/build/services/LocalTranslationService.js.map +1 -1
  22. package/build/services/OfflineActivity.d.ts +54 -0
  23. package/build/services/OfflineActivity.d.ts.map +1 -0
  24. package/build/services/OfflineActivity.js +86 -0
  25. package/build/services/OfflineActivity.js.map +1 -0
  26. package/build/services/STTModelManager.d.ts +1 -0
  27. package/build/services/STTModelManager.d.ts.map +1 -1
  28. package/build/services/STTModelManager.js +6 -0
  29. package/build/services/STTModelManager.js.map +1 -1
  30. package/build/services/effectiveCapabilities.js +3 -1
  31. package/build/services/effectiveCapabilities.js.map +1 -1
  32. package/build/stores/settings.d.ts.map +1 -1
  33. package/build/stores/settings.js +7 -0
  34. package/build/stores/settings.js.map +1 -1
  35. package/build/types/capabilities/memomind-one.d.ts.map +1 -1
  36. package/build/types/capabilities/memomind-one.js +1 -0
  37. package/build/types/capabilities/memomind-one.js.map +1 -1
  38. package/build/types/capabilities/rayneo-io.d.ts.map +1 -1
  39. package/build/types/capabilities/rayneo-io.js +3 -2
  40. package/build/types/capabilities/rayneo-io.js.map +1 -1
  41. package/package.json +7 -7
  42. package/src/facades/ai.ts +4 -0
  43. package/src/generated/releaseMetadata.ts +3 -3
  44. package/src/index.ts +8 -0
  45. package/src/services/LocalMiniappRuntime.ts +4 -0
  46. package/src/services/LocalSttFallbackCoordinator.ts +4 -0
  47. package/src/services/LocalTranslationService.ts +20 -0
  48. package/src/services/OfflineActivity.ts +131 -0
  49. package/src/services/STTModelManager.ts +6 -0
  50. package/src/services/effectiveCapabilities.ts +3 -1
  51. package/src/stores/settings.ts +7 -0
  52. package/src/types/capabilities/memomind-one.ts +1 -0
  53. package/src/types/capabilities/rayneo-io.ts +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/engine",
3
- "version": "0.3.8-dev.996",
3
+ "version": "0.3.8-dev.998",
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.8-dev.996",
101
- "@glassly/cloud-client": "0.3.8-dev.996",
102
- "@glassly/cloud-protocol": "0.3.8-dev.996",
103
- "@glassly/crust": "0.3.8-dev.996",
104
- "@glassly/miniapp": "0.3.8-dev.996",
100
+ "@glassly/bluetooth-sdk": "0.3.8-dev.998",
101
+ "@glassly/cloud-client": "0.3.8-dev.998",
102
+ "@glassly/cloud-protocol": "0.3.8-dev.998",
103
+ "@glassly/crust": "0.3.8-dev.998",
104
+ "@glassly/miniapp": "0.3.8-dev.998",
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.8-dev.996",
110
+ "@glassly/llm-providers": "0.3.8-dev.998",
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
  }
@@ -12,9 +12,9 @@ export interface EngineReleaseMetadata {
12
12
  export const ENGINE_RELEASE_METADATA: Readonly<EngineReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "0.3.8",
15
- "releaseIdentity": "0.3.8-dev.996",
16
- "releaseSetId": "glassly-0.3.8-dev.996",
17
- "sourceCommit": "22488936029211136c68c221ab326db85360d8ef",
15
+ "releaseIdentity": "0.3.8-dev.998",
16
+ "releaseSetId": "glassly-0.3.8-dev.998",
17
+ "sourceCommit": "56392e6a51a274b34157ef34792183652af28ebe",
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
@@ -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,
@@ -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
@@ -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: true}
86
+ ? {shapes: [...ALL_DISPLAY_SHAPES, "svg" as const], animation: true, rotation: true, depth: stereo}
85
87
  : {}),
86
88
  },
87
89
  }
@@ -930,6 +930,13 @@ export const SETTINGS: Record<string, Setting> = {
930
930
  saveOnServer: false,
931
931
  persist: true,
932
932
  },
933
+ home_offline_card: {
934
+ key: "home_offline_card",
935
+ defaultValue: () => true,
936
+ writable: true,
937
+ saveOnServer: false,
938
+ persist: true,
939
+ },
933
940
  // Gesture-dots debug indicator: flash a glyph on the glasses when a touch
934
941
  // gesture is received while the display is empty. Works on any
935
942
  // scene-capable device, not only CFW. Device-local debug knob, default off.
@@ -6,6 +6,7 @@ export const memoMindOne: Capabilities = {
6
6
  camera: null,
7
7
  hasDisplay: true,
8
8
  display: {
9
+ // One panel: the vector canvas offers no depth on it.
9
10
  count: 1,
10
11
  isColor: false,
11
12
  canDisplayBitmap: true,
@@ -19,10 +19,11 @@ export const rayneoIO: Capabilities = {
19
19
  hasCamera: false,
20
20
  camera: null,
21
21
 
22
- // Display capabilities - single monochrome (1-bit) panel
22
+ // Display capabilities - a monochrome panel per eye; the Glassly CFW keeps
23
+ // a scene per lens, so element depth is a real disparity here
23
24
  hasDisplay: true,
24
25
  display: {
25
- count: 1,
26
+ count: 2,
26
27
  isColor: false,
27
28
  color: "green",
28
29
  // Bitmaps and positioned elements need the Glassly CFW; stock firmware