@larc-iu/plaid-client 0.2.0-alpha.2 → 0.2.0-alpha.20
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/index.d.ts +1638 -156
- package/package.json +1 -1
- package/src/batchRef.js +128 -0
- package/src/constraints.js +84 -0
- package/src/created.js +29 -0
- package/src/documentLock.js +342 -0
- package/src/events.js +266 -0
- package/src/http.js +719 -90
- package/src/ids.js +54 -0
- package/src/index.js +2781 -645
- package/src/metadataOps.js +117 -0
- package/src/pagination.js +19 -35
- package/src/provenance.js +268 -27
- package/src/replayed.js +27 -0
- package/src/requester.js +56 -0
- package/src/roles.js +26 -0
- package/src/serviceSchema.js +87 -33
- package/src/services.js +550 -54
- package/src/sse.js +60 -3
- package/src/textEdits.js +191 -0
- package/src/transforms.js +11 -1
package/src/roles.js
CHANGED
|
@@ -25,6 +25,32 @@ export const PLAID_NAMESPACE = 'plaid';
|
|
|
25
25
|
export const ROLE_KEY = 'role';
|
|
26
26
|
|
|
27
27
|
/** The fixed role inventory. */
|
|
28
|
+
/**
|
|
29
|
+
* Layer config key naming the metadata keys a token born of a SPLIT inherits
|
|
30
|
+
* from the token it came from. Plaid honors it without knowing what the keys
|
|
31
|
+
* mean; see the manual's `Metadata Preserved Across a Split`. An app declares
|
|
32
|
+
* it so that a split in ANY app, including one that has never heard of these
|
|
33
|
+
* keys, does not silently drop them.
|
|
34
|
+
*/
|
|
35
|
+
export const PRESERVE_ON_SPLIT_KEY = 'preserveOnSplit';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Layer config key (true) asking Plaid to take a text edit plainly on the
|
|
39
|
+
* layer: an edit inside a token, or touching its edge with no whitespace
|
|
40
|
+
* between, grows or shrinks it, a typed space never splits it, a deleted one
|
|
41
|
+
* never joins two, and only a token whose whole text is deleted goes. See the
|
|
42
|
+
* manual's `Changing a text's body`.
|
|
43
|
+
*/
|
|
44
|
+
export const PLAIN_EDITS_KEY = 'plainEdits';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Layer config key (true), beside PLAIN_EDITS_KEY: a space typed inside a
|
|
48
|
+
* token of the text's words splits it, the token going on the half sharing
|
|
49
|
+
* more letters with it. Declared on a word layer another app shares, it holds
|
|
50
|
+
* for the whole text.
|
|
51
|
+
*/
|
|
52
|
+
export const SPLIT_ON_SPACE_KEY = 'splitOnSpace';
|
|
53
|
+
|
|
28
54
|
export const ROLES = Object.freeze({
|
|
29
55
|
BASELINE: 'baseline',
|
|
30
56
|
SENTENCE: 'sentence',
|
package/src/serviceSchema.js
CHANGED
|
@@ -21,11 +21,16 @@
|
|
|
21
21
|
* parameters: [ // ordered; rendered into a form
|
|
22
22
|
* { key, label, type, description?, default?, required?,
|
|
23
23
|
* options?: [{value, label}], // enum / multiselect
|
|
24
|
-
* min?, max?, step?,
|
|
25
|
-
* placeholder?, multiline
|
|
24
|
+
* min?, max?, step?, slider?, // number (slider: drag, don't type)
|
|
25
|
+
* placeholder?, multiline?, // string
|
|
26
|
+
* scope? } // field: 'Sentence' | 'Word' | 'Morpheme'
|
|
26
27
|
* ]
|
|
27
28
|
* }
|
|
28
29
|
*
|
|
30
|
+
* A `field` parameter names one of the project's annotation fields at its
|
|
31
|
+
* `scope`. A UI that knows the project offers those fields to choose from; to
|
|
32
|
+
* everything here, and to a UI that does not, it is a string.
|
|
33
|
+
*
|
|
29
34
|
* A parameter's `key` is a string VALUE, so it passes over the wire verbatim;
|
|
30
35
|
* the UI sends `{ [param.key]: value }` in the request data. Declare each `key`
|
|
31
36
|
* in your service's own convention (Python: snake_case; JS: camelCase) and read
|
|
@@ -43,13 +48,48 @@
|
|
|
43
48
|
* without an explicit `overwrite` parameter. See the manual, "Provenance".
|
|
44
49
|
*/
|
|
45
50
|
export const TASKS = Object.freeze({
|
|
46
|
-
TOKENIZE:
|
|
47
|
-
PARSE:
|
|
48
|
-
TRANSCRIBE:
|
|
51
|
+
TOKENIZE: "tokenize",
|
|
52
|
+
PARSE: "parse",
|
|
53
|
+
TRANSCRIBE: "transcribe",
|
|
49
54
|
/** Create vocab links for unlinked tokens. */
|
|
50
|
-
LINK_VOCAB:
|
|
55
|
+
LINK_VOCAB: "link-vocab",
|
|
51
56
|
/** Propose an interlinear analysis (morpheme segmentation + glosses) for words. */
|
|
52
|
-
ANALYZE:
|
|
57
|
+
ANALYZE: "analyze",
|
|
58
|
+
/**
|
|
59
|
+
* Draft a meaning representation graph over the sentences of a document,
|
|
60
|
+
* for correction by hand. The request names a `scope` ('document' or
|
|
61
|
+
* 'sentence'), a `sentence` number when the scope is one sentence, and
|
|
62
|
+
* `overwrite`. See plaid-umr's drafting services.
|
|
63
|
+
*
|
|
64
|
+
* Apart from `analyze` on purpose. A task is the CONTRACT a spot offers a
|
|
65
|
+
* service against, and these two share almost none of it: an analyze
|
|
66
|
+
* request names the three token layers plus the language, metalanguage and
|
|
67
|
+
* orthography, and writes morphemes and glosses. Both claimed `analyze` for
|
|
68
|
+
* a while, so every app offering an analyze spot offered the other app's
|
|
69
|
+
* services, which could not answer the request they would have been given.
|
|
70
|
+
*/
|
|
71
|
+
DRAFT_GRAPH: "draft-graph",
|
|
72
|
+
/** Propose a free translation for each sentence. */
|
|
73
|
+
TRANSLATE: "translate",
|
|
74
|
+
/**
|
|
75
|
+
* Propose time-aligned speech regions for a recording.
|
|
76
|
+
*
|
|
77
|
+
* The odd one out: a detect-speech service RETURNS its regions and writes
|
|
78
|
+
* nothing, because a time-aligned segment is a stretch of the baseline and
|
|
79
|
+
* cannot exist without text. The response carries
|
|
80
|
+
* `{segments: [{timeBegin, timeEnd, speaker?}]}` (seconds), and the app
|
|
81
|
+
* holds them as proposals until a person types into one.
|
|
82
|
+
*/
|
|
83
|
+
DETECT_SPEECH: "detect-speech",
|
|
84
|
+
/** A conversational assistant over a project (chat turns; see plaid-agent). */
|
|
85
|
+
ASSIST: "assist",
|
|
86
|
+
/**
|
|
87
|
+
* Score one document's annotation against another document's of the same
|
|
88
|
+
* text (two annotators' copies), writing a report on the scored document's
|
|
89
|
+
* metadata and never an annotation. The request names the other document
|
|
90
|
+
* as `against`. See plaid-umr's AnCast service.
|
|
91
|
+
*/
|
|
92
|
+
COMPARE: "compare",
|
|
53
93
|
});
|
|
54
94
|
|
|
55
95
|
/**
|
|
@@ -57,8 +97,8 @@ export const TASKS = Object.freeze({
|
|
|
57
97
|
* declared `tasks` array. Drop once all services advertise `tasks`.
|
|
58
98
|
*/
|
|
59
99
|
const LEGACY_TASK_PREFIXES = Object.freeze({
|
|
60
|
-
[TASKS.TOKENIZE]:
|
|
61
|
-
[TASKS.TRANSCRIBE]:
|
|
100
|
+
[TASKS.TOKENIZE]: "tok:",
|
|
101
|
+
[TASKS.TRANSCRIBE]: "asr:",
|
|
62
102
|
});
|
|
63
103
|
|
|
64
104
|
/**
|
|
@@ -74,7 +114,11 @@ export function servesTask(service, task) {
|
|
|
74
114
|
return declared.includes(task);
|
|
75
115
|
}
|
|
76
116
|
const prefix = LEGACY_TASK_PREFIXES[task];
|
|
77
|
-
return
|
|
117
|
+
return (
|
|
118
|
+
!!prefix &&
|
|
119
|
+
typeof service?.serviceId === "string" &&
|
|
120
|
+
service.serviceId.startsWith(prefix)
|
|
121
|
+
);
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
/**
|
|
@@ -104,7 +148,7 @@ export function getParamSchema(service) {
|
|
|
104
148
|
* @returns {string}
|
|
105
149
|
*/
|
|
106
150
|
export function getServiceSummary(service) {
|
|
107
|
-
return service?.extras?.summary || service?.description ||
|
|
151
|
+
return service?.extras?.summary || service?.description || "";
|
|
108
152
|
}
|
|
109
153
|
|
|
110
154
|
/** Valid option values for an enum/multiselect param. */
|
|
@@ -119,20 +163,25 @@ function optionValues(param) {
|
|
|
119
163
|
*/
|
|
120
164
|
function defaultForParam(param) {
|
|
121
165
|
const opts = optionValues(param);
|
|
122
|
-
if (param.type ===
|
|
123
|
-
if (param.default != null && opts.includes(param.default))
|
|
124
|
-
|
|
166
|
+
if (param.type === "enum") {
|
|
167
|
+
if (param.default != null && opts.includes(param.default))
|
|
168
|
+
return param.default;
|
|
169
|
+
return opts[0] ?? "";
|
|
125
170
|
}
|
|
126
|
-
if (param.type ===
|
|
171
|
+
if (param.type === "multiselect") {
|
|
127
172
|
const arr = Array.isArray(param.default) ? param.default : [];
|
|
128
173
|
return opts.length ? arr.filter((x) => opts.includes(x)) : arr;
|
|
129
174
|
}
|
|
130
|
-
if (param.default !== undefined && param.default !== null)
|
|
175
|
+
if (param.default !== undefined && param.default !== null)
|
|
176
|
+
return param.default;
|
|
131
177
|
switch (param.type) {
|
|
132
|
-
case
|
|
133
|
-
|
|
134
|
-
case
|
|
135
|
-
|
|
178
|
+
case "number":
|
|
179
|
+
return typeof param.min === "number" ? param.min : 0;
|
|
180
|
+
case "boolean":
|
|
181
|
+
return false;
|
|
182
|
+
case "string":
|
|
183
|
+
default:
|
|
184
|
+
return "";
|
|
136
185
|
}
|
|
137
186
|
}
|
|
138
187
|
|
|
@@ -169,43 +218,48 @@ export function coerceParamValues(schema, raw) {
|
|
|
169
218
|
if (v === undefined) v = defaultForParam(param);
|
|
170
219
|
|
|
171
220
|
switch (param.type) {
|
|
172
|
-
case
|
|
221
|
+
case "number": {
|
|
173
222
|
// Blank / nullish counts as "missing" → the param's default (matches the
|
|
174
223
|
// Python client, where float('') raises and falls back to the default).
|
|
175
224
|
let n;
|
|
176
|
-
if (
|
|
225
|
+
if (
|
|
226
|
+
v === "" ||
|
|
227
|
+
v == null ||
|
|
228
|
+
(typeof v === "string" && v.trim() === "")
|
|
229
|
+
) {
|
|
177
230
|
n = defaultForParam(param);
|
|
178
231
|
} else {
|
|
179
|
-
n = typeof v ===
|
|
232
|
+
n = typeof v === "number" ? v : Number(v);
|
|
180
233
|
if (Number.isNaN(n)) n = defaultForParam(param);
|
|
181
234
|
}
|
|
182
|
-
if (typeof param.min ===
|
|
183
|
-
if (typeof param.max ===
|
|
235
|
+
if (typeof param.min === "number") n = Math.max(param.min, n);
|
|
236
|
+
if (typeof param.max === "number") n = Math.min(param.max, n);
|
|
184
237
|
v = n;
|
|
185
238
|
break;
|
|
186
239
|
}
|
|
187
|
-
case
|
|
188
|
-
v = v === true || v ===
|
|
240
|
+
case "boolean":
|
|
241
|
+
v = v === true || v === "true";
|
|
189
242
|
break;
|
|
190
|
-
case
|
|
243
|
+
case "enum": {
|
|
191
244
|
const opts = optionValues(param);
|
|
192
245
|
if (opts.length && !opts.includes(v)) v = defaultForParam(param);
|
|
193
246
|
break;
|
|
194
247
|
}
|
|
195
|
-
case
|
|
248
|
+
case "multiselect": {
|
|
196
249
|
const opts = optionValues(param);
|
|
197
|
-
const arr = Array.isArray(v) ? v :
|
|
250
|
+
const arr = Array.isArray(v) ? v : v == null || v === "" ? [] : [v];
|
|
198
251
|
v = opts.length ? arr.filter((x) => opts.includes(x)) : arr;
|
|
199
252
|
break;
|
|
200
253
|
}
|
|
201
|
-
case
|
|
254
|
+
case "string":
|
|
202
255
|
default:
|
|
203
|
-
v = v == null ?
|
|
256
|
+
v = v == null ? "" : String(v);
|
|
204
257
|
break;
|
|
205
258
|
}
|
|
206
259
|
|
|
207
260
|
if (param.required) {
|
|
208
|
-
const empty =
|
|
261
|
+
const empty =
|
|
262
|
+
v === "" || v == null || (Array.isArray(v) && v.length === 0);
|
|
209
263
|
if (empty) errors[k] = `${param.label || k} is required`;
|
|
210
264
|
}
|
|
211
265
|
values[k] = v;
|