@semiont/sdk 0.5.1 → 0.5.2

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/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { SemiontError, annotationId, resourceId, email, googleCredential, refreshToken, EventBus, baseUrl, accessToken, isHighlight, isComment, isAssessment, isReference, isTag, decodeWithCharset, getPrimaryMediaType, userDID, searchQuery } from '@semiont/core';
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, BehaviorSubject, map as map$1, distinctUntilChanged, Subject, Subscription, of, filter as filter$1, take as take$1, timeout as timeout$1 } from 'rxjs';
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 as distinctUntilChanged$1, switchMap } from 'rxjs/operators';
6
- import { HttpTransport, HttpContentTransport, createActorVM, APIError } from '@semiont/api-client';
7
- export { APIError, DEGRADED_THRESHOLD_MS, HttpContentTransport, HttpTransport, createActorVM } from '@semiont/api-client';
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 React-side consumers depend on.
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
  }
@@ -499,7 +510,7 @@ var BrowseNamespace = class {
499
510
  this.on("yield:update-ok", this.onYieldResourceMutated);
500
511
  this.on("mark:archived", this.onArchiveToggled);
501
512
  this.on("mark:unarchived", this.onArchiveToggled);
502
- this.on("mark:entity-type-added", () => this.invalidateEntityTypes());
513
+ this.on("frame:entity-type-added", () => this.invalidateEntityTypes());
503
514
  }
504
515
  };
