arcane-os 0.46.0 → 0.48.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 +42 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-speech-recognition.mjs +495 -0
- package/browser-runtime/ai/browser-speech.mjs +1 -0
- package/docs/reference/ai/browser-speech.md +80 -2
- package/docs/reference/availability-and-normalization.md +2 -1
- package/docs/reference/inventory/package-api.json +14 -0
- package/docs/reference/runtime-components.md +89 -14
- package/docs/reference/runtime-modules.md +55 -12
- package/docs/reference/sdk-api.md +57 -0
- package/package.json +1 -1
- package/runtime/arcane/components/chat.html +223 -2
- package/runtime/arcane/components/speech.html +263 -14
- package/runtime/arcane/components/voice-transcription.html +77 -10
- package/runtime/arcane/css/document-site.css +2 -35
- package/runtime/arcane/css/print.css +235 -0
- package/runtime/arcane/css/theme.css +2 -40
- package/runtime/arcane/modules/AI.js +15 -4
- package/runtime/arcane/modules/AIProviderRuntime.js +118 -0
- package/runtime/arcane/modules/PrintView.js +19 -55
|
@@ -902,6 +902,30 @@
|
|
|
902
902
|
const browserDeviceClass = getBrowserDeviceClass();
|
|
903
903
|
|
|
904
904
|
host.language=host.language||'English';
|
|
905
|
+
let transcriptionMode=host.transcriptionMode??'submit';
|
|
906
|
+
speech.displayTranscription=transcriptionMode!=='draft';
|
|
907
|
+
Object.defineProperty(host,'transcriptionMode',{
|
|
908
|
+
configurable:true,
|
|
909
|
+
enumerable:true,
|
|
910
|
+
get:function getTranscriptionMode(){return transcriptionMode;},
|
|
911
|
+
set:function setTranscriptionMode(mode){
|
|
912
|
+
transcriptionMode=mode;
|
|
913
|
+
speech.displayTranscription=mode!=='draft';
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
if(host.recognitionLanguage!==undefined){
|
|
917
|
+
speech.recognitionLanguage=host.recognitionLanguage;
|
|
918
|
+
}
|
|
919
|
+
Object.defineProperty(host,'recognitionLanguage',{
|
|
920
|
+
configurable:true,
|
|
921
|
+
enumerable:true,
|
|
922
|
+
get:function getRecognitionLanguage(){
|
|
923
|
+
return speech.recognitionLanguage;
|
|
924
|
+
},
|
|
925
|
+
set:function setRecognitionLanguage(language){
|
|
926
|
+
speech.recognitionLanguage=language;
|
|
927
|
+
}
|
|
928
|
+
});
|
|
905
929
|
host.name = host.name||'User';
|
|
906
930
|
host.aiName='AI';
|
|
907
931
|
let requestedInitialSpeechMuted=is.boolean(speech.initialMuted)
|
|
@@ -920,6 +944,22 @@
|
|
|
920
944
|
let hostSubmissionGeneration=0;
|
|
921
945
|
let eventOperationSequence=0;
|
|
922
946
|
let destroyed=false;
|
|
947
|
+
let recognitionDraft=null;
|
|
948
|
+
let writingRecognitionDraft=false;
|
|
949
|
+
const printViewPromise=import('../modules/PrintView.js').then(
|
|
950
|
+
function registerChatPrint({createPrintView}){
|
|
951
|
+
if(destroyed) return null;
|
|
952
|
+
return createPrintView({
|
|
953
|
+
host,
|
|
954
|
+
content:function chatPrintContent(){return chatOutput;},
|
|
955
|
+
title:function chatPrintTitle(){return host.printTitle||'Conversation';},
|
|
956
|
+
active:function chatPrintActive(){return !destroyed&&chatOutput.children.length>0;},
|
|
957
|
+
signal:aiRuntimeStateAbortController.signal,
|
|
958
|
+
onError:reportChatPrintFailure
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
);
|
|
962
|
+
void printViewPromise.catch(reportChatPrintFailure);
|
|
923
963
|
let boundChatSession=null;
|
|
924
964
|
let boundChatAI=null;
|
|
925
965
|
let sessionBindingPending=false;
|
|
@@ -1852,6 +1892,8 @@
|
|
|
1852
1892
|
host.bindConversationTimebox=bindConversationTimebox;
|
|
1853
1893
|
host.bindSession=bindSession;
|
|
1854
1894
|
host.submitMessage=submitMessage;
|
|
1895
|
+
host.appendDraft=appendDraft;
|
|
1896
|
+
host.print=print;
|
|
1855
1897
|
host.submitToolResult=submitToolResult;
|
|
1856
1898
|
host.submitToolResults=submitToolResults;
|
|
1857
1899
|
Object.defineProperty(host,'pendingTool',{
|
|
@@ -3005,6 +3047,12 @@
|
|
|
3005
3047
|
{signal:aiRuntimeStateAbortController.signal}
|
|
3006
3048
|
);
|
|
3007
3049
|
|
|
3050
|
+
textArea.addEventListener(
|
|
3051
|
+
'input',
|
|
3052
|
+
trackRecognitionDraftEdit,
|
|
3053
|
+
{signal:aiRuntimeStateAbortController.signal}
|
|
3054
|
+
);
|
|
3055
|
+
|
|
3008
3056
|
textArea.addEventListener(
|
|
3009
3057
|
'input',
|
|
3010
3058
|
resizeTextArea,
|
|
@@ -3084,9 +3132,29 @@
|
|
|
3084
3132
|
{signal:aiRuntimeStateAbortController.signal}
|
|
3085
3133
|
);
|
|
3086
3134
|
|
|
3135
|
+
speech.addEventListener(
|
|
3136
|
+
'speech-transcription-progress',
|
|
3137
|
+
receiveRecognitionDraft,
|
|
3138
|
+
{signal:aiRuntimeStateAbortController.signal}
|
|
3139
|
+
);
|
|
3140
|
+
speech.addEventListener(
|
|
3141
|
+
'speech-transcription-cancelled',
|
|
3142
|
+
finishRecognitionDraft,
|
|
3143
|
+
{signal:aiRuntimeStateAbortController.signal}
|
|
3144
|
+
);
|
|
3087
3145
|
speech.addEventListener(
|
|
3088
3146
|
'speech-transcription-complete',
|
|
3089
|
-
function
|
|
3147
|
+
function receiveTranscribedChatMessage(event){
|
|
3148
|
+
if(recognitionDraft&&recognitionDraft.operationId===event.detail?.operationId){
|
|
3149
|
+
const completedDraft=recognitionDraft;
|
|
3150
|
+
receiveRecognitionDraft(event);
|
|
3151
|
+
if(recognitionDraft===completedDraft) recognitionDraft=null;
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
if(host.transcriptionMode==='draft'){
|
|
3155
|
+
appendDraft(event.detail?.text??'');
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3090
3158
|
void submitMessage(
|
|
3091
3159
|
event.detail?.text||'',
|
|
3092
3160
|
{source:'speech'}
|
|
@@ -3170,6 +3238,140 @@
|
|
|
3170
3238
|
resizeTextArea();
|
|
3171
3239
|
}
|
|
3172
3240
|
|
|
3241
|
+
function trackRecognitionDraftEdit(){
|
|
3242
|
+
if(!recognitionDraft||writingRecognitionDraft){
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
const state=recognitionDraft;
|
|
3246
|
+
const before=state.value;
|
|
3247
|
+
const after=textArea.value;
|
|
3248
|
+
let start=0;
|
|
3249
|
+
while(start<before.length&&start<after.length&&before[start]===after[start]) start++;
|
|
3250
|
+
let oldEnd=before.length;
|
|
3251
|
+
let newEnd=after.length;
|
|
3252
|
+
while(oldEnd>start&&newEnd>start&&before[oldEnd-1]===after[newEnd-1]){
|
|
3253
|
+
oldEnd--;
|
|
3254
|
+
newEnd--;
|
|
3255
|
+
}
|
|
3256
|
+
if(state.range&&before!==after){
|
|
3257
|
+
const range=state.range;
|
|
3258
|
+
if(oldEnd<=range.start){
|
|
3259
|
+
const shift=newEnd-oldEnd;
|
|
3260
|
+
range.start+=shift;
|
|
3261
|
+
range.end+=shift;
|
|
3262
|
+
}else if(start<range.end){
|
|
3263
|
+
// An intentional edit promotes the hypothesis to user text.
|
|
3264
|
+
// Later recognition must not overwrite that edit.
|
|
3265
|
+
state.range=null;
|
|
3266
|
+
state.editedInterim=true;
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
state.value=after;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
function replaceRecognitionDraft(text,finalLength,state=recognitionDraft){
|
|
3273
|
+
const existing=textArea.value;
|
|
3274
|
+
const range=state.range??{start:existing.length,end:existing.length};
|
|
3275
|
+
const selectionStart=textArea.selectionStart;
|
|
3276
|
+
const selectionEnd=textArea.selectionEnd;
|
|
3277
|
+
const direction=textArea.selectionDirection;
|
|
3278
|
+
const editing=shadowRoot.activeElement===textArea;
|
|
3279
|
+
const wasWriting=writingRecognitionDraft;
|
|
3280
|
+
writingRecognitionDraft=true;
|
|
3281
|
+
try{
|
|
3282
|
+
textArea.value=existing.slice(0,range.start)+text+existing.slice(range.end);
|
|
3283
|
+
if(editing){
|
|
3284
|
+
function moveSelection(position){
|
|
3285
|
+
if(position<=range.start) return position;
|
|
3286
|
+
if(position>=range.end) return position+text.length-(range.end-range.start);
|
|
3287
|
+
return range.start+text.length;
|
|
3288
|
+
}
|
|
3289
|
+
textArea.setSelectionRange(moveSelection(selectionStart),moveSelection(selectionEnd),direction);
|
|
3290
|
+
}
|
|
3291
|
+
state.range={start:range.start+finalLength,end:range.start+text.length};
|
|
3292
|
+
state.value=textArea.value;
|
|
3293
|
+
textArea.dispatchEvent(new Event('input',{bubbles:true,composed:true}));
|
|
3294
|
+
}finally{
|
|
3295
|
+
writingRecognitionDraft=wasWriting;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
function receiveRecognitionDraft(event){
|
|
3300
|
+
if(destroyed||(!recognitionDraft&&host.transcriptionMode!=='draft')) return;
|
|
3301
|
+
const {text='',interim='',operationId=null}=event.detail??{};
|
|
3302
|
+
if(recognitionDraft&&recognitionDraft.operationId!==operationId){
|
|
3303
|
+
finishRecognitionDraft();
|
|
3304
|
+
// A synchronous input observer may already own a newer capture.
|
|
3305
|
+
if(recognitionDraft) return;
|
|
3306
|
+
}
|
|
3307
|
+
if(!recognitionDraft){
|
|
3308
|
+
recognitionDraft={
|
|
3309
|
+
operationId,
|
|
3310
|
+
finalText:'',
|
|
3311
|
+
value:textArea.value,
|
|
3312
|
+
range:null,
|
|
3313
|
+
separator:'',
|
|
3314
|
+
started:false,
|
|
3315
|
+
editedInterim:false
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
trackRecognitionDraftEdit();
|
|
3319
|
+
const state=recognitionDraft;
|
|
3320
|
+
const finalText=text.startsWith(state.finalText)?text.slice(state.finalText.length):text;
|
|
3321
|
+
state.finalText=text;
|
|
3322
|
+
if(state.editedInterim){
|
|
3323
|
+
if(!finalText) return;
|
|
3324
|
+
// The edited words stand in place of this finalized hypothesis.
|
|
3325
|
+
state.editedInterim=false;
|
|
3326
|
+
state.started=false;
|
|
3327
|
+
state.separator='';
|
|
3328
|
+
if(!interim) return;
|
|
3329
|
+
receiveRecognitionDraft({detail:{text,interim,operationId}});
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
if(!state.started&&(finalText||interim)){
|
|
3333
|
+
const incoming=finalText||interim;
|
|
3334
|
+
state.separator=textArea.value&&!/\s$/.test(textArea.value)&&!/^\s/.test(incoming)?'\n':'';
|
|
3335
|
+
state.started=true;
|
|
3336
|
+
}
|
|
3337
|
+
const separator=state.separator;
|
|
3338
|
+
const replacement=finalText||interim?separator+finalText+interim:'';
|
|
3339
|
+
if(finalText) state.separator='';
|
|
3340
|
+
replaceRecognitionDraft(replacement,finalText?separator.length+finalText.length:0,state);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
function finishRecognitionDraft(event){
|
|
3344
|
+
if(!recognitionDraft||(event&&recognitionDraft.operationId!==event.detail?.operationId)) return;
|
|
3345
|
+
trackRecognitionDraftEdit();
|
|
3346
|
+
const completedDraft=recognitionDraft;
|
|
3347
|
+
recognitionDraft=null;
|
|
3348
|
+
if(completedDraft.range){
|
|
3349
|
+
replaceRecognitionDraft('',0,completedDraft);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
function appendDraft(text){
|
|
3354
|
+
if(!is.string(text)){
|
|
3355
|
+
throw new TypeError('Chat draft text must be a string.');
|
|
3356
|
+
}
|
|
3357
|
+
if(destroyed||text===''){
|
|
3358
|
+
return false;
|
|
3359
|
+
}
|
|
3360
|
+
const existing=textArea.value;
|
|
3361
|
+
const selectionStart=textArea.selectionStart;
|
|
3362
|
+
const selectionEnd=textArea.selectionEnd;
|
|
3363
|
+
const selectionDirection=textArea.selectionDirection;
|
|
3364
|
+
const editing=shadowRoot.activeElement===textArea;
|
|
3365
|
+
// Join distinct entries without changing either entry's authored text.
|
|
3366
|
+
const separator=existing&&!/\s$/.test(existing)&&!/^\s/.test(text)?'\n':'';
|
|
3367
|
+
textArea.value=existing+separator+text;
|
|
3368
|
+
if(editing){
|
|
3369
|
+
textArea.setSelectionRange(selectionStart,selectionEnd,selectionDirection);
|
|
3370
|
+
}
|
|
3371
|
+
textArea.dispatchEvent(new Event('input',{bubbles:true,composed:true}));
|
|
3372
|
+
return true;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3173
3375
|
async function resizeTextArea() {
|
|
3174
3376
|
// Reset height to shrink to content size
|
|
3175
3377
|
trackTranscriptScrollPosition();
|
|
@@ -3934,6 +4136,24 @@
|
|
|
3934
4136
|
return message;
|
|
3935
4137
|
}
|
|
3936
4138
|
|
|
4139
|
+
async function print(){
|
|
4140
|
+
if(destroyed) return false;
|
|
4141
|
+
try{
|
|
4142
|
+
const view=await printViewPromise;
|
|
4143
|
+
return destroyed||!view?false:await view.print();
|
|
4144
|
+
}catch(error){
|
|
4145
|
+
reportChatPrintFailure(error);
|
|
4146
|
+
throw error;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
function reportChatPrintFailure(error){
|
|
4151
|
+
arcaneLogging.error('Unable to print the conversation.',error);
|
|
4152
|
+
if(!destroyed){
|
|
4153
|
+
setSessionStatus('error','Unable to open print preview. Please try again.');
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
|
|
3937
4157
|
function reportTTSError(error,boundary='synthesis'){
|
|
3938
4158
|
if(destroyed){
|
|
3939
4159
|
return false;
|
|
@@ -4001,9 +4221,10 @@
|
|
|
4001
4221
|
if(destroyed){
|
|
4002
4222
|
return false;
|
|
4003
4223
|
}
|
|
4224
|
+
destroyed=true;
|
|
4225
|
+
finishRecognitionDraft();
|
|
4004
4226
|
sessionBindingGeneration+=1;
|
|
4005
4227
|
setSessionStatus('destroyed','Chat closed.');
|
|
4006
|
-
destroyed=true;
|
|
4007
4228
|
transcriptResizeObserver?.disconnect();
|
|
4008
4229
|
transcriptMutationObserver?.disconnect();
|
|
4009
4230
|
const destroyReason=createChatSubmissionAbort(
|