arcane-os 0.4.1 → 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 +25 -0
- package/README.md +47 -11
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1140 -79
- package/package.json +1 -1
- package/runtime/arcane/components/chat.html +162 -5
- 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,23 +2083,162 @@
|
|
|
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
|
+
|
|
2177
|
+
function progressElapsedText(){
|
|
2178
|
+
const elapsedMilliseconds=Number(role?.progress?.elapsedMs);
|
|
2179
|
+
if(!Number.isFinite(elapsedMilliseconds)||elapsedMilliseconds<0){
|
|
2180
|
+
return '';
|
|
2181
|
+
}
|
|
2182
|
+
const elapsedSeconds=Math.floor(elapsedMilliseconds/1000);
|
|
2183
|
+
const hours=Math.floor(elapsedSeconds/3600);
|
|
2184
|
+
const minutes=Math.floor(elapsedSeconds%3600/60);
|
|
2185
|
+
const seconds=elapsedSeconds%60;
|
|
2186
|
+
if(hours>0){
|
|
2187
|
+
return `${hours}h ${minutes}m elapsed`;
|
|
2188
|
+
}
|
|
2189
|
+
if(minutes>0){
|
|
2190
|
+
return `${minutes}m ${String(seconds).padStart(2,'0')}s elapsed`;
|
|
2191
|
+
}
|
|
2192
|
+
return `${seconds}s elapsed`;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2075
2195
|
function progressMessage(){
|
|
2076
2196
|
const measured=determinateProgress();
|
|
2077
|
-
const
|
|
2197
|
+
const phaseValue=typeof role?.progress?.phase==='string'&&role.progress.phase
|
|
2078
2198
|
?role.progress.phase
|
|
2079
2199
|
:'loading';
|
|
2200
|
+
const phase=phaseValue.replaceAll('-',' ');
|
|
2080
2201
|
const parts=[
|
|
2081
2202
|
`Loading ${modelDisplayName()} through the Arcane SDK`,
|
|
2082
2203
|
phase
|
|
2083
2204
|
];
|
|
2084
|
-
if(measured){
|
|
2085
|
-
|
|
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`);
|
|
2215
|
+
}else{
|
|
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');
|
|
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);
|
|
2231
|
+
}
|
|
2232
|
+
const elapsed=progressElapsedText();
|
|
2233
|
+
if(elapsed){
|
|
2234
|
+
parts.push(elapsed);
|
|
2235
|
+
if(role?.progress?.heartbeat===true){
|
|
2236
|
+
parts.push('Still working');
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
if(!measured?.byteTelemetry){
|
|
2240
|
+
parts.push('The first activation can take several minutes; keep this tab open');
|
|
2086
2241
|
}
|
|
2087
|
-
parts.push('The first activation can take several minutes; keep this tab open');
|
|
2088
2242
|
return parts.join(' · ');
|
|
2089
2243
|
}
|
|
2090
2244
|
|
|
@@ -2094,16 +2248,19 @@
|
|
|
2094
2248
|
if(!visible){
|
|
2095
2249
|
progress.removeAttribute('value');
|
|
2096
2250
|
progress.removeAttribute('max');
|
|
2251
|
+
progress.removeAttribute('aria-valuetext');
|
|
2097
2252
|
return;
|
|
2098
2253
|
}
|
|
2099
2254
|
const measured=determinateProgress();
|
|
2100
2255
|
if(!measured){
|
|
2101
2256
|
progress.removeAttribute('value');
|
|
2102
2257
|
progress.removeAttribute('max');
|
|
2258
|
+
progress.setAttribute('aria-valuetext',progressMessage());
|
|
2103
2259
|
return;
|
|
2104
2260
|
}
|
|
2105
2261
|
progress.max=measured.total;
|
|
2106
2262
|
progress.value=measured.completed;
|
|
2263
|
+
progress.setAttribute('aria-valuetext',progressMessage());
|
|
2107
2264
|
}
|
|
2108
2265
|
|
|
2109
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({
|