arcane-os 0.2.1 → 0.2.3
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 +5 -5
- package/browser-runtime/ai/browser-speech-providers.mjs +1 -0
- package/docs/architecture.md +2 -2
- package/docs/reference/README.md +79 -13
- package/docs/reference/ai/browser-speech.md +336 -0
- package/docs/reference/ai/browser-wasm.md +207 -82
- package/docs/reference/availability-and-normalization.md +33 -4
- package/docs/reference/behavioral-testing.md +4 -1
- package/docs/reference/cli.md +29 -10
- package/docs/reference/core/arcane-ai-contracts.md +43 -9
- package/docs/reference/inventory/package-api.json +110 -14
- package/docs/reference/inventory/runtime-components.json +42 -14
- package/docs/reference/inventory/runtime-modules.json +118 -13
- package/docs/reference/protocols.md +264 -38
- package/docs/reference/runtime-components.md +177 -21
- package/docs/reference/runtime-modules.md +449 -13
- package/docs/reference/sdk-api.md +626 -85
- package/package.json +1 -1
- package/runtime/ARCANE_RUNTIME_RELEASE.json +19 -19
- package/runtime/arcane/components/chat.html +17 -52
- package/runtime/arcane/components/speech.html +109 -29
- package/runtime/arcane/components/voice-transcription.html +437 -68
- package/runtime/arcane/modules/AI.js +556 -128
- package/runtime/arcane/modules/AIProviderRuntime.js +299 -30
- package/runtime/arcane/modules/AIRuntimeState.js +23 -4
- package/runtime/arcane/modules/ComponentContracts.js +272 -0
- package/runtime/arcane/modules/LocalAIReadinessController.js +1 -1
- package/schemas/arcane-lock.schema.json +2 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"builder": "arcane-sdk-runtime-v1",
|
|
4
|
-
"sdkVersion": "0.2.
|
|
4
|
+
"sdkVersion": "0.2.3",
|
|
5
5
|
"source": {
|
|
6
6
|
"authority": "sdk-canonical",
|
|
7
7
|
"repository": "https://github.com/TheWizardNexus/arcane-os-sdk.git",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"fileCount": 160,
|
|
17
|
-
"totalBytes":
|
|
18
|
-
"contentSha256": "
|
|
17
|
+
"totalBytes": 3655536,
|
|
18
|
+
"contentSha256": "f96a27bd79a809201708f73ca729bc60ab02696daf619365f77267a28b47b4b3",
|
|
19
19
|
"files": [
|
|
20
20
|
{
|
|
21
21
|
"path": "arcane/components/app-bar.html",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"path": "arcane/components/chat.html",
|
|
42
|
-
"bytes":
|
|
43
|
-
"sha256": "
|
|
42
|
+
"bytes": 60478,
|
|
43
|
+
"sha256": "2f3a7f454c1e3da64c9eaeedb0869d49f04636b20ff59e5116a671e0d0a17973"
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"path": "arcane/components/conversation-view.html",
|
|
@@ -159,8 +159,8 @@
|
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
"path": "arcane/components/speech.html",
|
|
162
|
-
"bytes":
|
|
163
|
-
"sha256": "
|
|
162
|
+
"bytes": 41300,
|
|
163
|
+
"sha256": "0ed5dd29c37747702f114114ad66bedd419fd39bed35fc12362c63c6357a41d9"
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
"path": "arcane/components/summary-strip.html",
|
|
@@ -199,8 +199,8 @@
|
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
"path": "arcane/components/voice-transcription.html",
|
|
202
|
-
"bytes":
|
|
203
|
-
"sha256": "
|
|
202
|
+
"bytes": 26991,
|
|
203
|
+
"sha256": "8140b918791df5043be9254d52bf7864fe91759ab6e5e60af2364d6b4a8fcb7b"
|
|
204
204
|
},
|
|
205
205
|
{
|
|
206
206
|
"path": "arcane/components/weather-widget.html",
|
|
@@ -374,8 +374,8 @@
|
|
|
374
374
|
},
|
|
375
375
|
{
|
|
376
376
|
"path": "arcane/modules/AI.js",
|
|
377
|
-
"bytes":
|
|
378
|
-
"sha256": "
|
|
377
|
+
"bytes": 120607,
|
|
378
|
+
"sha256": "703a5342ed0c0758cccdfd8469fbae6d86e91de8c43ec01c893619daf97c2d34"
|
|
379
379
|
},
|
|
380
380
|
{
|
|
381
381
|
"path": "arcane/modules/AIPreferenceRuntime.js",
|
|
@@ -389,8 +389,8 @@
|
|
|
389
389
|
},
|
|
390
390
|
{
|
|
391
391
|
"path": "arcane/modules/AIProviderRuntime.js",
|
|
392
|
-
"bytes":
|
|
393
|
-
"sha256": "
|
|
392
|
+
"bytes": 93507,
|
|
393
|
+
"sha256": "d1b33f678431128cefe6f43daabe177794e758c770394098871cf4ac726926b8"
|
|
394
394
|
},
|
|
395
395
|
{
|
|
396
396
|
"path": "arcane/modules/AIResponseLength.js",
|
|
@@ -404,8 +404,8 @@
|
|
|
404
404
|
},
|
|
405
405
|
{
|
|
406
406
|
"path": "arcane/modules/AIRuntimeState.js",
|
|
407
|
-
"bytes":
|
|
408
|
-
"sha256": "
|
|
407
|
+
"bytes": 25379,
|
|
408
|
+
"sha256": "492162cec5e6a8b9e9b437833172905b2cb4e352ca1ef30104c65ec29cf3fa29"
|
|
409
409
|
},
|
|
410
410
|
{
|
|
411
411
|
"path": "arcane/modules/AnsiText.js",
|
|
@@ -494,8 +494,8 @@
|
|
|
494
494
|
},
|
|
495
495
|
{
|
|
496
496
|
"path": "arcane/modules/ComponentContracts.js",
|
|
497
|
-
"bytes":
|
|
498
|
-
"sha256": "
|
|
497
|
+
"bytes": 28736,
|
|
498
|
+
"sha256": "f5ba011094835b45258ee833a90bfa17980e860ef9ceac88aa56ecad3abe9d53"
|
|
499
499
|
},
|
|
500
500
|
{
|
|
501
501
|
"path": "arcane/modules/ConfiguredAIChatSession.js",
|
|
@@ -599,8 +599,8 @@
|
|
|
599
599
|
},
|
|
600
600
|
{
|
|
601
601
|
"path": "arcane/modules/LocalAIReadinessController.js",
|
|
602
|
-
"bytes":
|
|
603
|
-
"sha256": "
|
|
602
|
+
"bytes": 4340,
|
|
603
|
+
"sha256": "f80d51d0ad499664f265b4f2d52a46ede521a78d56714fecb0e13b939b261007"
|
|
604
604
|
},
|
|
605
605
|
{
|
|
606
606
|
"path": "arcane/modules/MD.js",
|
|
@@ -719,8 +719,7 @@
|
|
|
719
719
|
?speech.initialMuted
|
|
720
720
|
:true;
|
|
721
721
|
let initialSpeechMuteListenerInstalled=false;
|
|
722
|
-
let
|
|
723
|
-
let speechAvailabilityListenerInstalled=false;
|
|
722
|
+
let latestAIRuntimeRoles=null;
|
|
724
723
|
const hostSubmissionBarrier=new ConversationSubmissionBarrier();
|
|
725
724
|
const aiRuntimeStateAbortController=new AbortController();
|
|
726
725
|
let programmaticSubmissionQueue=Promise.resolve(true);
|
|
@@ -910,36 +909,32 @@
|
|
|
910
909
|
}
|
|
911
910
|
|
|
912
911
|
function setAIAvailability(input={}){
|
|
913
|
-
const explicitSpeechAvailability={};
|
|
914
912
|
const availability={
|
|
915
|
-
llm:
|
|
913
|
+
llm:hasSelectedAIRuntimeRole('llm')
|
|
914
|
+
?latestAIRuntimeRoles.llm.state==='ready'
|
|
915
|
+
:Object.prototype.hasOwnProperty.call(input,'llm')
|
|
916
916
|
?Boolean(input.llm)
|
|
917
917
|
:host.aiAvailability.llm,
|
|
918
|
-
stt:
|
|
919
|
-
?
|
|
918
|
+
stt:latestAIRuntimeRoles
|
|
919
|
+
?latestAIRuntimeRoles.stt.state==='ready'
|
|
920
920
|
:host.aiAvailability.stt,
|
|
921
|
-
tts:
|
|
922
|
-
?
|
|
921
|
+
tts:latestAIRuntimeRoles
|
|
922
|
+
?latestAIRuntimeRoles.tts.state==='ready'
|
|
923
923
|
:host.aiAvailability.tts
|
|
924
924
|
};
|
|
925
925
|
|
|
926
|
-
if(Object.prototype.hasOwnProperty.call(input,'stt')){
|
|
927
|
-
explicitSpeechAvailability.stt=availability.stt;
|
|
928
|
-
}
|
|
929
|
-
if(Object.prototype.hasOwnProperty.call(input,'tts')){
|
|
930
|
-
explicitSpeechAvailability.tts=availability.tts;
|
|
931
|
-
}
|
|
932
926
|
applyAIAvailability(availability);
|
|
933
|
-
if(Object.keys(explicitSpeechAvailability).length){
|
|
934
|
-
pendingSpeechAvailability={
|
|
935
|
-
...(pendingSpeechAvailability||{}),
|
|
936
|
-
...explicitSpeechAvailability
|
|
937
|
-
};
|
|
938
|
-
synchronizeSpeechCompatibilityAvailability();
|
|
939
|
-
}
|
|
940
927
|
return {...availability};
|
|
941
928
|
}
|
|
942
929
|
|
|
930
|
+
function hasSelectedAIRuntimeRole(role){
|
|
931
|
+
const record=latestAIRuntimeRoles?.[role];
|
|
932
|
+
return typeof record?.providerId==='string'
|
|
933
|
+
&&record.providerId.length>0
|
|
934
|
+
&&typeof record?.modelId==='string'
|
|
935
|
+
&&record.modelId.length>0;
|
|
936
|
+
}
|
|
937
|
+
|
|
943
938
|
function applyAIAvailability(availability){
|
|
944
939
|
host.aiAvailability={...availability};
|
|
945
940
|
send.disabled=host.conversationComplete||!availability.llm;
|
|
@@ -1131,6 +1126,7 @@
|
|
|
1131
1126
|
}
|
|
1132
1127
|
|
|
1133
1128
|
function synchronizeAIRuntimeState(snapshot){
|
|
1129
|
+
latestAIRuntimeRoles=snapshot.roles;
|
|
1134
1130
|
aiActivationController.synchronize(snapshot.roles.llm);
|
|
1135
1131
|
applyAIAvailability({
|
|
1136
1132
|
llm:snapshot.roles.llm.state==='ready',
|
|
@@ -1139,37 +1135,6 @@
|
|
|
1139
1135
|
});
|
|
1140
1136
|
}
|
|
1141
1137
|
|
|
1142
|
-
function synchronizeSpeechCompatibilityAvailability(){
|
|
1143
|
-
if(!pendingSpeechAvailability){
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
if(speech.componentReady){
|
|
1147
|
-
const availability=pendingSpeechAvailability;
|
|
1148
|
-
pendingSpeechAvailability=null;
|
|
1149
|
-
speech.setAvailability?.({...availability});
|
|
1150
|
-
return;
|
|
1151
|
-
}
|
|
1152
|
-
if(speechAvailabilityListenerInstalled){
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
speechAvailabilityListenerInstalled=true;
|
|
1157
|
-
speech.addEventListener(
|
|
1158
|
-
'speech-ready',
|
|
1159
|
-
applyPendingSpeechAvailability,
|
|
1160
|
-
{once:true}
|
|
1161
|
-
);
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
function applyPendingSpeechAvailability(){
|
|
1165
|
-
speechAvailabilityListenerInstalled=false;
|
|
1166
|
-
if(pendingSpeechAvailability){
|
|
1167
|
-
const availability=pendingSpeechAvailability;
|
|
1168
|
-
pendingSpeechAvailability=null;
|
|
1169
|
-
speech.setAvailability?.({...availability});
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
1138
|
function formatConversationTime(milliseconds=0){
|
|
1174
1139
|
const totalSeconds=Math.max(0,Math.floor(milliseconds/1000));
|
|
1175
1140
|
const hours=Math.floor(totalSeconds/3600);
|
|
@@ -63,6 +63,13 @@
|
|
|
63
63
|
text-align: center;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
#sttActivationButton {
|
|
67
|
+
grid-column: 1;
|
|
68
|
+
max-width: 8rem;
|
|
69
|
+
background: var(--arcane-action, var(--primary-color));
|
|
70
|
+
color: var(--arcane-action-text, var(--button-text-color, var(--text-color)));
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
#recordButton:active,
|
|
67
74
|
#recordButton.pressed {
|
|
68
75
|
opacity: .82;
|
|
@@ -108,7 +115,8 @@
|
|
|
108
115
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
#recordButton
|
|
118
|
+
#recordButton,
|
|
119
|
+
#sttActivationButton {
|
|
112
120
|
grid-row: 1 / span 2;
|
|
113
121
|
}
|
|
114
122
|
|
|
@@ -131,6 +139,7 @@
|
|
|
131
139
|
|
|
132
140
|
<p class="hidden">Will auto send after X seconds of silence set in code.</p>
|
|
133
141
|
<button id="recordButton" type="button" aria-label="Transcription unavailable" disabled>Hold to talk</button>
|
|
142
|
+
<button id="sttActivationButton" type="button" hidden disabled>Start transcription</button>
|
|
134
143
|
<button id="stopButton" type="button" class="hidden" disabled>Send</button>
|
|
135
144
|
<p id="speechStatus" class="speech_status" role="status" aria-live="polite"></p>
|
|
136
145
|
<button id="endButton" type="button" class="end">End</button>
|
|
@@ -147,10 +156,14 @@
|
|
|
147
156
|
requestAIRuntimeIntent,
|
|
148
157
|
subscribeAIRuntimeState
|
|
149
158
|
} = await import('../modules/AIRuntimeState.js');
|
|
159
|
+
const {
|
|
160
|
+
createSTTActivationController
|
|
161
|
+
} = await import('../modules/ComponentContracts.js');
|
|
150
162
|
|
|
151
163
|
const host = this;
|
|
152
164
|
const shadowRoot = host.shadowRoot;
|
|
153
165
|
const recordButton = shadowRoot.querySelector('#recordButton');
|
|
166
|
+
const sttActivationButton = shadowRoot.querySelector('#sttActivationButton');
|
|
154
167
|
const stopButton = shadowRoot.querySelector('#stopButton');
|
|
155
168
|
const endButton = shadowRoot.querySelector('#endButton');
|
|
156
169
|
const muteButton = shadowRoot.querySelector('#muteButton');
|
|
@@ -167,11 +180,13 @@
|
|
|
167
180
|
let recordingGeneration = 0;
|
|
168
181
|
let transcriptionGeneration = 0;
|
|
169
182
|
let transcriptionActive = false;
|
|
183
|
+
let transcriptionAbortController = null;
|
|
170
184
|
let silenceTimer = null;
|
|
171
185
|
let activePointerId = null;
|
|
172
186
|
let keyboardRecording = false;
|
|
173
187
|
let localStatus = null;
|
|
174
188
|
let pendingTTSIntent = null;
|
|
189
|
+
let ttsIntentGeneration = 0;
|
|
175
190
|
let destroyed = false;
|
|
176
191
|
|
|
177
192
|
const initialMutedWasHydrated = typeof host.initialMuted === 'boolean';
|
|
@@ -180,6 +195,10 @@
|
|
|
180
195
|
host.configure = configure;
|
|
181
196
|
host.destroy = destroy;
|
|
182
197
|
host.reportTTSError = reportTTSError;
|
|
198
|
+
host.requestSTTActivation = host.requestSTTActivation
|
|
199
|
+
|| function requestSTTActivation(intent) {
|
|
200
|
+
return requestAIRuntimeIntent(intent);
|
|
201
|
+
};
|
|
183
202
|
host.setAvailability = setAvailability;
|
|
184
203
|
host.setMuted = setMuted;
|
|
185
204
|
host.availability = {
|
|
@@ -190,6 +209,13 @@
|
|
|
190
209
|
stt: false,
|
|
191
210
|
tts: false
|
|
192
211
|
};
|
|
212
|
+
const sttActivationController = createSTTActivationController(
|
|
213
|
+
{
|
|
214
|
+
host,
|
|
215
|
+
button: sttActivationButton,
|
|
216
|
+
onChange: renderSTTActivationState
|
|
217
|
+
}
|
|
218
|
+
);
|
|
193
219
|
|
|
194
220
|
recordButton.addEventListener('pointerdown', handleRecordPointerDown);
|
|
195
221
|
recordButton.addEventListener('pointerup', handleRecordPointerUp);
|
|
@@ -248,14 +274,11 @@
|
|
|
248
274
|
);
|
|
249
275
|
}
|
|
250
276
|
|
|
251
|
-
function
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
loaded: ready
|
|
257
|
-
}
|
|
258
|
-
);
|
|
277
|
+
function selectedRole(role) {
|
|
278
|
+
return typeof role?.providerId === 'string'
|
|
279
|
+
&& role.providerId.length > 0
|
|
280
|
+
&& typeof role?.modelId === 'string'
|
|
281
|
+
&& role.modelId.length > 0;
|
|
259
282
|
}
|
|
260
283
|
|
|
261
284
|
function configure(options = {}) {
|
|
@@ -288,11 +311,15 @@
|
|
|
288
311
|
if (Object.prototype.hasOwnProperty.call(input, 'microphone')) {
|
|
289
312
|
host.availability.microphone = Boolean(input.microphone);
|
|
290
313
|
}
|
|
291
|
-
if (Object.prototype.hasOwnProperty.call(input, 'stt')
|
|
292
|
-
|
|
314
|
+
if (Object.prototype.hasOwnProperty.call(input, 'stt')
|
|
315
|
+
&& !Boolean(input.stt)
|
|
316
|
+
&& !selectedRole(sttRole)) {
|
|
317
|
+
sttRole = unavailableRole('stt');
|
|
293
318
|
}
|
|
294
|
-
if (Object.prototype.hasOwnProperty.call(input, 'tts')
|
|
295
|
-
|
|
319
|
+
if (Object.prototype.hasOwnProperty.call(input, 'tts')
|
|
320
|
+
&& !Boolean(input.tts)
|
|
321
|
+
&& !selectedRole(ttsRole)) {
|
|
322
|
+
ttsRole = unavailableRole('tts');
|
|
296
323
|
}
|
|
297
324
|
|
|
298
325
|
if (microphoneWasAvailable && !host.availability.microphone) {
|
|
@@ -337,6 +364,7 @@
|
|
|
337
364
|
}
|
|
338
365
|
|
|
339
366
|
function applyRoleTransitions(previousSTTRole, previousTTSRole) {
|
|
367
|
+
sttActivationController.synchronize(sttRole);
|
|
340
368
|
const sttBecameUnready = previousSTTRole.state === 'ready'
|
|
341
369
|
&& sttRole.state !== 'ready';
|
|
342
370
|
const ttsBecameUnready = previousTTSRole.state === 'ready'
|
|
@@ -362,6 +390,11 @@
|
|
|
362
390
|
renderStatus();
|
|
363
391
|
}
|
|
364
392
|
|
|
393
|
+
function renderSTTActivationState() {
|
|
394
|
+
renderControls();
|
|
395
|
+
renderStatus();
|
|
396
|
+
}
|
|
397
|
+
|
|
365
398
|
function clearSettledTTSIntent() {
|
|
366
399
|
if (!pendingTTSIntent) {
|
|
367
400
|
return;
|
|
@@ -383,6 +416,8 @@
|
|
|
383
416
|
function renderControls() {
|
|
384
417
|
const transcriptionReady = sttRole.state === 'ready';
|
|
385
418
|
const microphoneReady = host.availability.microphone;
|
|
419
|
+
const activationVisible = sttActivationController.visible;
|
|
420
|
+
recordButton.hidden = activationVisible;
|
|
386
421
|
recordButton.disabled = !transcriptionReady
|
|
387
422
|
|| !microphoneReady
|
|
388
423
|
|| sttRole.busy
|
|
@@ -395,6 +430,24 @@
|
|
|
395
430
|
'aria-pressed',
|
|
396
431
|
String(recordingRequested || Boolean(captureSession))
|
|
397
432
|
);
|
|
433
|
+
sttActivationButton.hidden = !activationVisible;
|
|
434
|
+
sttActivationButton.disabled = destroyed
|
|
435
|
+
|| sttActivationController.pending
|
|
436
|
+
|| sttRole.state === 'unloading'
|
|
437
|
+
|| sttActivationController.action === null;
|
|
438
|
+
sttActivationButton.textContent = sttActivationController.label;
|
|
439
|
+
sttActivationButton.title = sttActivationController.title;
|
|
440
|
+
sttActivationButton.setAttribute(
|
|
441
|
+
'aria-label',
|
|
442
|
+
sttActivationButton.title
|
|
443
|
+
);
|
|
444
|
+
sttActivationButton.setAttribute(
|
|
445
|
+
'aria-busy',
|
|
446
|
+
String(
|
|
447
|
+
sttActivationController.pending
|
|
448
|
+
|| ['loading', 'unloading'].includes(sttRole.state)
|
|
449
|
+
)
|
|
450
|
+
);
|
|
398
451
|
renderTTSControl();
|
|
399
452
|
}
|
|
400
453
|
|
|
@@ -492,31 +545,21 @@
|
|
|
492
545
|
'aria-busy',
|
|
493
546
|
String(
|
|
494
547
|
sttRole.state === 'loading'
|
|
548
|
+
|| sttRole.state === 'unloading'
|
|
495
549
|
|| ttsRole.state === 'loading'
|
|
496
550
|
|| sttRole.busy
|
|
497
551
|
|| ttsRole.busy
|
|
552
|
+
|| sttActivationController.pending
|
|
498
553
|
|| transcriptionActive
|
|
499
554
|
)
|
|
500
555
|
);
|
|
501
556
|
}
|
|
502
557
|
|
|
503
558
|
function describeSTTRole() {
|
|
504
|
-
if (sttRole.state === 'ready') {
|
|
505
|
-
|
|
506
|
-
return 'Microphone unavailable.';
|
|
507
|
-
}
|
|
508
|
-
return sttRole.busy ? 'Transcription busy.' : 'Transcription ready.';
|
|
509
|
-
}
|
|
510
|
-
if (sttRole.state === 'loading') {
|
|
511
|
-
return `Transcription ${formatRoleProgress(sttRole.progress, 'loading')}.`;
|
|
512
|
-
}
|
|
513
|
-
if (sttRole.state === 'error') {
|
|
514
|
-
return `Transcription error: ${visibleErrorMessage(sttRole.error, 'Unknown error')}.`;
|
|
559
|
+
if (sttRole.state === 'ready' && !host.availability.microphone) {
|
|
560
|
+
return 'Microphone unavailable.';
|
|
515
561
|
}
|
|
516
|
-
|
|
517
|
-
return 'Transcription disposed.';
|
|
518
|
-
}
|
|
519
|
-
return 'Transcription unavailable.';
|
|
562
|
+
return sttActivationController.status;
|
|
520
563
|
}
|
|
521
564
|
|
|
522
565
|
function describeTTSRole() {
|
|
@@ -748,10 +791,15 @@
|
|
|
748
791
|
}
|
|
749
792
|
|
|
750
793
|
function requestTTSIntent(action, reason) {
|
|
794
|
+
const generation = ++ttsIntentGeneration;
|
|
751
795
|
pendingTTSIntent = action === 'load' && ttsRole.state === 'ready'
|
|
752
796
|
? null
|
|
753
797
|
: action;
|
|
798
|
+
let lifecycle = null;
|
|
754
799
|
try {
|
|
800
|
+
if (typeof globalThis.ai?.setSpeechMuted === 'function') {
|
|
801
|
+
lifecycle = globalThis.ai.setSpeechMuted(action === 'unload');
|
|
802
|
+
}
|
|
755
803
|
requestAIRuntimeIntent(
|
|
756
804
|
{
|
|
757
805
|
role: 'tts',
|
|
@@ -761,8 +809,27 @@
|
|
|
761
809
|
);
|
|
762
810
|
} catch (error) {
|
|
763
811
|
pendingTTSIntent = null;
|
|
812
|
+
if (action === 'load') {
|
|
813
|
+
host.muted = true;
|
|
814
|
+
stopTTSPlayback();
|
|
815
|
+
}
|
|
764
816
|
reportTTSLifecycleError(error, action);
|
|
817
|
+
return false;
|
|
765
818
|
}
|
|
819
|
+
void Promise.resolve(lifecycle).catch(
|
|
820
|
+
function reportCurrentTTSSpeechLifecycleFailure(error) {
|
|
821
|
+
if (destroyed || generation !== ttsIntentGeneration) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
pendingTTSIntent = null;
|
|
825
|
+
if (action === 'load') {
|
|
826
|
+
host.muted = true;
|
|
827
|
+
stopTTSPlayback();
|
|
828
|
+
}
|
|
829
|
+
reportTTSLifecycleError(error, action);
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
return true;
|
|
766
833
|
}
|
|
767
834
|
|
|
768
835
|
function reportTTSLifecycleError(error, action) {
|
|
@@ -1051,6 +1118,9 @@
|
|
|
1051
1118
|
|
|
1052
1119
|
async function transcribeAudio(audioFile) {
|
|
1053
1120
|
const generation = ++transcriptionGeneration;
|
|
1121
|
+
const controller = new AbortController();
|
|
1122
|
+
transcriptionAbortController?.abort();
|
|
1123
|
+
transcriptionAbortController = controller;
|
|
1054
1124
|
transcriptionActive = true;
|
|
1055
1125
|
localStatus = {
|
|
1056
1126
|
message: 'Transcribing speech.',
|
|
@@ -1065,7 +1135,11 @@
|
|
|
1065
1135
|
throw new Error('The selected transcription service is unavailable.');
|
|
1066
1136
|
}
|
|
1067
1137
|
|
|
1068
|
-
const transcription = await aiRuntime.fetchSTT(
|
|
1138
|
+
const transcription = await aiRuntime.fetchSTT(
|
|
1139
|
+
audioFile,
|
|
1140
|
+
undefined,
|
|
1141
|
+
controller.signal
|
|
1142
|
+
);
|
|
1069
1143
|
if (
|
|
1070
1144
|
generation !== transcriptionGeneration
|
|
1071
1145
|
|| sttRole.state !== 'ready'
|
|
@@ -1086,6 +1160,9 @@
|
|
|
1086
1160
|
}
|
|
1087
1161
|
return false;
|
|
1088
1162
|
} finally {
|
|
1163
|
+
if (transcriptionAbortController === controller) {
|
|
1164
|
+
transcriptionAbortController = null;
|
|
1165
|
+
}
|
|
1089
1166
|
if (generation === transcriptionGeneration) {
|
|
1090
1167
|
transcriptionActive = false;
|
|
1091
1168
|
renderControls();
|
|
@@ -1126,6 +1203,8 @@
|
|
|
1126
1203
|
|| transcriptionActive;
|
|
1127
1204
|
recordingGeneration += 1;
|
|
1128
1205
|
transcriptionGeneration += 1;
|
|
1206
|
+
transcriptionAbortController?.abort();
|
|
1207
|
+
transcriptionAbortController = null;
|
|
1129
1208
|
recordingRequested = false;
|
|
1130
1209
|
transcriptionActive = false;
|
|
1131
1210
|
clearSilenceTimer();
|
|
@@ -1199,6 +1278,7 @@
|
|
|
1199
1278
|
|
|
1200
1279
|
destroyed = true;
|
|
1201
1280
|
runtimeStateAbortController.abort();
|
|
1281
|
+
sttActivationController.destroy();
|
|
1202
1282
|
window.removeEventListener('pagehide', destroy);
|
|
1203
1283
|
microphonePermissionStatus?.removeEventListener?.(
|
|
1204
1284
|
'change',
|