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.
@@ -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 submitTranscribedChatMessage(event){
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(