@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,281 @@
|
|
|
1
|
+
import { Resolver, Query, Mutation, Arg, Ctx, Int, Directive } from 'type-graphql'
|
|
2
|
+
import { labelStudioApi } from '../../utils/label-studio-api-client.js'
|
|
3
|
+
import {
|
|
4
|
+
LabelStudioPrediction,
|
|
5
|
+
PredictionInput,
|
|
6
|
+
PredictionImportResult,
|
|
7
|
+
BulkPredictionInput
|
|
8
|
+
} from '../../types/label-studio-types.js'
|
|
9
|
+
|
|
10
|
+
@Resolver()
|
|
11
|
+
export class PredictionManagement {
|
|
12
|
+
/**
|
|
13
|
+
* Get predictions for a task
|
|
14
|
+
*/
|
|
15
|
+
@Query(returns => [LabelStudioPrediction], {
|
|
16
|
+
description: 'Get all predictions for a Label Studio task'
|
|
17
|
+
})
|
|
18
|
+
@Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
19
|
+
async labelStudioTaskPredictions(
|
|
20
|
+
@Arg('taskId', type => Int) taskId: number,
|
|
21
|
+
@Ctx() context: ResolverContext
|
|
22
|
+
): Promise<LabelStudioPrediction[]> {
|
|
23
|
+
try {
|
|
24
|
+
const predictions = await labelStudioApi.getPredictions(taskId)
|
|
25
|
+
|
|
26
|
+
return predictions.map((prediction: any) => ({
|
|
27
|
+
id: prediction.id,
|
|
28
|
+
taskId: prediction.task || taskId,
|
|
29
|
+
result: JSON.stringify(prediction.result),
|
|
30
|
+
score: prediction.score || undefined,
|
|
31
|
+
modelVersion: prediction.model_version || undefined,
|
|
32
|
+
createdAt: prediction.created_at ? new Date(prediction.created_at) : undefined
|
|
33
|
+
}))
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(`Failed to fetch predictions for task ${taskId}:`, error)
|
|
36
|
+
throw new Error(`Failed to fetch predictions: ${error.message}`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get predictions for a project
|
|
42
|
+
*/
|
|
43
|
+
@Query(returns => [LabelStudioPrediction], {
|
|
44
|
+
description: 'Get all predictions for a Label Studio project'
|
|
45
|
+
})
|
|
46
|
+
@Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
47
|
+
async labelStudioProjectPredictions(
|
|
48
|
+
@Arg('projectId', type => Int) projectId: number,
|
|
49
|
+
@Ctx() context: ResolverContext
|
|
50
|
+
): Promise<LabelStudioPrediction[]> {
|
|
51
|
+
try {
|
|
52
|
+
const predictions = await labelStudioApi.getProjectPredictions(projectId)
|
|
53
|
+
|
|
54
|
+
return predictions.map((prediction: any) => ({
|
|
55
|
+
id: prediction.id,
|
|
56
|
+
taskId: prediction.task,
|
|
57
|
+
result: JSON.stringify(prediction.result),
|
|
58
|
+
score: prediction.score || undefined,
|
|
59
|
+
modelVersion: prediction.model_version || undefined,
|
|
60
|
+
createdAt: prediction.created_at ? new Date(prediction.created_at) : undefined
|
|
61
|
+
}))
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(`Failed to fetch predictions for project ${projectId}:`, error)
|
|
64
|
+
throw new Error(`Failed to fetch predictions: ${error.message}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get single prediction by ID
|
|
70
|
+
*/
|
|
71
|
+
@Query(returns => LabelStudioPrediction, {
|
|
72
|
+
description: 'Get a single prediction by ID',
|
|
73
|
+
nullable: true
|
|
74
|
+
})
|
|
75
|
+
@Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
76
|
+
async labelStudioPrediction(
|
|
77
|
+
@Arg('predictionId', type => Int) predictionId: number,
|
|
78
|
+
@Ctx() context: ResolverContext
|
|
79
|
+
): Promise<LabelStudioPrediction | null> {
|
|
80
|
+
try {
|
|
81
|
+
const prediction = await labelStudioApi.getPrediction(predictionId)
|
|
82
|
+
|
|
83
|
+
if (!prediction) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: prediction.id,
|
|
89
|
+
taskId: prediction.task,
|
|
90
|
+
result: JSON.stringify(prediction.result),
|
|
91
|
+
score: prediction.score || undefined,
|
|
92
|
+
modelVersion: prediction.model_version || undefined,
|
|
93
|
+
createdAt: prediction.created_at ? new Date(prediction.created_at) : undefined
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(`Failed to fetch prediction ${predictionId}:`, error)
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a prediction for a task
|
|
103
|
+
*/
|
|
104
|
+
@Mutation(returns => LabelStudioPrediction, {
|
|
105
|
+
description: 'Create a prediction for a Label Studio task'
|
|
106
|
+
})
|
|
107
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
108
|
+
async createLabelStudioPrediction(
|
|
109
|
+
@Arg('input') input: PredictionInput,
|
|
110
|
+
@Ctx() context: ResolverContext
|
|
111
|
+
): Promise<LabelStudioPrediction> {
|
|
112
|
+
try {
|
|
113
|
+
// Parse result JSON
|
|
114
|
+
const result = JSON.parse(input.result)
|
|
115
|
+
|
|
116
|
+
// Create prediction
|
|
117
|
+
const prediction = await labelStudioApi.createPrediction({
|
|
118
|
+
task: input.taskId,
|
|
119
|
+
result,
|
|
120
|
+
score: input.score,
|
|
121
|
+
model_version: input.modelVersion
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
id: prediction.id,
|
|
126
|
+
taskId: prediction.task,
|
|
127
|
+
result: JSON.stringify(prediction.result),
|
|
128
|
+
score: prediction.score || undefined,
|
|
129
|
+
modelVersion: prediction.model_version || undefined,
|
|
130
|
+
createdAt: prediction.created_at ? new Date(prediction.created_at) : undefined
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error(`Failed to create prediction for task ${input.taskId}:`, error)
|
|
134
|
+
throw new Error(`Failed to create prediction: ${error.message}`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create predictions in bulk
|
|
140
|
+
*/
|
|
141
|
+
@Mutation(returns => PredictionImportResult, {
|
|
142
|
+
description: 'Create multiple predictions at once'
|
|
143
|
+
})
|
|
144
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
145
|
+
async createBulkPredictions(
|
|
146
|
+
@Arg('predictions', type => [BulkPredictionInput]) predictions: BulkPredictionInput[],
|
|
147
|
+
@Ctx() context: ResolverContext
|
|
148
|
+
): Promise<PredictionImportResult> {
|
|
149
|
+
const errors: string[] = []
|
|
150
|
+
let created = 0
|
|
151
|
+
let failed = 0
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
// Parse and validate predictions
|
|
155
|
+
const parsedPredictions = predictions.map((pred, index) => {
|
|
156
|
+
try {
|
|
157
|
+
return {
|
|
158
|
+
task: pred.taskId,
|
|
159
|
+
result: JSON.parse(pred.result),
|
|
160
|
+
score: pred.score,
|
|
161
|
+
model_version: pred.modelVersion
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
failed++
|
|
165
|
+
errors.push(`Invalid JSON in prediction ${index}: ${pred.result}`)
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const validPredictions = parsedPredictions.filter(p => p !== null)
|
|
171
|
+
|
|
172
|
+
if (validPredictions.length === 0) {
|
|
173
|
+
return {
|
|
174
|
+
created: 0,
|
|
175
|
+
failed: predictions.length,
|
|
176
|
+
errors
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Create predictions
|
|
181
|
+
const response = await labelStudioApi.createPredictions(validPredictions)
|
|
182
|
+
created = response.created || validPredictions.length
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
created,
|
|
186
|
+
failed,
|
|
187
|
+
errors: errors.length > 0 ? errors : undefined
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('Failed to create bulk predictions:', error)
|
|
191
|
+
throw new Error(`Failed to create predictions: ${error.message}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Import predictions for a project
|
|
197
|
+
* This is the recommended way to bulk import AI model predictions
|
|
198
|
+
*/
|
|
199
|
+
@Mutation(returns => PredictionImportResult, {
|
|
200
|
+
description: 'Import predictions for a Label Studio project in bulk'
|
|
201
|
+
})
|
|
202
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
203
|
+
async importPredictionsToProject(
|
|
204
|
+
@Arg('projectId', type => Int) projectId: number,
|
|
205
|
+
@Arg('predictions', type => [BulkPredictionInput]) predictions: BulkPredictionInput[],
|
|
206
|
+
@Ctx() context: ResolverContext
|
|
207
|
+
): Promise<PredictionImportResult> {
|
|
208
|
+
const errors: string[] = []
|
|
209
|
+
let created = 0
|
|
210
|
+
let failed = 0
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
// Parse and validate predictions
|
|
214
|
+
const parsedPredictions = predictions.map((pred, index) => {
|
|
215
|
+
try {
|
|
216
|
+
return {
|
|
217
|
+
task: pred.taskId,
|
|
218
|
+
result: JSON.parse(pred.result),
|
|
219
|
+
score: pred.score,
|
|
220
|
+
model_version: pred.modelVersion
|
|
221
|
+
}
|
|
222
|
+
} catch (e) {
|
|
223
|
+
failed++
|
|
224
|
+
errors.push(`Invalid JSON in prediction ${index}: ${pred.result}`)
|
|
225
|
+
return null
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const validPredictions = parsedPredictions.filter(p => p !== null)
|
|
230
|
+
|
|
231
|
+
if (validPredictions.length === 0) {
|
|
232
|
+
return {
|
|
233
|
+
created: 0,
|
|
234
|
+
failed: predictions.length,
|
|
235
|
+
errors
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Import predictions using project endpoint
|
|
240
|
+
const response = await labelStudioApi.importPredictions(projectId, validPredictions)
|
|
241
|
+
|
|
242
|
+
// Parse response
|
|
243
|
+
if (response.created !== undefined) {
|
|
244
|
+
created = response.created
|
|
245
|
+
} else if (response.task_count !== undefined) {
|
|
246
|
+
created = response.task_count
|
|
247
|
+
} else if (Array.isArray(response)) {
|
|
248
|
+
created = response.length
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
created,
|
|
253
|
+
failed,
|
|
254
|
+
errors: errors.length > 0 ? errors : undefined
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error(`Failed to import predictions to project ${projectId}:`, error)
|
|
258
|
+
throw new Error(`Failed to import predictions: ${error.message}`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Delete prediction
|
|
264
|
+
*/
|
|
265
|
+
@Mutation(returns => Boolean, {
|
|
266
|
+
description: 'Delete a prediction from Label Studio'
|
|
267
|
+
})
|
|
268
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
269
|
+
async deleteLabelStudioPrediction(
|
|
270
|
+
@Arg('predictionId', type => Int) predictionId: number,
|
|
271
|
+
@Ctx() context: ResolverContext
|
|
272
|
+
): Promise<boolean> {
|
|
273
|
+
try {
|
|
274
|
+
await labelStudioApi.deletePrediction(predictionId)
|
|
275
|
+
return true
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error(`Failed to delete prediction ${predictionId}:`, error)
|
|
278
|
+
throw new Error(`Failed to delete prediction: ${error.message}`)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { Resolver, Query, Mutation, Arg, Ctx, Int, Directive } from 'type-graphql'
|
|
2
|
+
import { labelStudioApi } from '../../utils/label-studio-api-client.js'
|
|
3
|
+
import { LabelConfigBuilder } from '../../utils/label-config-builder.js'
|
|
4
|
+
import {
|
|
5
|
+
LabelStudioProject,
|
|
6
|
+
CreateProjectInput,
|
|
7
|
+
CreateProjectWithSpecInput,
|
|
8
|
+
ProjectMetrics,
|
|
9
|
+
AnnotatorStats
|
|
10
|
+
} from '../../types/label-studio-types.js'
|
|
11
|
+
|
|
12
|
+
@Resolver()
|
|
13
|
+
export class ProjectManagement {
|
|
14
|
+
/**
|
|
15
|
+
* Get JWT token for Label Studio SSO
|
|
16
|
+
*/
|
|
17
|
+
@Query(returns => String, {
|
|
18
|
+
description: 'Get JWT token for Label Studio authentication',
|
|
19
|
+
nullable: true
|
|
20
|
+
})
|
|
21
|
+
async labelStudioToken(@Ctx() context: ResolverContext): Promise<string | null> {
|
|
22
|
+
try {
|
|
23
|
+
const { user } = context.state
|
|
24
|
+
if (!user) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Generate JWT token using User.sign() method
|
|
29
|
+
const token = await user.sign()
|
|
30
|
+
return token
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to generate Label Studio token:', error)
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get all Label Studio projects
|
|
39
|
+
*/
|
|
40
|
+
@Query(returns => [LabelStudioProject], {
|
|
41
|
+
description: 'Get all Label Studio projects accessible to the user'
|
|
42
|
+
})
|
|
43
|
+
@Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
44
|
+
async labelStudioProjects(@Ctx() context: ResolverContext): Promise<LabelStudioProject[]> {
|
|
45
|
+
try {
|
|
46
|
+
const projects = await labelStudioApi.getProjects()
|
|
47
|
+
|
|
48
|
+
return projects.map(project => ({
|
|
49
|
+
id: project.id,
|
|
50
|
+
title: project.title,
|
|
51
|
+
description: project.description || '',
|
|
52
|
+
labelConfig: project.label_config || '',
|
|
53
|
+
expertInstruction: project.expert_instruction || '',
|
|
54
|
+
taskCount: project.task_number || 0,
|
|
55
|
+
completedTaskCount: project.finished_task_number || 0,
|
|
56
|
+
completionRate:
|
|
57
|
+
project.task_number > 0 ? (project.finished_task_number || 0) / project.task_number : 0,
|
|
58
|
+
createdAt: new Date(project.created_at),
|
|
59
|
+
updatedAt: project.updated_at ? new Date(project.updated_at) : new Date(project.created_at)
|
|
60
|
+
}))
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to fetch Label Studio projects:', error)
|
|
63
|
+
throw new Error(`Failed to fetch projects: ${error.message}`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get single Label Studio project by ID
|
|
69
|
+
*/
|
|
70
|
+
@Query(returns => LabelStudioProject, {
|
|
71
|
+
description: 'Get a single Label Studio project by ID',
|
|
72
|
+
nullable: true
|
|
73
|
+
})
|
|
74
|
+
@Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
75
|
+
async labelStudioProject(
|
|
76
|
+
@Arg('projectId', type => Int) projectId: number,
|
|
77
|
+
@Ctx() context: ResolverContext
|
|
78
|
+
): Promise<LabelStudioProject | null> {
|
|
79
|
+
try {
|
|
80
|
+
const project = await labelStudioApi.getProject(projectId)
|
|
81
|
+
|
|
82
|
+
if (!project) {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
id: project.id,
|
|
88
|
+
title: project.title,
|
|
89
|
+
description: project.description || '',
|
|
90
|
+
labelConfig: project.label_config || '',
|
|
91
|
+
expertInstruction: project.expert_instruction || '',
|
|
92
|
+
taskCount: project.task_number || 0,
|
|
93
|
+
completedTaskCount: project.finished_task_number || 0,
|
|
94
|
+
completionRate:
|
|
95
|
+
project.task_number > 0 ? (project.finished_task_number || 0) / project.task_number : 0,
|
|
96
|
+
createdAt: new Date(project.created_at),
|
|
97
|
+
updatedAt: project.updated_at ? new Date(project.updated_at) : new Date(project.created_at)
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(`Failed to fetch Label Studio project ${projectId}:`, error)
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create new Label Studio project
|
|
107
|
+
*/
|
|
108
|
+
@Mutation(returns => LabelStudioProject, {
|
|
109
|
+
description: 'Create a new Label Studio project'
|
|
110
|
+
})
|
|
111
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
112
|
+
async createLabelStudioProject(
|
|
113
|
+
@Arg('input') input: CreateProjectInput,
|
|
114
|
+
@Ctx() context: ResolverContext
|
|
115
|
+
): Promise<LabelStudioProject> {
|
|
116
|
+
try {
|
|
117
|
+
const project = await labelStudioApi.createProject({
|
|
118
|
+
title: input.title,
|
|
119
|
+
description: input.description,
|
|
120
|
+
label_config: input.labelConfig,
|
|
121
|
+
expert_instruction: input.expertInstruction
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
id: project.id,
|
|
126
|
+
title: project.title,
|
|
127
|
+
description: project.description || '',
|
|
128
|
+
labelConfig: project.label_config || '',
|
|
129
|
+
expertInstruction: project.expert_instruction || '',
|
|
130
|
+
taskCount: 0,
|
|
131
|
+
completedTaskCount: 0,
|
|
132
|
+
completionRate: 0,
|
|
133
|
+
createdAt: new Date(project.created_at),
|
|
134
|
+
updatedAt: new Date(project.updated_at)
|
|
135
|
+
}
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Failed to create Label Studio project:', error)
|
|
138
|
+
throw new Error(`Failed to create project: ${error.message}`)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Update Label Studio project
|
|
144
|
+
*/
|
|
145
|
+
@Mutation(returns => LabelStudioProject, {
|
|
146
|
+
description: 'Update an existing Label Studio project'
|
|
147
|
+
})
|
|
148
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
149
|
+
async updateLabelStudioProject(
|
|
150
|
+
@Arg('projectId', type => Int) projectId: number,
|
|
151
|
+
@Arg('input') input: CreateProjectInput,
|
|
152
|
+
@Ctx() context: ResolverContext
|
|
153
|
+
): Promise<LabelStudioProject> {
|
|
154
|
+
try {
|
|
155
|
+
const project = await labelStudioApi.updateProject(projectId, {
|
|
156
|
+
title: input.title,
|
|
157
|
+
description: input.description,
|
|
158
|
+
label_config: input.labelConfig,
|
|
159
|
+
expert_instruction: input.expertInstruction
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
id: project.id,
|
|
164
|
+
title: project.title,
|
|
165
|
+
description: project.description || '',
|
|
166
|
+
labelConfig: project.label_config || '',
|
|
167
|
+
expertInstruction: project.expert_instruction || '',
|
|
168
|
+
taskCount: project.task_number || 0,
|
|
169
|
+
completedTaskCount: project.finished_task_number || 0,
|
|
170
|
+
completionRate:
|
|
171
|
+
project.task_number > 0 ? (project.finished_task_number || 0) / project.task_number : 0,
|
|
172
|
+
createdAt: new Date(project.created_at),
|
|
173
|
+
updatedAt: project.updated_at ? new Date(project.updated_at) : new Date(project.created_at)
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(`Failed to update Label Studio project ${projectId}:`, error)
|
|
177
|
+
throw new Error(`Failed to update project: ${error.message}`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Delete Label Studio project
|
|
183
|
+
*/
|
|
184
|
+
@Mutation(returns => Boolean, {
|
|
185
|
+
description: 'Delete a Label Studio project'
|
|
186
|
+
})
|
|
187
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
188
|
+
async deleteLabelStudioProject(
|
|
189
|
+
@Arg('projectId', type => Int) projectId: number,
|
|
190
|
+
@Ctx() context: ResolverContext
|
|
191
|
+
): Promise<boolean> {
|
|
192
|
+
try {
|
|
193
|
+
await labelStudioApi.deleteProject(projectId)
|
|
194
|
+
return true
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(`Failed to delete Label Studio project ${projectId}:`, error)
|
|
197
|
+
throw new Error(`Failed to delete project: ${error.message}`)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get project metrics and statistics
|
|
203
|
+
*/
|
|
204
|
+
@Query(returns => ProjectMetrics, {
|
|
205
|
+
description: 'Get metrics and statistics for a Label Studio project'
|
|
206
|
+
})
|
|
207
|
+
@Directive('@privilege(category: "label-studio", privilege: "query")')
|
|
208
|
+
async labelStudioProjectMetrics(
|
|
209
|
+
@Arg('projectId', type => Int) projectId: number,
|
|
210
|
+
@Ctx() context: ResolverContext
|
|
211
|
+
): Promise<ProjectMetrics> {
|
|
212
|
+
try {
|
|
213
|
+
const stats = await labelStudioApi.getProjectStats(projectId)
|
|
214
|
+
const annotatorStats = await labelStudioApi.getAnnotatorStats(projectId)
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
totalTasks: stats.totalTasks,
|
|
218
|
+
completedTasks: stats.completedTasks,
|
|
219
|
+
totalAnnotations: stats.totalAnnotations,
|
|
220
|
+
avgAnnotationsPerTask: stats.avgAnnotationsPerTask,
|
|
221
|
+
completionRate: stats.completionRate,
|
|
222
|
+
avgTimePerTask: null, // 계산 필요
|
|
223
|
+
annotatorStats: annotatorStats.map((stat: any) => ({
|
|
224
|
+
email: stat.email,
|
|
225
|
+
annotationCount: stat.annotationCount,
|
|
226
|
+
avgTime: stat.avgTime,
|
|
227
|
+
lastAnnotationDate: new Date(stat.lastAnnotationDate)
|
|
228
|
+
}))
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(`Failed to fetch project metrics for ${projectId}:`, error)
|
|
232
|
+
throw new Error(`Failed to fetch project metrics: ${error.message}`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Create project with flexible config specification
|
|
238
|
+
* Uses LabelConfigBuilder for declarative template generation
|
|
239
|
+
*/
|
|
240
|
+
@Mutation(returns => LabelStudioProject, {
|
|
241
|
+
description: 'Create Label Studio project with flexible config specification'
|
|
242
|
+
})
|
|
243
|
+
@Directive('@privilege(category: "label-studio", privilege: "mutation")')
|
|
244
|
+
async createLabelStudioProjectWithSpec(
|
|
245
|
+
@Arg('input') input: CreateProjectWithSpecInput,
|
|
246
|
+
@Ctx() context: ResolverContext
|
|
247
|
+
): Promise<LabelStudioProject> {
|
|
248
|
+
try {
|
|
249
|
+
// Parse controls JSON
|
|
250
|
+
const controls = JSON.parse(input.labelConfigSpec.controls)
|
|
251
|
+
|
|
252
|
+
// Build label config using LabelConfigBuilder
|
|
253
|
+
const labelConfig = LabelConfigBuilder.build({
|
|
254
|
+
dataType: input.labelConfigSpec.dataType as any,
|
|
255
|
+
dataName: input.labelConfigSpec.dataName,
|
|
256
|
+
controls
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
// Create project
|
|
260
|
+
const project = await labelStudioApi.createProject({
|
|
261
|
+
title: input.title,
|
|
262
|
+
description: input.description,
|
|
263
|
+
label_config: labelConfig,
|
|
264
|
+
expert_instruction: input.expertInstruction
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
id: project.id,
|
|
269
|
+
title: project.title,
|
|
270
|
+
description: project.description || '',
|
|
271
|
+
labelConfig: project.label_config || '',
|
|
272
|
+
expertInstruction: project.expert_instruction || '',
|
|
273
|
+
taskCount: 0,
|
|
274
|
+
completedTaskCount: 0,
|
|
275
|
+
completionRate: 0,
|
|
276
|
+
createdAt: new Date(project.created_at),
|
|
277
|
+
updatedAt: new Date(project.updated_at)
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('Failed to create Label Studio project with spec:', error)
|
|
281
|
+
throw new Error(`Failed to create project: ${error.message}`)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|