@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/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',
@@ -16,16 +16,21 @@
16
16
  *
17
17
  * {
18
18
  * schemaVersion: 1,
19
- * tasks: ["tokenize"], // controlled vocab; REPLACES tok:/asr: id prefixes
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?, // number
25
- * placeholder?, multiline? } // string
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: 'tokenize',
47
- PARSE: 'parse',
48
- TRANSCRIBE: 'transcribe',
51
+ TOKENIZE: "tokenize",
52
+ PARSE: "parse",
53
+ TRANSCRIBE: "transcribe",
49
54
  /** Create vocab links for unlinked tokens. */
50
- LINK_VOCAB: 'link-vocab',
55
+ LINK_VOCAB: "link-vocab",
51
56
  /** Propose an interlinear analysis (morpheme segmentation + glosses) for words. */
52
- ANALYZE: 'analyze',
53
- });
54
-
55
- /**
56
- * Legacy id-prefix → task map, for services that have not yet migrated to a
57
- * declared `tasks` array. Drop once all services advertise `tasks`.
58
- */
59
- const LEGACY_TASK_PREFIXES = Object.freeze({
60
- [TASKS.TOKENIZE]: 'tok:',
61
- [TASKS.TRANSCRIBE]: 'asr:',
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`? Prefers the declared `extras.tasks` array; falls
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
- if (Array.isArray(declared) && declared.length) {
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 === 'enum') {
123
- if (param.default != null && opts.includes(param.default)) return param.default;
124
- return opts[0] ?? '';
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 === 'multiselect') {
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) return param.default;
157
+ if (param.default !== undefined && param.default !== null)
158
+ return param.default;
131
159
  switch (param.type) {
132
- case 'number': return typeof param.min === 'number' ? param.min : 0;
133
- case 'boolean': return false;
134
- case 'string':
135
- default: return '';
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 'number': {
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 (v === '' || v == null || (typeof v === 'string' && v.trim() === '')) {
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 === 'number' ? v : Number(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 === 'number') n = Math.max(param.min, n);
183
- if (typeof param.max === 'number') n = Math.min(param.max, n);
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 'boolean':
188
- v = v === true || v === 'true';
222
+ case "boolean":
223
+ v = v === true || v === "true";
189
224
  break;
190
- case 'enum': {
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 'multiselect': {
230
+ case "multiselect": {
196
231
  const opts = optionValues(param);
197
- const arr = Array.isArray(v) ? v : (v == null || 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 'string':
236
+ case "string":
202
237
  default:
203
- v = v == null ? '' : String(v);
238
+ v = v == null ? "" : String(v);
204
239
  break;
205
240
  }
206
241
 
207
242
  if (param.required) {
208
- const empty = v === '' || v == null || (Array.isArray(v) && v.length === 0);
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;