arcane-os 0.2.2 → 0.3.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 +29 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +27 -27
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +6 -3
- package/docs/reference/behavioral-testing.md +29 -6
- package/docs/reference/cli.md +117 -9
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +108 -44
- package/docs/reference/inventory/runtime-modules.json +131 -53
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +157 -43
- package/docs/reference/runtime-components.md +258 -83
- package/docs/reference/runtime-modules.md +613 -77
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +462 -384
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +639 -98
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +470 -0
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -50,16 +50,39 @@
|
|
|
50
50
|
<section class="voice-transcription">
|
|
51
51
|
<p id="description" class="description"></p>
|
|
52
52
|
<section id="transcript" class="transcript"></section>
|
|
53
|
-
<p id="status" class="status" aria-live="polite"></p>
|
|
53
|
+
<p id="status" class="status" role="status" aria-live="polite"></p>
|
|
54
54
|
<section class="controls">
|
|
55
55
|
<button id="start" type="button" disabled></button>
|
|
56
|
+
<button id="sttActivation" type="button" hidden disabled>Start transcription</button>
|
|
56
57
|
<button id="stop" type="button" disabled></button>
|
|
57
58
|
<button id="complete" type="button" disabled></button>
|
|
58
59
|
</section>
|
|
59
60
|
</section>
|
|
60
61
|
|
|
61
62
|
<script type="module">
|
|
63
|
+
const {
|
|
64
|
+
createArcaneEventSource,
|
|
65
|
+
projectArcaneDOMEvent
|
|
66
|
+
}=await import('arcane-os/event-manager');
|
|
67
|
+
|
|
62
68
|
const host=this;
|
|
69
|
+
const events=createArcaneEventSource(
|
|
70
|
+
host,
|
|
71
|
+
{
|
|
72
|
+
source:'arcane.component.voice-transcription',
|
|
73
|
+
eventTypes:[
|
|
74
|
+
'voice-transcription-ready',
|
|
75
|
+
'voice-transcription-state',
|
|
76
|
+
'voice-transcription-segment',
|
|
77
|
+
'voice-transcription-change',
|
|
78
|
+
'voice-transcription-complete',
|
|
79
|
+
'speech-transcription-complete',
|
|
80
|
+
'speech-transcription-cancelled',
|
|
81
|
+
'speech-stt-activation-request',
|
|
82
|
+
'speech-stt-activation-error'
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
);
|
|
63
86
|
const componentURL=new URL(
|
|
64
87
|
host.getAttribute('href')||'./arcane/components/voice-transcription.html',
|
|
65
88
|
document.baseURI
|
|
@@ -68,23 +91,97 @@
|
|
|
68
91
|
const {default:MD}=await import(moduleURL('MD.js?v=2'));
|
|
69
92
|
const {
|
|
70
93
|
appendTranscription,
|
|
94
|
+
createSTTActivationController,
|
|
71
95
|
normalizeVoiceOptions
|
|
72
96
|
}=await import(moduleURL('ComponentContracts.js'));
|
|
97
|
+
const {
|
|
98
|
+
requestAIRuntimeIntent,
|
|
99
|
+
subscribeAIRuntimeState
|
|
100
|
+
}=await import(moduleURL('AIRuntimeState.js'));
|
|
73
101
|
const description=host.shadowRoot.querySelector('#description');
|
|
74
102
|
const transcriptElement=host.shadowRoot.querySelector('#transcript');
|
|
75
103
|
const status=host.shadowRoot.querySelector('#status');
|
|
76
104
|
const startButton=host.shadowRoot.querySelector('#start');
|
|
105
|
+
const sttActivationButton=host.shadowRoot.querySelector('#sttActivation');
|
|
77
106
|
const stopButton=host.shadowRoot.querySelector('#stop');
|
|
78
107
|
const completeButton=host.shadowRoot.querySelector('#complete');
|
|
108
|
+
const runtimeStateAbortController=new AbortController();
|
|
79
109
|
|
|
80
110
|
let chunks=[];
|
|
81
111
|
let mediaStream=null;
|
|
82
112
|
let recorder=null;
|
|
83
113
|
let state='idle';
|
|
114
|
+
let stateMessage='';
|
|
115
|
+
let sttRole=unavailableRole('stt');
|
|
84
116
|
let transcript='';
|
|
85
117
|
let sessionGeneration=0;
|
|
118
|
+
let transcriptionAbortController=null;
|
|
119
|
+
let eventOperationSequence=0;
|
|
120
|
+
let voiceOperationId=null;
|
|
121
|
+
let destroyed=false;
|
|
86
122
|
let options=normalizeVoiceOptions(optionsFromDataset());
|
|
87
123
|
|
|
124
|
+
function nextVoiceOperationId(kind){
|
|
125
|
+
eventOperationSequence+=1;
|
|
126
|
+
return `${events.descriptor.instanceId}:${kind}:${eventOperationSequence}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function currentVoiceOperationId(kind='transcription'){
|
|
130
|
+
voiceOperationId=voiceOperationId||nextVoiceOperationId(kind);
|
|
131
|
+
return voiceOperationId;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function canonicalSTTCancellationReason(reason){
|
|
135
|
+
if(reason==='runtime-unready'){
|
|
136
|
+
return 'stt-role-unready';
|
|
137
|
+
}
|
|
138
|
+
if(reason==='stt-provider-request-cancelled'){
|
|
139
|
+
return 'stt-provider-transcription-cancelled';
|
|
140
|
+
}
|
|
141
|
+
return String(reason||'stt-transcription-cancelled');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function dispatchVoiceEvent(
|
|
145
|
+
type,
|
|
146
|
+
compatibilityDetail={},
|
|
147
|
+
{
|
|
148
|
+
publicDetail={},
|
|
149
|
+
operationId=null,
|
|
150
|
+
beginOperation=null,
|
|
151
|
+
bubbles=false,
|
|
152
|
+
composed=false,
|
|
153
|
+
cancelable=false
|
|
154
|
+
}={}
|
|
155
|
+
){
|
|
156
|
+
if(destroyed&&type!=='speech-transcription-cancelled'){
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
if(beginOperation!==null){
|
|
160
|
+
voiceOperationId=nextVoiceOperationId(beginOperation);
|
|
161
|
+
}
|
|
162
|
+
const publication=events.dispatch(
|
|
163
|
+
type,
|
|
164
|
+
compatibilityDetail,
|
|
165
|
+
{
|
|
166
|
+
publicDetail,
|
|
167
|
+
operationId:operationId||currentVoiceOperationId(),
|
|
168
|
+
cancelable
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
if(!publication.accepted){
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
return projectArcaneDOMEvent(
|
|
175
|
+
host,
|
|
176
|
+
publication.occurrence,
|
|
177
|
+
{
|
|
178
|
+
bubbles,
|
|
179
|
+
composed,
|
|
180
|
+
cancelable
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
88
185
|
host.clear=clear;
|
|
89
186
|
host.completeTranscription=completeStream;
|
|
90
187
|
host.configure=configure;
|
|
@@ -92,6 +189,10 @@
|
|
|
92
189
|
host.reset=clear;
|
|
93
190
|
host.startRecording=startRecording;
|
|
94
191
|
host.stopRecording=stopRecording;
|
|
192
|
+
host.requestSTTActivation=host.requestSTTActivation
|
|
193
|
+
||function requestSTTActivation(intent){
|
|
194
|
+
return requestAIRuntimeIntent(intent);
|
|
195
|
+
};
|
|
95
196
|
|
|
96
197
|
Object.defineProperties(
|
|
97
198
|
host,
|
|
@@ -102,9 +203,50 @@
|
|
|
102
203
|
}
|
|
103
204
|
);
|
|
104
205
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
206
|
+
const lifecycleListenerOptions=Object.freeze(
|
|
207
|
+
{signal:runtimeStateAbortController.signal}
|
|
208
|
+
);
|
|
209
|
+
startButton.addEventListener('click',startRecording,lifecycleListenerOptions);
|
|
210
|
+
stopButton.addEventListener('click',stopRecording,lifecycleListenerOptions);
|
|
211
|
+
completeButton.addEventListener('click',completeStream,lifecycleListenerOptions);
|
|
212
|
+
window.addEventListener(
|
|
213
|
+
'pagehide',
|
|
214
|
+
destroy,
|
|
215
|
+
{once:true,signal:runtimeStateAbortController.signal}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const sttActivationController=createSTTActivationController(
|
|
219
|
+
{
|
|
220
|
+
host,
|
|
221
|
+
button:sttActivationButton,
|
|
222
|
+
onChange:renderState,
|
|
223
|
+
eventSource:events
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
function unavailableRole(role){
|
|
228
|
+
return Object.freeze(
|
|
229
|
+
{
|
|
230
|
+
role,
|
|
231
|
+
state:'unavailable',
|
|
232
|
+
providerId:null,
|
|
233
|
+
modelId:null,
|
|
234
|
+
localOnly:null,
|
|
235
|
+
loaded:false,
|
|
236
|
+
busy:false,
|
|
237
|
+
operationId:null,
|
|
238
|
+
progress:null,
|
|
239
|
+
error:null
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function canStartVoiceRecording(role,workflowState,componentDestroyed){
|
|
245
|
+
return !componentDestroyed
|
|
246
|
+
&&role?.state==='ready'
|
|
247
|
+
&&!role.busy
|
|
248
|
+
&&['idle','error'].includes(workflowState);
|
|
249
|
+
}
|
|
108
250
|
|
|
109
251
|
function optionsFromDataset(){
|
|
110
252
|
const dataset=host.dataset||{};
|
|
@@ -129,19 +271,31 @@
|
|
|
129
271
|
}
|
|
130
272
|
|
|
131
273
|
function configure(input={}){
|
|
274
|
+
if(destroyed){
|
|
275
|
+
return getOptions();
|
|
276
|
+
}
|
|
132
277
|
const setInitial=Object.prototype.hasOwnProperty.call(input||{},'initialValue');
|
|
133
|
-
|
|
278
|
+
const nextOptions=normalizeVoiceOptions(input,options);
|
|
279
|
+
if(!setInitial){
|
|
280
|
+
if(supersedeForConfigurationReplacement()){
|
|
281
|
+
reportSTTCancellation(
|
|
282
|
+
'configuration-replaced',
|
|
283
|
+
'Transcription configuration replaced.'
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
options=nextOptions;
|
|
134
288
|
renderOptions();
|
|
135
289
|
if(setInitial){
|
|
136
290
|
setValue(options.initialValue);
|
|
137
291
|
}else{
|
|
138
|
-
|
|
292
|
+
renderState();
|
|
139
293
|
}
|
|
140
294
|
return getOptions();
|
|
141
295
|
}
|
|
142
296
|
|
|
143
297
|
async function startRecording(){
|
|
144
|
-
if(!
|
|
298
|
+
if(!canStartVoiceRecording(sttRole,state,destroyed)){
|
|
145
299
|
return false;
|
|
146
300
|
}
|
|
147
301
|
if(
|
|
@@ -152,41 +306,84 @@
|
|
|
152
306
|
return false;
|
|
153
307
|
}
|
|
154
308
|
|
|
155
|
-
setState('starting',options.messages.requesting);
|
|
156
|
-
chunks=[];
|
|
157
309
|
const generation=++sessionGeneration;
|
|
310
|
+
chunks=[];
|
|
311
|
+
setState('starting',options.messages.requesting,{
|
|
312
|
+
beginOperation:'transcription'
|
|
313
|
+
});
|
|
314
|
+
if(!recordingStartIsCurrent(generation,'starting')){
|
|
315
|
+
return rejectRecordingStart(generation);
|
|
316
|
+
}
|
|
158
317
|
try{
|
|
159
318
|
const stream=await navigator.mediaDevices.getUserMedia(
|
|
160
319
|
options.mediaConstraints
|
|
161
320
|
);
|
|
162
|
-
if(generation
|
|
163
|
-
|
|
164
|
-
track.stop();
|
|
165
|
-
}
|
|
166
|
-
return false;
|
|
321
|
+
if(!recordingStartIsCurrent(generation,'starting')){
|
|
322
|
+
return rejectRecordingStart(generation,stream);
|
|
167
323
|
}
|
|
168
324
|
mediaStream=stream;
|
|
169
|
-
|
|
170
|
-
recorder
|
|
171
|
-
|
|
172
|
-
|
|
325
|
+
const activeRecorder=createRecorder(mediaStream);
|
|
326
|
+
recorder=activeRecorder;
|
|
327
|
+
activeRecorder.addEventListener('dataavailable',collectAudio);
|
|
328
|
+
activeRecorder.addEventListener('stop',finishRecording,{once:true});
|
|
329
|
+
activeRecorder.addEventListener('error',recordingError,{once:true});
|
|
173
330
|
for(const track of mediaStream.getAudioTracks()){
|
|
174
331
|
track.addEventListener('ended',recordingInterrupted,{once:true});
|
|
175
332
|
}
|
|
176
|
-
|
|
333
|
+
activeRecorder.start();
|
|
334
|
+
if(!recordingStartIsCurrent(generation,'starting')
|
|
335
|
+
||recorder!==activeRecorder){
|
|
336
|
+
return rejectAssignedRecordingStart(generation,stream);
|
|
337
|
+
}
|
|
177
338
|
setState('recording',options.messages.recording);
|
|
339
|
+
if(!recordingStartIsCurrent(generation,'recording')
|
|
340
|
+
||recorder!==activeRecorder){
|
|
341
|
+
return rejectAssignedRecordingStart(generation,stream);
|
|
342
|
+
}
|
|
178
343
|
return true;
|
|
179
344
|
}catch(error){
|
|
180
|
-
if(generation!==sessionGeneration){
|
|
345
|
+
if(generation!==sessionGeneration||destroyed){
|
|
181
346
|
return false;
|
|
182
347
|
}
|
|
183
348
|
releaseMicrophone();
|
|
184
|
-
setState('error',options.messages.startError);
|
|
185
349
|
console.error('Unable to start recording:',error);
|
|
350
|
+
setState('error',options.messages.startError);
|
|
186
351
|
return false;
|
|
187
352
|
}
|
|
188
353
|
}
|
|
189
354
|
|
|
355
|
+
function recordingStartIsCurrent(generation,expectedState){
|
|
356
|
+
return isCurrentVoiceOperation(generation,expectedState)
|
|
357
|
+
&&sttRole.state==='ready'
|
|
358
|
+
&&!sttRole.busy;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isCurrentVoiceOperation(generation,expectedState){
|
|
362
|
+
return !destroyed
|
|
363
|
+
&&generation===sessionGeneration
|
|
364
|
+
&&state===expectedState;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function rejectRecordingStart(generation,stream=null){
|
|
368
|
+
if(mediaStream===stream){
|
|
369
|
+
releaseMicrophone();
|
|
370
|
+
}else{
|
|
371
|
+
stopMediaStream(stream);
|
|
372
|
+
}
|
|
373
|
+
if(generation===sessionGeneration
|
|
374
|
+
&&!destroyed
|
|
375
|
+
&&['starting','recording'].includes(state)){
|
|
376
|
+
setState('idle',options.messages.ready);
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function rejectAssignedRecordingStart(generation,stream){
|
|
382
|
+
return mediaStream===stream
|
|
383
|
+
?rejectRecordingStart(generation,stream)
|
|
384
|
+
:false;
|
|
385
|
+
}
|
|
386
|
+
|
|
190
387
|
function createRecorder(stream){
|
|
191
388
|
for(const mimeType of options.mimeTypes){
|
|
192
389
|
if(MediaRecorder.isTypeSupported?.(mimeType)){
|
|
@@ -203,38 +400,71 @@
|
|
|
203
400
|
}
|
|
204
401
|
|
|
205
402
|
function stopRecording(){
|
|
206
|
-
if(state!=='recording'||recorder?.state!=='recording'){
|
|
403
|
+
if(destroyed||state!=='recording'||recorder?.state!=='recording'){
|
|
207
404
|
return false;
|
|
208
405
|
}
|
|
406
|
+
const generation=sessionGeneration;
|
|
407
|
+
const activeRecorder=recorder;
|
|
209
408
|
setState('transcribing',options.messages.transcribing);
|
|
210
|
-
|
|
211
|
-
|
|
409
|
+
if(!isCurrentVoiceOperation(generation,'transcribing')
|
|
410
|
+
||recorder!==activeRecorder
|
|
411
|
+
||activeRecorder.state!=='recording'){
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
activeRecorder.stop();
|
|
415
|
+
return isCurrentVoiceOperation(generation,'transcribing');
|
|
212
416
|
}
|
|
213
417
|
|
|
214
418
|
async function finishRecording(){
|
|
419
|
+
const generation=sessionGeneration;
|
|
420
|
+
if(!isCurrentVoiceOperation(generation,'transcribing')){
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
215
423
|
const mimeType=recorder?.mimeType||chunks[0]?.type||'audio/webm';
|
|
216
424
|
const audio=new Blob(chunks,{type:mimeType});
|
|
425
|
+
let controller=null;
|
|
217
426
|
releaseMicrophone();
|
|
218
427
|
if(!audio.size){
|
|
219
428
|
setState('error',options.messages.emptyAudio);
|
|
220
429
|
return false;
|
|
221
430
|
}
|
|
431
|
+
if(sttRole.state!=='ready'||sttRole.busy){
|
|
432
|
+
reportSTTCancellation(
|
|
433
|
+
sttRole.busy?'stt-role-busy':'runtime-unready',
|
|
434
|
+
sttRole.busy
|
|
435
|
+
?'Transcription stopped because the selected service is busy.'
|
|
436
|
+
:'Transcription stopped because the selected service became unavailable.'
|
|
437
|
+
);
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
222
440
|
|
|
223
441
|
try{
|
|
442
|
+
controller=new AbortController();
|
|
443
|
+
transcriptionAbortController?.abort();
|
|
444
|
+
transcriptionAbortController=controller;
|
|
224
445
|
const file=new File(
|
|
225
446
|
[audio],
|
|
226
447
|
`audio.${fileExtension(mimeType)}`,
|
|
227
448
|
{type:mimeType}
|
|
228
449
|
);
|
|
229
|
-
const
|
|
230
|
-
const result=await transcribe(
|
|
450
|
+
const result=await transcribeAudio(
|
|
231
451
|
file,
|
|
232
452
|
{
|
|
233
453
|
audio,
|
|
234
454
|
mimeType,
|
|
235
|
-
transcript
|
|
236
|
-
|
|
455
|
+
transcript,
|
|
456
|
+
signal:controller.signal
|
|
457
|
+
},
|
|
458
|
+
controller.signal
|
|
237
459
|
);
|
|
460
|
+
releaseTranscriptionController(controller);
|
|
461
|
+
if(controller.signal.aborted
|
|
462
|
+
||generation!==sessionGeneration
|
|
463
|
+
||sttRole.state!=='ready'
|
|
464
|
+
||sttRole.busy
|
|
465
|
+
||destroyed){
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
238
468
|
const segment=typeof result==='string'?result.trim():'';
|
|
239
469
|
if(!segment){
|
|
240
470
|
setState('idle',options.messages.noSpeech);
|
|
@@ -257,51 +487,128 @@
|
|
|
257
487
|
return false;
|
|
258
488
|
}
|
|
259
489
|
setState('saving',options.messages.saving);
|
|
490
|
+
if(!isCurrentVoiceOperation(generation,'saving')){
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
260
493
|
try{
|
|
261
494
|
await save({transcript,segment});
|
|
262
495
|
}catch(error){
|
|
263
|
-
|
|
496
|
+
if(!isCurrentVoiceOperation(generation,'saving')){
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
264
499
|
console.error('Unable to save transcription:',error);
|
|
500
|
+
setState('error',options.messages.saveError);
|
|
265
501
|
return false;
|
|
266
502
|
}
|
|
267
503
|
}
|
|
268
504
|
|
|
505
|
+
const successState=options.persist?'saving':'transcribing';
|
|
269
506
|
const detail={text:segment,transcript};
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
'voice-transcription-segment',
|
|
279
|
-
{bubbles:true,composed:true,detail}
|
|
280
|
-
)
|
|
281
|
-
);
|
|
282
|
-
setState(
|
|
283
|
-
'idle',
|
|
284
|
-
options.persist?options.messages.saved:options.messages.transcribed
|
|
507
|
+
return reportTranscriptionSuccess(
|
|
508
|
+
generation,
|
|
509
|
+
successState,
|
|
510
|
+
detail,
|
|
511
|
+
options.persist
|
|
512
|
+
?options.messages.saved
|
|
513
|
+
:options.messages.transcribed,
|
|
514
|
+
currentVoiceOperationId()
|
|
285
515
|
);
|
|
286
|
-
return true;
|
|
287
516
|
}catch(error){
|
|
288
|
-
|
|
517
|
+
if(generation!==sessionGeneration||destroyed){
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
if(isTranscriptionCancellation(error,controller)){
|
|
521
|
+
releaseTranscriptionController(controller);
|
|
522
|
+
reportSTTCancellation(
|
|
523
|
+
'stt-provider-request-cancelled',
|
|
524
|
+
options.messages.cancelled
|
|
525
|
+
);
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
289
528
|
console.error('Unable to transcribe recording:',error);
|
|
529
|
+
setState('error',options.messages.transcribeError);
|
|
290
530
|
return false;
|
|
531
|
+
}finally{
|
|
532
|
+
releaseTranscriptionController(controller);
|
|
291
533
|
}
|
|
292
534
|
}
|
|
293
535
|
|
|
294
|
-
|
|
536
|
+
function reportTranscriptionSuccess(
|
|
537
|
+
generation,
|
|
538
|
+
expectedState,
|
|
539
|
+
detail,
|
|
540
|
+
message,
|
|
541
|
+
operationId=null
|
|
542
|
+
){
|
|
543
|
+
if(!isCurrentVoiceOperation(generation,expectedState)){
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
dispatchVoiceEvent(
|
|
547
|
+
'speech-transcription-complete',
|
|
548
|
+
detail,
|
|
549
|
+
{
|
|
550
|
+
bubbles:true,
|
|
551
|
+
composed:true,
|
|
552
|
+
operationId,
|
|
553
|
+
publicDetail:{
|
|
554
|
+
segmentCharacterCount:detail.text.length,
|
|
555
|
+
transcriptCharacterCount:detail.transcript.length
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
if(!isCurrentVoiceOperation(generation,expectedState)){
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
dispatchVoiceEvent(
|
|
563
|
+
'voice-transcription-segment',
|
|
564
|
+
detail,
|
|
565
|
+
{
|
|
566
|
+
bubbles:true,
|
|
567
|
+
composed:true,
|
|
568
|
+
operationId,
|
|
569
|
+
publicDetail:{
|
|
570
|
+
segmentCharacterCount:detail.text.length,
|
|
571
|
+
transcriptCharacterCount:detail.transcript.length
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
if(!isCurrentVoiceOperation(generation,expectedState)){
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
setState('idle',message);
|
|
579
|
+
return isCurrentVoiceOperation(generation,'idle');
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function releaseTranscriptionController(controller){
|
|
583
|
+
if(transcriptionAbortController!==controller){
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
transcriptionAbortController=null;
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function transcribeAudio(file,context,signal){
|
|
295
591
|
if(options.transcribe){
|
|
296
|
-
return options.transcribe;
|
|
592
|
+
return options.transcribe(file,context);
|
|
297
593
|
}
|
|
298
594
|
if(typeof globalThis.ai?.fetchSTT!=='function'){
|
|
299
|
-
await import(moduleURL('AI.js
|
|
595
|
+
await import(moduleURL('AI.js'));
|
|
300
596
|
}
|
|
301
597
|
if(typeof globalThis.ai?.fetchSTT!=='function'){
|
|
302
|
-
|
|
598
|
+
const error=new Error('Speech transcription is not configured.');
|
|
599
|
+
error.code='ARCANE_VOICE_TRANSCRIPTION_STT_RUNTIME_METHOD_UNAVAILABLE';
|
|
600
|
+
throw error;
|
|
303
601
|
}
|
|
304
|
-
return globalThis.ai.fetchSTT
|
|
602
|
+
return globalThis.ai.fetchSTT(file,undefined,signal);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function isTranscriptionCancellation(error,controller){
|
|
606
|
+
return controller?.signal.aborted
|
|
607
|
+
||error?.name==='AbortError'
|
|
608
|
+
||[
|
|
609
|
+
'ARCANE_AI_REQUEST_ABORTED',
|
|
610
|
+
'ARCANE_AI_OPERATION_SUPERSEDED'
|
|
611
|
+
].includes(error?.code);
|
|
305
612
|
}
|
|
306
613
|
|
|
307
614
|
function fileExtension(mimeType=''){
|
|
@@ -330,57 +637,121 @@
|
|
|
330
637
|
}
|
|
331
638
|
|
|
332
639
|
async function completeStream(){
|
|
333
|
-
if(
|
|
640
|
+
if(destroyed||!transcript.trim()||!['idle','error'].includes(state)){
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
const generation=sessionGeneration;
|
|
644
|
+
const completionTranscript=transcript;
|
|
645
|
+
const complete=options.onComplete||(
|
|
646
|
+
typeof host.complete==='function'?host.complete.bind(host):null
|
|
647
|
+
);
|
|
648
|
+
setState('completing',options.messages.completing,{
|
|
649
|
+
beginOperation:'completion'
|
|
650
|
+
});
|
|
651
|
+
if(!isCurrentVoiceOperation(generation,'completing')){
|
|
334
652
|
return false;
|
|
335
653
|
}
|
|
336
|
-
setState('completing',options.messages.completing);
|
|
337
654
|
try{
|
|
338
|
-
const complete=options.onComplete||(
|
|
339
|
-
typeof host.complete==='function'?host.complete.bind(host):null
|
|
340
|
-
);
|
|
341
655
|
if(complete){
|
|
342
|
-
await complete({transcript});
|
|
656
|
+
await complete({transcript:completionTranscript});
|
|
343
657
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
658
|
+
if(!isCurrentVoiceOperation(generation,'completing')){
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
dispatchVoiceEvent(
|
|
662
|
+
'voice-transcription-complete',
|
|
663
|
+
{transcript:completionTranscript},
|
|
664
|
+
{
|
|
665
|
+
bubbles:true,
|
|
666
|
+
composed:true,
|
|
667
|
+
publicDetail:{
|
|
668
|
+
transcriptCharacterCount:completionTranscript.length
|
|
351
669
|
}
|
|
352
|
-
|
|
670
|
+
}
|
|
353
671
|
);
|
|
672
|
+
if(!isCurrentVoiceOperation(generation,'completing')){
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
354
675
|
setState('complete',options.messages.complete);
|
|
355
|
-
return
|
|
676
|
+
return isCurrentVoiceOperation(generation,'complete');
|
|
356
677
|
}catch(error){
|
|
357
|
-
|
|
678
|
+
if(!isCurrentVoiceOperation(generation,'completing')){
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
358
681
|
console.error('Unable to complete transcription:',error);
|
|
682
|
+
setState('error',options.messages.completeError);
|
|
359
683
|
return false;
|
|
360
684
|
}
|
|
361
685
|
}
|
|
362
686
|
|
|
687
|
+
function supersedeForTranscriptReplacement(){
|
|
688
|
+
const hadActiveOperation=[
|
|
689
|
+
'starting',
|
|
690
|
+
'transcribing',
|
|
691
|
+
'saving',
|
|
692
|
+
'completing'
|
|
693
|
+
].includes(state)||Boolean(transcriptionAbortController);
|
|
694
|
+
if(!hadActiveOperation){
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
sessionGeneration+=1;
|
|
698
|
+
transcriptionAbortController?.abort();
|
|
699
|
+
transcriptionAbortController=null;
|
|
700
|
+
releaseMicrophone();
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function supersedeForConfigurationReplacement(){
|
|
705
|
+
const hadActiveOperation=[
|
|
706
|
+
'starting',
|
|
707
|
+
'recording',
|
|
708
|
+
'transcribing',
|
|
709
|
+
'saving',
|
|
710
|
+
'completing'
|
|
711
|
+
].includes(state)||Boolean(transcriptionAbortController);
|
|
712
|
+
if(!hadActiveOperation){
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
sessionGeneration+=1;
|
|
716
|
+
transcriptionAbortController?.abort();
|
|
717
|
+
transcriptionAbortController=null;
|
|
718
|
+
releaseMicrophone();
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
|
|
363
722
|
function setValue(value=''){
|
|
723
|
+
if(destroyed){
|
|
724
|
+
return transcript;
|
|
725
|
+
}
|
|
726
|
+
const operationSuperseded=
|
|
727
|
+
supersedeForTranscriptReplacement();
|
|
364
728
|
transcript=`${value??''}`;
|
|
365
729
|
renderTranscript();
|
|
366
|
-
if(
|
|
730
|
+
if(operationSuperseded){
|
|
731
|
+
reportSTTCancellation(
|
|
732
|
+
'transcript-replaced',
|
|
733
|
+
options.messages.transcriptReplaced
|
|
734
|
+
);
|
|
735
|
+
}else if(state!=='recording'){
|
|
367
736
|
setState('idle',options.messages.ready);
|
|
368
737
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
738
|
+
dispatchVoiceEvent(
|
|
739
|
+
'voice-transcription-change',
|
|
740
|
+
{transcript},
|
|
741
|
+
{
|
|
742
|
+
beginOperation:'transcript-change',
|
|
743
|
+
bubbles:true,
|
|
744
|
+
composed:true,
|
|
745
|
+
publicDetail:{
|
|
746
|
+
transcriptCharacterCount:transcript.length
|
|
376
747
|
}
|
|
377
|
-
|
|
748
|
+
}
|
|
378
749
|
);
|
|
379
750
|
return transcript;
|
|
380
751
|
}
|
|
381
752
|
|
|
382
753
|
function clear(){
|
|
383
|
-
if(
|
|
754
|
+
if(destroyed||!['complete','error','idle'].includes(state)){
|
|
384
755
|
return false;
|
|
385
756
|
}
|
|
386
757
|
transcript='';
|
|
@@ -412,39 +783,185 @@
|
|
|
412
783
|
}
|
|
413
784
|
|
|
414
785
|
function releaseMicrophone(){
|
|
415
|
-
const
|
|
786
|
+
const stream=mediaStream;
|
|
416
787
|
recorder?.removeEventListener('dataavailable',collectAudio);
|
|
417
788
|
recorder?.removeEventListener('stop',finishRecording);
|
|
418
789
|
recorder?.removeEventListener('error',recordingError);
|
|
790
|
+
if(recorder?.state==='recording'){
|
|
791
|
+
recorder.stop();
|
|
792
|
+
}
|
|
419
793
|
mediaStream=null;
|
|
420
794
|
recorder=null;
|
|
421
795
|
chunks=[];
|
|
796
|
+
stopMediaStream(stream);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function stopMediaStream(stream){
|
|
800
|
+
const tracks=stream?.getTracks?.()||[];
|
|
422
801
|
for(const track of tracks){
|
|
423
802
|
track.removeEventListener('ended',recordingInterrupted);
|
|
424
803
|
track.stop();
|
|
425
804
|
}
|
|
426
805
|
}
|
|
427
806
|
|
|
428
|
-
function setState(nextState,message=''){
|
|
807
|
+
function setState(nextState,message='',{beginOperation=null}={}){
|
|
808
|
+
if(destroyed){
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
if(beginOperation!==null){
|
|
812
|
+
voiceOperationId=nextVoiceOperationId(beginOperation);
|
|
813
|
+
}
|
|
429
814
|
state=nextState;
|
|
430
|
-
|
|
815
|
+
stateMessage=message||nextState;
|
|
816
|
+
renderState();
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function synchronizeAIRuntimeState(snapshot){
|
|
821
|
+
const previousRole=sttRole;
|
|
822
|
+
sttRole=snapshot.roles.stt;
|
|
823
|
+
sttActivationController.synchronize(sttRole);
|
|
824
|
+
if(previousRole.state==='ready'&&sttRole.state!=='ready'){
|
|
825
|
+
if(cancelSTTOperation('runtime-unready')){
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
renderState();
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function cancelSTTOperation(reason){
|
|
833
|
+
const hadActiveOperation=['starting','recording','transcribing'].includes(state)
|
|
834
|
+
||Boolean(mediaStream)
|
|
835
|
+
||Boolean(recorder)
|
|
836
|
+
||Boolean(transcriptionAbortController);
|
|
837
|
+
if(!hadActiveOperation){
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
sessionGeneration+=1;
|
|
841
|
+
transcriptionAbortController?.abort();
|
|
842
|
+
transcriptionAbortController=null;
|
|
843
|
+
releaseMicrophone();
|
|
844
|
+
reportSTTCancellation(
|
|
845
|
+
reason,
|
|
846
|
+
'Transcription stopped because the selected service became unavailable.'
|
|
847
|
+
);
|
|
848
|
+
return true;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function reportSTTCancellation(reason,message,operationId=null){
|
|
852
|
+
state='idle';
|
|
853
|
+
stateMessage=message;
|
|
854
|
+
dispatchVoiceEvent(
|
|
855
|
+
'speech-transcription-cancelled',
|
|
856
|
+
Object.freeze({reason}),
|
|
857
|
+
{
|
|
858
|
+
bubbles:true,
|
|
859
|
+
composed:true,
|
|
860
|
+
operationId:operationId||currentVoiceOperationId(),
|
|
861
|
+
publicDetail:{reason:canonicalSTTCancellationReason(reason)}
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
if(!destroyed){
|
|
865
|
+
renderState();
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function renderState(){
|
|
870
|
+
status.innerText=runtimeStatusMessage();
|
|
431
871
|
status.classList.toggle('recording',state==='recording');
|
|
432
|
-
|
|
872
|
+
status.setAttribute(
|
|
873
|
+
'aria-busy',
|
|
874
|
+
String(
|
|
875
|
+
!destroyed
|
|
876
|
+
&&(
|
|
877
|
+
sttActivationController.pending
|
|
878
|
+
||['loading','unloading'].includes(sttRole.state)
|
|
879
|
+
||sttRole.busy
|
|
880
|
+
||['starting','transcribing','saving','completing'].includes(state)
|
|
881
|
+
)
|
|
882
|
+
)
|
|
883
|
+
);
|
|
884
|
+
startButton.disabled=!canStartVoiceRecording(sttRole,state,destroyed);
|
|
885
|
+
startButton.title=recordingButtonTitle();
|
|
886
|
+
startButton.setAttribute('aria-label',startButton.title);
|
|
433
887
|
stopButton.disabled=state!=='recording';
|
|
434
|
-
completeButton.disabled
|
|
888
|
+
completeButton.disabled=destroyed
|
|
889
|
+
||!transcript.trim()
|
|
435
890
|
||!['idle','error'].includes(state);
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
891
|
+
sttActivationButton.hidden=destroyed||!sttActivationController.visible;
|
|
892
|
+
sttActivationButton.disabled=destroyed
|
|
893
|
+
||sttActivationController.pending
|
|
894
|
+
||sttRole.state==='unloading'
|
|
895
|
+
||sttActivationController.action===null;
|
|
896
|
+
sttActivationButton.textContent=sttActivationController.label;
|
|
897
|
+
sttActivationButton.title=sttActivationController.title;
|
|
898
|
+
sttActivationButton.setAttribute('aria-label',sttActivationButton.title);
|
|
899
|
+
sttActivationButton.setAttribute(
|
|
900
|
+
'aria-busy',
|
|
901
|
+
String(
|
|
902
|
+
sttActivationController.pending
|
|
903
|
+
||['loading','unloading'].includes(sttRole.state)
|
|
904
|
+
)
|
|
905
|
+
);
|
|
906
|
+
dispatchVoiceEvent(
|
|
907
|
+
'voice-transcription-state',
|
|
908
|
+
Object.freeze(
|
|
439
909
|
{
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
910
|
+
message:status.innerText,
|
|
911
|
+
state,
|
|
912
|
+
stt:sttRole
|
|
443
913
|
}
|
|
444
|
-
)
|
|
914
|
+
),
|
|
915
|
+
{
|
|
916
|
+
bubbles:true,
|
|
917
|
+
composed:true,
|
|
918
|
+
publicDetail:{
|
|
919
|
+
state,
|
|
920
|
+
sttState:sttRole.state
|
|
921
|
+
}
|
|
922
|
+
}
|
|
445
923
|
);
|
|
446
924
|
}
|
|
447
925
|
|
|
926
|
+
function recordingButtonTitle(){
|
|
927
|
+
if(destroyed||sttRole.state==='disposed'){
|
|
928
|
+
return 'Transcription is unavailable.';
|
|
929
|
+
}
|
|
930
|
+
if(sttRole.state==='loading'){
|
|
931
|
+
return 'The selected transcription service is loading.';
|
|
932
|
+
}
|
|
933
|
+
if(sttRole.state==='error'){
|
|
934
|
+
return visibleErrorMessage(
|
|
935
|
+
sttRole.error,
|
|
936
|
+
'The selected transcription service reported an error.'
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
if(sttRole.state!=='ready'){
|
|
940
|
+
return 'The selected transcription service is unavailable.';
|
|
941
|
+
}
|
|
942
|
+
if(sttRole.busy){
|
|
943
|
+
return 'The selected transcription service is busy.';
|
|
944
|
+
}
|
|
945
|
+
return options.labels.start;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function runtimeStatusMessage(){
|
|
949
|
+
if(destroyed){
|
|
950
|
+
return 'Transcription unavailable.';
|
|
951
|
+
}
|
|
952
|
+
return sttRole.state==='ready'
|
|
953
|
+
&&!sttRole.busy
|
|
954
|
+
&&!sttActivationController.pending
|
|
955
|
+
&&!sttActivationController.error
|
|
956
|
+
?stateMessage
|
|
957
|
+
:sttActivationController.status;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function visibleErrorMessage(error,fallback){
|
|
961
|
+
const message=typeof error?.message==='string'?error.message.trim():'';
|
|
962
|
+
return (message||fallback).slice(0,240);
|
|
963
|
+
}
|
|
964
|
+
|
|
448
965
|
function getOptions(){
|
|
449
966
|
return {
|
|
450
967
|
...options,
|
|
@@ -456,9 +973,21 @@
|
|
|
456
973
|
}
|
|
457
974
|
|
|
458
975
|
function destroy(){
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
976
|
+
if(destroyed){
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
destroyed=true;
|
|
980
|
+
runtimeStateAbortController.abort();
|
|
981
|
+
sttActivationController.destroy();
|
|
982
|
+
window.removeEventListener('pagehide',destroy);
|
|
983
|
+
if(!cancelSTTOperation('component-destroyed')){
|
|
984
|
+
sessionGeneration++;
|
|
985
|
+
}
|
|
986
|
+
state='idle';
|
|
987
|
+
stateMessage='Transcription unavailable.';
|
|
988
|
+
host.ready=false;
|
|
989
|
+
renderState();
|
|
990
|
+
events.dispose();
|
|
462
991
|
return true;
|
|
463
992
|
}
|
|
464
993
|
|
|
@@ -466,11 +995,23 @@
|
|
|
466
995
|
transcript=options.initialValue;
|
|
467
996
|
renderTranscript();
|
|
468
997
|
setState('idle',options.messages.ready);
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
998
|
+
if(!destroyed){
|
|
999
|
+
subscribeAIRuntimeState(
|
|
1000
|
+
synchronizeAIRuntimeState,
|
|
1001
|
+
{signal:runtimeStateAbortController.signal}
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
if(!destroyed){
|
|
1005
|
+
host.ready=true;
|
|
1006
|
+
dispatchVoiceEvent(
|
|
472
1007
|
'voice-transcription-ready',
|
|
473
|
-
{
|
|
474
|
-
|
|
475
|
-
|
|
1008
|
+
{},
|
|
1009
|
+
{
|
|
1010
|
+
beginOperation:'ready',
|
|
1011
|
+
bubbles:true,
|
|
1012
|
+
composed:true,
|
|
1013
|
+
publicDetail:{ready:true}
|
|
1014
|
+
}
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
476
1017
|
</script>
|