@semiont/sdk 0.5.1 → 0.5.3
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/README.md +116 -22
- package/dist/index.d.ts +468 -582
- package/dist/index.js +387 -1020
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { SemiontError, annotationId, resourceId, email, googleCredential, refreshToken, EventBus, baseUrl, accessToken,
|
|
1
|
+
import { SemiontError, annotationId, resourceId, email, googleCredential, refreshToken, EventBus, baseUrl, accessToken, searchQuery } from '@semiont/core';
|
|
2
2
|
export { SemiontError, accessToken, annotationId, baseUrl, entityType, refreshToken, resourceId, userId } from '@semiont/core';
|
|
3
|
-
import { Observable, lastValueFrom, firstValueFrom, merge, TimeoutError, throwError,
|
|
3
|
+
import { Observable, lastValueFrom, firstValueFrom, merge, TimeoutError, throwError, map as map$1, BehaviorSubject, Subject, Subscription, of, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
4
4
|
export { firstValueFrom, lastValueFrom } from 'rxjs';
|
|
5
|
-
import { filter, map, take, timeout, catchError, takeUntil, startWith, debounceTime, distinctUntilChanged
|
|
6
|
-
import { HttpTransport, HttpContentTransport,
|
|
7
|
-
export { APIError,
|
|
5
|
+
import { filter, map, take, timeout, catchError, takeUntil, startWith, debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
|
|
6
|
+
import { HttpTransport, HttpContentTransport, APIError } from '@semiont/api-client';
|
|
7
|
+
export { APIError, HttpContentTransport, HttpTransport } from '@semiont/api-client';
|
|
8
8
|
|
|
9
9
|
// src/client.ts
|
|
10
10
|
var StreamObservable = class _StreamObservable extends Observable {
|
|
@@ -28,7 +28,7 @@ var CacheObservable = class _CacheObservable extends Observable {
|
|
|
28
28
|
* Observable per key (its B4 contract), so this preserves that contract
|
|
29
29
|
* through the awaitable wrapping. Without the memo, every public-method
|
|
30
30
|
* call would produce a fresh wrapper and break referential-equality
|
|
31
|
-
* guarantees that
|
|
31
|
+
* guarantees that hook-style reactive consumers depend on.
|
|
32
32
|
*
|
|
33
33
|
* Backed by a `WeakMap`, so wrappers are GC'd when their source is.
|
|
34
34
|
*/
|
|
@@ -42,6 +42,17 @@ var CacheObservable = class _CacheObservable extends Observable {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
var wrapperCache = /* @__PURE__ */ new WeakMap();
|
|
45
|
+
var UploadObservable = class extends Observable {
|
|
46
|
+
then(onfulfilled, onrejected) {
|
|
47
|
+
return lastValueFrom(this).then((v) => {
|
|
48
|
+
if (v.phase !== "finished") {
|
|
49
|
+
throw new Error(`UploadObservable resolved on a non-finished event: ${v.phase}`);
|
|
50
|
+
}
|
|
51
|
+
const result = { resourceId: v.resourceId };
|
|
52
|
+
return onfulfilled ? onfulfilled(result) : result;
|
|
53
|
+
}, onrejected);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
45
56
|
var BusRequestError = class extends SemiontError {
|
|
46
57
|
constructor(message, code, details) {
|
|
47
58
|
super(message, code, details);
|
|
@@ -117,7 +128,7 @@ function createCache(fetchFn) {
|
|
|
117
128
|
if (!obs) {
|
|
118
129
|
obs = store$.pipe(
|
|
119
130
|
map$1((m) => m.get(key)),
|
|
120
|
-
distinctUntilChanged()
|
|
131
|
+
distinctUntilChanged$1()
|
|
121
132
|
);
|
|
122
133
|
obsCache.set(key, obs);
|
|
123
134
|
}
|
|
@@ -160,6 +171,7 @@ function createCache(fetchFn) {
|
|
|
160
171
|
|
|
161
172
|
// src/namespaces/browse.ts
|
|
162
173
|
var ENTITY_TYPES_KEY = "_";
|
|
174
|
+
var TAG_SCHEMAS_KEY = "_";
|
|
163
175
|
var BrowseNamespace = class {
|
|
164
176
|
constructor(transport, bus, content) {
|
|
165
177
|
this.transport = transport;
|
|
@@ -220,6 +232,16 @@ var BrowseNamespace = class {
|
|
|
220
232
|
);
|
|
221
233
|
return result.entityTypes;
|
|
222
234
|
});
|
|
235
|
+
this.tagSchemasCache = createCache(async () => {
|
|
236
|
+
const result = await busRequest(
|
|
237
|
+
this.transport,
|
|
238
|
+
"browse:tag-schemas-requested",
|
|
239
|
+
{},
|
|
240
|
+
"browse:tag-schemas-result",
|
|
241
|
+
"browse:tag-schemas-failed"
|
|
242
|
+
);
|
|
243
|
+
return result.tagSchemas;
|
|
244
|
+
});
|
|
223
245
|
this.referencedByCache = createCache(async (resourceId2) => {
|
|
224
246
|
const result = await busRequest(
|
|
225
247
|
this.transport,
|
|
@@ -263,6 +285,7 @@ var BrowseNamespace = class {
|
|
|
263
285
|
annotationDetailCache;
|
|
264
286
|
annotationResources = /* @__PURE__ */ new Map();
|
|
265
287
|
entityTypesCache;
|
|
288
|
+
tagSchemasCache;
|
|
266
289
|
referencedByCache;
|
|
267
290
|
resourceEventsCache;
|
|
268
291
|
/** Filter-blob memory so `invalidateResourceLists` can replay per-key. */
|
|
@@ -305,6 +328,9 @@ var BrowseNamespace = class {
|
|
|
305
328
|
entityTypes() {
|
|
306
329
|
return CacheObservable.from(this.entityTypesCache.observe(ENTITY_TYPES_KEY));
|
|
307
330
|
}
|
|
331
|
+
tagSchemas() {
|
|
332
|
+
return CacheObservable.from(this.tagSchemasCache.observe(TAG_SCHEMAS_KEY));
|
|
333
|
+
}
|
|
308
334
|
referencedBy(resourceId2) {
|
|
309
335
|
return CacheObservable.from(this.referencedByCache.observe(resourceId2));
|
|
310
336
|
}
|
|
@@ -389,6 +415,9 @@ var BrowseNamespace = class {
|
|
|
389
415
|
invalidateEntityTypes() {
|
|
390
416
|
this.entityTypesCache.invalidate(ENTITY_TYPES_KEY);
|
|
391
417
|
}
|
|
418
|
+
invalidateTagSchemas() {
|
|
419
|
+
this.tagSchemasCache.invalidate(TAG_SCHEMAS_KEY);
|
|
420
|
+
}
|
|
392
421
|
invalidateReferencedBy(resourceId2) {
|
|
393
422
|
this.referencedByCache.invalidate(resourceId2);
|
|
394
423
|
}
|
|
@@ -465,6 +494,7 @@ var BrowseNamespace = class {
|
|
|
465
494
|
for (const rId of this.referencedByCache.keys()) this.invalidateReferencedBy(rId);
|
|
466
495
|
}
|
|
467
496
|
this.invalidateEntityTypes();
|
|
497
|
+
this.invalidateTagSchemas();
|
|
468
498
|
});
|
|
469
499
|
this.on("mark:delete-ok", (event) => {
|
|
470
500
|
this.removeAnnotationDetail(annotationId(event.annotationId));
|
|
@@ -499,7 +529,8 @@ var BrowseNamespace = class {
|
|
|
499
529
|
this.on("yield:update-ok", this.onYieldResourceMutated);
|
|
500
530
|
this.on("mark:archived", this.onArchiveToggled);
|
|
501
531
|
this.on("mark:unarchived", this.onArchiveToggled);
|
|
502
|
-
this.on("
|
|
532
|
+
this.on("frame:entity-type-added", () => this.invalidateEntityTypes());
|
|
533
|
+
this.on("frame:tag-schema-added", () => this.invalidateTagSchemas());
|
|
503
534
|
}
|
|
504
535
|
};
|
|
505
536
|
var MarkNamespace = class {
|
|
@@ -507,7 +538,8 @@ var MarkNamespace = class {
|
|
|
507
538
|
this.transport = transport;
|
|
508
539
|
this.bus = bus;
|
|
509
540
|
}
|
|
510
|
-
async annotation(
|
|
541
|
+
async annotation(input) {
|
|
542
|
+
const resourceId2 = resourceId(input.target.source);
|
|
511
543
|
const result = await busRequest(
|
|
512
544
|
this.transport,
|
|
513
545
|
"mark:create-request",
|
|
@@ -520,14 +552,6 @@ var MarkNamespace = class {
|
|
|
520
552
|
async delete(resourceId2, annotationId2) {
|
|
521
553
|
await this.transport.emit("mark:delete", { annotationId: annotationId2, resourceId: resourceId2 });
|
|
522
554
|
}
|
|
523
|
-
async entityType(type) {
|
|
524
|
-
await this.transport.emit("mark:add-entity-type", { tag: type });
|
|
525
|
-
}
|
|
526
|
-
async entityTypes(types) {
|
|
527
|
-
for (const tag of types) {
|
|
528
|
-
await this.transport.emit("mark:add-entity-type", { tag });
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
555
|
async archive(resourceId2) {
|
|
532
556
|
await this.transport.emit("mark:archive", { resourceId: resourceId2 });
|
|
533
557
|
}
|
|
@@ -539,6 +563,7 @@ var MarkNamespace = class {
|
|
|
539
563
|
let done = false;
|
|
540
564
|
let pollTimer = null;
|
|
541
565
|
let pollInterval = null;
|
|
566
|
+
let unsubscribeResource = this.transport.subscribeToResource(resourceId2);
|
|
542
567
|
const cleanup = () => {
|
|
543
568
|
done = true;
|
|
544
569
|
if (pollTimer) {
|
|
@@ -549,6 +574,10 @@ var MarkNamespace = class {
|
|
|
549
574
|
clearInterval(pollInterval);
|
|
550
575
|
pollInterval = null;
|
|
551
576
|
}
|
|
577
|
+
if (unsubscribeResource) {
|
|
578
|
+
unsubscribeResource();
|
|
579
|
+
unsubscribeResource = null;
|
|
580
|
+
}
|
|
552
581
|
};
|
|
553
582
|
const resetPollTimer = (jobId) => {
|
|
554
583
|
if (pollTimer) clearTimeout(pollTimer);
|
|
@@ -682,6 +711,7 @@ var MarkNamespace = class {
|
|
|
682
711
|
if (options.density !== void 0) params.density = options.density;
|
|
683
712
|
if (options.tone !== void 0) params.tone = options.tone;
|
|
684
713
|
if (options.language !== void 0) params.language = options.language;
|
|
714
|
+
if (options.sourceLanguage !== void 0) params.sourceLanguage = options.sourceLanguage;
|
|
685
715
|
if (options.schemaId !== void 0) params.schemaId = options.schemaId;
|
|
686
716
|
if (options.categories !== void 0) params.categories = options.categories;
|
|
687
717
|
return busRequest(
|
|
@@ -717,7 +747,7 @@ var GatherNamespace = class {
|
|
|
717
747
|
this.transport = transport;
|
|
718
748
|
this.bus = bus;
|
|
719
749
|
}
|
|
720
|
-
annotation(
|
|
750
|
+
annotation(resourceId2, annotationId2, options) {
|
|
721
751
|
return new StreamObservable((subscriber) => {
|
|
722
752
|
const correlationId = crypto.randomUUID();
|
|
723
753
|
const complete$ = this.bus.get("gather:complete").pipe(
|
|
@@ -809,28 +839,60 @@ var YieldNamespace = class {
|
|
|
809
839
|
this.bus = bus;
|
|
810
840
|
this.content = content;
|
|
811
841
|
}
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
842
|
+
resource(data) {
|
|
843
|
+
const totalBytes = typeof Buffer !== "undefined" && data.file instanceof Buffer ? data.file.length : data.file.size;
|
|
844
|
+
return new UploadObservable((subscriber) => {
|
|
845
|
+
subscriber.next({ phase: "started", totalBytes });
|
|
846
|
+
let cancelled = false;
|
|
847
|
+
const abortController = new AbortController();
|
|
848
|
+
this.content.putBinary(
|
|
849
|
+
{
|
|
850
|
+
name: data.name,
|
|
851
|
+
file: data.file,
|
|
852
|
+
format: data.format,
|
|
853
|
+
storageUri: data.storageUri,
|
|
854
|
+
...data.entityTypes ? { entityTypes: data.entityTypes } : {},
|
|
855
|
+
...data.language ? { language: data.language } : {},
|
|
856
|
+
...data.creationMethod ? { creationMethod: data.creationMethod } : {},
|
|
857
|
+
...data.sourceAnnotationId ? { sourceAnnotationId: data.sourceAnnotationId } : {},
|
|
858
|
+
...data.sourceResourceId ? { sourceResourceId: data.sourceResourceId } : {},
|
|
859
|
+
...data.generationPrompt ? { generationPrompt: data.generationPrompt } : {},
|
|
860
|
+
...data.generator ? { generator: data.generator } : {},
|
|
861
|
+
...data.isDraft !== void 0 ? { isDraft: data.isDraft } : {}
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
// Byte-progress hook. Honored by `HttpContentTransport`'s XHR
|
|
865
|
+
// path; ignored by ky-path uploads (no `onProgress` consumer)
|
|
866
|
+
// and by `LocalContentTransport` (no wire to observe).
|
|
867
|
+
onProgress: ({ bytesUploaded, totalBytes: txTotal }) => {
|
|
868
|
+
if (cancelled) return;
|
|
869
|
+
const total = txTotal > 0 ? txTotal : totalBytes;
|
|
870
|
+
subscriber.next({ phase: "progress", bytesUploaded, totalBytes: total });
|
|
871
|
+
},
|
|
872
|
+
signal: abortController.signal
|
|
873
|
+
}
|
|
874
|
+
).then((result) => {
|
|
875
|
+
if (cancelled) return;
|
|
876
|
+
subscriber.next({
|
|
877
|
+
phase: "finished",
|
|
878
|
+
resourceId: resourceId(result.resourceId)
|
|
879
|
+
});
|
|
880
|
+
subscriber.complete();
|
|
881
|
+
}).catch((err) => {
|
|
882
|
+
if (!cancelled) subscriber.error(err);
|
|
883
|
+
});
|
|
884
|
+
return () => {
|
|
885
|
+
cancelled = true;
|
|
886
|
+
abortController.abort();
|
|
887
|
+
};
|
|
826
888
|
});
|
|
827
|
-
return { resourceId: result.resourceId };
|
|
828
889
|
}
|
|
829
890
|
fromAnnotation(resourceId2, annotationId2, options) {
|
|
830
891
|
return new StreamObservable((subscriber) => {
|
|
831
892
|
let done = false;
|
|
832
893
|
let pollTimer = null;
|
|
833
894
|
let pollInterval = null;
|
|
895
|
+
let unsubscribeResource = this.transport.subscribeToResource(resourceId2);
|
|
834
896
|
const cleanup = () => {
|
|
835
897
|
done = true;
|
|
836
898
|
if (pollTimer) {
|
|
@@ -841,6 +903,10 @@ var YieldNamespace = class {
|
|
|
841
903
|
clearInterval(pollInterval);
|
|
842
904
|
pollInterval = null;
|
|
843
905
|
}
|
|
906
|
+
if (unsubscribeResource) {
|
|
907
|
+
unsubscribeResource();
|
|
908
|
+
unsubscribeResource = null;
|
|
909
|
+
}
|
|
844
910
|
};
|
|
845
911
|
const resetPollTimer = (jid) => {
|
|
846
912
|
if (pollTimer) clearTimeout(pollTimer);
|
|
@@ -914,7 +980,9 @@ var YieldNamespace = class {
|
|
|
914
980
|
referenceId: annotationId2,
|
|
915
981
|
title: options.title,
|
|
916
982
|
prompt: options.prompt,
|
|
983
|
+
entityTypes: options.entityTypes,
|
|
917
984
|
language: options.language,
|
|
985
|
+
sourceLanguage: options.sourceLanguage,
|
|
918
986
|
temperature: options.temperature,
|
|
919
987
|
maxTokens: options.maxTokens,
|
|
920
988
|
storageUri: options.storageUri,
|
|
@@ -960,13 +1028,14 @@ var YieldNamespace = class {
|
|
|
960
1028
|
return result.sourceResource;
|
|
961
1029
|
}
|
|
962
1030
|
async createFromToken(options) {
|
|
963
|
-
|
|
1031
|
+
const result = await busRequest(
|
|
964
1032
|
this.transport,
|
|
965
1033
|
"yield:clone-create",
|
|
966
1034
|
options,
|
|
967
1035
|
"yield:clone-created",
|
|
968
1036
|
"yield:clone-create-failed"
|
|
969
1037
|
);
|
|
1038
|
+
return { resourceId: resourceId(result.resourceId) };
|
|
970
1039
|
}
|
|
971
1040
|
clone() {
|
|
972
1041
|
this.bus.get("yield:clone").next(void 0);
|
|
@@ -979,7 +1048,7 @@ var BeckonNamespace = class {
|
|
|
979
1048
|
this.transport = transport;
|
|
980
1049
|
this.bus = bus;
|
|
981
1050
|
}
|
|
982
|
-
attention(
|
|
1051
|
+
attention(resourceId2, annotationId2) {
|
|
983
1052
|
void this.transport.emit("beckon:focus", { annotationId: annotationId2, resourceId: resourceId2 });
|
|
984
1053
|
}
|
|
985
1054
|
hover(annotationId2) {
|
|
@@ -990,6 +1059,24 @@ var BeckonNamespace = class {
|
|
|
990
1059
|
}
|
|
991
1060
|
};
|
|
992
1061
|
|
|
1062
|
+
// src/namespaces/frame.ts
|
|
1063
|
+
var FrameNamespace = class {
|
|
1064
|
+
constructor(transport) {
|
|
1065
|
+
this.transport = transport;
|
|
1066
|
+
}
|
|
1067
|
+
async addEntityType(type) {
|
|
1068
|
+
await this.transport.emit("frame:add-entity-type", { tag: type });
|
|
1069
|
+
}
|
|
1070
|
+
async addEntityTypes(types) {
|
|
1071
|
+
for (const tag of types) {
|
|
1072
|
+
await this.transport.emit("frame:add-entity-type", { tag });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async addTagSchema(schema) {
|
|
1076
|
+
await this.transport.emit("frame:add-tag-schema", { schema });
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
|
|
993
1080
|
// src/namespaces/job.ts
|
|
994
1081
|
var JobNamespace = class {
|
|
995
1082
|
constructor(transport, bus) {
|
|
@@ -1027,7 +1114,7 @@ var JobNamespace = class {
|
|
|
1027
1114
|
}
|
|
1028
1115
|
async pollUntilComplete(jobId, options) {
|
|
1029
1116
|
const interval = options?.interval ?? 1e3;
|
|
1030
|
-
const
|
|
1117
|
+
const timeout6 = options?.timeout ?? 6e4;
|
|
1031
1118
|
const startTime = Date.now();
|
|
1032
1119
|
while (true) {
|
|
1033
1120
|
const status = await this.status(jobId);
|
|
@@ -1035,89 +1122,97 @@ var JobNamespace = class {
|
|
|
1035
1122
|
if (status.status === "complete" || status.status === "failed" || status.status === "cancelled") {
|
|
1036
1123
|
return status;
|
|
1037
1124
|
}
|
|
1038
|
-
if (Date.now() - startTime >
|
|
1039
|
-
throw new Error(`Job polling timeout after ${
|
|
1125
|
+
if (Date.now() - startTime > timeout6) {
|
|
1126
|
+
throw new Error(`Job polling timeout after ${timeout6}ms`);
|
|
1040
1127
|
}
|
|
1041
1128
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
1042
1129
|
}
|
|
1043
1130
|
}
|
|
1044
|
-
async
|
|
1045
|
-
await this.transport.emit("job:cancel-requested", {
|
|
1046
|
-
jobType: type === "generation" ? "generation" : "annotation"
|
|
1047
|
-
});
|
|
1131
|
+
async cancelByType(jobType) {
|
|
1132
|
+
await this.transport.emit("job:cancel-requested", { jobType });
|
|
1048
1133
|
}
|
|
1049
1134
|
cancelRequest(jobType) {
|
|
1050
1135
|
this.bus.get("job:cancel-requested").next({ jobType });
|
|
1051
1136
|
}
|
|
1052
1137
|
};
|
|
1053
1138
|
var AuthNamespace = class {
|
|
1054
|
-
constructor(
|
|
1055
|
-
this.
|
|
1139
|
+
constructor(backend) {
|
|
1140
|
+
this.backend = backend;
|
|
1056
1141
|
}
|
|
1057
1142
|
async password(emailStr, passwordStr) {
|
|
1058
|
-
return this.
|
|
1143
|
+
return this.backend.authenticatePassword(email(emailStr), passwordStr);
|
|
1059
1144
|
}
|
|
1060
1145
|
async google(credential) {
|
|
1061
|
-
return this.
|
|
1146
|
+
return this.backend.authenticateGoogle(googleCredential(credential));
|
|
1062
1147
|
}
|
|
1063
1148
|
async refresh(token) {
|
|
1064
|
-
return this.
|
|
1149
|
+
return this.backend.refreshAccessToken(refreshToken(token));
|
|
1065
1150
|
}
|
|
1066
1151
|
async logout() {
|
|
1067
|
-
await this.
|
|
1152
|
+
await this.backend.logout();
|
|
1068
1153
|
}
|
|
1069
1154
|
async me() {
|
|
1070
|
-
return this.
|
|
1155
|
+
return this.backend.getCurrentUser();
|
|
1071
1156
|
}
|
|
1072
1157
|
async acceptTerms() {
|
|
1073
|
-
await this.
|
|
1158
|
+
await this.backend.acceptTerms();
|
|
1074
1159
|
}
|
|
1075
1160
|
async mcpToken() {
|
|
1076
|
-
return this.
|
|
1161
|
+
return this.backend.generateMcpToken();
|
|
1077
1162
|
}
|
|
1078
1163
|
async mediaToken(resourceId2) {
|
|
1079
|
-
return this.
|
|
1164
|
+
return this.backend.getMediaToken(resourceId2);
|
|
1080
1165
|
}
|
|
1081
1166
|
};
|
|
1082
1167
|
|
|
1083
1168
|
// src/namespaces/admin.ts
|
|
1084
1169
|
var AdminNamespace = class {
|
|
1085
|
-
constructor(
|
|
1086
|
-
this.
|
|
1170
|
+
constructor(backend) {
|
|
1171
|
+
this.backend = backend;
|
|
1087
1172
|
}
|
|
1088
1173
|
async users() {
|
|
1089
|
-
const result = await this.
|
|
1174
|
+
const result = await this.backend.listUsers();
|
|
1090
1175
|
return result.users;
|
|
1091
1176
|
}
|
|
1092
1177
|
async userStats() {
|
|
1093
|
-
return this.
|
|
1178
|
+
return this.backend.getUserStats();
|
|
1094
1179
|
}
|
|
1095
1180
|
async updateUser(userId2, data) {
|
|
1096
|
-
const result = await this.
|
|
1181
|
+
const result = await this.backend.updateUser(userId2, data);
|
|
1097
1182
|
return result.user;
|
|
1098
1183
|
}
|
|
1099
1184
|
async oauthConfig() {
|
|
1100
|
-
return this.
|
|
1185
|
+
return this.backend.getOAuthConfig();
|
|
1101
1186
|
}
|
|
1102
1187
|
async healthCheck() {
|
|
1103
|
-
return this.
|
|
1188
|
+
return this.backend.healthCheck();
|
|
1104
1189
|
}
|
|
1105
1190
|
async status() {
|
|
1106
|
-
return this.
|
|
1191
|
+
return this.backend.getStatus();
|
|
1107
1192
|
}
|
|
1108
1193
|
async backup() {
|
|
1109
|
-
return this.
|
|
1194
|
+
return this.backend.backupKnowledgeBase();
|
|
1110
1195
|
}
|
|
1111
|
-
|
|
1112
|
-
return this.
|
|
1196
|
+
restore(file) {
|
|
1197
|
+
return wrapAsStream(this.backend.restoreKnowledgeBase(file));
|
|
1113
1198
|
}
|
|
1114
1199
|
async exportKnowledgeBase(params) {
|
|
1115
|
-
return this.
|
|
1200
|
+
return this.backend.exportKnowledgeBase(params);
|
|
1116
1201
|
}
|
|
1117
|
-
|
|
1118
|
-
return this.
|
|
1202
|
+
importKnowledgeBase(file) {
|
|
1203
|
+
return wrapAsStream(this.backend.importKnowledgeBase(file));
|
|
1119
1204
|
}
|
|
1120
1205
|
};
|
|
1206
|
+
function wrapAsStream(source) {
|
|
1207
|
+
return new StreamObservable((subscriber) => {
|
|
1208
|
+
const sub = source.subscribe({
|
|
1209
|
+
next: (v) => subscriber.next(v),
|
|
1210
|
+
error: (e) => subscriber.error(e),
|
|
1211
|
+
complete: () => subscriber.complete()
|
|
1212
|
+
});
|
|
1213
|
+
return () => sub.unsubscribe();
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1121
1216
|
var SemiontClient = class _SemiontClient {
|
|
1122
1217
|
/**
|
|
1123
1218
|
* The wire-facing transport. Owns bus actor, HTTP, auth, admin, exchange,
|
|
@@ -1137,6 +1232,15 @@ var SemiontClient = class _SemiontClient {
|
|
|
1137
1232
|
bus;
|
|
1138
1233
|
baseUrl;
|
|
1139
1234
|
// ── Verb-oriented namespace API ──────────────────────────────────────────
|
|
1235
|
+
//
|
|
1236
|
+
// The first nine namespaces are bus-driven and always present. `frame`
|
|
1237
|
+
// is the schema-layer flow's surface (eighth flow); the other eight are
|
|
1238
|
+
// content-layer flows plus `job`. `auth` and `admin` are backend-ops
|
|
1239
|
+
// namespaces — they're only constructed when the caller passes an
|
|
1240
|
+
// `IBackendOperations` instance to the constructor. A `SemiontClient`
|
|
1241
|
+
// over a transport-only setup (e.g. `LocalTransport`) has
|
|
1242
|
+
// `auth === undefined` / `admin === undefined`.
|
|
1243
|
+
frame;
|
|
1140
1244
|
browse;
|
|
1141
1245
|
mark;
|
|
1142
1246
|
bind;
|
|
@@ -1159,13 +1263,20 @@ var SemiontClient = class _SemiontClient {
|
|
|
1159
1263
|
* Callers do not pass a bus in. If they need to interact with the bus
|
|
1160
1264
|
* (e.g. for tests or to subscribe to arbitrary channels), they read it
|
|
1161
1265
|
* back via `client.bus`.
|
|
1266
|
+
*
|
|
1267
|
+
* `backend` is optional. When provided, the `auth` and `admin`
|
|
1268
|
+
* namespaces are constructed against it; when omitted, they're
|
|
1269
|
+
* `undefined`. For HTTP setups this is conventionally the same
|
|
1270
|
+
* `HttpTransport` instance that's also passed as `transport` (HTTP
|
|
1271
|
+
* implements both `ITransport` and `IBackendOperations`).
|
|
1162
1272
|
*/
|
|
1163
|
-
constructor(transport, content) {
|
|
1273
|
+
constructor(transport, content, backend) {
|
|
1164
1274
|
this.transport = transport;
|
|
1165
1275
|
this.content = content;
|
|
1166
1276
|
this.baseUrl = transport.baseUrl;
|
|
1167
1277
|
this.bus = new EventBus();
|
|
1168
1278
|
this.transport.bridgeInto(this.bus);
|
|
1279
|
+
this.frame = new FrameNamespace(this.transport);
|
|
1169
1280
|
this.browse = new BrowseNamespace(this.transport, this.bus, this.content);
|
|
1170
1281
|
this.mark = new MarkNamespace(this.transport, this.bus);
|
|
1171
1282
|
this.bind = new BindNamespace(this.transport, this.bus);
|
|
@@ -1174,8 +1285,8 @@ var SemiontClient = class _SemiontClient {
|
|
|
1174
1285
|
this.yield = new YieldNamespace(this.transport, this.bus, this.content);
|
|
1175
1286
|
this.beckon = new BeckonNamespace(this.transport, this.bus);
|
|
1176
1287
|
this.job = new JobNamespace(this.transport, this.bus);
|
|
1177
|
-
this.auth = new AuthNamespace(
|
|
1178
|
-
this.admin = new AdminNamespace(
|
|
1288
|
+
this.auth = backend ? new AuthNamespace(backend) : void 0;
|
|
1289
|
+
this.admin = backend ? new AdminNamespace(backend) : void 0;
|
|
1179
1290
|
}
|
|
1180
1291
|
/** Transport-level connection state. HTTP reflects SSE health; local is always 'connected'. */
|
|
1181
1292
|
get state$() {
|
|
@@ -1197,9 +1308,10 @@ var SemiontClient = class _SemiontClient {
|
|
|
1197
1308
|
* Use this for one-shot scripts, CLI commands, or any consumer that
|
|
1198
1309
|
* doesn't need to drive the token from outside (no manual refresh,
|
|
1199
1310
|
* no cross-tab sync). For long-running scripts that need refresh,
|
|
1200
|
-
* use `SemiontSession.fromHttp(...)`
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1311
|
+
* use `SemiontSession.fromHttp(...)` (with a token already on hand)
|
|
1312
|
+
* or `SemiontSession.signInHttp(...)` (credentials-first) instead —
|
|
1313
|
+
* either owns the same transport/client wiring plus the
|
|
1314
|
+
* proactive-refresh + storage machinery.
|
|
1203
1315
|
*
|
|
1204
1316
|
* Strings are accepted for `baseUrl` and `token`; they are branded
|
|
1205
1317
|
* via `baseUrl()` / `accessToken()` from `@semiont/core` automatically.
|
|
@@ -1213,13 +1325,13 @@ var SemiontClient = class _SemiontClient {
|
|
|
1213
1325
|
const token$ = new BehaviorSubject(tok);
|
|
1214
1326
|
const transport = new HttpTransport({ baseUrl: url, token$ });
|
|
1215
1327
|
const content = new HttpContentTransport(transport);
|
|
1216
|
-
return new _SemiontClient(transport, content);
|
|
1328
|
+
return new _SemiontClient(transport, content, transport);
|
|
1217
1329
|
}
|
|
1218
1330
|
/**
|
|
1219
1331
|
* Async factory for the credentials-first script case. Builds a
|
|
1220
|
-
* transient transport, calls `auth.password(email, password)`
|
|
1221
|
-
* acquire an access token, and returns the wired client with
|
|
1222
|
-
* token populated.
|
|
1332
|
+
* transient HTTP transport, calls `auth.password(email, password)`
|
|
1333
|
+
* to acquire an access token, and returns the wired client with
|
|
1334
|
+
* the token populated.
|
|
1223
1335
|
*
|
|
1224
1336
|
* This is the right entry point for skills, CLI scripts, and any
|
|
1225
1337
|
* consumer that starts with email + password rather than a JWT
|
|
@@ -1228,18 +1340,24 @@ var SemiontClient = class _SemiontClient {
|
|
|
1228
1340
|
* `fromHttp({ baseUrl, token })` instead.
|
|
1229
1341
|
*
|
|
1230
1342
|
* For long-running scripts that need refresh, use
|
|
1231
|
-
* `SemiontSession.
|
|
1232
|
-
* session machinery for proactive refresh and persistence.
|
|
1343
|
+
* `SemiontSession.signInHttp(...)` — same credentials shape, plus
|
|
1344
|
+
* the session machinery for proactive refresh and persistence.
|
|
1345
|
+
*
|
|
1346
|
+
* Named `signInHttp` because email+password authentication is
|
|
1347
|
+
* inherently an HTTP-shaped operation in the current backend; an
|
|
1348
|
+
* in-process `LocalTransport` doesn't have a credentials login
|
|
1349
|
+
* path. Non-HTTP transports construct the client directly from
|
|
1350
|
+
* their package's transport instance.
|
|
1233
1351
|
*
|
|
1234
1352
|
* Throws if authentication fails. The transient client is disposed
|
|
1235
1353
|
* before the throw, so no resources leak on failure.
|
|
1236
1354
|
*/
|
|
1237
|
-
static async
|
|
1355
|
+
static async signInHttp(opts) {
|
|
1238
1356
|
const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
|
|
1239
1357
|
const token$ = new BehaviorSubject(null);
|
|
1240
1358
|
const transport = new HttpTransport({ baseUrl: url, token$ });
|
|
1241
1359
|
const content = new HttpContentTransport(transport);
|
|
1242
|
-
const client = new _SemiontClient(transport, content);
|
|
1360
|
+
const client = new _SemiontClient(transport, content, transport);
|
|
1243
1361
|
try {
|
|
1244
1362
|
const auth = await client.auth.password(opts.email, opts.password);
|
|
1245
1363
|
token$.next(accessToken(auth.token));
|
|
@@ -1293,35 +1411,28 @@ function isJwtExpired(token) {
|
|
|
1293
1411
|
if (!expiry) return true;
|
|
1294
1412
|
return expiry.getTime() < Date.now();
|
|
1295
1413
|
}
|
|
1296
|
-
function
|
|
1297
|
-
if (entry
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return
|
|
1301
|
-
id: entry.id,
|
|
1302
|
-
label: entry.label,
|
|
1303
|
-
host: url.hostname,
|
|
1304
|
-
port: parseInt(url.port, 10) || (url.protocol === "https:" ? 443 : 80),
|
|
1305
|
-
protocol: url.protocol === "https:" ? "https" : "http",
|
|
1306
|
-
email: ""
|
|
1307
|
-
};
|
|
1308
|
-
} catch {
|
|
1309
|
-
return {
|
|
1310
|
-
id: entry.id,
|
|
1311
|
-
label: entry.label || "Unknown",
|
|
1312
|
-
host: "localhost",
|
|
1313
|
-
port: 4e3,
|
|
1314
|
-
protocol: "http",
|
|
1315
|
-
email: ""
|
|
1316
|
-
};
|
|
1414
|
+
function isKnowledgeBase(entry) {
|
|
1415
|
+
if (!entry || typeof entry !== "object") return false;
|
|
1416
|
+
const e = entry;
|
|
1417
|
+
if (typeof e.id !== "string" || typeof e.label !== "string" || typeof e.email !== "string") {
|
|
1418
|
+
return false;
|
|
1317
1419
|
}
|
|
1420
|
+
const ep = e.endpoint;
|
|
1421
|
+
if (!ep || typeof ep !== "object") return false;
|
|
1422
|
+
if (ep.kind === "http") {
|
|
1423
|
+
return typeof ep.host === "string" && typeof ep.port === "number" && (ep.protocol === "http" || ep.protocol === "https");
|
|
1424
|
+
}
|
|
1425
|
+
if (ep.kind === "local") {
|
|
1426
|
+
return typeof ep.kbId === "string";
|
|
1427
|
+
}
|
|
1428
|
+
return false;
|
|
1318
1429
|
}
|
|
1319
1430
|
function loadKnowledgeBases(storage) {
|
|
1320
1431
|
try {
|
|
1321
1432
|
const raw = storage.get(STORAGE_KEY);
|
|
1322
1433
|
if (!raw) return [];
|
|
1323
1434
|
const entries = JSON.parse(raw);
|
|
1324
|
-
return entries.
|
|
1435
|
+
return entries.filter(isKnowledgeBase);
|
|
1325
1436
|
} catch {
|
|
1326
1437
|
return [];
|
|
1327
1438
|
}
|
|
@@ -1336,15 +1447,15 @@ var HOSTNAME_RE = /^(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]
|
|
|
1336
1447
|
function isValidHostname(host) {
|
|
1337
1448
|
return HOSTNAME_RE.test(host);
|
|
1338
1449
|
}
|
|
1339
|
-
function kbBackendUrl(
|
|
1340
|
-
if (!isValidHostname(
|
|
1341
|
-
throw new Error(`Invalid KB hostname: "${
|
|
1450
|
+
function kbBackendUrl(endpoint) {
|
|
1451
|
+
if (!isValidHostname(endpoint.host)) {
|
|
1452
|
+
throw new Error(`Invalid KB hostname: "${endpoint.host}"`);
|
|
1342
1453
|
}
|
|
1343
1454
|
const url = new URL("http://x");
|
|
1344
|
-
url.protocol =
|
|
1345
|
-
url.hostname =
|
|
1346
|
-
url.port = String(
|
|
1347
|
-
return `${
|
|
1455
|
+
url.protocol = endpoint.protocol + ":";
|
|
1456
|
+
url.hostname = endpoint.host;
|
|
1457
|
+
url.port = String(endpoint.port);
|
|
1458
|
+
return `${endpoint.protocol}://${url.hostname}:${endpoint.port}`;
|
|
1348
1459
|
}
|
|
1349
1460
|
function generateKbId() {
|
|
1350
1461
|
return crypto.randomUUID();
|
|
@@ -1365,6 +1476,18 @@ var SemiontSession = class _SemiontSession {
|
|
|
1365
1476
|
token$;
|
|
1366
1477
|
user$;
|
|
1367
1478
|
streamState$;
|
|
1479
|
+
/**
|
|
1480
|
+
* Stream of `SemiontError` instances surfaced by the underlying transport
|
|
1481
|
+
* just before they're thrown to the caller. For `HttpTransport` this is
|
|
1482
|
+
* an `APIError` (status-coded); other transports emit their own subclass.
|
|
1483
|
+
* Surfaced here so a host layer (e.g. `SemiontBrowser`) can route by
|
|
1484
|
+
* `err.code` to global notifications without every call site handling
|
|
1485
|
+
* errors itself. Headless consumers can subscribe for logging.
|
|
1486
|
+
*
|
|
1487
|
+
* Re-published from `client.transport.errors$` per the `ITransport`
|
|
1488
|
+
* contract — the session is purely a passthrough.
|
|
1489
|
+
*/
|
|
1490
|
+
errors$;
|
|
1368
1491
|
/** Resolves after the initial validation round-trip completes (success or failure). */
|
|
1369
1492
|
ready;
|
|
1370
1493
|
storage;
|
|
@@ -1387,6 +1510,7 @@ var SemiontSession = class _SemiontSession {
|
|
|
1387
1510
|
this.client = config.client;
|
|
1388
1511
|
this.token$ = config.token$;
|
|
1389
1512
|
this.user$ = new BehaviorSubject(null);
|
|
1513
|
+
this.errors$ = this.client.transport.errors$;
|
|
1390
1514
|
const stored = getStoredSession(this.storage, this.kb.id);
|
|
1391
1515
|
if (stored && !isJwtExpired(stored.access) && this.token$.getValue() === null) {
|
|
1392
1516
|
this.token$.next(accessToken(stored.access));
|
|
@@ -1571,7 +1695,7 @@ var SemiontSession = class _SemiontSession {
|
|
|
1571
1695
|
const token$ = new BehaviorSubject(tok);
|
|
1572
1696
|
const transport = new HttpTransport({ baseUrl: url, token$ });
|
|
1573
1697
|
const content = new HttpContentTransport(transport);
|
|
1574
|
-
const client = new SemiontClient(transport, content);
|
|
1698
|
+
const client = new SemiontClient(transport, content, transport);
|
|
1575
1699
|
const config = { kb: opts.kb, storage: opts.storage, client, token$ };
|
|
1576
1700
|
if (opts.refresh) config.refresh = opts.refresh;
|
|
1577
1701
|
if (opts.validate) config.validate = opts.validate;
|
|
@@ -1581,11 +1705,11 @@ var SemiontSession = class _SemiontSession {
|
|
|
1581
1705
|
}
|
|
1582
1706
|
/**
|
|
1583
1707
|
* Async factory for the credentials-first long-running script case.
|
|
1584
|
-
* Builds the transport stack, calls `auth.password(email,
|
|
1585
|
-
* to acquire access + refresh tokens, persists them via
|
|
1586
|
-
* adapter, wires a default `refresh` callback that
|
|
1587
|
-
* refresh token via `auth.refresh(...)`, and returns
|
|
1588
|
-
* session.
|
|
1708
|
+
* Builds the HTTP transport stack, calls `auth.password(email,
|
|
1709
|
+
* password)` to acquire access + refresh tokens, persists them via
|
|
1710
|
+
* the storage adapter, wires a default `refresh` callback that
|
|
1711
|
+
* exchanges the refresh token via `auth.refresh(...)`, and returns
|
|
1712
|
+
* the ready session.
|
|
1589
1713
|
*
|
|
1590
1714
|
* The consumer-supplied `refresh` callback becomes optional — only
|
|
1591
1715
|
* needed for non-standard refresh flows (worker-pool shared secret,
|
|
@@ -1598,15 +1722,21 @@ var SemiontSession = class _SemiontSession {
|
|
|
1598
1722
|
* trampling each other's tokens. The factory does not synthesize a
|
|
1599
1723
|
* default; the consumer makes the choice.
|
|
1600
1724
|
*
|
|
1725
|
+
* Named `signInHttp` because email+password authentication is
|
|
1726
|
+
* inherently an HTTP-shaped operation in the current backend; an
|
|
1727
|
+
* in-process `LocalTransport` doesn't have a credentials login
|
|
1728
|
+
* path. Non-HTTP transports construct the session directly from
|
|
1729
|
+
* their package's transport instance.
|
|
1730
|
+
*
|
|
1601
1731
|
* Throws on auth failure with no resources leaked. On success, the
|
|
1602
1732
|
* returned session's `ready` promise has already resolved.
|
|
1603
1733
|
*/
|
|
1604
|
-
static async
|
|
1734
|
+
static async signInHttp(opts) {
|
|
1605
1735
|
const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
|
|
1606
1736
|
const token$ = new BehaviorSubject(null);
|
|
1607
1737
|
const transport = new HttpTransport({ baseUrl: url, token$ });
|
|
1608
1738
|
const content = new HttpContentTransport(transport);
|
|
1609
|
-
const client = new SemiontClient(transport, content);
|
|
1739
|
+
const client = new SemiontClient(transport, content, transport);
|
|
1610
1740
|
let auth;
|
|
1611
1741
|
try {
|
|
1612
1742
|
auth = await client.auth.password(opts.email, opts.password);
|
|
@@ -1641,25 +1771,7 @@ var SemiontSession = class _SemiontSession {
|
|
|
1641
1771
|
return session;
|
|
1642
1772
|
}
|
|
1643
1773
|
};
|
|
1644
|
-
|
|
1645
|
-
// src/session/notify.ts
|
|
1646
|
-
var activeOnSessionExpired = null;
|
|
1647
|
-
var activeOnPermissionDenied = null;
|
|
1648
|
-
function notifySessionExpired(message) {
|
|
1649
|
-
activeOnSessionExpired?.(message);
|
|
1650
|
-
}
|
|
1651
|
-
function notifyPermissionDenied(message) {
|
|
1652
|
-
activeOnPermissionDenied?.(message);
|
|
1653
|
-
}
|
|
1654
|
-
function registerAuthNotifyHandlers(handlers) {
|
|
1655
|
-
activeOnSessionExpired = handlers.onSessionExpired;
|
|
1656
|
-
activeOnPermissionDenied = handlers.onPermissionDenied;
|
|
1657
|
-
return () => {
|
|
1658
|
-
activeOnSessionExpired = null;
|
|
1659
|
-
activeOnPermissionDenied = null;
|
|
1660
|
-
};
|
|
1661
|
-
}
|
|
1662
|
-
var FrontendSessionSignals = class {
|
|
1774
|
+
var SessionSignals = class {
|
|
1663
1775
|
sessionExpiredAt$;
|
|
1664
1776
|
sessionExpiredMessage$;
|
|
1665
1777
|
permissionDeniedAt$;
|
|
@@ -1724,7 +1836,7 @@ var SemiontBrowser = class {
|
|
|
1724
1836
|
* non-null when `activeSession$` is non-null, always null when it
|
|
1725
1837
|
* is. Extracted from the session itself so headless sessions
|
|
1726
1838
|
* (workers, CLIs, tests) don't carry dead modal observables.
|
|
1727
|
-
* See [
|
|
1839
|
+
* See [SessionSignals](./session-signals.ts).
|
|
1728
1840
|
*/
|
|
1729
1841
|
activeSignals$;
|
|
1730
1842
|
/**
|
|
@@ -1740,6 +1852,7 @@ var SemiontBrowser = class {
|
|
|
1740
1852
|
error$;
|
|
1741
1853
|
identityToken$;
|
|
1742
1854
|
storage;
|
|
1855
|
+
sessionFactory;
|
|
1743
1856
|
/**
|
|
1744
1857
|
* App-scoped EventBus. Hosts UI-shell events that must work regardless
|
|
1745
1858
|
* of whether a KB session is active: panel toggles, sidebar state,
|
|
@@ -1748,20 +1861,12 @@ var SemiontBrowser = class {
|
|
|
1748
1861
|
* (mark:*, beckon:*, gather:*, match:*, bind:*, yield:*, browse:click).
|
|
1749
1862
|
*/
|
|
1750
1863
|
eventBus = new EventBus();
|
|
1751
|
-
unregisterNotify = null;
|
|
1752
1864
|
unsubscribeStorage = null;
|
|
1753
1865
|
disposed = false;
|
|
1754
1866
|
activating = null;
|
|
1755
|
-
/**
|
|
1756
|
-
* Per-KB in-flight refresh dedup. Simultaneous 401s for the same
|
|
1757
|
-
* KB converge on a single `/api/tokens/refresh` network call.
|
|
1758
|
-
* Was previously module-scoped in `refresh.ts`; moved here when
|
|
1759
|
-
* that file was deleted — SemiontBrowser is a singleton so the
|
|
1760
|
-
* scoping is equivalent.
|
|
1761
|
-
*/
|
|
1762
|
-
inFlightRefreshes = /* @__PURE__ */ new Map();
|
|
1763
1867
|
constructor(config) {
|
|
1764
1868
|
this.storage = config.storage;
|
|
1869
|
+
this.sessionFactory = config.sessionFactory;
|
|
1765
1870
|
const kbs = loadKnowledgeBases(this.storage);
|
|
1766
1871
|
const storedActive = this.storage.get(ACTIVE_KEY);
|
|
1767
1872
|
const initialActive = storedActive && kbs.some((kb) => kb.id === storedActive) ? storedActive : kbs[0]?.id ?? null;
|
|
@@ -1788,14 +1893,6 @@ var SemiontBrowser = class {
|
|
|
1788
1893
|
} catch {
|
|
1789
1894
|
}
|
|
1790
1895
|
}) ?? null;
|
|
1791
|
-
this.unregisterNotify = registerAuthNotifyHandlers({
|
|
1792
|
-
onSessionExpired: (message) => {
|
|
1793
|
-
this.activeSignals$.getValue()?.notifySessionExpired(message ?? null);
|
|
1794
|
-
},
|
|
1795
|
-
onPermissionDenied: (message) => {
|
|
1796
|
-
this.activeSignals$.getValue()?.notifyPermissionDenied(message ?? null);
|
|
1797
|
-
}
|
|
1798
|
-
});
|
|
1799
1896
|
if (initialActive) {
|
|
1800
1897
|
void this.setActiveKb(initialActive);
|
|
1801
1898
|
}
|
|
@@ -1817,9 +1914,10 @@ var SemiontBrowser = class {
|
|
|
1817
1914
|
}
|
|
1818
1915
|
// ── Identity token (NextAuth bridge; D1) ──────────────────────────────
|
|
1819
1916
|
/**
|
|
1820
|
-
* Set the app-level identity token
|
|
1821
|
-
*
|
|
1822
|
-
*
|
|
1917
|
+
* Set the app-level identity token. Sourced from whatever the host
|
|
1918
|
+
* environment uses for OAuth sessions (e.g. NextAuth in a browser app).
|
|
1919
|
+
* Should be called once from the host's startup-and-on-change site;
|
|
1920
|
+
* no other code should write to this slot.
|
|
1823
1921
|
*/
|
|
1824
1922
|
setIdentityToken(token) {
|
|
1825
1923
|
if (this.disposed) return;
|
|
@@ -1841,6 +1939,12 @@ var SemiontBrowser = class {
|
|
|
1841
1939
|
void this.setActiveKb(next[0]?.id ?? null);
|
|
1842
1940
|
}
|
|
1843
1941
|
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Patch a KB in the list. Restricted to the common, endpoint-agnostic
|
|
1944
|
+
* fields (`label`, `email`, `gitBranch`) — the `endpoint` shape isn't
|
|
1945
|
+
* editable in place; remove and re-add to change the connection
|
|
1946
|
+
* target.
|
|
1947
|
+
*/
|
|
1844
1948
|
updateKb(id, updates) {
|
|
1845
1949
|
this.kbs$.next(
|
|
1846
1950
|
this.kbs$.getValue().map((kb) => kb.id === id ? { ...kb, ...updates } : kb)
|
|
@@ -1896,25 +2000,32 @@ var SemiontBrowser = class {
|
|
|
1896
2000
|
if (!id) return;
|
|
1897
2001
|
const kb = this.kbs$.getValue().find((k) => k.id === id);
|
|
1898
2002
|
if (!kb) return;
|
|
1899
|
-
const signals = new
|
|
1900
|
-
const token$ = new BehaviorSubject(null);
|
|
2003
|
+
const signals = new SessionSignals();
|
|
1901
2004
|
let session;
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
2005
|
+
try {
|
|
2006
|
+
session = this.sessionFactory({
|
|
2007
|
+
kb,
|
|
2008
|
+
storage: this.storage,
|
|
2009
|
+
signals,
|
|
2010
|
+
onError: (err) => this.error$.next(err)
|
|
2011
|
+
});
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
this.error$.next(
|
|
2014
|
+
err instanceof SemiontSessionError ? err : new SemiontSessionError(
|
|
2015
|
+
"session.construct-failed",
|
|
2016
|
+
err instanceof Error ? err.message : String(err),
|
|
2017
|
+
id
|
|
2018
|
+
)
|
|
2019
|
+
);
|
|
2020
|
+
signals.dispose();
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
session.errors$.subscribe((err) => {
|
|
2024
|
+
if (err.code === "unauthorized") {
|
|
2025
|
+
signals.notifySessionExpired(err.message);
|
|
2026
|
+
} else if (err.code === "forbidden") {
|
|
2027
|
+
signals.notifyPermissionDenied(err.message);
|
|
2028
|
+
}
|
|
1918
2029
|
});
|
|
1919
2030
|
try {
|
|
1920
2031
|
await session.ready;
|
|
@@ -2030,80 +2141,10 @@ var SemiontBrowser = class {
|
|
|
2030
2141
|
if (moved) list.splice(newIndex, 0, moved);
|
|
2031
2142
|
this.openResources$.next(list);
|
|
2032
2143
|
}
|
|
2033
|
-
// ── Auth callbacks bound per session ──────────────────────────────────
|
|
2034
|
-
//
|
|
2035
|
-
// These closures back the `refresh` and `validate` callbacks passed
|
|
2036
|
-
// to `SemiontSession` in `setActiveKb`. Factored out as methods
|
|
2037
|
-
// (rather than inline in the activation closure) so test-doubles
|
|
2038
|
-
// can override them cleanly, and so the in-flight dedup map
|
|
2039
|
-
// survives across activations of the same KB.
|
|
2040
|
-
/**
|
|
2041
|
-
* Refresh the active KB's access token. Returns the new token on
|
|
2042
|
-
* success, null on failure. Concurrent calls for the same KB
|
|
2043
|
-
* dedupe through `inFlightRefreshes`, so simultaneous 401s trigger
|
|
2044
|
-
* only one `/api/tokens/refresh` round trip.
|
|
2045
|
-
*
|
|
2046
|
-
* Uses a throwaway `SemiontClient` with no `tokenRefresher` —
|
|
2047
|
-
* a refresh call returning 401 would otherwise re-enter this
|
|
2048
|
-
* function infinitely.
|
|
2049
|
-
*/
|
|
2050
|
-
async performRefresh(kb) {
|
|
2051
|
-
const existing = this.inFlightRefreshes.get(kb.id);
|
|
2052
|
-
if (existing) return existing;
|
|
2053
|
-
const promise = (async () => {
|
|
2054
|
-
const stored = getStoredSession(this.storage, kb.id);
|
|
2055
|
-
if (!stored) return null;
|
|
2056
|
-
const throwawayTransport = new HttpTransport({ baseUrl: baseUrl(kbBackendUrl(kb)) });
|
|
2057
|
-
const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport));
|
|
2058
|
-
try {
|
|
2059
|
-
const response = await throwaway.auth.refresh(stored.refresh);
|
|
2060
|
-
const newAccess = response.access_token;
|
|
2061
|
-
if (!newAccess) return null;
|
|
2062
|
-
setStoredSession(this.storage, kb.id, { access: newAccess, refresh: stored.refresh });
|
|
2063
|
-
return newAccess;
|
|
2064
|
-
} catch {
|
|
2065
|
-
return null;
|
|
2066
|
-
} finally {
|
|
2067
|
-
throwaway.dispose();
|
|
2068
|
-
}
|
|
2069
|
-
})();
|
|
2070
|
-
this.inFlightRefreshes.set(kb.id, promise);
|
|
2071
|
-
try {
|
|
2072
|
-
return await promise;
|
|
2073
|
-
} finally {
|
|
2074
|
-
this.inFlightRefreshes.delete(kb.id);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
/**
|
|
2078
|
-
* Validate an access token by calling `auth.me` on a throwaway
|
|
2079
|
-
* client. The session uses this once at startup to populate
|
|
2080
|
-
* `user$`; 401 triggers a refresh-then-retry inside the session.
|
|
2081
|
-
*
|
|
2082
|
-
* The throwaway transport is seeded with the specific token to
|
|
2083
|
-
* validate so the request actually carries it (HttpTransport
|
|
2084
|
-
* sources `Authorization` from its `token$`).
|
|
2085
|
-
*/
|
|
2086
|
-
async performValidate(kb, token) {
|
|
2087
|
-
const tokenSubject = new BehaviorSubject(token);
|
|
2088
|
-
const throwawayTransport = new HttpTransport({
|
|
2089
|
-
baseUrl: baseUrl(kbBackendUrl(kb)),
|
|
2090
|
-
token$: tokenSubject
|
|
2091
|
-
});
|
|
2092
|
-
const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport));
|
|
2093
|
-
try {
|
|
2094
|
-
const data = await throwaway.auth.me();
|
|
2095
|
-
return data;
|
|
2096
|
-
} finally {
|
|
2097
|
-
throwaway.dispose();
|
|
2098
|
-
tokenSubject.complete();
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
2144
|
// ── Lifecycle ─────────────────────────────────────────────────────────
|
|
2102
2145
|
async dispose() {
|
|
2103
2146
|
if (this.disposed) return;
|
|
2104
2147
|
this.disposed = true;
|
|
2105
|
-
this.unregisterNotify?.();
|
|
2106
|
-
this.unregisterNotify = null;
|
|
2107
2148
|
if (this.unsubscribeStorage) {
|
|
2108
2149
|
this.unsubscribeStorage();
|
|
2109
2150
|
this.unsubscribeStorage = null;
|
|
@@ -2124,12 +2165,91 @@ var SemiontBrowser = class {
|
|
|
2124
2165
|
this.eventBus.destroy();
|
|
2125
2166
|
}
|
|
2126
2167
|
};
|
|
2168
|
+
function createHttpSessionFactory() {
|
|
2169
|
+
const inFlightRefreshes = /* @__PURE__ */ new Map();
|
|
2170
|
+
return (opts) => {
|
|
2171
|
+
const { kb, storage, signals, onError } = opts;
|
|
2172
|
+
if (kb.endpoint.kind !== "http") {
|
|
2173
|
+
throw new SemiontSessionError(
|
|
2174
|
+
"session.construct-failed",
|
|
2175
|
+
`HTTP session factory cannot construct a session for endpoint kind "${kb.endpoint.kind}"`,
|
|
2176
|
+
kb.id
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
const endpoint = kb.endpoint;
|
|
2180
|
+
const performRefresh = async () => {
|
|
2181
|
+
const existing = inFlightRefreshes.get(kb.id);
|
|
2182
|
+
if (existing) return existing;
|
|
2183
|
+
const promise = (async () => {
|
|
2184
|
+
const stored = getStoredSession(storage, kb.id);
|
|
2185
|
+
if (!stored) return null;
|
|
2186
|
+
const throwawayTransport = new HttpTransport({ baseUrl: baseUrl(kbBackendUrl(endpoint)) });
|
|
2187
|
+
const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport), throwawayTransport);
|
|
2188
|
+
try {
|
|
2189
|
+
const response = await throwaway.auth.refresh(stored.refresh);
|
|
2190
|
+
const newAccess = response.access_token;
|
|
2191
|
+
if (!newAccess) return null;
|
|
2192
|
+
setStoredSession(storage, kb.id, { access: newAccess, refresh: stored.refresh });
|
|
2193
|
+
return newAccess;
|
|
2194
|
+
} catch {
|
|
2195
|
+
return null;
|
|
2196
|
+
} finally {
|
|
2197
|
+
throwaway.dispose();
|
|
2198
|
+
}
|
|
2199
|
+
})();
|
|
2200
|
+
inFlightRefreshes.set(kb.id, promise);
|
|
2201
|
+
try {
|
|
2202
|
+
return await promise;
|
|
2203
|
+
} finally {
|
|
2204
|
+
inFlightRefreshes.delete(kb.id);
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
const performValidate = async (token) => {
|
|
2208
|
+
const tokenSubject = new BehaviorSubject(token);
|
|
2209
|
+
const throwawayTransport = new HttpTransport({
|
|
2210
|
+
baseUrl: baseUrl(kbBackendUrl(endpoint)),
|
|
2211
|
+
token$: tokenSubject
|
|
2212
|
+
});
|
|
2213
|
+
const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport), throwawayTransport);
|
|
2214
|
+
try {
|
|
2215
|
+
const data = await throwaway.auth.me();
|
|
2216
|
+
return data;
|
|
2217
|
+
} finally {
|
|
2218
|
+
throwaway.dispose();
|
|
2219
|
+
tokenSubject.complete();
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
const token$ = new BehaviorSubject(null);
|
|
2223
|
+
let session;
|
|
2224
|
+
const transport = new HttpTransport({
|
|
2225
|
+
baseUrl: baseUrl(kbBackendUrl(endpoint)),
|
|
2226
|
+
token$,
|
|
2227
|
+
tokenRefresher: () => session.refresh().then((t) => t ?? null)
|
|
2228
|
+
});
|
|
2229
|
+
const content = new HttpContentTransport(transport);
|
|
2230
|
+
const client = new SemiontClient(transport, content, transport);
|
|
2231
|
+
session = new SemiontSession({
|
|
2232
|
+
kb,
|
|
2233
|
+
storage,
|
|
2234
|
+
client,
|
|
2235
|
+
token$,
|
|
2236
|
+
refresh: performRefresh,
|
|
2237
|
+
validate: performValidate,
|
|
2238
|
+
onAuthFailed: (msg) => signals.notifySessionExpired(msg),
|
|
2239
|
+
onError
|
|
2240
|
+
});
|
|
2241
|
+
return session;
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2127
2244
|
|
|
2128
2245
|
// src/session/registry.ts
|
|
2129
2246
|
var instance = null;
|
|
2130
2247
|
function getBrowser(options) {
|
|
2131
2248
|
if (!instance) {
|
|
2132
|
-
instance = new SemiontBrowser({
|
|
2249
|
+
instance = new SemiontBrowser({
|
|
2250
|
+
storage: options.storage,
|
|
2251
|
+
sessionFactory: options.sessionFactory
|
|
2252
|
+
});
|
|
2133
2253
|
}
|
|
2134
2254
|
return instance;
|
|
2135
2255
|
}
|
|
@@ -2147,6 +2267,18 @@ var InMemorySessionStorage = class {
|
|
|
2147
2267
|
this.map.delete(key);
|
|
2148
2268
|
}
|
|
2149
2269
|
};
|
|
2270
|
+
|
|
2271
|
+
// src/session/knowledge-base.ts
|
|
2272
|
+
function httpKb(opts) {
|
|
2273
|
+
const { id, label, email, host, port, protocol, gitBranch } = opts;
|
|
2274
|
+
return {
|
|
2275
|
+
id,
|
|
2276
|
+
label,
|
|
2277
|
+
email,
|
|
2278
|
+
...gitBranch !== void 0 ? { gitBranch } : {},
|
|
2279
|
+
endpoint: { kind: "http", host, port, protocol }
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2150
2282
|
function createDisposer() {
|
|
2151
2283
|
const sub = new Subscription();
|
|
2152
2284
|
return {
|
|
@@ -2162,7 +2294,7 @@ function createSearchPipeline(fetch, options = {}) {
|
|
|
2162
2294
|
const state$ = input$.pipe(
|
|
2163
2295
|
startWith(initial),
|
|
2164
2296
|
debounceTime(debounceMs),
|
|
2165
|
-
distinctUntilChanged
|
|
2297
|
+
distinctUntilChanged(),
|
|
2166
2298
|
switchMap((q) => {
|
|
2167
2299
|
const trimmed = q.trim();
|
|
2168
2300
|
if (!trimmed) {
|
|
@@ -2184,7 +2316,7 @@ function createSearchPipeline(fetch, options = {}) {
|
|
|
2184
2316
|
dispose: () => input$.complete()
|
|
2185
2317
|
};
|
|
2186
2318
|
}
|
|
2187
|
-
function
|
|
2319
|
+
function createBeckonStateUnit(client) {
|
|
2188
2320
|
const subs = [];
|
|
2189
2321
|
const hovered$ = new BehaviorSubject(null);
|
|
2190
2322
|
subs.push(client.bus.get("beckon:hover").subscribe(({ annotationId: annotationId2 }) => {
|
|
@@ -2235,59 +2367,7 @@ function createHoverHandlers(emit, delayMs) {
|
|
|
2235
2367
|
};
|
|
2236
2368
|
return { handleMouseEnter, handleMouseLeave, cleanup: cancelTimer };
|
|
2237
2369
|
}
|
|
2238
|
-
|
|
2239
|
-
var RESOURCE_PANELS = ["history", "info", "annotations", "collaboration", "jsonld"];
|
|
2240
|
-
var MOTIVATION_TO_TAB = {
|
|
2241
|
-
"linking": "reference",
|
|
2242
|
-
"commenting": "comment",
|
|
2243
|
-
"tagging": "tag",
|
|
2244
|
-
"highlighting": "highlight",
|
|
2245
|
-
"assessing": "assessment"
|
|
2246
|
-
};
|
|
2247
|
-
var tabGenerationCounter = 0;
|
|
2248
|
-
function createShellVM(browser, options) {
|
|
2249
|
-
const subs = [];
|
|
2250
|
-
const activePanel$ = new BehaviorSubject(options?.initialPanel ?? null);
|
|
2251
|
-
const scrollToAnnotationId$ = new BehaviorSubject(null);
|
|
2252
|
-
const panelInitialTab$ = new BehaviorSubject(null);
|
|
2253
|
-
if (options?.onPanelChange) {
|
|
2254
|
-
const cb = options.onPanelChange;
|
|
2255
|
-
subs.push(activePanel$.subscribe(cb));
|
|
2256
|
-
}
|
|
2257
|
-
subs.push(browser.stream("panel:toggle").subscribe(({ panel }) => {
|
|
2258
|
-
const current = activePanel$.getValue();
|
|
2259
|
-
activePanel$.next(current === panel ? null : panel);
|
|
2260
|
-
}));
|
|
2261
|
-
subs.push(browser.stream("panel:open").subscribe(({ panel, scrollToAnnotationId, motivation }) => {
|
|
2262
|
-
if (scrollToAnnotationId) {
|
|
2263
|
-
scrollToAnnotationId$.next(scrollToAnnotationId);
|
|
2264
|
-
}
|
|
2265
|
-
if (motivation) {
|
|
2266
|
-
const tab = MOTIVATION_TO_TAB[motivation] || "highlight";
|
|
2267
|
-
panelInitialTab$.next({ tab, generation: ++tabGenerationCounter });
|
|
2268
|
-
}
|
|
2269
|
-
activePanel$.next(panel);
|
|
2270
|
-
}));
|
|
2271
|
-
subs.push(browser.stream("panel:close").subscribe(() => {
|
|
2272
|
-
activePanel$.next(null);
|
|
2273
|
-
}));
|
|
2274
|
-
return {
|
|
2275
|
-
activePanel$: activePanel$.asObservable(),
|
|
2276
|
-
scrollToAnnotationId$: scrollToAnnotationId$.asObservable(),
|
|
2277
|
-
panelInitialTab$: panelInitialTab$.asObservable(),
|
|
2278
|
-
openPanel: (panel) => browser.emit("panel:open", { panel }),
|
|
2279
|
-
closePanel: () => browser.emit("panel:close", void 0),
|
|
2280
|
-
togglePanel: (panel) => browser.emit("panel:toggle", { panel }),
|
|
2281
|
-
onScrollCompleted: () => scrollToAnnotationId$.next(null),
|
|
2282
|
-
dispose() {
|
|
2283
|
-
subs.forEach((s) => s.unsubscribe());
|
|
2284
|
-
activePanel$.complete();
|
|
2285
|
-
scrollToAnnotationId$.complete();
|
|
2286
|
-
panelInitialTab$.complete();
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
function createGatherVM(client, resourceId2) {
|
|
2370
|
+
function createGatherStateUnit(client, resourceId2) {
|
|
2291
2371
|
const subs = [];
|
|
2292
2372
|
const context$ = new BehaviorSubject(null);
|
|
2293
2373
|
const loading$ = new BehaviorSubject(false);
|
|
@@ -2299,8 +2379,8 @@ function createGatherVM(client, resourceId2) {
|
|
|
2299
2379
|
context$.next(null);
|
|
2300
2380
|
annotationId$.next(annotationId(event.annotationId));
|
|
2301
2381
|
const gatherSub = client.gather.annotation(
|
|
2302
|
-
annotationId(event.annotationId),
|
|
2303
2382
|
resourceId2,
|
|
2383
|
+
annotationId(event.annotationId),
|
|
2304
2384
|
{ contextWindow: event.options?.contextWindow ?? 2e3 }
|
|
2305
2385
|
).pipe(
|
|
2306
2386
|
timeout(6e4)
|
|
@@ -2337,7 +2417,7 @@ function createGatherVM(client, resourceId2) {
|
|
|
2337
2417
|
}
|
|
2338
2418
|
};
|
|
2339
2419
|
}
|
|
2340
|
-
function
|
|
2420
|
+
function createMatchStateUnit(client, _resourceId) {
|
|
2341
2421
|
const subs = [];
|
|
2342
2422
|
subs.push(client.bus.get("match:search-requested").subscribe((event) => {
|
|
2343
2423
|
const searchSub = client.match.search(
|
|
@@ -2363,7 +2443,7 @@ function createMatchVM(client, _resourceId) {
|
|
|
2363
2443
|
}
|
|
2364
2444
|
};
|
|
2365
2445
|
}
|
|
2366
|
-
function
|
|
2446
|
+
function createYieldStateUnit(client, resourceId2, locale) {
|
|
2367
2447
|
const subs = [];
|
|
2368
2448
|
const isGenerating$ = new BehaviorSubject(false);
|
|
2369
2449
|
const progress$ = new BehaviorSubject(null);
|
|
@@ -2418,7 +2498,7 @@ function selectionToSelector(selection) {
|
|
|
2418
2498
|
}
|
|
2419
2499
|
return { type: "TextQuoteSelector", exact: selection.exact, ...selection.prefix && { prefix: selection.prefix }, ...selection.suffix && { suffix: selection.suffix } };
|
|
2420
2500
|
}
|
|
2421
|
-
function
|
|
2501
|
+
function createMarkStateUnit(client, resourceId2) {
|
|
2422
2502
|
const subs = [];
|
|
2423
2503
|
const pendingAnnotation$ = new BehaviorSubject(null);
|
|
2424
2504
|
const assistingMotivation$ = new BehaviorSubject(null);
|
|
@@ -2442,7 +2522,7 @@ function createMarkVM(client, resourceId2) {
|
|
|
2442
2522
|
subs.push(client.bus.get("mark:create-ok").subscribe(() => pendingAnnotation$.next(null)));
|
|
2443
2523
|
subs.push(client.bus.get("mark:submit").subscribe(async (event) => {
|
|
2444
2524
|
try {
|
|
2445
|
-
const result = await client.mark.annotation(
|
|
2525
|
+
const result = await client.mark.annotation({
|
|
2446
2526
|
motivation: event.motivation,
|
|
2447
2527
|
target: { source: resourceId2, selector: event.selector },
|
|
2448
2528
|
body: event.body
|
|
@@ -2503,720 +2583,7 @@ function createMarkVM(client, resourceId2) {
|
|
|
2503
2583
|
}
|
|
2504
2584
|
};
|
|
2505
2585
|
}
|
|
2506
|
-
var RECENT_LIMIT = 10;
|
|
2507
|
-
var SEARCH_LIMIT = 20;
|
|
2508
|
-
function createDiscoverVM(client, browse) {
|
|
2509
|
-
const disposer = createDisposer();
|
|
2510
|
-
const search = createSearchPipeline(
|
|
2511
|
-
(q) => client.browse.resources({ search: q, limit: SEARCH_LIMIT })
|
|
2512
|
-
);
|
|
2513
|
-
disposer.add(search);
|
|
2514
|
-
disposer.add(browse);
|
|
2515
|
-
const recent$ = client.browse.resources({ limit: RECENT_LIMIT, archived: false });
|
|
2516
|
-
const recentResources$ = recent$.pipe(
|
|
2517
|
-
map$1((r) => r ?? [])
|
|
2518
|
-
);
|
|
2519
|
-
const isLoadingRecent$ = recent$.pipe(
|
|
2520
|
-
map$1((r) => r === void 0)
|
|
2521
|
-
);
|
|
2522
|
-
const entityTypes$ = client.browse.entityTypes().pipe(
|
|
2523
|
-
map$1((e) => e ?? [])
|
|
2524
|
-
);
|
|
2525
|
-
return {
|
|
2526
|
-
browse,
|
|
2527
|
-
search,
|
|
2528
|
-
recentResources$,
|
|
2529
|
-
entityTypes$,
|
|
2530
|
-
isLoadingRecent$,
|
|
2531
|
-
dispose: () => disposer.dispose()
|
|
2532
|
-
};
|
|
2533
|
-
}
|
|
2534
|
-
function createEntityTagsVM(client, browse) {
|
|
2535
|
-
const disposer = createDisposer();
|
|
2536
|
-
disposer.add(browse);
|
|
2537
|
-
const newTag$ = new BehaviorSubject("");
|
|
2538
|
-
const error$ = new BehaviorSubject("");
|
|
2539
|
-
const isAdding$ = new BehaviorSubject(false);
|
|
2540
|
-
const raw$ = client.browse.entityTypes();
|
|
2541
|
-
const entityTypes$ = raw$.pipe(map$1((e) => e ?? []));
|
|
2542
|
-
const isLoading$ = raw$.pipe(map$1((e) => e === void 0));
|
|
2543
|
-
const addTag = async () => {
|
|
2544
|
-
const tag = newTag$.getValue().trim();
|
|
2545
|
-
if (!tag) return;
|
|
2546
|
-
error$.next("");
|
|
2547
|
-
isAdding$.next(true);
|
|
2548
|
-
try {
|
|
2549
|
-
await client.mark.entityType(tag);
|
|
2550
|
-
newTag$.next("");
|
|
2551
|
-
} catch (err) {
|
|
2552
|
-
error$.next(err instanceof Error ? err.message : "Failed to add entity type");
|
|
2553
|
-
} finally {
|
|
2554
|
-
isAdding$.next(false);
|
|
2555
|
-
}
|
|
2556
|
-
};
|
|
2557
|
-
return {
|
|
2558
|
-
browse,
|
|
2559
|
-
entityTypes$,
|
|
2560
|
-
isLoading$,
|
|
2561
|
-
newTag$: newTag$.asObservable(),
|
|
2562
|
-
error$: error$.asObservable(),
|
|
2563
|
-
isAdding$: isAdding$.asObservable(),
|
|
2564
|
-
setNewTag: (v) => newTag$.next(v),
|
|
2565
|
-
addTag,
|
|
2566
|
-
dispose: () => {
|
|
2567
|
-
newTag$.complete();
|
|
2568
|
-
error$.complete();
|
|
2569
|
-
isAdding$.complete();
|
|
2570
|
-
disposer.dispose();
|
|
2571
|
-
}
|
|
2572
|
-
};
|
|
2573
|
-
}
|
|
2574
|
-
function createExchangeVM(browse, exportFn, importFn) {
|
|
2575
|
-
const disposer = createDisposer();
|
|
2576
|
-
disposer.add(browse);
|
|
2577
|
-
const selectedFile$ = new BehaviorSubject(null);
|
|
2578
|
-
const preview$ = new BehaviorSubject(null);
|
|
2579
|
-
const importPhase$ = new BehaviorSubject(null);
|
|
2580
|
-
const importMessage$ = new BehaviorSubject(void 0);
|
|
2581
|
-
const importResult$ = new BehaviorSubject(void 0);
|
|
2582
|
-
const isExporting$ = new BehaviorSubject(false);
|
|
2583
|
-
const isImporting$ = new BehaviorSubject(false);
|
|
2584
|
-
const selectFile = (file) => {
|
|
2585
|
-
selectedFile$.next(file);
|
|
2586
|
-
importPhase$.next(null);
|
|
2587
|
-
importMessage$.next(void 0);
|
|
2588
|
-
importResult$.next(void 0);
|
|
2589
|
-
preview$.next({
|
|
2590
|
-
format: file.name.endsWith(".tar.gz") || file.name.endsWith(".gz") ? "semiont-linked-data" : "unknown",
|
|
2591
|
-
version: 1,
|
|
2592
|
-
sourceUrl: "",
|
|
2593
|
-
stats: {}
|
|
2594
|
-
});
|
|
2595
|
-
};
|
|
2596
|
-
const cancelImport = () => {
|
|
2597
|
-
selectedFile$.next(null);
|
|
2598
|
-
preview$.next(null);
|
|
2599
|
-
importPhase$.next(null);
|
|
2600
|
-
importMessage$.next(void 0);
|
|
2601
|
-
importResult$.next(void 0);
|
|
2602
|
-
};
|
|
2603
|
-
const doExport = async () => {
|
|
2604
|
-
isExporting$.next(true);
|
|
2605
|
-
try {
|
|
2606
|
-
const response = await exportFn();
|
|
2607
|
-
if (!response.ok) throw new Error(`Export failed: ${response.status} ${response.statusText}`);
|
|
2608
|
-
const blob = await response.blob();
|
|
2609
|
-
const contentDisposition = response.headers.get("Content-Disposition");
|
|
2610
|
-
const filename = contentDisposition?.match(/filename="(.+?)"/)?.[1] ?? `semiont-export-${Date.now()}.tar.gz`;
|
|
2611
|
-
return { blob, filename };
|
|
2612
|
-
} finally {
|
|
2613
|
-
isExporting$.next(false);
|
|
2614
|
-
}
|
|
2615
|
-
};
|
|
2616
|
-
const doImport = async () => {
|
|
2617
|
-
const file = selectedFile$.getValue();
|
|
2618
|
-
if (!file) return;
|
|
2619
|
-
isImporting$.next(true);
|
|
2620
|
-
importPhase$.next("started");
|
|
2621
|
-
importMessage$.next(void 0);
|
|
2622
|
-
importResult$.next(void 0);
|
|
2623
|
-
try {
|
|
2624
|
-
await importFn(file, {
|
|
2625
|
-
onProgress: (event) => {
|
|
2626
|
-
importPhase$.next(event.phase);
|
|
2627
|
-
importMessage$.next(event.message);
|
|
2628
|
-
if (event.result) importResult$.next(event.result);
|
|
2629
|
-
}
|
|
2630
|
-
});
|
|
2631
|
-
} finally {
|
|
2632
|
-
isImporting$.next(false);
|
|
2633
|
-
}
|
|
2634
|
-
};
|
|
2635
|
-
return {
|
|
2636
|
-
browse,
|
|
2637
|
-
selectedFile$: selectedFile$.asObservable(),
|
|
2638
|
-
preview$: preview$.asObservable(),
|
|
2639
|
-
importPhase$: importPhase$.asObservable(),
|
|
2640
|
-
importMessage$: importMessage$.asObservable(),
|
|
2641
|
-
importResult$: importResult$.asObservable(),
|
|
2642
|
-
isExporting$: isExporting$.asObservable(),
|
|
2643
|
-
isImporting$: isImporting$.asObservable(),
|
|
2644
|
-
selectFile,
|
|
2645
|
-
cancelImport,
|
|
2646
|
-
doExport,
|
|
2647
|
-
doImport,
|
|
2648
|
-
dispose: () => {
|
|
2649
|
-
selectedFile$.complete();
|
|
2650
|
-
preview$.complete();
|
|
2651
|
-
importPhase$.complete();
|
|
2652
|
-
importMessage$.complete();
|
|
2653
|
-
importResult$.complete();
|
|
2654
|
-
isExporting$.complete();
|
|
2655
|
-
isImporting$.complete();
|
|
2656
|
-
disposer.dispose();
|
|
2657
|
-
}
|
|
2658
|
-
};
|
|
2659
|
-
}
|
|
2660
|
-
function createAdminUsersVM(client, browse) {
|
|
2661
|
-
const disposer = createDisposer();
|
|
2662
|
-
disposer.add(browse);
|
|
2663
|
-
const users$ = new BehaviorSubject([]);
|
|
2664
|
-
const stats$ = new BehaviorSubject(null);
|
|
2665
|
-
const usersLoading$ = new BehaviorSubject(true);
|
|
2666
|
-
const statsLoading$ = new BehaviorSubject(true);
|
|
2667
|
-
const fetchUsers = () => {
|
|
2668
|
-
usersLoading$.next(true);
|
|
2669
|
-
client.admin.users().then((data) => {
|
|
2670
|
-
users$.next(data.users ?? []);
|
|
2671
|
-
usersLoading$.next(false);
|
|
2672
|
-
}).catch(() => usersLoading$.next(false));
|
|
2673
|
-
};
|
|
2674
|
-
const fetchStats = () => {
|
|
2675
|
-
statsLoading$.next(true);
|
|
2676
|
-
client.admin.userStats().then((data) => {
|
|
2677
|
-
stats$.next(data.stats ?? null);
|
|
2678
|
-
statsLoading$.next(false);
|
|
2679
|
-
}).catch(() => statsLoading$.next(false));
|
|
2680
|
-
};
|
|
2681
|
-
fetchUsers();
|
|
2682
|
-
fetchStats();
|
|
2683
|
-
const updateUser = async (id, data) => {
|
|
2684
|
-
await client.admin.updateUser(userDID(id), data);
|
|
2685
|
-
fetchUsers();
|
|
2686
|
-
fetchStats();
|
|
2687
|
-
};
|
|
2688
|
-
return {
|
|
2689
|
-
browse,
|
|
2690
|
-
users$: users$.asObservable(),
|
|
2691
|
-
stats$: stats$.asObservable(),
|
|
2692
|
-
usersLoading$: usersLoading$.asObservable(),
|
|
2693
|
-
statsLoading$: statsLoading$.asObservable(),
|
|
2694
|
-
updateUser,
|
|
2695
|
-
dispose: () => {
|
|
2696
|
-
users$.complete();
|
|
2697
|
-
stats$.complete();
|
|
2698
|
-
usersLoading$.complete();
|
|
2699
|
-
statsLoading$.complete();
|
|
2700
|
-
disposer.dispose();
|
|
2701
|
-
}
|
|
2702
|
-
};
|
|
2703
|
-
}
|
|
2704
|
-
function createAdminSecurityVM(client, browse) {
|
|
2705
|
-
const disposer = createDisposer();
|
|
2706
|
-
disposer.add(browse);
|
|
2707
|
-
const providers$ = new BehaviorSubject([]);
|
|
2708
|
-
const allowedDomains$ = new BehaviorSubject([]);
|
|
2709
|
-
const isLoading$ = new BehaviorSubject(true);
|
|
2710
|
-
client.admin.oauthConfig().then((data) => {
|
|
2711
|
-
const config = data;
|
|
2712
|
-
providers$.next(config.providers ?? []);
|
|
2713
|
-
allowedDomains$.next(config.allowedDomains ?? []);
|
|
2714
|
-
isLoading$.next(false);
|
|
2715
|
-
}).catch(() => isLoading$.next(false));
|
|
2716
|
-
return {
|
|
2717
|
-
browse,
|
|
2718
|
-
providers$: providers$.asObservable(),
|
|
2719
|
-
allowedDomains$: allowedDomains$.asObservable(),
|
|
2720
|
-
isLoading$: isLoading$.asObservable(),
|
|
2721
|
-
dispose: () => {
|
|
2722
|
-
providers$.complete();
|
|
2723
|
-
allowedDomains$.complete();
|
|
2724
|
-
isLoading$.complete();
|
|
2725
|
-
disposer.dispose();
|
|
2726
|
-
}
|
|
2727
|
-
};
|
|
2728
|
-
}
|
|
2729
|
-
function createWelcomeVM(client) {
|
|
2730
|
-
const disposer = createDisposer();
|
|
2731
|
-
const userData$ = new BehaviorSubject(null);
|
|
2732
|
-
const isProcessing$ = new BehaviorSubject(false);
|
|
2733
|
-
client.auth.me().then((data) => userData$.next(data)).catch(() => {
|
|
2734
|
-
});
|
|
2735
|
-
const acceptTerms = async () => {
|
|
2736
|
-
isProcessing$.next(true);
|
|
2737
|
-
try {
|
|
2738
|
-
await client.auth.acceptTerms();
|
|
2739
|
-
userData$.next({ ...userData$.getValue(), termsAcceptedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2740
|
-
} finally {
|
|
2741
|
-
isProcessing$.next(false);
|
|
2742
|
-
}
|
|
2743
|
-
};
|
|
2744
|
-
return {
|
|
2745
|
-
userData$: userData$.asObservable(),
|
|
2746
|
-
isProcessing$: isProcessing$.asObservable(),
|
|
2747
|
-
acceptTerms,
|
|
2748
|
-
dispose: () => {
|
|
2749
|
-
userData$.complete();
|
|
2750
|
-
isProcessing$.complete();
|
|
2751
|
-
disposer.dispose();
|
|
2752
|
-
}
|
|
2753
|
-
};
|
|
2754
|
-
}
|
|
2755
|
-
function createResourceLoaderVM(client, resourceId2) {
|
|
2756
|
-
const raw$ = client.browse.resource(resourceId2);
|
|
2757
|
-
const resource$ = raw$;
|
|
2758
|
-
const isLoading$ = raw$.pipe(map$1((r) => r === void 0));
|
|
2759
|
-
return {
|
|
2760
|
-
resource$,
|
|
2761
|
-
isLoading$,
|
|
2762
|
-
invalidate: () => client.browse.invalidateResourceDetail(resourceId2),
|
|
2763
|
-
dispose: () => {
|
|
2764
|
-
}
|
|
2765
|
-
};
|
|
2766
|
-
}
|
|
2767
|
-
function createSessionVM(client) {
|
|
2768
|
-
const isLoggingOut$ = new BehaviorSubject(false);
|
|
2769
|
-
const logout = async () => {
|
|
2770
|
-
isLoggingOut$.next(true);
|
|
2771
|
-
try {
|
|
2772
|
-
await client.auth.logout();
|
|
2773
|
-
} catch {
|
|
2774
|
-
} finally {
|
|
2775
|
-
isLoggingOut$.next(false);
|
|
2776
|
-
}
|
|
2777
|
-
};
|
|
2778
|
-
return {
|
|
2779
|
-
isLoggingOut$: isLoggingOut$.asObservable(),
|
|
2780
|
-
logout,
|
|
2781
|
-
dispose: () => {
|
|
2782
|
-
isLoggingOut$.complete();
|
|
2783
|
-
}
|
|
2784
|
-
};
|
|
2785
|
-
}
|
|
2786
|
-
var SMELTER_CHANNELS = [
|
|
2787
|
-
"yield:created",
|
|
2788
|
-
"yield:updated",
|
|
2789
|
-
"yield:representation-added",
|
|
2790
|
-
"mark:archived",
|
|
2791
|
-
"mark:added",
|
|
2792
|
-
"mark:removed"
|
|
2793
|
-
];
|
|
2794
|
-
function createSmelterActorVM(options) {
|
|
2795
|
-
const actor = createActorVM({
|
|
2796
|
-
baseUrl: options.baseUrl,
|
|
2797
|
-
token: options.token,
|
|
2798
|
-
channels: [...SMELTER_CHANNELS],
|
|
2799
|
-
reconnectMs: options.reconnectMs
|
|
2800
|
-
});
|
|
2801
|
-
const events$ = merge(
|
|
2802
|
-
...SMELTER_CHANNELS.map(
|
|
2803
|
-
(channel) => actor.on$(channel).pipe(
|
|
2804
|
-
map((payload) => ({
|
|
2805
|
-
type: channel,
|
|
2806
|
-
resourceId: payload.resourceId,
|
|
2807
|
-
payload
|
|
2808
|
-
}))
|
|
2809
|
-
)
|
|
2810
|
-
)
|
|
2811
|
-
);
|
|
2812
|
-
return {
|
|
2813
|
-
events$,
|
|
2814
|
-
state$: actor.state$,
|
|
2815
|
-
emit: (channel, payload) => actor.emit(channel, payload),
|
|
2816
|
-
start: () => actor.start(),
|
|
2817
|
-
stop: () => actor.stop(),
|
|
2818
|
-
dispose: () => actor.dispose()
|
|
2819
|
-
};
|
|
2820
|
-
}
|
|
2821
|
-
function createJobClaimAdapter(options) {
|
|
2822
|
-
const { actor, jobTypes } = options;
|
|
2823
|
-
const activeJob$ = new BehaviorSubject(null);
|
|
2824
|
-
const isProcessing$ = new BehaviorSubject(false);
|
|
2825
|
-
const jobsCompleted$ = new BehaviorSubject(0);
|
|
2826
|
-
const errors$ = new Subject();
|
|
2827
|
-
let jobSubscription = null;
|
|
2828
|
-
let started = false;
|
|
2829
|
-
const claimJob = async (assignment) => {
|
|
2830
|
-
try {
|
|
2831
|
-
const correlationId = crypto.randomUUID();
|
|
2832
|
-
const result$ = merge(
|
|
2833
|
-
actor.on$("job:claimed").pipe(
|
|
2834
|
-
filter$1((e) => e.correlationId === correlationId),
|
|
2835
|
-
map$1((e) => ({ ok: true, response: e.response }))
|
|
2836
|
-
),
|
|
2837
|
-
actor.on$("job:claim-failed").pipe(
|
|
2838
|
-
filter$1((e) => e.correlationId === correlationId),
|
|
2839
|
-
map$1(() => ({ ok: false }))
|
|
2840
|
-
)
|
|
2841
|
-
).pipe(take$1(1), timeout$1(1e4));
|
|
2842
|
-
const resultPromise = firstValueFrom(result$);
|
|
2843
|
-
await actor.emit("job:claim", { correlationId, jobId: assignment.jobId });
|
|
2844
|
-
const result = await resultPromise;
|
|
2845
|
-
if (!result.ok) return null;
|
|
2846
|
-
const job = result.response;
|
|
2847
|
-
return {
|
|
2848
|
-
jobId: assignment.jobId,
|
|
2849
|
-
type: assignment.type,
|
|
2850
|
-
resourceId: assignment.resourceId,
|
|
2851
|
-
userId: job.metadata?.userId ?? "",
|
|
2852
|
-
params: job.params ?? {}
|
|
2853
|
-
};
|
|
2854
|
-
} catch {
|
|
2855
|
-
return null;
|
|
2856
|
-
}
|
|
2857
|
-
};
|
|
2858
|
-
return {
|
|
2859
|
-
activeJob$: activeJob$.asObservable(),
|
|
2860
|
-
isProcessing$: isProcessing$.asObservable(),
|
|
2861
|
-
jobsCompleted$: jobsCompleted$.asObservable(),
|
|
2862
|
-
errors$: errors$.asObservable(),
|
|
2863
|
-
start: () => {
|
|
2864
|
-
if (started) return;
|
|
2865
|
-
started = true;
|
|
2866
|
-
actor.addChannels(["job:queued"]);
|
|
2867
|
-
jobSubscription = actor.on$("job:queued").subscribe((event) => {
|
|
2868
|
-
const jobType = event.jobType;
|
|
2869
|
-
if (jobTypes.length > 0 && !jobTypes.includes(jobType)) return;
|
|
2870
|
-
if (isProcessing$.getValue()) return;
|
|
2871
|
-
isProcessing$.next(true);
|
|
2872
|
-
claimJob({ jobId: event.jobId, type: jobType, resourceId: event.resourceId }).then((job) => {
|
|
2873
|
-
if (job) {
|
|
2874
|
-
activeJob$.next(job);
|
|
2875
|
-
} else {
|
|
2876
|
-
isProcessing$.next(false);
|
|
2877
|
-
}
|
|
2878
|
-
}).catch(() => {
|
|
2879
|
-
isProcessing$.next(false);
|
|
2880
|
-
});
|
|
2881
|
-
});
|
|
2882
|
-
},
|
|
2883
|
-
stop: () => {
|
|
2884
|
-
jobSubscription?.unsubscribe();
|
|
2885
|
-
jobSubscription = null;
|
|
2886
|
-
started = false;
|
|
2887
|
-
},
|
|
2888
|
-
completeJob: () => {
|
|
2889
|
-
activeJob$.next(null);
|
|
2890
|
-
isProcessing$.next(false);
|
|
2891
|
-
jobsCompleted$.next(jobsCompleted$.getValue() + 1);
|
|
2892
|
-
},
|
|
2893
|
-
failJob: (jid, error) => {
|
|
2894
|
-
activeJob$.next(null);
|
|
2895
|
-
isProcessing$.next(false);
|
|
2896
|
-
errors$.next({ jobId: jid, error });
|
|
2897
|
-
},
|
|
2898
|
-
dispose: () => {
|
|
2899
|
-
jobSubscription?.unsubscribe();
|
|
2900
|
-
jobSubscription = null;
|
|
2901
|
-
started = false;
|
|
2902
|
-
activeJob$.complete();
|
|
2903
|
-
isProcessing$.complete();
|
|
2904
|
-
jobsCompleted$.complete();
|
|
2905
|
-
errors$.complete();
|
|
2906
|
-
}
|
|
2907
|
-
};
|
|
2908
|
-
}
|
|
2909
|
-
function createJobQueueVM(client) {
|
|
2910
|
-
const jobs$ = new BehaviorSubject([]);
|
|
2911
|
-
const jobCreated$ = new Subject();
|
|
2912
|
-
const jobCompleted$ = new Subject();
|
|
2913
|
-
const jobFailed$ = new Subject();
|
|
2914
|
-
const pendingByType$ = jobs$.pipe(
|
|
2915
|
-
map$1((all) => {
|
|
2916
|
-
const counts = /* @__PURE__ */ new Map();
|
|
2917
|
-
for (const j of all) {
|
|
2918
|
-
if (j.status === "pending") {
|
|
2919
|
-
counts.set(j.type, (counts.get(j.type) ?? 0) + 1);
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
return counts;
|
|
2923
|
-
})
|
|
2924
|
-
);
|
|
2925
|
-
const runningJobs$ = jobs$.pipe(
|
|
2926
|
-
map$1((all) => all.filter((j) => j.status === "running"))
|
|
2927
|
-
);
|
|
2928
|
-
const addOrUpdate = (job) => {
|
|
2929
|
-
const current = jobs$.getValue();
|
|
2930
|
-
const idx = current.findIndex((j) => j.jobId === job.jobId);
|
|
2931
|
-
if (idx >= 0) {
|
|
2932
|
-
const next = [...current];
|
|
2933
|
-
next[idx] = job;
|
|
2934
|
-
jobs$.next(next);
|
|
2935
|
-
} else {
|
|
2936
|
-
jobs$.next([...current, job]);
|
|
2937
|
-
}
|
|
2938
|
-
};
|
|
2939
|
-
const subs = [
|
|
2940
|
-
client.bus.get("job:queued").subscribe((event) => {
|
|
2941
|
-
const job = {
|
|
2942
|
-
jobId: event.jobId,
|
|
2943
|
-
type: event.jobType,
|
|
2944
|
-
status: "pending",
|
|
2945
|
-
resourceId: event.resourceId,
|
|
2946
|
-
userId: event.userId,
|
|
2947
|
-
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
2948
|
-
};
|
|
2949
|
-
addOrUpdate(job);
|
|
2950
|
-
jobCreated$.next(job);
|
|
2951
|
-
}),
|
|
2952
|
-
client.bus.get("job:complete").subscribe((event) => {
|
|
2953
|
-
if (!event._userId) {
|
|
2954
|
-
throw new Error("job:complete missing _userId (gateway injection)");
|
|
2955
|
-
}
|
|
2956
|
-
const job = {
|
|
2957
|
-
jobId: event.jobId,
|
|
2958
|
-
type: event.jobType,
|
|
2959
|
-
status: "complete",
|
|
2960
|
-
resourceId: event.resourceId,
|
|
2961
|
-
userId: event._userId,
|
|
2962
|
-
created: "",
|
|
2963
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2964
|
-
result: event.result
|
|
2965
|
-
};
|
|
2966
|
-
addOrUpdate(job);
|
|
2967
|
-
jobCompleted$.next(job);
|
|
2968
|
-
}),
|
|
2969
|
-
client.bus.get("job:fail").subscribe((event) => {
|
|
2970
|
-
if (!event._userId) {
|
|
2971
|
-
throw new Error("job:fail missing _userId (gateway injection)");
|
|
2972
|
-
}
|
|
2973
|
-
const job = {
|
|
2974
|
-
jobId: event.jobId,
|
|
2975
|
-
type: event.jobType,
|
|
2976
|
-
status: "failed",
|
|
2977
|
-
resourceId: event.resourceId,
|
|
2978
|
-
userId: event._userId,
|
|
2979
|
-
created: "",
|
|
2980
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2981
|
-
error: event.error
|
|
2982
|
-
};
|
|
2983
|
-
addOrUpdate(job);
|
|
2984
|
-
jobFailed$.next(job);
|
|
2985
|
-
})
|
|
2986
|
-
];
|
|
2987
|
-
return {
|
|
2988
|
-
jobs$: jobs$.asObservable(),
|
|
2989
|
-
pendingByType$,
|
|
2990
|
-
runningJobs$,
|
|
2991
|
-
jobCreated$: jobCreated$.asObservable(),
|
|
2992
|
-
jobCompleted$: jobCompleted$.asObservable(),
|
|
2993
|
-
jobFailed$: jobFailed$.asObservable(),
|
|
2994
|
-
dispose: () => {
|
|
2995
|
-
subs.forEach((s) => s.unsubscribe());
|
|
2996
|
-
jobs$.complete();
|
|
2997
|
-
jobCreated$.complete();
|
|
2998
|
-
jobCompleted$.complete();
|
|
2999
|
-
jobFailed$.complete();
|
|
3000
|
-
}
|
|
3001
|
-
};
|
|
3002
|
-
}
|
|
3003
|
-
var WIZARD_CLOSED = {
|
|
3004
|
-
open: false,
|
|
3005
|
-
annotationId: null,
|
|
3006
|
-
resourceId: null,
|
|
3007
|
-
defaultTitle: "",
|
|
3008
|
-
entityTypes: []
|
|
3009
|
-
};
|
|
3010
|
-
function createResourceViewerPageVM(client, resourceId2, locale, browse, options) {
|
|
3011
|
-
const disposer = createDisposer();
|
|
3012
|
-
const beckon = createBeckonVM(client);
|
|
3013
|
-
const mark = createMarkVM(client, resourceId2);
|
|
3014
|
-
const gather = createGatherVM(client, resourceId2);
|
|
3015
|
-
const matchVM = createMatchVM(client);
|
|
3016
|
-
const yieldVM = createYieldVM(client, resourceId2, locale);
|
|
3017
|
-
disposer.add(beckon);
|
|
3018
|
-
disposer.add(browse);
|
|
3019
|
-
disposer.add(mark);
|
|
3020
|
-
disposer.add(gather);
|
|
3021
|
-
disposer.add(matchVM);
|
|
3022
|
-
disposer.add(yieldVM);
|
|
3023
|
-
const annotations$ = client.browse.annotations(resourceId2).pipe(
|
|
3024
|
-
map$1((a) => a ?? [])
|
|
3025
|
-
);
|
|
3026
|
-
const annotationGroups$ = annotations$.pipe(
|
|
3027
|
-
map$1((anns) => {
|
|
3028
|
-
const groups = { highlights: [], comments: [], assessments: [], references: [], tags: [] };
|
|
3029
|
-
for (const ann of anns) {
|
|
3030
|
-
if (isHighlight(ann)) groups.highlights.push(ann);
|
|
3031
|
-
else if (isComment(ann)) groups.comments.push(ann);
|
|
3032
|
-
else if (isAssessment(ann)) groups.assessments.push(ann);
|
|
3033
|
-
else if (isReference(ann)) groups.references.push(ann);
|
|
3034
|
-
else if (isTag(ann)) groups.tags.push(ann);
|
|
3035
|
-
}
|
|
3036
|
-
return groups;
|
|
3037
|
-
})
|
|
3038
|
-
);
|
|
3039
|
-
const entityTypes$ = client.browse.entityTypes().pipe(
|
|
3040
|
-
map$1((e) => e ?? [])
|
|
3041
|
-
);
|
|
3042
|
-
const events$ = client.browse.events(resourceId2).pipe(
|
|
3043
|
-
map$1((e) => e ?? [])
|
|
3044
|
-
);
|
|
3045
|
-
const referencedBy$ = client.browse.referencedBy(resourceId2).pipe(
|
|
3046
|
-
map$1((r) => r ?? [])
|
|
3047
|
-
);
|
|
3048
|
-
const content$ = new BehaviorSubject("");
|
|
3049
|
-
const contentLoading$ = new BehaviorSubject(false);
|
|
3050
|
-
const mediaToken$ = new BehaviorSubject(null);
|
|
3051
|
-
const mediaType = options?.mediaType || "text/plain";
|
|
3052
|
-
const isBinaryType = mediaType.startsWith("image/") || mediaType === "application/pdf";
|
|
3053
|
-
if (!isBinaryType && mediaType) {
|
|
3054
|
-
contentLoading$.next(true);
|
|
3055
|
-
client.browse.resourceRepresentation(resourceId2, { accept: mediaType }).then(({ data }) => {
|
|
3056
|
-
content$.next(decodeWithCharset(data, mediaType));
|
|
3057
|
-
contentLoading$.next(false);
|
|
3058
|
-
}).catch(() => {
|
|
3059
|
-
contentLoading$.next(false);
|
|
3060
|
-
});
|
|
3061
|
-
}
|
|
3062
|
-
if (isBinaryType) {
|
|
3063
|
-
client.auth.mediaToken(resourceId2).then(({ token }) => mediaToken$.next(token)).catch(() => {
|
|
3064
|
-
});
|
|
3065
|
-
}
|
|
3066
|
-
const wizard$ = new BehaviorSubject(WIZARD_CLOSED);
|
|
3067
|
-
const unsubscribeResource = client.subscribeToResource(resourceId2);
|
|
3068
|
-
disposer.add(unsubscribeResource);
|
|
3069
|
-
const bindInitiateSub = client.bus.get("bind:initiate").subscribe((event) => {
|
|
3070
|
-
wizard$.next({
|
|
3071
|
-
open: true,
|
|
3072
|
-
annotationId: event.annotationId,
|
|
3073
|
-
resourceId: event.resourceId,
|
|
3074
|
-
defaultTitle: event.defaultTitle,
|
|
3075
|
-
entityTypes: event.entityTypes
|
|
3076
|
-
});
|
|
3077
|
-
client.bus.get("gather:requested").next({
|
|
3078
|
-
correlationId: crypto.randomUUID(),
|
|
3079
|
-
annotationId: event.annotationId,
|
|
3080
|
-
resourceId: event.resourceId,
|
|
3081
|
-
options: { contextWindow: 2e3 }
|
|
3082
|
-
});
|
|
3083
|
-
});
|
|
3084
|
-
disposer.add(() => bindInitiateSub.unsubscribe());
|
|
3085
|
-
return {
|
|
3086
|
-
beckon,
|
|
3087
|
-
browse,
|
|
3088
|
-
mark,
|
|
3089
|
-
gather,
|
|
3090
|
-
yield: yieldVM,
|
|
3091
|
-
annotations$,
|
|
3092
|
-
annotationGroups$,
|
|
3093
|
-
entityTypes$,
|
|
3094
|
-
events$,
|
|
3095
|
-
referencedBy$,
|
|
3096
|
-
content$: content$.asObservable(),
|
|
3097
|
-
contentLoading$: contentLoading$.asObservable(),
|
|
3098
|
-
mediaToken$: mediaToken$.asObservable(),
|
|
3099
|
-
wizard$: wizard$.asObservable(),
|
|
3100
|
-
closeWizard: () => wizard$.next(WIZARD_CLOSED),
|
|
3101
|
-
dispose: () => {
|
|
3102
|
-
wizard$.complete();
|
|
3103
|
-
content$.complete();
|
|
3104
|
-
contentLoading$.complete();
|
|
3105
|
-
mediaToken$.complete();
|
|
3106
|
-
disposer.dispose();
|
|
3107
|
-
}
|
|
3108
|
-
};
|
|
3109
|
-
}
|
|
3110
|
-
function createComposePageVM(client, browse, params, auth) {
|
|
3111
|
-
const disposer = createDisposer();
|
|
3112
|
-
disposer.add(browse);
|
|
3113
|
-
const isReferenceMode = Boolean(params.annotationUri && params.sourceDocumentId && params.name);
|
|
3114
|
-
const isCloneMode = params.mode === "clone" && Boolean(params.token);
|
|
3115
|
-
const pageMode = isCloneMode ? "clone" : isReferenceMode ? "reference" : "new";
|
|
3116
|
-
const mode$ = new BehaviorSubject(pageMode);
|
|
3117
|
-
const loading$ = new BehaviorSubject(true);
|
|
3118
|
-
const cloneData$ = new BehaviorSubject(null);
|
|
3119
|
-
const referenceData$ = new BehaviorSubject(null);
|
|
3120
|
-
const gatheredContext$ = new BehaviorSubject(null);
|
|
3121
|
-
const entityTypes$ = client.browse.entityTypes().pipe(
|
|
3122
|
-
map$1((e) => e ?? [])
|
|
3123
|
-
);
|
|
3124
|
-
if (isReferenceMode) {
|
|
3125
|
-
const entityTypes = params.entityTypes ? params.entityTypes.split(",") : [];
|
|
3126
|
-
referenceData$.next({
|
|
3127
|
-
annotationUri: params.annotationUri,
|
|
3128
|
-
sourceDocumentId: params.sourceDocumentId,
|
|
3129
|
-
name: params.name,
|
|
3130
|
-
entityTypes
|
|
3131
|
-
});
|
|
3132
|
-
if (params.storedContext) {
|
|
3133
|
-
try {
|
|
3134
|
-
gatheredContext$.next(JSON.parse(params.storedContext));
|
|
3135
|
-
} catch {
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
loading$.next(false);
|
|
3139
|
-
} else if (isCloneMode) {
|
|
3140
|
-
void (async () => {
|
|
3141
|
-
try {
|
|
3142
|
-
const tokenResult = await client.yield.fromToken(params.token);
|
|
3143
|
-
if (tokenResult && auth) {
|
|
3144
|
-
const rId = resourceId(tokenResult["@id"]);
|
|
3145
|
-
const mediaType = getPrimaryMediaType(tokenResult) || "text/plain";
|
|
3146
|
-
const { data } = await client.browse.resourceRepresentation(rId, {
|
|
3147
|
-
accept: mediaType
|
|
3148
|
-
});
|
|
3149
|
-
const content = decodeWithCharset(data, mediaType);
|
|
3150
|
-
cloneData$.next({ sourceResource: tokenResult, sourceContent: content });
|
|
3151
|
-
}
|
|
3152
|
-
} catch {
|
|
3153
|
-
}
|
|
3154
|
-
loading$.next(false);
|
|
3155
|
-
})();
|
|
3156
|
-
} else {
|
|
3157
|
-
loading$.next(false);
|
|
3158
|
-
}
|
|
3159
|
-
const save = async (saveParams) => {
|
|
3160
|
-
if (saveParams.mode === "clone") {
|
|
3161
|
-
const response2 = await client.yield.createFromToken({
|
|
3162
|
-
token: params.token,
|
|
3163
|
-
name: saveParams.name,
|
|
3164
|
-
content: saveParams.content,
|
|
3165
|
-
archiveOriginal: saveParams.archiveOriginal ?? true
|
|
3166
|
-
});
|
|
3167
|
-
return response2.resourceId;
|
|
3168
|
-
}
|
|
3169
|
-
let fileToUpload;
|
|
3170
|
-
let mimeType;
|
|
3171
|
-
if (saveParams.file) {
|
|
3172
|
-
fileToUpload = saveParams.file;
|
|
3173
|
-
mimeType = saveParams.format ?? "application/octet-stream";
|
|
3174
|
-
} else {
|
|
3175
|
-
const blob = new Blob([saveParams.content || ""], { type: saveParams.format ?? "application/octet-stream" });
|
|
3176
|
-
const extension = saveParams.format === "text/plain" ? ".txt" : saveParams.format === "text/html" ? ".html" : ".md";
|
|
3177
|
-
fileToUpload = new File([blob], saveParams.name + extension, { type: saveParams.format ?? "application/octet-stream" });
|
|
3178
|
-
mimeType = saveParams.format ?? "application/octet-stream";
|
|
3179
|
-
}
|
|
3180
|
-
const format = saveParams.charset && !saveParams.file ? `${mimeType}; charset=${saveParams.charset}` : mimeType;
|
|
3181
|
-
const response = await client.yield.resource({
|
|
3182
|
-
name: saveParams.name,
|
|
3183
|
-
file: fileToUpload,
|
|
3184
|
-
format,
|
|
3185
|
-
entityTypes: saveParams.entityTypes || [],
|
|
3186
|
-
language: saveParams.language,
|
|
3187
|
-
creationMethod: "ui",
|
|
3188
|
-
storageUri: saveParams.storageUri
|
|
3189
|
-
});
|
|
3190
|
-
const newResourceId = response.resourceId;
|
|
3191
|
-
if (saveParams.mode === "reference" && saveParams.annotationUri && saveParams.sourceDocumentId) {
|
|
3192
|
-
await client.bind.body(
|
|
3193
|
-
resourceId(saveParams.sourceDocumentId),
|
|
3194
|
-
annotationId(saveParams.annotationUri),
|
|
3195
|
-
[{ op: "add", item: { type: "SpecificResource", source: newResourceId, purpose: "linking" } }]
|
|
3196
|
-
);
|
|
3197
|
-
}
|
|
3198
|
-
return newResourceId;
|
|
3199
|
-
};
|
|
3200
|
-
return {
|
|
3201
|
-
browse,
|
|
3202
|
-
mode$: mode$.asObservable(),
|
|
3203
|
-
loading$: loading$.asObservable(),
|
|
3204
|
-
cloneData$: cloneData$.asObservable(),
|
|
3205
|
-
referenceData$: referenceData$.asObservable(),
|
|
3206
|
-
gatheredContext$: gatheredContext$.asObservable(),
|
|
3207
|
-
entityTypes$,
|
|
3208
|
-
save,
|
|
3209
|
-
dispose: () => {
|
|
3210
|
-
mode$.complete();
|
|
3211
|
-
loading$.complete();
|
|
3212
|
-
cloneData$.complete();
|
|
3213
|
-
referenceData$.complete();
|
|
3214
|
-
gatheredContext$.complete();
|
|
3215
|
-
disposer.dispose();
|
|
3216
|
-
}
|
|
3217
|
-
};
|
|
3218
|
-
}
|
|
3219
2586
|
|
|
3220
|
-
export { AdminNamespace, AuthNamespace, BeckonNamespace, BindNamespace, BrowseNamespace, BusRequestError,
|
|
2587
|
+
export { AdminNamespace, AuthNamespace, BeckonNamespace, BindNamespace, BrowseNamespace, BusRequestError, CacheObservable, FrameNamespace, GatherNamespace, HOVER_DELAY_MS, InMemorySessionStorage, JobNamespace, MarkNamespace, MatchNamespace, SemiontBrowser, SemiontClient, SemiontSession, SemiontSessionError, SessionSignals, StreamObservable, UploadObservable, YieldNamespace, busRequest, createBeckonStateUnit, createDisposer, createGatherStateUnit, createHoverHandlers, createHttpSessionFactory, createMarkStateUnit, createMatchStateUnit, createSearchPipeline, createYieldStateUnit, defaultProtocol, getBrowser, httpKb, isValidHostname, kbBackendUrl, setStoredSession };
|
|
3221
2588
|
//# sourceMappingURL=index.js.map
|
|
3222
2589
|
//# sourceMappingURL=index.js.map
|