@larc-iu/plaid-client 0.2.0-alpha.2 → 0.2.0-alpha.21
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 +1636 -164
- package/package.json +1 -1
- package/src/batchRef.js +128 -0
- package/src/constraints.js +73 -0
- package/src/created.js +29 -0
- package/src/documentLock.js +325 -0
- package/src/events.js +266 -0
- package/src/http.js +736 -90
- package/src/ids.js +54 -0
- package/src/index.js +2832 -650
- package/src/metadataOps.js +119 -0
- package/src/pagination.js +19 -35
- package/src/provenance.js +265 -27
- package/src/replayed.js +27 -0
- package/src/requester.js +56 -0
- package/src/roles.js +17 -0
- package/src/serviceSchema.js +83 -47
- package/src/services.js +619 -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,23 @@ 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): a space typed inside a token of the text's words
|
|
39
|
+
* splits it, the token going on the half sharing more letters with it.
|
|
40
|
+
* Declared on a word layer another app shares, it holds for the whole text.
|
|
41
|
+
* See the manual's `Changing a text's body`.
|
|
42
|
+
*/
|
|
43
|
+
export const SPLIT_ON_SPACE_KEY = 'splitOnSpace';
|
|
44
|
+
|
|
28
45
|
export const ROLES = Object.freeze({
|
|
29
46
|
BASELINE: 'baseline',
|
|
30
47
|
SENTENCE: 'sentence',
|
package/src/serviceSchema.js
CHANGED
|
@@ -16,16 +16,21 @@
|
|
|
16
16
|
*
|
|
17
17
|
* {
|
|
18
18
|
* schemaVersion: 1,
|
|
19
|
-
* tasks: ["tokenize"], // controlled vocab
|
|
19
|
+
* tasks: ["tokenize"], // controlled vocab (TASKS)
|
|
20
20
|
* summary: "## markdown …", // rich human description
|
|
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,38 +48,59 @@
|
|
|
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:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 and metalanguage,
|
|
67
|
+
* 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",
|
|
62
93
|
});
|
|
63
94
|
|
|
64
95
|
/**
|
|
65
|
-
* Does `service` serve `task
|
|
66
|
-
* back to the legacy id-prefix convention for un-migrated services.
|
|
96
|
+
* Does `service` serve `task`, as its declared `extras.tasks` says?
|
|
67
97
|
* @param {{serviceId?: string, extras?: {tasks?: string[]}}} service
|
|
68
98
|
* @param {string} task one of TASKS
|
|
69
99
|
* @returns {boolean}
|
|
70
100
|
*/
|
|
71
101
|
export function servesTask(service, task) {
|
|
72
102
|
const declared = service?.extras?.tasks;
|
|
73
|
-
|
|
74
|
-
return declared.includes(task);
|
|
75
|
-
}
|
|
76
|
-
const prefix = LEGACY_TASK_PREFIXES[task];
|
|
77
|
-
return !!prefix && typeof service?.serviceId === 'string' && service.serviceId.startsWith(prefix);
|
|
103
|
+
return Array.isArray(declared) && declared.includes(task);
|
|
78
104
|
}
|
|
79
105
|
|
|
80
106
|
/**
|
|
@@ -104,7 +130,7 @@ export function getParamSchema(service) {
|
|
|
104
130
|
* @returns {string}
|
|
105
131
|
*/
|
|
106
132
|
export function getServiceSummary(service) {
|
|
107
|
-
return service?.extras?.summary || service?.description ||
|
|
133
|
+
return service?.extras?.summary || service?.description || "";
|
|
108
134
|
}
|
|
109
135
|
|
|
110
136
|
/** Valid option values for an enum/multiselect param. */
|
|
@@ -119,20 +145,25 @@ function optionValues(param) {
|
|
|
119
145
|
*/
|
|
120
146
|
function defaultForParam(param) {
|
|
121
147
|
const opts = optionValues(param);
|
|
122
|
-
if (param.type ===
|
|
123
|
-
if (param.default != null && opts.includes(param.default))
|
|
124
|
-
|
|
148
|
+
if (param.type === "enum") {
|
|
149
|
+
if (param.default != null && opts.includes(param.default))
|
|
150
|
+
return param.default;
|
|
151
|
+
return opts[0] ?? "";
|
|
125
152
|
}
|
|
126
|
-
if (param.type ===
|
|
153
|
+
if (param.type === "multiselect") {
|
|
127
154
|
const arr = Array.isArray(param.default) ? param.default : [];
|
|
128
155
|
return opts.length ? arr.filter((x) => opts.includes(x)) : arr;
|
|
129
156
|
}
|
|
130
|
-
if (param.default !== undefined && param.default !== null)
|
|
157
|
+
if (param.default !== undefined && param.default !== null)
|
|
158
|
+
return param.default;
|
|
131
159
|
switch (param.type) {
|
|
132
|
-
case
|
|
133
|
-
|
|
134
|
-
case
|
|
135
|
-
|
|
160
|
+
case "number":
|
|
161
|
+
return typeof param.min === "number" ? param.min : 0;
|
|
162
|
+
case "boolean":
|
|
163
|
+
return false;
|
|
164
|
+
case "string":
|
|
165
|
+
default:
|
|
166
|
+
return "";
|
|
136
167
|
}
|
|
137
168
|
}
|
|
138
169
|
|
|
@@ -169,43 +200,48 @@ export function coerceParamValues(schema, raw) {
|
|
|
169
200
|
if (v === undefined) v = defaultForParam(param);
|
|
170
201
|
|
|
171
202
|
switch (param.type) {
|
|
172
|
-
case
|
|
203
|
+
case "number": {
|
|
173
204
|
// Blank / nullish counts as "missing" → the param's default (matches the
|
|
174
205
|
// Python client, where float('') raises and falls back to the default).
|
|
175
206
|
let n;
|
|
176
|
-
if (
|
|
207
|
+
if (
|
|
208
|
+
v === "" ||
|
|
209
|
+
v == null ||
|
|
210
|
+
(typeof v === "string" && v.trim() === "")
|
|
211
|
+
) {
|
|
177
212
|
n = defaultForParam(param);
|
|
178
213
|
} else {
|
|
179
|
-
n = typeof v ===
|
|
214
|
+
n = typeof v === "number" ? v : Number(v);
|
|
180
215
|
if (Number.isNaN(n)) n = defaultForParam(param);
|
|
181
216
|
}
|
|
182
|
-
if (typeof param.min ===
|
|
183
|
-
if (typeof param.max ===
|
|
217
|
+
if (typeof param.min === "number") n = Math.max(param.min, n);
|
|
218
|
+
if (typeof param.max === "number") n = Math.min(param.max, n);
|
|
184
219
|
v = n;
|
|
185
220
|
break;
|
|
186
221
|
}
|
|
187
|
-
case
|
|
188
|
-
v = v === true || v ===
|
|
222
|
+
case "boolean":
|
|
223
|
+
v = v === true || v === "true";
|
|
189
224
|
break;
|
|
190
|
-
case
|
|
225
|
+
case "enum": {
|
|
191
226
|
const opts = optionValues(param);
|
|
192
227
|
if (opts.length && !opts.includes(v)) v = defaultForParam(param);
|
|
193
228
|
break;
|
|
194
229
|
}
|
|
195
|
-
case
|
|
230
|
+
case "multiselect": {
|
|
196
231
|
const opts = optionValues(param);
|
|
197
|
-
const arr = Array.isArray(v) ? v :
|
|
232
|
+
const arr = Array.isArray(v) ? v : v == null || v === "" ? [] : [v];
|
|
198
233
|
v = opts.length ? arr.filter((x) => opts.includes(x)) : arr;
|
|
199
234
|
break;
|
|
200
235
|
}
|
|
201
|
-
case
|
|
236
|
+
case "string":
|
|
202
237
|
default:
|
|
203
|
-
v = v == null ?
|
|
238
|
+
v = v == null ? "" : String(v);
|
|
204
239
|
break;
|
|
205
240
|
}
|
|
206
241
|
|
|
207
242
|
if (param.required) {
|
|
208
|
-
const empty =
|
|
243
|
+
const empty =
|
|
244
|
+
v === "" || v == null || (Array.isArray(v) && v.length === 0);
|
|
209
245
|
if (empty) errors[k] = `${param.label || k} is required`;
|
|
210
246
|
}
|
|
211
247
|
values[k] = v;
|