@things-factory/integration-label-studio 9.1.19
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/CHANGELOG.md +85 -0
- package/EXTERNAL_DATA_SOURCING.md +484 -0
- package/IMPLEMENTATION_GUIDE.md +469 -0
- package/INTEGRATION.md +279 -0
- package/README.md +1014 -0
- package/SETUP_GUIDE.md +577 -0
- package/TEST_GUIDE.md +387 -0
- package/UI_CUSTOMIZATION.md +395 -0
- package/USER_SYNC_GUIDE.md +514 -0
- package/client/bootstrap.ts +1 -0
- package/client/index.ts +1 -0
- package/client/label-studio-label-page.ts +52 -0
- package/client/label-studio-project-create.ts +216 -0
- package/client/label-studio-project-list.ts +214 -0
- package/client/label-studio-wrapper.ts +294 -0
- package/client/route.ts +15 -0
- package/client/tsconfig.json +13 -0
- package/config/config.development.js +124 -0
- package/config/config.production.js +182 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +2 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +2 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/label-studio-label-page.d.ts +8 -0
- package/dist-client/label-studio-label-page.js +54 -0
- package/dist-client/label-studio-label-page.js.map +1 -0
- package/dist-client/label-studio-project-create.d.ts +16 -0
- package/dist-client/label-studio-project-create.js +235 -0
- package/dist-client/label-studio-project-create.js.map +1 -0
- package/dist-client/label-studio-project-list.d.ts +16 -0
- package/dist-client/label-studio-project-list.js +222 -0
- package/dist-client/label-studio-project-list.js.map +1 -0
- package/dist-client/label-studio-wrapper.d.ts +57 -0
- package/dist-client/label-studio-wrapper.js +304 -0
- package/dist-client/label-studio-wrapper.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +14 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/controller/label-studio-role-mapper.d.ts +35 -0
- package/dist-server/controller/label-studio-role-mapper.js +65 -0
- package/dist-server/controller/label-studio-role-mapper.js.map +1 -0
- package/dist-server/controller/user-provisioning-service.d.ts +66 -0
- package/dist-server/controller/user-provisioning-service.js +264 -0
- package/dist-server/controller/user-provisioning-service.js.map +1 -0
- package/dist-server/index.d.ts +7 -0
- package/dist-server/index.js +19 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/route/label-studio-sso.d.ts +2 -0
- package/dist-server/route/label-studio-sso.js +156 -0
- package/dist-server/route/label-studio-sso.js.map +1 -0
- package/dist-server/route/webhook.d.ts +65 -0
- package/dist-server/route/webhook.js +248 -0
- package/dist-server/route/webhook.js.map +1 -0
- package/dist-server/route.d.ts +1 -0
- package/dist-server/route.js +21 -0
- package/dist-server/route.js.map +1 -0
- package/dist-server/service/ai-prediction-service.d.ts +27 -0
- package/dist-server/service/ai-prediction-service.js +222 -0
- package/dist-server/service/ai-prediction-service.js.map +1 -0
- package/dist-server/service/dataset-labeling-integration.d.ts +44 -0
- package/dist-server/service/dataset-labeling-integration.js +512 -0
- package/dist-server/service/dataset-labeling-integration.js.map +1 -0
- package/dist-server/service/external-data-source-service.d.ts +78 -0
- package/dist-server/service/external-data-source-service.js +415 -0
- package/dist-server/service/external-data-source-service.js.map +1 -0
- package/dist-server/service/index.d.ts +12 -0
- package/dist-server/service/index.js +27 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/label-studio-sso-service.d.ts +38 -0
- package/dist-server/service/label-studio-sso-service.js +98 -0
- package/dist-server/service/label-studio-sso-service.js.map +1 -0
- package/dist-server/service/ml/ml-backend-service.d.ts +23 -0
- package/dist-server/service/ml/ml-backend-service.js +153 -0
- package/dist-server/service/ml/ml-backend-service.js.map +1 -0
- package/dist-server/service/prediction/prediction-management.d.ts +32 -0
- package/dist-server/service/prediction/prediction-management.js +299 -0
- package/dist-server/service/prediction/prediction-management.js.map +1 -0
- package/dist-server/service/project/project-management.d.ts +36 -0
- package/dist-server/service/project/project-management.js +309 -0
- package/dist-server/service/project/project-management.js.map +1 -0
- package/dist-server/service/task/task-management.d.ts +42 -0
- package/dist-server/service/task/task-management.js +372 -0
- package/dist-server/service/task/task-management.js.map +1 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.d.ts +28 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.js +111 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.js.map +1 -0
- package/dist-server/service/webhook/webhook-management.d.ts +21 -0
- package/dist-server/service/webhook/webhook-management.js +134 -0
- package/dist-server/service/webhook/webhook-management.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/dist-server/types/dataset-labeling-types.d.ts +71 -0
- package/dist-server/types/dataset-labeling-types.js +259 -0
- package/dist-server/types/dataset-labeling-types.js.map +1 -0
- package/dist-server/types/label-studio-types.d.ts +128 -0
- package/dist-server/types/label-studio-types.js +494 -0
- package/dist-server/types/label-studio-types.js.map +1 -0
- package/dist-server/types/prediction-types.d.ts +39 -0
- package/dist-server/types/prediction-types.js +121 -0
- package/dist-server/types/prediction-types.js.map +1 -0
- package/dist-server/utils/annotation-exporter.d.ts +104 -0
- package/dist-server/utils/annotation-exporter.js +261 -0
- package/dist-server/utils/annotation-exporter.js.map +1 -0
- package/dist-server/utils/label-config-builder.d.ts +117 -0
- package/dist-server/utils/label-config-builder.js +286 -0
- package/dist-server/utils/label-config-builder.js.map +1 -0
- package/dist-server/utils/label-studio-api-client.d.ts +180 -0
- package/dist-server/utils/label-studio-api-client.js +401 -0
- package/dist-server/utils/label-studio-api-client.js.map +1 -0
- package/dist-server/utils/media-url-extractor.d.ts +45 -0
- package/dist-server/utils/media-url-extractor.js +152 -0
- package/dist-server/utils/media-url-extractor.js.map +1 -0
- package/dist-server/utils/task-transformer.d.ts +108 -0
- package/dist-server/utils/task-transformer.js +260 -0
- package/dist-server/utils/task-transformer.js.map +1 -0
- package/package.json +47 -0
- package/server/SERVER_STRUCTURE.md +351 -0
- package/server/controller/label-studio-role-mapper.ts +76 -0
- package/server/controller/user-provisioning-service.ts +340 -0
- package/server/index.ts +19 -0
- package/server/route/label-studio-sso.ts +194 -0
- package/server/route/webhook.ts +304 -0
- package/server/route.ts +35 -0
- package/server/service/ai-prediction-service.ts +239 -0
- package/server/service/dataset-labeling-integration.ts +590 -0
- package/server/service/external-data-source-service.ts +438 -0
- package/server/service/index.ts +24 -0
- package/server/service/label-studio-sso-service.ts +108 -0
- package/server/service/labeling-scenario-service.ts.deprecated +566 -0
- package/server/service/ml/ml-backend-service.ts +127 -0
- package/server/service/prediction/prediction-management.ts +281 -0
- package/server/service/project/project-management.ts +284 -0
- package/server/service/task/task-management.ts +363 -0
- package/server/service/user-provisioning/user-sync-mutation.ts +80 -0
- package/server/service/webhook/webhook-management.ts +109 -0
- package/server/tsconfig.json +11 -0
- package/server/types/dataset-labeling-types.ts +181 -0
- package/server/types/global.d.ts +23 -0
- package/server/types/label-studio-types.ts +346 -0
- package/server/types/prediction-types.ts +86 -0
- package/server/types/scenario-types.ts.deprecated +362 -0
- package/server/utils/annotation-exporter.ts +340 -0
- package/server/utils/label-config-builder.ts +340 -0
- package/server/utils/label-studio-api-client.ts +487 -0
- package/server/utils/media-url-extractor.ts +193 -0
- package/server/utils/task-transformer.ts +342 -0
- package/test-ai-prediction.js +268 -0
- package/test-dataset-integration.js +449 -0
- package/test-simple.js +89 -0
- package/things-factory.config.js +12 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label Config Builder
|
|
3
|
+
*
|
|
4
|
+
* Flexible, declarative Label Studio configuration generator.
|
|
5
|
+
* Supports any data type and control combination without hardcoding templates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface LabelConfigSpec {
|
|
9
|
+
dataType: 'image' | 'text' | 'audio' | 'video' | 'timeseries' | 'html' | 'hypertext'
|
|
10
|
+
dataName?: string // default: 'data'
|
|
11
|
+
controls: ControlSpec[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ControlSpec {
|
|
15
|
+
type: 'choices' | 'labels' | 'textarea' | 'rating' | 'rectangles' | 'polygon' | 'keypoint' | 'brush'
|
|
16
|
+
name: string
|
|
17
|
+
toName: string
|
|
18
|
+
config: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ChoicesConfig {
|
|
22
|
+
choices: string[]
|
|
23
|
+
choice?: 'single' | 'multiple'
|
|
24
|
+
required?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LabelsConfig {
|
|
28
|
+
labels: Array<{
|
|
29
|
+
value: string
|
|
30
|
+
background?: string
|
|
31
|
+
hotkey?: string
|
|
32
|
+
}>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RatingConfig {
|
|
36
|
+
maxRating?: number
|
|
37
|
+
icon?: string
|
|
38
|
+
size?: 'small' | 'medium' | 'large'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class LabelConfigBuilder {
|
|
42
|
+
/**
|
|
43
|
+
* Build Label Studio XML configuration from specification
|
|
44
|
+
*/
|
|
45
|
+
static build(spec: LabelConfigSpec): string {
|
|
46
|
+
const dataName = spec.dataName || 'data'
|
|
47
|
+
const dataTag = this.buildDataTag(spec.dataType, dataName)
|
|
48
|
+
const controlTags = spec.controls.map(c => this.buildControlTag(c))
|
|
49
|
+
|
|
50
|
+
return `
|
|
51
|
+
<View>
|
|
52
|
+
${dataTag}
|
|
53
|
+
${controlTags.join('\n ')}
|
|
54
|
+
</View>
|
|
55
|
+
`.trim()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build data source tag (Image, Text, Audio, etc.)
|
|
60
|
+
*/
|
|
61
|
+
private static buildDataTag(type: string, name: string): string {
|
|
62
|
+
const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1)
|
|
63
|
+
return `<${capitalizedType} name="${name}" value="$${name}"/>`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build control tag based on type
|
|
68
|
+
*/
|
|
69
|
+
private static buildControlTag(control: ControlSpec): string {
|
|
70
|
+
switch (control.type) {
|
|
71
|
+
case 'choices':
|
|
72
|
+
return this.buildChoices(control)
|
|
73
|
+
case 'labels':
|
|
74
|
+
return this.buildLabels(control)
|
|
75
|
+
case 'textarea':
|
|
76
|
+
return this.buildTextArea(control)
|
|
77
|
+
case 'rating':
|
|
78
|
+
return this.buildRating(control)
|
|
79
|
+
case 'rectangles':
|
|
80
|
+
return this.buildRectangles(control)
|
|
81
|
+
case 'polygon':
|
|
82
|
+
return this.buildPolygon(control)
|
|
83
|
+
case 'keypoint':
|
|
84
|
+
return this.buildKeypoint(control)
|
|
85
|
+
case 'brush':
|
|
86
|
+
return this.buildBrush(control)
|
|
87
|
+
default:
|
|
88
|
+
throw new Error(`Unknown control type: ${control.type}`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build Choices control (single or multiple selection)
|
|
94
|
+
*/
|
|
95
|
+
private static buildChoices(control: ControlSpec): string {
|
|
96
|
+
const config = control.config as ChoicesConfig
|
|
97
|
+
const choice = config.choice || 'single'
|
|
98
|
+
const required = config.required ? 'required="true"' : ''
|
|
99
|
+
|
|
100
|
+
const choiceElements = config.choices
|
|
101
|
+
.map(c => ` <Choice value="${this.escapeXml(c)}"/>`)
|
|
102
|
+
.join('\n')
|
|
103
|
+
|
|
104
|
+
return `<Choices name="${control.name}" toName="${control.toName}" choice="${choice}" ${required}>
|
|
105
|
+
${choiceElements}
|
|
106
|
+
</Choices>`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build Labels control (for NER, classification, etc.)
|
|
111
|
+
*/
|
|
112
|
+
private static buildLabels(control: ControlSpec): string {
|
|
113
|
+
const config = control.config as LabelsConfig
|
|
114
|
+
|
|
115
|
+
const labelElements = config.labels
|
|
116
|
+
.map(l => {
|
|
117
|
+
const background = l.background ? `background="${l.background}"` : ''
|
|
118
|
+
const hotkey = l.hotkey ? `hotkey="${l.hotkey}"` : ''
|
|
119
|
+
return ` <Label value="${this.escapeXml(l.value)}" ${background} ${hotkey}/>`
|
|
120
|
+
})
|
|
121
|
+
.join('\n')
|
|
122
|
+
|
|
123
|
+
return `<Labels name="${control.name}" toName="${control.toName}">
|
|
124
|
+
${labelElements}
|
|
125
|
+
</Labels>`
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build TextArea control
|
|
130
|
+
*/
|
|
131
|
+
private static buildTextArea(control: ControlSpec): string {
|
|
132
|
+
const placeholder = control.config.placeholder ? `placeholder="${control.config.placeholder}"` : ''
|
|
133
|
+
const required = control.config.required ? 'required="true"' : ''
|
|
134
|
+
const maxSubmissions = control.config.maxSubmissions ? `maxSubmissions="${control.config.maxSubmissions}"` : ''
|
|
135
|
+
|
|
136
|
+
return `<TextArea name="${control.name}" toName="${control.toName}" ${placeholder} ${required} ${maxSubmissions}/>`
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Build Rating control
|
|
141
|
+
*/
|
|
142
|
+
private static buildRating(control: ControlSpec): string {
|
|
143
|
+
const config = control.config as RatingConfig
|
|
144
|
+
const maxRating = config.maxRating || 5
|
|
145
|
+
const icon = config.icon || 'star'
|
|
146
|
+
const size = config.size || 'medium'
|
|
147
|
+
|
|
148
|
+
return `<Rating name="${control.name}" toName="${control.toName}" maxRating="${maxRating}" icon="${icon}" size="${size}"/>`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Build Rectangle control (bounding boxes)
|
|
153
|
+
*/
|
|
154
|
+
private static buildRectangles(control: ControlSpec): string {
|
|
155
|
+
const labels = control.config.labels ? `labels="${control.config.labels.join(',')}"` : ''
|
|
156
|
+
return `<Rectangle name="${control.name}" toName="${control.toName}" ${labels}/>`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build Polygon control
|
|
161
|
+
*/
|
|
162
|
+
private static buildPolygon(control: ControlSpec): string {
|
|
163
|
+
const labels = control.config.labels ? `labels="${control.config.labels.join(',')}"` : ''
|
|
164
|
+
return `<Polygon name="${control.name}" toName="${control.toName}" ${labels}/>`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Build Keypoint control
|
|
169
|
+
*/
|
|
170
|
+
private static buildKeypoint(control: ControlSpec): string {
|
|
171
|
+
const labels = control.config.labels ? `labels="${control.config.labels.join(',')}"` : ''
|
|
172
|
+
return `<KeyPoint name="${control.name}" toName="${control.toName}" ${labels}/>`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Build Brush control (semantic segmentation)
|
|
177
|
+
*/
|
|
178
|
+
private static buildBrush(control: ControlSpec): string {
|
|
179
|
+
const labels = control.config.labels ? `labels="${control.config.labels.join(',')}"` : ''
|
|
180
|
+
return `<Brush name="${control.name}" toName="${control.toName}" ${labels}/>`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Escape XML special characters
|
|
185
|
+
*/
|
|
186
|
+
private static escapeXml(str: string): string {
|
|
187
|
+
return str
|
|
188
|
+
.replace(/&/g, '&')
|
|
189
|
+
.replace(/</g, '<')
|
|
190
|
+
.replace(/>/g, '>')
|
|
191
|
+
.replace(/"/g, '"')
|
|
192
|
+
.replace(/'/g, ''')
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Pre-built template generators for common use cases
|
|
198
|
+
*/
|
|
199
|
+
export class LabelConfigTemplates {
|
|
200
|
+
/**
|
|
201
|
+
* Generate Rank N image classification config
|
|
202
|
+
* Example: WBM Rank 3 classification
|
|
203
|
+
*/
|
|
204
|
+
static imageRankN(ranks: number, classes: string[]): LabelConfigSpec {
|
|
205
|
+
const controls: ControlSpec[] = []
|
|
206
|
+
|
|
207
|
+
for (let i = 1; i <= ranks; i++) {
|
|
208
|
+
controls.push({
|
|
209
|
+
type: 'choices',
|
|
210
|
+
name: `rank${i}`,
|
|
211
|
+
toName: 'data',
|
|
212
|
+
config: {
|
|
213
|
+
choices: classes,
|
|
214
|
+
choice: 'single',
|
|
215
|
+
required: i === 1 // First rank is required
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
dataType: 'image',
|
|
222
|
+
controls
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Simple image classification
|
|
228
|
+
*/
|
|
229
|
+
static imageClassification(classes: string[], multiLabel: boolean = false): LabelConfigSpec {
|
|
230
|
+
return {
|
|
231
|
+
dataType: 'image',
|
|
232
|
+
controls: [
|
|
233
|
+
{
|
|
234
|
+
type: 'choices',
|
|
235
|
+
name: 'choice',
|
|
236
|
+
toName: 'data',
|
|
237
|
+
config: {
|
|
238
|
+
choices: classes,
|
|
239
|
+
choice: multiLabel ? 'multiple' : 'single',
|
|
240
|
+
required: true
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Text classification (sentiment, intent, etc.)
|
|
249
|
+
*/
|
|
250
|
+
static textClassification(classes: string[]): LabelConfigSpec {
|
|
251
|
+
return {
|
|
252
|
+
dataType: 'text',
|
|
253
|
+
controls: [
|
|
254
|
+
{
|
|
255
|
+
type: 'choices',
|
|
256
|
+
name: 'choice',
|
|
257
|
+
toName: 'data',
|
|
258
|
+
config: {
|
|
259
|
+
choices: classes,
|
|
260
|
+
choice: 'single',
|
|
261
|
+
required: true
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Named Entity Recognition
|
|
270
|
+
*/
|
|
271
|
+
static namedEntityRecognition(labels: Array<{ value: string; background: string }>): LabelConfigSpec {
|
|
272
|
+
return {
|
|
273
|
+
dataType: 'text',
|
|
274
|
+
controls: [
|
|
275
|
+
{
|
|
276
|
+
type: 'labels',
|
|
277
|
+
name: 'label',
|
|
278
|
+
toName: 'data',
|
|
279
|
+
config: {
|
|
280
|
+
labels
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Object detection (bounding boxes)
|
|
289
|
+
*/
|
|
290
|
+
static objectDetection(labels: string[]): LabelConfigSpec {
|
|
291
|
+
return {
|
|
292
|
+
dataType: 'image',
|
|
293
|
+
controls: [
|
|
294
|
+
{
|
|
295
|
+
type: 'rectangles',
|
|
296
|
+
name: 'bbox',
|
|
297
|
+
toName: 'data',
|
|
298
|
+
config: {
|
|
299
|
+
labels
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
type: 'labels',
|
|
304
|
+
name: 'label',
|
|
305
|
+
toName: 'data',
|
|
306
|
+
config: {
|
|
307
|
+
labels: labels.map(l => ({ value: l }))
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Semantic segmentation
|
|
316
|
+
*/
|
|
317
|
+
static semanticSegmentation(labels: string[]): LabelConfigSpec {
|
|
318
|
+
return {
|
|
319
|
+
dataType: 'image',
|
|
320
|
+
controls: [
|
|
321
|
+
{
|
|
322
|
+
type: 'brush',
|
|
323
|
+
name: 'segmentation',
|
|
324
|
+
toName: 'data',
|
|
325
|
+
config: {
|
|
326
|
+
labels
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: 'labels',
|
|
331
|
+
name: 'label',
|
|
332
|
+
toName: 'data',
|
|
333
|
+
config: {
|
|
334
|
+
labels: labels.map(l => ({ value: l }))
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|