arcane-os 0.5.13 → 0.5.15
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 +32 -0
- package/README.md +33 -8
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +11 -10
- package/browser-runtime/ai/browser-wllama-runtime.mjs +2 -1
- package/browser-runtime/ai/model-controller.mjs +6 -5
- package/browser-runtime/ai/speech-worker-client.mjs +45 -3
- package/browser-runtime/event-manager.mjs +2 -1
- package/browser-runtime/logging.mjs +58 -0
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +9 -7
- package/docs/reference/ai/browser-speech.md +90 -1
- 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 +34 -6
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +4 -4
- package/docs/reference/protocols.md +5 -5
- package/docs/reference/runtime-modules.md +70 -15
- package/docs/reference/sdk-api.md +120 -11
- package/package.json +4 -3
- package/runtime/arcane/components/app-bar.html +3 -1
- package/runtime/arcane/components/chat.html +22 -20
- package/runtime/arcane/components/dashboard-config.html +3 -1
- package/runtime/arcane/components/data-maintenance.html +4 -2
- package/runtime/arcane/components/data-view.html +3 -1
- package/runtime/arcane/components/directory-picker.html +3 -1
- package/runtime/arcane/components/file-manager.html +11 -9
- package/runtime/arcane/components/header.html +5 -3
- package/runtime/arcane/components/markdown-document.html +3 -1
- package/runtime/arcane/components/markdown-editor.html +4 -2
- package/runtime/arcane/components/modal.html +3 -1
- package/runtime/arcane/components/screen-capture.html +4 -2
- package/runtime/arcane/components/speech.html +10 -8
- package/runtime/arcane/components/table.html +3 -1
- package/runtime/arcane/components/voice-transcription.html +8 -6
- package/runtime/arcane/entities/Chat.js +5 -4
- package/runtime/arcane/entities/User.js +2 -1
- package/runtime/arcane/modules/AI.js +442 -292
- package/runtime/arcane/modules/AIProviderRuntime.js +359 -82
- package/runtime/arcane/modules/CommunicationAppController.js +2 -1
- package/runtime/arcane/modules/ComponentContracts.js +3 -2
- package/runtime/arcane/modules/ConversationTimebox.js +2 -1
- package/runtime/arcane/modules/DBOPFS.js +5 -4
- package/runtime/arcane/modules/Errors.js +3 -14
- package/runtime/arcane/modules/HTMLImport.js +4 -3
- package/runtime/arcane/modules/LocalAIReadinessController.js +2 -1
- package/runtime/arcane/modules/MD.js +3 -2
- package/runtime/arcane/modules/MailOutbox.mjs +2 -1
- package/runtime/arcane/modules/PersistentAIChatSession.js +2 -1
- package/runtime/arcane/modules/ScreenCapture.js +2 -1
- package/runtime/arcane/modules/SpeechPlayback.js +206 -40
- package/runtime/arcane/modules/ThemeBootstrap.js +3 -2
- package/runtime/arcane/modules/ToolCallRouter.js +2 -1
- package/src/import-map.mjs +65 -10
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import ArcaneCommunicationBridge from './ArcaneCommunicationBridge.js';
|
|
2
3
|
import CommunicationHub from './CommunicationHub.js?v=2';
|
|
3
4
|
import CommunicationPreferences from './CommunicationPreferences.js';
|
|
@@ -360,7 +361,7 @@ export default class CommunicationAppController{
|
|
|
360
361
|
hub.dispose();
|
|
361
362
|
}
|
|
362
363
|
}catch(error){
|
|
363
|
-
|
|
364
|
+
arcaneLogging.error('The communication hub could not be disposed cleanly.',error);
|
|
364
365
|
}
|
|
365
366
|
}
|
|
366
367
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import {
|
|
2
3
|
createArcaneEventSource,
|
|
3
4
|
projectArcaneDOMEvent
|
|
@@ -713,7 +714,7 @@ function createSTTActivationController({
|
|
|
713
714
|
try{
|
|
714
715
|
onChange();
|
|
715
716
|
}catch(error){
|
|
716
|
-
|
|
717
|
+
arcaneLogging.error('Unable to render STT activation state:',error);
|
|
717
718
|
}
|
|
718
719
|
}
|
|
719
720
|
}
|
|
@@ -724,7 +725,7 @@ function createSTTActivationController({
|
|
|
724
725
|
if(nextAction){
|
|
725
726
|
void request(nextAction).catch(
|
|
726
727
|
function reportSTTActivationRequestFailure(error){
|
|
727
|
-
|
|
728
|
+
arcaneLogging.error('The STT activation request could not settle.',error);
|
|
728
729
|
}
|
|
729
730
|
);
|
|
730
731
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
3
|
|
|
3
4
|
const CONVERSATION_TIMEBOX_TOOL_NAME='conversation_timebox';
|
|
@@ -168,7 +169,7 @@ function conversationTimeboxSubmissionKey(context={}){
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
function reportConversationTimeboxListenerError(error){
|
|
171
|
-
|
|
172
|
+
arcaneLogging.error('A conversation timebox listener failed.',error);
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
function conversationTimeboxError(message,code,reason,ErrorType=Error){
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import Is from "../../node_modules/strong-type/index.js";
|
|
2
3
|
import {openApplicationDataDirectory} from './AppDataScope.js';
|
|
3
4
|
import {
|
|
@@ -458,7 +459,7 @@ class DBOPFS {
|
|
|
458
459
|
);
|
|
459
460
|
}
|
|
460
461
|
}catch(error){
|
|
461
|
-
|
|
462
|
+
arcaneLogging.error(`Error writing file '${fileName}' to table '${tableName}':`,error)
|
|
462
463
|
throw error
|
|
463
464
|
}
|
|
464
465
|
|
|
@@ -603,7 +604,7 @@ class DBOPFS {
|
|
|
603
604
|
const fileName=entries[index][0]
|
|
604
605
|
|
|
605
606
|
if(result.status!=='fulfilled'){
|
|
606
|
-
|
|
607
|
+
arcaneLogging.error(`Failed to set file '${fileName}':`,result.reason)
|
|
607
608
|
}
|
|
608
609
|
}
|
|
609
610
|
);
|
|
@@ -742,7 +743,7 @@ class DBOPFS {
|
|
|
742
743
|
await table.removeEntry(fileName)
|
|
743
744
|
}catch(error){
|
|
744
745
|
if(error.name!=='NotFoundError'){
|
|
745
|
-
|
|
746
|
+
arcaneLogging.error(error)
|
|
746
747
|
throw error
|
|
747
748
|
}
|
|
748
749
|
}
|
|
@@ -784,7 +785,7 @@ class DBOPFS {
|
|
|
784
785
|
delete this.#tables[tableName]
|
|
785
786
|
delete this.#tableHandles[tableName]
|
|
786
787
|
}catch(error){
|
|
787
|
-
|
|
788
|
+
arcaneLogging.error(error)
|
|
788
789
|
}
|
|
789
790
|
|
|
790
791
|
return true
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import waitForComponent from './WaitForComponent.js';
|
|
2
|
+
import { arcaneLogging,readArcaneDeveloperMode } from 'arcane-os/logging';
|
|
2
3
|
import {
|
|
3
4
|
arcaneEvents,
|
|
4
5
|
createArcaneEventSource
|
|
@@ -154,18 +155,6 @@ async function sendWithWindowMail(target,...args){
|
|
|
154
155
|
return target.mail.send(...args);
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
function defaultDeveloperModeStatus(target){
|
|
158
|
-
try{
|
|
159
|
-
if(target?.user?.ready!==true){
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return target.user.developer===true;
|
|
164
|
-
}catch{
|
|
165
|
-
return false;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
158
|
function appendDeveloperDetail(document,list,label,value){
|
|
170
159
|
if(value===null||value===undefined||value===''){
|
|
171
160
|
return;
|
|
@@ -340,7 +329,7 @@ class Errors {
|
|
|
340
329
|
this.now=options.now||Date.now;
|
|
341
330
|
this.schedule=options.schedule||globalThis.setTimeout.bind(globalThis);
|
|
342
331
|
this.cancel=options.cancel||globalThis.clearTimeout.bind(globalThis);
|
|
343
|
-
this.logger=options.logger||
|
|
332
|
+
this.logger=options.logger||arcaneLogging;
|
|
344
333
|
this.storage=options.storage===undefined
|
|
345
334
|
? defaultStorage(target)
|
|
346
335
|
: options.storage;
|
|
@@ -352,7 +341,7 @@ class Errors {
|
|
|
352
341
|
|| sendWithWindowMail.bind(null,target);
|
|
353
342
|
this.isDeveloperMode=typeof options.isDeveloperMode==='function'
|
|
354
343
|
? options.isDeveloperMode
|
|
355
|
-
:
|
|
344
|
+
: readArcaneDeveloperMode.bind(null,target);
|
|
356
345
|
this.presentDeveloperIncident=typeof options.presentDeveloperIncident==='function'
|
|
357
346
|
? options.presentDeveloperIncident
|
|
358
347
|
: presentDeveloperIncidentModal.bind(null,target);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import {
|
|
2
3
|
createArcaneEventSource,
|
|
3
4
|
projectArcaneDOMEvent
|
|
@@ -299,7 +300,7 @@ class HTMLImport extends HTMLElement {
|
|
|
299
300
|
let resolvedHref='';
|
|
300
301
|
const operationId=`html-import-load-${this.#events.instanceId}-${generation}`;
|
|
301
302
|
if(!href){
|
|
302
|
-
|
|
303
|
+
arcaneLogging.log('no href provided for html-import tag',this);
|
|
303
304
|
this.#loadController=null;
|
|
304
305
|
return;
|
|
305
306
|
}
|
|
@@ -319,7 +320,7 @@ class HTMLImport extends HTMLElement {
|
|
|
319
320
|
await this.#destroyImportedHost();
|
|
320
321
|
return;
|
|
321
322
|
}
|
|
322
|
-
|
|
323
|
+
arcaneLogging.error('Error loading HTML component:',err);
|
|
323
324
|
const detail={
|
|
324
325
|
code:'HTML_IMPORT_FAILED',
|
|
325
326
|
href,
|
|
@@ -369,7 +370,7 @@ class HTMLImport extends HTMLElement {
|
|
|
369
370
|
}
|
|
370
371
|
|
|
371
372
|
#reportImportedDestroyError(error,{events,href,operationId}){
|
|
372
|
-
|
|
373
|
+
arcaneLogging.error('Error destroying imported HTML component:',error);
|
|
373
374
|
if(!events||events.disposed)return;
|
|
374
375
|
const detail={
|
|
375
376
|
code:'HTML_IMPORT_IMPORTED_COMPONENT_DESTROY_REJECTED',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import {
|
|
2
3
|
checkLocalAIReadiness,
|
|
3
4
|
deriveLocalAIRequirements
|
|
@@ -287,7 +288,7 @@ export function createLocalAIReadinessController({
|
|
|
287
288
|
function retry(){
|
|
288
289
|
void check().catch(function ignoreDestroyedLocalAIRetry(error){
|
|
289
290
|
if(error?.name!=='AbortError'){
|
|
290
|
-
|
|
291
|
+
arcaneLogging.error('Local AI readiness retry failed.',error);
|
|
291
292
|
}
|
|
292
293
|
});
|
|
293
294
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import { marked } from './Marked.min.js';
|
|
2
3
|
import Is from '../../node_modules/strong-type/index.js';
|
|
3
4
|
|
|
@@ -45,7 +46,7 @@ class MD {
|
|
|
45
46
|
|
|
46
47
|
set raw(value=''){
|
|
47
48
|
if(!is.string(value)){
|
|
48
|
-
|
|
49
|
+
arcaneLogging.trace('MD.raw must be a string.');
|
|
49
50
|
return this.#raw;
|
|
50
51
|
}
|
|
51
52
|
this.#raw = value;
|
|
@@ -55,7 +56,7 @@ class MD {
|
|
|
55
56
|
|
|
56
57
|
append(value=''){
|
|
57
58
|
if(!is.string(value)){
|
|
58
|
-
|
|
59
|
+
arcaneLogging.trace('MD.append must be a string.');
|
|
59
60
|
return this.#raw;
|
|
60
61
|
}
|
|
61
62
|
this.#raw += value;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
export const MAIL_OUTBOX_PROTOCOL='arcane-mail-outbox/1';
|
|
2
3
|
export const MAIL_OUTBOX_TABLE='mail_outbox';
|
|
3
4
|
export const MAIL_OUTBOX_IDEMPOTENCY_WINDOW_MS=24*60*60*1000;
|
|
@@ -34,7 +35,7 @@ function reportMailOutboxObserverError(error){
|
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
}catch{}
|
|
37
|
-
try{
|
|
38
|
+
try{arcaneLogging.error?.(error);}catch{}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function fail(message,code='MAIL_OUTBOX_INVALID',ErrorType=Error,cause){
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import ChatEntity from '../entities/Chat.js';
|
|
2
3
|
import ConfiguredAIChatSession,{
|
|
3
4
|
normalizeStructuralToolCall
|
|
@@ -475,7 +476,7 @@ class PersistentAIChatSession{
|
|
|
475
476
|
error instanceof Error?error:new Error('The saved chat history is invalid.'),
|
|
476
477
|
'AI_CHAT_INCOHERENT_PERSISTENCE',
|
|
477
478
|
);
|
|
478
|
-
|
|
479
|
+
arcaneLogging.error('Arcane saved chat history is readable but not actionable.',error);
|
|
479
480
|
}
|
|
480
481
|
const storedSystem=storedMessages.find(message=>message.role==='system');
|
|
481
482
|
const initialMessages=storedMessages
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
2
3
|
import GifEncoder from './GifEncoder.js';
|
|
3
4
|
|
|
@@ -252,7 +253,7 @@ function supportedRecorderType(Recorder){
|
|
|
252
253
|
|
|
253
254
|
function reportDetachedError(error){
|
|
254
255
|
if(typeof globalThis.reportError==='function')globalThis.reportError(error);
|
|
255
|
-
else
|
|
256
|
+
else arcaneLogging.error?.(error);
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
function monotonicNow(){
|
|
@@ -289,6 +289,90 @@ function queueFor(speech){
|
|
|
289
289
|
return queue;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
function providerSpeechCapacity(speech){
|
|
293
|
+
if(typeof speech?.fetchTTS!=='function')return null;
|
|
294
|
+
try{
|
|
295
|
+
const providerRuntime=speech.providerRuntime;
|
|
296
|
+
if(!providerRuntime||typeof providerRuntime.status!=='function')return null;
|
|
297
|
+
const capacity=providerRuntime.status(
|
|
298
|
+
'tts',
|
|
299
|
+
{execution:true}
|
|
300
|
+
)?.execution?.maxConcurrentRequests;
|
|
301
|
+
return Number.isSafeInteger(capacity)&&capacity>0?capacity:null;
|
|
302
|
+
}catch{
|
|
303
|
+
// Clients without inspectable provider capacity retain serialized admission.
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function startProviderSegment(playback,index,providerSynthesisBatch){
|
|
309
|
+
if(
|
|
310
|
+
providerSynthesisBatch!==playback.providerSynthesisBatch
|
|
311
|
+
||index<0
|
|
312
|
+
||index>=playback.parts.length
|
|
313
|
+
||playback.urls[index]
|
|
314
|
+
)return null;
|
|
315
|
+
const existing=playback.segmentRequests.get(index);
|
|
316
|
+
if(existing?.providerSynthesisBatch===providerSynthesisBatch){
|
|
317
|
+
return existing;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
playback.segmentErrors.delete(index);
|
|
321
|
+
const record={providerSynthesisBatch,index,promise:null};
|
|
322
|
+
record.promise=playback.synthesizeSegment(
|
|
323
|
+
index,
|
|
324
|
+
playback.generation,
|
|
325
|
+
index===0,
|
|
326
|
+
providerSynthesisBatch
|
|
327
|
+
).then(
|
|
328
|
+
function storeProviderSynthesizedSegment(outcome){
|
|
329
|
+
if(outcome===SUPERSEDED)return {ready:false,superseded:true};
|
|
330
|
+
if(providerSynthesisBatch!==playback.providerSynthesisBatch){
|
|
331
|
+
playback.revokeObjectURL(outcome.url);
|
|
332
|
+
return {ready:false,superseded:true};
|
|
333
|
+
}
|
|
334
|
+
playback.urls[index]=outcome.url;
|
|
335
|
+
return {ready:true};
|
|
336
|
+
},
|
|
337
|
+
function handleProviderSegmentFailure(error){
|
|
338
|
+
if(providerSynthesisBatch===playback.providerSynthesisBatch){
|
|
339
|
+
playback.segmentErrors.set(
|
|
340
|
+
index,
|
|
341
|
+
{providerSynthesisBatch,index,error}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
return {ready:false,error};
|
|
345
|
+
}
|
|
346
|
+
).finally(function clearSettledProviderSegment(){
|
|
347
|
+
if(playback.segmentRequests.get(index)===record){
|
|
348
|
+
playback.segmentRequests.delete(index);
|
|
349
|
+
}
|
|
350
|
+
}).catch(function observeProviderSegmentHandlingFailure(error){
|
|
351
|
+
if(providerSynthesisBatch===playback.providerSynthesisBatch){
|
|
352
|
+
playback.segmentErrors.set(
|
|
353
|
+
index,
|
|
354
|
+
{providerSynthesisBatch,index,error}
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return {ready:false,error};
|
|
358
|
+
});
|
|
359
|
+
playback.segmentRequests.set(index,record);
|
|
360
|
+
return record;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function startProviderSynthesis(playback,providerSynthesisBatch){
|
|
364
|
+
let first=null;
|
|
365
|
+
for(let index=0;index<playback.parts.length;index+=1){
|
|
366
|
+
const record=startProviderSegment(
|
|
367
|
+
playback,
|
|
368
|
+
index,
|
|
369
|
+
providerSynthesisBatch
|
|
370
|
+
);
|
|
371
|
+
if(index===0)first=record;
|
|
372
|
+
}
|
|
373
|
+
return first;
|
|
374
|
+
}
|
|
375
|
+
|
|
292
376
|
const DEFAULT_MESSAGES={
|
|
293
377
|
idle:'Speech is ready when visual content is available.',
|
|
294
378
|
queued:'Waiting for the current local speech request. The latest narration will begin next.',
|
|
@@ -364,6 +448,9 @@ class SpeechPlayback{
|
|
|
364
448
|
this.pendingGenerations=new Set();
|
|
365
449
|
this.lookahead=null;
|
|
366
450
|
this.lookaheadError=null;
|
|
451
|
+
this.providerSynthesisBatch=null;
|
|
452
|
+
this.segmentRequests=new Map();
|
|
453
|
+
this.segmentErrors=new Map();
|
|
367
454
|
this.model=model===null||model===undefined?null:String(model);
|
|
368
455
|
this.voice=voice===null||voice===undefined?null:String(voice);
|
|
369
456
|
this.responseFormat=responseFormat===null||responseFormat===undefined
|
|
@@ -441,7 +528,12 @@ class SpeechPlayback{
|
|
|
441
528
|
);
|
|
442
529
|
}
|
|
443
530
|
|
|
444
|
-
hasAudio(key=this.key){
|
|
531
|
+
hasAudio(key=this.key){
|
|
532
|
+
const playable=this.providerSynthesisBatch
|
|
533
|
+
?Boolean(this.urls[0])
|
|
534
|
+
:this.urls.some(Boolean);
|
|
535
|
+
return Boolean(playable&&(!key||key===this.key));
|
|
536
|
+
}
|
|
445
537
|
|
|
446
538
|
releaseURLs(){
|
|
447
539
|
for(const url of this.urls){
|
|
@@ -451,6 +543,9 @@ class SpeechPlayback{
|
|
|
451
543
|
this.parts=[];
|
|
452
544
|
this.lookahead=null;
|
|
453
545
|
this.lookaheadError=null;
|
|
546
|
+
this.providerSynthesisBatch=null;
|
|
547
|
+
this.segmentRequests.clear();
|
|
548
|
+
this.segmentErrors.clear();
|
|
454
549
|
}
|
|
455
550
|
|
|
456
551
|
releaseUrls(){this.releaseURLs();}
|
|
@@ -496,39 +591,55 @@ class SpeechPlayback{
|
|
|
496
591
|
throw error;
|
|
497
592
|
}
|
|
498
593
|
|
|
499
|
-
async synthesizeSegment(
|
|
594
|
+
async synthesizeSegment(
|
|
595
|
+
index,
|
|
596
|
+
generation,
|
|
597
|
+
announce=false,
|
|
598
|
+
providerSynthesisBatch=null
|
|
599
|
+
){
|
|
500
600
|
const playback=this;
|
|
501
601
|
const controller=new AbortController();
|
|
502
602
|
const token={generation,index,controller};
|
|
503
|
-
const queue=queueFor(this.speech);
|
|
603
|
+
const queue=providerSynthesisBatch?null:queueFor(this.speech);
|
|
504
604
|
this.pendingGenerations.add(token);
|
|
505
605
|
this.abortControllers.add(controller);
|
|
606
|
+
function segmentRequestIsCurrent(){
|
|
607
|
+
return providerSynthesisBatch
|
|
608
|
+
?providerSynthesisBatch===playback.providerSynthesisBatch
|
|
609
|
+
:generation===playback.generation;
|
|
610
|
+
}
|
|
611
|
+
async function synthesizeQueuedSegment(){
|
|
612
|
+
const part=playback.parts[index];
|
|
613
|
+
if(!segmentRequestIsCurrent()||!part){
|
|
614
|
+
controller.abort();
|
|
615
|
+
return SUPERSEDED;
|
|
616
|
+
}
|
|
617
|
+
if(announce){
|
|
618
|
+
playback.emit('synthesizing',playback.message('preparing',{count:playback.parts.length}));
|
|
619
|
+
}
|
|
620
|
+
const audio=await playback.requestSpeech(
|
|
621
|
+
part,
|
|
622
|
+
controller.signal
|
|
623
|
+
);
|
|
624
|
+
if(!segmentRequestIsCurrent()){
|
|
625
|
+
controller.abort();
|
|
626
|
+
return SUPERSEDED;
|
|
627
|
+
}
|
|
628
|
+
const url=playback.createObjectURL(audio);
|
|
629
|
+
if(!segmentRequestIsCurrent()){
|
|
630
|
+
playback.revokeObjectURL(url);
|
|
631
|
+
controller.abort();
|
|
632
|
+
return SUPERSEDED;
|
|
633
|
+
}
|
|
634
|
+
return {url};
|
|
635
|
+
}
|
|
506
636
|
try{
|
|
507
|
-
return
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
if(announce){
|
|
514
|
-
playback.emit('synthesizing',playback.message('preparing',{count:playback.parts.length}));
|
|
515
|
-
}
|
|
516
|
-
const audio=await playback.requestSpeech(
|
|
517
|
-
part,
|
|
518
|
-
controller.signal
|
|
637
|
+
return providerSynthesisBatch
|
|
638
|
+
?await Promise.resolve().then(synthesizeQueuedSegment)
|
|
639
|
+
:await queue.enqueue(
|
|
640
|
+
synthesizeQueuedSegment,
|
|
641
|
+
{speculative:!announce}
|
|
519
642
|
);
|
|
520
|
-
if(generation!==playback.generation){
|
|
521
|
-
controller.abort();
|
|
522
|
-
return SUPERSEDED;
|
|
523
|
-
}
|
|
524
|
-
const url=playback.createObjectURL(audio);
|
|
525
|
-
if(generation!==playback.generation){
|
|
526
|
-
playback.revokeObjectURL(url);
|
|
527
|
-
controller.abort();
|
|
528
|
-
return SUPERSEDED;
|
|
529
|
-
}
|
|
530
|
-
return {url};
|
|
531
|
-
},{speculative:!announce});
|
|
532
643
|
}finally{
|
|
533
644
|
this.pendingGenerations.delete(token);
|
|
534
645
|
this.abortControllers.delete(controller);
|
|
@@ -576,6 +687,19 @@ class SpeechPlayback{
|
|
|
576
687
|
async waitForSegment(index,generation){
|
|
577
688
|
if(generation!==this.generation)return false;
|
|
578
689
|
if(this.urls[index])return true;
|
|
690
|
+
if(this.providerSynthesisBatch){
|
|
691
|
+
const segmentError=this.segmentErrors.get(index);
|
|
692
|
+
if(segmentError?.providerSynthesisBatch===this.providerSynthesisBatch){
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
const record=this.segmentRequests.get(index);
|
|
696
|
+
if(!record)return Boolean(this.urls[index]);
|
|
697
|
+
const result=await record.promise;
|
|
698
|
+
return generation===this.generation
|
|
699
|
+
&&record.providerSynthesisBatch===this.providerSynthesisBatch
|
|
700
|
+
&&result.ready===true
|
|
701
|
+
&&Boolean(this.urls[index]);
|
|
702
|
+
}
|
|
579
703
|
if(
|
|
580
704
|
this.lookaheadError
|
|
581
705
|
&&this.lookaheadError.generation===generation
|
|
@@ -666,13 +790,28 @@ class SpeechPlayback{
|
|
|
666
790
|
throw error;
|
|
667
791
|
}
|
|
668
792
|
|
|
669
|
-
const queue=queueFor(this.speech);
|
|
670
|
-
if(queue.busy)this.emit('synthesizing',this.message('queued',{count:normalized.length}));
|
|
671
793
|
let outcome;
|
|
672
794
|
try{
|
|
673
|
-
|
|
795
|
+
const providerCapacity=providerSpeechCapacity(this.speech);
|
|
796
|
+
if(providerCapacity===null){
|
|
797
|
+
const queue=queueFor(this.speech);
|
|
798
|
+
if(queue.busy)this.emit('synthesizing',this.message('queued',{count:normalized.length}));
|
|
799
|
+
outcome=await this.synthesizeSegment(0,generation,true);
|
|
800
|
+
}else{
|
|
801
|
+
const providerSynthesisBatch={capacity:providerCapacity};
|
|
802
|
+
this.providerSynthesisBatch=providerSynthesisBatch;
|
|
803
|
+
const first=startProviderSynthesis(
|
|
804
|
+
this,
|
|
805
|
+
providerSynthesisBatch
|
|
806
|
+
);
|
|
807
|
+
const result=await first.promise;
|
|
808
|
+
if(Object.hasOwn(result,'error'))throw result.error;
|
|
809
|
+
outcome=result.ready?{url:this.urls[0]}:SUPERSEDED;
|
|
810
|
+
}
|
|
674
811
|
}catch(error){
|
|
675
812
|
if(generation!==this.generation)return {ready:false,played:false};
|
|
813
|
+
for(const controller of this.abortControllers)controller.abort();
|
|
814
|
+
this.abortControllers.clear();
|
|
676
815
|
this.releaseURLs();
|
|
677
816
|
this.fail(error);
|
|
678
817
|
throw error;
|
|
@@ -693,7 +832,9 @@ class SpeechPlayback{
|
|
|
693
832
|
this.loadCurrent();
|
|
694
833
|
this.emit('ready',this.message('ready'));
|
|
695
834
|
const played=autoplay?await this.play():false;
|
|
696
|
-
if(generation===this.generation
|
|
835
|
+
if(generation===this.generation&&!this.providerSynthesisBatch){
|
|
836
|
+
this.startLookahead(1,generation);
|
|
837
|
+
}
|
|
697
838
|
return {ready:true,played};
|
|
698
839
|
}
|
|
699
840
|
|
|
@@ -723,14 +864,34 @@ class SpeechPlayback{
|
|
|
723
864
|
if(!this.hasAudio())return false;
|
|
724
865
|
this.generation+=1;
|
|
725
866
|
const generation=this.generation;
|
|
726
|
-
this.
|
|
727
|
-
|
|
867
|
+
if(this.providerSynthesisBatch){
|
|
868
|
+
const providerSynthesisBatch=this.providerSynthesisBatch;
|
|
869
|
+
for(const [index,segmentError] of this.segmentErrors){
|
|
870
|
+
if(
|
|
871
|
+
segmentError?.providerSynthesisBatch
|
|
872
|
+
===providerSynthesisBatch
|
|
873
|
+
&&!this.urls[index]
|
|
874
|
+
&&!this.segmentRequests.has(index)
|
|
875
|
+
){
|
|
876
|
+
startProviderSegment(
|
|
877
|
+
this,
|
|
878
|
+
index,
|
|
879
|
+
providerSynthesisBatch
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}else{
|
|
884
|
+
this.lookahead=null;
|
|
885
|
+
this.lookaheadError=null;
|
|
886
|
+
}
|
|
728
887
|
this.audio.pause();
|
|
729
888
|
this.index=0;
|
|
730
889
|
this.loadCurrent();
|
|
731
890
|
this.audio.currentTime=0;
|
|
732
891
|
const played=await this.play();
|
|
733
|
-
if(generation===this.generation
|
|
892
|
+
if(generation===this.generation&&!this.providerSynthesisBatch){
|
|
893
|
+
this.startLookahead(1,generation);
|
|
894
|
+
}
|
|
734
895
|
return played;
|
|
735
896
|
}
|
|
736
897
|
|
|
@@ -772,12 +933,15 @@ class SpeechPlayback{
|
|
|
772
933
|
const ready=await this.waitForSegment(nextIndex,generation);
|
|
773
934
|
if(!ready||generation!==this.generation){
|
|
774
935
|
if(generation===this.generation&&this.state==='buffering'){
|
|
775
|
-
const
|
|
776
|
-
|
|
936
|
+
const providerError=this.segmentErrors.get(nextIndex);
|
|
937
|
+
const providerFailed=this.providerSynthesisBatch
|
|
938
|
+
&&providerError?.providerSynthesisBatch
|
|
939
|
+
===this.providerSynthesisBatch;
|
|
940
|
+
const lookaheadFailed=this.lookaheadError
|
|
777
941
|
&&this.lookaheadError.generation===generation
|
|
778
|
-
&&this.lookaheadError.index===nextIndex
|
|
779
|
-
)
|
|
780
|
-
if(
|
|
942
|
+
&&this.lookaheadError.index===nextIndex;
|
|
943
|
+
if(providerFailed)this.fail(providerError.error);
|
|
944
|
+
else if(lookaheadFailed)this.fail(this.lookaheadError.error);
|
|
781
945
|
else this.emit('ready',this.message('ready'));
|
|
782
946
|
}
|
|
783
947
|
return false;
|
|
@@ -786,7 +950,9 @@ class SpeechPlayback{
|
|
|
786
950
|
this.index=nextIndex;
|
|
787
951
|
this.loadCurrent();
|
|
788
952
|
const played=await this.play();
|
|
789
|
-
if(generation===this.generation
|
|
953
|
+
if(generation===this.generation&&!this.providerSynthesisBatch){
|
|
954
|
+
this.startLookahead(nextIndex+1,generation);
|
|
955
|
+
}
|
|
790
956
|
return played;
|
|
791
957
|
}
|
|
792
958
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
import {loadAndApplyTheme} from './ThemeManager.js';
|
|
2
3
|
import {createArcaneEventSource} from 'arcane-os/event-manager';
|
|
3
4
|
|
|
@@ -43,7 +44,7 @@ function installAppearanceListener(ready){
|
|
|
43
44
|
Promise.resolve(ready).then(function reloadTheme(result){
|
|
44
45
|
return result?.manager?.load?.();
|
|
45
46
|
}).catch(function reportThemeReloadFailure(error){
|
|
46
|
-
|
|
47
|
+
arcaneLogging.warn(
|
|
47
48
|
'[Arcane theme] Unable to apply the changed host appearance.',
|
|
48
49
|
error
|
|
49
50
|
);
|
|
@@ -80,7 +81,7 @@ export function bootstrapArcaneTheme(options={}){
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const ready=loadAndApplyTheme(options).catch(error=>{
|
|
83
|
-
|
|
84
|
+
arcaneLogging.warn('[Arcane theme] Unable to load the saved appearance; using the system theme.',error);
|
|
84
85
|
return {manager:null,state:null,error};
|
|
85
86
|
});
|
|
86
87
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { arcaneLogging } from 'arcane-os/logging';
|
|
1
2
|
function parseArguments(value,name=''){
|
|
2
3
|
let parsed=value;
|
|
3
4
|
if(typeof parsed==='string'){
|
|
4
5
|
try{
|
|
5
6
|
parsed=JSON.parse(parsed);
|
|
6
7
|
}catch(error){
|
|
7
|
-
|
|
8
|
+
arcaneLogging.warn(error);
|
|
8
9
|
throw new Error(`Tool ${name} returned invalid JSON arguments.`);
|
|
9
10
|
}
|
|
10
11
|
}
|