arcane-os 0.5.15 → 0.5.16
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 +10 -0
- package/README.md +20 -8
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +5 -5
- package/docs/reference/ai/browser-speech.md +46 -7
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +18 -5
- package/docs/reference/protocols.md +5 -5
- package/docs/reference/runtime-components.md +9 -6
- package/docs/reference/runtime-modules.md +52 -4
- package/docs/reference/sdk-api.md +5 -5
- package/examples/wasm-ai-demo/README.md +3 -1
- package/examples/wasm-ai-demo/app.js +2 -1
- package/package.json +2 -2
- package/runtime/arcane/components/chat.html +3 -1
- package/runtime/arcane/modules/AI.js +22 -2
- package/runtime/arcane/modules/MarkdownSpeech.js +50 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.16
|
|
6
|
+
|
|
7
|
+
- Add optional Markdown narration filtering before `AI.streamTTS()`
|
|
8
|
+
segmentation. Omit repeated same formatting marks across streamed chunks,
|
|
9
|
+
preserve single marks and ordinary punctuation, and clear formatting state
|
|
10
|
+
on terminal flush or cancellation. Keep plain speech, displayed and stored
|
|
11
|
+
text, language, voice, synthesis capacity, and audio scheduling unchanged.
|
|
12
|
+
- Select Markdown narration in shared chat and include focused filter test
|
|
13
|
+
source without adding a parser dependency.
|
|
14
|
+
|
|
5
15
|
## 0.5.15
|
|
6
16
|
|
|
7
17
|
- Let `SpeechPlayback.prepare()` submit every complete segment immediately when
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.5.
|
|
22
|
+
This checkout defines the `0.5.16` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
@@ -35,7 +35,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
35
35
|
server:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
npx arcane-os@0.5.
|
|
38
|
+
npx arcane-os@0.5.16 new hello-speech --path ./hello-speech --target browser
|
|
39
39
|
cd hello-speech
|
|
40
40
|
npm install
|
|
41
41
|
npm run dev
|
|
@@ -51,6 +51,10 @@ For a first spoken sentence, copy the application-owned
|
|
|
51
51
|
beside `App.js`, then use this module. That one configuration file defines the
|
|
52
52
|
upstream runtime, model, dtype, and voice. This module creates the application's
|
|
53
53
|
DBOPFS store; the SDK creates and manages its speech providers and Workers.
|
|
54
|
+
The linked automatic/WebGPU-first selection uses `fp32` because
|
|
55
|
+
[Kokoro.js recommends `fp32` when using WebGPU](https://github.com/hexgrad/kokoro/tree/main/kokoro.js#usage).
|
|
56
|
+
Its `selectedDevice` status reports the loaded route, not speech correctness or
|
|
57
|
+
audio quality.
|
|
54
58
|
|
|
55
59
|
```javascript
|
|
56
60
|
import arcaneThemeReady from 'arcane/ThemeBootstrap';
|
|
@@ -301,6 +305,14 @@ keeping apostrophes, commas, and hyphens that join Unicode letters or numbers
|
|
|
301
305
|
inside the same segment.
|
|
302
306
|
Mute, stop, provider transition, and cancellation still govern the whole queue.
|
|
303
307
|
|
|
308
|
+
For raw Markdown narration, use
|
|
309
|
+
`ai.streamTTS(text,false,{textFormat:'markdown'})`. The SDK removes repeated
|
|
310
|
+
same formatting marks (`*`, `#`, `_`, backtick, `~`) before segmentation,
|
|
311
|
+
including runs split across chunks. Single punctuation and ordinary repeated
|
|
312
|
+
punctuation remain literal. The format lasts through `finishTTS()`; new
|
|
313
|
+
streams default to exact plain text. Shared chat selects Markdown mode while
|
|
314
|
+
preserving original messages for display and storage.
|
|
315
|
+
|
|
304
316
|
The SDK runtime also owns `DBOPFSDocumentLibrary`,
|
|
305
317
|
`DocumentLexicalSearch`, and `PersistentAIChatSession`. Document bootstrap is
|
|
306
318
|
schema-driven and explicit; chat never searches a corpus unless the app wires
|
|
@@ -322,7 +334,7 @@ uses the same controller for automatic memory extraction.
|
|
|
322
334
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
323
335
|
|
|
324
336
|
```bash
|
|
325
|
-
npx arcane-os@0.5.
|
|
337
|
+
npx arcane-os@0.5.16 new my-app --path ./my-app --target portable --git
|
|
326
338
|
cd my-app
|
|
327
339
|
npm install
|
|
328
340
|
npm run dev
|
|
@@ -332,7 +344,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
332
344
|
missing Arcane files:
|
|
333
345
|
|
|
334
346
|
```bash
|
|
335
|
-
npm install --save-dev --save-exact arcane-os@0.5.
|
|
347
|
+
npm install --save-dev --save-exact arcane-os@0.5.16
|
|
336
348
|
npm exec -- arcane init my-app --target portable
|
|
337
349
|
```
|
|
338
350
|
|
|
@@ -348,7 +360,7 @@ npm exec -- arcane-os targets
|
|
|
348
360
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
349
361
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
350
362
|
|
|
351
|
-
Use `npx arcane-os@0.5.
|
|
363
|
+
Use `npx arcane-os@0.5.16` for the initial bootstrap because it names this npm
|
|
352
364
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
353
365
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
354
366
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -368,7 +380,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
368
380
|
|
|
369
381
|
# From the generated app repository
|
|
370
382
|
cd ../local-app
|
|
371
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.
|
|
383
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.16.tgz
|
|
372
384
|
npm ci
|
|
373
385
|
```
|
|
374
386
|
|
|
@@ -377,7 +389,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
377
389
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
378
390
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
379
391
|
runner also needs that tarball at the locked path. After publication, replace
|
|
380
|
-
the local declaration with the exact `arcane-os@0.5.
|
|
392
|
+
the local declaration with the exact `arcane-os@0.5.16` registry package and
|
|
381
393
|
commit the regenerated lock.
|
|
382
394
|
|
|
383
395
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -516,7 +528,7 @@ package installation, or assertions.
|
|
|
516
528
|
|
|
517
529
|
## Current target support
|
|
518
530
|
|
|
519
|
-
Version `0.5.
|
|
531
|
+
Version `0.5.16` exposes one browser target and five explicitly paired
|
|
520
532
|
native development targets: a non-runnable portable directory, a
|
|
521
533
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
522
534
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
package/docs/architecture.md
CHANGED
|
@@ -279,7 +279,7 @@ paths are withheld from the native provider. The provider copies the complete
|
|
|
279
279
|
selected release rather than accepting an unrelated source path. Verification
|
|
280
280
|
is a separate explicit operation for a selected release artifact.
|
|
281
281
|
|
|
282
|
-
The SDK `0.5.
|
|
282
|
+
The SDK `0.5.16` runtime requires Arcane `0.8.12` or newer. Compatibility
|
|
283
283
|
is contractual rather than exact-version pinning: the prepared Core must meet
|
|
284
284
|
the highest minimum declared by the runtime, selected app, and bundled app
|
|
285
285
|
dependencies; keep each app's Arcane protocol generation; and provide every
|
package/docs/reference/README.md
CHANGED
|
@@ -17,7 +17,7 @@ high-level page links to the relevant deep section instead of repeating it.
|
|
|
17
17
|
Install the SDK in your application:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
npm install --save-exact arcane-os@0.5.
|
|
20
|
+
npm install --save-exact arcane-os@0.5.16
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
For your first AI call, follow the [TWiN Cloud quick start](ai/twin-cloud.md).
|
|
@@ -58,9 +58,9 @@ This repository contains explicitly versioned surfaces with different owners:
|
|
|
58
58
|
|
|
59
59
|
| Surface | Source identity | Meaning |
|
|
60
60
|
| --- | --- | --- |
|
|
61
|
-
| SDK and CLI | `arcane-os` `0.5.
|
|
62
|
-
| Browser runtime | SDK `0.5.
|
|
63
|
-
| Browser SDK runtime | SDK `0.5.
|
|
61
|
+
| SDK and CLI | `arcane-os` `0.5.16` | The Node.js toolchain, portable `arcane-os/event-manager`, `arcane-os/logging`, `arcane-os/mail`, `arcane-os/preference-store`, and `arcane-os/speech-playback` entrypoints, plus the browser-only `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints in this checkout. |
|
|
62
|
+
| Browser runtime | SDK `0.5.16`, protocol `arcane/1`, `runtime/` | The SDK-canonical runtime tree. `listRuntimeFiles()`, `readRuntimeFile()`, and `loadRuntimeRelease()` derive its current inventory directly from the selected directory. |
|
|
63
|
+
| Browser SDK runtime | SDK `0.5.16`, `browser-runtime/` | The browser closure for events, shared logging, Wllama, and Browser Speech mechanisms. `listSdkBrowserRuntimeFiles()`, `readSdkBrowserRuntimeFile()`, and `loadSdkBrowserRuntimeRelease()` derive its current inventory directly from the selected directory. |
|
|
64
64
|
| Core reference snapshot | Arcane OS commit `567ad110bf57a1c2d4a3daa22ae93716cc5f4d7e`, protocol `arcane/1` | The application-facing Core contract imported into `docs/reference/core/`, with SDK-local links and package-boundary notes added explicitly. |
|
|
65
65
|
|
|
66
66
|
The SDK runtime source and Core reference have different owners. A browser
|
|
@@ -75,7 +75,7 @@ and the distinction between a documentation snapshot and the selected runtime.
|
|
|
75
75
|
|
|
76
76
|
## Installed documentation and release identity
|
|
77
77
|
|
|
78
|
-
This reference accompanies `arcane-os@0.5.
|
|
78
|
+
This reference accompanies `arcane-os@0.5.16`. The installed package includes
|
|
79
79
|
the maintained `docs/` tree and `examples/wasm-ai-demo/` source alongside
|
|
80
80
|
README and CHANGELOG. Open `node_modules/arcane-os/docs/reference/README.md`
|
|
81
81
|
for the matching local reference. The generated website and test suites remain
|
|
@@ -13,7 +13,7 @@ import map resolves `arcane/AI` and `arcane/DBOPFS`. These browser modules are
|
|
|
13
13
|
not Node inference APIs. To create an application:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx arcane-os@0.5.
|
|
16
|
+
npx arcane-os@0.5.16 new hello-speech --path ./hello-speech --target browser
|
|
17
17
|
cd hello-speech
|
|
18
18
|
npm install
|
|
19
19
|
npm run dev
|
|
@@ -37,7 +37,7 @@ export const speechSelection = {
|
|
|
37
37
|
id: 'onnx-community/Kokoro-82M-v1.0-ONNX',
|
|
38
38
|
repository: 'onnx-community/Kokoro-82M-v1.0-ONNX',
|
|
39
39
|
revision: '1939ad2a8e416c0acfeecc08a694d14ef25f2231',
|
|
40
|
-
dtype: '
|
|
40
|
+
dtype: 'fp32',
|
|
41
41
|
defaultVoice: 'af_heart'
|
|
42
42
|
},
|
|
43
43
|
runtime: {
|
|
@@ -55,6 +55,15 @@ export const speechSelection = {
|
|
|
55
55
|
};
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
The omitted execution record below uses the SDK's WebGPU-first automatic
|
|
59
|
+
selection, so this basic configuration uses `fp32` because
|
|
60
|
+
[Kokoro.js recommends `fp32` when using WebGPU](https://github.com/hexgrad/kokoro/tree/main/kokoro.js#usage).
|
|
61
|
+
Automatic fallback carries this same selected model and dtype to WASM; the SDK
|
|
62
|
+
does not rewrite the application selection. If you intentionally choose
|
|
63
|
+
another dtype, evaluate that exact model, browser, and execution route.
|
|
64
|
+
`selectedDevice` reports routing after load, not pronunciation, text fidelity,
|
|
65
|
+
or audio quality.
|
|
66
|
+
|
|
58
67
|
Then use this **`App.js`**. The application creates and owns the DBOPFS
|
|
59
68
|
instance. Configuration selects the provider without loading it; the button
|
|
60
69
|
explicitly loads and unmutes TTS before requesting speech.
|
|
@@ -221,8 +230,10 @@ only failed missing segments.
|
|
|
221
230
|
|
|
222
231
|
The default `{device:'auto',maxConcurrentRequests:4}` attempts the full ONNX
|
|
223
232
|
Worker/session pool on WebGPU, then recreates that pool on WASM only if WebGPU
|
|
224
|
-
loading fails.
|
|
225
|
-
|
|
233
|
+
loading fails. The basic configuration above keeps `fp32` for this WebGPU-first
|
|
234
|
+
path and any automatic WASM fallback. Use the status example below to read
|
|
235
|
+
`selectedDevice`; console node-assignment warnings alone do not identify the
|
|
236
|
+
selected execution device or assess the generated audio.
|
|
226
237
|
|
|
227
238
|
## Queue complete passages and wait for playback
|
|
228
239
|
|
|
@@ -275,8 +286,9 @@ all speech owned by that AI instance and settles pending playback results
|
|
|
275
286
|
The selected voice and speed are captured for segments extracted by that call.
|
|
276
287
|
That includes any text left in the same AI instance's partial-stream buffer;
|
|
277
288
|
finish the previous producer before starting a separate complete passage.
|
|
278
|
-
|
|
279
|
-
call supplies its own options, and `finishTTS()`
|
|
289
|
+
Voice, speed, pause, and playback options are not retained with an unfinished
|
|
290
|
+
`end:false` remainder. A later call supplies its own options, and `finishTTS()`
|
|
291
|
+
uses their defaults. The optional text format below lasts through that flush.
|
|
280
292
|
A call extracting no segments returns `true` without waiting for earlier jobs.
|
|
281
293
|
An already muted call returns `false`.
|
|
282
294
|
|
|
@@ -337,6 +349,30 @@ boundary; `finishTTS()` flushes any remaining text. It is not a playback-ended
|
|
|
337
349
|
notification. Do not mute or dispose immediately after it if playback should
|
|
338
350
|
continue.
|
|
339
351
|
|
|
352
|
+
## Omit Markdown formatting marks from narration
|
|
353
|
+
|
|
354
|
+
Select `textFormat:'markdown'` for raw model text that contains formatting:
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
ai.streamTTS('## Heading\n**Hello', false, {textFormat:'markdown'});
|
|
358
|
+
ai.streamTTS('**. Next sentence.');
|
|
359
|
+
await ai.finishTTS();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The SDK omits runs of two or more of the same `*`, `#`, `_`, backtick, or `~`
|
|
363
|
+
before speech segmentation. A candidate run split across chunks is still
|
|
364
|
+
recognized. Single marks, ellipses, quoted endings, and all other text are
|
|
365
|
+
preserved. This is a small narration filter, not a full Markdown parser.
|
|
366
|
+
Ordinary prose is forwarded immediately; only a trailing formatting candidate
|
|
367
|
+
waits for its next character or the final flush.
|
|
368
|
+
|
|
369
|
+
The format stays active until `end:true`, `finishTTS()`, or cancellation. New
|
|
370
|
+
streams default to `plain`, which preserves exact submitted text. The shared
|
|
371
|
+
chat component selects Markdown mode automatically. Applications already
|
|
372
|
+
speaking visible DOM text can keep plain mode. Displayed messages, saved
|
|
373
|
+
history, model input, language, voice, synthesis capacity, and playback timing
|
|
374
|
+
are not changed by the filter.
|
|
375
|
+
|
|
340
376
|
## Choose a device or reduce memory use
|
|
341
377
|
|
|
342
378
|
Omitting `tts.execution` selects `{device:'auto',maxConcurrentRequests:4}`.
|
|
@@ -401,6 +437,8 @@ until a pool is selected and returns to `null` on unload. Providers without an
|
|
|
401
437
|
execution report omit `execution`; do not infer a device from `navigator.gpu`
|
|
402
438
|
or a configured preference alone. An explicit inspection can throw a provider
|
|
403
439
|
status error; handle it with the same `error.code` / `error.message` pattern.
|
|
440
|
+
Treat `selectedDevice` as route status only; evaluate actual speech output for
|
|
441
|
+
the model, dtype, browser, and device combinations your application supports.
|
|
404
442
|
|
|
405
443
|
## Stop, mute, cancel, and release
|
|
406
444
|
|
|
@@ -875,7 +913,8 @@ cache state, and warnings. Kokoro status also includes an `execution` record
|
|
|
875
913
|
with requested and selected device, request limit, and active request count. A
|
|
876
914
|
successful `selectedDevice:'webgpu'` reports the execution provider selected by
|
|
877
915
|
the upstream model load; it does not claim that browser, driver, or GPU kernels
|
|
878
|
-
overlap physically
|
|
916
|
+
overlap physically or that generated audio has been quality-validated. A
|
|
917
|
+
security field is absent in ordinary mode.
|
|
879
918
|
|
|
880
919
|
The provider/2 load context accepts an optional progress callback for interface
|
|
881
920
|
compatibility, but the current browser-speech artifact and Worker transport
|
|
@@ -9,7 +9,7 @@ same managed browser imports as the [browser speech quick start](browser-speech.
|
|
|
9
9
|
Create an application and start its source server:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx arcane-os@0.5.
|
|
12
|
+
npx arcane-os@0.5.16 new hello-twin --path ./hello-twin --target browser
|
|
13
13
|
cd hello-twin
|
|
14
14
|
npm install
|
|
15
15
|
npm run dev
|
package/docs/reference/cli.md
CHANGED
|
@@ -214,7 +214,7 @@ portable runtime subpaths such as `arcane-os/preference-store` and
|
|
|
214
214
|
modules. The result reports the complete map written to the selected
|
|
215
215
|
application; no fixed entry count is a release contract.
|
|
216
216
|
|
|
217
|
-
SDK `0.5.
|
|
217
|
+
SDK `0.5.16` preserves the physical workspace route count and ordered include
|
|
218
218
|
list. External and modern integrated routes require `components`, `css`,
|
|
219
219
|
`dependencies`, `entities`, `img`, `modules`, and `sdk`; a physical workspace
|
|
220
220
|
may omit only an optional trailing `security` include. The external license
|
|
@@ -9,7 +9,7 @@ They are not TypeScript declarations.
|
|
|
9
9
|
|
|
10
10
|
## Portable SDK AI and Core AI
|
|
11
11
|
|
|
12
|
-
The SDK `0.5.
|
|
12
|
+
The SDK `0.5.16` has two related but separate normalized boundaries:
|
|
13
13
|
|
|
14
14
|
| Boundary | Use | Host |
|
|
15
15
|
|---|---|---|
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
"repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
|
|
6
6
|
"branch": "main",
|
|
7
7
|
"path": "runtime/arcane",
|
|
8
|
-
"sdkVersion": "0.5.
|
|
8
|
+
"sdkVersion": "0.5.16",
|
|
9
9
|
"protocol": "arcane/1"
|
|
10
10
|
},
|
|
11
|
-
"artifactCount":
|
|
12
|
-
"javascriptArtifactCount":
|
|
13
|
-
"esmExportCount":
|
|
11
|
+
"artifactCount": 85,
|
|
12
|
+
"javascriptArtifactCount": 83,
|
|
13
|
+
"esmExportCount": 354,
|
|
14
14
|
"artifacts": [
|
|
15
15
|
{
|
|
16
16
|
"file": "runtime/arcane/modules/AI.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"availability": "Browser + native bridge + TWiN Cloud",
|
|
31
31
|
"protocol": "arcane-ai-browser-speech-configuration/1, AIProviderRuntime arcane-ai-provider/2 routes, globalThis.arcaneEvents, TWiN Cloud HTTPS, Arcane.ollama, Arcane.speech, Android WebView bridge",
|
|
32
32
|
"normalization": "Complete mutable caller-owned browser speech authority, SDK-owned provider registration/replacement/disposal, explicit STT/TTS activation, normalized Kokoro TTS execution with default capacity 4, explicit provider-neutral execution snapshots through status(role,{execution:true}), sticky readiness, canonical window.user readiness without compatibility-event object-identity admission or polling, exact ordered structural calls with required arguments.message, atomic nonblank all-ID tool-result sequencing, complete all-choice streaming/data validation, native Ollama structural adaptation, complete-response then all validated tool callbacks then completion ordering, observed async callbacks, Blob/File transcription, immediate exact-segment synthesis, per-call voice/speed capture, optional final-segment pause on the existing audio clock, optional per-call terminal playback completion, ordered audio-clock scheduling, playable audio, active-generation TTS operation-failure routing, mute, and cancellation are normalized; provider/model/runtime/voice policy remains caller-owned.",
|
|
33
|
-
"surface": "Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs,
|
|
33
|
+
"surface": "Browser-speech protocol/event/error/reason constants; default `AI`; read-only `providerRuntime`, `browserSpeechConfiguration`, and `browserSpeechDescriptor`; explicit `providerRuntime.status(role,{execution:true})` snapshots; `streamTTS(text,end,options={})` with optional voice, speed, pauseAfterMs, waitForPlayback, and speech-only textFormat plain/markdown; configure/dispose speech, route lifecycle, declaration-validated chat/stream requests with exact ordered calls, synthesis/transcription, and playback controls; initializes from current canonical user readiness, installs `window.ai`, and projects `ai-ready` plus active-generation `ai-tts-failure`."
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"file": "runtime/arcane/modules/AIPreferenceRuntime.js",
|
|
@@ -824,6 +824,19 @@
|
|
|
824
824
|
"normalization": "Endpoint, timeout, cancellation, network, HTTP, and response-contract errors are normalized.",
|
|
825
825
|
"surface": "`MailTransportError`, `normalizeMailEndpoint()`, `serializeMailReport()`, and `sendMailReport()`."
|
|
826
826
|
},
|
|
827
|
+
{
|
|
828
|
+
"file": "runtime/arcane/modules/MarkdownSpeech.js",
|
|
829
|
+
"name": "MarkdownSpeech.js",
|
|
830
|
+
"kind": "esm",
|
|
831
|
+
"exports": [
|
|
832
|
+
"MarkdownSpeech"
|
|
833
|
+
],
|
|
834
|
+
"summary": "Removes repeated same Markdown formatting marks from streamed narration before speech segmentation.",
|
|
835
|
+
"availability": "Cross-host",
|
|
836
|
+
"protocol": "In-process only",
|
|
837
|
+
"normalization": "Speech-only filtering of repeated *, #, _, backtick, and ~ runs across chunks; single marks and all other characters remain literal; terminal flush and reset clear pending formatting state.",
|
|
838
|
+
"surface": "`MarkdownSpeech`; `append(text='',end=false)`, `reset()`."
|
|
839
|
+
},
|
|
827
840
|
{
|
|
828
841
|
"file": "runtime/arcane/modules/Marked.min.js",
|
|
829
842
|
"name": "Marked.min.js",
|
|
@@ -159,7 +159,7 @@ imports such as:
|
|
|
159
159
|
import ollama from 'arcane/Ollama';
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
The physical-v1 tree lives entirely beneath `arcane/`. SDK `0.5.
|
|
162
|
+
The physical-v1 tree lives entirely beneath `arcane/`. SDK `0.5.16` projects the
|
|
163
163
|
complete canonical runtime and browser runtime selected by the installed SDK
|
|
164
164
|
package. Runtime dependencies stay under
|
|
165
165
|
`arcane/dependencies/`; the SDK event and browser-AI closure stays under
|
|
@@ -174,7 +174,7 @@ integrated physical route uses the same ordered include list in its one route.
|
|
|
174
174
|
Omitting only that final optional entry is compatible. Removing, reordering, or
|
|
175
175
|
renaming any preceding entry changes the physical contract.
|
|
176
176
|
|
|
177
|
-
The `0.5.
|
|
177
|
+
The `0.5.16` map derives its complete entries from the selected runtime graph;
|
|
178
178
|
application source imports do not select a fixed entry count. The operation
|
|
179
179
|
result reports `imports`, `entryCount`, and `excludedModules`; reached-file
|
|
180
180
|
traversal remains internal. The
|
|
@@ -239,8 +239,8 @@ exactly `dependencyName`, `packageSource`,
|
|
|
239
239
|
`canonicalPackageRoot`, `packageName`, `packageVersion`, `runtimeRoot`,
|
|
240
240
|
and `browserRuntimeRoot`. A
|
|
241
241
|
workspace may use the canonical dependency name or one exact npm alias such as
|
|
242
|
-
`npm:arcane-os@0.5.
|
|
243
|
-
exactly as `arcane-os@0.5.
|
|
242
|
+
`npm:arcane-os@0.5.16`; the physical package manifest must still identify
|
|
243
|
+
exactly as `arcane-os@0.5.16`. Canonical-plus-alias duplicates, multiple aliases,
|
|
244
244
|
links/junctions, indirect package roots, or version drift are reported.
|
|
245
245
|
|
|
246
246
|
For external workspaces, `arcane dev` serves the projected `arcane/` root,
|
|
@@ -351,7 +351,7 @@ does not silently delete the app-owned cache. A complete whole member supersedes
|
|
|
351
351
|
its current resumable fragments. Cleanup failure is warned without hiding the
|
|
352
352
|
usable model.
|
|
353
353
|
|
|
354
|
-
SDK `0.5.
|
|
354
|
+
SDK `0.5.16` requires WebGPU. Load requests full offload and waits for the runtime
|
|
355
355
|
to report a loaded model. `navigator.gpu` presence alone is
|
|
356
356
|
not readiness. There is no CPU fallback, partial-offload success mode, or
|
|
357
357
|
silent switch to native/Core/cloud inference.
|
|
@@ -360,17 +360,20 @@ unload.
|
|
|
360
360
|
Chat listens for the bound (or current global) AI runtime's `ai-tts-failure`
|
|
361
361
|
event and forwards its complete Error and exact operation boundary to
|
|
362
362
|
`speech.reportTTSError()`. Playback-start or playback-resume failures can occur
|
|
363
|
-
after Chat's
|
|
363
|
+
after Chat's `streamTTS()` preparation promise has resolved.
|
|
364
364
|
SDK `0.5.12` adds a `waitForPlayback:true` mode for the
|
|
365
365
|
terminal result of the segments submitted by one invocation; Chat continues
|
|
366
366
|
to feed chunks without waiting for playback. Runtime mute, cancellation,
|
|
367
367
|
permission waiting, and stale generations remain non-errors.
|
|
368
368
|
|
|
369
|
-
Each visible model chunk is
|
|
370
|
-
order. The AI
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
369
|
+
Each visible model chunk is forwarded to
|
|
370
|
+
`AI.streamTTS(text,false,{textFormat:'markdown'})` in arrival order. The AI
|
|
371
|
+
owner removes repeated Markdown formatting marks from narration before the
|
|
372
|
+
configured segmentation, admits completed segments to bounded synthesis
|
|
373
|
+
immediately, and schedules the contiguous ready audio prefix on the audio
|
|
374
|
+
clock. Chat retains the original Markdown for display and storage and owns no
|
|
375
|
+
second speech queue. Single punctuation, ordinary repeated punctuation,
|
|
376
|
+
language, and voice retain their existing behavior.
|
|
374
377
|
|
|
375
378
|
Events: `chat-ready`, `chat-session-bound`, `chat-session-message`,
|
|
376
379
|
`chat-session-error`, `chat-send-message`, `chat-send-error`,
|
|
@@ -88,6 +88,7 @@ own asynchronous work, cancellation, and backpressure.
|
|
|
88
88
|
| [`Mail.js`](#mailjs) | esm | Builds complete reports and prefers the native mail capability with an explicit HTTP transport fallback. | Browser/native hybrid + cloud | Mail inputs/results normalized; transport failures mixed. |
|
|
89
89
|
| [`MailOutbox.mjs`](#mailoutboxmjs) | esm | Persists complete mail reports before delivery and normalizes idempotent enqueue, retry, reconciliation, and invalid-record maintenance. | Browser/native WebView or compatible injected host | Complete records, full work, cancellation, and lifecycle states normalized; storage, lock, and delivery failures coded. |
|
|
90
90
|
| [`MailTransport.mjs`](#mailtransportmjs) | esm | Sends one complete mail report to a normalized HTTP(S) endpoint. | Browser/server with fetch + cloud | Normalized endpoint and transport errors; remote detail preserved. |
|
|
91
|
+
| [`MarkdownSpeech.js`](#markdownspeechjs) | esm | Removes repeated Markdown formatting marks from streamed narration. | Cross-host | Speech-only filtering; single marks and ordinary punctuation remain literal. |
|
|
91
92
|
| [`Marked.min.js`](#markedminjs) | esm | Vendored Marked 18.0.5 Markdown lexer, parser, renderer, extension, and walk-token API. | Cross-host vendor module | Vendor-native Marked contract. |
|
|
92
93
|
| [`MD.js`](#mdjs) | esm | Renders complete Markdown with Marked and exposes the complete rendered markup. | Browser / native WebView | Complete raw and rendered Marked values; parse errors vendor-native. |
|
|
93
94
|
| [`MemoryRecords.js`](#memoryrecordsjs) | esm | Normalizes memory content and detects meaningful stored memory. | Cross-host | Fully normalized string/boolean results. |
|
|
@@ -274,7 +275,7 @@ rejected. It propagates the caller-owned signal and returns a playable `Blob`;
|
|
|
274
275
|
it does not independently choose a provider, cloud fallback, model, runtime, or
|
|
275
276
|
voice policy for the application. `streamTTS(text='',end=false,options={})` and
|
|
276
277
|
`finishTTS()` use this same request boundary. The third-argument options below
|
|
277
|
-
are available in SDK `0.5.12`:
|
|
278
|
+
are available in SDK `0.5.12`, with `textFormat` added in SDK `0.5.16`:
|
|
278
279
|
|
|
279
280
|
| Field | Default | Meaning |
|
|
280
281
|
| --- | --- | --- |
|
|
@@ -282,12 +283,24 @@ are available in SDK `0.5.12`:
|
|
|
282
283
|
| `speed` | Current `ai.voiceSpeed` | A supplied positive speed is captured for those segments and forwarded to `fetchTTS()`. It does not change `ai.voiceSpeed`. |
|
|
283
284
|
| `pauseAfterMs` | `0` | Finite, nonnegative milliseconds placed after the final extracted segment on the existing audio clock. Invalid values throw `RangeError`; no pause is inserted between this call's other segments. |
|
|
284
285
|
| `waitForPlayback` | `false` | Omission retains the preparation promise. With `true`, the promise resolves after every extracted segment reaches a terminal playback state: `true` when all naturally end, or `false` after terminal cancellation or failure. |
|
|
286
|
+
| `textFormat` | `plain` for a new stream | `markdown` removes repeated same formatting marks (`*`, `#`, `_`, backtick, `~`) before segmentation. Single marks and ordinary punctuation remain literal. The selected format lasts through this producer's terminal flush. |
|
|
285
287
|
|
|
286
288
|
The voice and speed use the existing `fetchTTS()` validation and error path.
|
|
287
|
-
|
|
289
|
+
Plain mode preserves the submitted text. Markdown mode changes narration only;
|
|
290
|
+
it leaves displayed, stored, and model content untouched. It is a narrow
|
|
291
|
+
formatting-mark filter, not a full Markdown parser: links, code contents, list
|
|
292
|
+
text, and other characters remain literal. A trailing candidate mark waits for
|
|
293
|
+
the next character so a repeated run split across chunks is still omitted.
|
|
294
|
+
Ordinary prose streams immediately. `end:true`, `finishTTS()`, muted terminal
|
|
295
|
+
calls, and `stopAudio()` clear the formatting state. An explicit format change
|
|
296
|
+
flushes pending formatting marks under the preceding mode before accepting
|
|
297
|
+
the next text. `fetchTTS()` retains its exact-text contract.
|
|
298
|
+
|
|
299
|
+
Voice, speed, pause, and playback overrides belong to the segments
|
|
288
300
|
extracted in that invocation, including any text buffered by an earlier call.
|
|
289
|
-
|
|
290
|
-
call supplies its own options, and `finishTTS()` uses defaults
|
|
301
|
+
Those overrides are not retained with an unfinished `end:false` remainder; a
|
|
302
|
+
later call supplies its own options, and `finishTTS()` uses their defaults
|
|
303
|
+
while retaining the active text format. Use `end:true`
|
|
291
304
|
for a complete passage. A call extracting no segments resolves
|
|
292
305
|
`true` without waiting for earlier jobs; `finishTTS()` remains a preparation
|
|
293
306
|
flush, not a queue-wide playback barrier. A muted call resolves `false`.
|
|
@@ -2244,6 +2257,41 @@ import * as module from '/arcane/modules/MailTransport.mjs';
|
|
|
2244
2257
|
console.log(Object.keys(module));
|
|
2245
2258
|
```
|
|
2246
2259
|
|
|
2260
|
+
## MarkdownSpeech.js
|
|
2261
|
+
|
|
2262
|
+
### Overview
|
|
2263
|
+
|
|
2264
|
+
Filters repeated same Markdown formatting marks from narration before speech
|
|
2265
|
+
segmentation. The filter recognizes `*`, `#`, `_`, backtick, and `~` runs of two
|
|
2266
|
+
or more, including runs arriving across separate chunks. Single marks,
|
|
2267
|
+
ellipses, quoted sentence endings, whitespace, and all other text remain
|
|
2268
|
+
literal. It neither interprets links nor changes language or voice.
|
|
2269
|
+
|
|
2270
|
+
### Public surface
|
|
2271
|
+
|
|
2272
|
+
`MarkdownSpeech`; `append(text='',end=false)`, `reset()`.
|
|
2273
|
+
|
|
2274
|
+
Exact exports: `MarkdownSpeech`.
|
|
2275
|
+
|
|
2276
|
+
### Availability and normalization
|
|
2277
|
+
|
|
2278
|
+
**Cross-host.** Plain JavaScript with no dependencies. `append()` returns only
|
|
2279
|
+
the newly available narration. Only a trailing candidate marker and whether
|
|
2280
|
+
it repeats are retained; ordinary text is emitted immediately. Terminal
|
|
2281
|
+
`append(text,true)` flushes a single pending mark and resets state. `reset()`
|
|
2282
|
+
clears pending formatting state when its narration is cancelled. Non-string
|
|
2283
|
+
input throws `TypeError`.
|
|
2284
|
+
|
|
2285
|
+
### Example
|
|
2286
|
+
|
|
2287
|
+
```javascript
|
|
2288
|
+
import {MarkdownSpeech} from '/arcane/modules/MarkdownSpeech.js';
|
|
2289
|
+
|
|
2290
|
+
const speech = new MarkdownSpeech();
|
|
2291
|
+
const first = speech.append('**Hello'); // Hello
|
|
2292
|
+
const last = speech.append('**...', true); // ...
|
|
2293
|
+
```
|
|
2294
|
+
|
|
2247
2295
|
## Marked.min.js
|
|
2248
2296
|
|
|
2249
2297
|
### Overview
|
|
@@ -18,7 +18,7 @@ This table is the Node `package.json#exports` map: it defines package
|
|
|
18
18
|
entrypoints for SDK/tooling code. It is distinct from the generated browser
|
|
19
19
|
import map that resolves application-facing `arcane/*` modules and the focused
|
|
20
20
|
EventManager entry. See [browser runtime delivery](protocols.md#browser-runtime-delivery)
|
|
21
|
-
for the installed-inventory-derived physical-runtime contract in SDK `0.5.
|
|
21
|
+
for the installed-inventory-derived physical-runtime contract in SDK `0.5.16`.
|
|
22
22
|
|
|
23
23
|
| Specifier | Purpose |
|
|
24
24
|
| --- | --- |
|
|
@@ -751,7 +751,7 @@ deterministic map. The package root also contains the public
|
|
|
751
751
|
{
|
|
752
752
|
schemaVersion: 1,
|
|
753
753
|
kind: 'arcane-app-runtime-projection',
|
|
754
|
-
sdkVersion: '0.5.
|
|
754
|
+
sdkVersion: '0.5.16',
|
|
755
755
|
pathPrefix: 'arcane/',
|
|
756
756
|
files: [{path}]
|
|
757
757
|
}
|
|
@@ -3463,7 +3463,7 @@ workspace it additionally returns the exact installed package authority:
|
|
|
3463
3463
|
packageSource,
|
|
3464
3464
|
canonicalPackageRoot,
|
|
3465
3465
|
packageName: 'arcane-os',
|
|
3466
|
-
packageVersion: '0.5.
|
|
3466
|
+
packageVersion: '0.5.16',
|
|
3467
3467
|
runtimeRoot,
|
|
3468
3468
|
browserRuntimeRoot
|
|
3469
3469
|
}
|
|
@@ -3471,9 +3471,9 @@ workspace it additionally returns the exact installed package authority:
|
|
|
3471
3471
|
```
|
|
3472
3472
|
|
|
3473
3473
|
The dependency can be named `arcane-os` or be one exact npm alias for
|
|
3474
|
-
`npm:arcane-os@0.5.
|
|
3474
|
+
`npm:arcane-os@0.5.16`. The selected installation must still be one direct,
|
|
3475
3475
|
physical, non-link package directory whose manifest identifies exactly as
|
|
3476
|
-
`arcane-os@0.5.
|
|
3476
|
+
`arcane-os@0.5.16`; duplicate canonical/alias declarations reject.
|
|
3477
3477
|
`allowMissingManagedImportMap` is an internal packaging/development seam. An
|
|
3478
3478
|
ordinary caller should leave it `false`.
|
|
3479
3479
|
|
|
@@ -8,6 +8,8 @@ For the smallest first request, start with the [browser speech quick start](../.
|
|
|
8
8
|
|
|
9
9
|
The demo deliberately omits `tts.execution` in `speechConfiguration(dbopfs)` so it consumes the SDK default `{device:'auto',maxConcurrentRequests:4}`. Change that application's TTS record to `execution:{device:'wasm',maxConcurrentRequests:1}` to use less memory, or choose `webgpu` explicitly. Allowed capacities are 1 through 4; Whisper and the LLM retain capacity one.
|
|
10
10
|
|
|
11
|
+
The maintained Kokoro selection uses `fp32` because [Kokoro.js recommends `fp32` when using WebGPU](https://github.com/hexgrad/kokoro/tree/main/kokoro.js#usage). Automatic fallback keeps the same dtype on WASM. `selectedDevice` identifies the loaded route; it does not validate speech correctness or audio quality, so evaluate actual output for each browser and device combination your application supports.
|
|
12
|
+
|
|
11
13
|
Capacity 4 means up to four segments synthesize at once. Segment 5 and later wait in the SDK's FIFO queue; they are not dropped. Synthesis may finish out of order, but playback waits for earlier segments and plays exact input order. Each slot owns a Worker/model session, so raising capacity trades memory for latency.
|
|
12
14
|
|
|
13
15
|
Use the shared Speech component to load/unmute speech, then select **Inspect speech execution** below Chat. The button reads `ai.providerRuntime.status('tts',{execution:true}).execution` and displays requested device, selected device, capacity, active requests, and automatic WASM fallback. It is a snapshot at the moment clicked. `selectedDevice:null` means no pool is selected; `webgpu` reports the upstream execution-provider selection and does not prove physical GPU kernel overlap or audio quality. Auto may fall back to WASM with the same selected model/dtype; explicit WebGPU reports an error when it cannot load.
|
|
@@ -39,7 +41,7 @@ Model weights are deliberately excluded from Git. Place the maintained GGUF shar
|
|
|
39
41
|
|
|
40
42
|
The maintained model descriptors include each shard's known byte length as progress metadata. During a cold install, the SDK reports aggregate loaded and remaining bytes, transfer speed, estimated time, and active file or Range transfers while Chat presents the existing activation progress bar. Completed shards and completed Range parts within a shard remain available after interruption, so retry downloads only missing work.
|
|
41
43
|
|
|
42
|
-
The app gives the SDK browser-speech owner the existing Whisper tiny.en FP32 and Kokoro 82M
|
|
44
|
+
The app gives the SDK browser-speech owner the existing Whisper tiny.en FP32 and Kokoro 82M FP32 descriptors. The app does not import those runtimes or create a speech worker itself.
|
|
43
45
|
|
|
44
46
|
## Local retrieval
|
|
45
47
|
|
|
@@ -435,12 +435,13 @@ function speechConfiguration(dbopfs) {
|
|
|
435
435
|
},
|
|
436
436
|
tts: {
|
|
437
437
|
// Omit execution to use the SDK's auto device and four synthesis slots.
|
|
438
|
+
// Kokoro.js recommends fp32 for the WebGPU route attempted by auto.
|
|
438
439
|
providerId: "wasm-ai-demo-browser-kokoro",
|
|
439
440
|
model: {
|
|
440
441
|
id: "onnx-community/Kokoro-82M-v1.0-ONNX",
|
|
441
442
|
repository: "onnx-community/Kokoro-82M-v1.0-ONNX",
|
|
442
443
|
revision: "1939ad2a8e416c0acfeecc08a694d14ef25f2231",
|
|
443
|
-
dtype: "
|
|
444
|
+
dtype: "fp32",
|
|
444
445
|
defaultVoice: "af_heart",
|
|
445
446
|
},
|
|
446
447
|
runtime: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
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",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.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",
|
|
67
67
|
"test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.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/packaging.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/update-check.test.mjs",
|
|
68
68
|
"test:integration": "node ./bin/arcane-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/wllama-webgpu-runtime.test.mjs",
|
|
69
|
-
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/logging-regression.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
69
|
+
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
70
70
|
"check": "node tools/check-source.mjs && npm test",
|
|
71
71
|
"check:release": "node tools/check-source.mjs --package-only && npm run test:release",
|
|
72
72
|
"pack:local": "npm pack --ignore-scripts",
|
|
@@ -2817,7 +2817,9 @@
|
|
|
2817
2817
|
){
|
|
2818
2818
|
const aiRuntime=boundChatAI??globalThis.ai;
|
|
2819
2819
|
if(typeof aiRuntime?.streamTTS==='function'){
|
|
2820
|
-
void Promise.resolve(aiRuntime.streamTTS(
|
|
2820
|
+
void Promise.resolve(aiRuntime.streamTTS(
|
|
2821
|
+
text,false,{textFormat:'markdown'}
|
|
2822
|
+
)).catch(
|
|
2821
2823
|
reportTTSError
|
|
2822
2824
|
);
|
|
2823
2825
|
}else{
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from './AIProviderRuntime.js';
|
|
14
14
|
import {normalizeOllamaModelIdentifier} from './OllamaModelIdentifier.js';
|
|
15
15
|
import {arcaneLogging} from 'arcane-os/logging';
|
|
16
|
+
import {MarkdownSpeech} from './MarkdownSpeech.js';
|
|
16
17
|
|
|
17
18
|
const completeValue=(value)=>value;
|
|
18
19
|
|
|
@@ -2029,6 +2030,8 @@ class AI {
|
|
|
2029
2030
|
}
|
|
2030
2031
|
|
|
2031
2032
|
audioMessageChunks='';
|
|
2033
|
+
#speechTextFormat='plain';
|
|
2034
|
+
#markdownSpeech=new MarkdownSpeech();
|
|
2032
2035
|
sourceNodes=[];
|
|
2033
2036
|
isSpeaking=false;
|
|
2034
2037
|
audioContext=null;
|
|
@@ -5731,6 +5734,8 @@ class AI {
|
|
|
5731
5734
|
if(this.muted){
|
|
5732
5735
|
if(end){
|
|
5733
5736
|
this.audioMessageChunks='';
|
|
5737
|
+
this.#speechTextFormat='plain';
|
|
5738
|
+
this.#markdownSpeech.reset();
|
|
5734
5739
|
}
|
|
5735
5740
|
this.#traceSpeech('streamTTS.result',{callId,result:false,reason:'muted'});
|
|
5736
5741
|
return Promise.resolve(false);
|
|
@@ -5742,11 +5747,24 @@ class AI {
|
|
|
5742
5747
|
throw new RangeError('Speech pauses must be a nonnegative number of milliseconds.');
|
|
5743
5748
|
}
|
|
5744
5749
|
|
|
5745
|
-
this
|
|
5750
|
+
const textFormat=options.textFormat??this.#speechTextFormat;
|
|
5751
|
+
if(textFormat!=='plain'&&textFormat!=='markdown'){
|
|
5752
|
+
throw new TypeError('Speech textFormat must be plain or markdown.');
|
|
5753
|
+
}
|
|
5754
|
+
let speechText='';
|
|
5755
|
+
if(textFormat!==this.#speechTextFormat){
|
|
5756
|
+
speechText=this.#markdownSpeech.append('',true);
|
|
5757
|
+
}
|
|
5758
|
+
const incomingText=String(text||'');
|
|
5759
|
+
speechText+=textFormat==='markdown'
|
|
5760
|
+
?this.#markdownSpeech.append(incomingText,end)
|
|
5761
|
+
:incomingText;
|
|
5762
|
+
this.#speechTextFormat=end?'plain':textFormat;
|
|
5763
|
+
this.audioMessageChunks+=speechText;
|
|
5746
5764
|
const outputs=this.#extractSpeechSegments(end);
|
|
5747
5765
|
this.#traceSpeech('streamTTS.segments',{
|
|
5748
5766
|
callId,segments:outputs,remainder:this.audioMessageChunks,
|
|
5749
|
-
segmentation:this.ttsSegmentation
|
|
5767
|
+
segmentation:this.ttsSegmentation,textFormat,speechText
|
|
5750
5768
|
});
|
|
5751
5769
|
|
|
5752
5770
|
if(!outputs.length){
|
|
@@ -6414,6 +6432,8 @@ class AI {
|
|
|
6414
6432
|
this.speechScheduleContext=null;
|
|
6415
6433
|
this.speechScheduleTime=0;
|
|
6416
6434
|
this.audioMessageChunks='';
|
|
6435
|
+
this.#speechTextFormat='plain';
|
|
6436
|
+
this.#markdownSpeech.reset();
|
|
6417
6437
|
this.#clearSpeechUnlock();
|
|
6418
6438
|
|
|
6419
6439
|
for(const job of this.speechJobs){
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const FORMATTING_MARKERS=new Set(['*','#','_','`','~']);
|
|
2
|
+
|
|
3
|
+
// Speech-only filtering; single markers and all other text remain literal.
|
|
4
|
+
class MarkdownSpeech {
|
|
5
|
+
#pendingMarker='';
|
|
6
|
+
#repeated=false;
|
|
7
|
+
|
|
8
|
+
append(text='',end=false){
|
|
9
|
+
if(typeof text!=='string'){
|
|
10
|
+
throw new TypeError('Markdown speech input must be text.');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let narration='';
|
|
14
|
+
|
|
15
|
+
for(const character of text){
|
|
16
|
+
if(character===this.#pendingMarker){
|
|
17
|
+
this.#repeated=true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if(this.#pendingMarker&&!this.#repeated){
|
|
22
|
+
narration+=this.#pendingMarker;
|
|
23
|
+
}
|
|
24
|
+
this.reset();
|
|
25
|
+
|
|
26
|
+
if(FORMATTING_MARKERS.has(character)){
|
|
27
|
+
// Wait for the next character to distinguish one mark from a run.
|
|
28
|
+
this.#pendingMarker=character;
|
|
29
|
+
}else{
|
|
30
|
+
narration+=character;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if(end){
|
|
35
|
+
if(this.#pendingMarker&&!this.#repeated){
|
|
36
|
+
narration+=this.#pendingMarker;
|
|
37
|
+
}
|
|
38
|
+
this.reset();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return narration;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
reset(){
|
|
45
|
+
this.#pendingMarker='';
|
|
46
|
+
this.#repeated=false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {MarkdownSpeech};
|