arcane-os 0.1.2 → 0.2.1
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 +35 -0
- package/NOTICE +5 -3
- package/README.md +73 -24
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +67 -18
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +16 -5
- package/browser-runtime/ai/browser-kokoro-worker.mjs +3 -0
- package/browser-runtime/ai/browser-speech-artifacts.mjs +1108 -0
- package/browser-runtime/ai/browser-speech-providers.mjs +780 -0
- package/browser-runtime/ai/browser-speech.mjs +9 -0
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1537 -167
- package/browser-runtime/ai/browser-wasm.mjs +46 -1
- package/browser-runtime/ai/browser-whisper-worker.mjs +3 -0
- package/browser-runtime/ai/browser-wllama-runtime.mjs +677 -132
- package/browser-runtime/ai/model-controller.mjs +138 -12
- package/browser-runtime/ai/speech-worker-client.mjs +207 -0
- package/browser-runtime/ai/speech-worker-runtime.mjs +516 -0
- package/browser-runtime/ai/wllama/index.mjs +389 -0
- package/docs/architecture.md +132 -22
- package/docs/reference/README.md +1 -1
- package/docs/reference/ai/browser-wasm.md +101 -42
- package/docs/reference/availability-and-normalization.md +19 -5
- package/docs/reference/behavioral-testing.md +18 -5
- package/docs/reference/cli.md +2 -2
- package/docs/reference/inventory/package-api.json +14 -14
- package/docs/reference/protocols.md +4 -4
- package/docs/reference/sdk-api.md +68 -38
- package/docs/work-amplification.md +8 -4
- package/package.json +7 -3
- package/runtime/ARCANE_RUNTIME_RELEASE.json +50 -20
- package/runtime/arcane/components/chat.html +551 -62
- package/runtime/arcane/components/speech.html +1113 -265
- package/runtime/arcane/entities/Chat.js +246 -43
- package/runtime/arcane/modules/AI.js +1394 -162
- package/runtime/arcane/modules/AIProviderRuntime.js +2289 -0
- package/runtime/arcane/modules/AIRuntimeState.js +872 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +382 -31
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +1106 -0
- package/runtime/arcane/modules/DocumentLexicalSearch.js +292 -0
- package/runtime/arcane/modules/PersistentAIChatSession.js +268 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +25 -206
- package/schemas/arcane-lock.schema.json +10 -6
- package/src/cli/main.mjs +14 -2
- package/src/constants.mjs +1 -1
- package/src/dev-server.mjs +273 -26
- package/src/doctor.mjs +1 -3
- package/src/import-map.mjs +193 -84
- package/src/packager/core.mjs +313 -41
- package/src/runtime.mjs +14 -4
- package/src/scaffold.mjs +45 -17
- package/src/sdk-browser-runtime.mjs +28 -75
- package/src/templates/workspace-template.mjs +27 -8
- package/src/toolchain.mjs +13 -2
- package/src/workspace-runtime.mjs +1 -1
- package/src/workspace.mjs +178 -25
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
<link rel="stylesheet" href="./arcane/css/layout.css?v=3" />
|
|
2
2
|
<style>
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
height: 100%;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
min-height: 0;
|
|
8
|
+
--arcane-chat-gap: var(--arcane-space-3, .75rem);
|
|
9
|
+
--arcane-chat-radius: var(--arcane-radius-large, 1rem);
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
.chat_area {
|
|
4
13
|
display: flex;
|
|
5
14
|
flex-direction: column;
|
|
6
15
|
justify-content: flex-end;
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
width: min(100%, 72rem);
|
|
18
|
+
height: 100%;
|
|
19
|
+
min-height: 0;
|
|
20
|
+
margin-inline: auto;
|
|
21
|
+
padding: clamp(.65rem, 1.5vw, 1.25rem);
|
|
22
|
+
gap: var(--arcane-chat-gap);
|
|
9
23
|
overflow: hidden;
|
|
10
24
|
}
|
|
11
25
|
|
|
@@ -86,6 +100,7 @@
|
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
.chat_area[data-conversation-complete="true"] .conversation_timebox,
|
|
103
|
+
.chat_area[data-conversation-complete="true"] .ai_activation,
|
|
89
104
|
.chat_area[data-conversation-complete="true"] .chat_input_area,
|
|
90
105
|
.chat_area[data-conversation-complete="true"] > #speech,
|
|
91
106
|
.chat_area[data-conversation-complete="true"] > .controls{
|
|
@@ -114,80 +129,185 @@
|
|
|
114
129
|
border:0;
|
|
115
130
|
}
|
|
116
131
|
|
|
117
|
-
.
|
|
132
|
+
.chat_output {
|
|
118
133
|
flex:1 1 auto;
|
|
134
|
+
box-sizing: border-box;
|
|
135
|
+
width: 100%;
|
|
119
136
|
min-height:0;
|
|
137
|
+
margin: 0;
|
|
138
|
+
padding: clamp(1rem, 2vw, 1.5rem);
|
|
120
139
|
list-style: none;
|
|
121
140
|
overflow-y: auto;
|
|
122
|
-
|
|
141
|
+
scrollbar-gutter: stable;
|
|
142
|
+
border: 1px solid var(--arcane-border, var(--border-color));
|
|
143
|
+
border-radius: var(--arcane-chat-radius);
|
|
144
|
+
background: var(--arcane-surface-raised, var(--modal-background));
|
|
145
|
+
box-shadow: var(--arcane-shadow-medium, 0 .65rem 1.8rem var(--shadow-color));
|
|
123
146
|
}
|
|
124
147
|
|
|
125
|
-
.
|
|
126
|
-
|
|
127
|
-
margin: 0 0 1em;
|
|
148
|
+
.chat_output > li {
|
|
149
|
+
box-sizing: border-box;
|
|
128
150
|
width: auto;
|
|
129
|
-
max-width:
|
|
151
|
+
max-width: min(92%, 48rem);
|
|
152
|
+
margin: 0 0 .85rem;
|
|
153
|
+
padding: .85rem 1rem;
|
|
130
154
|
clear: both;
|
|
155
|
+
border: 1px solid var(--arcane-border, var(--border-color));
|
|
156
|
+
border-radius: calc(var(--arcane-chat-radius) - .1rem);
|
|
157
|
+
overflow-wrap: anywhere;
|
|
131
158
|
}
|
|
132
159
|
|
|
133
|
-
.
|
|
134
|
-
width:
|
|
160
|
+
.chat_output > li.ai {
|
|
161
|
+
width: auto;
|
|
135
162
|
float: left;
|
|
136
|
-
|
|
163
|
+
background: var(--arcane-surface, var(--background));
|
|
164
|
+
border-start-start-radius: var(--arcane-radius-small, .3rem);
|
|
137
165
|
}
|
|
138
166
|
|
|
139
|
-
|
|
140
|
-
.chat_area li.user {
|
|
141
|
-
text-align: right;
|
|
167
|
+
.chat_output > li.user {
|
|
142
168
|
float: right;
|
|
169
|
+
background: var(--arcane-action, var(--primary-color));
|
|
170
|
+
color: var(--arcane-action-text, var(--button-text-color, var(--text-color)));
|
|
171
|
+
border-color: var(--arcane-action, var(--primary-color));
|
|
172
|
+
border-start-end-radius: var(--arcane-radius-small, .3rem);
|
|
173
|
+
text-align: right;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.chat_output li ul,
|
|
177
|
+
.chat_output li ol {
|
|
178
|
+
display: block;
|
|
179
|
+
min-height: auto;
|
|
180
|
+
margin: .55rem 0 .55rem 1.35rem;
|
|
181
|
+
padding: 0;
|
|
182
|
+
overflow: visible;
|
|
183
|
+
list-style: revert;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.chat_output li li {
|
|
187
|
+
float: none;
|
|
188
|
+
width: auto;
|
|
189
|
+
max-width: none;
|
|
190
|
+
margin: .28rem 0;
|
|
191
|
+
padding: 0;
|
|
192
|
+
border: 0;
|
|
193
|
+
border-radius: 0;
|
|
194
|
+
background: transparent;
|
|
195
|
+
color: inherit;
|
|
196
|
+
text-align: start;
|
|
143
197
|
}
|
|
144
198
|
|
|
145
199
|
.chat_input_area {
|
|
146
200
|
display: grid;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
201
|
+
box-sizing: border-box;
|
|
202
|
+
width: 100%;
|
|
203
|
+
padding: var(--arcane-space-3, .7rem);
|
|
204
|
+
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: var(--arcane-space-2, .6rem);
|
|
207
|
+
border: 1px solid var(--arcane-border, var(--border-color));
|
|
208
|
+
border-radius: var(--arcane-chat-radius);
|
|
209
|
+
background: var(--arcane-surface-raised, var(--modal-background));
|
|
210
|
+
box-shadow: var(--arcane-shadow-small, 0 .35rem 1rem var(--shadow-color));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.ai_activation {
|
|
214
|
+
display:flex;
|
|
215
|
+
flex:0 0 auto;
|
|
216
|
+
box-sizing:border-box;
|
|
217
|
+
width:100%;
|
|
218
|
+
padding:var(--arcane-space-3, .75rem);
|
|
219
|
+
align-items:center;
|
|
220
|
+
justify-content:space-between;
|
|
221
|
+
gap:var(--arcane-space-3, .75rem);
|
|
222
|
+
border:1px solid var(--arcane-border, var(--border-color));
|
|
223
|
+
border-radius:var(--arcane-chat-radius);
|
|
224
|
+
background:var(--arcane-surface-raised, var(--modal-background));
|
|
225
|
+
color:var(--text-color);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.ai_activation[hidden] {
|
|
229
|
+
display:none;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.ai_activation_copy {
|
|
233
|
+
min-width:0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.ai_activation_copy strong,
|
|
237
|
+
.ai_activation_copy span {
|
|
238
|
+
display:block;
|
|
239
|
+
overflow-wrap:anywhere;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.ai_activation_copy span {
|
|
243
|
+
margin-block-start:.22rem;
|
|
244
|
+
font-size:.9rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.ai_activation button {
|
|
248
|
+
flex:0 0 auto;
|
|
249
|
+
min-height:2.65rem;
|
|
250
|
+
padding:.55rem .8rem;
|
|
251
|
+
border:1px solid var(--arcane-action, var(--primary-color));
|
|
252
|
+
border-radius:var(--arcane-radius-medium, .7rem);
|
|
253
|
+
background:var(--arcane-action, var(--primary-color));
|
|
254
|
+
color:var(--arcane-action-text, var(--button-text-color, var(--text-color)));
|
|
255
|
+
font:inherit;
|
|
256
|
+
font-weight:600;
|
|
257
|
+
cursor:pointer;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.ai_activation button:focus-visible {
|
|
261
|
+
outline:.2rem solid var(--arcane-focus, var(--focus-color));
|
|
262
|
+
outline-offset:.18rem;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.ai_activation button:disabled {
|
|
266
|
+
cursor:not-allowed;
|
|
267
|
+
opacity:.65;
|
|
150
268
|
}
|
|
151
269
|
|
|
152
270
|
.chat_input {
|
|
153
|
-
width: calc(100% - 1em);
|
|
154
|
-
min-height: 48px;
|
|
155
|
-
max-height: 160px;
|
|
156
|
-
padding: 1.1em 1em;
|
|
157
|
-
margin: 0;
|
|
158
|
-
border:2px solid var(--border-color);
|
|
159
|
-
border-radius: 0.5em;
|
|
160
|
-
font-family: Arial, sans-serif;
|
|
161
|
-
font-size: 16px;
|
|
162
|
-
line-height: 20px;
|
|
163
271
|
box-sizing: border-box;
|
|
272
|
+
width: 100%;
|
|
273
|
+
min-height: 3rem;
|
|
274
|
+
max-height: 10rem;
|
|
275
|
+
margin: 0;
|
|
276
|
+
padding: .82rem .9rem;
|
|
277
|
+
border: 1px solid var(--arcane-border, var(--border-color));
|
|
278
|
+
border-radius: var(--arcane-radius-medium, .7rem);
|
|
279
|
+
background: var(--arcane-surface, var(--background));
|
|
280
|
+
color: var(--text-color);
|
|
281
|
+
font: inherit;
|
|
282
|
+
font-size: 1rem;
|
|
283
|
+
line-height: 1.35;
|
|
164
284
|
resize: none;
|
|
165
285
|
overflow-y: auto;
|
|
166
286
|
}
|
|
167
287
|
|
|
168
|
-
.chat_input:focus {
|
|
169
|
-
outline
|
|
170
|
-
outline-offset
|
|
171
|
-
border-color:var(--focus-color);
|
|
288
|
+
.chat_input:focus-visible {
|
|
289
|
+
outline: .18em solid var(--arcane-focus, var(--focus-color));
|
|
290
|
+
outline-offset: .12em;
|
|
291
|
+
border-color: var(--arcane-focus, var(--focus-color));
|
|
172
292
|
}
|
|
173
293
|
|
|
174
294
|
.chat_submit,
|
|
175
295
|
button.chat_upload {
|
|
176
296
|
display:grid;
|
|
177
297
|
place-items:center;
|
|
178
|
-
width:
|
|
179
|
-
height:
|
|
298
|
+
width:3rem;
|
|
299
|
+
height:3rem;
|
|
180
300
|
padding:0;
|
|
181
301
|
margin:0;
|
|
182
|
-
background-color:var(--primary-color);
|
|
183
|
-
border:.2em solid var(--primary-color);
|
|
184
|
-
border-radius
|
|
302
|
+
background-color:var(--arcane-action, var(--primary-color));
|
|
303
|
+
border:.2em solid var(--arcane-action, var(--primary-color));
|
|
304
|
+
border-radius:var(--arcane-radius-medium, .7rem);
|
|
185
305
|
cursor: pointer;
|
|
186
306
|
outline: none;
|
|
187
|
-
box-shadow:0 .18em .45em var(--shadow-color);
|
|
188
|
-
align-self:
|
|
307
|
+
box-shadow:var(--arcane-shadow-small, 0 .18em .45em var(--shadow-color));
|
|
308
|
+
align-self:center;
|
|
189
309
|
transition:background-color 180ms,border-color 180ms,box-shadow 180ms,transform 120ms;
|
|
190
|
-
color:var(--button-text-color,var(--text-color));
|
|
310
|
+
color:var(--arcane-action-text, var(--button-text-color,var(--text-color)));
|
|
191
311
|
}
|
|
192
312
|
|
|
193
313
|
.chat_submit::before,
|
|
@@ -221,12 +341,59 @@
|
|
|
221
341
|
|
|
222
342
|
.chat_submit:hover,
|
|
223
343
|
button.chat_upload:hover {
|
|
224
|
-
background-color:var(--secondary-color);
|
|
225
|
-
border-color:var(--secondary-color);
|
|
226
|
-
color:var(--text-color);
|
|
344
|
+
background-color:var(--arcane-secondary, var(--secondary-color));
|
|
345
|
+
border-color:var(--arcane-secondary, var(--secondary-color));
|
|
346
|
+
color:var(--arcane-secondary-text, var(--text-color));
|
|
227
347
|
box-shadow:0 .25em .6em var(--shadow-color);
|
|
228
348
|
}
|
|
229
349
|
|
|
350
|
+
.chat_submit:focus-visible,
|
|
351
|
+
button.chat_upload:focus-visible {
|
|
352
|
+
outline: .2rem solid var(--arcane-focus, var(--focus-color));
|
|
353
|
+
outline-offset: .18rem;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#speech {
|
|
357
|
+
display: grid;
|
|
358
|
+
flex: 0 0 auto;
|
|
359
|
+
min-width: 0;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.controls {
|
|
363
|
+
display: flex;
|
|
364
|
+
justify-content: flex-end;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.controls select {
|
|
368
|
+
width: min(100%, 18rem);
|
|
369
|
+
min-height: 2.65rem;
|
|
370
|
+
padding: .55rem .75rem;
|
|
371
|
+
border: 1px solid var(--arcane-border, var(--border-color));
|
|
372
|
+
border-radius: var(--arcane-radius-medium, .7rem);
|
|
373
|
+
background: var(--arcane-surface-raised, var(--modal-background));
|
|
374
|
+
color: var(--text-color);
|
|
375
|
+
font: inherit;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.controls select:focus-visible {
|
|
379
|
+
outline: .2rem solid var(--arcane-focus, var(--focus-color));
|
|
380
|
+
outline-offset: .18rem;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
@media (max-width: 44rem) {
|
|
384
|
+
.chat_area {
|
|
385
|
+
padding: var(--arcane-space-2, .5rem);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.chat_output > li {
|
|
389
|
+
max-width: 96%;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.controls select {
|
|
393
|
+
width: 100%;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
230
397
|
|
|
231
398
|
:host(.journal){
|
|
232
399
|
.chat_output {
|
|
@@ -312,6 +479,20 @@
|
|
|
312
479
|
>Starting the elapsed timer.</span>
|
|
313
480
|
</section>
|
|
314
481
|
<ul type='text' class='chat_output' id='chat_output' tabindex='-1' aria-label='Conversation transcript'></ul>
|
|
482
|
+
<section
|
|
483
|
+
id="ai_activation"
|
|
484
|
+
class="ai_activation"
|
|
485
|
+
role="status"
|
|
486
|
+
aria-live="polite"
|
|
487
|
+
aria-busy="false"
|
|
488
|
+
hidden
|
|
489
|
+
>
|
|
490
|
+
<span class="ai_activation_copy">
|
|
491
|
+
<strong id="ai_activation_title">Language model not active</strong>
|
|
492
|
+
<span id="ai_activation_status">Start the selected language model to enable chat.</span>
|
|
493
|
+
</span>
|
|
494
|
+
<button type="button" id="ai_activation_button">Start language model</button>
|
|
495
|
+
</section>
|
|
315
496
|
<div class="chat_input_area">
|
|
316
497
|
<textarea type='text' class='chat_input' id='chat_input'></textarea>
|
|
317
498
|
<button type="button" id="chat_upload" class="chat_upload" title="Upload files" aria-label="Upload files">
|
|
@@ -515,14 +696,20 @@
|
|
|
515
696
|
const conversationTimeboxPanel=shadowRoot.querySelector('#conversation_timebox');
|
|
516
697
|
const conversationTimeboxElapsed=shadowRoot.querySelector('#conversation_timebox_elapsed');
|
|
517
698
|
const conversationTimeboxStatus=shadowRoot.querySelector('#conversation_timebox_status');
|
|
699
|
+
const aiActivationPanel=shadowRoot.querySelector('#ai_activation');
|
|
700
|
+
const aiActivationTitle=shadowRoot.querySelector('#ai_activation_title');
|
|
701
|
+
const aiActivationStatus=shadowRoot.querySelector('#ai_activation_status');
|
|
702
|
+
const aiActivationButton=shadowRoot.querySelector('#ai_activation_button');
|
|
518
703
|
const [
|
|
519
704
|
{default:MD},
|
|
520
705
|
{default:FileEntity},
|
|
521
|
-
{ConversationSubmissionBarrier,requireConversationTimeboxDelivery}
|
|
706
|
+
{ConversationSubmissionBarrier,requireConversationTimeboxDelivery},
|
|
707
|
+
{requestAIRuntimeIntent,subscribeAIRuntimeState}
|
|
522
708
|
]=await Promise.all([
|
|
523
709
|
import('../modules/MD.js'),
|
|
524
710
|
import('../entities/File.js'),
|
|
525
|
-
import('../modules/ConversationTimebox.js')
|
|
711
|
+
import('../modules/ConversationTimebox.js'),
|
|
712
|
+
import('../modules/AIRuntimeState.js')
|
|
526
713
|
]);
|
|
527
714
|
|
|
528
715
|
host.language=host.language||'English';
|
|
@@ -532,7 +719,10 @@
|
|
|
532
719
|
?speech.initialMuted
|
|
533
720
|
:true;
|
|
534
721
|
let initialSpeechMuteListenerInstalled=false;
|
|
722
|
+
let pendingSpeechAvailability=null;
|
|
723
|
+
let speechAvailabilityListenerInstalled=false;
|
|
535
724
|
const hostSubmissionBarrier=new ConversationSubmissionBarrier();
|
|
725
|
+
const aiRuntimeStateAbortController=new AbortController();
|
|
536
726
|
let programmaticSubmissionQueue=Promise.resolve(true);
|
|
537
727
|
let conversationTimeboxController=null;
|
|
538
728
|
let conversationTimeboxUnsubscribe=null;
|
|
@@ -549,6 +739,18 @@
|
|
|
549
739
|
console.warn('html-import chat.html `host.languageChanged` method not implemented.\n\nThis can also be done by listening for the "chat-language-changed" event.\n\nIt is still best practice to implement the `.languageChanged` method atleast with an empty function.\n\n')
|
|
550
740
|
}
|
|
551
741
|
|
|
742
|
+
host.requestAIActivation=host.requestAIActivation||function requestAIActivation(intent){
|
|
743
|
+
return requestAIRuntimeIntent(intent);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const aiActivationController=createAIActivationController({
|
|
747
|
+
host,
|
|
748
|
+
panel:aiActivationPanel,
|
|
749
|
+
title:aiActivationTitle,
|
|
750
|
+
status:aiActivationStatus,
|
|
751
|
+
button:aiActivationButton
|
|
752
|
+
});
|
|
753
|
+
|
|
552
754
|
host.streamMessage=streamMessage;
|
|
553
755
|
host.setMessageProgress=setMessageProgress;
|
|
554
756
|
host.setAIAvailability=setAIAvailability;
|
|
@@ -556,8 +758,17 @@
|
|
|
556
758
|
host.setConversationComplete=setConversationComplete;
|
|
557
759
|
host.bindConversationTimebox=bindConversationTimebox;
|
|
558
760
|
host.submitMessage=submitMessage;
|
|
559
|
-
host.
|
|
761
|
+
host.destroy=destroy;
|
|
762
|
+
host.aiAvailability={llm:false,stt:false,tts:false};
|
|
560
763
|
host.conversationComplete=false;
|
|
764
|
+
applyAIAvailability(host.aiAvailability);
|
|
765
|
+
subscribeAIRuntimeState(
|
|
766
|
+
synchronizeAIRuntimeState,
|
|
767
|
+
{
|
|
768
|
+
signal:aiRuntimeStateAbortController.signal
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
window.addEventListener('pagehide',destroy,{once:true});
|
|
561
772
|
|
|
562
773
|
function setConversationComplete(complete=true){
|
|
563
774
|
if(typeof complete!=='boolean'){
|
|
@@ -699,6 +910,7 @@
|
|
|
699
910
|
}
|
|
700
911
|
|
|
701
912
|
function setAIAvailability(input={}){
|
|
913
|
+
const explicitSpeechAvailability={};
|
|
702
914
|
const availability={
|
|
703
915
|
llm:Object.prototype.hasOwnProperty.call(input,'llm')
|
|
704
916
|
?Boolean(input.llm)
|
|
@@ -711,25 +923,251 @@
|
|
|
711
923
|
:host.aiAvailability.tts
|
|
712
924
|
};
|
|
713
925
|
|
|
714
|
-
|
|
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
|
+
applyAIAvailability(availability);
|
|
933
|
+
if(Object.keys(explicitSpeechAvailability).length){
|
|
934
|
+
pendingSpeechAvailability={
|
|
935
|
+
...(pendingSpeechAvailability||{}),
|
|
936
|
+
...explicitSpeechAvailability
|
|
937
|
+
};
|
|
938
|
+
synchronizeSpeechCompatibilityAvailability();
|
|
939
|
+
}
|
|
940
|
+
return {...availability};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function applyAIAvailability(availability){
|
|
944
|
+
host.aiAvailability={...availability};
|
|
715
945
|
send.disabled=host.conversationComplete||!availability.llm;
|
|
716
946
|
send.title=availability.llm
|
|
717
947
|
?'Send message'
|
|
718
948
|
:'The selected language model service is unavailable.';
|
|
719
949
|
send.setAttribute('aria-label',send.title);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function createAIActivationController({host,panel,title,status,button,EventClass=CustomEvent}){
|
|
953
|
+
let role=null;
|
|
954
|
+
let requestPending=false;
|
|
955
|
+
let requestError='';
|
|
956
|
+
let destroyed=false;
|
|
957
|
+
|
|
958
|
+
function visibleError(error,fallback){
|
|
959
|
+
const message=typeof error?.message==='string'
|
|
960
|
+
?error.message.trim()
|
|
961
|
+
:'';
|
|
962
|
+
return (message||fallback).slice(0,240);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function cancellation(error){
|
|
966
|
+
return error?.name==='AbortError'
|
|
967
|
+
||['ARCANE_AI_REQUEST_ABORTED','AI_CHAT_ABORTED'].includes(error?.code);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function selected(){
|
|
971
|
+
return typeof role?.providerId==='string'
|
|
972
|
+
&&role.providerId.length>0
|
|
973
|
+
&&typeof role?.modelId==='string'
|
|
974
|
+
&&role.modelId.length>0;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function progressMessage(){
|
|
978
|
+
if(!role?.progress){
|
|
979
|
+
return 'The selected language model is loading.';
|
|
980
|
+
}
|
|
981
|
+
const progress=role.progress;
|
|
982
|
+
const amount=progress.total===null
|
|
983
|
+
?`${progress.completed} ${progress.unit}`
|
|
984
|
+
:`${progress.completed} of ${progress.total} ${progress.unit}`;
|
|
985
|
+
return `${progress.phase}, ${amount}${progress.heartbeat?', active heartbeat':''}.`;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function render(){
|
|
989
|
+
const visible=selected()&&role.state!=='ready';
|
|
990
|
+
panel.hidden=!visible;
|
|
991
|
+
panel.setAttribute(
|
|
992
|
+
'aria-busy',
|
|
993
|
+
String(requestPending||['loading','unloading'].includes(role?.state))
|
|
994
|
+
);
|
|
995
|
+
if(!visible){
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if(requestPending){
|
|
999
|
+
title.textContent='Requesting language model activation';
|
|
1000
|
+
status.textContent='Waiting for the selected language model runtime.';
|
|
1001
|
+
button.textContent='Requesting…';
|
|
1002
|
+
button.disabled=true;
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if(role.state==='loading'){
|
|
1006
|
+
title.textContent='Starting language model';
|
|
1007
|
+
status.textContent=progressMessage();
|
|
1008
|
+
button.textContent='Cancel loading';
|
|
1009
|
+
button.disabled=false;
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if(role.state==='unloading'){
|
|
1013
|
+
title.textContent='Canceling language model load';
|
|
1014
|
+
status.textContent='Waiting for the selected language model runtime to stop.';
|
|
1015
|
+
button.textContent='Canceling…';
|
|
1016
|
+
button.disabled=true;
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if(role.state==='error'||requestError){
|
|
1020
|
+
title.textContent='Language model activation failed';
|
|
1021
|
+
status.textContent=requestError||visibleError(
|
|
1022
|
+
role.error,
|
|
1023
|
+
'The selected language model could not start.'
|
|
1024
|
+
);
|
|
1025
|
+
button.textContent='Try again';
|
|
1026
|
+
button.disabled=false;
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if(role.state==='unloaded'){
|
|
1030
|
+
title.textContent='Language model not active';
|
|
1031
|
+
status.textContent='Start the selected language model to enable chat.';
|
|
1032
|
+
button.textContent='Start language model';
|
|
1033
|
+
button.disabled=false;
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
title.textContent='Language model unavailable';
|
|
1037
|
+
status.textContent='The selected language model cannot be activated in this state.';
|
|
1038
|
+
button.textContent='Unavailable';
|
|
1039
|
+
button.disabled=true;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
async function request(action){
|
|
1043
|
+
if(destroyed||requestPending||!selected()){
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
const permitted=action==='load'
|
|
1047
|
+
?['unloaded','error'].includes(role.state)
|
|
1048
|
+
:action==='unload'&&role.state==='loading';
|
|
1049
|
+
if(!permitted){
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
requestError='';
|
|
1053
|
+
const intent=Object.freeze({role:'llm',action,reason:'user'});
|
|
1054
|
+
const request=Object.freeze({intent,state:role});
|
|
1055
|
+
const activationEvent=new EventClass(
|
|
1056
|
+
'chat-ai-activation-request',
|
|
1057
|
+
{
|
|
1058
|
+
bubbles:true,
|
|
1059
|
+
composed:true,
|
|
1060
|
+
cancelable:true,
|
|
1061
|
+
detail:request
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
if(!host.dispatchEvent(activationEvent)){
|
|
1065
|
+
render();
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
requestPending=true;
|
|
1069
|
+
render();
|
|
1070
|
+
try{
|
|
1071
|
+
await host.requestAIActivation(intent);
|
|
1072
|
+
return true;
|
|
1073
|
+
}catch(error){
|
|
1074
|
+
if(action==='load'
|
|
1075
|
+
&&cancellation(error)
|
|
1076
|
+
&&['unloaded','unloading'].includes(role?.state)){
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
const message=visibleError(
|
|
1080
|
+
error,
|
|
1081
|
+
`The language model ${action} request failed.`
|
|
1082
|
+
);
|
|
1083
|
+
requestError=message;
|
|
1084
|
+
host.dispatchEvent(
|
|
1085
|
+
new EventClass(
|
|
1086
|
+
'chat-ai-activation-error',
|
|
1087
|
+
{
|
|
1088
|
+
bubbles:true,
|
|
1089
|
+
composed:true,
|
|
1090
|
+
detail:Object.freeze({request,error,message})
|
|
1091
|
+
}
|
|
1092
|
+
)
|
|
1093
|
+
);
|
|
1094
|
+
return false;
|
|
1095
|
+
}finally{
|
|
1096
|
+
requestPending=false;
|
|
1097
|
+
render();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
720
1100
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1101
|
+
function activateSelectedAI(){
|
|
1102
|
+
const action=role?.state==='loading'?'unload':'load';
|
|
1103
|
+
void request(action);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function synchronize(nextRole){
|
|
1107
|
+
if(destroyed){
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if(role?.state!==nextRole.state
|
|
1111
|
+
||role?.providerId!==nextRole.providerId
|
|
1112
|
+
||role?.modelId!==nextRole.modelId){
|
|
1113
|
+
requestError='';
|
|
1114
|
+
requestPending=false;
|
|
1115
|
+
}
|
|
1116
|
+
role=nextRole;
|
|
1117
|
+
render();
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function destroy(){
|
|
1121
|
+
if(destroyed){
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
destroyed=true;
|
|
1125
|
+
button.removeEventListener('click',activateSelectedAI);
|
|
1126
|
+
panel.hidden=true;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
button.addEventListener('click',activateSelectedAI);
|
|
1130
|
+
return Object.freeze({request,synchronize,destroy});
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function synchronizeAIRuntimeState(snapshot){
|
|
1134
|
+
aiActivationController.synchronize(snapshot.roles.llm);
|
|
1135
|
+
applyAIAvailability({
|
|
1136
|
+
llm:snapshot.roles.llm.state==='ready',
|
|
1137
|
+
stt:snapshot.roles.stt.state==='ready',
|
|
1138
|
+
tts:snapshot.roles.tts.state==='ready'
|
|
724
1139
|
});
|
|
1140
|
+
}
|
|
725
1141
|
|
|
1142
|
+
function synchronizeSpeechCompatibilityAvailability(){
|
|
1143
|
+
if(!pendingSpeechAvailability){
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
726
1146
|
if(speech.componentReady){
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
speech.
|
|
1147
|
+
const availability=pendingSpeechAvailability;
|
|
1148
|
+
pendingSpeechAvailability=null;
|
|
1149
|
+
speech.setAvailability?.({...availability});
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if(speechAvailabilityListenerInstalled){
|
|
1153
|
+
return;
|
|
730
1154
|
}
|
|
731
1155
|
|
|
732
|
-
|
|
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
|
+
}
|
|
733
1171
|
}
|
|
734
1172
|
|
|
735
1173
|
function formatConversationTime(milliseconds=0){
|
|
@@ -928,7 +1366,7 @@
|
|
|
928
1366
|
}
|
|
929
1367
|
)
|
|
930
1368
|
);
|
|
931
|
-
console.error('The conversation timebox message could not be sent.'
|
|
1369
|
+
console.error('The conversation timebox message could not be sent.');
|
|
932
1370
|
return false;
|
|
933
1371
|
}
|
|
934
1372
|
);
|
|
@@ -997,10 +1435,22 @@
|
|
|
997
1435
|
const clearThinking=thinkingHTML;
|
|
998
1436
|
|
|
999
1437
|
//console.table(arguments);
|
|
1000
|
-
if(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1438
|
+
if(
|
|
1439
|
+
!isThinking
|
|
1440
|
+
&&text
|
|
1441
|
+
&&host.aiAvailability.tts
|
|
1442
|
+
&&speech.muted===false
|
|
1443
|
+
){
|
|
1444
|
+
const aiRuntime=globalThis.ai;
|
|
1445
|
+
if(typeof aiRuntime?.streamTTS==='function'){
|
|
1446
|
+
void Promise.resolve(aiRuntime.streamTTS(text)).catch(
|
|
1447
|
+
reportTTSError
|
|
1448
|
+
);
|
|
1449
|
+
}else{
|
|
1450
|
+
reportTTSError(
|
|
1451
|
+
new Error('The ready speech provider has no synthesis adapter.')
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1004
1454
|
}
|
|
1005
1455
|
|
|
1006
1456
|
if(clearThinking){
|
|
@@ -1126,7 +1576,7 @@
|
|
|
1126
1576
|
}
|
|
1127
1577
|
)
|
|
1128
1578
|
);
|
|
1129
|
-
console.error('The chat message could not be submitted.'
|
|
1579
|
+
console.error('The chat message could not be submitted.');
|
|
1130
1580
|
return false;
|
|
1131
1581
|
}
|
|
1132
1582
|
);
|
|
@@ -1143,6 +1593,9 @@
|
|
|
1143
1593
|
if(host.conversationComplete){
|
|
1144
1594
|
return false;
|
|
1145
1595
|
}
|
|
1596
|
+
if(!host.aiAvailability.llm){
|
|
1597
|
+
return false;
|
|
1598
|
+
}
|
|
1146
1599
|
const submissionContext=Object.freeze({
|
|
1147
1600
|
source:'user',
|
|
1148
1601
|
preserveDraft:false,
|
|
@@ -1154,7 +1607,6 @@
|
|
|
1154
1607
|
return false;
|
|
1155
1608
|
}
|
|
1156
1609
|
const messageTime = getMilTime();
|
|
1157
|
-
console.info("Dispatching message:", text);
|
|
1158
1610
|
if(!submissionContext.preserveDraft){
|
|
1159
1611
|
textArea.value='';
|
|
1160
1612
|
}
|
|
@@ -1189,8 +1641,15 @@
|
|
|
1189
1641
|
}
|
|
1190
1642
|
);
|
|
1191
1643
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1644
|
+
if(host.aiAvailability.tts&&speech.muted===false){
|
|
1645
|
+
const aiRuntime=globalThis.ai;
|
|
1646
|
+
aiRuntime?.stopAudio?.();
|
|
1647
|
+
if(typeof aiRuntime?.resumeAudio==='function'){
|
|
1648
|
+
void Promise.resolve(aiRuntime.resumeAudio()).catch(
|
|
1649
|
+
reportTTSError
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1194
1653
|
|
|
1195
1654
|
host.dispatchEvent(chatEvent);
|
|
1196
1655
|
|
|
@@ -1219,6 +1678,36 @@
|
|
|
1219
1678
|
chatOutput.scrollTop = chatOutput.scrollHeight;
|
|
1220
1679
|
}
|
|
1221
1680
|
|
|
1681
|
+
function reportTTSError(error){
|
|
1682
|
+
if(typeof speech.reportTTSError==='function'){
|
|
1683
|
+
speech.reportTTSError(error);
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
const message=typeof error?.message==='string'&&error.message.trim()
|
|
1688
|
+
?error.message.trim().slice(0,240)
|
|
1689
|
+
:'Speech synthesis failed.';
|
|
1690
|
+
host.dispatchEvent(
|
|
1691
|
+
new CustomEvent(
|
|
1692
|
+
'chat-speech-synthesis-error',
|
|
1693
|
+
{
|
|
1694
|
+
bubbles:true,
|
|
1695
|
+
composed:true,
|
|
1696
|
+
detail:{error,message}
|
|
1697
|
+
}
|
|
1698
|
+
)
|
|
1699
|
+
);
|
|
1700
|
+
console.error('Speech synthesis failed.');
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function destroy(){
|
|
1704
|
+
aiRuntimeStateAbortController.abort();
|
|
1705
|
+
aiActivationController.destroy();
|
|
1706
|
+
window.removeEventListener('pagehide',destroy);
|
|
1707
|
+
speech.destroy?.();
|
|
1708
|
+
host.ready=false;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1222
1711
|
// Initialize and adjust on input
|
|
1223
1712
|
resizeTextArea();
|
|
1224
1713
|
|