arcane-os 0.5.4 → 0.5.5
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 +9 -0
- package/README.md +25 -7
- package/package.json +1 -1
- package/runtime/arcane/modules/AI.js +136 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.5
|
|
4
|
+
|
|
5
|
+
- Added configurable complete text-to-speech stream segmentation through
|
|
6
|
+
`AI.configureTTSSegmentation()`. Applications can speak on Unicode punctuation
|
|
7
|
+
or a whole-word cadence without discarding any text, while apostrophes,
|
|
8
|
+
commas, and hyphens that join Unicode letters or numbers remain intact.
|
|
9
|
+
Synthesis and playback stay sequential, and mute or cancellation still stops
|
|
10
|
+
the complete queue.
|
|
11
|
+
|
|
3
12
|
## 0.5.4
|
|
4
13
|
|
|
5
14
|
- Reduced Browser-WASM HTTP Range parts from roughly 128 MB to roughly 4 MB
|
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.5` 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
|
|
|
@@ -153,6 +153,24 @@ must supply each runtime/model selection explicitly. Speech roles
|
|
|
153
153
|
load, cancel, unload, fail, and recover independently, so speech failure never
|
|
154
154
|
silently falls back or prevents text chat.
|
|
155
155
|
|
|
156
|
+
Applications that need faster spoken-response onset can configure the shared
|
|
157
|
+
TTS stream without taking over synthesis or playback:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
ai.configureTTSSegmentation({
|
|
161
|
+
punctuation:'any',
|
|
162
|
+
wordCadence:4
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The compatibility default remains sentence punctuation with no word cadence.
|
|
167
|
+
The configured stream preserves every character and punctuation mark, chooses
|
|
168
|
+
the earliest complete boundary, and keeps synthesis and playback sequential.
|
|
169
|
+
Any-punctuation mode recognizes boundaries without requiring whitespace while
|
|
170
|
+
keeping apostrophes, commas, and hyphens that join Unicode letters or numbers
|
|
171
|
+
inside the same segment.
|
|
172
|
+
Mute, stop, provider transition, and cancellation still govern the whole queue.
|
|
173
|
+
|
|
156
174
|
The SDK runtime also owns `DBOPFSDocumentLibrary`,
|
|
157
175
|
`DocumentLexicalSearch`, and `PersistentAIChatSession`. Document bootstrap is
|
|
158
176
|
schema-driven and explicit; chat never searches a corpus unless the app wires
|
|
@@ -168,7 +186,7 @@ uses the same controller for automatic memory extraction.
|
|
|
168
186
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
169
187
|
|
|
170
188
|
```bash
|
|
171
|
-
npx arcane-os@0.5.
|
|
189
|
+
npx arcane-os@0.5.5 new my-app --path ./my-app --target portable --git
|
|
172
190
|
cd my-app
|
|
173
191
|
npm install
|
|
174
192
|
npm run check
|
|
@@ -179,7 +197,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
179
197
|
missing Arcane files:
|
|
180
198
|
|
|
181
199
|
```bash
|
|
182
|
-
npm install --save-dev --save-exact arcane-os@0.5.
|
|
200
|
+
npm install --save-dev --save-exact arcane-os@0.5.5
|
|
183
201
|
npm exec -- arcane init my-app --target portable
|
|
184
202
|
```
|
|
185
203
|
|
|
@@ -195,7 +213,7 @@ npm exec -- arcane-os targets
|
|
|
195
213
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
196
214
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
197
215
|
|
|
198
|
-
Use `npx arcane-os@0.5.
|
|
216
|
+
Use `npx arcane-os@0.5.5` for the initial bootstrap because it names this npm
|
|
199
217
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
200
218
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
201
219
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -216,7 +234,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
216
234
|
|
|
217
235
|
# From the generated app repository
|
|
218
236
|
cd ../local-app
|
|
219
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.
|
|
237
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.5.5.tgz
|
|
220
238
|
npm run check
|
|
221
239
|
npm ci
|
|
222
240
|
```
|
|
@@ -226,7 +244,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
226
244
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
227
245
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
228
246
|
runner also needs that tarball at the locked path. After publication, replace
|
|
229
|
-
the local declaration with the exact `arcane-os@0.5.
|
|
247
|
+
the local declaration with the exact `arcane-os@0.5.5` registry package and
|
|
230
248
|
commit the regenerated lock.
|
|
231
249
|
|
|
232
250
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -364,7 +382,7 @@ package installation, or assertions.
|
|
|
364
382
|
|
|
365
383
|
## Current target support
|
|
366
384
|
|
|
367
|
-
Version `0.5.
|
|
385
|
+
Version `0.5.5` exposes one browser target and five explicitly paired
|
|
368
386
|
native development targets: a non-runnable portable directory, a
|
|
369
387
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
370
388
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
package/package.json
CHANGED
|
@@ -17,6 +17,16 @@ const completeValue=(value)=>value;
|
|
|
17
17
|
|
|
18
18
|
let credentials='include';
|
|
19
19
|
const LEGACY_TTS_RESPONSE_FORMAT='opus';
|
|
20
|
+
const DEFAULT_TTS_SEGMENTATION={
|
|
21
|
+
punctuation:'sentence',
|
|
22
|
+
wordCadence:null
|
|
23
|
+
};
|
|
24
|
+
const TTS_PUNCTUATION_MODES=new Set(['sentence','any','none']);
|
|
25
|
+
// A complete punctuation run is a boundary unless the whole run consists of
|
|
26
|
+
// apostrophe, comma, or dash punctuation joining Unicode letters or numbers.
|
|
27
|
+
// The streaming expression leaves a trailing joining run for the next chunk.
|
|
28
|
+
const TTS_ANY_PUNCTUATION=/(?<!\p{P})(?:(?<![\p{L}\p{N}])\p{P}+(?!\p{P})(?=[\s\S])|\p{P}+(?!\p{P})(?=[^\p{L}\p{N}])|\p{P}*[^\P{P}'\u2019\uFF07,\u060C\u3001\uFF0C\p{Pd}]\p{P}*)(?!\p{P})/gu;
|
|
29
|
+
const TTS_ANY_PUNCTUATION_AT_END=/(?<!\p{P})(?:(?<![\p{L}\p{N}])\p{P}+|\p{P}+(?![\p{L}\p{N}])|\p{P}*[^\P{P}'\u2019\uFF07,\u060C\u3001\uFF0C\p{Pd}]\p{P}*)(?!\p{P})/gu;
|
|
20
30
|
credentials='omit';
|
|
21
31
|
|
|
22
32
|
const LEGACY_AI_SERVICES=new Set(['OPENAI','OLLAMA','LOCAL_SPEACH']);
|
|
@@ -142,6 +152,38 @@ function isPlainAIRecord(value){
|
|
|
142
152
|
return prototype===Object.prototype||prototype===null;
|
|
143
153
|
}
|
|
144
154
|
|
|
155
|
+
function normalizeTTSSegmentation(value,current=DEFAULT_TTS_SEGMENTATION){
|
|
156
|
+
if(value===undefined||value===null){
|
|
157
|
+
return {...current};
|
|
158
|
+
}
|
|
159
|
+
if(!isPlainAIRecord(value)){
|
|
160
|
+
throw new TypeError('TTS segmentation must be a plain object.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const punctuation=value.punctuation===undefined
|
|
164
|
+
?current.punctuation
|
|
165
|
+
:value.punctuation;
|
|
166
|
+
if(!TTS_PUNCTUATION_MODES.has(punctuation)){
|
|
167
|
+
throw new TypeError(
|
|
168
|
+
'TTS segmentation punctuation must be sentence, any, or none.'
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const wordCadence=value.wordCadence===undefined
|
|
173
|
+
?current.wordCadence
|
|
174
|
+
:value.wordCadence;
|
|
175
|
+
if(
|
|
176
|
+
wordCadence!==null
|
|
177
|
+
&&(!Number.isSafeInteger(wordCadence)||wordCadence<1)
|
|
178
|
+
){
|
|
179
|
+
throw new RangeError(
|
|
180
|
+
'TTS segmentation wordCadence must be null or a positive integer.'
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {punctuation,wordCadence};
|
|
185
|
+
}
|
|
186
|
+
|
|
145
187
|
function requireAIToolMessageSchemas(value,label='AI tools'){
|
|
146
188
|
if(!Array.isArray(value)){
|
|
147
189
|
throw aiStructuralError(
|
|
@@ -1069,6 +1111,7 @@ class AI {
|
|
|
1069
1111
|
#speechControlGeneration=0;
|
|
1070
1112
|
#speechFailureSequence=0;
|
|
1071
1113
|
#stopOllamaReady=null;
|
|
1114
|
+
#ttsSegmentation={...DEFAULT_TTS_SEGMENTATION};
|
|
1072
1115
|
#preferenceTuple=completeValue([
|
|
1073
1116
|
'OPENAI',
|
|
1074
1117
|
'LOCAL_SPEACH',
|
|
@@ -5512,6 +5555,18 @@ class AI {
|
|
|
5512
5555
|
return responseJSON;
|
|
5513
5556
|
}
|
|
5514
5557
|
|
|
5558
|
+
get ttsSegmentation(){
|
|
5559
|
+
return {...this.#ttsSegmentation};
|
|
5560
|
+
}
|
|
5561
|
+
|
|
5562
|
+
configureTTSSegmentation(options={}){
|
|
5563
|
+
this.#ttsSegmentation=normalizeTTSSegmentation(
|
|
5564
|
+
options,
|
|
5565
|
+
this.#ttsSegmentation
|
|
5566
|
+
);
|
|
5567
|
+
return this.ttsSegmentation;
|
|
5568
|
+
}
|
|
5569
|
+
|
|
5515
5570
|
streamTTS(
|
|
5516
5571
|
text='',
|
|
5517
5572
|
end=false
|
|
@@ -5563,10 +5618,16 @@ class AI {
|
|
|
5563
5618
|
let remainder=this.audioMessageChunks;
|
|
5564
5619
|
|
|
5565
5620
|
while(remainder.length>0){
|
|
5566
|
-
const
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5621
|
+
const punctuationBoundary=this.#findSpeechPunctuationBoundary(
|
|
5622
|
+
remainder,
|
|
5623
|
+
end
|
|
5624
|
+
);
|
|
5625
|
+
const cadenceBoundary=this.#findSpeechCadenceBoundary(remainder,end);
|
|
5626
|
+
let boundary=punctuationBoundary;
|
|
5627
|
+
|
|
5628
|
+
if(boundary<0||(cadenceBoundary>0&&cadenceBoundary<boundary)){
|
|
5629
|
+
boundary=cadenceBoundary;
|
|
5630
|
+
}
|
|
5570
5631
|
|
|
5571
5632
|
if(boundary<0&&end){
|
|
5572
5633
|
boundary=remainder.length;
|
|
@@ -5588,7 +5649,14 @@ class AI {
|
|
|
5588
5649
|
return segments;
|
|
5589
5650
|
}
|
|
5590
5651
|
|
|
5591
|
-
#
|
|
5652
|
+
#findSpeechPunctuationBoundary(text,end=false){
|
|
5653
|
+
if(this.#ttsSegmentation.punctuation==='none'){
|
|
5654
|
+
return -1;
|
|
5655
|
+
}
|
|
5656
|
+
if(this.#ttsSegmentation.punctuation==='any'){
|
|
5657
|
+
return this.#findAnySpeechPunctuationBoundary(text,end);
|
|
5658
|
+
}
|
|
5659
|
+
|
|
5592
5660
|
const pattern=end
|
|
5593
5661
|
?/(?:[\u3002\uFF01\uFF1F]|[.!?](?=\s|$))/g
|
|
5594
5662
|
:/(?:[\u3002\uFF01\uFF1F]|[.!?](?=\s+\S))/g;
|
|
@@ -5602,10 +5670,71 @@ class AI {
|
|
|
5602
5670
|
continue;
|
|
5603
5671
|
}
|
|
5604
5672
|
|
|
5605
|
-
return
|
|
5673
|
+
return this.#includeSpeechBoundaryWhitespace(
|
|
5674
|
+
text,
|
|
5675
|
+
terminator.index+terminator[0].length
|
|
5676
|
+
);
|
|
5606
5677
|
}
|
|
5607
5678
|
|
|
5608
|
-
return
|
|
5679
|
+
return -1;
|
|
5680
|
+
}
|
|
5681
|
+
|
|
5682
|
+
#findAnySpeechPunctuationBoundary(text,end=false){
|
|
5683
|
+
const pattern=end
|
|
5684
|
+
?TTS_ANY_PUNCTUATION_AT_END
|
|
5685
|
+
:TTS_ANY_PUNCTUATION;
|
|
5686
|
+
pattern.lastIndex=0;
|
|
5687
|
+
const firstContent=text.search(/\S/u);
|
|
5688
|
+
let punctuation;
|
|
5689
|
+
|
|
5690
|
+
while((punctuation=pattern.exec(text))){
|
|
5691
|
+
if(firstContent<0||punctuation.index<=firstContent){
|
|
5692
|
+
continue;
|
|
5693
|
+
}
|
|
5694
|
+
const boundary=punctuation.index+punctuation[0].length;
|
|
5695
|
+
return this.#includeSpeechBoundaryWhitespace(text,boundary);
|
|
5696
|
+
}
|
|
5697
|
+
|
|
5698
|
+
return -1;
|
|
5699
|
+
}
|
|
5700
|
+
|
|
5701
|
+
#findSpeechCadenceBoundary(text,end=false){
|
|
5702
|
+
const wordCadence=this.#ttsSegmentation.wordCadence;
|
|
5703
|
+
if(wordCadence===null){
|
|
5704
|
+
return -1;
|
|
5705
|
+
}
|
|
5706
|
+
|
|
5707
|
+
const words=/\S+/gu;
|
|
5708
|
+
let word;
|
|
5709
|
+
let wordCount=0;
|
|
5710
|
+
|
|
5711
|
+
while((word=words.exec(text))){
|
|
5712
|
+
wordCount+=1;
|
|
5713
|
+
if(wordCount!==wordCadence){
|
|
5714
|
+
continue;
|
|
5715
|
+
}
|
|
5716
|
+
const boundary=word.index+word[0].length;
|
|
5717
|
+
if(boundary===text.length&&!end){
|
|
5718
|
+
return -1;
|
|
5719
|
+
}
|
|
5720
|
+
if(boundary<text.length&&!/\s/u.test(text[boundary])){
|
|
5721
|
+
return -1;
|
|
5722
|
+
}
|
|
5723
|
+
return this.#includeSpeechBoundaryWhitespace(text,boundary);
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5726
|
+
return -1;
|
|
5727
|
+
}
|
|
5728
|
+
|
|
5729
|
+
#includeSpeechBoundaryWhitespace(text,boundary){
|
|
5730
|
+
let completeBoundary=boundary;
|
|
5731
|
+
while(
|
|
5732
|
+
completeBoundary<text.length
|
|
5733
|
+
&&/\s/u.test(text[completeBoundary])
|
|
5734
|
+
){
|
|
5735
|
+
completeBoundary+=1;
|
|
5736
|
+
}
|
|
5737
|
+
return completeBoundary;
|
|
5609
5738
|
}
|
|
5610
5739
|
|
|
5611
5740
|
#isSpeechAbbreviation(text,periodIndex){
|