@semiont/api-client 0.2.1 → 0.2.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/{types.d.ts → index-DHh0ToZB.d.ts} +460 -6
- package/dist/index.d.ts +909 -13
- package/dist/index.js +1598 -60
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +1 -12
- package/dist/utils/index.js +597 -26
- package/dist/utils/index.js.map +1 -1
- package/package.json +11 -8
- package/dist/__tests__/client.test.d.ts +0 -28
- package/dist/__tests__/client.test.d.ts.map +0 -1
- package/dist/__tests__/client.test.js +0 -567
- package/dist/__tests__/client.test.js.map +0 -1
- package/dist/__tests__/sse-client.test.d.ts +0 -7
- package/dist/__tests__/sse-client.test.d.ts.map +0 -1
- package/dist/__tests__/sse-client.test.js +0 -421
- package/dist/__tests__/sse-client.test.js.map +0 -1
- package/dist/__tests__/sse-stream.test.d.ts +0 -7
- package/dist/__tests__/sse-stream.test.d.ts.map +0 -1
- package/dist/__tests__/sse-stream.test.js +0 -394
- package/dist/__tests__/sse-stream.test.js.map +0 -1
- package/dist/__tests__/svg-selectors.test.d.ts +0 -5
- package/dist/__tests__/svg-selectors.test.d.ts.map +0 -1
- package/dist/__tests__/svg-selectors.test.js +0 -124
- package/dist/__tests__/svg-selectors.test.js.map +0 -1
- package/dist/branded-types.d.ts +0 -70
- package/dist/branded-types.d.ts.map +0 -1
- package/dist/branded-types.js +0 -62
- package/dist/branded-types.js.map +0 -1
- package/dist/client.d.ts +0 -243
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -460
- package/dist/client.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/mime-utils.d.ts +0 -27
- package/dist/mime-utils.d.ts.map +0 -1
- package/dist/mime-utils.js +0 -49
- package/dist/mime-utils.js.map +0 -1
- package/dist/sse/index.d.ts +0 -343
- package/dist/sse/index.d.ts.map +0 -1
- package/dist/sse/index.js +0 -404
- package/dist/sse/index.js.map +0 -1
- package/dist/sse/stream.d.ts +0 -58
- package/dist/sse/stream.d.ts.map +0 -1
- package/dist/sse/stream.js +0 -187
- package/dist/sse/stream.js.map +0 -1
- package/dist/sse/types.d.ts +0 -295
- package/dist/sse/types.d.ts.map +0 -1
- package/dist/sse/types.js +0 -10
- package/dist/sse/types.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -7
- package/dist/types.js.map +0 -1
- package/dist/utils/annotations.d.ts +0 -191
- package/dist/utils/annotations.d.ts.map +0 -1
- package/dist/utils/annotations.js +0 -404
- package/dist/utils/annotations.js.map +0 -1
- package/dist/utils/events.d.ts +0 -74
- package/dist/utils/events.d.ts.map +0 -1
- package/dist/utils/events.js +0 -329
- package/dist/utils/events.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/locales.d.ts +0 -31
- package/dist/utils/locales.d.ts.map +0 -1
- package/dist/utils/locales.js +0 -83
- package/dist/utils/locales.js.map +0 -1
- package/dist/utils/resources.d.ts +0 -34
- package/dist/utils/resources.d.ts.map +0 -1
- package/dist/utils/resources.js +0 -63
- package/dist/utils/resources.js.map +0 -1
- package/dist/utils/validation.d.ts +0 -57
- package/dist/utils/validation.d.ts.map +0 -1
- package/dist/utils/validation.js +0 -89
- package/dist/utils/validation.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,62 +1,1600 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
import ky from 'ky';
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
|
|
5
|
+
// src/sse/stream.ts
|
|
6
|
+
function createSSEStream(url, fetchOptions, config) {
|
|
7
|
+
const abortController = new AbortController();
|
|
8
|
+
let progressCallback = null;
|
|
9
|
+
let completeCallback = null;
|
|
10
|
+
let errorCallback = null;
|
|
11
|
+
const customHandlers = /* @__PURE__ */ new Map();
|
|
12
|
+
let closed = false;
|
|
13
|
+
const connect = async () => {
|
|
14
|
+
try {
|
|
15
|
+
const response = await fetch(url, {
|
|
16
|
+
...fetchOptions,
|
|
17
|
+
signal: abortController.signal,
|
|
18
|
+
headers: {
|
|
19
|
+
...fetchOptions.headers,
|
|
20
|
+
"Accept": "text/event-stream"
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const errorData = await response.json().catch(() => ({}));
|
|
25
|
+
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
if (!response.body) {
|
|
28
|
+
throw new Error("Response body is null - server did not return a stream");
|
|
29
|
+
}
|
|
30
|
+
const reader = response.body.getReader();
|
|
31
|
+
const decoder = new TextDecoder();
|
|
32
|
+
let buffer = "";
|
|
33
|
+
let eventType = "";
|
|
34
|
+
let eventData = "";
|
|
35
|
+
let eventId = "";
|
|
36
|
+
while (true) {
|
|
37
|
+
const { done, value } = await reader.read();
|
|
38
|
+
if (done || closed) break;
|
|
39
|
+
buffer += decoder.decode(value, { stream: true });
|
|
40
|
+
const lines = buffer.split("\n");
|
|
41
|
+
buffer = lines.pop() || "";
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
if (line.startsWith("event:")) {
|
|
44
|
+
eventType = line.slice(6).trim();
|
|
45
|
+
} else if (line.startsWith("data:")) {
|
|
46
|
+
eventData = line.slice(5).trim();
|
|
47
|
+
} else if (line.startsWith("id:")) {
|
|
48
|
+
eventId = line.slice(3).trim();
|
|
49
|
+
} else if (line === "") {
|
|
50
|
+
if (eventData && !closed) {
|
|
51
|
+
handleEvent(eventType, eventData, eventId);
|
|
52
|
+
if (closed) break;
|
|
53
|
+
eventType = "";
|
|
54
|
+
eventData = "";
|
|
55
|
+
eventId = "";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (closed) break;
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof Error && error.name !== "AbortError") {
|
|
63
|
+
errorCallback?.(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const handleEvent = (eventType, data, _id) => {
|
|
68
|
+
if (data.startsWith(":")) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(data);
|
|
73
|
+
if (config.customEventHandler) {
|
|
74
|
+
const handler = customHandlers.get(eventType);
|
|
75
|
+
if (handler) {
|
|
76
|
+
handler(parsed);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
progressCallback?.(parsed);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (config.progressEvents.includes(eventType)) {
|
|
83
|
+
progressCallback?.(parsed);
|
|
84
|
+
}
|
|
85
|
+
if (config.completeEvent && eventType === config.completeEvent) {
|
|
86
|
+
completeCallback?.(parsed);
|
|
87
|
+
closed = true;
|
|
88
|
+
abortController.abort();
|
|
89
|
+
}
|
|
90
|
+
if (config.errorEvent && eventType === config.errorEvent) {
|
|
91
|
+
errorCallback?.(new Error(parsed.message || "Stream error"));
|
|
92
|
+
closed = true;
|
|
93
|
+
abortController.abort();
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error("[SSE] Failed to parse event data:", error);
|
|
97
|
+
console.error("[SSE] Event type:", eventType);
|
|
98
|
+
console.error("[SSE] Data:", data);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
connect();
|
|
102
|
+
return {
|
|
103
|
+
onProgress(callback) {
|
|
104
|
+
progressCallback = callback;
|
|
105
|
+
},
|
|
106
|
+
onComplete(callback) {
|
|
107
|
+
completeCallback = callback;
|
|
108
|
+
},
|
|
109
|
+
onError(callback) {
|
|
110
|
+
errorCallback = callback;
|
|
111
|
+
},
|
|
112
|
+
close() {
|
|
113
|
+
abortController.abort();
|
|
114
|
+
},
|
|
115
|
+
// Internal method for custom event handlers (used by resourceEvents)
|
|
116
|
+
on(event, callback) {
|
|
117
|
+
customHandlers.set(event, callback);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/sse/index.ts
|
|
123
|
+
var SSEClient = class {
|
|
124
|
+
baseUrl;
|
|
125
|
+
accessToken = null;
|
|
126
|
+
constructor(config) {
|
|
127
|
+
this.baseUrl = config.baseUrl.endsWith("/") ? config.baseUrl.slice(0, -1) : config.baseUrl;
|
|
128
|
+
this.accessToken = config.accessToken || null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Set the access token for authenticated requests
|
|
132
|
+
*/
|
|
133
|
+
setAccessToken(token) {
|
|
134
|
+
this.accessToken = token;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Clear the access token
|
|
138
|
+
*/
|
|
139
|
+
clearAccessToken() {
|
|
140
|
+
this.accessToken = null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get common headers for SSE requests
|
|
144
|
+
*/
|
|
145
|
+
getHeaders() {
|
|
146
|
+
const headers = {
|
|
147
|
+
"Content-Type": "application/json"
|
|
148
|
+
};
|
|
149
|
+
if (this.accessToken) {
|
|
150
|
+
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
151
|
+
}
|
|
152
|
+
return headers;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Extract resource ID from URI
|
|
156
|
+
*
|
|
157
|
+
* Handles both full URIs and plain IDs:
|
|
158
|
+
* - 'http://localhost:4000/resources/doc-123' -> 'doc-123'
|
|
159
|
+
* - 'doc-123' -> 'doc-123'
|
|
160
|
+
*/
|
|
161
|
+
extractId(uri) {
|
|
162
|
+
const parts = uri.split("/");
|
|
163
|
+
return parts[parts.length - 1];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Detect annotations in a resource (streaming)
|
|
167
|
+
*
|
|
168
|
+
* Streams entity detection progress via Server-Sent Events.
|
|
169
|
+
*
|
|
170
|
+
* @param resourceId - Resource URI or ID
|
|
171
|
+
* @param request - Detection configuration (entity types to detect)
|
|
172
|
+
* @returns SSE stream controller with progress/complete/error callbacks
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* const stream = sseClient.detectAnnotations(
|
|
177
|
+
* 'http://localhost:4000/resources/doc-123',
|
|
178
|
+
* { entityTypes: ['Person', 'Organization'] }
|
|
179
|
+
* );
|
|
180
|
+
*
|
|
181
|
+
* stream.onProgress((progress) => {
|
|
182
|
+
* console.log(`Scanning: ${progress.currentEntityType}`);
|
|
183
|
+
* console.log(`Progress: ${progress.processedEntityTypes}/${progress.totalEntityTypes}`);
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* stream.onComplete((result) => {
|
|
187
|
+
* console.log(`Detection complete! Found ${result.foundCount} entities`);
|
|
188
|
+
* });
|
|
189
|
+
*
|
|
190
|
+
* stream.onError((error) => {
|
|
191
|
+
* console.error('Detection failed:', error.message);
|
|
192
|
+
* });
|
|
193
|
+
*
|
|
194
|
+
* // Cleanup when done
|
|
195
|
+
* stream.close();
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
detectAnnotations(resourceId, request) {
|
|
199
|
+
const id = this.extractId(resourceId);
|
|
200
|
+
const url = `${this.baseUrl}/resources/${id}/detect-annotations-stream`;
|
|
201
|
+
return createSSEStream(
|
|
202
|
+
url,
|
|
203
|
+
{
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: this.getHeaders(),
|
|
206
|
+
body: JSON.stringify(request)
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
progressEvents: ["detection-started", "detection-progress"],
|
|
210
|
+
completeEvent: "detection-complete",
|
|
211
|
+
errorEvent: "detection-error"
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Generate resource from annotation (streaming)
|
|
217
|
+
*
|
|
218
|
+
* Streams resource generation progress via Server-Sent Events.
|
|
219
|
+
*
|
|
220
|
+
* @param resourceId - Source resource URI or ID
|
|
221
|
+
* @param annotationId - Annotation URI or ID to use as generation source
|
|
222
|
+
* @param request - Generation options (title, prompt, language)
|
|
223
|
+
* @returns SSE stream controller with progress/complete/error callbacks
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```typescript
|
|
227
|
+
* const stream = sseClient.generateResourceFromAnnotation(
|
|
228
|
+
* 'http://localhost:4000/resources/doc-123',
|
|
229
|
+
* 'http://localhost:4000/annotations/ann-456',
|
|
230
|
+
* { language: 'es', title: 'Spanish Summary' }
|
|
231
|
+
* );
|
|
232
|
+
*
|
|
233
|
+
* stream.onProgress((progress) => {
|
|
234
|
+
* console.log(`${progress.status}: ${progress.percentage}%`);
|
|
235
|
+
* console.log(progress.message);
|
|
236
|
+
* });
|
|
237
|
+
*
|
|
238
|
+
* stream.onComplete((result) => {
|
|
239
|
+
* console.log(`Generated resource: ${result.resourceId}`);
|
|
240
|
+
* });
|
|
241
|
+
*
|
|
242
|
+
* stream.onError((error) => {
|
|
243
|
+
* console.error('Generation failed:', error.message);
|
|
244
|
+
* });
|
|
245
|
+
*
|
|
246
|
+
* // Cleanup when done
|
|
247
|
+
* stream.close();
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
generateResourceFromAnnotation(resourceId, annotationId, request) {
|
|
251
|
+
const resId = this.extractId(resourceId);
|
|
252
|
+
const annId = this.extractId(annotationId);
|
|
253
|
+
const url = `${this.baseUrl}/resources/${resId}/annotations/${annId}/generate-resource-stream`;
|
|
254
|
+
return createSSEStream(
|
|
255
|
+
url,
|
|
256
|
+
{
|
|
257
|
+
method: "POST",
|
|
258
|
+
headers: this.getHeaders(),
|
|
259
|
+
body: JSON.stringify(request)
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
progressEvents: ["generation-started", "generation-progress"],
|
|
263
|
+
completeEvent: "generation-complete",
|
|
264
|
+
errorEvent: "generation-error"
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Detect highlights in a resource (streaming)
|
|
270
|
+
*
|
|
271
|
+
* Streams highlight detection progress via Server-Sent Events.
|
|
272
|
+
*
|
|
273
|
+
* @param resourceId - Resource URI or ID
|
|
274
|
+
* @param request - Detection configuration (optional instructions)
|
|
275
|
+
* @returns SSE stream controller with progress/complete/error callbacks
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```typescript
|
|
279
|
+
* const stream = sseClient.detectHighlights(
|
|
280
|
+
* 'http://localhost:4000/resources/doc-123',
|
|
281
|
+
* { instructions: 'Focus on key technical points' }
|
|
282
|
+
* );
|
|
283
|
+
*
|
|
284
|
+
* stream.onProgress((progress) => {
|
|
285
|
+
* console.log(`${progress.status}: ${progress.percentage}%`);
|
|
286
|
+
* console.log(progress.message);
|
|
287
|
+
* });
|
|
288
|
+
*
|
|
289
|
+
* stream.onComplete((result) => {
|
|
290
|
+
* console.log(`Detection complete! Created ${result.createdCount} highlights`);
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* stream.onError((error) => {
|
|
294
|
+
* console.error('Detection failed:', error.message);
|
|
295
|
+
* });
|
|
296
|
+
*
|
|
297
|
+
* // Cleanup when done
|
|
298
|
+
* stream.close();
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
detectHighlights(resourceId, request = {}) {
|
|
302
|
+
const id = this.extractId(resourceId);
|
|
303
|
+
const url = `${this.baseUrl}/resources/${id}/detect-highlights-stream`;
|
|
304
|
+
return createSSEStream(
|
|
305
|
+
url,
|
|
306
|
+
{
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: this.getHeaders(),
|
|
309
|
+
body: JSON.stringify(request)
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
progressEvents: ["highlight-detection-started", "highlight-detection-progress"],
|
|
313
|
+
completeEvent: "highlight-detection-complete",
|
|
314
|
+
errorEvent: "highlight-detection-error"
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Detect assessments in a resource (streaming)
|
|
320
|
+
*
|
|
321
|
+
* Streams assessment detection progress via Server-Sent Events.
|
|
322
|
+
*
|
|
323
|
+
* @param resourceId - Resource URI or ID
|
|
324
|
+
* @param request - Detection configuration (optional instructions)
|
|
325
|
+
* @returns SSE stream controller with progress/complete/error callbacks
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* const stream = sseClient.detectAssessments(
|
|
330
|
+
* 'http://localhost:4000/resources/doc-123',
|
|
331
|
+
* { instructions: 'Evaluate claims for accuracy' }
|
|
332
|
+
* );
|
|
333
|
+
*
|
|
334
|
+
* stream.onProgress((progress) => {
|
|
335
|
+
* console.log(`${progress.status}: ${progress.percentage}%`);
|
|
336
|
+
* console.log(progress.message);
|
|
337
|
+
* });
|
|
338
|
+
*
|
|
339
|
+
* stream.onComplete((result) => {
|
|
340
|
+
* console.log(`Detection complete! Created ${result.createdCount} assessments`);
|
|
341
|
+
* });
|
|
342
|
+
*
|
|
343
|
+
* stream.onError((error) => {
|
|
344
|
+
* console.error('Detection failed:', error.message);
|
|
345
|
+
* });
|
|
346
|
+
*
|
|
347
|
+
* // Cleanup when done
|
|
348
|
+
* stream.close();
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
detectAssessments(resourceId, request = {}) {
|
|
352
|
+
const id = this.extractId(resourceId);
|
|
353
|
+
const url = `${this.baseUrl}/resources/${id}/detect-assessments-stream`;
|
|
354
|
+
return createSSEStream(
|
|
355
|
+
url,
|
|
356
|
+
{
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: this.getHeaders(),
|
|
359
|
+
body: JSON.stringify(request)
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
progressEvents: ["assessment-detection-started", "assessment-detection-progress"],
|
|
363
|
+
completeEvent: "assessment-detection-complete",
|
|
364
|
+
errorEvent: "assessment-detection-error"
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Detect comments in a resource (streaming)
|
|
370
|
+
*
|
|
371
|
+
* Streams comment detection progress via Server-Sent Events.
|
|
372
|
+
* Uses AI to identify passages that would benefit from explanatory comments
|
|
373
|
+
* and creates comment annotations with contextual information.
|
|
374
|
+
*
|
|
375
|
+
* @param resourceId - Resource URI or ID
|
|
376
|
+
* @param request - Detection configuration (optional instructions and tone)
|
|
377
|
+
* @returns SSE stream controller with progress/complete/error callbacks
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```typescript
|
|
381
|
+
* const stream = sseClient.detectComments('http://localhost:4000/resources/doc-123', {
|
|
382
|
+
* instructions: 'Focus on technical terminology',
|
|
383
|
+
* tone: 'scholarly'
|
|
384
|
+
* });
|
|
385
|
+
*
|
|
386
|
+
* stream.onProgress((progress) => {
|
|
387
|
+
* console.log(`${progress.status}: ${progress.percentage}%`);
|
|
388
|
+
* });
|
|
389
|
+
*
|
|
390
|
+
* stream.onComplete((result) => {
|
|
391
|
+
* console.log(`Detection complete! Created ${result.createdCount} comments`);
|
|
392
|
+
* });
|
|
393
|
+
*
|
|
394
|
+
* stream.onError((error) => {
|
|
395
|
+
* console.error('Detection failed:', error.message);
|
|
396
|
+
* });
|
|
397
|
+
*
|
|
398
|
+
* // Cleanup when done
|
|
399
|
+
* stream.close();
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
detectComments(resourceId, request = {}) {
|
|
403
|
+
const id = this.extractId(resourceId);
|
|
404
|
+
const url = `${this.baseUrl}/resources/${id}/detect-comments-stream`;
|
|
405
|
+
return createSSEStream(
|
|
406
|
+
url,
|
|
407
|
+
{
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: this.getHeaders(),
|
|
410
|
+
body: JSON.stringify(request)
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
progressEvents: ["comment-detection-started", "comment-detection-progress"],
|
|
414
|
+
completeEvent: "comment-detection-complete",
|
|
415
|
+
errorEvent: "comment-detection-error"
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Detect tags in a resource (streaming)
|
|
421
|
+
*
|
|
422
|
+
* Streams tag detection progress via Server-Sent Events.
|
|
423
|
+
* Uses AI to identify passages serving specific structural roles
|
|
424
|
+
* (e.g., IRAC, IMRAD, Toulmin) and creates tag annotations with dual-body structure.
|
|
425
|
+
*
|
|
426
|
+
* @param resourceId - Resource URI or ID
|
|
427
|
+
* @param request - Detection configuration (schema and categories to detect)
|
|
428
|
+
* @returns SSE stream controller with progress/complete/error callbacks
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```typescript
|
|
432
|
+
* const stream = sseClient.detectTags('http://localhost:4000/resources/doc-123', {
|
|
433
|
+
* schemaId: 'legal-irac',
|
|
434
|
+
* categories: ['Issue', 'Rule', 'Application', 'Conclusion']
|
|
435
|
+
* });
|
|
436
|
+
*
|
|
437
|
+
* stream.onProgress((progress) => {
|
|
438
|
+
* console.log(`${progress.status}: ${progress.percentage}%`);
|
|
439
|
+
* console.log(`Processing ${progress.currentCategory}...`);
|
|
440
|
+
* });
|
|
441
|
+
*
|
|
442
|
+
* stream.onComplete((result) => {
|
|
443
|
+
* console.log(`Detection complete! Created ${result.tagsCreated} tags`);
|
|
444
|
+
* });
|
|
445
|
+
*
|
|
446
|
+
* stream.onError((error) => {
|
|
447
|
+
* console.error('Detection failed:', error.message);
|
|
448
|
+
* });
|
|
449
|
+
*
|
|
450
|
+
* // Cleanup when done
|
|
451
|
+
* stream.close();
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
detectTags(resourceId, request) {
|
|
455
|
+
const id = this.extractId(resourceId);
|
|
456
|
+
const url = `${this.baseUrl}/resources/${id}/detect-tags-stream`;
|
|
457
|
+
return createSSEStream(
|
|
458
|
+
url,
|
|
459
|
+
{
|
|
460
|
+
method: "POST",
|
|
461
|
+
headers: this.getHeaders(),
|
|
462
|
+
body: JSON.stringify(request)
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
progressEvents: ["tag-detection-started", "tag-detection-progress"],
|
|
466
|
+
completeEvent: "tag-detection-complete",
|
|
467
|
+
errorEvent: "tag-detection-error"
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Subscribe to resource events (long-lived stream)
|
|
473
|
+
*
|
|
474
|
+
* Opens a long-lived SSE connection to receive real-time events for a resource.
|
|
475
|
+
* Used for collaborative editing - see events from other users as they happen.
|
|
476
|
+
*
|
|
477
|
+
* This stream does NOT have a complete event - it stays open until explicitly closed.
|
|
478
|
+
*
|
|
479
|
+
* @param resourceId - Resource URI or ID to subscribe to
|
|
480
|
+
* @returns SSE stream controller with event callback
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* const stream = sseClient.resourceEvents('http://localhost:4000/resources/doc-123');
|
|
485
|
+
*
|
|
486
|
+
* stream.onProgress((event) => {
|
|
487
|
+
* console.log(`Event: ${event.type}`);
|
|
488
|
+
* console.log(`User: ${event.userId}`);
|
|
489
|
+
* console.log(`Sequence: ${event.metadata.sequenceNumber}`);
|
|
490
|
+
* console.log(`Payload:`, event.payload);
|
|
491
|
+
* });
|
|
492
|
+
*
|
|
493
|
+
* stream.onError((error) => {
|
|
494
|
+
* console.error('Stream error:', error.message);
|
|
495
|
+
* });
|
|
496
|
+
*
|
|
497
|
+
* // Close when no longer needed (e.g., component unmount)
|
|
498
|
+
* stream.close();
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
resourceEvents(resourceId) {
|
|
502
|
+
const id = this.extractId(resourceId);
|
|
503
|
+
const url = `${this.baseUrl}/resources/${id}/events/stream`;
|
|
504
|
+
return createSSEStream(
|
|
505
|
+
url,
|
|
506
|
+
{
|
|
507
|
+
method: "GET",
|
|
508
|
+
headers: this.getHeaders()
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
progressEvents: ["*"],
|
|
512
|
+
// Accept all event types
|
|
513
|
+
completeEvent: null,
|
|
514
|
+
// Long-lived stream - no completion
|
|
515
|
+
errorEvent: "error",
|
|
516
|
+
// Generic error event
|
|
517
|
+
customEventHandler: true
|
|
518
|
+
// Use custom event handling
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
// src/client.ts
|
|
525
|
+
var APIError = class extends Error {
|
|
526
|
+
constructor(message, status, statusText, details) {
|
|
527
|
+
super(message);
|
|
528
|
+
this.status = status;
|
|
529
|
+
this.statusText = statusText;
|
|
530
|
+
this.details = details;
|
|
531
|
+
this.name = "APIError";
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
var SemiontApiClient = class {
|
|
535
|
+
http;
|
|
536
|
+
baseUrl;
|
|
537
|
+
accessToken = null;
|
|
538
|
+
/**
|
|
539
|
+
* SSE streaming client for real-time operations
|
|
540
|
+
*
|
|
541
|
+
* Separate from the main HTTP client to clearly mark streaming endpoints.
|
|
542
|
+
* Uses native fetch() instead of ky for SSE support.
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* ```typescript
|
|
546
|
+
* const stream = client.sse.detectAnnotations(
|
|
547
|
+
* resourceId,
|
|
548
|
+
* { entityTypes: ['Person', 'Organization'] }
|
|
549
|
+
* );
|
|
550
|
+
*
|
|
551
|
+
* stream.onProgress((p) => console.log(p.message));
|
|
552
|
+
* stream.onComplete((r) => console.log(`Found ${r.foundCount} entities`));
|
|
553
|
+
* stream.close();
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
sse;
|
|
557
|
+
constructor(config) {
|
|
558
|
+
const { baseUrl: baseUrl2, accessToken: accessToken2, timeout = 3e4, retry = 2 } = config;
|
|
559
|
+
this.baseUrl = baseUrl2.endsWith("/") ? baseUrl2.slice(0, -1) : baseUrl2;
|
|
560
|
+
this.http = ky.create({
|
|
561
|
+
timeout,
|
|
562
|
+
retry,
|
|
563
|
+
hooks: {
|
|
564
|
+
beforeRequest: [
|
|
565
|
+
(request) => {
|
|
566
|
+
if (this.accessToken) {
|
|
567
|
+
request.headers.set("Authorization", `Bearer ${this.accessToken}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
],
|
|
571
|
+
beforeError: [
|
|
572
|
+
async (error) => {
|
|
573
|
+
const { response } = error;
|
|
574
|
+
if (response) {
|
|
575
|
+
const body = await response.json().catch(() => ({}));
|
|
576
|
+
throw new APIError(
|
|
577
|
+
body.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
578
|
+
response.status,
|
|
579
|
+
response.statusText,
|
|
580
|
+
body
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
return error;
|
|
584
|
+
}
|
|
585
|
+
]
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
if (accessToken2) {
|
|
589
|
+
this.accessToken = accessToken2;
|
|
590
|
+
}
|
|
591
|
+
this.sse = new SSEClient({
|
|
592
|
+
baseUrl: this.baseUrl,
|
|
593
|
+
accessToken: this.accessToken || void 0
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Set the access token for authenticated requests
|
|
598
|
+
*/
|
|
599
|
+
setAccessToken(token) {
|
|
600
|
+
this.accessToken = token;
|
|
601
|
+
this.sse.setAccessToken(token);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Clear the access token
|
|
605
|
+
*/
|
|
606
|
+
clearAccessToken() {
|
|
607
|
+
this.accessToken = null;
|
|
608
|
+
this.sse.clearAccessToken();
|
|
609
|
+
}
|
|
610
|
+
// ============================================================================
|
|
611
|
+
// AUTHENTICATION
|
|
612
|
+
// ============================================================================
|
|
613
|
+
async authenticatePassword(email2, password) {
|
|
614
|
+
const response = await this.http.post(`${this.baseUrl}/api/tokens/password`, { json: { email: email2, password } }).json();
|
|
615
|
+
if (response.token) {
|
|
616
|
+
this.setAccessToken(response.token);
|
|
617
|
+
}
|
|
618
|
+
return response;
|
|
619
|
+
}
|
|
620
|
+
async refreshToken(token) {
|
|
621
|
+
const response = await this.http.post(`${this.baseUrl}/api/tokens/refresh`, { json: { refreshToken: token } }).json();
|
|
622
|
+
if (response.access_token) {
|
|
623
|
+
this.setAccessToken(response.access_token);
|
|
624
|
+
}
|
|
625
|
+
return response;
|
|
626
|
+
}
|
|
627
|
+
async authenticateGoogle(credential) {
|
|
628
|
+
const response = await this.http.post(`${this.baseUrl}/api/tokens/google`, { json: { credential } }).json();
|
|
629
|
+
if (response.token) {
|
|
630
|
+
this.setAccessToken(response.token);
|
|
631
|
+
}
|
|
632
|
+
return response;
|
|
633
|
+
}
|
|
634
|
+
async generateMCPToken() {
|
|
635
|
+
return this.http.post(`${this.baseUrl}/api/tokens/mcp-generate`).json();
|
|
636
|
+
}
|
|
637
|
+
// ============================================================================
|
|
638
|
+
// USERS
|
|
639
|
+
// ============================================================================
|
|
640
|
+
async getMe() {
|
|
641
|
+
return this.http.get(`${this.baseUrl}/api/users/me`).json();
|
|
642
|
+
}
|
|
643
|
+
async acceptTerms() {
|
|
644
|
+
return this.http.post(`${this.baseUrl}/api/users/accept-terms`).json();
|
|
645
|
+
}
|
|
646
|
+
async logout() {
|
|
647
|
+
return this.http.post(`${this.baseUrl}/api/users/logout`).json();
|
|
648
|
+
}
|
|
649
|
+
// ============================================================================
|
|
650
|
+
// RESOURCES
|
|
651
|
+
// ============================================================================
|
|
652
|
+
/**
|
|
653
|
+
* Create a new resource with binary content support
|
|
654
|
+
*
|
|
655
|
+
* @param data - Resource creation data
|
|
656
|
+
* @param data.name - Resource name
|
|
657
|
+
* @param data.file - File object or Buffer with binary content
|
|
658
|
+
* @param data.format - MIME type (e.g., 'text/markdown', 'image/png')
|
|
659
|
+
* @param data.entityTypes - Optional array of entity types
|
|
660
|
+
* @param data.language - Optional ISO 639-1 language code
|
|
661
|
+
* @param data.creationMethod - Optional creation method
|
|
662
|
+
* @param data.sourceAnnotationId - Optional source annotation ID
|
|
663
|
+
* @param data.sourceResourceId - Optional source resource ID
|
|
664
|
+
*/
|
|
665
|
+
async createResource(data) {
|
|
666
|
+
const formData = new FormData();
|
|
667
|
+
formData.append("name", data.name);
|
|
668
|
+
formData.append("format", data.format);
|
|
669
|
+
if (data.file instanceof File) {
|
|
670
|
+
formData.append("file", data.file);
|
|
671
|
+
} else if (Buffer.isBuffer(data.file)) {
|
|
672
|
+
const blob = new Blob([data.file], { type: data.format });
|
|
673
|
+
formData.append("file", blob, data.name);
|
|
674
|
+
} else {
|
|
675
|
+
throw new Error("file must be a File or Buffer");
|
|
676
|
+
}
|
|
677
|
+
if (data.entityTypes && data.entityTypes.length > 0) {
|
|
678
|
+
formData.append("entityTypes", JSON.stringify(data.entityTypes));
|
|
679
|
+
}
|
|
680
|
+
if (data.language) {
|
|
681
|
+
formData.append("language", data.language);
|
|
682
|
+
}
|
|
683
|
+
if (data.creationMethod) {
|
|
684
|
+
formData.append("creationMethod", data.creationMethod);
|
|
685
|
+
}
|
|
686
|
+
if (data.sourceAnnotationId) {
|
|
687
|
+
formData.append("sourceAnnotationId", data.sourceAnnotationId);
|
|
688
|
+
}
|
|
689
|
+
if (data.sourceResourceId) {
|
|
690
|
+
formData.append("sourceResourceId", data.sourceResourceId);
|
|
691
|
+
}
|
|
692
|
+
return this.http.post(`${this.baseUrl}/resources`, { body: formData }).json();
|
|
693
|
+
}
|
|
694
|
+
async getResource(resourceUri2) {
|
|
695
|
+
return this.http.get(resourceUri2).json();
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Get resource representation using W3C content negotiation
|
|
699
|
+
* Returns raw binary content (images, PDFs, text, etc.) with content type
|
|
700
|
+
*
|
|
701
|
+
* @param resourceUri - Full resource URI
|
|
702
|
+
* @param options - Options including Accept header for content negotiation
|
|
703
|
+
* @returns Object with data (ArrayBuffer) and contentType (string)
|
|
704
|
+
*
|
|
705
|
+
* @example
|
|
706
|
+
* ```typescript
|
|
707
|
+
* // Get markdown representation
|
|
708
|
+
* const { data, contentType } = await client.getResourceRepresentation(rUri, { accept: 'text/markdown' });
|
|
709
|
+
* const markdown = new TextDecoder().decode(data);
|
|
710
|
+
*
|
|
711
|
+
* // Get image representation
|
|
712
|
+
* const { data, contentType } = await client.getResourceRepresentation(rUri, { accept: 'image/png' });
|
|
713
|
+
* const blob = new Blob([data], { type: contentType });
|
|
714
|
+
*
|
|
715
|
+
* // Get PDF representation
|
|
716
|
+
* const { data, contentType } = await client.getResourceRepresentation(rUri, { accept: 'application/pdf' });
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
async getResourceRepresentation(resourceUri2, options) {
|
|
720
|
+
const response = await this.http.get(resourceUri2, {
|
|
721
|
+
headers: {
|
|
722
|
+
Accept: options?.accept || "text/plain"
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
726
|
+
const data = await response.arrayBuffer();
|
|
727
|
+
return { data, contentType };
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Get resource representation as a stream using W3C content negotiation
|
|
731
|
+
* Returns streaming binary content (for large files: videos, large PDFs, etc.)
|
|
732
|
+
*
|
|
733
|
+
* Use this for large files to avoid loading entire content into memory.
|
|
734
|
+
* The stream is consumed incrementally and the backend connection stays open
|
|
735
|
+
* until the stream is fully consumed or closed.
|
|
736
|
+
*
|
|
737
|
+
* @param resourceUri - Full resource URI
|
|
738
|
+
* @param options - Options including Accept header for content negotiation
|
|
739
|
+
* @returns Object with stream (ReadableStream) and contentType (string)
|
|
740
|
+
*
|
|
741
|
+
* @example
|
|
742
|
+
* ```typescript
|
|
743
|
+
* // Stream large file
|
|
744
|
+
* const { stream, contentType } = await client.getResourceRepresentationStream(rUri, {
|
|
745
|
+
* accept: 'video/mp4'
|
|
746
|
+
* });
|
|
747
|
+
*
|
|
748
|
+
* // Consume stream chunk by chunk (never loads entire file into memory)
|
|
749
|
+
* for await (const chunk of stream) {
|
|
750
|
+
* // Process chunk
|
|
751
|
+
* console.log(`Received ${chunk.length} bytes`);
|
|
752
|
+
* }
|
|
753
|
+
*
|
|
754
|
+
* // Or pipe to a file in Node.js
|
|
755
|
+
* const fileStream = fs.createWriteStream('output.mp4');
|
|
756
|
+
* const reader = stream.getReader();
|
|
757
|
+
* while (true) {
|
|
758
|
+
* const { done, value } = await reader.read();
|
|
759
|
+
* if (done) break;
|
|
760
|
+
* fileStream.write(value);
|
|
761
|
+
* }
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
764
|
+
async getResourceRepresentationStream(resourceUri2, options) {
|
|
765
|
+
const response = await this.http.get(resourceUri2, {
|
|
766
|
+
headers: {
|
|
767
|
+
Accept: options?.accept || "text/plain"
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
771
|
+
if (!response.body) {
|
|
772
|
+
throw new Error("Response body is null - cannot create stream");
|
|
773
|
+
}
|
|
774
|
+
return { stream: response.body, contentType };
|
|
775
|
+
}
|
|
776
|
+
async listResources(limit, archived, query) {
|
|
777
|
+
const searchParams = new URLSearchParams();
|
|
778
|
+
if (limit) searchParams.append("limit", limit.toString());
|
|
779
|
+
if (archived !== void 0) searchParams.append("archived", archived.toString());
|
|
780
|
+
if (query) searchParams.append("q", query);
|
|
781
|
+
return this.http.get(`${this.baseUrl}/resources`, { searchParams }).json();
|
|
782
|
+
}
|
|
783
|
+
async updateResource(resourceUri2, data) {
|
|
784
|
+
return this.http.patch(resourceUri2, { json: data }).json();
|
|
785
|
+
}
|
|
786
|
+
async getResourceEvents(resourceUri2) {
|
|
787
|
+
return this.http.get(`${resourceUri2}/events`).json();
|
|
788
|
+
}
|
|
789
|
+
async getResourceAnnotations(resourceUri2) {
|
|
790
|
+
return this.http.get(`${resourceUri2}/annotations`).json();
|
|
791
|
+
}
|
|
792
|
+
async getAnnotationLLMContext(resourceUri2, annotationId, options) {
|
|
793
|
+
const searchParams = new URLSearchParams();
|
|
794
|
+
if (options?.contextWindow) {
|
|
795
|
+
searchParams.append("contextWindow", options.contextWindow.toString());
|
|
796
|
+
}
|
|
797
|
+
return this.http.get(
|
|
798
|
+
`${resourceUri2}/annotations/${annotationId}/llm-context`,
|
|
799
|
+
{ searchParams }
|
|
800
|
+
).json();
|
|
801
|
+
}
|
|
802
|
+
async getResourceReferencedBy(resourceUri2) {
|
|
803
|
+
return this.http.get(`${resourceUri2}/referenced-by`).json();
|
|
804
|
+
}
|
|
805
|
+
async generateCloneToken(resourceUri2) {
|
|
806
|
+
return this.http.post(`${resourceUri2}/clone-with-token`).json();
|
|
807
|
+
}
|
|
808
|
+
async getResourceByToken(token) {
|
|
809
|
+
return this.http.get(`${this.baseUrl}/api/resources/token/${token}`).json();
|
|
810
|
+
}
|
|
811
|
+
async createResourceFromToken(data) {
|
|
812
|
+
return this.http.post(`${this.baseUrl}/api/resources/create-from-token`, { json: data }).json();
|
|
813
|
+
}
|
|
814
|
+
// ============================================================================
|
|
815
|
+
// ANNOTATIONS
|
|
816
|
+
// ============================================================================
|
|
817
|
+
async createAnnotation(resourceUri2, data) {
|
|
818
|
+
return this.http.post(`${resourceUri2}/annotations`, { json: data }).json();
|
|
819
|
+
}
|
|
820
|
+
async getAnnotation(annotationUri2) {
|
|
821
|
+
return this.http.get(annotationUri2).json();
|
|
822
|
+
}
|
|
823
|
+
async getResourceAnnotation(annotationUri2) {
|
|
824
|
+
return this.http.get(annotationUri2).json();
|
|
825
|
+
}
|
|
826
|
+
async listAnnotations(resourceUri2, motivation) {
|
|
827
|
+
const searchParams = new URLSearchParams();
|
|
828
|
+
if (motivation) searchParams.append("motivation", motivation);
|
|
829
|
+
return this.http.get(`${resourceUri2}/annotations`, { searchParams }).json();
|
|
830
|
+
}
|
|
831
|
+
async deleteAnnotation(annotationUri2) {
|
|
832
|
+
await this.http.delete(annotationUri2);
|
|
833
|
+
}
|
|
834
|
+
async updateAnnotationBody(annotationUri2, data) {
|
|
835
|
+
return this.http.put(`${annotationUri2}/body`, {
|
|
836
|
+
json: data
|
|
837
|
+
}).json();
|
|
838
|
+
}
|
|
839
|
+
async getAnnotationHistory(annotationUri2) {
|
|
840
|
+
return this.http.get(`${annotationUri2}/history`).json();
|
|
841
|
+
}
|
|
842
|
+
// ============================================================================
|
|
843
|
+
// ENTITY TYPES
|
|
844
|
+
// ============================================================================
|
|
845
|
+
async addEntityType(type) {
|
|
846
|
+
return this.http.post(`${this.baseUrl}/api/entity-types`, { json: { type } }).json();
|
|
847
|
+
}
|
|
848
|
+
async addEntityTypesBulk(types) {
|
|
849
|
+
return this.http.post(`${this.baseUrl}/api/entity-types/bulk`, { json: { tags: types } }).json();
|
|
850
|
+
}
|
|
851
|
+
async listEntityTypes() {
|
|
852
|
+
return this.http.get(`${this.baseUrl}/api/entity-types`).json();
|
|
853
|
+
}
|
|
854
|
+
// ============================================================================
|
|
855
|
+
// ADMIN
|
|
856
|
+
// ============================================================================
|
|
857
|
+
async listUsers() {
|
|
858
|
+
return this.http.get(`${this.baseUrl}/api/admin/users`).json();
|
|
859
|
+
}
|
|
860
|
+
async getUserStats() {
|
|
861
|
+
return this.http.get(`${this.baseUrl}/api/admin/users/stats`).json();
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Update a user by ID
|
|
865
|
+
* Note: Users use DID identifiers (did:web:domain:users:id), not HTTP URIs.
|
|
866
|
+
*/
|
|
867
|
+
async updateUser(id, data) {
|
|
868
|
+
return this.http.patch(`${this.baseUrl}/api/admin/users/${id}`, { json: data }).json();
|
|
869
|
+
}
|
|
870
|
+
async getOAuthConfig() {
|
|
871
|
+
return this.http.get(`${this.baseUrl}/api/admin/oauth/config`).json();
|
|
872
|
+
}
|
|
873
|
+
// ============================================================================
|
|
874
|
+
// JOB STATUS
|
|
875
|
+
// ============================================================================
|
|
876
|
+
async getJobStatus(id) {
|
|
877
|
+
return this.http.get(`${this.baseUrl}/api/jobs/${id}`).json();
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Poll a job until it completes or fails
|
|
881
|
+
* @param id - The job ID to poll
|
|
882
|
+
* @param options - Polling options
|
|
883
|
+
* @returns The final job status
|
|
884
|
+
*/
|
|
885
|
+
async pollJobUntilComplete(id, options) {
|
|
886
|
+
const interval = options?.interval ?? 1e3;
|
|
887
|
+
const timeout = options?.timeout ?? 6e4;
|
|
888
|
+
const startTime = Date.now();
|
|
889
|
+
while (true) {
|
|
890
|
+
const status = await this.getJobStatus(id);
|
|
891
|
+
if (options?.onProgress) {
|
|
892
|
+
options.onProgress(status);
|
|
893
|
+
}
|
|
894
|
+
if (status.status === "complete" || status.status === "failed" || status.status === "cancelled") {
|
|
895
|
+
return status;
|
|
896
|
+
}
|
|
897
|
+
if (Date.now() - startTime > timeout) {
|
|
898
|
+
throw new Error(`Job polling timeout after ${timeout}ms`);
|
|
899
|
+
}
|
|
900
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
// ============================================================================
|
|
904
|
+
// LLM CONTEXT
|
|
905
|
+
// ============================================================================
|
|
906
|
+
async getResourceLLMContext(resourceUri2, options) {
|
|
907
|
+
const searchParams = new URLSearchParams();
|
|
908
|
+
if (options?.depth !== void 0) searchParams.append("depth", options.depth.toString());
|
|
909
|
+
if (options?.maxResources !== void 0) searchParams.append("maxResources", options.maxResources.toString());
|
|
910
|
+
if (options?.includeContent !== void 0) searchParams.append("includeContent", options.includeContent.toString());
|
|
911
|
+
if (options?.includeSummary !== void 0) searchParams.append("includeSummary", options.includeSummary.toString());
|
|
912
|
+
return this.http.get(`${resourceUri2}/llm-context`, { searchParams }).json();
|
|
913
|
+
}
|
|
914
|
+
// ============================================================================
|
|
915
|
+
// SYSTEM STATUS
|
|
916
|
+
// ============================================================================
|
|
917
|
+
async healthCheck() {
|
|
918
|
+
return this.http.get(`${this.baseUrl}/api/health`).json();
|
|
919
|
+
}
|
|
920
|
+
async getStatus() {
|
|
921
|
+
return this.http.get(`${this.baseUrl}/api/status`).json();
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/branded-types.ts
|
|
926
|
+
function email(value) {
|
|
927
|
+
return value;
|
|
928
|
+
}
|
|
929
|
+
function authCode(value) {
|
|
930
|
+
return value;
|
|
931
|
+
}
|
|
932
|
+
function googleCredential(value) {
|
|
933
|
+
return value;
|
|
934
|
+
}
|
|
935
|
+
function accessToken(value) {
|
|
936
|
+
return value;
|
|
937
|
+
}
|
|
938
|
+
function refreshToken(value) {
|
|
939
|
+
return value;
|
|
940
|
+
}
|
|
941
|
+
function mcpToken(value) {
|
|
942
|
+
return value;
|
|
943
|
+
}
|
|
944
|
+
function cloneToken(value) {
|
|
945
|
+
return value;
|
|
946
|
+
}
|
|
947
|
+
function jobId(value) {
|
|
948
|
+
return value;
|
|
949
|
+
}
|
|
950
|
+
function userDID(value) {
|
|
951
|
+
return value;
|
|
952
|
+
}
|
|
953
|
+
function entityType(value) {
|
|
954
|
+
return value;
|
|
955
|
+
}
|
|
956
|
+
function searchQuery(value) {
|
|
957
|
+
return value;
|
|
958
|
+
}
|
|
959
|
+
function baseUrl(value) {
|
|
960
|
+
return value;
|
|
961
|
+
}
|
|
962
|
+
function resourceUri(uri) {
|
|
963
|
+
if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
|
|
964
|
+
throw new TypeError(`Expected ResourceUri, got: ${uri}`);
|
|
965
|
+
}
|
|
966
|
+
return uri;
|
|
967
|
+
}
|
|
968
|
+
function annotationUri(uri) {
|
|
969
|
+
if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
|
|
970
|
+
throw new TypeError(`Expected AnnotationUri, got: ${uri}`);
|
|
971
|
+
}
|
|
972
|
+
return uri;
|
|
973
|
+
}
|
|
974
|
+
function resourceAnnotationUri(uri) {
|
|
975
|
+
if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
|
|
976
|
+
throw new TypeError(`Expected ResourceAnnotationUri, got: ${uri}`);
|
|
977
|
+
}
|
|
978
|
+
if (!uri.includes("/resources/") || !uri.includes("/annotations/")) {
|
|
979
|
+
throw new TypeError(`Expected nested ResourceAnnotationUri format, got: ${uri}`);
|
|
980
|
+
}
|
|
981
|
+
return uri;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/utils/annotations.ts
|
|
985
|
+
function getBodySource(body) {
|
|
986
|
+
if (Array.isArray(body)) {
|
|
987
|
+
for (const item of body) {
|
|
988
|
+
if (typeof item === "object" && item !== null && "type" in item && "source" in item) {
|
|
989
|
+
const itemType = item.type;
|
|
990
|
+
const itemSource = item.source;
|
|
991
|
+
if (itemType === "SpecificResource" && typeof itemSource === "string") {
|
|
992
|
+
return resourceUri(itemSource);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
if (typeof body === "object" && body !== null && "type" in body && "source" in body) {
|
|
999
|
+
const bodyType = body.type;
|
|
1000
|
+
const bodySource = body.source;
|
|
1001
|
+
if (bodyType === "SpecificResource" && typeof bodySource === "string") {
|
|
1002
|
+
return resourceUri(bodySource);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function getBodyType(body) {
|
|
1008
|
+
if (Array.isArray(body)) {
|
|
1009
|
+
if (body.length === 0) {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
if (typeof body[0] === "object" && body[0] !== null && "type" in body[0]) {
|
|
1013
|
+
const firstType = body[0].type;
|
|
1014
|
+
if (firstType === "TextualBody" || firstType === "SpecificResource") {
|
|
1015
|
+
return firstType;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
if (typeof body === "object" && body !== null && "type" in body) {
|
|
1021
|
+
const bodyType = body.type;
|
|
1022
|
+
if (bodyType === "TextualBody" || bodyType === "SpecificResource") {
|
|
1023
|
+
return bodyType;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
function isBodyResolved(body) {
|
|
1029
|
+
return getBodySource(body) !== null;
|
|
1030
|
+
}
|
|
1031
|
+
function getTargetSource(target) {
|
|
1032
|
+
if (typeof target === "string") {
|
|
1033
|
+
return resourceUri(target);
|
|
1034
|
+
}
|
|
1035
|
+
return resourceUri(target.source);
|
|
1036
|
+
}
|
|
1037
|
+
function getTargetSelector(target) {
|
|
1038
|
+
if (typeof target === "string") {
|
|
1039
|
+
return void 0;
|
|
1040
|
+
}
|
|
1041
|
+
return target.selector;
|
|
1042
|
+
}
|
|
1043
|
+
function hasTargetSelector(target) {
|
|
1044
|
+
return typeof target !== "string" && target.selector !== void 0;
|
|
1045
|
+
}
|
|
1046
|
+
function getEntityTypes(annotation) {
|
|
1047
|
+
if (Array.isArray(annotation.body)) {
|
|
1048
|
+
const entityTags = [];
|
|
1049
|
+
for (const item of annotation.body) {
|
|
1050
|
+
if (typeof item === "object" && item !== null && "type" in item && "value" in item && "purpose" in item) {
|
|
1051
|
+
const itemType = item.type;
|
|
1052
|
+
const itemValue = item.value;
|
|
1053
|
+
const itemPurpose = item.purpose;
|
|
1054
|
+
if (itemType === "TextualBody" && itemPurpose === "tagging" && typeof itemValue === "string" && itemValue.length > 0) {
|
|
1055
|
+
entityTags.push(itemValue);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return entityTags;
|
|
1060
|
+
}
|
|
1061
|
+
return [];
|
|
1062
|
+
}
|
|
1063
|
+
function isHighlight(annotation) {
|
|
1064
|
+
return annotation.motivation === "highlighting";
|
|
1065
|
+
}
|
|
1066
|
+
function isReference(annotation) {
|
|
1067
|
+
return annotation.motivation === "linking";
|
|
1068
|
+
}
|
|
1069
|
+
function isAssessment(annotation) {
|
|
1070
|
+
return annotation.motivation === "assessing";
|
|
1071
|
+
}
|
|
1072
|
+
function isComment(annotation) {
|
|
1073
|
+
return annotation.motivation === "commenting";
|
|
1074
|
+
}
|
|
1075
|
+
function isTag(annotation) {
|
|
1076
|
+
return annotation.motivation === "tagging";
|
|
1077
|
+
}
|
|
1078
|
+
function getCommentText(annotation) {
|
|
1079
|
+
if (!isComment(annotation)) return void 0;
|
|
1080
|
+
const body = Array.isArray(annotation.body) ? annotation.body[0] : annotation.body;
|
|
1081
|
+
if (body && "value" in body) {
|
|
1082
|
+
return body.value;
|
|
1083
|
+
}
|
|
1084
|
+
return void 0;
|
|
1085
|
+
}
|
|
1086
|
+
function getTagCategory(annotation) {
|
|
1087
|
+
if (!isTag(annotation)) return void 0;
|
|
1088
|
+
const bodies = Array.isArray(annotation.body) ? annotation.body : [annotation.body];
|
|
1089
|
+
const taggingBody = bodies.find((b) => b && "purpose" in b && b.purpose === "tagging");
|
|
1090
|
+
if (taggingBody && "value" in taggingBody) {
|
|
1091
|
+
return taggingBody.value;
|
|
1092
|
+
}
|
|
1093
|
+
return void 0;
|
|
1094
|
+
}
|
|
1095
|
+
function getTagSchemaId(annotation) {
|
|
1096
|
+
if (!isTag(annotation)) return void 0;
|
|
1097
|
+
const bodies = Array.isArray(annotation.body) ? annotation.body : [annotation.body];
|
|
1098
|
+
const classifyingBody = bodies.find((b) => b && "purpose" in b && b.purpose === "classifying");
|
|
1099
|
+
if (classifyingBody && "value" in classifyingBody) {
|
|
1100
|
+
return classifyingBody.value;
|
|
1101
|
+
}
|
|
1102
|
+
return void 0;
|
|
1103
|
+
}
|
|
1104
|
+
function isStubReference(annotation) {
|
|
1105
|
+
return isReference(annotation) && !isBodyResolved(annotation.body);
|
|
1106
|
+
}
|
|
1107
|
+
function isResolvedReference(annotation) {
|
|
1108
|
+
return isReference(annotation) && isBodyResolved(annotation.body);
|
|
1109
|
+
}
|
|
1110
|
+
function getExactText(selector) {
|
|
1111
|
+
if (!selector) {
|
|
1112
|
+
return "";
|
|
1113
|
+
}
|
|
1114
|
+
const selectors = Array.isArray(selector) ? selector : [selector];
|
|
1115
|
+
const quoteSelector = selectors.find((s) => s.type === "TextQuoteSelector");
|
|
1116
|
+
if (quoteSelector) {
|
|
1117
|
+
return quoteSelector.exact;
|
|
1118
|
+
}
|
|
1119
|
+
return "";
|
|
1120
|
+
}
|
|
1121
|
+
function getAnnotationExactText(annotation) {
|
|
1122
|
+
const selector = getTargetSelector(annotation.target);
|
|
1123
|
+
return getExactText(selector);
|
|
1124
|
+
}
|
|
1125
|
+
function getPrimarySelector(selector) {
|
|
1126
|
+
if (Array.isArray(selector)) {
|
|
1127
|
+
if (selector.length === 0) {
|
|
1128
|
+
throw new Error("Empty selector array");
|
|
1129
|
+
}
|
|
1130
|
+
const first = selector[0];
|
|
1131
|
+
if (!first) {
|
|
1132
|
+
throw new Error("Invalid selector array");
|
|
1133
|
+
}
|
|
1134
|
+
return first;
|
|
1135
|
+
}
|
|
1136
|
+
return selector;
|
|
1137
|
+
}
|
|
1138
|
+
function getTextPositionSelector(selector) {
|
|
1139
|
+
if (!selector) return null;
|
|
1140
|
+
const selectors = Array.isArray(selector) ? selector : [selector];
|
|
1141
|
+
const found = selectors.find((s) => s.type === "TextPositionSelector");
|
|
1142
|
+
if (!found) return null;
|
|
1143
|
+
return found.type === "TextPositionSelector" ? found : null;
|
|
1144
|
+
}
|
|
1145
|
+
function getTextQuoteSelector(selector) {
|
|
1146
|
+
const selectors = Array.isArray(selector) ? selector : [selector];
|
|
1147
|
+
const found = selectors.find((s) => s.type === "TextQuoteSelector");
|
|
1148
|
+
if (!found) return null;
|
|
1149
|
+
return found.type === "TextQuoteSelector" ? found : null;
|
|
1150
|
+
}
|
|
1151
|
+
function getSvgSelector(selector) {
|
|
1152
|
+
if (!selector) return null;
|
|
1153
|
+
const selectors = Array.isArray(selector) ? selector : [selector];
|
|
1154
|
+
const found = selectors.find((s) => s.type === "SvgSelector");
|
|
1155
|
+
if (!found) return null;
|
|
1156
|
+
return found.type === "SvgSelector" ? found : null;
|
|
1157
|
+
}
|
|
1158
|
+
function validateSvgMarkup(svg) {
|
|
1159
|
+
if (!svg.includes('xmlns="http://www.w3.org/2000/svg"')) {
|
|
1160
|
+
return 'SVG must include xmlns="http://www.w3.org/2000/svg" attribute';
|
|
1161
|
+
}
|
|
1162
|
+
if (!svg.includes("<svg") || !svg.includes("</svg>")) {
|
|
1163
|
+
return "SVG must have opening and closing tags";
|
|
1164
|
+
}
|
|
1165
|
+
const shapeElements = ["rect", "circle", "ellipse", "polygon", "polyline", "path", "line"];
|
|
1166
|
+
const hasShape = shapeElements.some(
|
|
1167
|
+
(shape) => svg.includes(`<${shape}`) || svg.includes(`<${shape} `)
|
|
1168
|
+
);
|
|
1169
|
+
if (!hasShape) {
|
|
1170
|
+
return "SVG must contain at least one shape element (rect, circle, ellipse, polygon, polyline, path, or line)";
|
|
1171
|
+
}
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
function extractBoundingBox(svg) {
|
|
1175
|
+
const viewBoxMatch = svg.match(/<svg[^>]*viewBox="([^"]+)"/);
|
|
1176
|
+
if (viewBoxMatch) {
|
|
1177
|
+
const values = viewBoxMatch[1].split(/\s+/).map(parseFloat);
|
|
1178
|
+
if (values.length === 4 && values.every((v) => !isNaN(v))) {
|
|
1179
|
+
return {
|
|
1180
|
+
x: values[0],
|
|
1181
|
+
y: values[1],
|
|
1182
|
+
width: values[2],
|
|
1183
|
+
height: values[3]
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const svgTagMatch = svg.match(/<svg[^>]*>/);
|
|
1188
|
+
if (svgTagMatch) {
|
|
1189
|
+
const svgTag = svgTagMatch[0];
|
|
1190
|
+
const widthMatch = svgTag.match(/width="([^"]+)"/);
|
|
1191
|
+
const heightMatch = svgTag.match(/height="([^"]+)"/);
|
|
1192
|
+
if (widthMatch && heightMatch) {
|
|
1193
|
+
const width = parseFloat(widthMatch[1]);
|
|
1194
|
+
const height = parseFloat(heightMatch[1]);
|
|
1195
|
+
if (!isNaN(width) && !isNaN(height)) {
|
|
1196
|
+
return { x: 0, y: 0, width, height };
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/utils/events.ts
|
|
1204
|
+
function getAnnotationUriFromEvent(event) {
|
|
1205
|
+
const eventData = event.event;
|
|
1206
|
+
const payload = eventData.payload;
|
|
1207
|
+
if (!payload) {
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
switch (eventData.type) {
|
|
1211
|
+
case "annotation.added":
|
|
1212
|
+
return payload.annotation?.id || null;
|
|
1213
|
+
case "annotation.removed":
|
|
1214
|
+
case "annotation.body.updated":
|
|
1215
|
+
if (payload.annotationId && eventData.resourceId) {
|
|
1216
|
+
try {
|
|
1217
|
+
const resourceUri2 = eventData.resourceId;
|
|
1218
|
+
const baseUrl2 = resourceUri2.substring(0, resourceUri2.lastIndexOf("/resources/"));
|
|
1219
|
+
return `${baseUrl2}/annotations/${payload.annotationId}`;
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return null;
|
|
1225
|
+
default:
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
function isEventRelatedToAnnotation(event, annotationUri2) {
|
|
1230
|
+
const eventAnnotationUri = getAnnotationUriFromEvent(event);
|
|
1231
|
+
return eventAnnotationUri === annotationUri2;
|
|
1232
|
+
}
|
|
1233
|
+
function isResourceEvent(event) {
|
|
1234
|
+
return event && typeof event.event === "object" && typeof event.event.id === "string" && typeof event.event.timestamp === "string" && typeof event.event.resourceId === "string" && typeof event.event.type === "string" && typeof event.metadata === "object" && typeof event.metadata.sequenceNumber === "number";
|
|
1235
|
+
}
|
|
1236
|
+
function formatEventType(type, t, payload) {
|
|
1237
|
+
switch (type) {
|
|
1238
|
+
case "resource.created":
|
|
1239
|
+
return t("resourceCreated");
|
|
1240
|
+
case "resource.cloned":
|
|
1241
|
+
return t("resourceCloned");
|
|
1242
|
+
case "resource.archived":
|
|
1243
|
+
return t("resourceArchived");
|
|
1244
|
+
case "resource.unarchived":
|
|
1245
|
+
return t("resourceUnarchived");
|
|
1246
|
+
case "annotation.added": {
|
|
1247
|
+
const motivation = payload?.annotation?.motivation;
|
|
1248
|
+
if (motivation === "highlighting") return t("highlightAdded");
|
|
1249
|
+
if (motivation === "linking") return t("referenceCreated");
|
|
1250
|
+
if (motivation === "assessing") return t("assessmentAdded");
|
|
1251
|
+
return t("annotationAdded");
|
|
1252
|
+
}
|
|
1253
|
+
case "annotation.removed": {
|
|
1254
|
+
return t("annotationRemoved");
|
|
1255
|
+
}
|
|
1256
|
+
case "annotation.body.updated": {
|
|
1257
|
+
return t("annotationBodyUpdated");
|
|
1258
|
+
}
|
|
1259
|
+
case "entitytag.added":
|
|
1260
|
+
return t("entitytagAdded");
|
|
1261
|
+
case "entitytag.removed":
|
|
1262
|
+
return t("entitytagRemoved");
|
|
1263
|
+
case "entitytype.added":
|
|
1264
|
+
return t("entitytypeAdded");
|
|
1265
|
+
case "job.completed":
|
|
1266
|
+
case "job.started":
|
|
1267
|
+
case "job.progress":
|
|
1268
|
+
case "job.failed":
|
|
1269
|
+
return t("jobEvent");
|
|
1270
|
+
default:
|
|
1271
|
+
const _exhaustiveCheck = type;
|
|
1272
|
+
return _exhaustiveCheck;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function getEventEmoji(type, payload) {
|
|
1276
|
+
switch (type) {
|
|
1277
|
+
case "resource.created":
|
|
1278
|
+
case "resource.cloned":
|
|
1279
|
+
case "resource.archived":
|
|
1280
|
+
case "resource.unarchived":
|
|
1281
|
+
return "\u{1F4C4}";
|
|
1282
|
+
case "annotation.added": {
|
|
1283
|
+
const motivation = payload?.annotation?.motivation;
|
|
1284
|
+
if (motivation === "highlighting") return "\u{1F7E1}";
|
|
1285
|
+
if (motivation === "linking") return "\u{1F535}";
|
|
1286
|
+
if (motivation === "assessing") return "\u{1F534}";
|
|
1287
|
+
return "\u{1F4DD}";
|
|
1288
|
+
}
|
|
1289
|
+
case "annotation.removed": {
|
|
1290
|
+
return "\u{1F5D1}\uFE0F";
|
|
1291
|
+
}
|
|
1292
|
+
case "annotation.body.updated": {
|
|
1293
|
+
return "\u270F\uFE0F";
|
|
1294
|
+
}
|
|
1295
|
+
case "entitytag.added":
|
|
1296
|
+
case "entitytag.removed":
|
|
1297
|
+
return "\u{1F3F7}\uFE0F";
|
|
1298
|
+
case "entitytype.added":
|
|
1299
|
+
return "\u{1F3F7}\uFE0F";
|
|
1300
|
+
// Same emoji as entitytag (global entity type collection)
|
|
1301
|
+
case "job.completed":
|
|
1302
|
+
return "\u{1F517}";
|
|
1303
|
+
// Link emoji for linked document creation
|
|
1304
|
+
case "job.started":
|
|
1305
|
+
case "job.progress":
|
|
1306
|
+
return "\u2699\uFE0F";
|
|
1307
|
+
// Gear for job processing
|
|
1308
|
+
case "job.failed":
|
|
1309
|
+
return "\u274C";
|
|
1310
|
+
// X mark for failed jobs
|
|
1311
|
+
default:
|
|
1312
|
+
const _exhaustiveCheck = type;
|
|
1313
|
+
return _exhaustiveCheck;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function formatRelativeTime(timestamp, t) {
|
|
1317
|
+
const date = new Date(timestamp);
|
|
1318
|
+
const now = /* @__PURE__ */ new Date();
|
|
1319
|
+
const diffMs = now.getTime() - date.getTime();
|
|
1320
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
1321
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
1322
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
1323
|
+
if (diffMins < 1) return t("justNow");
|
|
1324
|
+
if (diffMins < 60) return t("minutesAgo", { count: diffMins });
|
|
1325
|
+
if (diffHours < 24) return t("hoursAgo", { count: diffHours });
|
|
1326
|
+
if (diffDays < 7) return t("daysAgo", { count: diffDays });
|
|
1327
|
+
return date.toLocaleDateString();
|
|
1328
|
+
}
|
|
1329
|
+
function truncateText(text, maxLength = 50) {
|
|
1330
|
+
const trimmed = text.trim();
|
|
1331
|
+
return trimmed.length > maxLength ? trimmed.substring(0, maxLength) + "..." : trimmed;
|
|
1332
|
+
}
|
|
1333
|
+
function getEventDisplayContent(event, annotations, allEvents) {
|
|
1334
|
+
const eventData = event.event;
|
|
1335
|
+
const payload = eventData.payload;
|
|
1336
|
+
switch (eventData.type) {
|
|
1337
|
+
case "resource.created":
|
|
1338
|
+
case "resource.cloned": {
|
|
1339
|
+
return { exact: payload.name, isQuoted: false, isTag: false };
|
|
1340
|
+
}
|
|
1341
|
+
// Unified annotation events
|
|
1342
|
+
case "annotation.body.updated": {
|
|
1343
|
+
const annotation = annotations.find(
|
|
1344
|
+
(a) => a.id.endsWith(`/annotations/${payload.annotationId}`)
|
|
1345
|
+
);
|
|
1346
|
+
if (annotation?.target) {
|
|
1347
|
+
try {
|
|
1348
|
+
const targetSelector = getTargetSelector(annotation.target);
|
|
1349
|
+
const exact = getExactText(targetSelector);
|
|
1350
|
+
if (exact) {
|
|
1351
|
+
return { exact: truncateText(exact), isQuoted: true, isTag: false };
|
|
1352
|
+
}
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
case "annotation.removed": {
|
|
1359
|
+
const addedEvent = allEvents.find(
|
|
1360
|
+
(e) => e.event.type === "annotation.added" && e.event.payload.annotation?.id?.endsWith(`/annotations/${payload.annotationId}`)
|
|
1361
|
+
);
|
|
1362
|
+
if (addedEvent) {
|
|
1363
|
+
const addedPayload = addedEvent.event.payload;
|
|
1364
|
+
try {
|
|
1365
|
+
const exact = getExactText(addedPayload.annotation.target.selector);
|
|
1366
|
+
if (exact) {
|
|
1367
|
+
return { exact: truncateText(exact), isQuoted: true, isTag: false };
|
|
1368
|
+
}
|
|
1369
|
+
} catch {
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
case "annotation.added": {
|
|
1375
|
+
try {
|
|
1376
|
+
const exact = getExactText(payload.annotation.target.selector);
|
|
1377
|
+
if (exact) {
|
|
1378
|
+
return { exact: truncateText(exact), isQuoted: true, isTag: false };
|
|
1379
|
+
}
|
|
1380
|
+
} catch {
|
|
1381
|
+
}
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
case "entitytag.added":
|
|
1385
|
+
case "entitytag.removed": {
|
|
1386
|
+
return { exact: payload.entityType, isQuoted: false, isTag: true };
|
|
1387
|
+
}
|
|
1388
|
+
case "job.completed": {
|
|
1389
|
+
if (payload.annotationUri) {
|
|
1390
|
+
const annotation = annotations.find(
|
|
1391
|
+
(a) => a.id === payload.annotationUri
|
|
1392
|
+
);
|
|
1393
|
+
if (annotation?.target) {
|
|
1394
|
+
try {
|
|
1395
|
+
const targetSelector = getTargetSelector(annotation.target);
|
|
1396
|
+
const exact = getExactText(targetSelector);
|
|
1397
|
+
if (exact) {
|
|
1398
|
+
return { exact: truncateText(exact), isQuoted: true, isTag: false };
|
|
1399
|
+
}
|
|
1400
|
+
} catch {
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return null;
|
|
1405
|
+
}
|
|
1406
|
+
default:
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function getEventEntityTypes(event) {
|
|
1411
|
+
const eventData = event.event;
|
|
1412
|
+
if (eventData.type === "annotation.added") {
|
|
1413
|
+
const payload = eventData.payload;
|
|
1414
|
+
const motivation = payload?.annotation?.motivation;
|
|
1415
|
+
if (motivation === "linking") {
|
|
1416
|
+
return payload.annotation?.body?.entityTypes ?? [];
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return [];
|
|
1420
|
+
}
|
|
1421
|
+
function getResourceCreationDetails(event) {
|
|
1422
|
+
const eventData = event.event;
|
|
1423
|
+
const payload = eventData.payload;
|
|
1424
|
+
if (eventData.type === "resource.created") {
|
|
1425
|
+
return {
|
|
1426
|
+
type: "created",
|
|
1427
|
+
method: payload.creationMethod || "unknown",
|
|
1428
|
+
userId: eventData.userId,
|
|
1429
|
+
metadata: payload.metadata
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
if (eventData.type === "resource.cloned") {
|
|
1433
|
+
return {
|
|
1434
|
+
type: "cloned",
|
|
1435
|
+
method: payload.creationMethod || "clone",
|
|
1436
|
+
userId: eventData.userId,
|
|
1437
|
+
sourceDocId: payload.parentResourceId,
|
|
1438
|
+
parentResourceId: payload.parentResourceId,
|
|
1439
|
+
metadata: payload.metadata
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/utils/locales.ts
|
|
1446
|
+
var LOCALES = [
|
|
1447
|
+
{ code: "ar", nativeName: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", englishName: "Arabic" },
|
|
1448
|
+
{ code: "bn", nativeName: "\u09AC\u09BE\u0982\u09B2\u09BE", englishName: "Bengali" },
|
|
1449
|
+
{ code: "cs", nativeName: "\u010Ce\u0161tina", englishName: "Czech" },
|
|
1450
|
+
{ code: "da", nativeName: "Dansk", englishName: "Danish" },
|
|
1451
|
+
{ code: "de", nativeName: "Deutsch", englishName: "German" },
|
|
1452
|
+
{ code: "el", nativeName: "\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC", englishName: "Greek" },
|
|
1453
|
+
{ code: "en", nativeName: "English", englishName: "English" },
|
|
1454
|
+
{ code: "es", nativeName: "Espa\xF1ol", englishName: "Spanish" },
|
|
1455
|
+
{ code: "fa", nativeName: "\u0641\u0627\u0631\u0633\u06CC", englishName: "Persian" },
|
|
1456
|
+
{ code: "fi", nativeName: "Suomi", englishName: "Finnish" },
|
|
1457
|
+
{ code: "fr", nativeName: "Fran\xE7ais", englishName: "French" },
|
|
1458
|
+
{ code: "he", nativeName: "\u05E2\u05D1\u05E8\u05D9\u05EA", englishName: "Hebrew" },
|
|
1459
|
+
{ code: "hi", nativeName: "\u0939\u093F\u0928\u094D\u0926\u0940", englishName: "Hindi" },
|
|
1460
|
+
{ code: "id", nativeName: "Bahasa Indonesia", englishName: "Indonesian" },
|
|
1461
|
+
{ code: "it", nativeName: "Italiano", englishName: "Italian" },
|
|
1462
|
+
{ code: "ja", nativeName: "\u65E5\u672C\u8A9E", englishName: "Japanese" },
|
|
1463
|
+
{ code: "ko", nativeName: "\uD55C\uAD6D\uC5B4", englishName: "Korean" },
|
|
1464
|
+
{ code: "ms", nativeName: "Bahasa Melayu", englishName: "Malay" },
|
|
1465
|
+
{ code: "nl", nativeName: "Nederlands", englishName: "Dutch" },
|
|
1466
|
+
{ code: "no", nativeName: "Norsk", englishName: "Norwegian" },
|
|
1467
|
+
{ code: "pl", nativeName: "Polski", englishName: "Polish" },
|
|
1468
|
+
{ code: "pt", nativeName: "Portugu\xEAs", englishName: "Portuguese" },
|
|
1469
|
+
{ code: "ro", nativeName: "Rom\xE2n\u0103", englishName: "Romanian" },
|
|
1470
|
+
{ code: "sv", nativeName: "Svenska", englishName: "Swedish" },
|
|
1471
|
+
{ code: "th", nativeName: "\u0E44\u0E17\u0E22", englishName: "Thai" },
|
|
1472
|
+
{ code: "tr", nativeName: "T\xFCrk\xE7e", englishName: "Turkish" },
|
|
1473
|
+
{ code: "uk", nativeName: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", englishName: "Ukrainian" },
|
|
1474
|
+
{ code: "vi", nativeName: "Ti\u1EBFng Vi\u1EC7t", englishName: "Vietnamese" },
|
|
1475
|
+
{ code: "zh", nativeName: "\u4E2D\u6587", englishName: "Chinese" }
|
|
1476
|
+
];
|
|
1477
|
+
var localeByCode = new Map(
|
|
1478
|
+
LOCALES.map((locale) => [locale.code.toLowerCase(), locale])
|
|
1479
|
+
);
|
|
1480
|
+
function getLocaleInfo(code) {
|
|
1481
|
+
if (!code) return void 0;
|
|
1482
|
+
return localeByCode.get(code.toLowerCase());
|
|
1483
|
+
}
|
|
1484
|
+
function getLocaleNativeName(code) {
|
|
1485
|
+
return getLocaleInfo(code)?.nativeName;
|
|
1486
|
+
}
|
|
1487
|
+
function getLocaleEnglishName(code) {
|
|
1488
|
+
return getLocaleInfo(code)?.englishName;
|
|
1489
|
+
}
|
|
1490
|
+
function formatLocaleDisplay(code) {
|
|
1491
|
+
if (!code) return void 0;
|
|
1492
|
+
const info = getLocaleInfo(code);
|
|
1493
|
+
if (!info) return code;
|
|
1494
|
+
return `${info.nativeName} (${code.toLowerCase()})`;
|
|
1495
|
+
}
|
|
1496
|
+
function getAllLocaleCodes() {
|
|
1497
|
+
return LOCALES.map((l) => l.code);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/utils/resources.ts
|
|
1501
|
+
function getResourceId(resource) {
|
|
1502
|
+
if (!resource) return void 0;
|
|
1503
|
+
const fullId = resource["@id"];
|
|
1504
|
+
if (fullId.includes("/resources/")) {
|
|
1505
|
+
const parts = fullId.split("/resources/");
|
|
1506
|
+
const lastPart = parts[parts.length - 1];
|
|
1507
|
+
return lastPart || void 0;
|
|
1508
|
+
}
|
|
1509
|
+
return void 0;
|
|
1510
|
+
}
|
|
1511
|
+
function getPrimaryRepresentation(resource) {
|
|
1512
|
+
if (!resource?.representations) return void 0;
|
|
1513
|
+
const reps = Array.isArray(resource.representations) ? resource.representations : [resource.representations];
|
|
1514
|
+
return reps[0];
|
|
1515
|
+
}
|
|
1516
|
+
function getPrimaryMediaType(resource) {
|
|
1517
|
+
return getPrimaryRepresentation(resource)?.mediaType;
|
|
1518
|
+
}
|
|
1519
|
+
function getChecksum(resource) {
|
|
1520
|
+
return getPrimaryRepresentation(resource)?.checksum;
|
|
1521
|
+
}
|
|
1522
|
+
function getLanguage(resource) {
|
|
1523
|
+
return getPrimaryRepresentation(resource)?.language;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// src/utils/validation.ts
|
|
1527
|
+
var JWTTokenSchema = {
|
|
1528
|
+
parse(token) {
|
|
1529
|
+
if (typeof token !== "string") {
|
|
1530
|
+
throw new Error("Token must be a string");
|
|
1531
|
+
}
|
|
1532
|
+
if (!token || token.length === 0) {
|
|
1533
|
+
throw new Error("Token is required");
|
|
1534
|
+
}
|
|
1535
|
+
const jwtRegex = /^[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]*$/;
|
|
1536
|
+
if (!jwtRegex.test(token)) {
|
|
1537
|
+
throw new Error("Invalid JWT token format");
|
|
1538
|
+
}
|
|
1539
|
+
return token;
|
|
1540
|
+
},
|
|
1541
|
+
safeParse(token) {
|
|
1542
|
+
try {
|
|
1543
|
+
const validated = this.parse(token);
|
|
1544
|
+
return { success: true, data: validated };
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
return {
|
|
1547
|
+
success: false,
|
|
1548
|
+
error: error instanceof Error ? error.message : "Invalid JWT token"
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
45
1552
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1553
|
+
function validateData(schema, data) {
|
|
1554
|
+
try {
|
|
1555
|
+
const validated = schema.parse(data);
|
|
1556
|
+
return { success: true, data: validated };
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
return {
|
|
1559
|
+
success: false,
|
|
1560
|
+
error: error instanceof Error ? error.message : "Validation failed"
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function isValidEmail(email2) {
|
|
1565
|
+
if (email2.length < 1 || email2.length > 255) {
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1569
|
+
return emailRegex.test(email2);
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// src/mime-utils.ts
|
|
1573
|
+
function getExtensionForMimeType(mimeType) {
|
|
1574
|
+
const map = {
|
|
1575
|
+
"text/plain": "txt",
|
|
1576
|
+
"text/markdown": "md",
|
|
1577
|
+
"image/png": "png",
|
|
1578
|
+
"image/jpeg": "jpg"
|
|
1579
|
+
};
|
|
1580
|
+
return map[mimeType] || "dat";
|
|
1581
|
+
}
|
|
1582
|
+
function isImageMimeType(mimeType) {
|
|
1583
|
+
return mimeType === "image/png" || mimeType === "image/jpeg";
|
|
1584
|
+
}
|
|
1585
|
+
function isTextMimeType(mimeType) {
|
|
1586
|
+
return mimeType === "text/plain" || mimeType === "text/markdown";
|
|
1587
|
+
}
|
|
1588
|
+
function getMimeCategory(mimeType) {
|
|
1589
|
+
if (isTextMimeType(mimeType)) {
|
|
1590
|
+
return "text";
|
|
1591
|
+
}
|
|
1592
|
+
if (isImageMimeType(mimeType)) {
|
|
1593
|
+
return "image";
|
|
1594
|
+
}
|
|
1595
|
+
return "unsupported";
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
export { APIError, JWTTokenSchema, LOCALES, SSEClient, SemiontApiClient, accessToken, annotationUri, authCode, baseUrl, cloneToken, email, entityType, extractBoundingBox, formatEventType, formatLocaleDisplay, formatRelativeTime, getAllLocaleCodes, getAnnotationExactText, getAnnotationUriFromEvent, getBodySource, getBodyType, getChecksum, getCommentText, getEntityTypes, getEventDisplayContent, getEventEmoji, getEventEntityTypes, getExactText, getExtensionForMimeType, getLanguage, getLocaleEnglishName, getLocaleInfo, getLocaleNativeName, getMimeCategory, getPrimaryMediaType, getPrimaryRepresentation, getPrimarySelector, getResourceCreationDetails, getResourceId, getSvgSelector, getTagCategory, getTagSchemaId, getTargetSelector, getTargetSource, getTextPositionSelector, getTextQuoteSelector, googleCredential, hasTargetSelector, isAssessment, isBodyResolved, isComment, isEventRelatedToAnnotation, isHighlight, isImageMimeType, isReference, isResolvedReference, isResourceEvent, isStubReference, isTag, isTextMimeType, isValidEmail, jobId, mcpToken, refreshToken, resourceAnnotationUri, resourceUri, searchQuery, userDID, validateData, validateSvgMarkup };
|
|
1599
|
+
//# sourceMappingURL=index.js.map
|
|
62
1600
|
//# sourceMappingURL=index.js.map
|