arcane-os 0.2.0 → 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 +18 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +5 -5
- package/browser-runtime/ai/browser-speech-providers.mjs +330 -25
- package/docs/architecture.md +2 -2
- package/package.json +1 -1
- package/runtime/ARCANE_RUNTIME_RELEASE.json +11 -11
- package/runtime/arcane/components/chat.html +272 -1
- package/runtime/arcane/modules/AI.js +698 -17
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +93 -8
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +448 -24
- package/schemas/arcane-lock.schema.json +6 -4
- package/src/dev-server.mjs +29 -13
- package/src/doctor.mjs +1 -3
- package/src/import-map.mjs +134 -83
- package/src/packager/core.mjs +311 -39
- package/src/scaffold.mjs +45 -17
- package/src/templates/workspace-template.mjs +23 -4
- package/src/toolchain.mjs +10 -2
- package/src/workspace.mjs +177 -24
|
@@ -100,6 +100,7 @@
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
.chat_area[data-conversation-complete="true"] .conversation_timebox,
|
|
103
|
+
.chat_area[data-conversation-complete="true"] .ai_activation,
|
|
103
104
|
.chat_area[data-conversation-complete="true"] .chat_input_area,
|
|
104
105
|
.chat_area[data-conversation-complete="true"] > #speech,
|
|
105
106
|
.chat_area[data-conversation-complete="true"] > .controls{
|
|
@@ -209,6 +210,63 @@
|
|
|
209
210
|
box-shadow: var(--arcane-shadow-small, 0 .35rem 1rem var(--shadow-color));
|
|
210
211
|
}
|
|
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;
|
|
268
|
+
}
|
|
269
|
+
|
|
212
270
|
.chat_input {
|
|
213
271
|
box-sizing: border-box;
|
|
214
272
|
width: 100%;
|
|
@@ -421,6 +479,20 @@
|
|
|
421
479
|
>Starting the elapsed timer.</span>
|
|
422
480
|
</section>
|
|
423
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>
|
|
424
496
|
<div class="chat_input_area">
|
|
425
497
|
<textarea type='text' class='chat_input' id='chat_input'></textarea>
|
|
426
498
|
<button type="button" id="chat_upload" class="chat_upload" title="Upload files" aria-label="Upload files">
|
|
@@ -624,11 +696,15 @@
|
|
|
624
696
|
const conversationTimeboxPanel=shadowRoot.querySelector('#conversation_timebox');
|
|
625
697
|
const conversationTimeboxElapsed=shadowRoot.querySelector('#conversation_timebox_elapsed');
|
|
626
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');
|
|
627
703
|
const [
|
|
628
704
|
{default:MD},
|
|
629
705
|
{default:FileEntity},
|
|
630
706
|
{ConversationSubmissionBarrier,requireConversationTimeboxDelivery},
|
|
631
|
-
{subscribeAIRuntimeState}
|
|
707
|
+
{requestAIRuntimeIntent,subscribeAIRuntimeState}
|
|
632
708
|
]=await Promise.all([
|
|
633
709
|
import('../modules/MD.js'),
|
|
634
710
|
import('../entities/File.js'),
|
|
@@ -663,6 +739,18 @@
|
|
|
663
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')
|
|
664
740
|
}
|
|
665
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
|
+
|
|
666
754
|
host.streamMessage=streamMessage;
|
|
667
755
|
host.setMessageProgress=setMessageProgress;
|
|
668
756
|
host.setAIAvailability=setAIAvailability;
|
|
@@ -861,7 +949,189 @@
|
|
|
861
949
|
send.setAttribute('aria-label',send.title);
|
|
862
950
|
}
|
|
863
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
|
+
}
|
|
1100
|
+
|
|
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
|
+
|
|
864
1133
|
function synchronizeAIRuntimeState(snapshot){
|
|
1134
|
+
aiActivationController.synchronize(snapshot.roles.llm);
|
|
865
1135
|
applyAIAvailability({
|
|
866
1136
|
llm:snapshot.roles.llm.state==='ready',
|
|
867
1137
|
stt:snapshot.roles.stt.state==='ready',
|
|
@@ -1432,6 +1702,7 @@
|
|
|
1432
1702
|
|
|
1433
1703
|
function destroy(){
|
|
1434
1704
|
aiRuntimeStateAbortController.abort();
|
|
1705
|
+
aiActivationController.destroy();
|
|
1435
1706
|
window.removeEventListener('pagehide',destroy);
|
|
1436
1707
|
speech.destroy?.();
|
|
1437
1708
|
host.ready=false;
|