@glassly/engine 0.3.8-dev.997 → 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.
- package/build/engine.d.ts +2 -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/generated/releaseMetadata.js +3 -3
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +5 -0
- 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/effectiveCapabilities.js +3 -1
- package/build/services/effectiveCapabilities.js.map +1 -1
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +7 -0
- 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/package.json +7 -7
- package/src/facades/ai.ts +4 -0
- package/src/generated/releaseMetadata.ts +3 -3
- package/src/index.ts +8 -0
- package/src/services/LocalMiniappRuntime.ts +4 -0
- 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/effectiveCapabilities.ts +3 -1
- package/src/stores/settings.ts +7 -0
- package/src/types/capabilities/memomind-one.ts +1 -0
- package/src/types/capabilities/rayneo-io.ts +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalSttFallbackCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/LocalSttFallbackCoordinator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalSttFallbackCoordinator.d.ts","sourceRoot":"","sources":["../../src/services/LocalSttFallbackCoordinator.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;GAeG;AACH;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAC9B,aAAa,GACb,oBAAoB,GACpB,gBAAgB,GAChB,WAAW,GACX,sBAAsB,CAAA;AAE1B,cAAM,2BAA2B;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA6B;IAEpD,OAAO,CAAC,4BAA4B,CAAQ;IAC5C,OAAO,CAAC,yBAAyB,CAAQ;IACzC,oFAAoF;IACpF,OAAO,CAAC,uBAAuB,CAAQ;IACvC,OAAO,CAAC,cAAc,CAAsB;IAC5C;;;;;OAKG;IACH,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,oBAAoB,CAAQ;IACpC;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB,CAAQ;IACnC,OAAO,CAAC,mBAAmB,CAAO;IAClC;;;;;OAKG;IACH,OAAO,CAAC,eAAe,CAAQ;IAE/B,OAAO;YAYO,oBAAoB;IAQlC;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IAajC,MAAM,CAAC,WAAW,IAAI,2BAA2B;IAOjD,QAAQ,IAAI,OAAO;IAInB,iBAAiB,IAAI,MAAM,GAAG,IAAI;IAIlC,mEAAmE;IACnE,SAAS,IAAI,sBAAsB,GAAG,IAAI;IAI1C,oBAAoB,CAClB,gBAAgB,EAAE,OAAO,EACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,aAAa,UAAQ,EACrB,eAAe,UAAO,GACrB,IAAI;IAWP;;;;;;OAMG;IACH,iBAAiB,IAAI,IAAI;IAEzB;;;;OAIG;IACG,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9D,iBAAiB,IAAI,OAAO;IAI5B,uEAAuE;IACvE,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;IAQnE;2CACuC;IACvC,OAAO,CAAC,SAAS;YAWH,SAAS;IAmBvB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;YAWP,aAAa;YASb,gBAAgB;IAqB9B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,GAAG;CAGZ;AAED,QAAA,MAAM,2BAA2B,6BAA4C,CAAA;AAC7E,eAAe,2BAA2B,CAAA"}
|
|
@@ -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
|
class LocalSttFallbackCoordinator {
|
|
7
8
|
static instance;
|
|
@@ -180,6 +181,7 @@ class LocalSttFallbackCoordinator {
|
|
|
180
181
|
}
|
|
181
182
|
publishReason() {
|
|
182
183
|
const reason = this.localActive ? this.currentReason() : "";
|
|
184
|
+
offlineActivity.setTranscription(this.localActive ? { reason: this.currentReason(), model: sttModelManager.currentModelLabel() } : null);
|
|
183
185
|
const store = useSettingsStore.getState();
|
|
184
186
|
if (store.getSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason) !== reason) {
|
|
185
187
|
store.setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason, reason);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalSttFallbackCoordinator.js","sourceRoot":"","sources":["../../src/services/LocalSttFallbackCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {ISLAND_SETTINGS_KEYS} from \"../runtime/config\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {cloudClientService} from \"./CloudClientService\"\nimport sttModelManager from \"./STTModelManager\"\n\n/**\n * Decides whether on-device Sherpa STT should produce transcripts for local\n * miniapps, instead of (or in addition to) cloud transcription. Local STT is\n * active when the cloud is unavailable (disconnected or refusing STT for plan\n * quota) and some subscriber accepts fallback text, or any subscription\n * requires `forceLocal`.\n *\n * Activation flips `localSttFallbackActive` in the settings store, which\n * the bluetooth-sdk native side reads to decide whether to feed PCM into\n * the SherpaOnnxTranscriber. Local transcripts then flow back through the\n * `local_transcription` Bridge event; the host wires that event to\n * `localMiniappRuntime.forwardEvent`, with this coordinator providing the\n * subscription-active gate so we never publish transcripts to miniapps that\n * aren't asking for them. LocalMiniappRuntime filters mixed local/cloud\n * delivery per miniapp.\n */\n/**\n * Why local STT is currently active, mirrored into the\n * `local_stt_fallback_reason` setting (\"\" while inactive) so UI can show it.\n * When several causes hold at once, the primary-path outage wins over\n * `force_local` (a forced subscription would keep local STT on either way).\n */\nexport type LocalSttFallbackReason =\n | \"force_local\"\n | \"cloud_disconnected\"\n | \"cloud_disabled\"\n | \"stt_quota\"\n | \"provider_unreachable\"\n\nclass LocalSttFallbackCoordinator {\n private static instance: LocalSttFallbackCoordinator\n\n private hasTranscriptionSubscription = false\n private hasForceLocalSubscription = false\n /** Some default-route subscriber takes on-device text while the primary is down. */\n private hasFallbackSubscription = false\n private activeLanguage: string | null = null\n /**\n * Default to \"cloud is up\" so we never accidentally activate local STT before\n * the cloud client has had a chance to start. We attach lazily on the first\n * subscription/reconcile pass; once attached, this field reflects the real\n * cloud-client runtime status.\n */\n private cloudConnected = true\n private localActive = false\n private starting: Promise<void> | null = null\n private cloudAdapterAttached = false\n /**\n * Soniox-direct (user API key) state, pushed by SonioxDirectService. While\n * enabled, IT is the primary transcription path — cloud connectivity is\n * irrelevant and sherpa engages on Soniox health instead. Defaults healthy\n * for the same reason cloudConnected defaults true: never activate local STT\n * before the primary has had a chance to come up.\n */\n private sonioxDirectEnabled = false\n private sonioxDirectHealthy = true\n /**\n * Cloud STT refused for plan quota (`stream.quota` blocked=true). The cloud\n * connection stays up, so this is tracked separately from `cloudConnected`:\n * for transcription purposes the cloud path is down, and sherpa takes over\n * if its model is on-device. Soniox-direct is unmetered and unaffected.\n */\n private sttQuotaBlocked = false\n\n private constructor() {\n // Reset the persisted mirror flag on boot — the in-memory state in this\n // coordinator is the source of truth, and a stale \"true\" left from the\n // previous session would cause native to feed Sherpa before any miniapp\n // registered a subscription.\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackActive, false)\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason, \"\")\n sttModelManager.onModelReady(() => {\n void this.retryAfterModelReady()\n })\n }\n\n private async retryAfterModelReady(): Promise<void> {\n // A model can finish downloading while the initial availability check is\n // still pending. Wait for that attempt before retrying so readiness is\n // never lost and native restarts cannot overlap.\n await this.starting\n await this.reconcile()\n }\n\n /**\n * Lazy-attach the engine cloud connection listener. The coordinator is\n * constructed at module load, before engine.start() brings up the cloud\n * client, so we defer until the first reconcile.\n */\n private attachCloudAdapterIfReady(): void {\n if (this.cloudAdapterAttached) return\n this.cloudConnected = cloudClientService.isConnected()\n cloudClientService.onStatusChanged((status) => {\n const connected = status.status === \"connected\"\n if (this.cloudConnected === connected) return\n this.log(`cloud connection -> ${connected ? \"up\" : \"down\"}`)\n this.cloudConnected = connected\n void this.reconcile()\n })\n this.cloudAdapterAttached = true\n }\n\n static getInstance(): LocalSttFallbackCoordinator {\n if (!LocalSttFallbackCoordinator.instance) {\n LocalSttFallbackCoordinator.instance = new LocalSttFallbackCoordinator()\n }\n return LocalSttFallbackCoordinator.instance\n }\n\n isActive(): boolean {\n return this.localActive\n }\n\n getActiveLanguage(): string | null {\n return this.activeLanguage\n }\n\n /** The cause of the current activation, or null while inactive. */\n getReason(): LocalSttFallbackReason | null {\n return this.localActive ? this.currentReason() : null\n }\n\n onSubscriptionChange(\n hasTranscription: boolean,\n language: string | null,\n hasForceLocal = false,\n acceptsFallback = true,\n ): void {\n this.log(\n `onSubscriptionChange(hasTx=${hasTranscription}, lang=${language}, forceLocal=${hasForceLocal}, fallback=${acceptsFallback})`,\n )\n this.hasTranscriptionSubscription = hasTranscription\n this.hasForceLocalSubscription = hasTranscription && hasForceLocal\n this.hasFallbackSubscription = hasTranscription && acceptsFallback\n this.activeLanguage = hasTranscription ? language : null\n void this.reconcile()\n }\n\n /**\n * Informational hook, retained for API stability. No longer called: the\n * authoritative switching signal is the cloud-client adapter (local miniapps\n * are powered only by V2). v1 cloud transcripts deliberately do NOT drive this\n * coordinator anymore. Kept for future hysteresis if connection-state alone\n * proves too coarse.\n */\n onCloudTranscript(): void {}\n\n /**\n * Pushed when the cloud announces/clears an STT quota block. Awaitable so\n * the caller can inspect `isActive()` afterwards to phrase its user notice\n * (\"continuing on-device\" vs \"transcription paused\").\n */\n async onSttQuotaBlockedChange(blocked: boolean): Promise<void> {\n if (this.sttQuotaBlocked === blocked) return\n this.log(`stt quota -> ${blocked ? \"blocked\" : \"restored\"}`)\n this.sttQuotaBlocked = blocked\n await this.reconcile()\n }\n\n isSttQuotaBlocked(): boolean {\n return this.sttQuotaBlocked\n }\n\n /** Pushed by SonioxDirectService on every enable/health transition. */\n onSonioxDirectStateChange(enabled: boolean, healthy: boolean): void {\n if (this.sonioxDirectEnabled === enabled && this.sonioxDirectHealthy === healthy) return\n this.log(`soniox-direct -> enabled=${enabled} healthy=${healthy}`)\n this.sonioxDirectEnabled = enabled\n this.sonioxDirectHealthy = healthy\n void this.reconcile()\n }\n\n /** Whether the primary transcription path (Soniox-direct when a user key is\n * set, the cloud otherwise) is up. */\n private primaryUp(): boolean {\n // Cloud turned off in Privacy settings: the cloud path is not merely down,\n // it is never coming back, so on-device STT is the primary. Checked ahead\n // of `cloudConnected` because that field defaults to true (so local STT\n // doesn't pre-empt cloud at boot) and would otherwise report a path that\n // will never connect as up. Soniox-direct is a user-supplied third-party\n // key, not Glassly cloud, so it stays available.\n if (!this.sonioxDirectEnabled && cloudClientService.isCloudDisabled()) return false\n return this.sonioxDirectEnabled ? this.sonioxDirectHealthy : this.cloudConnected && !this.sttQuotaBlocked\n }\n\n private async reconcile(): Promise<void> {\n this.attachCloudAdapterIfReady()\n const shouldBeActive = this.shouldUseLocalStt()\n if (shouldBeActive && !this.localActive) {\n if (!this.starting) {\n this.starting = this.startLocalStt().finally(() => {\n this.starting = null\n })\n }\n await this.starting\n } else if (!shouldBeActive && this.localActive) {\n this.stopLocalStt(this.hasTranscriptionSubscription ? \"primary transcription recovered\" : \"subscription gone\")\n } else if (shouldBeActive && this.localActive) {\n // Still active, but the cause may have shifted (e.g. quota block landing\n // while already offline for a disconnect) — keep the mirror current.\n this.publishReason()\n }\n }\n\n /**\n * Ranks the concurrent causes: a dead-forever cloud path first, then the\n * primary provider's own failure, then transient outages, with `force_local`\n * only when the primary is actually up. Mirrors `primaryUp()`.\n */\n private currentReason(): LocalSttFallbackReason {\n if (!this.sonioxDirectEnabled && cloudClientService.isCloudDisabled()) return \"cloud_disabled\"\n if (this.sonioxDirectEnabled && !this.sonioxDirectHealthy) return \"provider_unreachable\"\n if (!this.sonioxDirectEnabled && !this.cloudConnected) return \"cloud_disconnected\"\n if (!this.sonioxDirectEnabled && this.sttQuotaBlocked) return \"stt_quota\"\n return \"force_local\"\n }\n\n private publishReason(): void {\n const reason = this.localActive ? this.currentReason() : \"\"\n const store = useSettingsStore.getState()\n if (store.getSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason) !== reason) {\n store.setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason, reason)\n }\n }\n\n private async startLocalStt(): Promise<void> {\n this.log(\"starting local stt\")\n try {\n await this.activateLocalStt()\n } catch (err) {\n this.log(`local stt startup failed: ${err}`)\n }\n }\n\n private async activateLocalStt(): Promise<void> {\n const modelAvailable = await sttModelManager.isModelAvailable()\n if (!modelAvailable) {\n this.log(\"local stt model is not available yet — skipping activation\")\n return\n }\n if (!this.shouldUseLocalStt()) {\n this.log(\"local stt activation skipped: routing changed before start completed\")\n return\n }\n // Native rejects unless the recognizer and its processing loop are ready.\n await BluetoothSdk.restartTranscriber()\n if (!this.shouldUseLocalStt()) {\n this.log(\"local stt activation skipped: routing changed during transcriber restart\")\n return\n }\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackActive, true)\n this.localActive = true\n this.publishReason()\n }\n\n private stopLocalStt(reason: string): void {\n this.log(`stopping local stt: ${reason}`)\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackActive, false)\n this.localActive = false\n this.publishReason()\n }\n\n private shouldUseLocalStt(): boolean {\n return (\n this.hasTranscriptionSubscription &&\n (this.hasForceLocalSubscription || (this.hasFallbackSubscription && !this.primaryUp()))\n )\n }\n\n private log(msg: string): void {\n console.log(`[LocalSttFallback] ${msg}`)\n }\n}\n\nconst localSttFallbackCoordinator = LocalSttFallbackCoordinator.getInstance()\nexport default localSttFallbackCoordinator\n"]}
|
|
1
|
+
{"version":3,"file":"LocalSttFallbackCoordinator.js","sourceRoot":"","sources":["../../src/services/LocalSttFallbackCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["import BluetoothSdk from \"@glassly/bluetooth-sdk/internal\"\nimport {ISLAND_SETTINGS_KEYS} from \"../runtime/config\"\nimport {useSettingsStore} from \"../stores/settings\"\nimport {cloudClientService} from \"./CloudClientService\"\nimport offlineActivity from \"./OfflineActivity\"\nimport sttModelManager from \"./STTModelManager\"\n\n/**\n * Decides whether on-device Sherpa STT should produce transcripts for local\n * miniapps, instead of (or in addition to) cloud transcription. Local STT is\n * active when the cloud is unavailable (disconnected or refusing STT for plan\n * quota) and some subscriber accepts fallback text, or any subscription\n * requires `forceLocal`.\n *\n * Activation flips `localSttFallbackActive` in the settings store, which\n * the bluetooth-sdk native side reads to decide whether to feed PCM into\n * the SherpaOnnxTranscriber. Local transcripts then flow back through the\n * `local_transcription` Bridge event; the host wires that event to\n * `localMiniappRuntime.forwardEvent`, with this coordinator providing the\n * subscription-active gate so we never publish transcripts to miniapps that\n * aren't asking for them. LocalMiniappRuntime filters mixed local/cloud\n * delivery per miniapp.\n */\n/**\n * Why local STT is currently active, mirrored into the\n * `local_stt_fallback_reason` setting (\"\" while inactive) so UI can show it.\n * When several causes hold at once, the primary-path outage wins over\n * `force_local` (a forced subscription would keep local STT on either way).\n */\nexport type LocalSttFallbackReason =\n | \"force_local\"\n | \"cloud_disconnected\"\n | \"cloud_disabled\"\n | \"stt_quota\"\n | \"provider_unreachable\"\n\nclass LocalSttFallbackCoordinator {\n private static instance: LocalSttFallbackCoordinator\n\n private hasTranscriptionSubscription = false\n private hasForceLocalSubscription = false\n /** Some default-route subscriber takes on-device text while the primary is down. */\n private hasFallbackSubscription = false\n private activeLanguage: string | null = null\n /**\n * Default to \"cloud is up\" so we never accidentally activate local STT before\n * the cloud client has had a chance to start. We attach lazily on the first\n * subscription/reconcile pass; once attached, this field reflects the real\n * cloud-client runtime status.\n */\n private cloudConnected = true\n private localActive = false\n private starting: Promise<void> | null = null\n private cloudAdapterAttached = false\n /**\n * Soniox-direct (user API key) state, pushed by SonioxDirectService. While\n * enabled, IT is the primary transcription path — cloud connectivity is\n * irrelevant and sherpa engages on Soniox health instead. Defaults healthy\n * for the same reason cloudConnected defaults true: never activate local STT\n * before the primary has had a chance to come up.\n */\n private sonioxDirectEnabled = false\n private sonioxDirectHealthy = true\n /**\n * Cloud STT refused for plan quota (`stream.quota` blocked=true). The cloud\n * connection stays up, so this is tracked separately from `cloudConnected`:\n * for transcription purposes the cloud path is down, and sherpa takes over\n * if its model is on-device. Soniox-direct is unmetered and unaffected.\n */\n private sttQuotaBlocked = false\n\n private constructor() {\n // Reset the persisted mirror flag on boot — the in-memory state in this\n // coordinator is the source of truth, and a stale \"true\" left from the\n // previous session would cause native to feed Sherpa before any miniapp\n // registered a subscription.\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackActive, false)\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason, \"\")\n sttModelManager.onModelReady(() => {\n void this.retryAfterModelReady()\n })\n }\n\n private async retryAfterModelReady(): Promise<void> {\n // A model can finish downloading while the initial availability check is\n // still pending. Wait for that attempt before retrying so readiness is\n // never lost and native restarts cannot overlap.\n await this.starting\n await this.reconcile()\n }\n\n /**\n * Lazy-attach the engine cloud connection listener. The coordinator is\n * constructed at module load, before engine.start() brings up the cloud\n * client, so we defer until the first reconcile.\n */\n private attachCloudAdapterIfReady(): void {\n if (this.cloudAdapterAttached) return\n this.cloudConnected = cloudClientService.isConnected()\n cloudClientService.onStatusChanged((status) => {\n const connected = status.status === \"connected\"\n if (this.cloudConnected === connected) return\n this.log(`cloud connection -> ${connected ? \"up\" : \"down\"}`)\n this.cloudConnected = connected\n void this.reconcile()\n })\n this.cloudAdapterAttached = true\n }\n\n static getInstance(): LocalSttFallbackCoordinator {\n if (!LocalSttFallbackCoordinator.instance) {\n LocalSttFallbackCoordinator.instance = new LocalSttFallbackCoordinator()\n }\n return LocalSttFallbackCoordinator.instance\n }\n\n isActive(): boolean {\n return this.localActive\n }\n\n getActiveLanguage(): string | null {\n return this.activeLanguage\n }\n\n /** The cause of the current activation, or null while inactive. */\n getReason(): LocalSttFallbackReason | null {\n return this.localActive ? this.currentReason() : null\n }\n\n onSubscriptionChange(\n hasTranscription: boolean,\n language: string | null,\n hasForceLocal = false,\n acceptsFallback = true,\n ): void {\n this.log(\n `onSubscriptionChange(hasTx=${hasTranscription}, lang=${language}, forceLocal=${hasForceLocal}, fallback=${acceptsFallback})`,\n )\n this.hasTranscriptionSubscription = hasTranscription\n this.hasForceLocalSubscription = hasTranscription && hasForceLocal\n this.hasFallbackSubscription = hasTranscription && acceptsFallback\n this.activeLanguage = hasTranscription ? language : null\n void this.reconcile()\n }\n\n /**\n * Informational hook, retained for API stability. No longer called: the\n * authoritative switching signal is the cloud-client adapter (local miniapps\n * are powered only by V2). v1 cloud transcripts deliberately do NOT drive this\n * coordinator anymore. Kept for future hysteresis if connection-state alone\n * proves too coarse.\n */\n onCloudTranscript(): void {}\n\n /**\n * Pushed when the cloud announces/clears an STT quota block. Awaitable so\n * the caller can inspect `isActive()` afterwards to phrase its user notice\n * (\"continuing on-device\" vs \"transcription paused\").\n */\n async onSttQuotaBlockedChange(blocked: boolean): Promise<void> {\n if (this.sttQuotaBlocked === blocked) return\n this.log(`stt quota -> ${blocked ? \"blocked\" : \"restored\"}`)\n this.sttQuotaBlocked = blocked\n await this.reconcile()\n }\n\n isSttQuotaBlocked(): boolean {\n return this.sttQuotaBlocked\n }\n\n /** Pushed by SonioxDirectService on every enable/health transition. */\n onSonioxDirectStateChange(enabled: boolean, healthy: boolean): void {\n if (this.sonioxDirectEnabled === enabled && this.sonioxDirectHealthy === healthy) return\n this.log(`soniox-direct -> enabled=${enabled} healthy=${healthy}`)\n this.sonioxDirectEnabled = enabled\n this.sonioxDirectHealthy = healthy\n void this.reconcile()\n }\n\n /** Whether the primary transcription path (Soniox-direct when a user key is\n * set, the cloud otherwise) is up. */\n private primaryUp(): boolean {\n // Cloud turned off in Privacy settings: the cloud path is not merely down,\n // it is never coming back, so on-device STT is the primary. Checked ahead\n // of `cloudConnected` because that field defaults to true (so local STT\n // doesn't pre-empt cloud at boot) and would otherwise report a path that\n // will never connect as up. Soniox-direct is a user-supplied third-party\n // key, not Glassly cloud, so it stays available.\n if (!this.sonioxDirectEnabled && cloudClientService.isCloudDisabled()) return false\n return this.sonioxDirectEnabled ? this.sonioxDirectHealthy : this.cloudConnected && !this.sttQuotaBlocked\n }\n\n private async reconcile(): Promise<void> {\n this.attachCloudAdapterIfReady()\n const shouldBeActive = this.shouldUseLocalStt()\n if (shouldBeActive && !this.localActive) {\n if (!this.starting) {\n this.starting = this.startLocalStt().finally(() => {\n this.starting = null\n })\n }\n await this.starting\n } else if (!shouldBeActive && this.localActive) {\n this.stopLocalStt(this.hasTranscriptionSubscription ? \"primary transcription recovered\" : \"subscription gone\")\n } else if (shouldBeActive && this.localActive) {\n // Still active, but the cause may have shifted (e.g. quota block landing\n // while already offline for a disconnect) — keep the mirror current.\n this.publishReason()\n }\n }\n\n /**\n * Ranks the concurrent causes: a dead-forever cloud path first, then the\n * primary provider's own failure, then transient outages, with `force_local`\n * only when the primary is actually up. Mirrors `primaryUp()`.\n */\n private currentReason(): LocalSttFallbackReason {\n if (!this.sonioxDirectEnabled && cloudClientService.isCloudDisabled()) return \"cloud_disabled\"\n if (this.sonioxDirectEnabled && !this.sonioxDirectHealthy) return \"provider_unreachable\"\n if (!this.sonioxDirectEnabled && !this.cloudConnected) return \"cloud_disconnected\"\n if (!this.sonioxDirectEnabled && this.sttQuotaBlocked) return \"stt_quota\"\n return \"force_local\"\n }\n\n private publishReason(): void {\n const reason = this.localActive ? this.currentReason() : \"\"\n offlineActivity.setTranscription(\n this.localActive ? {reason: this.currentReason(), model: sttModelManager.currentModelLabel()} : null,\n )\n const store = useSettingsStore.getState()\n if (store.getSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason) !== reason) {\n store.setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackReason, reason)\n }\n }\n\n private async startLocalStt(): Promise<void> {\n this.log(\"starting local stt\")\n try {\n await this.activateLocalStt()\n } catch (err) {\n this.log(`local stt startup failed: ${err}`)\n }\n }\n\n private async activateLocalStt(): Promise<void> {\n const modelAvailable = await sttModelManager.isModelAvailable()\n if (!modelAvailable) {\n this.log(\"local stt model is not available yet — skipping activation\")\n return\n }\n if (!this.shouldUseLocalStt()) {\n this.log(\"local stt activation skipped: routing changed before start completed\")\n return\n }\n // Native rejects unless the recognizer and its processing loop are ready.\n await BluetoothSdk.restartTranscriber()\n if (!this.shouldUseLocalStt()) {\n this.log(\"local stt activation skipped: routing changed during transcriber restart\")\n return\n }\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackActive, true)\n this.localActive = true\n this.publishReason()\n }\n\n private stopLocalStt(reason: string): void {\n this.log(`stopping local stt: ${reason}`)\n useSettingsStore.getState().setSetting(ISLAND_SETTINGS_KEYS.localSttFallbackActive, false)\n this.localActive = false\n this.publishReason()\n }\n\n private shouldUseLocalStt(): boolean {\n return (\n this.hasTranscriptionSubscription &&\n (this.hasForceLocalSubscription || (this.hasFallbackSubscription && !this.primaryUp()))\n )\n }\n\n private log(msg: string): void {\n console.log(`[LocalSttFallback] ${msg}`)\n }\n}\n\nconst localSttFallbackCoordinator = LocalSttFallbackCoordinator.getInstance()\nexport default localSttFallbackCoordinator\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ServedTranslationPair } from "./OfflineActivity";
|
|
1
2
|
/** One concrete-target `translation:<source>:<target>` stream and who listens to it. */
|
|
2
3
|
export interface LocalTranslationSubscription {
|
|
3
4
|
/** Subscribed source: a language code, or `*` / `auto` for any. */
|
|
@@ -39,6 +40,7 @@ export declare class LocalTranslationService {
|
|
|
39
40
|
private utteranceCounter;
|
|
40
41
|
attach(host: Host): void;
|
|
41
42
|
setSubscriptions(subscriptions: LocalTranslationSubscription[]): void;
|
|
43
|
+
servedPairs(): ServedTranslationPair[];
|
|
42
44
|
onLocalTranscript(transcript: LocalTranscript, sourceLanguage: string): void;
|
|
43
45
|
private wantedTargets;
|
|
44
46
|
private enqueue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalTranslationService.d.ts","sourceRoot":"","sources":["../../src/services/LocalTranslationService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LocalTranslationService.d.ts","sourceRoot":"","sources":["../../src/services/LocalTranslationService.ts"],"names":[],"mappings":"AAAA,OAAwB,EAAC,KAAK,qBAAqB,EAAC,MAAM,mBAAmB,CAAA;AAG7E,wFAAwF;AACxF,MAAM,WAAW,4BAA4B;IAC3C,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,KAAK,EAAE,OAAO,CAAA;IACd,iEAAiE;IACjE,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,IAAI;IACZ,mEAAmE;IACnE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAChE,8DAA8D;IAC9D,SAAS,EAAE,MAAM,OAAO,CAAA;IACxB,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7D;AAwBD;;;;;;;;GAQG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,IAAI,CAAoB;IAChC,OAAO,CAAC,gBAAgB,CAAI;IAE5B,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAKxB,gBAAgB,CAAC,aAAa,EAAE,4BAA4B,EAAE,GAAG,IAAI;IASrE,WAAW,IAAI,qBAAqB,EAAE;IAiBtC,iBAAiB,CAAC,UAAU,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAkB5E,OAAO,CAAC,aAAa;IAerB,OAAO,CAAC,OAAO;YAoBD,KAAK;IAoBnB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,IAAI;CAgBb;AAED,QAAA,MAAM,uBAAuB,yBAAgC,CAAA;AAC7D,eAAe,uBAAuB,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import offlineActivity from "./OfflineActivity";
|
|
1
2
|
import { offlineTranslationManager } from "./OfflineTranslationManager";
|
|
2
3
|
/** Interims are translated at most this often; finals always are. */
|
|
3
4
|
const INTERIM_MIN_INTERVAL_MS = 300;
|
|
@@ -19,6 +20,7 @@ export class LocalTranslationService {
|
|
|
19
20
|
utteranceCounter = 0;
|
|
20
21
|
attach(host) {
|
|
21
22
|
this.host = host;
|
|
23
|
+
offlineActivity.setTranslationSource(() => this.servedPairs());
|
|
22
24
|
}
|
|
23
25
|
setSubscriptions(subscriptions) {
|
|
24
26
|
this.subscriptions = subscriptions;
|
|
@@ -27,6 +29,26 @@ export class LocalTranslationService {
|
|
|
27
29
|
if (!live.has(key.substring(key.indexOf(">") + 1)))
|
|
28
30
|
this.lanes.delete(key);
|
|
29
31
|
}
|
|
32
|
+
offlineActivity.refresh();
|
|
33
|
+
}
|
|
34
|
+
servedPairs() {
|
|
35
|
+
if (!this.host || !offlineTranslationManager.isSupported())
|
|
36
|
+
return [];
|
|
37
|
+
const pairs = new Map();
|
|
38
|
+
let primaryUp = null;
|
|
39
|
+
for (const sub of this.subscriptions) {
|
|
40
|
+
if (baseLanguage(sub.source) === baseLanguage(sub.target))
|
|
41
|
+
continue;
|
|
42
|
+
if (!sub.forceLocal) {
|
|
43
|
+
primaryUp ??= this.host.primaryUp();
|
|
44
|
+
if (primaryUp || !sub.cloud)
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const key = `${sub.source}>${sub.target}`;
|
|
48
|
+
const existing = pairs.get(key);
|
|
49
|
+
pairs.set(key, { source: sub.source, target: sub.target, forceLocal: sub.forceLocal || !!existing?.forceLocal });
|
|
50
|
+
}
|
|
51
|
+
return [...pairs.values()];
|
|
30
52
|
}
|
|
31
53
|
onLocalTranscript(transcript, sourceLanguage) {
|
|
32
54
|
const text = transcript.text?.trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalTranslationService.js","sourceRoot":"","sources":["../../src/services/LocalTranslationService.ts"],"names":[],"mappings":"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","sourcesContent":["import {offlineTranslationManager} from \"./OfflineTranslationManager\"\n\n/** One concrete-target `translation:<source>:<target>` stream and who listens to it. */\nexport interface LocalTranslationSubscription {\n /** Subscribed source: a language code, or `*` / `auto` for any. */\n source: string\n target: string\n /** At least one listener on the default (cloud) route. */\n cloud: boolean\n /** At least one listener that requires on-device translation. */\n forceLocal: boolean\n}\n\nexport interface LocalTranscript {\n text: string\n isFinal?: boolean\n speakerId?: number | string\n startTime?: number\n endTime?: number\n}\n\ninterface Host {\n /** Fan a `translation:<source>:<target>` event out to miniapps. */\n deliver: (stream: string, data: Record<string, unknown>) => void\n /** Whether the primary (cloud / Soniox-direct) path is up. */\n primaryUp: () => boolean\n /** A wanted pair has no language pack on the device. */\n onPairUnavailable?: (source: string, target: string) => void\n}\n\ninterface Job {\n text: string\n isFinal: boolean\n utteranceId: string\n transcript: LocalTranscript\n}\n\ninterface Lane {\n busy: boolean\n finals: Job[]\n pendingInterim: Job | null\n lastInterimAt: number\n retryAt: number\n reportedUnavailable: boolean\n}\n\n/** Interims are translated at most this often; finals always are. */\nconst INTERIM_MIN_INTERVAL_MS = 300\nconst FAILURE_BACKOFF_MS = 5000\n\nconst baseLanguage = (code: string): string => code.split(\"-\")[0].toLowerCase()\n\n/**\n * Turns on-device transcripts into on-device translations. Apple's Translation\n * framework has no streaming API, so each update is a whole `translate` call:\n * one runs at a time per language pair, a newer interim replaces a waiting\n * one, and finals are never dropped.\n *\n * A pair is translated here when a subscriber forces local, or when the\n * primary path is down and would otherwise leave default subscribers silent.\n */\nexport class LocalTranslationService {\n private subscriptions: LocalTranslationSubscription[] = []\n private lanes = new Map<string, Lane>()\n private host: Host | null = null\n private utteranceCounter = 0\n\n attach(host: Host): void {\n this.host = host\n }\n\n setSubscriptions(subscriptions: LocalTranslationSubscription[]): void {\n this.subscriptions = subscriptions\n const live = new Set(subscriptions.map((s) => s.target))\n for (const key of this.lanes.keys()) {\n if (!live.has(key.substring(key.indexOf(\">\") + 1))) this.lanes.delete(key)\n }\n }\n\n onLocalTranscript(transcript: LocalTranscript, sourceLanguage: string): void {\n const text = transcript.text?.trim()\n if (!text || !this.host || !offlineTranslationManager.isSupported()) return\n const isFinal = transcript.isFinal === true\n const utteranceId = `local-${this.utteranceCounter}`\n if (isFinal) this.utteranceCounter++\n\n for (const target of this.wantedTargets(sourceLanguage)) {\n const job: Job = {text, isFinal, utteranceId, transcript}\n if (baseLanguage(target) === baseLanguage(sourceLanguage)) {\n // Same-language passthrough, as the cloud emits it: no originalText.\n this.emit(sourceLanguage, target, job, text, undefined)\n continue\n }\n this.enqueue(sourceLanguage, target, job)\n }\n }\n\n private wantedTargets(sourceLanguage: string): Set<string> {\n const targets = new Set<string>()\n let primaryUp: boolean | null = null\n for (const sub of this.subscriptions) {\n const anySource = sub.source === \"*\" || sub.source === \"auto\"\n if (!anySource && baseLanguage(sub.source) !== baseLanguage(sourceLanguage)) continue\n if (!sub.forceLocal) {\n primaryUp ??= this.host!.primaryUp()\n if (primaryUp || !sub.cloud) continue\n }\n targets.add(sub.target)\n }\n return targets\n }\n\n private enqueue(source: string, target: string, job: Job): void {\n const key = `${source}>${target}`\n let lane = this.lanes.get(key)\n if (!lane) {\n lane = {busy: false, finals: [], pendingInterim: null, lastInterimAt: 0, retryAt: 0, reportedUnavailable: false}\n this.lanes.set(key, lane)\n }\n const now = Date.now()\n if (now < lane.retryAt) return\n if (job.isFinal) {\n lane.pendingInterim = null\n lane.finals.push(job)\n } else {\n if (now - lane.lastInterimAt < INTERIM_MIN_INTERVAL_MS) return\n lane.lastInterimAt = now\n lane.pendingInterim = job\n }\n if (!lane.busy) void this.drain(source, target, lane)\n }\n\n private async drain(source: string, target: string, lane: Lane): Promise<void> {\n lane.busy = true\n try {\n for (;;) {\n const job = lane.finals.shift() ?? lane.pendingInterim\n if (!job) break\n if (!job.isFinal) lane.pendingInterim = null\n try {\n const translated = await offlineTranslationManager.translate(job.text, source, target)\n this.emit(source, target, job, translated, job.text)\n } catch (err) {\n this.handleFailure(source, target, lane, err)\n break\n }\n }\n } finally {\n lane.busy = false\n }\n }\n\n private handleFailure(source: string, target: string, lane: Lane, err: unknown): void {\n const message = (err as Error)?.message ?? String(err)\n console.warn(`[LocalTranslation] ${source}>${target} failed: ${message}`)\n lane.finals = []\n lane.pendingInterim = null\n lane.retryAt = Date.now() + FAILURE_BACKOFF_MS\n if (message.includes(\"not_installed\") && !lane.reportedUnavailable) {\n lane.reportedUnavailable = true\n this.host?.onPairUnavailable?.(source, target)\n }\n }\n\n private emit(source: string, target: string, job: Job, text: string, originalText: string | undefined): void {\n this.host?.deliver(`translation:${source}:${target}`, {\n type: \"translation\",\n text,\n originalText,\n isFinal: job.isFinal,\n utteranceId: job.utteranceId,\n startTime: job.transcript.startTime,\n endTime: job.transcript.endTime,\n speakerId: job.transcript.speakerId,\n transcribeLanguage: source,\n translateLanguage: target,\n didTranslate: originalText !== undefined,\n provider: \"apple-translation\",\n })\n }\n}\n\nconst localTranslationService = new LocalTranslationService()\nexport default localTranslationService\n"]}
|
|
1
|
+
{"version":3,"file":"LocalTranslationService.js","sourceRoot":"","sources":["../../src/services/LocalTranslationService.ts"],"names":[],"mappings":"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","sourcesContent":["import offlineActivity, {type ServedTranslationPair} from \"./OfflineActivity\"\nimport {offlineTranslationManager} from \"./OfflineTranslationManager\"\n\n/** One concrete-target `translation:<source>:<target>` stream and who listens to it. */\nexport interface LocalTranslationSubscription {\n /** Subscribed source: a language code, or `*` / `auto` for any. */\n source: string\n target: string\n /** At least one listener on the default (cloud) route. */\n cloud: boolean\n /** At least one listener that requires on-device translation. */\n forceLocal: boolean\n}\n\nexport interface LocalTranscript {\n text: string\n isFinal?: boolean\n speakerId?: number | string\n startTime?: number\n endTime?: number\n}\n\ninterface Host {\n /** Fan a `translation:<source>:<target>` event out to miniapps. */\n deliver: (stream: string, data: Record<string, unknown>) => void\n /** Whether the primary (cloud / Soniox-direct) path is up. */\n primaryUp: () => boolean\n /** A wanted pair has no language pack on the device. */\n onPairUnavailable?: (source: string, target: string) => void\n}\n\ninterface Job {\n text: string\n isFinal: boolean\n utteranceId: string\n transcript: LocalTranscript\n}\n\ninterface Lane {\n busy: boolean\n finals: Job[]\n pendingInterim: Job | null\n lastInterimAt: number\n retryAt: number\n reportedUnavailable: boolean\n}\n\n/** Interims are translated at most this often; finals always are. */\nconst INTERIM_MIN_INTERVAL_MS = 300\nconst FAILURE_BACKOFF_MS = 5000\n\nconst baseLanguage = (code: string): string => code.split(\"-\")[0].toLowerCase()\n\n/**\n * Turns on-device transcripts into on-device translations. Apple's Translation\n * framework has no streaming API, so each update is a whole `translate` call:\n * one runs at a time per language pair, a newer interim replaces a waiting\n * one, and finals are never dropped.\n *\n * A pair is translated here when a subscriber forces local, or when the\n * primary path is down and would otherwise leave default subscribers silent.\n */\nexport class LocalTranslationService {\n private subscriptions: LocalTranslationSubscription[] = []\n private lanes = new Map<string, Lane>()\n private host: Host | null = null\n private utteranceCounter = 0\n\n attach(host: Host): void {\n this.host = host\n offlineActivity.setTranslationSource(() => this.servedPairs())\n }\n\n setSubscriptions(subscriptions: LocalTranslationSubscription[]): void {\n this.subscriptions = subscriptions\n const live = new Set(subscriptions.map((s) => s.target))\n for (const key of this.lanes.keys()) {\n if (!live.has(key.substring(key.indexOf(\">\") + 1))) this.lanes.delete(key)\n }\n offlineActivity.refresh()\n }\n\n servedPairs(): ServedTranslationPair[] {\n if (!this.host || !offlineTranslationManager.isSupported()) return []\n const pairs = new Map<string, ServedTranslationPair>()\n let primaryUp: boolean | null = null\n for (const sub of this.subscriptions) {\n if (baseLanguage(sub.source) === baseLanguage(sub.target)) continue\n if (!sub.forceLocal) {\n primaryUp ??= this.host.primaryUp()\n if (primaryUp || !sub.cloud) continue\n }\n const key = `${sub.source}>${sub.target}`\n const existing = pairs.get(key)\n pairs.set(key, {source: sub.source, target: sub.target, forceLocal: sub.forceLocal || !!existing?.forceLocal})\n }\n return [...pairs.values()]\n }\n\n onLocalTranscript(transcript: LocalTranscript, sourceLanguage: string): void {\n const text = transcript.text?.trim()\n if (!text || !this.host || !offlineTranslationManager.isSupported()) return\n const isFinal = transcript.isFinal === true\n const utteranceId = `local-${this.utteranceCounter}`\n if (isFinal) this.utteranceCounter++\n\n for (const target of this.wantedTargets(sourceLanguage)) {\n const job: Job = {text, isFinal, utteranceId, transcript}\n if (baseLanguage(target) === baseLanguage(sourceLanguage)) {\n // Same-language passthrough, as the cloud emits it: no originalText.\n this.emit(sourceLanguage, target, job, text, undefined)\n continue\n }\n this.enqueue(sourceLanguage, target, job)\n }\n }\n\n private wantedTargets(sourceLanguage: string): Set<string> {\n const targets = new Set<string>()\n let primaryUp: boolean | null = null\n for (const sub of this.subscriptions) {\n const anySource = sub.source === \"*\" || sub.source === \"auto\"\n if (!anySource && baseLanguage(sub.source) !== baseLanguage(sourceLanguage)) continue\n if (!sub.forceLocal) {\n primaryUp ??= this.host!.primaryUp()\n if (primaryUp || !sub.cloud) continue\n }\n targets.add(sub.target)\n }\n return targets\n }\n\n private enqueue(source: string, target: string, job: Job): void {\n const key = `${source}>${target}`\n let lane = this.lanes.get(key)\n if (!lane) {\n lane = {busy: false, finals: [], pendingInterim: null, lastInterimAt: 0, retryAt: 0, reportedUnavailable: false}\n this.lanes.set(key, lane)\n }\n const now = Date.now()\n if (now < lane.retryAt) return\n if (job.isFinal) {\n lane.pendingInterim = null\n lane.finals.push(job)\n } else {\n if (now - lane.lastInterimAt < INTERIM_MIN_INTERVAL_MS) return\n lane.lastInterimAt = now\n lane.pendingInterim = job\n }\n if (!lane.busy) void this.drain(source, target, lane)\n }\n\n private async drain(source: string, target: string, lane: Lane): Promise<void> {\n lane.busy = true\n try {\n for (;;) {\n const job = lane.finals.shift() ?? lane.pendingInterim\n if (!job) break\n if (!job.isFinal) lane.pendingInterim = null\n try {\n const translated = await offlineTranslationManager.translate(job.text, source, target)\n this.emit(source, target, job, translated, job.text)\n } catch (err) {\n this.handleFailure(source, target, lane, err)\n break\n }\n }\n } finally {\n lane.busy = false\n }\n }\n\n private handleFailure(source: string, target: string, lane: Lane, err: unknown): void {\n const message = (err as Error)?.message ?? String(err)\n console.warn(`[LocalTranslation] ${source}>${target} failed: ${message}`)\n lane.finals = []\n lane.pendingInterim = null\n lane.retryAt = Date.now() + FAILURE_BACKOFF_MS\n if (message.includes(\"not_installed\") && !lane.reportedUnavailable) {\n lane.reportedUnavailable = true\n this.host?.onPairUnavailable?.(source, target)\n }\n }\n\n private emit(source: string, target: string, job: Job, text: string, originalText: string | undefined): void {\n this.host?.deliver(`translation:${source}:${target}`, {\n type: \"translation\",\n text,\n originalText,\n isFinal: job.isFinal,\n utteranceId: job.utteranceId,\n startTime: job.transcript.startTime,\n endTime: job.transcript.endTime,\n speakerId: job.transcript.speakerId,\n transcribeLanguage: source,\n translateLanguage: target,\n didTranslate: originalText !== undefined,\n provider: \"apple-translation\",\n })\n }\n}\n\nconst localTranslationService = new LocalTranslationService()\nexport default localTranslationService\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
export type OfflineActivityReason = LocalSttFallbackReason | LocalLlmReason;
|
|
9
|
+
export interface OfflineTranscriptionActivity {
|
|
10
|
+
reason: LocalSttFallbackReason;
|
|
11
|
+
model: string;
|
|
12
|
+
}
|
|
13
|
+
export interface OfflineTranslationPair {
|
|
14
|
+
source: string;
|
|
15
|
+
target: string;
|
|
16
|
+
}
|
|
17
|
+
export interface OfflineTranslationActivity {
|
|
18
|
+
reason: OfflineActivityReason;
|
|
19
|
+
pairs: OfflineTranslationPair[];
|
|
20
|
+
}
|
|
21
|
+
export interface OfflineLlmActivity {
|
|
22
|
+
reason: LocalLlmReason;
|
|
23
|
+
model: string;
|
|
24
|
+
requests: number;
|
|
25
|
+
}
|
|
26
|
+
export interface OfflineActivity {
|
|
27
|
+
transcription: OfflineTranscriptionActivity | null;
|
|
28
|
+
translation: OfflineTranslationActivity | null;
|
|
29
|
+
llm: OfflineLlmActivity | null;
|
|
30
|
+
}
|
|
31
|
+
export interface ServedTranslationPair extends OfflineTranslationPair {
|
|
32
|
+
forceLocal: boolean;
|
|
33
|
+
}
|
|
34
|
+
declare class OfflineActivityTracker {
|
|
35
|
+
private transcription;
|
|
36
|
+
private llmRuns;
|
|
37
|
+
private translationSource;
|
|
38
|
+
private snapshot;
|
|
39
|
+
private snapshotKey;
|
|
40
|
+
private listeners;
|
|
41
|
+
current(): OfflineActivity;
|
|
42
|
+
subscribe(listener: (activity: OfflineActivity) => void): () => void;
|
|
43
|
+
setTranscription(transcription: OfflineTranscriptionActivity | null): void;
|
|
44
|
+
beginLlm(reason: LocalLlmReason, model: string): () => void;
|
|
45
|
+
setTranslationSource(source: (() => ServedTranslationPair[]) | null): void;
|
|
46
|
+
refresh(): void;
|
|
47
|
+
reset(): void;
|
|
48
|
+
private translation;
|
|
49
|
+
private llm;
|
|
50
|
+
}
|
|
51
|
+
export { OfflineActivityTracker };
|
|
52
|
+
declare const offlineActivity: OfflineActivityTracker;
|
|
53
|
+
export default offlineActivity;
|
|
54
|
+
//# sourceMappingURL=OfflineActivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OfflineActivity.d.ts","sourceRoot":"","sources":["../../src/services/OfflineActivity.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,+BAA+B,CAAA;AAEzE,MAAM,MAAM,qBAAqB,GAAG,sBAAsB,GAAG,cAAc,CAAA;AAE3E,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,sBAAsB,CAAA;IAC9B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,qBAAqB,CAAA;IAC7B,KAAK,EAAE,sBAAsB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,cAAc,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,4BAA4B,GAAG,IAAI,CAAA;IAClD,WAAW,EAAE,0BAA0B,GAAG,IAAI,CAAA;IAC9C,GAAG,EAAE,kBAAkB,GAAG,IAAI,CAAA;CAC/B;AAED,MAAM,WAAW,qBAAsB,SAAQ,sBAAsB;IACnE,UAAU,EAAE,OAAO,CAAA;CACpB;AAID,cAAM,sBAAsB;IAC1B,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,OAAO,CAAqD;IACpE,OAAO,CAAC,iBAAiB,CAA+C;IACxE,OAAO,CAAC,QAAQ,CAAyB;IACzC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,SAAS,CAAiD;IAElE,OAAO,IAAI,eAAe;IAI1B,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAOpE,gBAAgB,CAAC,aAAa,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IAK1E,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI;IAa3D,oBAAoB,CAAC,MAAM,EAAE,CAAC,MAAM,qBAAqB,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI;IAK1E,OAAO,IAAI,IAAI;IAmBf,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,GAAG;CAKZ;AAED,OAAO,EAAC,sBAAsB,EAAC,CAAA;AAC/B,QAAA,MAAM,eAAe,wBAA+B,CAAA;AACpD,eAAe,eAAe,CAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const EMPTY = { transcription: null, translation: null, llm: null };
|
|
2
|
+
class OfflineActivityTracker {
|
|
3
|
+
transcription = null;
|
|
4
|
+
llmRuns = [];
|
|
5
|
+
translationSource = null;
|
|
6
|
+
snapshot = EMPTY;
|
|
7
|
+
snapshotKey = JSON.stringify(EMPTY);
|
|
8
|
+
listeners = new Set();
|
|
9
|
+
current() {
|
|
10
|
+
return this.snapshot;
|
|
11
|
+
}
|
|
12
|
+
subscribe(listener) {
|
|
13
|
+
this.listeners.add(listener);
|
|
14
|
+
return () => {
|
|
15
|
+
this.listeners.delete(listener);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
setTranscription(transcription) {
|
|
19
|
+
this.transcription = transcription;
|
|
20
|
+
this.refresh();
|
|
21
|
+
}
|
|
22
|
+
beginLlm(reason, model) {
|
|
23
|
+
const run = { reason, model };
|
|
24
|
+
this.llmRuns.push(run);
|
|
25
|
+
this.refresh();
|
|
26
|
+
let ended = false;
|
|
27
|
+
return () => {
|
|
28
|
+
if (ended)
|
|
29
|
+
return;
|
|
30
|
+
ended = true;
|
|
31
|
+
this.llmRuns = this.llmRuns.filter((r) => r !== run);
|
|
32
|
+
this.refresh();
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
setTranslationSource(source) {
|
|
36
|
+
this.translationSource = source;
|
|
37
|
+
this.refresh();
|
|
38
|
+
}
|
|
39
|
+
refresh() {
|
|
40
|
+
const next = {
|
|
41
|
+
transcription: this.transcription,
|
|
42
|
+
translation: this.translation(),
|
|
43
|
+
llm: this.llm(),
|
|
44
|
+
};
|
|
45
|
+
const key = JSON.stringify(next);
|
|
46
|
+
if (key === this.snapshotKey)
|
|
47
|
+
return;
|
|
48
|
+
this.snapshotKey = key;
|
|
49
|
+
this.snapshot = next;
|
|
50
|
+
for (const listener of this.listeners) {
|
|
51
|
+
try {
|
|
52
|
+
listener(next);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.warn("OfflineActivity: listener failed:", error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
reset() {
|
|
60
|
+
this.transcription = null;
|
|
61
|
+
this.llmRuns = [];
|
|
62
|
+
this.translationSource = null;
|
|
63
|
+
this.refresh();
|
|
64
|
+
}
|
|
65
|
+
translation() {
|
|
66
|
+
if (!this.transcription || !this.translationSource)
|
|
67
|
+
return null;
|
|
68
|
+
const pairs = this.translationSource();
|
|
69
|
+
if (pairs.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
return {
|
|
72
|
+
reason: pairs.some((p) => p.forceLocal) ? "force_local" : this.transcription.reason,
|
|
73
|
+
pairs: pairs.map(({ source, target }) => ({ source, target })),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
llm() {
|
|
77
|
+
const latest = this.llmRuns[this.llmRuns.length - 1];
|
|
78
|
+
if (!latest)
|
|
79
|
+
return null;
|
|
80
|
+
return { reason: latest.reason, model: latest.model, requests: this.llmRuns.length };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export { OfflineActivityTracker };
|
|
84
|
+
const offlineActivity = new OfflineActivityTracker();
|
|
85
|
+
export default offlineActivity;
|
|
86
|
+
//# sourceMappingURL=OfflineActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OfflineActivity.js","sourceRoot":"","sources":["../../src/services/OfflineActivity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What the phone is running on-device right now (transcription, translation,\n * LLM) and why, for the home offline card. Producers push in; it imports\n * nothing at runtime so their test stubs stay unaffected.\n */\nimport type {LocalLlmReason} from \"./LlmRouting\"\nimport type {LocalSttFallbackReason} from \"./LocalSttFallbackCoordinator\"\n\nexport type OfflineActivityReason = LocalSttFallbackReason | LocalLlmReason\n\nexport interface OfflineTranscriptionActivity {\n reason: LocalSttFallbackReason\n model: string\n}\n\nexport interface OfflineTranslationPair {\n source: string\n target: string\n}\n\nexport interface OfflineTranslationActivity {\n reason: OfflineActivityReason\n pairs: OfflineTranslationPair[]\n}\n\nexport interface OfflineLlmActivity {\n reason: LocalLlmReason\n model: string\n requests: number\n}\n\nexport interface OfflineActivity {\n transcription: OfflineTranscriptionActivity | null\n translation: OfflineTranslationActivity | null\n llm: OfflineLlmActivity | null\n}\n\nexport interface ServedTranslationPair extends OfflineTranslationPair {\n forceLocal: boolean\n}\n\nconst EMPTY: OfflineActivity = {transcription: null, translation: null, llm: null}\n\nclass OfflineActivityTracker {\n private transcription: OfflineTranscriptionActivity | null = null\n private llmRuns: Array<{reason: LocalLlmReason; model: string}> = []\n private translationSource: (() => ServedTranslationPair[]) | null = null\n private snapshot: OfflineActivity = EMPTY\n private snapshotKey = JSON.stringify(EMPTY)\n private listeners = new Set<(activity: OfflineActivity) => void>()\n\n current(): OfflineActivity {\n return this.snapshot\n }\n\n subscribe(listener: (activity: OfflineActivity) => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n setTranscription(transcription: OfflineTranscriptionActivity | null): void {\n this.transcription = transcription\n this.refresh()\n }\n\n beginLlm(reason: LocalLlmReason, model: string): () => void {\n const run = {reason, model}\n this.llmRuns.push(run)\n this.refresh()\n let ended = false\n return () => {\n if (ended) return\n ended = true\n this.llmRuns = this.llmRuns.filter((r) => r !== run)\n this.refresh()\n }\n }\n\n setTranslationSource(source: (() => ServedTranslationPair[]) | null): void {\n this.translationSource = source\n this.refresh()\n }\n\n refresh(): void {\n const next: OfflineActivity = {\n transcription: this.transcription,\n translation: this.translation(),\n llm: this.llm(),\n }\n const key = JSON.stringify(next)\n if (key === this.snapshotKey) return\n this.snapshotKey = key\n this.snapshot = next\n for (const listener of this.listeners) {\n try {\n listener(next)\n } catch (error) {\n console.warn(\"OfflineActivity: listener failed:\", error)\n }\n }\n }\n\n reset(): void {\n this.transcription = null\n this.llmRuns = []\n this.translationSource = null\n this.refresh()\n }\n\n private translation(): OfflineTranslationActivity | null {\n if (!this.transcription || !this.translationSource) return null\n const pairs = this.translationSource()\n if (pairs.length === 0) return null\n return {\n reason: pairs.some((p) => p.forceLocal) ? \"force_local\" : this.transcription.reason,\n pairs: pairs.map(({source, target}) => ({source, target})),\n }\n }\n\n private llm(): OfflineLlmActivity | null {\n const latest = this.llmRuns[this.llmRuns.length - 1]\n if (!latest) return null\n return {reason: latest.reason, model: latest.model, requests: this.llmRuns.length}\n }\n}\n\nexport {OfflineActivityTracker}\nconst offlineActivity = new OfflineActivityTracker()\nexport default offlineActivity\n"]}
|
|
@@ -52,6 +52,7 @@ declare class STTModelManager {
|
|
|
52
52
|
static getInstance(): STTModelManager;
|
|
53
53
|
getCurrentLanguageFromPreferences(): Promise<string>;
|
|
54
54
|
getCurrentLanguage(): string;
|
|
55
|
+
currentModelLabel(): string;
|
|
55
56
|
setCurrentLanguage(code: string): void;
|
|
56
57
|
getBcp47ForLanguage(code?: string): string;
|
|
57
58
|
/** Sherpa models first, then whatever Apple locales the last refresh reported. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"STTModelManager.d.ts","sourceRoot":"","sources":["../../src/services/STTModelManager.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"STTModelManager.d.ts","sourceRoot":"","sources":["../../src/services/STTModelManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -183,6 +183,12 @@ class STTModelManager {
|
|
|
183
183
|
getCurrentLanguage() {
|
|
184
184
|
return this.currentLanguage;
|
|
185
185
|
}
|
|
186
|
+
currentModelLabel() {
|
|
187
|
+
const language = this.config(this.currentLanguage);
|
|
188
|
+
if (!language)
|
|
189
|
+
return this.currentLanguage;
|
|
190
|
+
return language.type === "apple" ? `Apple · ${language.displayName}` : language.displayName;
|
|
191
|
+
}
|
|
186
192
|
setCurrentLanguage(code) {
|
|
187
193
|
if (this.config(code)) {
|
|
188
194
|
this.currentLanguage = code;
|