arcane-os 0.80.0 → 0.82.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 +44 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +97 -19
- package/browser-runtime/ai/dbopfs-model-parts.mjs +12 -5
- package/browser-runtime/ai/dbopfs-model-resources.mjs +2 -5
- package/browser-runtime/core/app-control.mjs +439 -0
- package/docs/reference/ai/browser-speech.md +11 -0
- package/docs/reference/ai/browser-wasm.md +21 -10
- package/docs/reference/cli.md +9 -1
- package/docs/reference/core-native-packaging.md +14 -0
- package/docs/reference/inventory/package-api.json +23 -9
- package/docs/reference/inventory/runtime-components.json +2 -1
- package/docs/reference/native-app-control.md +147 -0
- package/docs/reference/native-decisions.md +74 -16
- package/docs/reference/runtime-components.md +20 -0
- package/docs/reference/sdk-api.md +49 -6
- package/package.json +2 -1
- package/runtime/arcane/components/chat.html +140 -18
- package/runtime/arcane/modules/AI.js +10 -5
- package/src/cli/app-control.mjs +106 -0
- package/src/cli/main.mjs +5 -0
- package/src/core/app-control.mjs +23 -0
- package/src/core/hosts/windows/ArcaneAppControl.cs +525 -0
- package/src/core/hosts/windows/ArcaneCoreProcess.cs +2 -63
- package/src/core/hosts/windows/ArcaneFrameTransport.cs +80 -0
- package/src/core/hosts/windows/ArcaneHost.cs +45 -7
- package/src/core/hosts/windows/ArcaneLauncher.cs +5 -0
- package/src/core/hosts/windows/ArcaneWindowControl.cs +248 -0
- package/src/core/services/decisions.mjs +16 -2
- package/src/core/shared-host.mjs +7 -91
- package/src/core/socket-connection.mjs +109 -0
- package/src/local-ai/decision-tokenizer-worker.mjs +1 -1
- package/src/local-ai/decision-tokenizer.mjs +2 -2
- package/src/local-ai/decisions.mjs +33 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.82.0
|
|
4
|
+
|
|
5
|
+
- Add `connectAppControl` from `arcane-os/core/app-control` and the
|
|
6
|
+
`arcane app-control` CLI for an explicitly selected running Windows WebView2
|
|
7
|
+
application. Inspect complete document content and live controls, capture the
|
|
8
|
+
rendered viewport, and perform targeted click, fill, select and scroll actions
|
|
9
|
+
without activating the desktop window or injecting global input. Preserve the
|
|
10
|
+
application's existing Core, profile and lifecycle; closing the client closes
|
|
11
|
+
only its connection. The matching Windows host archive includes the generated
|
|
12
|
+
DOM operation source. Other native host adapters remain unavailable.
|
|
13
|
+
- Render stored `arcane-media:` images in shared Chat through MarkdownMedia,
|
|
14
|
+
including restored history, live chunks and final responses. Preserve complete
|
|
15
|
+
original Markdown and immediate text rendering. Release obsolete display URLs
|
|
16
|
+
on replacement, removal and destruction; retain requested print snapshots
|
|
17
|
+
through `afterprint` and await current images for explicit printing.
|
|
18
|
+
- Keep dependencies, saved-data formats, model and speech routing unchanged.
|
|
19
|
+
Package verification and host compilation do not establish actual built-app,
|
|
20
|
+
Core/profile, covered-window capture or model behavior.
|
|
21
|
+
|
|
22
|
+
## 0.81.0
|
|
23
|
+
|
|
24
|
+
- Add per-activation native decision `family`, `model`, `revision`, and `dtype`
|
|
25
|
+
selection through the direct runtime and Core service. Support Laya FP16 and
|
|
26
|
+
Julia 1 FP32 with their own model companions and tokenizer family, preserving
|
|
27
|
+
Laya FP32 defaults, complete input rows and actual output tensors. Keep
|
|
28
|
+
requested replacement metadata separate from the active model and retain
|
|
29
|
+
prepared files through actual worker exit.
|
|
30
|
+
- Report cached model loading as real stored-part progress and preserve known
|
|
31
|
+
GGUF shard totals. Forward complete progress details and cancellation through
|
|
32
|
+
cache reads without presenting cached data as a download or inventing totals
|
|
33
|
+
for an unfinished network stream.
|
|
34
|
+
- Add object-form `AI.streamRequest({speech:false})` for text-only operations.
|
|
35
|
+
Completion, failure and cancellation leave unrelated prepared, native and
|
|
36
|
+
buffered speech alone. Existing defaults, positional methods and explicit
|
|
37
|
+
speech APIs retain their behavior.
|
|
38
|
+
- Close and drain the Windows host after WebView2 `BrowserProcessExited`,
|
|
39
|
+
preserving its full diagnostic instead of leaving a blank window awaiting
|
|
40
|
+
manual closure. Existing renderer/Core-only handling and saved profiles stay
|
|
41
|
+
unchanged. This source correction does not establish the cause of the browser
|
|
42
|
+
exit; executable consumers require the matching rebuilt host archive.
|
|
43
|
+
- Keep dependencies, app-control, native GGUF activation and native Kokoro
|
|
44
|
+
routing unchanged. Source/package verification is separate from actual model,
|
|
45
|
+
audio or application execution.
|
|
46
|
+
|
|
3
47
|
## 0.80.0
|
|
4
48
|
|
|
5
49
|
- Add `assetProjectionId` and `resourcePaths` to native decision factories and
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
20
20
|
share the theme, packaging, event, cancellation,
|
|
21
21
|
and browser run contracts while retaining their selected application layout.
|
|
22
22
|
|
|
23
|
-
This checkout defines the `0.
|
|
23
|
+
This checkout defines the `0.82.0` SDK contract. Development applications declare
|
|
24
24
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
25
25
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
26
26
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
|
@@ -667,19 +667,21 @@ export function createDbopfsModelStore({
|
|
|
667
667
|
}
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
-
function
|
|
670
|
+
function createModelProgressReporter(members, onProgress, retainFailure) {
|
|
671
671
|
const shards = members.map(function initialMemberProgress(member) {
|
|
672
672
|
return {name: member.name, completed: 0, total: null, unit: 'shards'};
|
|
673
673
|
});
|
|
674
674
|
let completed = 0;
|
|
675
|
+
let phase = "cache-check";
|
|
675
676
|
let activeTransfers = 0;
|
|
676
677
|
let transferMode = members.length === 1 ? "probing" : "files";
|
|
677
678
|
let timer = null;
|
|
678
679
|
let lastPublishedAt = 0;
|
|
680
|
+
let progressFailure = null;
|
|
679
681
|
|
|
680
682
|
function progressRecord() {
|
|
681
683
|
return completeValue({
|
|
682
|
-
phase
|
|
684
|
+
phase,
|
|
683
685
|
completed,
|
|
684
686
|
total: members.length,
|
|
685
687
|
unit: "files",
|
|
@@ -713,18 +715,28 @@ export function createDbopfsModelStore({
|
|
|
713
715
|
return true;
|
|
714
716
|
} catch (error) {
|
|
715
717
|
stopTimer();
|
|
718
|
+
progressFailure ??= error;
|
|
716
719
|
retainFailure(error);
|
|
717
720
|
return false;
|
|
718
721
|
}
|
|
719
722
|
}
|
|
720
723
|
|
|
724
|
+
function assertProgress() {
|
|
725
|
+
if (progressFailure !== null) throw progressFailure;
|
|
726
|
+
}
|
|
727
|
+
|
|
721
728
|
return completeValue({
|
|
722
729
|
reportShards(memberIndex, value) {
|
|
730
|
+
assertProgress();
|
|
723
731
|
shards[memberIndex] = {...value, name: members[memberIndex].name};
|
|
732
|
+
if (value.phase === "download" || activeTransfers > 0) phase = "download";
|
|
733
|
+
else if (value.phase === "load") phase = "load";
|
|
724
734
|
publishSafely({force: true});
|
|
735
|
+
if (value.phase === "load") assertProgress();
|
|
725
736
|
},
|
|
726
737
|
beginTransfer() {
|
|
727
738
|
activeTransfers += 1;
|
|
739
|
+
phase = "download";
|
|
728
740
|
publishSafely({ force: true });
|
|
729
741
|
},
|
|
730
742
|
completeMember() {
|
|
@@ -745,14 +757,17 @@ export function createDbopfsModelStore({
|
|
|
745
757
|
publishSafely({ force: true });
|
|
746
758
|
},
|
|
747
759
|
restoreMember(memberIndex) {
|
|
748
|
-
|
|
760
|
+
assertProgress();
|
|
761
|
+
shards[memberIndex] = {...shards[memberIndex], phase: 'load', cached: true, state: 'cached'};
|
|
762
|
+
if (activeTransfers === 0) phase = "load";
|
|
749
763
|
completed += 1;
|
|
750
764
|
publishSafely({ force: true });
|
|
765
|
+
assertProgress();
|
|
751
766
|
},
|
|
752
767
|
start() {
|
|
753
768
|
if (!publishSafely({ force: true })) return;
|
|
754
769
|
if (onProgress !== null && timer === null) {
|
|
755
|
-
timer = setInterval(function
|
|
770
|
+
timer = setInterval(function publishModelProgressTick() {
|
|
756
771
|
publishSafely();
|
|
757
772
|
}, MODEL_DOWNLOAD_PROGRESS_INTERVAL_MS);
|
|
758
773
|
}
|
|
@@ -795,6 +810,9 @@ export function createDbopfsModelStore({
|
|
|
795
810
|
const partFiles = new Array(ranges.length);
|
|
796
811
|
const pendingRangeIndexes = [];
|
|
797
812
|
let completedParts = 0;
|
|
813
|
+
progress?.reportShards(memberIndex, {
|
|
814
|
+
phase: 'load', completed: 0, total: ranges.length, unit: 'shards', cached: true,
|
|
815
|
+
});
|
|
798
816
|
for (let rangeIndex = 0; rangeIndex < ranges.length; rangeIndex += 1) {
|
|
799
817
|
throwIfAborted(signal, "install");
|
|
800
818
|
const range = ranges[rangeIndex];
|
|
@@ -804,15 +822,20 @@ export function createDbopfsModelStore({
|
|
|
804
822
|
if (partFile?.size === expected) {
|
|
805
823
|
partFiles[rangeIndex] = partFile;
|
|
806
824
|
completedParts += 1;
|
|
825
|
+
progress?.reportShards(memberIndex, {
|
|
826
|
+
phase: 'load', completed: completedParts, total: ranges.length, unit: 'shards', cached: true,
|
|
827
|
+
});
|
|
807
828
|
} else {
|
|
808
829
|
if (partFile) await removeEntry(partName);
|
|
809
830
|
pendingRangeIndexes.push(rangeIndex);
|
|
810
831
|
}
|
|
811
832
|
}
|
|
812
833
|
progress?.reportShards(memberIndex, {
|
|
834
|
+
phase: 'load',
|
|
813
835
|
completed: completedParts,
|
|
814
836
|
total: ranges.length,
|
|
815
|
-
unit: 'shards'
|
|
837
|
+
unit: 'shards',
|
|
838
|
+
cached: true,
|
|
816
839
|
});
|
|
817
840
|
const linked = linkAbortSignal(signal);
|
|
818
841
|
const downloadSignal = linked.controller.signal;
|
|
@@ -868,6 +891,7 @@ export function createDbopfsModelStore({
|
|
|
868
891
|
throwIfAborted(downloadSignal, "install");
|
|
869
892
|
completedParts += 1;
|
|
870
893
|
progress?.reportShards(memberIndex, {
|
|
894
|
+
phase: 'download',
|
|
871
895
|
completed: completedParts,
|
|
872
896
|
total: ranges.length,
|
|
873
897
|
unit: 'shards'
|
|
@@ -1120,13 +1144,27 @@ export function createDbopfsModelStore({
|
|
|
1120
1144
|
});
|
|
1121
1145
|
}
|
|
1122
1146
|
|
|
1123
|
-
async function availableModelFiles(names, signal) {
|
|
1147
|
+
async function availableModelFiles(names, signal, progress) {
|
|
1124
1148
|
const modelFiles = [];
|
|
1125
|
-
for (
|
|
1149
|
+
for (let memberIndex = 0; memberIndex < names.models.length; memberIndex += 1) {
|
|
1150
|
+
const entry = names.models[memberIndex];
|
|
1126
1151
|
throwIfAborted(signal, "install");
|
|
1127
1152
|
// Existing whole-file caches remain readable; new downloads use parts.
|
|
1128
|
-
|
|
1153
|
+
let modelFile = await parts.read(entry.name, {
|
|
1154
|
+
signal,
|
|
1155
|
+
onProgress: function reportStoredShards(value) {
|
|
1156
|
+
progress?.reportShards(memberIndex, value);
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
if (!modelFile) {
|
|
1160
|
+
modelFile = await file(entry.name);
|
|
1161
|
+
if (modelFile) progress?.reportShards(memberIndex, {
|
|
1162
|
+
phase: 'load', completed: 1, total: 1, unit: 'shards', cached: true,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1129
1165
|
modelFiles.push(modelFile);
|
|
1166
|
+
if (modelFile) progress?.restoreMember(memberIndex);
|
|
1167
|
+
throwIfAborted(signal, "install");
|
|
1130
1168
|
}
|
|
1131
1169
|
return modelFiles;
|
|
1132
1170
|
}
|
|
@@ -1136,9 +1174,16 @@ export function createDbopfsModelStore({
|
|
|
1136
1174
|
ranges,
|
|
1137
1175
|
signal,
|
|
1138
1176
|
availableNames = null,
|
|
1177
|
+
onProgress = null,
|
|
1139
1178
|
) {
|
|
1140
1179
|
const partFiles = [];
|
|
1141
1180
|
let lastModified = 0;
|
|
1181
|
+
function reportStoredRange() {
|
|
1182
|
+
throwIfAborted(signal, "install");
|
|
1183
|
+
onProgress?.({phase: 'load', completed: partFiles.length,
|
|
1184
|
+
total: ranges.length, unit: 'shards', cached: true});
|
|
1185
|
+
}
|
|
1186
|
+
reportStoredRange();
|
|
1142
1187
|
for (const range of ranges) {
|
|
1143
1188
|
throwIfAborted(signal, "install");
|
|
1144
1189
|
const partName = rangePartName(modelName, range);
|
|
@@ -1151,10 +1196,12 @@ export function createDbopfsModelStore({
|
|
|
1151
1196
|
return null;
|
|
1152
1197
|
}
|
|
1153
1198
|
partFiles.push(partFile);
|
|
1199
|
+
reportStoredRange();
|
|
1154
1200
|
if (is.finite(partFile.lastModified)) {
|
|
1155
1201
|
lastModified = Math.max(lastModified, partFile.lastModified);
|
|
1156
1202
|
}
|
|
1157
1203
|
}
|
|
1204
|
+
throwIfAborted(signal, "install");
|
|
1158
1205
|
return completeValue({
|
|
1159
1206
|
file: new Blob(partFiles, { type: "application/octet-stream" }),
|
|
1160
1207
|
lastModified,
|
|
@@ -1168,6 +1215,7 @@ export function createDbopfsModelStore({
|
|
|
1168
1215
|
total,
|
|
1169
1216
|
signal,
|
|
1170
1217
|
availableNames = null,
|
|
1218
|
+
onProgress = null,
|
|
1171
1219
|
) {
|
|
1172
1220
|
const ranges = rangePlanForInstall(total, signal);
|
|
1173
1221
|
return storedRangeCandidateForPlan(
|
|
@@ -1175,10 +1223,11 @@ export function createDbopfsModelStore({
|
|
|
1175
1223
|
ranges,
|
|
1176
1224
|
signal,
|
|
1177
1225
|
availableNames,
|
|
1226
|
+
onProgress,
|
|
1178
1227
|
);
|
|
1179
1228
|
}
|
|
1180
1229
|
|
|
1181
|
-
async function storedRangeMember(member, modelName, signal) {
|
|
1230
|
+
async function storedRangeMember(member, modelName, signal, onProgress) {
|
|
1182
1231
|
const names = await memberRangePartNames(modelName);
|
|
1183
1232
|
const availableNames = names === null ? null : new Set(names);
|
|
1184
1233
|
const declaredTotal = is.safeInteger(member.bytes) && member.bytes > 0
|
|
@@ -1190,6 +1239,7 @@ export function createDbopfsModelStore({
|
|
|
1190
1239
|
declaredTotal,
|
|
1191
1240
|
signal,
|
|
1192
1241
|
availableNames,
|
|
1242
|
+
onProgress,
|
|
1193
1243
|
);
|
|
1194
1244
|
if (declared) {
|
|
1195
1245
|
await removeStaleRangePartsAfterCompletion(
|
|
@@ -1214,6 +1264,7 @@ export function createDbopfsModelStore({
|
|
|
1214
1264
|
total,
|
|
1215
1265
|
signal,
|
|
1216
1266
|
availableNames,
|
|
1267
|
+
onProgress,
|
|
1217
1268
|
);
|
|
1218
1269
|
if (
|
|
1219
1270
|
candidate
|
|
@@ -1224,16 +1275,20 @@ export function createDbopfsModelStore({
|
|
|
1224
1275
|
) selected = candidate;
|
|
1225
1276
|
}
|
|
1226
1277
|
if (selected === null) return null;
|
|
1278
|
+
throwIfAborted(signal, "install");
|
|
1227
1279
|
await removeStaleRangePartsAfterCompletion(
|
|
1228
1280
|
modelName,
|
|
1229
1281
|
selected.ranges,
|
|
1230
1282
|
);
|
|
1283
|
+
onProgress?.({phase: 'load', completed: selected.ranges.length,
|
|
1284
|
+
total: selected.ranges.length, unit: 'shards', cached: true});
|
|
1285
|
+
throwIfAborted(signal, "install");
|
|
1231
1286
|
return selected.file;
|
|
1232
1287
|
}
|
|
1233
1288
|
|
|
1234
|
-
async function availableResumableModelFiles(source, names, signal) {
|
|
1289
|
+
async function availableResumableModelFiles(source, names, signal, progress) {
|
|
1235
1290
|
const members = sourceMetadata(source).files;
|
|
1236
|
-
const modelFiles = await availableModelFiles(names, signal);
|
|
1291
|
+
const modelFiles = await availableModelFiles(names, signal, progress);
|
|
1237
1292
|
await removeRangePartsBehindWholeFiles(members, names, modelFiles);
|
|
1238
1293
|
for (let memberIndex = 0; memberIndex < modelFiles.length; memberIndex += 1) {
|
|
1239
1294
|
if (modelFiles[memberIndex]) continue;
|
|
@@ -1241,20 +1296,40 @@ export function createDbopfsModelStore({
|
|
|
1241
1296
|
members[memberIndex],
|
|
1242
1297
|
names.models[memberIndex].name,
|
|
1243
1298
|
signal,
|
|
1299
|
+
function reportStoredRangeShards(value) {
|
|
1300
|
+
progress?.reportShards(memberIndex, value);
|
|
1301
|
+
},
|
|
1244
1302
|
);
|
|
1303
|
+
if (modelFiles[memberIndex]) progress?.restoreMember(memberIndex);
|
|
1304
|
+
throwIfAborted(signal, "install");
|
|
1245
1305
|
}
|
|
1246
1306
|
return modelFiles;
|
|
1247
1307
|
}
|
|
1248
1308
|
|
|
1249
1309
|
async function openCached(source, {
|
|
1250
1310
|
signal,
|
|
1311
|
+
onProgress = null,
|
|
1251
1312
|
} = {}) {
|
|
1252
1313
|
const names = storageName(source);
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
files: completeValue(modelFiles),
|
|
1314
|
+
let failure = null;
|
|
1315
|
+
const progress = createModelProgressReporter(sourceMetadata(source).files, onProgress, function retainProgressFailure(error) {
|
|
1316
|
+
failure ??= error;
|
|
1257
1317
|
});
|
|
1318
|
+
try {
|
|
1319
|
+
progress.start();
|
|
1320
|
+
if (failure !== null) throw failure;
|
|
1321
|
+
const modelFiles = await availableResumableModelFiles(source, names, signal, progress);
|
|
1322
|
+
if (failure !== null) throw failure;
|
|
1323
|
+
if (!modelFiles.every(Boolean)) return null;
|
|
1324
|
+
progress.finish();
|
|
1325
|
+
if (failure !== null) throw failure;
|
|
1326
|
+
throwIfAborted(signal, "install");
|
|
1327
|
+
return completeValue({files: completeValue(modelFiles)});
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
throw failure ?? error;
|
|
1330
|
+
} finally {
|
|
1331
|
+
progress.dispose();
|
|
1332
|
+
}
|
|
1258
1333
|
}
|
|
1259
1334
|
|
|
1260
1335
|
async function install(source, { signal, onProgress = null } = {}) {
|
|
@@ -1263,7 +1338,7 @@ export function createDbopfsModelStore({
|
|
|
1263
1338
|
}
|
|
1264
1339
|
const names = storageName(source);
|
|
1265
1340
|
const members = sourceMetadata(source).files;
|
|
1266
|
-
|
|
1341
|
+
let modelFiles;
|
|
1267
1342
|
const linked = linkAbortSignal(signal);
|
|
1268
1343
|
const downloadSignal = linked.controller.signal;
|
|
1269
1344
|
let nextMemberIndex = 0;
|
|
@@ -1273,7 +1348,7 @@ export function createDbopfsModelStore({
|
|
|
1273
1348
|
if (failure === null) failure = error;
|
|
1274
1349
|
}
|
|
1275
1350
|
|
|
1276
|
-
const progress =
|
|
1351
|
+
const progress = createModelProgressReporter(members, onProgress, retainFailure);
|
|
1277
1352
|
|
|
1278
1353
|
async function installMember(memberIndex) {
|
|
1279
1354
|
let modelFile = null;
|
|
@@ -1338,12 +1413,14 @@ export function createDbopfsModelStore({
|
|
|
1338
1413
|
|
|
1339
1414
|
try {
|
|
1340
1415
|
progress.start();
|
|
1416
|
+
if (failure !== null) throw failure;
|
|
1417
|
+
modelFiles = await availableResumableModelFiles(source, names, downloadSignal, progress);
|
|
1418
|
+
if (failure !== null) throw failure;
|
|
1341
1419
|
let pendingMembers = members.length;
|
|
1342
1420
|
for (let memberIndex = 0; memberIndex < modelFiles.length; memberIndex += 1) {
|
|
1343
1421
|
const modelFile = modelFiles[memberIndex];
|
|
1344
1422
|
if (!modelFile) continue;
|
|
1345
1423
|
pendingMembers -= 1;
|
|
1346
|
-
progress.restoreMember(memberIndex);
|
|
1347
1424
|
}
|
|
1348
1425
|
const workers = [];
|
|
1349
1426
|
const workerCount = Math.min(workerLimit, pendingMembers);
|
|
@@ -1392,10 +1469,11 @@ export function createDbopfsModelStore({
|
|
|
1392
1469
|
unit: "files",
|
|
1393
1470
|
heartbeat: false,
|
|
1394
1471
|
}));
|
|
1395
|
-
const cached = await openCached(source, { signal });
|
|
1472
|
+
const cached = await openCached(source, { signal, onProgress });
|
|
1396
1473
|
if (cached) {
|
|
1397
1474
|
const storage = await storagePolicy({ cached: true });
|
|
1398
1475
|
onCapabilityPolicy?.(storage);
|
|
1476
|
+
throwIfAborted(signal, "install");
|
|
1399
1477
|
return completeValue({ ...cached, cache: "cached", storage });
|
|
1400
1478
|
}
|
|
1401
1479
|
if (offline) {
|
|
@@ -178,13 +178,20 @@ export function createDbopfsModelPartStore({dbopfs, tableName}) {
|
|
|
178
178
|
throw error;
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
async function assemble(record, signal) {
|
|
181
|
+
async function assemble(record, signal, onProgress) {
|
|
182
182
|
const parts = [];
|
|
183
|
+
function reportStoredPart() {
|
|
184
|
+
throwIfModelAborted(signal);
|
|
185
|
+
onProgress?.({phase: 'load', message: 'Reading stored model resource',
|
|
186
|
+
completed: parts.length, total: record.parts.length, unit: 'shards', cached: true});
|
|
187
|
+
}
|
|
188
|
+
reportStoredPart();
|
|
183
189
|
for (const name of record.parts) {
|
|
184
190
|
throwIfModelAborted(signal);
|
|
185
191
|
const part = await file(name);
|
|
186
192
|
if (!part) return null;
|
|
187
193
|
parts.push(part);
|
|
194
|
+
reportStoredPart();
|
|
188
195
|
}
|
|
189
196
|
throwIfModelAborted(signal);
|
|
190
197
|
return new Blob(parts);
|
|
@@ -209,18 +216,18 @@ export function createDbopfsModelPartStore({dbopfs, tableName}) {
|
|
|
209
216
|
return readState(name);
|
|
210
217
|
}, signal);
|
|
211
218
|
}
|
|
212
|
-
function read(name, {signal} = {}) {
|
|
219
|
+
function read(name, {signal, onProgress} = {}) {
|
|
213
220
|
return locked(name, async function readCompleteModelParts() {
|
|
214
221
|
throwIfModelAborted(signal);
|
|
215
222
|
const record = await readState(name);
|
|
216
|
-
return record?.complete ? assemble(record, signal) : null;
|
|
223
|
+
return record?.complete ? assemble(record, signal, onProgress) : null;
|
|
217
224
|
}, signal);
|
|
218
225
|
}
|
|
219
|
-
function readPartial(name, {signal} = {}) {
|
|
226
|
+
function readPartial(name, {signal, onProgress} = {}) {
|
|
220
227
|
return locked(name, async function readClosedModelParts() {
|
|
221
228
|
throwIfModelAborted(signal);
|
|
222
229
|
const record = await readState(name);
|
|
223
|
-
return record ? assemble(record, signal) : null;
|
|
230
|
+
return record ? assemble(record, signal, onProgress) : null;
|
|
224
231
|
}, signal);
|
|
225
232
|
}
|
|
226
233
|
function remove(name, {signal} = {}) {
|
|
@@ -93,11 +93,8 @@ export function createDbopfsResourceStore({dbopfs, tableName, fetchImpl = global
|
|
|
93
93
|
const reusable = request.method === 'GET' && !request.headers.has('range')
|
|
94
94
|
&& !['reload', 'no-cache', 'no-store'].includes(request.cache);
|
|
95
95
|
if (reusable && entry.metadata?.status === 200) {
|
|
96
|
-
const file = await parts.read(entry.name, {signal});
|
|
96
|
+
const file = await parts.read(entry.name, {signal, onProgress: reportResourceProgress});
|
|
97
97
|
if (file) {
|
|
98
|
-
const state = await parts.state(entry.name, {signal});
|
|
99
|
-
onProgress?.({phase: 'load', message: 'Reading stored model resource', completed: state.parts.length,
|
|
100
|
-
total: state.parts.length, unit: 'shards', cached: true, url: request.url});
|
|
101
98
|
return {...entry.metadata, file};
|
|
102
99
|
}
|
|
103
100
|
}
|
|
@@ -111,7 +108,7 @@ export function createDbopfsResourceStore({dbopfs, tableName, fetchImpl = global
|
|
|
111
108
|
const encoding = previousHeaders.get('content-encoding');
|
|
112
109
|
const partial = reusable && entry.metadata?.status === 200
|
|
113
110
|
&& (!encoding || encoding === 'identity')
|
|
114
|
-
? await parts.readPartial(entry.name, {signal}) : null;
|
|
111
|
+
? await parts.readPartial(entry.name, {signal, onProgress: reportResourceProgress}) : null;
|
|
115
112
|
try {
|
|
116
113
|
if (partial?.size) {
|
|
117
114
|
const headers = new Headers(request.headers);
|