arcane-os 0.33.6 → 0.34.1
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/CHANGELOG.md +33 -0
- package/README.md +2 -2
- package/browser-runtime/ai/browser-speech.mjs +1 -0
- package/browser-runtime/ai/digitalocean-speech.mjs +468 -0
- package/docs/reference/ai/browser-speech.md +86 -5
- package/docs/reference/ai/twin-cloud.md +32 -0
- package/docs/reference/core/arcane-entities.md +1 -1
- package/docs/reference/inventory/package-api.json +15 -1
- package/docs/reference/inventory/runtime-entities.json +1 -1
- package/docs/reference/runtime-entities.md +40 -0
- package/docs/reference/runtime-modules.md +54 -9
- package/docs/reference/sdk-api.md +66 -2
- package/package.json +2 -2
- package/runtime/arcane/components/chat.html +8 -6
- package/runtime/arcane/components/speech.html +40 -6
- package/runtime/arcane/entities/User.js +30 -7
- package/runtime/arcane/modules/AI.js +364 -54
|
@@ -41,7 +41,7 @@ authoritative in the linked contract document.
|
|
|
41
41
|
| `arcane/entities/Theme.js#themeTokens` | constant | `themeTokens` | Frozen ordered definitions that map public theme-token keys to CSS custom properties, labels, and defaults. |
|
|
42
42
|
| `arcane/entities/Theme.js#themeColorToHex` | function | `themeColorToHex` | Validates an RGB, RGBA, or six-digit hexadecimal color and returns its RGB channels as a normalized six-digit hexadecimal color. |
|
|
43
43
|
| `arcane/entities/Theme.js#default` | class | `Theme` | Validates and freezes a named light or dark token set and can serialize, restore, apply, or clear that theme on a document root. |
|
|
44
|
-
| `arcane/entities/User.js#default` | class | `UserEntity` | Owns the browser user's
|
|
44
|
+
| `arcane/entities/User.js#default` | class | `UserEntity` | Owns the browser user's settings and profile record, including DBOPFS load, refresh, serialized partial or fresh-baseline callback updates, and optional persistence. Callback null skips saving; timestamp setters merge only their fields through the same write owner. |
|
|
45
45
|
| `arcane/entities/Weather.js#WeatherLocation` | class | `WeatherLocation` | Validates and freezes geographic identity, coordinates, and timezone metadata for a weather location. |
|
|
46
46
|
| `arcane/entities/Weather.js#WeatherObservation` | class | `WeatherObservation` | Validates and freezes one timestamped current-weather observation and its units. |
|
|
47
47
|
| `arcane/entities/Weather.js#WeatherDay` | class | `WeatherDay` | Validates and freezes one daily forecast with temperature, precipitation, and optional sunrise and sunset times. |
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"minimumVersion": "22.23.2 for Node entrypoints",
|
|
7
7
|
"moduleSystem": "ESM"
|
|
8
8
|
},
|
|
9
|
-
"memberCount":
|
|
9
|
+
"memberCount": 218,
|
|
10
10
|
"runtimeSubpathPatterns": {
|
|
11
11
|
"arcane-os/modules/*": "./runtime/arcane/modules/*",
|
|
12
12
|
"arcane-os/entities/*": "./runtime/arcane/entities/*"
|
|
@@ -3106,6 +3106,20 @@
|
|
|
3106
3106
|
"protocol": "arcane-ai-browser-speech-artifacts/1 over arcane-ai-provider/2",
|
|
3107
3107
|
"normalization": "Exact protocol string; it grants no model authority and names no model download"
|
|
3108
3108
|
},
|
|
3109
|
+
{
|
|
3110
|
+
"id": "browser-speech:createDigitalOceanFalTTSProvider",
|
|
3111
|
+
"name": "createDigitalOceanFalTTSProvider",
|
|
3112
|
+
"displayName": "createDigitalOceanFalTTSProvider()",
|
|
3113
|
+
"kind": "function",
|
|
3114
|
+
"signature": "createDigitalOceanFalTTSProvider({id,model,getApiKey,maxConcurrentRequests=4,fetch=globalThis.fetch}={})",
|
|
3115
|
+
"entrypoints": ["arcane-os/ai/browser-speech"],
|
|
3116
|
+
"primaryImport": "arcane-os/ai/browser-speech",
|
|
3117
|
+
"group": "Browser speech providers",
|
|
3118
|
+
"summary": "Creates an explicitly selected DigitalOcean FAL remote TTS provider using standard Fetch and completed audio jobs.",
|
|
3119
|
+
"availability": "Browser or Node with Fetch, Blob, AbortController, and an application-owned DigitalOcean inference key",
|
|
3120
|
+
"protocol": "arcane-ai-provider/2 tts/synthesize",
|
|
3121
|
+
"normalization": "Construction and activation make no adapter network requests. Activation awaits the application-owned string or promise key reader. localOnly false, concurrent requests, complete audio Blob results, and local cancellation without claiming remote-job cancellation. Applications own model, voice, credentials, and activation."
|
|
3122
|
+
},
|
|
3109
3123
|
{
|
|
3110
3124
|
"id": "browser-speech:createBrowserKokoroProvider",
|
|
3111
3125
|
"name": "createBrowserKokoroProvider",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
{"file":"runtime/arcane/entities/Preference.js","exports":["default","preferenceSchema"],"availability":"cross-host","normalization":"normalized"},
|
|
20
20
|
{"file":"runtime/arcane/entities/TerminalSession.js","exports":["default","terminalShells"],"availability":"cross-host","normalization":"normalized"},
|
|
21
21
|
{"file":"runtime/arcane/entities/Theme.js","exports":["arcaneDarkThemeTokens","arcaneLightThemeTokens","default","themeColorToHex","themeTokens"],"availability":"cross-host/dom-when-applied","normalization":"normalized/mixed"},
|
|
22
|
-
{"file":"runtime/arcane/entities/User.js","exports":["default","profileUpdateFromSearchParams"],"availability":"browser/native-webview","normalization":"
|
|
22
|
+
{"file":"runtime/arcane/entities/User.js","exports":["default","profileUpdateFromSearchParams"],"availability":"browser/native-webview","normalization":"updateExplicit accepts existing partial/JSON updates or an awaited fresh-profile callback under the existing serialized write owner; callback null skips saving and returns the fresh profile, while failed writes restore the fresh baseline. Timestamp setters persist partial fields through that owner. profileUpdateFromSearchParams purely maps nonempty supplied URLSearchParams values without further decoding, trimming, or persistence."},
|
|
23
23
|
{"file":"runtime/arcane/entities/Weather.js","exports":["WeatherDay","WeatherLocation","WeatherObservation","WeatherSnapshot"],"availability":"cross-host","normalization":"normalized"}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -274,6 +274,46 @@ Owns persisted user preference/profile fields, explicit-preference updates,
|
|
|
274
274
|
fresh reads, saves, and JSON projection. It installs `window.user` after its
|
|
275
275
|
DBLS/DBOPFS lifecycle and emits `user-entity-loaded`.
|
|
276
276
|
|
|
277
|
+
### updateExplicit(update)
|
|
278
|
+
|
|
279
|
+
Accepts an ordinary partial profile object, a JSON string, or a function
|
|
280
|
+
`current => partialOrNull`. It returns `Promise<UserEntityData>` with the
|
|
281
|
+
resulting explicit profile, retaining the existing field setters and their
|
|
282
|
+
validation behavior.
|
|
283
|
+
|
|
284
|
+
The function is called once after the latest durable profile has been read,
|
|
285
|
+
inside the existing serialized update and Web Lock. It may return a promise.
|
|
286
|
+
Return only the fields to change, or return `null` to skip the durable write
|
|
287
|
+
and receive the fresh profile unchanged. This no-write meaning applies only
|
|
288
|
+
to the callback result; ordinary object/string inputs and direct `null` retain
|
|
289
|
+
their existing behavior. With `persist=false`, the callback uses current
|
|
290
|
+
memory and performs no durable read or write.
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
const displayedName = user.username;
|
|
294
|
+
const profile = await user.updateExplicit(function renameTheCaptain(current) {
|
|
295
|
+
if (current.username !== displayedName) return null;
|
|
296
|
+
return {username: 'Captain Moonboots'};
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Callbacks compute a patch without mutating `current`. Do not call or await
|
|
301
|
+
`updateExplicit()`, `withFreshExplicit()`, or `refresh()` on the same instance
|
|
302
|
+
inside the callback: those operations depend on the current queue entry
|
|
303
|
+
finishing. Fetch outside the callback; compare against `current` at the write
|
|
304
|
+
boundary. Rendering and unrelated background work need not await this update.
|
|
305
|
+
|
|
306
|
+
A thrown or rejected callback causes no save and rejects with the original
|
|
307
|
+
error. A failed save restores the fresh baseline in memory and rejects with
|
|
308
|
+
the save error. The persistence setting is restored and later queued updates
|
|
309
|
+
remain usable. The existing `current_time` and `last_successful_time` setters
|
|
310
|
+
keep their immediate in-memory assignment, then save only their timestamp
|
|
311
|
+
field through this same fresh partial-update owner. This preserves newer
|
|
312
|
+
durable profile fields when TimeGuard records a check. Automatic timestamp
|
|
313
|
+
save failures are reported through Arcane logging; callers requiring durable
|
|
314
|
+
completion use and await `updateExplicit({current_time: value})` or the
|
|
315
|
+
corresponding `last_successful_time` partial update.
|
|
316
|
+
|
|
277
317
|
### profileUpdateFromSearchParams(searchParams)
|
|
278
318
|
|
|
279
319
|
Named export from `arcane-os/entities/User.js`. Pass an existing
|
|
@@ -144,10 +144,11 @@ playback.
|
|
|
144
144
|
|
|
145
145
|
### Public surface
|
|
146
146
|
|
|
147
|
-
default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and
|
|
147
|
+
default `AI`; read-only `providerRuntime`, `speechActivationPending`, `browserSpeechConfiguration`, and
|
|
148
148
|
`browserSpeechDescriptor`; `configureBrowserSpeech(configuration,{signal})`,
|
|
149
149
|
`disposeBrowserSpeech({signal})`, `setAI()`, `configureProviders()`,
|
|
150
|
-
`configureSpeechProviders()`, `
|
|
150
|
+
`configureSpeechProviders()`, `configureSpeechProvider(role,provider,options)`,
|
|
151
|
+
`transitionAI()`, `transitionProviders()`,
|
|
151
152
|
`transitionSpeechProviders()`, `startProviders()`, `setSpeechMuted()`,
|
|
152
153
|
`streamRequest()`, `streamMessage()`, `fetchRequest()`, `fetch()`,
|
|
153
154
|
read-only `ttsSegmentation`, `configureTTSSegmentation()`,
|
|
@@ -163,6 +164,21 @@ entry points. `streamMessage(...)` and `streamRequest(options)` deliver
|
|
|
163
164
|
incremental responses. The positional and object forms share the existing
|
|
164
165
|
provider implementations; neither form is a retired compatibility API.
|
|
165
166
|
|
|
167
|
+
`fetchRequest({model,...})` and `streamRequest({model,...})` accept an optional
|
|
168
|
+
request-local model. On the built-in TWiN Cloud route, its exact value becomes
|
|
169
|
+
the outbound `model` field; native Ollama receives the same request-local value.
|
|
170
|
+
Omitting it or passing `undefined` preserves the configured model. Each built-in
|
|
171
|
+
request captures its model before asynchronous callbacks, so concurrent requests
|
|
172
|
+
may use different models without assigning `ai.model`, changing provider
|
|
173
|
+
selection, or loading/unloading another model. Request observers and retries
|
|
174
|
+
receive that request's model. Stream/native completion metadata uses it only
|
|
175
|
+
when the provider omits its own `model` field.
|
|
176
|
+
|
|
177
|
+
Registered providers receive a supplied `model` unchanged in their request
|
|
178
|
+
payload; an omitted value remains absent. The provider owns interpretation.
|
|
179
|
+
This option does not switch a loaded browser-WASM model or its lifecycle:
|
|
180
|
+
explicit provider/model selection and activation remain necessary there.
|
|
181
|
+
|
|
166
182
|
Built-in cloud chat decodes an HTTP error body once as JSON or text and rejects
|
|
167
183
|
with that complete value unchanged. It does not reconstruct an Error, replace
|
|
168
184
|
the message, or add `providerMessage`, `status`, or an SDK failure code to the
|
|
@@ -217,16 +233,17 @@ returns aggregate runtime status; `transitionProviders()` returns the configured
|
|
|
217
233
|
three-role route configuration. Selected TWiN Cloud `TWIN` LLM, `OLLAMA` LLM,
|
|
218
234
|
and Core `LOCAL_SPEACH` STT/TTS built-in routes expose truthful capability-only
|
|
219
235
|
readiness through internal provider/2 adapters without probing, downloading, or
|
|
220
|
-
hiding a load.
|
|
221
|
-
|
|
236
|
+
hiding a load. Explicit speech preference slots remain unchanged, including a
|
|
237
|
+
selected external provider that the application registers after AI startup.
|
|
238
|
+
TWiN Cloud availability
|
|
222
239
|
requires the selected LLM route, its model, a credential, and `fetch`; Core speech
|
|
223
240
|
availability requires the exact selected `Arcane.speech.transcribe` or
|
|
224
241
|
`synthesize` method. `fetchRequest()`
|
|
225
242
|
keeps the selected provider's public response shape. Browser speech routes
|
|
226
243
|
translate the existing AI.js STT `{audio:Blob|File,mimeType,model}` and TTS
|
|
227
244
|
`{model,input,responseFormat,voice?,speed?}` requests at the provider boundary;
|
|
228
|
-
|
|
229
|
-
the exact selected
|
|
245
|
+
the selected provider declares its supported audio format. TTS voice selection comes from
|
|
246
|
+
the exact selected provider/model catalog `defaultVoice`; a saved
|
|
230
247
|
OpenAI-route voice is never forwarded to another provider route.
|
|
231
248
|
|
|
232
249
|
The TWiN Cloud built-in provider and default-model preference sentinel are
|
|
@@ -252,14 +269,34 @@ tool declarations, emitted tool calls, or callback ordering.
|
|
|
252
269
|
|
|
253
270
|
`configureSpeechProviders({stt,tts})` commits only the two speech routes and
|
|
254
271
|
leaves the current LLM route and sticky lifecycle record unchanged. Both speech
|
|
255
|
-
roles must be unloaded
|
|
256
|
-
|
|
257
|
-
|
|
272
|
+
roles must be unloaded and own no request, load, unload, or dispose operation.
|
|
273
|
+
STT remains local-only (`AI_STT_DEVICE_ONLY` for a remote selection); an
|
|
274
|
+
explicit TTS provider may declare `localOnly:false`.
|
|
258
275
|
`transitionSpeechProviders({stt,tts})` stops queued audio, explicitly unloads
|
|
259
276
|
only STT and TTS, then commits that same closed speech route record. Neither
|
|
260
277
|
method loads a model, selects a fallback, or changes caller-owned model or voice
|
|
261
278
|
policy.
|
|
262
279
|
|
|
280
|
+
`configureSpeechProvider(role,provider,{modelId,signal,expectedProvider})`
|
|
281
|
+
asynchronously registers and selects one provider/2 speech role, unloading and
|
|
282
|
+
disposing only its previous SDK-owned provider. `modelId` defaults to the first
|
|
283
|
+
catalog entry. It returns the selected `{providerId,modelId,localOnly}` record;
|
|
284
|
+
passing `null` removes that role and returns `null`. An optional
|
|
285
|
+
`expectedProvider` is compared inside the configuration lane; if another
|
|
286
|
+
provider has taken ownership, removal resolves `false` without changing it.
|
|
287
|
+
An unaccepted candidate is disposed on failure; a failure after replacement
|
|
288
|
+
has `committed:true`, and runtime ownership identifies the installed provider.
|
|
289
|
+
|
|
290
|
+
This method composes with an STT-only or TTS-only `configureBrowserSpeech()`
|
|
291
|
+
in either order. The other speech role and LLM keep their routes, readiness,
|
|
292
|
+
requests, and storage. Use sequential disposal when combining this method
|
|
293
|
+
with `disposeBrowserSpeech()`. Configuration does not activate the new provider
|
|
294
|
+
or make a network request. `setSpeechMuted(false)` waits for an in-progress
|
|
295
|
+
configuration before activating the committed TTS selection; a later mute
|
|
296
|
+
cancels that pending unmute. The application keeps model, voice, and credential
|
|
297
|
+
selection. See [DigitalOcean FAL speech](ai/browser-speech.md#digitalocean-fal-text-to-speech)
|
|
298
|
+
for the public remote adapter and completed-job audio transport.
|
|
299
|
+
|
|
263
300
|
`startProviders({startLanguageModel=true,startMuted=true,startTranscription=false,signal=null}={})`
|
|
264
301
|
starts provider-owned text chat without requesting an STT load by default.
|
|
265
302
|
Callers selecting a browser-WASM LLM pass `startLanguageModel:false` so it
|
|
@@ -273,6 +310,14 @@ user activation intent exposed by the shared speech component.
|
|
|
273
310
|
`setSpeechMuted(false)` records the public unmuted state only after the selected
|
|
274
311
|
TTS route reaches ready; a failed load leaves the public state muted. In contrast,
|
|
275
312
|
`setSpeechMuted(true)` cancels active TTS work and unloads that role.
|
|
313
|
+
An explicit unmute may wait for its selected external provider to register.
|
|
314
|
+
`speechActivationPending` distinguishes that intent from an ordinary muted
|
|
315
|
+
state. Streamed text and its final remainder may enter the existing speech
|
|
316
|
+
queue during that wait; synthesis waits for the same activation promise.
|
|
317
|
+
Text never activates a provider itself. Stop cancels queued preparation even
|
|
318
|
+
while registration is pending; mute, disposal, a new startup operation, or an
|
|
319
|
+
actual route replacement also revoke the pending activation. Initial
|
|
320
|
+
registration of the already-selected provider/model preserves queued speech.
|
|
276
321
|
The optional browser-speech `stt.execution` and `tts.execution` records select
|
|
277
322
|
`device:'auto'|'webnn-npu'|'webgpu'|'wasm'` and `maxConcurrentRequests`.
|
|
278
323
|
Omission uses NPU, GPU, then CPU automatic selection with one Whisper slot and
|
|
@@ -67,7 +67,7 @@ runtime layouts.
|
|
|
67
67
|
| `arcane-os/ai/browser-wasm` | Caller-selected browser-local Wllama inference, complete DBOPFS model storage, streaming, cancellation, and structural tool-call results. |
|
|
68
68
|
| `arcane-os/ai/tool-text-stream` | Shared selected tool-argument text observer for provider integration. |
|
|
69
69
|
| `arcane-os/ai/twin-cloud` | Complete TWiN Cloud requests from Node or a browser with an explicit key/model and shared retry/cancellation behavior. |
|
|
70
|
-
| `arcane-os/ai/browser-speech` | Caller-selected browser-local Whisper STT and Kokoro TTS
|
|
70
|
+
| `arcane-os/ai/browser-speech` | Caller-selected browser-local Whisper STT and Kokoro TTS, explicit DigitalOcean FAL remote TTS, ordinary upstream assets, role lifecycle, and cancellation. |
|
|
71
71
|
| `arcane-os/mail` | Portable Mail runtime, durable outbox, complete transport responses, and provider-neutral acceptance contracts. |
|
|
72
72
|
|
|
73
73
|
These lowercase runtime-module entrypoints expose their existing exports; they
|
|
@@ -178,6 +178,7 @@ browser map are cataloged separately in [Runtime modules](runtime-modules.md).
|
|
|
178
178
|
| `getPwaInstall()` | function | `arcane-os/pwa` | Progressive web applications | Browser native installation events; waiting state while no prompt is available |
|
|
179
179
|
| `mountPwaInstallPrompt()` | function | `arcane-os/pwa` | Progressive web applications | Browser document and managed HTML import; resolves to null without a document |
|
|
180
180
|
| `createDbopfsSpeechArtifactStore()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser with ready DBOPFS, Web Locks, Fetch, File/Blob, and object URLs |
|
|
181
|
+
| `createDigitalOceanFalTTSProvider()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser or Node with Fetch, Blob, and an application-supplied DigitalOcean inference key |
|
|
181
182
|
| `removeBrowserSpeechModelCache()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser CacheStorage; explicit removal of one selected upstream model |
|
|
182
183
|
| `createNativeBuildPlan()` | function | `arcane-os` | Targets, native plans, and providers | Node; selected browser/native target or provider as documented |
|
|
183
184
|
| `createNativeTargetAdapter()` | function | `arcane-os` | Targets, native plans, and providers | Node; selected browser/native target or provider as documented |
|
|
@@ -6763,7 +6764,7 @@ releaseProvider();
|
|
|
6763
6764
|
|
|
6764
6765
|
# Browser speech providers
|
|
6765
6766
|
|
|
6766
|
-
`arcane-os/ai/browser-speech` exports
|
|
6767
|
+
`arcane-os/ai/browser-speech` exports the package members below.
|
|
6767
6768
|
It ships provider, authority, artifact-store, and Worker mechanisms but no
|
|
6768
6769
|
Whisper/Kokoro model weights, adapter runtime artifacts, voices, download URLs,
|
|
6769
6770
|
default catalog, CDN loader, native bridge, or cloud fallback. The providers
|
|
@@ -7121,6 +7122,69 @@ async function transcribeAfterUserChoice(audioBlob) {
|
|
|
7121
7122
|
}
|
|
7122
7123
|
```
|
|
7123
7124
|
|
|
7125
|
+
## createDigitalOceanFalTTSProvider()
|
|
7126
|
+
|
|
7127
|
+
### Overview
|
|
7128
|
+
|
|
7129
|
+
Creates a `localOnly:false` provider/2 TTS adapter for the caller-selected
|
|
7130
|
+
DigitalOcean FAL model. It submits independent synthesis jobs, polls their
|
|
7131
|
+
completion, and returns each complete downloaded audio `Blob`. The shared AI
|
|
7132
|
+
queue owns punctuation segmentation, bounded concurrent requests, and playback
|
|
7133
|
+
in input order; this is not provider-native live audio streaming.
|
|
7134
|
+
|
|
7135
|
+
### Signature and parameters
|
|
7136
|
+
|
|
7137
|
+
```text
|
|
7138
|
+
createDigitalOceanFalTTSProvider({id,model,getApiKey,maxConcurrentRequests=4,fetch=globalThis.fetch}={})
|
|
7139
|
+
```
|
|
7140
|
+
|
|
7141
|
+
`id` is the application-owned provider identifier. `model` contains the selected
|
|
7142
|
+
`id` and `defaultVoice`; the supported multilingual-v2 selection is
|
|
7143
|
+
`fal-ai/elevenlabs/tts/multilingual-v2`. `getApiKey()` returns the
|
|
7144
|
+
application's current DigitalOcean inference key or a promise for it. The credential is never
|
|
7145
|
+
included in catalog or status. `maxConcurrentRequests` is a positive integer;
|
|
7146
|
+
the runtime owns admission and its FIFO queue. Optional `fetch` supplies the
|
|
7147
|
+
standard Fetch interface.
|
|
7148
|
+
|
|
7149
|
+
### Availability and normalization
|
|
7150
|
+
|
|
7151
|
+
The adapter needs Fetch, Blob, and AbortController; it needs no Worker or
|
|
7152
|
+
DBOPFS. Browser access depends on endpoint CORS support. Construction makes
|
|
7153
|
+
no network request. Inspection and activation await the application's key
|
|
7154
|
+
reader without making an SDK network request. Activation checks
|
|
7155
|
+
local credential presence, not remote validity. Status reports remote
|
|
7156
|
+
execution and current activity. Requests preserve complete text, selected
|
|
7157
|
+
voice, and speed; multilingual-v2 speed outside `0.7` through `1.2` rejects
|
|
7158
|
+
instead of clamping. Returned media is fetched without the inference key.
|
|
7159
|
+
Cancelling a credential wait leaves the application's shared refresh alone
|
|
7160
|
+
and prevents a late result from starting this cancelled operation.
|
|
7161
|
+
|
|
7162
|
+
Each provider/2 `tts/synthesize` request returns one complete audio Blob.
|
|
7163
|
+
Request cancellation, unload, and disposal stop local polling/downloads and
|
|
7164
|
+
discard late results; they cannot promise cancellation of an already submitted
|
|
7165
|
+
remote job. HTTP, provider, and media failures remain observable. The adapter
|
|
7166
|
+
does not select a fallback or silently resubmit a paid synthesis POST.
|
|
7167
|
+
|
|
7168
|
+
### Example
|
|
7169
|
+
|
|
7170
|
+
```javascript
|
|
7171
|
+
import {createDigitalOceanFalTTSProvider} from 'arcane-os/ai/browser-speech';
|
|
7172
|
+
|
|
7173
|
+
async function configureCloudVoice(ai, getApiKey) {
|
|
7174
|
+
const provider = createDigitalOceanFalTTSProvider({
|
|
7175
|
+
id: 'selected-cloud-voice',
|
|
7176
|
+
model: {id: 'fal-ai/elevenlabs/tts/multilingual-v2', defaultVoice: 'Rachel'},
|
|
7177
|
+
getApiKey
|
|
7178
|
+
});
|
|
7179
|
+
await ai.configureSpeechProvider('tts', provider);
|
|
7180
|
+
return provider;
|
|
7181
|
+
}
|
|
7182
|
+
```
|
|
7183
|
+
|
|
7184
|
+
Call activation and synthesis from the application's explicit speech action.
|
|
7185
|
+
For streaming text, cancellation, and independent STT configuration, see
|
|
7186
|
+
[DigitalOcean FAL speech](ai/browser-speech.md#digitalocean-fal-text-to-speech).
|
|
7187
|
+
|
|
7124
7188
|
## createBrowserKokoroProvider()
|
|
7125
7189
|
|
|
7126
7190
|
### Overview
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.1",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
87
87
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
88
88
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
89
|
-
"test:functional": "node ./bin/arcane-test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs",
|
|
89
|
+
"test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
90
90
|
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
91
91
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
92
92
|
"check": "node tools/check-source.mjs && npm test",
|
|
@@ -2965,13 +2965,13 @@
|
|
|
2965
2965
|
const clearThinking=thinkingHTML;
|
|
2966
2966
|
|
|
2967
2967
|
//console.table(arguments);
|
|
2968
|
+
const aiRuntime=boundChatAI??globalThis.ai;
|
|
2968
2969
|
if(
|
|
2969
2970
|
!isThinking
|
|
2970
2971
|
&&text
|
|
2971
|
-
&&host.aiAvailability.tts
|
|
2972
|
-
|
|
2972
|
+
&&((host.aiAvailability.tts&&speech.muted===false)
|
|
2973
|
+
||aiRuntime?.speechActivationPending===true)
|
|
2973
2974
|
){
|
|
2974
|
-
const aiRuntime=boundChatAI??globalThis.ai;
|
|
2975
2975
|
if(is.function(aiRuntime?.streamTTS)){
|
|
2976
2976
|
void Promise.resolve(aiRuntime.streamTTS(text)).catch(
|
|
2977
2977
|
reportTTSError
|
|
@@ -3837,10 +3837,12 @@
|
|
|
3837
3837
|
}
|
|
3838
3838
|
scrollTranscriptToBottom(!eventContext.synthetic);
|
|
3839
3839
|
|
|
3840
|
-
|
|
3841
|
-
|
|
3840
|
+
const aiRuntime=boundChatAI??globalThis.ai;
|
|
3841
|
+
if((host.aiAvailability.tts&&speech.muted===false)
|
|
3842
|
+
||aiRuntime?.speechActivationPending===true){
|
|
3842
3843
|
aiRuntime?.stopAudio?.();
|
|
3843
|
-
if(
|
|
3844
|
+
if(host.aiAvailability.tts&&speech.muted===false
|
|
3845
|
+
&&is.function(aiRuntime?.resumeAudio)){
|
|
3844
3846
|
void Promise.resolve(aiRuntime.resumeAudio()).catch(
|
|
3845
3847
|
function reportPlaybackResumeFailure(error){
|
|
3846
3848
|
reportTTSError(error,'playback-resume');
|
|
@@ -392,6 +392,7 @@
|
|
|
392
392
|
recordButton.addEventListener('blur', handleRecordBlur, lifecycleListenerOptions);
|
|
393
393
|
stopButton.addEventListener('click', stop, lifecycleListenerOptions);
|
|
394
394
|
muteButton.addEventListener('click', muteToggle, lifecycleListenerOptions);
|
|
395
|
+
window.addEventListener('ai-ready', synchronizeAIReadiness, lifecycleListenerOptions);
|
|
395
396
|
window.addEventListener(
|
|
396
397
|
'pagehide',
|
|
397
398
|
handlePageHide,
|
|
@@ -417,11 +418,7 @@
|
|
|
417
418
|
renderStatus();
|
|
418
419
|
void observeMicrophonePermission();
|
|
419
420
|
void aiModuleImport.then(
|
|
420
|
-
|
|
421
|
-
if (!destroyed) {
|
|
422
|
-
synchronizeAIMutedState();
|
|
423
|
-
}
|
|
424
|
-
},
|
|
421
|
+
synchronizeAIReadiness,
|
|
425
422
|
function reportAIModuleImportFailure(error) {
|
|
426
423
|
if (destroyed) {
|
|
427
424
|
return;
|
|
@@ -546,6 +543,17 @@
|
|
|
546
543
|
applyRoleTransitions(previousSTTRole, previousTTSRole);
|
|
547
544
|
}
|
|
548
545
|
|
|
546
|
+
function synchronizeAIReadiness() {
|
|
547
|
+
if (destroyed) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
settleSuccessfulUnmute();
|
|
551
|
+
continuePendingUnmute();
|
|
552
|
+
synchronizeAIMutedState();
|
|
553
|
+
renderControls();
|
|
554
|
+
renderStatus();
|
|
555
|
+
}
|
|
556
|
+
|
|
549
557
|
function applyRoleTransitions(previousSTTRole, previousTTSRole) {
|
|
550
558
|
sttActivationController?.synchronize(sttRole);
|
|
551
559
|
const sttBecameUnready = previousSTTRole.state === 'ready'
|
|
@@ -996,6 +1004,13 @@
|
|
|
996
1004
|
) {
|
|
997
1005
|
return false;
|
|
998
1006
|
}
|
|
1007
|
+
if (!is.function(globalThis.ai?.setSpeechMuted)) {
|
|
1008
|
+
pendingUnmute = true;
|
|
1009
|
+
host.muted = true;
|
|
1010
|
+
renderControls();
|
|
1011
|
+
renderStatus();
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
999
1014
|
const providerSelected = is.string(ttsRole.providerId)
|
|
1000
1015
|
&& ttsRole.providerId.length > 0
|
|
1001
1016
|
&& is.string(ttsRole.modelId)
|
|
@@ -1016,8 +1031,12 @@
|
|
|
1016
1031
|
}
|
|
1017
1032
|
|
|
1018
1033
|
pendingUnmute = true;
|
|
1034
|
+
const generation = ttsIntentGeneration + 1;
|
|
1019
1035
|
const requested = await requestTTSIntent('load', 'user');
|
|
1020
|
-
if (
|
|
1036
|
+
if (destroyed || generation !== ttsIntentGeneration) {
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
if (!requested) {
|
|
1021
1040
|
pendingUnmute = false;
|
|
1022
1041
|
host.muted = true;
|
|
1023
1042
|
stopTTSPlayback();
|
|
@@ -1774,7 +1793,12 @@
|
|
|
1774
1793
|
return;
|
|
1775
1794
|
}
|
|
1776
1795
|
|
|
1796
|
+
const cancelPendingUnmute = activeTTSIntent?.action === 'load';
|
|
1777
1797
|
destroyed = true;
|
|
1798
|
+
ttsIntentGeneration += 1;
|
|
1799
|
+
pendingUnmute = false;
|
|
1800
|
+
pendingTTSIntent = null;
|
|
1801
|
+
activeTTSIntent = null;
|
|
1778
1802
|
runtimeStateAbortController.abort();
|
|
1779
1803
|
sttActivationController?.destroy();
|
|
1780
1804
|
window.removeEventListener('pagehide', handlePageHide);
|
|
@@ -1785,6 +1809,16 @@
|
|
|
1785
1809
|
);
|
|
1786
1810
|
cancelSTTOperation('component-destroyed');
|
|
1787
1811
|
stopTTSPlayback();
|
|
1812
|
+
const aiRuntime = globalThis.ai;
|
|
1813
|
+
if (cancelPendingUnmute
|
|
1814
|
+
&& aiRuntime?.speechActivationPending === true
|
|
1815
|
+
&& is.function(aiRuntime.setSpeechMuted)) {
|
|
1816
|
+
void Promise.resolve(aiRuntime.setSpeechMuted(true)).catch(
|
|
1817
|
+
function reportSpeechTeardownFailure(error) {
|
|
1818
|
+
arcaneLogging.error('Speech activation could not be cancelled during component teardown.', error);
|
|
1819
|
+
}
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1788
1822
|
events.dispose();
|
|
1789
1823
|
host.componentReady = false;
|
|
1790
1824
|
renderControls();
|
|
@@ -370,7 +370,7 @@ class UserEntity {
|
|
|
370
370
|
|
|
371
371
|
this.#current_time = v;
|
|
372
372
|
|
|
373
|
-
this.#persist();
|
|
373
|
+
this.#persist({current_time:v});
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
/** @returns {number} */
|
|
@@ -386,7 +386,7 @@ class UserEntity {
|
|
|
386
386
|
|
|
387
387
|
this.#last_successful_time = v;
|
|
388
388
|
|
|
389
|
-
this.#persist();
|
|
389
|
+
this.#persist({last_successful_time:v});
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
/** @returns {string} */
|
|
@@ -822,7 +822,7 @@ class UserEntity {
|
|
|
822
822
|
src=this.explicit;
|
|
823
823
|
}
|
|
824
824
|
|
|
825
|
-
if(!is.union(src,'object
|
|
825
|
+
if(!is.union(src,'object|string')){
|
|
826
826
|
throw new Error('UserEntity.explicit setter expects object or JSON string');
|
|
827
827
|
}
|
|
828
828
|
|
|
@@ -857,9 +857,11 @@ class UserEntity {
|
|
|
857
857
|
|
|
858
858
|
/**
|
|
859
859
|
* Applies and durably saves an explicit profile update as one serialized
|
|
860
|
-
* operation. A
|
|
860
|
+
* operation. A callback computes a partial update from the fresh profile;
|
|
861
|
+
* its null result skips the write. It must not await another queued User
|
|
862
|
+
* operation on this instance. A failed write restores the fresh baseline.
|
|
861
863
|
*
|
|
862
|
-
* @param {UserEntityData|string|Object} src
|
|
864
|
+
* @param {UserEntityData|string|Object|function(UserEntityData): (Object|null|Promise<Object|null>)} src
|
|
863
865
|
* @returns {Promise<UserEntityData>}
|
|
864
866
|
*/
|
|
865
867
|
updateExplicit(src){
|
|
@@ -883,7 +885,19 @@ class UserEntity {
|
|
|
883
885
|
this.persist=false;
|
|
884
886
|
try{
|
|
885
887
|
this.explicit=baseline;
|
|
886
|
-
|
|
888
|
+
}finally{
|
|
889
|
+
this.persist=persist;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const conditional=is.function(src);
|
|
893
|
+
const update=conditional?await src(this.explicit):src;
|
|
894
|
+
if(conditional&&update===null){
|
|
895
|
+
return this.explicit;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
this.persist=false;
|
|
899
|
+
try{
|
|
900
|
+
this.explicit=update;
|
|
887
901
|
}finally{
|
|
888
902
|
this.persist=persist;
|
|
889
903
|
}
|
|
@@ -1107,8 +1121,17 @@ class UserEntity {
|
|
|
1107
1121
|
/**
|
|
1108
1122
|
* Internal persistence trigger
|
|
1109
1123
|
*/
|
|
1110
|
-
#persist(){
|
|
1124
|
+
#persist(update){
|
|
1111
1125
|
if(this.persist){
|
|
1126
|
+
// TimeGuard's ordinary timestamp setters must merge only their
|
|
1127
|
+
// fields into the fresh profile, never overwrite another tab's edits.
|
|
1128
|
+
if(!is.undefined(update)){
|
|
1129
|
+
const operation=this.updateExplicit(update);
|
|
1130
|
+
void operation.catch(function reportTimestampSaveFailure(error){
|
|
1131
|
+
arcaneLogging.error('The user timestamp could not be saved.',error);
|
|
1132
|
+
});
|
|
1133
|
+
return operation;
|
|
1134
|
+
}
|
|
1112
1135
|
return this.save();
|
|
1113
1136
|
}
|
|
1114
1137
|
|