@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.
Files changed (152) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/EXTERNAL_DATA_SOURCING.md +484 -0
  3. package/IMPLEMENTATION_GUIDE.md +469 -0
  4. package/INTEGRATION.md +279 -0
  5. package/README.md +1014 -0
  6. package/SETUP_GUIDE.md +577 -0
  7. package/TEST_GUIDE.md +387 -0
  8. package/UI_CUSTOMIZATION.md +395 -0
  9. package/USER_SYNC_GUIDE.md +514 -0
  10. package/client/bootstrap.ts +1 -0
  11. package/client/index.ts +1 -0
  12. package/client/label-studio-label-page.ts +52 -0
  13. package/client/label-studio-project-create.ts +216 -0
  14. package/client/label-studio-project-list.ts +214 -0
  15. package/client/label-studio-wrapper.ts +294 -0
  16. package/client/route.ts +15 -0
  17. package/client/tsconfig.json +13 -0
  18. package/config/config.development.js +124 -0
  19. package/config/config.production.js +182 -0
  20. package/dist-client/bootstrap.d.ts +1 -0
  21. package/dist-client/bootstrap.js +2 -0
  22. package/dist-client/bootstrap.js.map +1 -0
  23. package/dist-client/index.d.ts +1 -0
  24. package/dist-client/index.js +2 -0
  25. package/dist-client/index.js.map +1 -0
  26. package/dist-client/label-studio-label-page.d.ts +8 -0
  27. package/dist-client/label-studio-label-page.js +54 -0
  28. package/dist-client/label-studio-label-page.js.map +1 -0
  29. package/dist-client/label-studio-project-create.d.ts +16 -0
  30. package/dist-client/label-studio-project-create.js +235 -0
  31. package/dist-client/label-studio-project-create.js.map +1 -0
  32. package/dist-client/label-studio-project-list.d.ts +16 -0
  33. package/dist-client/label-studio-project-list.js +222 -0
  34. package/dist-client/label-studio-project-list.js.map +1 -0
  35. package/dist-client/label-studio-wrapper.d.ts +57 -0
  36. package/dist-client/label-studio-wrapper.js +304 -0
  37. package/dist-client/label-studio-wrapper.js.map +1 -0
  38. package/dist-client/route.d.ts +1 -0
  39. package/dist-client/route.js +14 -0
  40. package/dist-client/route.js.map +1 -0
  41. package/dist-client/tsconfig.tsbuildinfo +1 -0
  42. package/dist-server/controller/label-studio-role-mapper.d.ts +35 -0
  43. package/dist-server/controller/label-studio-role-mapper.js +65 -0
  44. package/dist-server/controller/label-studio-role-mapper.js.map +1 -0
  45. package/dist-server/controller/user-provisioning-service.d.ts +66 -0
  46. package/dist-server/controller/user-provisioning-service.js +264 -0
  47. package/dist-server/controller/user-provisioning-service.js.map +1 -0
  48. package/dist-server/index.d.ts +7 -0
  49. package/dist-server/index.js +19 -0
  50. package/dist-server/index.js.map +1 -0
  51. package/dist-server/route/label-studio-sso.d.ts +2 -0
  52. package/dist-server/route/label-studio-sso.js +156 -0
  53. package/dist-server/route/label-studio-sso.js.map +1 -0
  54. package/dist-server/route/webhook.d.ts +65 -0
  55. package/dist-server/route/webhook.js +248 -0
  56. package/dist-server/route/webhook.js.map +1 -0
  57. package/dist-server/route.d.ts +1 -0
  58. package/dist-server/route.js +21 -0
  59. package/dist-server/route.js.map +1 -0
  60. package/dist-server/service/ai-prediction-service.d.ts +27 -0
  61. package/dist-server/service/ai-prediction-service.js +222 -0
  62. package/dist-server/service/ai-prediction-service.js.map +1 -0
  63. package/dist-server/service/dataset-labeling-integration.d.ts +44 -0
  64. package/dist-server/service/dataset-labeling-integration.js +512 -0
  65. package/dist-server/service/dataset-labeling-integration.js.map +1 -0
  66. package/dist-server/service/external-data-source-service.d.ts +78 -0
  67. package/dist-server/service/external-data-source-service.js +415 -0
  68. package/dist-server/service/external-data-source-service.js.map +1 -0
  69. package/dist-server/service/index.d.ts +12 -0
  70. package/dist-server/service/index.js +27 -0
  71. package/dist-server/service/index.js.map +1 -0
  72. package/dist-server/service/label-studio-sso-service.d.ts +38 -0
  73. package/dist-server/service/label-studio-sso-service.js +98 -0
  74. package/dist-server/service/label-studio-sso-service.js.map +1 -0
  75. package/dist-server/service/ml/ml-backend-service.d.ts +23 -0
  76. package/dist-server/service/ml/ml-backend-service.js +153 -0
  77. package/dist-server/service/ml/ml-backend-service.js.map +1 -0
  78. package/dist-server/service/prediction/prediction-management.d.ts +32 -0
  79. package/dist-server/service/prediction/prediction-management.js +299 -0
  80. package/dist-server/service/prediction/prediction-management.js.map +1 -0
  81. package/dist-server/service/project/project-management.d.ts +36 -0
  82. package/dist-server/service/project/project-management.js +309 -0
  83. package/dist-server/service/project/project-management.js.map +1 -0
  84. package/dist-server/service/task/task-management.d.ts +42 -0
  85. package/dist-server/service/task/task-management.js +372 -0
  86. package/dist-server/service/task/task-management.js.map +1 -0
  87. package/dist-server/service/user-provisioning/user-sync-mutation.d.ts +28 -0
  88. package/dist-server/service/user-provisioning/user-sync-mutation.js +111 -0
  89. package/dist-server/service/user-provisioning/user-sync-mutation.js.map +1 -0
  90. package/dist-server/service/webhook/webhook-management.d.ts +21 -0
  91. package/dist-server/service/webhook/webhook-management.js +134 -0
  92. package/dist-server/service/webhook/webhook-management.js.map +1 -0
  93. package/dist-server/tsconfig.tsbuildinfo +1 -0
  94. package/dist-server/types/dataset-labeling-types.d.ts +71 -0
  95. package/dist-server/types/dataset-labeling-types.js +259 -0
  96. package/dist-server/types/dataset-labeling-types.js.map +1 -0
  97. package/dist-server/types/label-studio-types.d.ts +128 -0
  98. package/dist-server/types/label-studio-types.js +494 -0
  99. package/dist-server/types/label-studio-types.js.map +1 -0
  100. package/dist-server/types/prediction-types.d.ts +39 -0
  101. package/dist-server/types/prediction-types.js +121 -0
  102. package/dist-server/types/prediction-types.js.map +1 -0
  103. package/dist-server/utils/annotation-exporter.d.ts +104 -0
  104. package/dist-server/utils/annotation-exporter.js +261 -0
  105. package/dist-server/utils/annotation-exporter.js.map +1 -0
  106. package/dist-server/utils/label-config-builder.d.ts +117 -0
  107. package/dist-server/utils/label-config-builder.js +286 -0
  108. package/dist-server/utils/label-config-builder.js.map +1 -0
  109. package/dist-server/utils/label-studio-api-client.d.ts +180 -0
  110. package/dist-server/utils/label-studio-api-client.js +401 -0
  111. package/dist-server/utils/label-studio-api-client.js.map +1 -0
  112. package/dist-server/utils/media-url-extractor.d.ts +45 -0
  113. package/dist-server/utils/media-url-extractor.js +152 -0
  114. package/dist-server/utils/media-url-extractor.js.map +1 -0
  115. package/dist-server/utils/task-transformer.d.ts +108 -0
  116. package/dist-server/utils/task-transformer.js +260 -0
  117. package/dist-server/utils/task-transformer.js.map +1 -0
  118. package/package.json +47 -0
  119. package/server/SERVER_STRUCTURE.md +351 -0
  120. package/server/controller/label-studio-role-mapper.ts +76 -0
  121. package/server/controller/user-provisioning-service.ts +340 -0
  122. package/server/index.ts +19 -0
  123. package/server/route/label-studio-sso.ts +194 -0
  124. package/server/route/webhook.ts +304 -0
  125. package/server/route.ts +35 -0
  126. package/server/service/ai-prediction-service.ts +239 -0
  127. package/server/service/dataset-labeling-integration.ts +590 -0
  128. package/server/service/external-data-source-service.ts +438 -0
  129. package/server/service/index.ts +24 -0
  130. package/server/service/label-studio-sso-service.ts +108 -0
  131. package/server/service/labeling-scenario-service.ts.deprecated +566 -0
  132. package/server/service/ml/ml-backend-service.ts +127 -0
  133. package/server/service/prediction/prediction-management.ts +281 -0
  134. package/server/service/project/project-management.ts +284 -0
  135. package/server/service/task/task-management.ts +363 -0
  136. package/server/service/user-provisioning/user-sync-mutation.ts +80 -0
  137. package/server/service/webhook/webhook-management.ts +109 -0
  138. package/server/tsconfig.json +11 -0
  139. package/server/types/dataset-labeling-types.ts +181 -0
  140. package/server/types/global.d.ts +23 -0
  141. package/server/types/label-studio-types.ts +346 -0
  142. package/server/types/prediction-types.ts +86 -0
  143. package/server/types/scenario-types.ts.deprecated +362 -0
  144. package/server/utils/annotation-exporter.ts +340 -0
  145. package/server/utils/label-config-builder.ts +340 -0
  146. package/server/utils/label-studio-api-client.ts +487 -0
  147. package/server/utils/media-url-extractor.ts +193 -0
  148. package/server/utils/task-transformer.ts +342 -0
  149. package/test-ai-prediction.js +268 -0
  150. package/test-dataset-integration.js +449 -0
  151. package/test-simple.js +89 -0
  152. package/things-factory.config.js +12 -0