505
516
  var MarkNamespace = class {
@@ -507,7 +518,8 @@ var MarkNamespace = class {
507
518
  this.transport = transport;
508
519
  this.bus = bus;
509
520
  }
510
- async annotation(resourceId2, input) {
521
+ async annotation(input) {
522
+ const resourceId2 = resourceId(input.target.source);
511
523
  const result = await busRequest(
512
524
  this.transport,
513
525
  "mark:create-request",
@@ -520,14 +532,6 @@ var MarkNamespace = class {
520
532
  async delete(resourceId2, annotationId2) {
521
533
  await this.transport.emit("mark:delete", { annotationId: annotationId2, resourceId: resourceId2 });
522
534
  }
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
535
  async archive(resourceId2) {
532
536
  await this.transport.emit("mark:archive", { resourceId: resourceId2 });
533
537
  }
@@ -682,6 +686,7 @@ var MarkNamespace = class {
682
686
  if (options.density !== void 0) params.density = options.density;
683
687
  if (options.tone !== void 0) params.tone = options.tone;
684
688
  if (options.language !== void 0) params.language = options.language;
689
+ if (options.sourceLanguage !== void 0) params.sourceLanguage = options.sourceLanguage;
685
690
  if (options.schemaId !== void 0) params.schemaId = options.schemaId;
686
691
  if (options.categories !== void 0) params.categories = options.categories;
687
692
  return busRequest(
@@ -717,7 +722,7 @@ var GatherNamespace = class {
717
722
  this.transport = transport;
718
723
  this.bus = bus;
719
724
  }
720
- annotation(annotationId2, resourceId2, options) {
725
+ annotation(resourceId2, annotationId2, options) {
721
726
  return new StreamObservable((subscriber) => {
722
727
  const correlationId = crypto.randomUUID();
723
728
  const complete$ = this.bus.get("gather:complete").pipe(
@@ -809,22 +814,53 @@ var YieldNamespace = class {
809
814
  this.bus = bus;
810
815
  this.content = content;
811
816
  }
812
- async resource(data) {
813
- const result = await this.content.putBinary({
814
- name: data.name,
815
- file: data.file,
816
- format: data.format,
817
- storageUri: data.storageUri,
818
- ...data.entityTypes ? { entityTypes: data.entityTypes } : {},
819
- ...data.language ? { language: data.language } : {},
820
- ...data.creationMethod ? { creationMethod: data.creationMethod } : {},
821
- ...data.sourceAnnotationId ? { sourceAnnotationId: data.sourceAnnotationId } : {},
822
- ...data.sourceResourceId ? { sourceResourceId: data.sourceResourceId } : {},
823
- ...data.generationPrompt ? { generationPrompt: data.generationPrompt } : {},
824
- ...data.generator ? { generator: data.generator } : {},
825
- ...data.isDraft !== void 0 ? { isDraft: data.isDraft } : {}
817
+ resource(data) {
818
+ const totalBytes = typeof Buffer !== "undefined" && data.file instanceof Buffer ? data.file.length : data.file.size;
819
+ return new UploadObservable((subscriber) => {
820
+ subscriber.next({ phase: "started", totalBytes });
821
+ let cancelled = false;
822
+ const abortController = new AbortController();
823
+ this.content.putBinary(
824
+ {
825
+ name: data.name,
826
+ file: data.file,
827
+ format: data.format,
828
+ storageUri: data.storageUri,
829
+ ...data.entityTypes ? { entityTypes: data.entityTypes } : {},
830
+ ...data.language ? { language: data.language } : {},
831
+ ...data.creationMethod ? { creationMethod: data.creationMethod } : {},
832
+ ...data.sourceAnnotationId ? { sourceAnnotationId: data.sourceAnnotationId } : {},
833
+ ...data.sourceResourceId ? { sourceResourceId: data.sourceResourceId } : {},
834
+ ...data.generationPrompt ? { generationPrompt: data.generationPrompt } : {},
835
+ ...data.generator ? { generator: data.generator } : {},
836
+ ...data.isDraft !== void 0 ? { isDraft: data.isDraft } : {}
837
+ },
838
+ {
839
+ // Byte-progress hook. Honored by `HttpContentTransport`'s XHR
840
+ // path; ignored by ky-path uploads (no `onProgress` consumer)
841
+ // and by `LocalContentTransport` (no wire to observe).
842
+ onProgress: ({ bytesUploaded, totalBytes: txTotal }) => {
843
+ if (cancelled) return;
844
+ const total = txTotal > 0 ? txTotal : totalBytes;
845
+ subscriber.next({ phase: "progress", bytesUploaded, totalBytes: total });
846
+ },
847
+ signal: abortController.signal
848
+ }
849
+ ).then((result) => {
850
+ if (cancelled) return;
851
+ subscriber.next({
852
+ phase: "finished",
853
+ resourceId: resourceId(result.resourceId)
854
+ });
855
+ subscriber.complete();
856
+ }).catch((err) => {
857
+ if (!cancelled) subscriber.error(err);
858
+ });
859
+ return () => {
860
+ cancelled = true;
861
+ abortController.abort();
862
+ };
826
863
  });
827
- return { resourceId: result.resourceId };
828
864
  }
829
865
  fromAnnotation(resourceId2, annotationId2, options) {
830
866
  return new StreamObservable((subscriber) => {
@@ -915,6 +951,7 @@ var YieldNamespace = class {
915
951
  title: options.title,
916
952
  prompt: options.prompt,
917
953
  language: options.language,
954
+ sourceLanguage: options.sourceLanguage,
918
955
  temperature: options.temperature,
919
956
  maxTokens: options.maxTokens,
920
957
  storageUri: options.storageUri,
@@ -960,13 +997,14 @@ var YieldNamespace = class {
960
997
  return result.sourceResource;
961
998
  }
962
999
  async createFromToken(options) {
963
- return busRequest(
1000
+ const result = await busRequest(
964
1001
  this.transport,
965
1002
  "yield:clone-create",
966
1003
  options,
967
1004
  "yield:clone-created",
968
1005
  "yield:clone-create-failed"
969
1006
  );
1007
+ return { resourceId: resourceId(result.resourceId) };
970
1008
  }
971
1009
  clone() {
972
1010
  this.bus.get("yield:clone").next(void 0);
@@ -979,7 +1017,7 @@ var BeckonNamespace = class {
979
1017
  this.transport = transport;
980
1018
  this.bus = bus;
981
1019
  }
982
- attention(annotationId2, resourceId2) {
1020
+ attention(resourceId2, annotationId2) {
983
1021
  void this.transport.emit("beckon:focus", { annotationId: annotationId2, resourceId: resourceId2 });
984
1022
  }
985
1023
  hover(annotationId2) {
@@ -990,6 +1028,21 @@ var BeckonNamespace = class {
990
1028
  }
991
1029
  };
992
1030
 
1031
+ // src/namespaces/frame.ts
1032
+ var FrameNamespace = class {
1033
+ constructor(transport) {
1034
+ this.transport = transport;
1035
+ }
1036
+ async addEntityType(type) {
1037
+ await this.transport.emit("frame:add-entity-type", { tag: type });
1038
+ }
1039
+ async addEntityTypes(types) {
1040
+ for (const tag of types) {
1041
+ await this.transport.emit("frame:add-entity-type", { tag });
1042
+ }
1043
+ }
1044
+ };
1045
+
993
1046
  // src/namespaces/job.ts
994
1047
  var JobNamespace = class {
995
1048
  constructor(transport, bus) {
@@ -1027,7 +1080,7 @@ var JobNamespace = class {
1027
1080
  }
1028
1081
  async pollUntilComplete(jobId, options) {
1029
1082
  const interval = options?.interval ?? 1e3;
1030
- const timeout7 = options?.timeout ?? 6e4;
1083
+ const timeout6 = options?.timeout ?? 6e4;
1031
1084
  const startTime = Date.now();
1032
1085
  while (true) {
1033
1086
  const status = await this.status(jobId);
@@ -1035,89 +1088,97 @@ var JobNamespace = class {
1035
1088
  if (status.status === "complete" || status.status === "failed" || status.status === "cancelled") {
1036
1089
  return status;
1037
1090
  }
1038
- if (Date.now() - startTime > timeout7) {
1039
- throw new Error(`Job polling timeout after ${timeout7}ms`);
1091
+ if (Date.now() - startTime > timeout6) {
1092
+ throw new Error(`Job polling timeout after ${timeout6}ms`);
1040
1093
  }
1041
1094
  await new Promise((resolve) => setTimeout(resolve, interval));
1042
1095
  }
1043
1096
  }
1044
- async cancel(_jobId, type) {
1045
- await this.transport.emit("job:cancel-requested", {
1046
- jobType: type === "generation" ? "generation" : "annotation"
1047
- });
1097
+ async cancelByType(jobType) {
1098
+ await this.transport.emit("job:cancel-requested", { jobType });
1048
1099
  }
1049
1100
  cancelRequest(jobType) {
1050
1101
  this.bus.get("job:cancel-requested").next({ jobType });
1051
1102
  }
1052
1103
  };
1053
1104
  var AuthNamespace = class {
1054
- constructor(transport) {
1055
- this.transport = transport;
1105
+ constructor(backend) {
1106
+ this.backend = backend;
1056
1107
  }
1057
1108
  async password(emailStr, passwordStr) {
1058
- return this.transport.authenticatePassword(email(emailStr), passwordStr);
1109
+ return this.backend.authenticatePassword(email(emailStr), passwordStr);
1059
1110
  }
1060
1111
  async google(credential) {
1061
- return this.transport.authenticateGoogle(googleCredential(credential));
1112
+ return this.backend.authenticateGoogle(googleCredential(credential));
1062
1113
  }
1063
1114
  async refresh(token) {
1064
- return this.transport.refreshAccessToken(refreshToken(token));
1115
+ return this.backend.refreshAccessToken(refreshToken(token));
1065
1116
  }
1066
1117
  async logout() {
1067
- await this.transport.logout();
1118
+ await this.backend.logout();
1068
1119
  }
1069
1120
  async me() {
1070
- return this.transport.getCurrentUser();
1121
+ return this.backend.getCurrentUser();
1071
1122
  }
1072
1123
  async acceptTerms() {
1073
- await this.transport.acceptTerms();
1124
+ await this.backend.acceptTerms();
1074
1125
  }
1075
1126
  async mcpToken() {
1076
- return this.transport.generateMcpToken();
1127
+ return this.backend.generateMcpToken();
1077
1128
  }
1078
1129
  async mediaToken(resourceId2) {
1079
- return this.transport.getMediaToken(resourceId2);
1130
+ return this.backend.getMediaToken(resourceId2);
1080
1131
  }
1081
1132
  };
1082
1133
 
1083
1134
  // src/namespaces/admin.ts
1084
1135
  var AdminNamespace = class {
1085
- constructor(transport) {
1086
- this.transport = transport;
1136
+ constructor(backend) {
1137
+ this.backend = backend;
1087
1138
  }
1088
1139
  async users() {
1089
- const result = await this.transport.listUsers();
1140
+ const result = await this.backend.listUsers();
1090
1141
  return result.users;
1091
1142
  }
1092
1143
  async userStats() {
1093
- return this.transport.getUserStats();
1144
+ return this.backend.getUserStats();
1094
1145
  }
1095
1146
  async updateUser(userId2, data) {
1096
- const result = await this.transport.updateUser(userId2, data);
1147
+ const result = await this.backend.updateUser(userId2, data);
1097
1148
  return result.user;
1098
1149
  }
1099
1150
  async oauthConfig() {
1100
- return this.transport.getOAuthConfig();
1151
+ return this.backend.getOAuthConfig();
1101
1152
  }
1102
1153
  async healthCheck() {
1103
- return this.transport.healthCheck();
1154
+ return this.backend.healthCheck();
1104
1155
  }
1105
1156
  async status() {
1106
- return this.transport.getStatus();
1157
+ return this.backend.getStatus();
1107
1158
  }
1108
1159
  async backup() {
1109
- return this.transport.backupKnowledgeBase();
1160
+ return this.backend.backupKnowledgeBase();
1110
1161
  }
1111
- async restore(file, onProgress) {
1112
- return this.transport.restoreKnowledgeBase(file, onProgress);
1162
+ restore(file) {
1163
+ return wrapAsStream(this.backend.restoreKnowledgeBase(file));
1113
1164
  }
1114
1165
  async exportKnowledgeBase(params) {
1115
- return this.transport.exportKnowledgeBase(params);
1166
+ return this.backend.exportKnowledgeBase(params);
1116
1167
  }
1117
- async importKnowledgeBase(file, onProgress) {
1118
- return this.transport.importKnowledgeBase(file, onProgress);
1168
+ importKnowledgeBase(file) {
1169
+ return wrapAsStream(this.backend.importKnowledgeBase(file));
1119
1170
  }
1120
1171
  };
1172
+ function wrapAsStream(source) {
1173
+ return new StreamObservable((subscriber) => {
1174
+ const sub = source.subscribe({
1175
+ next: (v) => subscriber.next(v),
1176
+ error: (e) => subscriber.error(e),
1177
+ complete: () => subscriber.complete()
1178
+ });
1179
+ return () => sub.unsubscribe();
1180
+ });
1181
+ }
1121
1182
  var SemiontClient = class _SemiontClient {
1122
1183
  /**
1123
1184
  * The wire-facing transport. Owns bus actor, HTTP, auth, admin, exchange,
@@ -1137,6 +1198,15 @@ var SemiontClient = class _SemiontClient {
1137
1198
  bus;
1138
1199
  baseUrl;
1139
1200
  // ── Verb-oriented namespace API ──────────────────────────────────────────
1201
+ //
1202
+ // The first nine namespaces are bus-driven and always present. `frame`
1203
+ // is the schema-layer flow's surface (eighth flow); the other eight are
1204
+ // content-layer flows plus `job`. `auth` and `admin` are backend-ops
1205
+ // namespaces — they're only constructed when the caller passes an
1206
+ // `IBackendOperations` instance to the constructor. A `SemiontClient`
1207
+ // over a transport-only setup (e.g. `LocalTransport`) has
1208
+ // `auth === undefined` / `admin === undefined`.
1209
+ frame;
1140
1210
  browse;
1141
1211
  mark;
1142
1212
  bind;
@@ -1159,13 +1229,20 @@ var SemiontClient = class _SemiontClient {
1159
1229
  * Callers do not pass a bus in. If they need to interact with the bus
1160
1230
  * (e.g. for tests or to subscribe to arbitrary channels), they read it
1161
1231
  * back via `client.bus`.
1232
+ *
1233
+ * `backend` is optional. When provided, the `auth` and `admin`
1234
+ * namespaces are constructed against it; when omitted, they're
1235
+ * `undefined`. For HTTP setups this is conventionally the same
1236
+ * `HttpTransport` instance that's also passed as `transport` (HTTP
1237
+ * implements both `ITransport` and `IBackendOperations`).
1162
1238
  */
1163
- constructor(transport, content) {
1239
+ constructor(transport, content, backend) {
1164
1240
  this.transport = transport;
1165
1241
  this.content = content;
1166
1242
  this.baseUrl = transport.baseUrl;
1167
1243
  this.bus = new EventBus();
1168
1244
  this.transport.bridgeInto(this.bus);
1245
+ this.frame = new FrameNamespace(this.transport);
1169
1246
  this.browse = new BrowseNamespace(this.transport, this.bus, this.content);
1170
1247
  this.mark = new MarkNamespace(this.transport, this.bus);
1171
1248
  this.bind = new BindNamespace(this.transport, this.bus);
@@ -1174,8 +1251,8 @@ var SemiontClient = class _SemiontClient {
1174
1251
  this.yield = new YieldNamespace(this.transport, this.bus, this.content);
1175
1252
  this.beckon = new BeckonNamespace(this.transport, this.bus);
1176
1253
  this.job = new JobNamespace(this.transport, this.bus);
1177
- this.auth = new AuthNamespace(this.transport);
1178
- this.admin = new AdminNamespace(this.transport);
1254
+ this.auth = backend ? new AuthNamespace(backend) : void 0;
1255
+ this.admin = backend ? new AdminNamespace(backend) : void 0;
1179
1256
  }
1180
1257
  /** Transport-level connection state. HTTP reflects SSE health; local is always 'connected'. */
1181
1258
  get state$() {
@@ -1197,9 +1274,10 @@ var SemiontClient = class _SemiontClient {
1197
1274
  * Use this for one-shot scripts, CLI commands, or any consumer that
1198
1275
  * doesn't need to drive the token from outside (no manual refresh,
1199
1276
  * no cross-tab sync). For long-running scripts that need refresh,
1200
- * use `SemiontSession.fromHttp(...)` instead it owns the same
1201
- * transport/client wiring plus the proactive-refresh + storage
1202
- * machinery.
1277
+ * use `SemiontSession.fromHttp(...)` (with a token already on hand)
1278
+ * or `SemiontSession.signInHttp(...)` (credentials-first) instead
1279
+ * either owns the same transport/client wiring plus the
1280
+ * proactive-refresh + storage machinery.
1203
1281
  *
1204
1282
  * Strings are accepted for `baseUrl` and `token`; they are branded
1205
1283
  * via `baseUrl()` / `accessToken()` from `@semiont/core` automatically.
@@ -1213,13 +1291,13 @@ var SemiontClient = class _SemiontClient {
1213
1291
  const token$ = new BehaviorSubject(tok);
1214
1292
  const transport = new HttpTransport({ baseUrl: url, token$ });
1215
1293
  const content = new HttpContentTransport(transport);
1216
- return new _SemiontClient(transport, content);
1294
+ return new _SemiontClient(transport, content, transport);
1217
1295
  }
1218
1296
  /**
1219
1297
  * Async factory for the credentials-first script case. Builds a
1220
- * transient transport, calls `auth.password(email, password)` to
1221
- * acquire an access token, and returns the wired client with the
1222
- * token populated.
1298
+ * transient HTTP transport, calls `auth.password(email, password)`
1299
+ * to acquire an access token, and returns the wired client with
1300
+ * the token populated.
1223
1301
  *
1224
1302
  * This is the right entry point for skills, CLI scripts, and any
1225
1303
  * consumer that starts with email + password rather than a JWT
@@ -1228,18 +1306,24 @@ var SemiontClient = class _SemiontClient {
1228
1306
  * `fromHttp({ baseUrl, token })` instead.
1229
1307
  *
1230
1308
  * For long-running scripts that need refresh, use
1231
- * `SemiontSession.signIn(...)` — same credentials shape, plus the
1232
- * session machinery for proactive refresh and persistence.
1309
+ * `SemiontSession.signInHttp(...)` — same credentials shape, plus
1310
+ * the session machinery for proactive refresh and persistence.
1311
+ *
1312
+ * Named `signInHttp` because email+password authentication is
1313
+ * inherently an HTTP-shaped operation in the current backend; an
1314
+ * in-process `LocalTransport` doesn't have a credentials login
1315
+ * path. Non-HTTP transports construct the client directly from
1316
+ * their package's transport instance.
1233
1317
  *
1234
1318
  * Throws if authentication fails. The transient client is disposed
1235
1319
  * before the throw, so no resources leak on failure.
1236
1320
  */
1237
- static async signIn(opts) {
1321
+ static async signInHttp(opts) {
1238
1322
  const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
1239
1323
  const token$ = new BehaviorSubject(null);
1240
1324
  const transport = new HttpTransport({ baseUrl: url, token$ });
1241
1325
  const content = new HttpContentTransport(transport);
1242
- const client = new _SemiontClient(transport, content);
1326
+ const client = new _SemiontClient(transport, content, transport);
1243
1327
  try {
1244
1328
  const auth = await client.auth.password(opts.email, opts.password);
1245
1329
  token$.next(accessToken(auth.token));
@@ -1293,35 +1377,28 @@ function isJwtExpired(token) {
1293
1377
  if (!expiry) return true;
1294
1378
  return expiry.getTime() < Date.now();
1295
1379
  }
1296
- function migrateLegacyEntry(entry) {
1297
- if (entry.host !== void 0) return entry;
1298
- try {
1299
- const url = new URL(entry.backendUrl);
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
- };
1380
+ function isKnowledgeBase(entry) {
1381
+ if (!entry || typeof entry !== "object") return false;
1382
+ const e = entry;
1383
+ if (typeof e.id !== "string" || typeof e.label !== "string" || typeof e.email !== "string") {
1384
+ return false;
1385
+ }
1386
+ const ep = e.endpoint;
1387
+ if (!ep || typeof ep !== "object") return false;
1388
+ if (ep.kind === "http") {
1389
+ return typeof ep.host === "string" && typeof ep.port === "number" && (ep.protocol === "http" || ep.protocol === "https");
1390
+ }
1391
+ if (ep.kind === "local") {
1392
+ return typeof ep.kbId === "string";
1317
1393
  }
1394
+ return false;
1318
1395
  }
1319
1396
  function loadKnowledgeBases(storage) {
1320
1397
  try {
1321
1398
  const raw = storage.get(STORAGE_KEY);
1322
1399
  if (!raw) return [];
1323
1400
  const entries = JSON.parse(raw);
1324
- return entries.map(migrateLegacyEntry);
1401
+ return entries.filter(isKnowledgeBase);
1325
1402
  } catch {
1326
1403
  return [];
1327
1404
  }
@@ -1336,15 +1413,15 @@ var HOSTNAME_RE = /^(([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9]
1336
1413
  function isValidHostname(host) {
1337
1414
  return HOSTNAME_RE.test(host);
1338
1415
  }
1339
- function kbBackendUrl(kb) {
1340
- if (!isValidHostname(kb.host)) {
1341
- throw new Error(`Invalid KB hostname: "${kb.host}"`);
1416
+ function kbBackendUrl(endpoint) {
1417
+ if (!isValidHostname(endpoint.host)) {
1418
+ throw new Error(`Invalid KB hostname: "${endpoint.host}"`);
1342
1419
  }
1343
1420
  const url = new URL("http://x");
1344
- url.protocol = kb.protocol + ":";
1345
- url.hostname = kb.host;
1346
- url.port = String(kb.port);
1347
- return `${kb.protocol}://${url.hostname}:${kb.port}`;
1421
+ url.protocol = endpoint.protocol + ":";
1422
+ url.hostname = endpoint.host;
1423
+ url.port = String(endpoint.port);
1424
+ return `${endpoint.protocol}://${url.hostname}:${endpoint.port}`;
1348
1425
  }
1349
1426
  function generateKbId() {
1350
1427
  return crypto.randomUUID();
@@ -1365,6 +1442,18 @@ var SemiontSession = class _SemiontSession {
1365
1442
  token$;
1366
1443
  user$;
1367
1444
  streamState$;
1445
+ /**
1446
+ * Stream of `SemiontError` instances surfaced by the underlying transport
1447
+ * just before they're thrown to the caller. For `HttpTransport` this is
1448
+ * an `APIError` (status-coded); other transports emit their own subclass.
1449
+ * Surfaced here so a host layer (e.g. `SemiontBrowser`) can route by
1450
+ * `err.code` to global notifications without every call site handling
1451
+ * errors itself. Headless consumers can subscribe for logging.
1452
+ *
1453
+ * Re-published from `client.transport.errors$` per the `ITransport`
1454
+ * contract — the session is purely a passthrough.
1455
+ */
1456
+ errors$;
1368
1457
  /** Resolves after the initial validation round-trip completes (success or failure). */
1369
1458
  ready;
1370
1459
  storage;
@@ -1387,6 +1476,7 @@ var SemiontSession = class _SemiontSession {
1387
1476
  this.client = config.client;
1388
1477
  this.token$ = config.token$;
1389
1478
  this.user$ = new BehaviorSubject(null);
1479
+ this.errors$ = this.client.transport.errors$;
1390
1480
  const stored = getStoredSession(this.storage, this.kb.id);
1391
1481
  if (stored && !isJwtExpired(stored.access) && this.token$.getValue() === null) {
1392
1482
  this.token$.next(accessToken(stored.access));
@@ -1571,7 +1661,7 @@ var SemiontSession = class _SemiontSession {
1571
1661
  const token$ = new BehaviorSubject(tok);
1572
1662
  const transport = new HttpTransport({ baseUrl: url, token$ });
1573
1663
  const content = new HttpContentTransport(transport);
1574
- const client = new SemiontClient(transport, content);
1664
+ const client = new SemiontClient(transport, content, transport);
1575
1665
  const config = { kb: opts.kb, storage: opts.storage, client, token$ };
1576
1666
  if (opts.refresh) config.refresh = opts.refresh;
1577
1667
  if (opts.validate) config.validate = opts.validate;
@@ -1581,11 +1671,11 @@ var SemiontSession = class _SemiontSession {
1581
1671
  }
1582
1672
  /**
1583
1673
  * Async factory for the credentials-first long-running script case.
1584
- * Builds the transport stack, calls `auth.password(email, password)`
1585
- * to acquire access + refresh tokens, persists them via the storage
1586
- * adapter, wires a default `refresh` callback that exchanges the
1587
- * refresh token via `auth.refresh(...)`, and returns the ready
1588
- * session.
1674
+ * Builds the HTTP transport stack, calls `auth.password(email,
1675
+ * password)` to acquire access + refresh tokens, persists them via
1676
+ * the storage adapter, wires a default `refresh` callback that
1677
+ * exchanges the refresh token via `auth.refresh(...)`, and returns
1678
+ * the ready session.
1589
1679
  *
1590
1680
  * The consumer-supplied `refresh` callback becomes optional — only
1591
1681
  * needed for non-standard refresh flows (worker-pool shared secret,
@@ -1598,15 +1688,21 @@ var SemiontSession = class _SemiontSession {
1598
1688
  * trampling each other's tokens. The factory does not synthesize a
1599
1689
  * default; the consumer makes the choice.
1600
1690
  *
1691
+ * Named `signInHttp` because email+password authentication is
1692
+ * inherently an HTTP-shaped operation in the current backend; an
1693
+ * in-process `LocalTransport` doesn't have a credentials login
1694
+ * path. Non-HTTP transports construct the session directly from
1695
+ * their package's transport instance.
1696
+ *
1601
1697
  * Throws on auth failure with no resources leaked. On success, the
1602
1698
  * returned session's `ready` promise has already resolved.
1603
1699
  */
1604
- static async signIn(opts) {
1700
+ static async signInHttp(opts) {
1605
1701
  const url = typeof opts.baseUrl === "string" ? baseUrl(opts.baseUrl) : opts.baseUrl;
1606
1702
  const token$ = new BehaviorSubject(null);
1607
1703
  const transport = new HttpTransport({ baseUrl: url, token$ });
1608
1704
  const content = new HttpContentTransport(transport);
1609
- const client = new SemiontClient(transport, content);
1705
+ const client = new SemiontClient(transport, content, transport);
1610
1706
  let auth;
1611
1707
  try {
1612
1708
  auth = await client.auth.password(opts.email, opts.password);
@@ -1641,25 +1737,7 @@ var SemiontSession = class _SemiontSession {
1641
1737
  return session;
1642
1738
  }
1643
1739
  };
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 {
1740
+ var SessionSignals = class {
1663
1741
  sessionExpiredAt$;
1664
1742
  sessionExpiredMessage$;
1665
1743
  permissionDeniedAt$;
@@ -1724,7 +1802,7 @@ var SemiontBrowser = class {
1724
1802
  * non-null when `activeSession$` is non-null, always null when it
1725
1803
  * is. Extracted from the session itself so headless sessions
1726
1804
  * (workers, CLIs, tests) don't carry dead modal observables.
1727
- * See [FrontendSessionSignals](./frontend-session-signals.ts).
1805
+ * See [SessionSignals](./session-signals.ts).
1728
1806
  */
1729
1807
  activeSignals$;
1730
1808
  /**
@@ -1740,6 +1818,7 @@ var SemiontBrowser = class {
1740
1818
  error$;
1741
1819
  identityToken$;
1742
1820
  storage;
1821
+ sessionFactory;
1743
1822
  /**
1744
1823
  * App-scoped EventBus. Hosts UI-shell events that must work regardless
1745
1824
  * of whether a KB session is active: panel toggles, sidebar state,
@@ -1748,20 +1827,12 @@ var SemiontBrowser = class {
1748
1827
  * (mark:*, beckon:*, gather:*, match:*, bind:*, yield:*, browse:click).
1749
1828
  */
1750
1829
  eventBus = new EventBus();
1751
- unregisterNotify = null;
1752
1830
  unsubscribeStorage = null;
1753
1831
  disposed = false;
1754
1832
  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
1833
  constructor(config) {
1764
1834
  this.storage = config.storage;
1835
+ this.sessionFactory = config.sessionFactory;
1765
1836
  const kbs = loadKnowledgeBases(this.storage);
1766
1837
  const storedActive = this.storage.get(ACTIVE_KEY);
1767
1838
  const initialActive = storedActive && kbs.some((kb) => kb.id === storedActive) ? storedActive : kbs[0]?.id ?? null;
@@ -1788,14 +1859,6 @@ var SemiontBrowser = class {
1788
1859
  } catch {
1789
1860
  }
1790
1861
  }) ?? 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
1862
  if (initialActive) {
1800
1863
  void this.setActiveKb(initialActive);
1801
1864
  }
@@ -1817,9 +1880,10 @@ var SemiontBrowser = class {
1817
1880
  }
1818
1881
  // ── Identity token (NextAuth bridge; D1) ──────────────────────────────
1819
1882
  /**
1820
- * Set the app-level identity token (from NextAuth's useSession).
1821
- * Called at the root layout via a single `useEffect`. No other site
1822
- * in the codebase should call this.
1883
+ * Set the app-level identity token. Sourced from whatever the host
1884
+ * environment uses for OAuth sessions (e.g. NextAuth in a browser app).
1885
+ * Should be called once from the host's startup-and-on-change site;
1886
+ * no other code should write to this slot.
1823
1887
  */
1824
1888
  setIdentityToken(token) {
1825
1889
  if (this.disposed) return;
@@ -1841,6 +1905,12 @@ var SemiontBrowser = class {
1841
1905
  void this.setActiveKb(next[0]?.id ?? null);
1842
1906
  }
1843
1907
  }
1908
+ /**
1909
+ * Patch a KB in the list. Restricted to the common, endpoint-agnostic
1910
+ * fields (`label`, `email`, `gitBranch`) — the `endpoint` shape isn't
1911
+ * editable in place; remove and re-add to change the connection
1912
+ * target.
1913
+ */
1844
1914
  updateKb(id, updates) {
1845
1915
  this.kbs$.next(
1846
1916
  this.kbs$.getValue().map((kb) => kb.id === id ? { ...kb, ...updates } : kb)
@@ -1896,25 +1966,32 @@ var SemiontBrowser = class {
1896
1966
  if (!id) return;
1897
1967
  const kb = this.kbs$.getValue().find((k) => k.id === id);
1898
1968
  if (!kb) return;
1899
- const signals = new FrontendSessionSignals();
1900
- const token$ = new BehaviorSubject(null);
1969
+ const signals = new SessionSignals();
1901
1970
  let session;
1902
- const transport = new HttpTransport({
1903
- baseUrl: baseUrl(kbBackendUrl(kb)),
1904
- token$,
1905
- tokenRefresher: () => session.refresh().then((t) => t ?? null)
1906
- });
1907
- const content = new HttpContentTransport(transport);
1908
- const client = new SemiontClient(transport, content);
1909
- session = new SemiontSession({
1910
- kb,
1911
- storage: this.storage,
1912
- client,
1913
- token$,
1914
- refresh: () => this.performRefresh(kb),
1915
- validate: (token) => this.performValidate(kb, token),
1916
- onAuthFailed: (msg) => signals.notifySessionExpired(msg),
1917
- onError: (err) => this.error$.next(err)
1971
+ try {
1972
+ session = this.sessionFactory({
1973
+ kb,
1974
+ storage: this.storage,
1975
+ signals,
1976
+ onError: (err) => this.error$.next(err)
1977
+ });
1978
+ } catch (err) {
1979
+ this.error$.next(
1980
+ err instanceof SemiontSessionError ? err : new SemiontSessionError(
1981
+ "session.construct-failed",
1982
+ err instanceof Error ? err.message : String(err),
1983
+ id
1984
+ )
1985
+ );
1986
+ signals.dispose();
1987
+ return;
1988
+ }
1989
+ session.errors$.subscribe((err) => {
1990
+ if (err.code === "unauthorized") {
1991
+ signals.notifySessionExpired(err.message);
1992
+ } else if (err.code === "forbidden") {
1993
+ signals.notifyPermissionDenied(err.message);
1994
+ }
1918
1995
  });
1919
1996
  try {
1920
1997
  await session.ready;
@@ -2030,80 +2107,10 @@ var SemiontBrowser = class {
2030
2107
  if (moved) list.splice(newIndex, 0, moved);
2031
2108
  this.openResources$.next(list);
2032
2109
  }
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
2110
  // ── Lifecycle ─────────────────────────────────────────────────────────
2102
2111
  async dispose() {
2103
2112
  if (this.disposed) return;
2104
2113
  this.disposed = true;
2105
- this.unregisterNotify?.();
2106
- this.unregisterNotify = null;
2107
2114
  if (this.unsubscribeStorage) {
2108
2115
  this.unsubscribeStorage();
2109
2116
  this.unsubscribeStorage = null;
@@ -2124,12 +2131,91 @@ var SemiontBrowser = class {
2124
2131
  this.eventBus.destroy();
2125
2132
  }
2126
2133
  };
2134
+ function createHttpSessionFactory() {
2135
+ const inFlightRefreshes = /* @__PURE__ */ new Map();
2136
+ return (opts) => {
2137
+ const { kb, storage, signals, onError } = opts;
2138
+ if (kb.endpoint.kind !== "http") {
2139
+ throw new SemiontSessionError(
2140
+ "session.construct-failed",
2141
+ `HTTP session factory cannot construct a session for endpoint kind "${kb.endpoint.kind}"`,
2142
+ kb.id
2143
+ );
2144
+ }
2145
+ const endpoint = kb.endpoint;
2146
+ const performRefresh = async () => {
2147
+ const existing = inFlightRefreshes.get(kb.id);
2148
+ if (existing) return existing;
2149
+ const promise = (async () => {
2150
+ const stored = getStoredSession(storage, kb.id);
2151
+ if (!stored) return null;
2152
+ const throwawayTransport = new HttpTransport({ baseUrl: baseUrl(kbBackendUrl(endpoint)) });
2153
+ const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport), throwawayTransport);
2154
+ try {
2155
+ const response = await throwaway.auth.refresh(stored.refresh);
2156
+ const newAccess = response.access_token;
2157
+ if (!newAccess) return null;
2158
+ setStoredSession(storage, kb.id, { access: newAccess, refresh: stored.refresh });
2159
+ return newAccess;
2160
+ } catch {
2161
+ return null;
2162
+ } finally {
2163
+ throwaway.dispose();
2164
+ }
2165
+ })();
2166
+ inFlightRefreshes.set(kb.id, promise);
2167
+ try {
2168
+ return await promise;
2169
+ } finally {
2170
+ inFlightRefreshes.delete(kb.id);
2171
+ }
2172
+ };
2173
+ const performValidate = async (token) => {
2174
+ const tokenSubject = new BehaviorSubject(token);
2175
+ const throwawayTransport = new HttpTransport({
2176
+ baseUrl: baseUrl(kbBackendUrl(endpoint)),
2177
+ token$: tokenSubject
2178
+ });
2179
+ const throwaway = new SemiontClient(throwawayTransport, new HttpContentTransport(throwawayTransport), throwawayTransport);
2180
+ try {
2181
+ const data = await throwaway.auth.me();
2182
+ return data;
2183
+ } finally {
2184
+ throwaway.dispose();
2185
+ tokenSubject.complete();
2186
+ }
2187
+ };
2188
+ const token$ = new BehaviorSubject(null);
2189
+ let session;
2190
+ const transport = new HttpTransport({
2191
+ baseUrl: baseUrl(kbBackendUrl(endpoint)),
2192
+ token$,
2193
+ tokenRefresher: () => session.refresh().then((t) => t ?? null)
2194
+ });
2195
+ const content = new HttpContentTransport(transport);
2196
+ const client = new SemiontClient(transport, content, transport);
2197
+ session = new SemiontSession({
2198
+ kb,
2199
+ storage,
2200
+ client,
2201
+ token$,
2202
+ refresh: performRefresh,
2203
+ validate: performValidate,
2204
+ onAuthFailed: (msg) => signals.notifySessionExpired(msg),
2205
+ onError
2206
+ });
2207
+ return session;
2208
+ };
2209
+ }
2127
2210
 
2128
2211
  // src/session/registry.ts
2129
2212
  var instance = null;
2130
2213
  function getBrowser(options) {
2131
2214
  if (!instance) {
2132
- instance = new SemiontBrowser({ storage: options.storage });
2215
+ instance = new SemiontBrowser({
2216
+ storage: options.storage,
2217
+ sessionFactory: options.sessionFactory
2218
+ });
2133
2219
  }
2134
2220
  return instance;
2135
2221
  }
@@ -2147,6 +2233,18 @@ var InMemorySessionStorage = class {
2147
2233
  this.map.delete(key);
2148
2234
  }
2149
2235
  };
2236
+
2237
+ // src/session/knowledge-base.ts
2238
+ function httpKb(opts) {
2239
+ const { id, label, email, host, port, protocol, gitBranch } = opts;
2240
+ return {
2241
+ id,
2242
+ label,
2243
+ email,
2244
+ ...gitBranch !== void 0 ? { gitBranch } : {},
2245
+ endpoint: { kind: "http", host, port, protocol }
2246
+ };
2247
+ }
2150
2248
  function createDisposer() {
2151
2249
  const sub = new Subscription();
2152
2250
  return {
@@ -2162,7 +2260,7 @@ function createSearchPipeline(fetch, options = {}) {
2162
2260
  const state$ = input$.pipe(
2163
2261
  startWith(initial),
2164
2262
  debounceTime(debounceMs),
2165
- distinctUntilChanged$1(),
2263
+ distinctUntilChanged(),
2166
2264
  switchMap((q) => {
2167
2265
  const trimmed = q.trim();
2168
2266
  if (!trimmed) {
@@ -2184,7 +2282,7 @@ function createSearchPipeline(fetch, options = {}) {
2184
2282
  dispose: () => input$.complete()
2185
2283
  };
2186
2284
  }
2187
- function createBeckonVM(client) {
2285
+ function createBeckonStateUnit(client) {
2188
2286
  const subs = [];
2189
2287
  const hovered$ = new BehaviorSubject(null);
2190
2288
  subs.push(client.bus.get("beckon:hover").subscribe(({ annotationId: annotationId2 }) => {
@@ -2235,59 +2333,7 @@ function createHoverHandlers(emit, delayMs) {
2235
2333
  };
2236
2334
  return { handleMouseEnter, handleMouseLeave, cleanup: cancelTimer };
2237
2335
  }
2238
- var COMMON_PANELS = ["knowledge-base", "user", "settings"];
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) {
2336
+ function createGatherStateUnit(client, resourceId2) {
2291
2337
  const subs = [];
2292
2338
  const context$ = new BehaviorSubject(null);
2293
2339
  const loading$ = new BehaviorSubject(false);
@@ -2299,8 +2345,8 @@ function createGatherVM(client, resourceId2) {
2299
2345
  context$.next(null);
2300
2346
  annotationId$.next(annotationId(event.annotationId));
2301
2347
  const gatherSub = client.gather.annotation(
2302
- annotationId(event.annotationId),
2303
2348
  resourceId2,
2349
+ annotationId(event.annotationId),
2304
2350
  { contextWindow: event.options?.contextWindow ?? 2e3 }
2305
2351
  ).pipe(
2306
2352
  timeout(6e4)
@@ -2337,7 +2383,7 @@ function createGatherVM(client, resourceId2) {
2337
2383
  }
2338
2384
  };
2339
2385
  }
2340
- function createMatchVM(client, _resourceId) {
2386
+ function createMatchStateUnit(client, _resourceId) {
2341
2387
  const subs = [];
2342
2388
  subs.push(client.bus.get("match:search-requested").subscribe((event) => {
2343
2389
  const searchSub = client.match.search(
@@ -2363,7 +2409,7 @@ function createMatchVM(client, _resourceId) {
2363
2409
  }
2364
2410
  };
2365
2411
  }
2366
- function createYieldVM(client, resourceId2, locale) {
2412
+ function createYieldStateUnit(client, resourceId2, locale) {
2367
2413
  const subs = [];
2368
2414
  const isGenerating$ = new BehaviorSubject(false);
2369
2415
  const progress$ = new BehaviorSubject(null);
@@ -2418,7 +2464,7 @@ function selectionToSelector(selection) {
2418
2464
  }
2419
2465
  return { type: "TextQuoteSelector", exact: selection.exact, ...selection.prefix && { prefix: selection.prefix }, ...selection.suffix && { suffix: selection.suffix } };
2420
2466
  }
2421
- function createMarkVM(client, resourceId2) {
2467
+ function createMarkStateUnit(client, resourceId2) {
2422
2468
  const subs = [];
2423
2469
  const pendingAnnotation$ = new BehaviorSubject(null);
2424
2470
  const assistingMotivation$ = new BehaviorSubject(null);
@@ -2442,7 +2488,7 @@ function createMarkVM(client, resourceId2) {
2442
2488
  subs.push(client.bus.get("mark:create-ok").subscribe(() => pendingAnnotation$.next(null)));
2443
2489
  subs.push(client.bus.get("mark:submit").subscribe(async (event) => {
2444
2490
  try {
2445
- const result = await client.mark.annotation(resourceId2, {
2491
+ const result = await client.mark.annotation({
2446
2492
  motivation: event.motivation,
2447
2493
  target: { source: resourceId2, selector: event.selector },
2448
2494
  body: event.body
@@ -2503,720 +2549,7 @@ function createMarkVM(client, resourceId2) {
2503
2549
  }
2504
2550
  };
2505
2551
  }
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
2552
 
3220
- export { AdminNamespace, AuthNamespace, BeckonNamespace, BindNamespace, BrowseNamespace, BusRequestError, COMMON_PANELS, CacheObservable, FrontendSessionSignals, GatherNamespace, HOVER_DELAY_MS, InMemorySessionStorage, JobNamespace, MarkNamespace, MatchNamespace, RESOURCE_PANELS, SemiontBrowser, SemiontClient, SemiontSession, SemiontSessionError, StreamObservable, YieldNamespace, busRequest, createAdminSecurityVM, createAdminUsersVM, createBeckonVM, createCache, createComposePageVM, createDiscoverVM, createDisposer, createEntityTagsVM, createExchangeVM, createGatherVM, createHoverHandlers, createJobClaimAdapter, createJobQueueVM, createMarkVM, createMatchVM, createResourceLoaderVM, createResourceViewerPageVM, createSearchPipeline, createSessionVM, createShellVM, createSmelterActorVM, createWelcomeVM, createYieldVM, defaultProtocol, getBrowser, isValidHostname, kbBackendUrl, notifyPermissionDenied, notifySessionExpired, setStoredSession };
2553
+ 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
2554
  //# sourceMappingURL=index.js.map
3222
2555
  //# sourceMappingURL=index.js.map