arcane-os 0.4.2 → 0.5.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 +18 -0
- package/README.md +47 -11
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1071 -91
- package/package.json +1 -1
- package/runtime/arcane/components/chat.html +133 -10
- package/runtime/arcane/components/local-ai-status.html +2 -2
- package/runtime/arcane/modules/AI.js +22 -6
- package/runtime/arcane/modules/LocalAIReadiness.js +8 -2
package/package.json
CHANGED
|
@@ -2053,6 +2053,21 @@
|
|
|
2053
2053
|
}
|
|
2054
2054
|
|
|
2055
2055
|
function determinateProgress(){
|
|
2056
|
+
const totalBytes=role?.progress?.totalBytes;
|
|
2057
|
+
const loadedBytes=role?.progress?.loadedBytes;
|
|
2058
|
+
if(
|
|
2059
|
+
Number.isFinite(totalBytes)
|
|
2060
|
+
&&totalBytes>0
|
|
2061
|
+
&&Number.isFinite(loadedBytes)
|
|
2062
|
+
&&loadedBytes>=0
|
|
2063
|
+
){
|
|
2064
|
+
return {
|
|
2065
|
+
completed:Math.min(loadedBytes,totalBytes),
|
|
2066
|
+
total:totalBytes,
|
|
2067
|
+
unit:'bytes',
|
|
2068
|
+
byteTelemetry:true
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2056
2071
|
const total=Number(role?.progress?.total);
|
|
2057
2072
|
const completed=Number(role?.progress?.completed);
|
|
2058
2073
|
const unit=typeof role?.progress?.unit==='string'&&role.progress.unit
|
|
@@ -2068,10 +2083,97 @@
|
|
|
2068
2083
|
return {
|
|
2069
2084
|
completed,
|
|
2070
2085
|
total,
|
|
2071
|
-
unit
|
|
2086
|
+
unit,
|
|
2087
|
+
byteTelemetry:false
|
|
2072
2088
|
};
|
|
2073
2089
|
}
|
|
2074
2090
|
|
|
2091
|
+
function formatBytes(value){
|
|
2092
|
+
if(!Number.isFinite(value)||value<0){
|
|
2093
|
+
return '';
|
|
2094
|
+
}
|
|
2095
|
+
const bytes=value;
|
|
2096
|
+
const units=['B','KB','MB','GB'];
|
|
2097
|
+
let unitIndex=0;
|
|
2098
|
+
let amount=bytes;
|
|
2099
|
+
while(amount>=1000&&unitIndex<units.length-1){
|
|
2100
|
+
amount/=1000;
|
|
2101
|
+
unitIndex+=1;
|
|
2102
|
+
}
|
|
2103
|
+
const decimals=unitIndex===0||amount>=100
|
|
2104
|
+
?0
|
|
2105
|
+
:amount>=10
|
|
2106
|
+
?1
|
|
2107
|
+
:2;
|
|
2108
|
+
return `${Number(amount.toFixed(decimals))} ${units[unitIndex]}`;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
function formatPercentage(loadedBytes,totalBytes){
|
|
2112
|
+
const percentage=Math.min(100,Math.max(0,loadedBytes/totalBytes*100));
|
|
2113
|
+
if(loadedBytes<=0){
|
|
2114
|
+
return '0%';
|
|
2115
|
+
}
|
|
2116
|
+
if(loadedBytes>=totalBytes){
|
|
2117
|
+
return '100%';
|
|
2118
|
+
}
|
|
2119
|
+
return `${Math.min(99.9,percentage).toFixed(1)}%`;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
function approximateDuration(value){
|
|
2123
|
+
if(!Number.isFinite(value)||value<0){
|
|
2124
|
+
return '';
|
|
2125
|
+
}
|
|
2126
|
+
const suppliedSeconds=value;
|
|
2127
|
+
const seconds=Math.ceil(suppliedSeconds);
|
|
2128
|
+
if(seconds<1){
|
|
2129
|
+
return '<1s';
|
|
2130
|
+
}
|
|
2131
|
+
if(seconds<60){
|
|
2132
|
+
return `${seconds}s`;
|
|
2133
|
+
}
|
|
2134
|
+
const minutes=Math.floor(seconds/60);
|
|
2135
|
+
const remainingSeconds=seconds%60;
|
|
2136
|
+
if(minutes<60){
|
|
2137
|
+
return `${minutes}m ${String(remainingSeconds).padStart(2,'0')}s`;
|
|
2138
|
+
}
|
|
2139
|
+
const hours=Math.floor(minutes/60);
|
|
2140
|
+
const remainingMinutes=minutes%60;
|
|
2141
|
+
return `${hours}h ${String(remainingMinutes).padStart(2,'0')}m`;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
function activeTransferText(){
|
|
2145
|
+
const active=role?.progress?.activeTransfers;
|
|
2146
|
+
const limit=role?.progress?.transferLimit;
|
|
2147
|
+
const mode=typeof role?.progress?.transferMode==='string'
|
|
2148
|
+
?role.progress.transferMode.trim().toLowerCase()
|
|
2149
|
+
:'';
|
|
2150
|
+
if(
|
|
2151
|
+
!Number.isSafeInteger(active)
|
|
2152
|
+
||active<0
|
|
2153
|
+
||!Number.isSafeInteger(limit)
|
|
2154
|
+
||limit<1
|
|
2155
|
+
||!mode
|
|
2156
|
+
){
|
|
2157
|
+
return '';
|
|
2158
|
+
}
|
|
2159
|
+
if(mode==='single'){
|
|
2160
|
+
return active>0?'single transfer active':'single transfer idle';
|
|
2161
|
+
}
|
|
2162
|
+
if(mode==='probing'){
|
|
2163
|
+
return 'checking parallel Range support';
|
|
2164
|
+
}
|
|
2165
|
+
const noun=['range','ranges'].includes(mode)
|
|
2166
|
+
?'range transfer worker'
|
|
2167
|
+
:['shard','shards'].includes(mode)
|
|
2168
|
+
?'shard transfer worker'
|
|
2169
|
+
:['file','files'].includes(mode)
|
|
2170
|
+
?'file transfer worker'
|
|
2171
|
+
:['single','single-file','monolith'].includes(mode)
|
|
2172
|
+
?'single-file transfer worker'
|
|
2173
|
+
:`${mode.replaceAll('-',' ')} transfer worker`;
|
|
2174
|
+
return `${active} of ${limit} ${noun}${limit===1?'':'s'} active`;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2075
2177
|
function progressElapsedText(){
|
|
2076
2178
|
const elapsedMilliseconds=Number(role?.progress?.elapsedMs);
|
|
2077
2179
|
if(!Number.isFinite(elapsedMilliseconds)||elapsedMilliseconds<0){
|
|
@@ -2100,16 +2202,32 @@
|
|
|
2100
2202
|
`Loading ${modelDisplayName()} through the Arcane SDK`,
|
|
2101
2203
|
phase
|
|
2102
2204
|
];
|
|
2103
|
-
if(measured){
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
)
|
|
2109
|
-
|
|
2205
|
+
if(measured?.byteTelemetry){
|
|
2206
|
+
const remainingBytes=Math.max(0,measured.total-measured.completed);
|
|
2207
|
+
parts.push(formatPercentage(measured.completed,measured.total));
|
|
2208
|
+
parts.push(
|
|
2209
|
+
`${formatBytes(measured.completed)} of ${formatBytes(measured.total)} downloaded`
|
|
2210
|
+
);
|
|
2211
|
+
parts.push(`${formatBytes(remainingBytes)} remaining`);
|
|
2212
|
+
const bytesPerSecond=role?.progress?.bytesPerSecond;
|
|
2213
|
+
if(Number.isFinite(bytesPerSecond)&&bytesPerSecond>=0){
|
|
2214
|
+
parts.push(`${formatBytes(bytesPerSecond)}/s`);
|
|
2110
2215
|
}else{
|
|
2111
|
-
parts.push(
|
|
2216
|
+
parts.push('Calculating speed');
|
|
2217
|
+
}
|
|
2218
|
+
if(remainingBytes>0){
|
|
2219
|
+
const eta=approximateDuration(role?.progress?.etaSeconds);
|
|
2220
|
+
parts.push(eta?`About ${eta} remaining`:'Estimating time remaining');
|
|
2112
2221
|
}
|
|
2222
|
+
}else if(measured){
|
|
2223
|
+
const suffix=phaseValue==='download'&&measured.unit==='files'
|
|
2224
|
+
?'files complete'
|
|
2225
|
+
:measured.unit;
|
|
2226
|
+
parts.push(`${measured.completed} of ${measured.total} ${suffix}`);
|
|
2227
|
+
}
|
|
2228
|
+
const transfers=activeTransferText();
|
|
2229
|
+
if(transfers){
|
|
2230
|
+
parts.push(transfers);
|
|
2113
2231
|
}
|
|
2114
2232
|
const elapsed=progressElapsedText();
|
|
2115
2233
|
if(elapsed){
|
|
@@ -2118,7 +2236,9 @@
|
|
|
2118
2236
|
parts.push('Still working');
|
|
2119
2237
|
}
|
|
2120
2238
|
}
|
|
2121
|
-
|
|
2239
|
+
if(!measured?.byteTelemetry){
|
|
2240
|
+
parts.push('The first activation can take several minutes; keep this tab open');
|
|
2241
|
+
}
|
|
2122
2242
|
return parts.join(' · ');
|
|
2123
2243
|
}
|
|
2124
2244
|
|
|
@@ -2128,16 +2248,19 @@
|
|
|
2128
2248
|
if(!visible){
|
|
2129
2249
|
progress.removeAttribute('value');
|
|
2130
2250
|
progress.removeAttribute('max');
|
|
2251
|
+
progress.removeAttribute('aria-valuetext');
|
|
2131
2252
|
return;
|
|
2132
2253
|
}
|
|
2133
2254
|
const measured=determinateProgress();
|
|
2134
2255
|
if(!measured){
|
|
2135
2256
|
progress.removeAttribute('value');
|
|
2136
2257
|
progress.removeAttribute('max');
|
|
2258
|
+
progress.setAttribute('aria-valuetext',progressMessage());
|
|
2137
2259
|
return;
|
|
2138
2260
|
}
|
|
2139
2261
|
progress.max=measured.total;
|
|
2140
2262
|
progress.value=measured.completed;
|
|
2263
|
+
progress.setAttribute('aria-valuetext',progressMessage());
|
|
2141
2264
|
}
|
|
2142
2265
|
|
|
2143
2266
|
function render(){
|
|
@@ -278,9 +278,9 @@
|
|
|
278
278
|
summary.setAttribute('role','alert');
|
|
279
279
|
summary.textContent=userManaged
|
|
280
280
|
?guidanceMessage
|
|
281
|
-
||'Review the unavailable user-managed Android service, then Retry or open Profile and switch the
|
|
281
|
+
||'Review the unavailable user-managed Android service, then Retry or open Profile and switch the language-model option to TWiN Cloud.'
|
|
282
282
|
:browser
|
|
283
|
-
?'Start the unavailable local service yourself and Retry, or open Profile, switch
|
|
283
|
+
?'Start the unavailable local service yourself and Retry, or open Profile, switch the language-model option to TWiN Cloud, and enter your TWiN access key.'
|
|
284
284
|
:'Arcane could not recover every selected managed local service. Retry or open Arcane Settings > Local AI.';
|
|
285
285
|
return report;
|
|
286
286
|
}
|
|
@@ -917,7 +917,9 @@ class AI {
|
|
|
917
917
|
// This is the enum section for inference configuration
|
|
918
918
|
#service = {
|
|
919
919
|
baseURL: {
|
|
920
|
-
OPENAI
|
|
920
|
+
// OPENAI remains the legacy route identifier for compatibility;
|
|
921
|
+
// remote LLM chat is provided by TWiN Cloud.
|
|
922
|
+
OPENAI: 'https://inference.do-ai.run/v1'
|
|
921
923
|
},
|
|
922
924
|
sttURL: {
|
|
923
925
|
LOCAL_SPEACH: 'http://127.0.0.1:8011/v1',
|
|
@@ -944,7 +946,7 @@ class AI {
|
|
|
944
946
|
}
|
|
945
947
|
|
|
946
948
|
#models = {
|
|
947
|
-
OPENAI:'gpt-
|
|
949
|
+
OPENAI:'openai-gpt-oss-120b'
|
|
948
950
|
}
|
|
949
951
|
|
|
950
952
|
#sttModels = {
|
|
@@ -1155,11 +1157,14 @@ class AI {
|
|
|
1155
1157
|
|
|
1156
1158
|
// Browser-delivered framework code must not contain provider credentials.
|
|
1157
1159
|
// The selected host, application, or user profile supplies one at runtime.
|
|
1158
|
-
get
|
|
1159
|
-
return this.#license
|
|
1160
|
+
get twinKey(){
|
|
1161
|
+
return this.#license
|
|
1162
|
+
||globalThis.arcane?.config?.twinCloud?.accessKey
|
|
1163
|
+
||globalThis.arcane?.config?.openAI?.apiKey
|
|
1164
|
+
||'';
|
|
1160
1165
|
}
|
|
1161
|
-
|
|
1162
|
-
set
|
|
1166
|
+
|
|
1167
|
+
set twinKey(value){
|
|
1163
1168
|
this.#license=typeof value==='string' ? value.trim():'';
|
|
1164
1169
|
this.#retainLegacyLLMReadiness(
|
|
1165
1170
|
this.#reconcileLegacyLLMReadiness()
|
|
@@ -1170,6 +1175,17 @@ class AI {
|
|
|
1170
1175
|
return this.#license;
|
|
1171
1176
|
}
|
|
1172
1177
|
|
|
1178
|
+
// Retain the established credential property while consumers move their
|
|
1179
|
+
// user-facing profile field to the TWiN key name.
|
|
1180
|
+
get license(){
|
|
1181
|
+
return this.twinKey;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
set license(value){
|
|
1185
|
+
this.twinKey=value;
|
|
1186
|
+
return this.#license;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1173
1189
|
#legacyLLMCapability(providerId){
|
|
1174
1190
|
if(providerId==='OPENAI'){
|
|
1175
1191
|
return this.llmService==='OPENAI'
|
|
@@ -718,12 +718,18 @@ function guidance(mode,slots){
|
|
|
718
718
|
|
|
719
719
|
const services=recoveryServices(slots);
|
|
720
720
|
const profileSettings=affectedSlots.map(slot=>slot+'Provider');
|
|
721
|
+
const twinCloudOption=affectedSlots.includes('llm')
|
|
722
|
+
?' Alternatively, switch the Profile language-model setting to TWiN Cloud and enter a TWiN access key.'
|
|
723
|
+
:'';
|
|
721
724
|
const message=mode==='browser'
|
|
722
725
|
?browserManualInstruction(services)
|
|
723
|
-
+
|
|
726
|
+
+twinCloudOption
|
|
727
|
+
+' Arcane will not switch providers automatically.'
|
|
724
728
|
:mode===USER_MANAGED_LOOPBACK_PROVIDER_MODE
|
|
725
729
|
?userManagedLoopbackInstruction(services)
|
|
726
|
-
+' Arcane does not install, start, repair, pull, or otherwise manage this service or its models.
|
|
730
|
+
+' Arcane does not install, start, repair, pull, or otherwise manage this service or its models.'
|
|
731
|
+
+twinCloudOption
|
|
732
|
+
+' Arcane will not switch providers automatically.'
|
|
727
733
|
:'Arcane could not recover the selected local AI service. Retry or review Local AI in Arcane Settings. Arcane will not switch providers automatically.';
|
|
728
734
|
|
|
729
735
|
return completeResult({
|