@@ -0,0 +1,304 @@
1
+ import Koa from 'koa'
2
+ import Router from 'koa-router'
3
+ import { getRepository } from 'typeorm'
4
+
5
+ const webhookRouter = new Router()
6
+
7
+ /**
8
+ * Label Studio Webhook Event Types
9
+ */
10
+ export enum WebhookAction {
11
+ ANNOTATION_CREATED = 'ANNOTATION_CREATED',
12
+ ANNOTATION_UPDATED = 'ANNOTATION_UPDATED',
13
+ ANNOTATION_DELETED = 'ANNOTATION_DELETED',
14
+ TASK_CREATED = 'TASK_CREATED',
15
+ TASK_UPDATED = 'TASK_UPDATED',
16
+ TASK_DELETED = 'TASK_DELETED',
17
+ PROJECT_UPDATED = 'PROJECT_UPDATED'
18
+ }
19
+
20
+ export interface WebhookPayload {
21
+ action: WebhookAction
22
+ project: {
23
+ id: number
24
+ title: string
25
+ }
26
+ task?: {
27
+ id: number
28
+ data: any
29
+ annotations: any[]
30
+ }
31
+ annotation?: {
32
+ id: number
33
+ result: any[]
34
+ completed_by: {
35
+ id: number
36
+ email: string
37
+ }
38
+ lead_time: number
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Webhook handler function type
44
+ * Applications can register custom handlers for any webhook action
45
+ */
46
+ export type WebhookHandler = (payload: WebhookPayload, context: Koa.Context) => Promise<void>
47
+
48
+ /**
49
+ * Registry for custom webhook handlers
50
+ * Multiple handlers can be registered per action
51
+ */
52
+ const customHandlers: Map<WebhookAction, WebhookHandler[]> = new Map()
53
+
54
+ /**
55
+ * Register a custom webhook handler
56
+ * Handlers are executed in registration order
57
+ *
58
+ * @param action - Webhook action to handle
59
+ * @param handler - Handler function
60
+ *
61
+ * @example
62
+ * registerWebhookHandler(WebhookAction.ANNOTATION_CREATED, async (payload, ctx) => {
63
+ * console.log('Custom handler:', payload.annotation?.id)
64
+ * // Store annotation in database
65
+ * // Trigger ML training
66
+ * // Send notifications
67
+ * })
68
+ */
69
+ export function registerWebhookHandler(action: WebhookAction, handler: WebhookHandler): void {
70
+ if (!customHandlers.has(action)) {
71
+ customHandlers.set(action, [])
72
+ }
73
+ customHandlers.get(action)!.push(handler)
74
+ }
75
+
76
+ /**
77
+ * Unregister a webhook handler
78
+ */
79
+ export function unregisterWebhookHandler(action: WebhookAction, handler: WebhookHandler): void {
80
+ const handlers = customHandlers.get(action)
81
+ if (handlers) {
82
+ const index = handlers.indexOf(handler)
83
+ if (index > -1) {
84
+ handlers.splice(index, 1)
85
+ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Clear all custom handlers for an action
91
+ */
92
+ export function clearWebhookHandlers(action?: WebhookAction): void {
93
+ if (action) {
94
+ customHandlers.delete(action)
95
+ } else {
96
+ customHandlers.clear()
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Execute all registered handlers for an action
102
+ */
103
+ async function executeCustomHandlers(action: WebhookAction, payload: WebhookPayload, ctx: Koa.Context): Promise<void> {
104
+ const handlers = customHandlers.get(action)
105
+ if (!handlers || handlers.length === 0) {
106
+ return
107
+ }
108
+
109
+ // Execute all handlers in sequence
110
+ for (const handler of handlers) {
111
+ try {
112
+ await handler(payload, ctx)
113
+ } catch (error) {
114
+ console.error(`[Webhook] Custom handler error for ${action}:`, error)
115
+ // Continue executing other handlers even if one fails
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Handle annotation created event
122
+ */
123
+ async function handleAnnotationCreated(payload: WebhookPayload) {
124
+ console.log(`[Webhook] Annotation created:`, {
125
+ projectId: payload.project.id,
126
+ taskId: payload.task?.id,
127
+ annotationId: payload.annotation?.id,
128
+ completedBy: payload.annotation?.completed_by?.email
129
+ })
130
+
131
+ // TODO: Implement business logic
132
+ // 1. Store annotation in Things-Factory database
133
+ // 2. Trigger downstream processes (e.g., model training)
134
+ // 3. Send notifications to stakeholders
135
+ // 4. Update task status in external systems
136
+ }
137
+
138
+ /**
139
+ * Handle annotation updated event
140
+ */
141
+ async function handleAnnotationUpdated(payload: WebhookPayload) {
142
+ console.log(`[Webhook] Annotation updated:`, {
143
+ projectId: payload.project.id,
144
+ annotationId: payload.annotation?.id
145
+ })
146
+
147
+ // TODO: Update annotation in database
148
+ }
149
+
150
+ /**
151
+ * Handle annotation deleted event
152
+ */
153
+ async function handleAnnotationDeleted(payload: WebhookPayload) {
154
+ console.log(`[Webhook] Annotation deleted:`, {
155
+ projectId: payload.project.id,
156
+ annotationId: payload.annotation?.id
157
+ })
158
+
159
+ // TODO: Remove annotation from database
160
+ }
161
+
162
+ /**
163
+ * Handle task created event
164
+ */
165
+ async function handleTaskCreated(payload: WebhookPayload) {
166
+ console.log(`[Webhook] Task created:`, {
167
+ projectId: payload.project.id,
168
+ taskId: payload.task?.id
169
+ })
170
+
171
+ // TODO: Store task reference
172
+ }
173
+
174
+ /**
175
+ * Handle project updated event
176
+ */
177
+ async function handleProjectUpdated(payload: WebhookPayload) {
178
+ console.log(`[Webhook] Project updated:`, {
179
+ projectId: payload.project.id,
180
+ projectTitle: payload.project.title
181
+ })
182
+
183
+ // TODO: Update project metadata
184
+ }
185
+
186
+ /**
187
+ * Main webhook endpoint
188
+ * Label Studio will POST events here
189
+ */
190
+ webhookRouter.post('/label-studio/webhook', async (ctx: Koa.Context) => {
191
+ try {
192
+ const payload = ctx.request.body as WebhookPayload
193
+
194
+ console.log(`[Webhook] Received event: ${payload.action}`)
195
+
196
+ // Execute default handlers first
197
+ switch (payload.action) {
198
+ case WebhookAction.ANNOTATION_CREATED:
199
+ await handleAnnotationCreated(payload)
200
+ break
201
+
202
+ case WebhookAction.ANNOTATION_UPDATED:
203
+ await handleAnnotationUpdated(payload)
204
+ break
205
+
206
+ case WebhookAction.ANNOTATION_DELETED:
207
+ await handleAnnotationDeleted(payload)
208
+ break
209
+
210
+ case WebhookAction.TASK_CREATED:
211
+ await handleTaskCreated(payload)
212
+ break
213
+
214
+ case WebhookAction.TASK_UPDATED:
215
+ // Optional: handle task updates
216
+ break
217
+
218
+ case WebhookAction.TASK_DELETED:
219
+ // Optional: handle task deletions
220
+ break
221
+
222
+ case WebhookAction.PROJECT_UPDATED:
223
+ await handleProjectUpdated(payload)
224
+ break
225
+
226
+ default:
227
+ console.warn(`[Webhook] Unknown action: ${payload.action}`)
228
+ }
229
+
230
+ // Execute custom handlers
231
+ await executeCustomHandlers(payload.action, payload, ctx)
232
+
233
+ ctx.status = 200
234
+ ctx.body = { success: true }
235
+ } catch (error) {
236
+ console.error('[Webhook] Error processing webhook:', error)
237
+ ctx.status = 500
238
+ ctx.body = { error: error.message }
239
+ }
240
+ })
241
+
242
+ /**
243
+ * Webhook registration helper
244
+ * Call this to register webhook with Label Studio
245
+ */
246
+ webhookRouter.post('/label-studio/webhook/register', async (ctx: Koa.Context) => {
247
+ try {
248
+ const { projectId } = ctx.request.body as { projectId: number }
249
+
250
+ // Get Things-Factory server URL
251
+ const serverUrl = process.env.SERVER_URL || 'http://localhost:3000'
252
+ const webhookUrl = `${serverUrl}/label-studio/webhook`
253
+
254
+ // Register webhook using Label Studio API
255
+ const { labelStudioApi } = await import('../utils/label-studio-api-client.js')
256
+
257
+ const webhook = await labelStudioApi.createWebhook({
258
+ project: projectId,
259
+ url: webhookUrl,
260
+ send_payload: true,
261
+ send_for_all_actions: true,
262
+ headers: {
263
+ 'Content-Type': 'application/json'
264
+ }
265
+ })
266
+
267
+ ctx.status = 200
268
+ ctx.body = {
269
+ success: true,
270
+ webhook
271
+ }
272
+ } catch (error) {
273
+ console.error('[Webhook] Error registering webhook:', error)
274
+ ctx.status = 500
275
+ ctx.body = { error: error.message }
276
+ }
277
+ })
278
+
279
+ /**
280
+ * Get webhooks for a project
281
+ */
282
+ webhookRouter.get('/label-studio/webhook/:projectId', async (ctx: Koa.Context) => {
283
+ try {
284
+ const projectId = parseInt(ctx.params.projectId)
285
+
286
+ const { labelStudioApi } = await import('../utils/label-studio-api-client.js')
287
+ const webhooks = await labelStudioApi.getWebhooks(projectId)
288
+
289
+ ctx.status = 200
290
+ ctx.body = webhooks
291
+ } catch (error) {
292
+ console.error('[Webhook] Error fetching webhooks:', error)
293
+ ctx.status = 500
294
+ ctx.body = { error: error.message }
295
+ }
296
+ })
297
+
298
+ // Register routes with Things-Factory
299
+ process.on('bootstrap-module-domain-private-route', (_app: Koa, router: Router) => {
300
+ router.use(webhookRouter.routes())
301
+ router.use(webhookRouter.allowedMethods())
302
+ })
303
+
304
+ export { webhookRouter }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Label Studio Integration Routes
3
+ *
4
+ * This module registers all Label Studio-related routes:
5
+ * - Webhook handler for Label Studio events (PUBLIC)
6
+ * - SSO authentication endpoint (PRIVATE - requires authentication)
7
+ *
8
+ * ## Subdomain Cookie Sharing Architecture:
9
+ * This integration uses subdomain-based cookie sharing instead of proxying.
10
+ * - Backend gets JWT token from Label Studio and sets shared domain cookie
11
+ * - Frontend loads Label Studio directly (not through proxy)
12
+ * - Cookie is automatically sent with all subdomain requests
13
+ */
14
+ import Koa from 'koa'
15
+ import Router from 'koa-router'
16
+ import { webhookRouter } from './route/webhook'
17
+ import { ssoRouter } from './route/label-studio-sso'
18
+
19
+ // Public routes - No authentication required
20
+ process.on('bootstrap-module-global-public-route' as any, (_app: Koa, routes: Router) => {
21
+ /*
22
+ * Register webhook routes (Label Studio → Things Factory)
23
+ * These must be public so Label Studio can send events
24
+ */
25
+ routes.use(webhookRouter.routes(), webhookRouter.allowedMethods())
26
+ })
27
+
28
+ // Private routes - Authentication required
29
+ process.on('bootstrap-module-domain-private-route' as any, (_app: Koa, routes: Router) => {
30
+ /*
31
+ * Register Label Studio SSO routes
32
+ * This requires authentication - ctx.state.user will be available
33
+ */
34
+ routes.use(ssoRouter.routes(), ssoRouter.allowedMethods())
35
+ })
@@ -0,0 +1,239 @@
1
+ import { Resolver, Mutation, Arg, Ctx, Int, Float, Directive } from 'type-graphql'
2
+ import { AIModelClientFactory, DetectedObject } from '@things-factory/ai-inference'
3
+ import { labelStudioApi } from '../utils/label-studio-api-client.js'
4
+ import {
5
+ GeneratePredictionRequest,
6
+ BatchGeneratePredictionRequest,
7
+ PredictionGenerationResult,
8
+ BatchPredictionResult
9
+ } from '../types/prediction-types.js'
10
+
11
+ /**
12
+ * Label Studio AI Prediction Service
13
+ *
14
+ * Integrates AI inference with Label Studio prediction system
15
+ * Uses ai-inference module for pure AI operations
16
+ */
17
+ @Resolver()
18
+ export class LabelStudioAIPredictionService {
19
+ /**
20
+ * Generate Label Studio prediction for a task
21
+ * Runs AI model and creates prediction in Label Studio
22
+ */
23
+ @Mutation(returns => PredictionGenerationResult, {
24
+ description: 'Generate Label Studio prediction for a task using AI model'
25
+ })
26
+ @Directive('@privilege(category: "label-studio", privilege: "mutation")')
27
+ async generatePrediction(
28
+ @Arg('input') input: GeneratePredictionRequest,
29
+ @Ctx() context: ResolverContext
30
+ ): Promise<PredictionGenerationResult> {
31
+ try {
32
+ // 1. Run AI model using ai-inference module
33
+ const modelClient = input.modelId
34
+ ? AIModelClientFactory.getClient(input.modelId)
35
+ : AIModelClientFactory.getDefaultClient()
36
+
37
+ const objects = await modelClient.detectObjects(input.imageUrl, {
38
+ confidenceThreshold: input.confidenceThreshold
39
+ })
40
+
41
+ if (objects.length === 0) {
42
+ return {
43
+ taskId: input.taskId,
44
+ success: true,
45
+ objectCount: 0,
46
+ error: 'No objects detected'
47
+ }
48
+ }
49
+
50
+ // 2. Convert AI results to Label Studio format
51
+ const labelStudioResult = this.convertToLabelStudioFormat(objects)
52
+
53
+ // 3. Calculate average confidence
54
+ const avgConfidence = objects.reduce((sum, obj) => sum + obj.confidence, 0) / objects.length
55
+
56
+ // 4. Create prediction in Label Studio
57
+ const prediction = await labelStudioApi.createPrediction({
58
+ task: input.taskId,
59
+ result: labelStudioResult,
60
+ score: avgConfidence,
61
+ model_version: input.modelId || 'default-model-v1.0'
62
+ })
63
+
64
+ console.log(
65
+ `[LS AI Prediction] Created prediction ${prediction.id} for task ${input.taskId} with ${objects.length} objects`
66
+ )
67
+
68
+ return {
69
+ taskId: input.taskId,
70
+ predictionId: prediction.id,
71
+ success: true,
72
+ objectCount: objects.length,
73
+ avgConfidence
74
+ }
75
+ } catch (error) {
76
+ console.error(`[LS AI Prediction] Failed for task ${input.taskId}:`, error)
77
+ return {
78
+ taskId: input.taskId,
79
+ success: false,
80
+ objectCount: 0,
81
+ error: error.message
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Generate predictions for multiple tasks in batch
88
+ */
89
+ @Mutation(returns => BatchPredictionResult, {
90
+ description: 'Generate Label Studio predictions for multiple tasks in batch'
91
+ })
92
+ @Directive('@privilege(category: "label-studio", privilege: "mutation")')
93
+ async generateBatchPredictions(
94
+ @Arg('input') input: BatchGeneratePredictionRequest,
95
+ @Ctx() context: ResolverContext
96
+ ): Promise<BatchPredictionResult> {
97
+ const results: PredictionGenerationResult[] = []
98
+ let succeeded = 0
99
+ let failed = 0
100
+
101
+ const modelId = input.modelId || 'default-model-v1.0'
102
+
103
+ try {
104
+ // Process tasks in parallel (limit concurrency to avoid overload)
105
+ const BATCH_SIZE = 5
106
+ for (let i = 0; i < input.taskIds.length; i += BATCH_SIZE) {
107
+ const batch = input.taskIds.slice(i, i + BATCH_SIZE)
108
+
109
+ const batchPromises = batch.map(async taskId => {
110
+ try {
111
+ // Get task data from Label Studio
112
+ const task = await labelStudioApi.getTask(taskId)
113
+
114
+ if (!task || !task.data || !task.data.image) {
115
+ failed++
116
+ return {
117
+ taskId,
118
+ success: false,
119
+ objectCount: 0,
120
+ error: 'Task has no image data'
121
+ }
122
+ }
123
+
124
+ // Generate prediction
125
+ const result = await this.generatePrediction(
126
+ {
127
+ taskId,
128
+ imageUrl: task.data.image,
129
+ modelId: input.modelId,
130
+ confidenceThreshold: input.confidenceThreshold
131
+ },
132
+ context
133
+ )
134
+
135
+ if (result.success) {
136
+ succeeded++
137
+ } else {
138
+ failed++
139
+ }
140
+
141
+ return result
142
+ } catch (error) {
143
+ failed++
144
+ return {
145
+ taskId,
146
+ success: false,
147
+ objectCount: 0,
148
+ error: error.message
149
+ }
150
+ }
151
+ })
152
+
153
+ const batchResults = await Promise.all(batchPromises)
154
+ results.push(...batchResults)
155
+ }
156
+
157
+ console.log(
158
+ `[LS AI Prediction] Batch completed: ${succeeded} succeeded, ${failed} failed out of ${input.taskIds.length} tasks`
159
+ )
160
+
161
+ return {
162
+ total: input.taskIds.length,
163
+ succeeded,
164
+ failed,
165
+ results,
166
+ modelVersion: modelId
167
+ }
168
+ } catch (error) {
169
+ console.error('[LS AI Prediction] Batch processing failed:', error)
170
+ throw new Error(`Batch prediction generation failed: ${error.message}`)
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Auto-generate predictions for all unlabeled tasks in a project
176
+ */
177
+ @Mutation(returns => BatchPredictionResult, {
178
+ description: 'Auto-generate predictions for all unlabeled tasks in a Label Studio project'
179
+ })
180
+ @Directive('@privilege(category: "label-studio", privilege: "mutation")')
181
+ async autoGeneratePredictions(
182
+ @Arg('projectId', type => Int) projectId: number,
183
+ @Arg('modelId', { nullable: true }) modelId: string,
184
+ @Arg('confidenceThreshold', type => Float, { nullable: true }) confidenceThreshold: number,
185
+ @Ctx() context: ResolverContext
186
+ ): Promise<BatchPredictionResult> {
187
+ try {
188
+ // Get all tasks from project
189
+ const tasksResponse = await labelStudioApi.getTasks(projectId, {
190
+ page_size: 1000 // Adjust as needed
191
+ })
192
+
193
+ const tasks = tasksResponse.tasks || tasksResponse.results || []
194
+
195
+ // Filter unlabeled tasks (no annotations)
196
+ const unlabeledTasks = tasks.filter((task: any) => {
197
+ const annotationCount = task.annotations?.length || task.total_annotations || 0
198
+ return annotationCount === 0
199
+ })
200
+
201
+ console.log(
202
+ `[LS AI Prediction] Auto-generating predictions for ${unlabeledTasks.length} unlabeled tasks in project ${projectId}`
203
+ )
204
+
205
+ // Generate predictions in batch
206
+ return await this.generateBatchPredictions(
207
+ {
208
+ projectId,
209
+ taskIds: unlabeledTasks.map((t: any) => t.id),
210
+ modelId,
211
+ confidenceThreshold
212
+ },
213
+ context
214
+ )
215
+ } catch (error) {
216
+ console.error(`[LS AI Prediction] Auto-generation failed for project ${projectId}:`, error)
217
+ throw new Error(`Auto prediction generation failed: ${error.message}`)
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Convert AI detection results to Label Studio format
223
+ * This handles the Label Studio-specific data structure
224
+ */
225
+ private convertToLabelStudioFormat(objects: DetectedObject[]) {
226
+ return objects.map(obj => ({
227
+ from_name: 'label',
228
+ to_name: 'image',
229
+ type: 'rectanglelabels',
230
+ value: {
231
+ x: obj.bbox.x,
232
+ y: obj.bbox.y,
233
+ width: obj.bbox.width,
234
+ height: obj.bbox.height,
235
+ rectanglelabels: [obj.className]
236
+ }
237
+ }))
238
+ }
239
+ }