arcane-os 0.68.0 → 0.70.0
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 +36 -0
- package/README.md +9 -4
- package/browser-runtime/ai/browser-decisions.mjs +46 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +58 -62
- package/browser-runtime/ai/browser-speech-providers.mjs +8 -3
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +76 -164
- package/browser-runtime/ai/dbopfs-model-parts.mjs +344 -0
- package/browser-runtime/ai/dbopfs-model-resources.mjs +190 -0
- package/browser-runtime/ai/decision-runtime.mjs +17 -1
- package/browser-runtime/ai/decision-worker.mjs +21 -1
- package/browser-runtime/ai/model-resource-bridge.mjs +231 -0
- package/browser-runtime/ai/speech-worker-client.mjs +68 -28
- package/browser-runtime/ai/speech-worker-runtime.mjs +212 -97
- package/browser-runtime/core/contracts.mjs +1 -1
- package/docs/reference/README.md +2 -0
- package/docs/reference/ai/browser-decisions.md +57 -3
- package/docs/reference/ai/browser-speech.md +94 -26
- package/docs/reference/ai/browser-wasm.md +28 -20
- package/docs/reference/core-native-packaging.md +54 -0
- package/docs/reference/core-runtime.md +9 -0
- package/docs/reference/core-shared-host.md +190 -0
- package/docs/reference/inventory/package-api.json +3 -3
- package/docs/reference/inventory/runtime-modules.json +3 -3
- package/docs/reference/runtime-components.md +4 -2
- package/docs/reference/runtime-modules.md +120 -20
- package/docs/reference/sdk-api.md +64 -30
- package/package.json +2 -2
- package/runtime/arcane/components/theme-switcher.html +1 -1
- package/runtime/arcane/modules/AI.js +7 -22
- package/runtime/arcane/modules/AIProviderRuntime.js +84 -6
- package/runtime/arcane/modules/DBOPFS.js +180 -26
- package/schemas/arcane-app.schema.json +10 -0
- package/src/app-descriptor.mjs +23 -1
- package/src/core/host.mjs +2 -0
- package/src/core/hosts/windows/ArcaneHost.cs +46 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +21 -0
- package/src/core/runtime.mjs +9 -4
- package/src/core/services/packaged-web.mjs +8 -1
- package/src/core/shared-host.mjs +500 -0
- package/src/native/portable-layout.mjs +66 -34
- package/src/native/portable-provider.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.70.0
|
|
4
|
+
|
|
5
|
+
- Add app-scoped DBOPFS committed-change subscriptions through the existing
|
|
6
|
+
event owner, with targeted cache invalidation and same-origin/storage-partition
|
|
7
|
+
live document notifications. Preserve complete records, successful partial
|
|
8
|
+
batch outcomes, cancellation and existing storage without migration or polling.
|
|
9
|
+
- Keep cold TTS activation owned by its pending preparation interests. Cancelling
|
|
10
|
+
the last pending interest cancels its activation; another preparation or direct
|
|
11
|
+
load retains shared work. Ready models and cache hits keep their existing lifetime.
|
|
12
|
+
- Add explicit same-app shared Core hosts over named pipes or Unix sockets,
|
|
13
|
+
retaining default stdio and one runtime owner. Correlate per-connection requests,
|
|
14
|
+
cancellation and events without confusing equal caller IDs across connections;
|
|
15
|
+
replay current packaged-web state while preserving complete payloads and errors.
|
|
16
|
+
- Store complete browser model/support resources as ordered DBOPFS parts and add
|
|
17
|
+
public model/speech-store `fetchResource()` support. Decision and speech Worker
|
|
18
|
+
resource fetches share the parent store, including nested module Workers, with
|
|
19
|
+
file/shard progress and cancellation that joins cleanup. Existing whole/range
|
|
20
|
+
cache formats remain readable; native ESM and unmapped classic Worker loading
|
|
21
|
+
remain platform-owned. No model/provider/default switch or cache migration.
|
|
22
|
+
|
|
23
|
+
## 0.69.0
|
|
24
|
+
|
|
25
|
+
- Add explicit `textFormat:'plain'` speech input through prepared speech,
|
|
26
|
+
direct TTS requests and the browser Kokoro provider. Preserve complete input,
|
|
27
|
+
whitespace and formatting characters, original-part cache identity and
|
|
28
|
+
cancellation while leaving default narration behavior unchanged. This input
|
|
29
|
+
option does not change the selected engine's synthesis limits.
|
|
30
|
+
- Add optional `native.window` width, height and resizable configuration.
|
|
31
|
+
Forward logical client dimensions through native manifests; the matching
|
|
32
|
+
Windows host applies initial DPI-aware usable-screen fitting while preserving
|
|
33
|
+
omitted defaults and subsequent application/user sizing. Other platform
|
|
34
|
+
adapters retain their existing sizing behavior.
|
|
35
|
+
- Preserve selected theme-switcher colors during hover by applying ordinary
|
|
36
|
+
hover fill only to unselected enabled buttons. Keep complete labels, focus,
|
|
37
|
+
disabled states, tokens and per-instance configuration unchanged.
|
|
38
|
+
|
|
3
39
|
## 0.68.0
|
|
4
40
|
|
|
5
41
|
- Add per-instance theme-switcher `configure({modes,labels})` and initial
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
20
20
|
share the theme, packaging, event, cancellation,
|
|
21
21
|
and browser run contracts while retaining their selected application layout.
|
|
22
22
|
|
|
23
|
-
This checkout defines the `0.
|
|
23
|
+
This checkout defines the `0.70.0` SDK contract. Development applications declare
|
|
24
24
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
25
25
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
26
26
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
|
@@ -469,11 +469,16 @@ keeping apostrophes, commas, and hyphens that join Unicode letters or numbers
|
|
|
469
469
|
inside the same segment.
|
|
470
470
|
Mute, stop, provider transition, and cancellation still govern the whole queue.
|
|
471
471
|
|
|
472
|
-
|
|
472
|
+
TTS narration removes repeated same formatting marks (`*`, `#`,
|
|
473
473
|
`_`, backtick, `~`) from its outbound speech-input copy before synthesis.
|
|
474
474
|
Streaming calls also recognize a run split across chunks. Single marks and
|
|
475
|
-
ordinary repeated punctuation remain literal.
|
|
476
|
-
|
|
475
|
+
ordinary repeated punctuation remain literal. Complete synthesis calls may
|
|
476
|
+
select `textFormat:'plain'` to preserve exact input: supply it in `prepareTTS`
|
|
477
|
+
options or the payload of `fetchTTS`, provider-runtime TTS requests, or direct
|
|
478
|
+
Kokoro requests. The SDK consumes this control before engine/host dispatch;
|
|
479
|
+
omission keeps the default cleanup. Streaming narration and `SpeechPlayback`
|
|
480
|
+
retain their existing formatting behavior. This option does not change the
|
|
481
|
+
selected engine's input capacity or establish complete upstream synthesis. Original
|
|
477
482
|
messages and caller payloads remain unchanged for display, storage, and model
|
|
478
483
|
input. The `arcane-os/speech-text` entrypoint exports the same
|
|
479
484
|
`MarkdownSpeech` streaming class and `stripSpeechFormatting()` one-pass helper
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Is from '../dependencies/strong-type/index.js';
|
|
2
2
|
import {createArcaneEventSource} from '../event-manager.mjs';
|
|
3
|
+
import {createModelResourceHost} from './model-resource-bridge.mjs';
|
|
3
4
|
|
|
4
5
|
const is = new Is(false);
|
|
5
6
|
const STATE_EVENT = 'ai.decisions.state';
|
|
@@ -65,6 +66,7 @@ function createDecisionOperation(op) {
|
|
|
65
66
|
*/
|
|
66
67
|
class BrowserDecisionModel {
|
|
67
68
|
#configuration;
|
|
69
|
+
#store;
|
|
68
70
|
#events;
|
|
69
71
|
#activation = null;
|
|
70
72
|
#generation = 0;
|
|
@@ -75,13 +77,17 @@ class BrowserDecisionModel {
|
|
|
75
77
|
#disposed = false;
|
|
76
78
|
|
|
77
79
|
constructor({family, model, revision = 'main', device = 'webgpu',
|
|
78
|
-
dtype = family === 'laya' ? 'fp16' : 'fp32', runtime = {}} = {}) {
|
|
80
|
+
dtype = family === 'laya' ? 'fp16' : 'fp32', runtime = {}, store = null} = {}) {
|
|
79
81
|
if (family !== 'laya' && family !== 'julia') {
|
|
80
82
|
throw new TypeError('family must be "laya" or "julia".');
|
|
81
83
|
}
|
|
82
84
|
if (runtime === null || !is.object(runtime)) {
|
|
83
85
|
throw new TypeError('runtime must be an object.');
|
|
84
86
|
}
|
|
87
|
+
if (store !== null && !is.function(store?.fetchResource)) {
|
|
88
|
+
throw new TypeError('store must expose fetchResource().');
|
|
89
|
+
}
|
|
90
|
+
this.#store = store;
|
|
85
91
|
const moduleUrl = runtime.moduleUrl === undefined
|
|
86
92
|
? DEFAULT_RUNTIME_MODULE
|
|
87
93
|
: runtime.moduleUrl;
|
|
@@ -197,6 +203,8 @@ class BrowserDecisionModel {
|
|
|
197
203
|
load: createDecisionOperation('load'),
|
|
198
204
|
pending: new Map(),
|
|
199
205
|
evaluations: new Set(),
|
|
206
|
+
resources: null,
|
|
207
|
+
cleanup: null,
|
|
200
208
|
reason: undefined
|
|
201
209
|
};
|
|
202
210
|
this.#activation = activation;
|
|
@@ -212,6 +220,22 @@ class BrowserDecisionModel {
|
|
|
212
220
|
{type: 'module', name: 'arcane-decisions'}
|
|
213
221
|
);
|
|
214
222
|
activation.worker = worker;
|
|
223
|
+
if (this.#store) {
|
|
224
|
+
const store = this.#store;
|
|
225
|
+
activation.resources = createModelResourceHost(
|
|
226
|
+
{
|
|
227
|
+
fetchResource: function fetchDecisionResource(input, options) {
|
|
228
|
+
return store.fetchResource(input, options);
|
|
229
|
+
},
|
|
230
|
+
send: function sendDecisionResource(message) {
|
|
231
|
+
worker.postMessage(message);
|
|
232
|
+
},
|
|
233
|
+
onError: function stopFailedDecisionResource(error) {
|
|
234
|
+
model.#stop(activation, error, 'error');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}
|
|
215
239
|
function handleDecisionWorkerMessage(event) {
|
|
216
240
|
model.#receive(activation, event.data);
|
|
217
241
|
}
|
|
@@ -240,7 +264,7 @@ class BrowserDecisionModel {
|
|
|
240
264
|
const id = this.#nextId++;
|
|
241
265
|
activation.pending.set(id, activation.load);
|
|
242
266
|
worker.postMessage(
|
|
243
|
-
{id, op: 'load', payload: this.#configuration}
|
|
267
|
+
{id, op: 'load', payload: this.#configuration, storedResources: this.#store !== null}
|
|
244
268
|
);
|
|
245
269
|
} catch (error) {
|
|
246
270
|
this.#stop(activation, error, 'error');
|
|
@@ -250,6 +274,7 @@ class BrowserDecisionModel {
|
|
|
250
274
|
|
|
251
275
|
#receive(activation, message) {
|
|
252
276
|
if (!this.#isCurrent(activation)) return;
|
|
277
|
+
if (activation.resources?.receive(message)) return;
|
|
253
278
|
const operation = message && activation.pending.get(message.id);
|
|
254
279
|
if (!operation) {
|
|
255
280
|
this.#stop(
|
|
@@ -308,6 +333,23 @@ class BrowserDecisionModel {
|
|
|
308
333
|
this.#state = state;
|
|
309
334
|
this.#progress = null;
|
|
310
335
|
this.#error = reason;
|
|
336
|
+
if (activation.resources) {
|
|
337
|
+
const model = this;
|
|
338
|
+
activation.cleanup = activation.resources.close(reason).catch(
|
|
339
|
+
function preserveResourceCleanupFailure(error) {
|
|
340
|
+
activation.reason = new AggregateError([reason, error], 'Unable to settle decision model resources.');
|
|
341
|
+
throw activation.reason;
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
activation.cleanup.catch(
|
|
345
|
+
function reportResourceCleanupFailure(error) {
|
|
346
|
+
if (!model.#activation) {
|
|
347
|
+
model.#error = error;
|
|
348
|
+
if (!model.#disposed) model.#publish();
|
|
349
|
+
} else globalThis.console?.error('Decision model resource cleanup failed.', error);
|
|
350
|
+
}
|
|
351
|
+
);
|
|
352
|
+
}
|
|
311
353
|
if (activation.worker) {
|
|
312
354
|
for (const [type, listener] of Object.entries(activation.listeners ?? {})) {
|
|
313
355
|
activation.worker.removeEventListener(type, listener);
|
|
@@ -330,6 +372,7 @@ class BrowserDecisionModel {
|
|
|
330
372
|
return this.status();
|
|
331
373
|
} finally {
|
|
332
374
|
stopObserving();
|
|
375
|
+
if (activation.cleanup) await activation.cleanup;
|
|
333
376
|
}
|
|
334
377
|
}
|
|
335
378
|
|
|
@@ -366,6 +409,7 @@ class BrowserDecisionModel {
|
|
|
366
409
|
} finally {
|
|
367
410
|
stopObserving();
|
|
368
411
|
activation.evaluations.delete(evaluation);
|
|
412
|
+
if (activation.cleanup) await activation.cleanup;
|
|
369
413
|
if (this.#isCurrent(activation)) this.#publish();
|
|
370
414
|
}
|
|
371
415
|
}
|
|
@@ -2,6 +2,8 @@ import Is from "../dependencies/strong-type/index.js";
|
|
|
2
2
|
import {
|
|
3
3
|
normalizeModelSecurity,
|
|
4
4
|
} from "./model-controller.mjs";
|
|
5
|
+
import { createDbopfsModelPartStore } from "./dbopfs-model-parts.mjs";
|
|
6
|
+
import { createDbopfsResourceStore } from "./dbopfs-model-resources.mjs";
|
|
5
7
|
|
|
6
8
|
const is = new Is(false);
|
|
7
9
|
|
|
@@ -1090,37 +1092,6 @@ function storageNames(authority, files) {
|
|
|
1090
1092
|
});
|
|
1091
1093
|
}
|
|
1092
1094
|
|
|
1093
|
-
async function* byteChunks(body, signal) {
|
|
1094
|
-
if (is.instanceCheck(body, Uint8Array) || is.instanceCheck(body, ArrayBuffer) || is.arrayBufferView(body)) {
|
|
1095
|
-
throwIfAborted(signal);
|
|
1096
|
-
yield is.instanceCheck(body, Uint8Array)
|
|
1097
|
-
? body
|
|
1098
|
-
: new Uint8Array(body.buffer ?? body, body.byteOffset ?? 0, body.byteLength);
|
|
1099
|
-
return;
|
|
1100
|
-
}
|
|
1101
|
-
if (body && is.function(body.getReader)) {
|
|
1102
|
-
const reader = body.getReader();
|
|
1103
|
-
const abort = () => void reader.cancel(signal?.reason).catch(() => undefined);
|
|
1104
|
-
signal?.addEventListener?.("abort", abort, { once: true });
|
|
1105
|
-
try {
|
|
1106
|
-
while (true) {
|
|
1107
|
-
throwIfAborted(signal);
|
|
1108
|
-
const { done, value } = await reader.read();
|
|
1109
|
-
if (done) return;
|
|
1110
|
-
yield is.instanceCheck(value, Uint8Array) ? value : new Uint8Array(value);
|
|
1111
|
-
}
|
|
1112
|
-
} finally {
|
|
1113
|
-
signal?.removeEventListener?.("abort", abort);
|
|
1114
|
-
if (signal?.aborted) await reader.cancel(signal.reason).catch(() => undefined);
|
|
1115
|
-
reader.releaseLock?.();
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
throw speechError(
|
|
1119
|
-
"ARCANE_AI_ARTIFACT_SOURCE_INVALID",
|
|
1120
|
-
"A browser speech artifact did not provide readable bytes.",
|
|
1121
|
-
);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
1095
|
function tokenizeArtifactGraphModule(source, modulePath) {
|
|
1125
1096
|
const tokens = [];
|
|
1126
1097
|
let index = 0;
|
|
@@ -1851,6 +1822,12 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
1851
1822
|
}
|
|
1852
1823
|
let tablePromise = null;
|
|
1853
1824
|
const operationTails = new Map();
|
|
1825
|
+
const parts = createDbopfsModelPartStore({dbopfs, tableName});
|
|
1826
|
+
const resources = createDbopfsResourceStore({
|
|
1827
|
+
dbopfs,
|
|
1828
|
+
tableName,
|
|
1829
|
+
fetchImpl: fetchImpl ?? globalThis.fetch?.bind(globalThis),
|
|
1830
|
+
});
|
|
1854
1831
|
|
|
1855
1832
|
function serializeAuthority(authority, operation) {
|
|
1856
1833
|
const key = storageKey(authority);
|
|
@@ -1916,22 +1893,9 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
1916
1893
|
}
|
|
1917
1894
|
}
|
|
1918
1895
|
|
|
1919
|
-
async function
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
const writable = await handle.createWritable();
|
|
1923
|
-
try {
|
|
1924
|
-
for await (const chunk of byteChunks(body, signal)) {
|
|
1925
|
-
await writable.write(chunk);
|
|
1926
|
-
}
|
|
1927
|
-
throwIfAborted(signal);
|
|
1928
|
-
await writable.close();
|
|
1929
|
-
return undefined;
|
|
1930
|
-
} catch (error) {
|
|
1931
|
-
await writable.abort?.(error).catch(() => undefined);
|
|
1932
|
-
await directory.removeEntry(name).catch(() => undefined);
|
|
1933
|
-
throw error;
|
|
1934
|
-
}
|
|
1896
|
+
async function readArtifact(name, signal) {
|
|
1897
|
+
// Reuse pre-existing complete files without rewriting saved model data.
|
|
1898
|
+
return await parts.read(name, {signal}) ?? await readFile(name);
|
|
1935
1899
|
}
|
|
1936
1900
|
|
|
1937
1901
|
async function removeUnlocked(authority) {
|
|
@@ -1951,6 +1915,9 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
1951
1915
|
const results = await Promise.all([
|
|
1952
1916
|
removeEntry(names.selection),
|
|
1953
1917
|
...fileNames.map(removeEntry),
|
|
1918
|
+
...fileNames.map(function removeArtifactParts(name) {
|
|
1919
|
+
return parts.remove(name);
|
|
1920
|
+
}),
|
|
1954
1921
|
]);
|
|
1955
1922
|
return results.some(Boolean);
|
|
1956
1923
|
}
|
|
@@ -1976,7 +1943,6 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
1976
1943
|
const names = storageNames(authority, metadata.files);
|
|
1977
1944
|
const storedSelection = await readJsonFile(names.selection);
|
|
1978
1945
|
if (JSON.stringify(storedSelection) !== JSON.stringify(cacheSelection(authority))) {
|
|
1979
|
-
await removeUnlocked(authority);
|
|
1980
1946
|
return null;
|
|
1981
1947
|
}
|
|
1982
1948
|
const files = [];
|
|
@@ -1996,9 +1962,8 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
1996
1962
|
unit: 'files',
|
|
1997
1963
|
}
|
|
1998
1964
|
);
|
|
1999
|
-
const file = await
|
|
1965
|
+
const file = await readArtifact(names.files[index], signal);
|
|
2000
1966
|
if (!file) {
|
|
2001
|
-
await removeUnlocked(authority);
|
|
2002
1967
|
return null;
|
|
2003
1968
|
}
|
|
2004
1969
|
files.push({ descriptor, file });
|
|
@@ -2037,7 +2002,6 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
2037
2002
|
routing,
|
|
2038
2003
|
});
|
|
2039
2004
|
} catch (error) {
|
|
2040
|
-
await removeUnlocked(authority);
|
|
2041
2005
|
throw error;
|
|
2042
2006
|
}
|
|
2043
2007
|
}
|
|
@@ -2050,19 +2014,36 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
2050
2014
|
if (!is.function(fetchFunction)) {
|
|
2051
2015
|
throw speechError("ARCANE_AI_ARTIFACT_SOURCE_UNAVAILABLE", "Browser fetch is unavailable.");
|
|
2052
2016
|
}
|
|
2053
|
-
await removeUnlocked(authority);
|
|
2054
2017
|
const installed = [];
|
|
2055
2018
|
try {
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2019
|
+
const selection = cacheSelection(authority);
|
|
2020
|
+
const storedSelection = await readJsonFile(names.selection);
|
|
2021
|
+
if (JSON.stringify(storedSelection) !== JSON.stringify(selection)) {
|
|
2022
|
+
await removeUnlocked(authority);
|
|
2023
|
+
// Selection describes the caller's resources, not download completion.
|
|
2024
|
+
await parts.writePart(
|
|
2025
|
+
names.selection,
|
|
2026
|
+
new TextEncoder().encode(`${JSON.stringify(selection)}\n`),
|
|
2027
|
+
{ signal },
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2063
2030
|
for (let index = 0; index < metadata.files.length; index += 1) {
|
|
2064
2031
|
throwIfAborted(signal);
|
|
2065
2032
|
const descriptor = metadata.files[index];
|
|
2033
|
+
const cachedFile = await readArtifact(names.files[index], signal);
|
|
2034
|
+
if (cachedFile) {
|
|
2035
|
+
installed.push({descriptor, file: cachedFile});
|
|
2036
|
+
reportArtifactProgress(onProgress, signal, {
|
|
2037
|
+
phase: 'prepare',
|
|
2038
|
+
stage: 'cache',
|
|
2039
|
+
message: 'Opened cached speech file',
|
|
2040
|
+
file: descriptor.path,
|
|
2041
|
+
completed: installed.length,
|
|
2042
|
+
total: metadata.files.length,
|
|
2043
|
+
unit: 'files'
|
|
2044
|
+
});
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2066
2047
|
const sourceUrl = graph ? descriptor.sourceUrl : descriptor.url;
|
|
2067
2048
|
reportArtifactProgress(
|
|
2068
2049
|
onProgress,
|
|
@@ -2139,8 +2120,21 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
2139
2120
|
`A speech artifact server returned HTTP ${String(responseStatus)}.`,
|
|
2140
2121
|
);
|
|
2141
2122
|
}
|
|
2142
|
-
await
|
|
2143
|
-
|
|
2123
|
+
const file = await parts.write(names.files[index], responseBody, {
|
|
2124
|
+
signal,
|
|
2125
|
+
onProgress: function reportSpeechShards(shards) {
|
|
2126
|
+
reportArtifactProgress(onProgress, signal, {
|
|
2127
|
+
phase: 'download',
|
|
2128
|
+
stage: 'artifacts',
|
|
2129
|
+
message: 'Storing speech file',
|
|
2130
|
+
file: descriptor.path,
|
|
2131
|
+
completed: installed.length,
|
|
2132
|
+
total: metadata.files.length,
|
|
2133
|
+
unit: 'files',
|
|
2134
|
+
resource: {...shards, file: descriptor.path},
|
|
2135
|
+
});
|
|
2136
|
+
},
|
|
2137
|
+
});
|
|
2144
2138
|
if (!file) {
|
|
2145
2139
|
if (graph) {
|
|
2146
2140
|
throw graphVerificationError(
|
|
@@ -2186,7 +2180,8 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
2186
2180
|
routing,
|
|
2187
2181
|
});
|
|
2188
2182
|
} catch (error) {
|
|
2189
|
-
|
|
2183
|
+
// Complete resources remain available for the next explicit activation.
|
|
2184
|
+
// The active writer owns cleanup of only its unfinished resource.
|
|
2190
2185
|
throw error;
|
|
2191
2186
|
}
|
|
2192
2187
|
}
|
|
@@ -2330,6 +2325,7 @@ export function createDbopfsSpeechArtifactStore({
|
|
|
2330
2325
|
tableName,
|
|
2331
2326
|
prepare,
|
|
2332
2327
|
remove,
|
|
2328
|
+
fetchResource: resources.fetchResource,
|
|
2333
2329
|
});
|
|
2334
2330
|
STORES.add(store);
|
|
2335
2331
|
return store;
|
|
@@ -906,7 +906,7 @@ function normalizeSynthesisPayload(payload, authority, speechInputPrepared) {
|
|
|
906
906
|
if (shared) {
|
|
907
907
|
descriptors = genericPayloadDescriptors(
|
|
908
908
|
payload,
|
|
909
|
-
["model", "voice", "input", "responseFormat", "speed"],
|
|
909
|
+
["model", "voice", "input", "textFormat", "responseFormat", "speed"],
|
|
910
910
|
["model", "input", "responseFormat"],
|
|
911
911
|
"Shared speech synthesis payload",
|
|
912
912
|
"tts-synthesis",
|
|
@@ -927,7 +927,7 @@ function normalizeSynthesisPayload(payload, authority, speechInputPrepared) {
|
|
|
927
927
|
} else {
|
|
928
928
|
descriptors = genericPayloadDescriptors(
|
|
929
929
|
payload,
|
|
930
|
-
["model", "voice", "text", "speed"],
|
|
930
|
+
["model", "voice", "text", "textFormat", "speed"],
|
|
931
931
|
["text"],
|
|
932
932
|
"Browser Kokoro synthesis payload",
|
|
933
933
|
"tts-synthesis",
|
|
@@ -936,7 +936,9 @@ function normalizeSynthesisPayload(payload, authority, speechInputPrepared) {
|
|
|
936
936
|
textValue = descriptors.text.value;
|
|
937
937
|
}
|
|
938
938
|
const suppliedText = is.string(textValue) ? textValue : "";
|
|
939
|
-
const text = speechInputPrepared === true
|
|
939
|
+
const text = speechInputPrepared === true || descriptors.textFormat?.value === "plain"
|
|
940
|
+
? suppliedText
|
|
941
|
+
: stripSpeechFormatting(suppliedText);
|
|
940
942
|
const voiceValue = Object.hasOwn(descriptors, "voice")
|
|
941
943
|
? descriptors.voice.value
|
|
942
944
|
: authority.defaultVoice;
|
|
@@ -1314,6 +1316,9 @@ function createBrowserSpeechProvider({
|
|
|
1314
1316
|
};
|
|
1315
1317
|
slot.client = createSpeechWorkerClient({
|
|
1316
1318
|
role,
|
|
1319
|
+
fetchResource: function fetchSpeechWorkerResource(input, options) {
|
|
1320
|
+
return store.fetchResource(input, options);
|
|
1321
|
+
},
|
|
1317
1322
|
onTermination: function handleSpeechWorkerTermination(termination) {
|
|
1318
1323
|
handlePoolTermination(pool, slot, termination);
|
|
1319
1324
|
},
|