@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,342 @@
1
+ /**
2
+ * Task Transformer
3
+ *
4
+ * Flexible data transformation from any source format to Label Studio tasks.
5
+ * Supports nested data, predictions, and custom field mapping.
6
+ */
7
+
8
+ export interface TaskTransformRule {
9
+ /**
10
+ * Field mapping: Label Studio field name -> source data path
11
+ * Example: { "image": "image_url", "date": "metadata.timestamp" }
12
+ */
13
+ dataFields: { [lsField: string]: string }
14
+
15
+ /**
16
+ * Prediction configuration (optional)
17
+ */
18
+ predictions?: PredictionConfig
19
+
20
+ /**
21
+ * Metadata to attach to task (optional)
22
+ */
23
+ meta?: { [key: string]: string }
24
+ }
25
+
26
+ export interface PredictionConfig {
27
+ /**
28
+ * Enable predictions
29
+ */
30
+ enabled: boolean
31
+
32
+ /**
33
+ * Path to prediction result in source data
34
+ */
35
+ resultPath: string
36
+
37
+ /**
38
+ * Path to confidence score (optional)
39
+ */
40
+ scorePath?: string
41
+
42
+ /**
43
+ * Model version identifier (optional)
44
+ */
45
+ modelVersion?: string
46
+
47
+ /**
48
+ * Transform function for prediction result
49
+ */
50
+ resultTransform?: (result: any) => any
51
+ }
52
+
53
+ export interface LabelStudioTask {
54
+ data: string // JSON string
55
+ predictions?: Array<{
56
+ result: any
57
+ score?: number
58
+ model_version?: string
59
+ }>
60
+ meta?: any
61
+ }
62
+
63
+ export class TaskTransformer {
64
+ /**
65
+ * Transform source data to Label Studio tasks
66
+ *
67
+ * @param sourceData - Array of source data objects
68
+ * @param rule - Transformation rules
69
+ * @returns Array of Label Studio tasks
70
+ */
71
+ static transform(sourceData: any[], rule: TaskTransformRule): LabelStudioTask[] {
72
+ return sourceData.map(data => this.transformOne(data, rule)).filter(task => task !== null) as LabelStudioTask[]
73
+ }
74
+
75
+ /**
76
+ * Transform a single data object
77
+ */
78
+ static transformOne(sourceData: any, rule: TaskTransformRule): LabelStudioTask | null {
79
+ try {
80
+ // Build task data
81
+ const taskData: any = {}
82
+ for (const [lsField, sourcePath] of Object.entries(rule.dataFields)) {
83
+ const value = this.getNestedValue(sourceData, sourcePath)
84
+ if (value !== undefined) {
85
+ taskData[lsField] = value
86
+ }
87
+ }
88
+
89
+ // Skip if no data extracted
90
+ if (Object.keys(taskData).length === 0) {
91
+ return null
92
+ }
93
+
94
+ const task: LabelStudioTask = {
95
+ data: JSON.stringify(taskData)
96
+ }
97
+
98
+ // Add predictions if configured
99
+ if (rule.predictions?.enabled) {
100
+ const prediction = this.buildPrediction(sourceData, rule.predictions)
101
+ if (prediction) {
102
+ task.predictions = [prediction]
103
+ }
104
+ }
105
+
106
+ // Add metadata if configured
107
+ if (rule.meta) {
108
+ const meta: any = {}
109
+ for (const [metaKey, sourcePath] of Object.entries(rule.meta)) {
110
+ const value = this.getNestedValue(sourceData, sourcePath)
111
+ if (value !== undefined) {
112
+ meta[metaKey] = value
113
+ }
114
+ }
115
+ if (Object.keys(meta).length > 0) {
116
+ task.meta = meta
117
+ }
118
+ }
119
+
120
+ return task
121
+ } catch (error) {
122
+ console.error('Failed to transform task:', error)
123
+ return null
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Build prediction object from source data
129
+ */
130
+ private static buildPrediction(
131
+ sourceData: any,
132
+ config: PredictionConfig
133
+ ): { result: any; score?: number; model_version?: string } | null {
134
+ const result = this.getNestedValue(sourceData, config.resultPath)
135
+ if (!result) {
136
+ return null
137
+ }
138
+
139
+ const prediction: any = {
140
+ result: config.resultTransform ? config.resultTransform(result) : result
141
+ }
142
+
143
+ if (config.scorePath) {
144
+ const score = this.getNestedValue(sourceData, config.scorePath)
145
+ if (score !== undefined) {
146
+ prediction.score = score
147
+ }
148
+ }
149
+
150
+ if (config.modelVersion) {
151
+ prediction.model_version = config.modelVersion
152
+ }
153
+
154
+ return prediction
155
+ }
156
+
157
+ /**
158
+ * Get nested value from object using dot notation path
159
+ * Example: "metadata.user.name" -> obj.metadata.user.name
160
+ */
161
+ private static getNestedValue(obj: any, path: string): any {
162
+ if (!path || !obj) return undefined
163
+
164
+ const keys = path.split('.')
165
+ let current = obj
166
+
167
+ for (const key of keys) {
168
+ if (current === null || current === undefined) {
169
+ return undefined
170
+ }
171
+
172
+ // Support array indexing: "items[0].name"
173
+ const arrayMatch = key.match(/^(\w+)\[(\d+)\]$/)
174
+ if (arrayMatch) {
175
+ const [, arrayKey, index] = arrayMatch
176
+ current = current[arrayKey]?.[parseInt(index, 10)]
177
+ } else {
178
+ current = current[key]
179
+ }
180
+ }
181
+
182
+ return current
183
+ }
184
+
185
+ /**
186
+ * Transform with multiple rules (useful for heterogeneous data)
187
+ */
188
+ static transformMultiRule(sourceData: any[], rules: TaskTransformRule[]): LabelStudioTask[] {
189
+ const tasks: LabelStudioTask[] = []
190
+
191
+ for (const rule of rules) {
192
+ tasks.push(...this.transform(sourceData, rule))
193
+ }
194
+
195
+ return tasks
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Pre-built transformation templates for common scenarios
201
+ */
202
+ export class TaskTransformTemplates {
203
+ /**
204
+ * WBM Image Classification with AI predictions
205
+ */
206
+ static wbmImageClassification(includeAiPrediction: boolean = true): TaskTransformRule {
207
+ const rule: TaskTransformRule = {
208
+ dataFields: {
209
+ image: 'image_url',
210
+ date: 'timestamp',
211
+ device_id: 'device_id'
212
+ },
213
+ meta: {
214
+ wafer_id: 'wafer_id',
215
+ lot_id: 'lot_id'
216
+ }
217
+ }
218
+
219
+ if (includeAiPrediction) {
220
+ rule.predictions = {
221
+ enabled: true,
222
+ resultPath: 'ai_prediction',
223
+ scorePath: 'confidence',
224
+ modelVersion: 'wbm-classifier-v1',
225
+ resultTransform: result => {
226
+ // Transform AI result to Label Studio format
227
+ return [
228
+ {
229
+ from_name: 'rank1',
230
+ to_name: 'data',
231
+ type: 'choices',
232
+ value: {
233
+ choices: [result.rank1]
234
+ }
235
+ },
236
+ {
237
+ from_name: 'rank2',
238
+ to_name: 'data',
239
+ type: 'choices',
240
+ value: {
241
+ choices: [result.rank2]
242
+ }
243
+ },
244
+ {
245
+ from_name: 'rank3',
246
+ to_name: 'data',
247
+ type: 'choices',
248
+ value: {
249
+ choices: [result.rank3]
250
+ }
251
+ }
252
+ ]
253
+ }
254
+ }
255
+ }
256
+
257
+ return rule
258
+ }
259
+
260
+ /**
261
+ * Simple image classification
262
+ */
263
+ static imageClassification(imageField: string = 'image_url'): TaskTransformRule {
264
+ return {
265
+ dataFields: {
266
+ image: imageField
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Text classification with metadata
273
+ */
274
+ static textClassification(textField: string = 'text', metaFields: string[] = []): TaskTransformRule {
275
+ const rule: TaskTransformRule = {
276
+ dataFields: {
277
+ text: textField
278
+ }
279
+ }
280
+
281
+ if (metaFields.length > 0) {
282
+ rule.meta = {}
283
+ for (const field of metaFields) {
284
+ rule.meta[field] = field
285
+ }
286
+ }
287
+
288
+ return rule
289
+ }
290
+
291
+ /**
292
+ * Time series data
293
+ */
294
+ static timeSeries(valuesField: string, timestampField?: string): TaskTransformRule {
295
+ const dataFields: any = {
296
+ timeseries: valuesField
297
+ }
298
+
299
+ if (timestampField) {
300
+ dataFields.timestamp = timestampField
301
+ }
302
+
303
+ return {
304
+ dataFields
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Object detection with bounding box predictions
310
+ */
311
+ static objectDetection(imageField: string = 'image_url', predictionsField?: string): TaskTransformRule {
312
+ const rule: TaskTransformRule = {
313
+ dataFields: {
314
+ image: imageField
315
+ }
316
+ }
317
+
318
+ if (predictionsField) {
319
+ rule.predictions = {
320
+ enabled: true,
321
+ resultPath: predictionsField,
322
+ resultTransform: bboxes => {
323
+ // Transform bounding boxes to Label Studio format
324
+ return bboxes.map((bbox: any) => ({
325
+ from_name: 'bbox',
326
+ to_name: 'data',
327
+ type: 'rectanglelabels',
328
+ value: {
329
+ x: bbox.x,
330
+ y: bbox.y,
331
+ width: bbox.width,
332
+ height: bbox.height,
333
+ rectanglelabels: [bbox.label]
334
+ }
335
+ }))
336
+ }
337
+ }
338
+ }
339
+
340
+ return rule
341
+ }
342
+ }
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Test script for AI Prediction integration with Label Studio
3
+ *
4
+ * This script tests the complete flow:
5
+ * 1. Get a task from Label Studio
6
+ * 2. Run AI inference on the task's image
7
+ * 3. Convert AI results to Label Studio format
8
+ * 4. Create prediction in Label Studio
9
+ */
10
+
11
+ const axios = require('axios')
12
+
13
+ // Configuration
14
+ // NOTE: With subdomain cookie-sharing approach, we access Label Studio directly
15
+ // No proxy path needed - Label Studio runs on its own subdomain
16
+ const LABEL_STUDIO_URL = process.env.LABEL_STUDIO_URL || 'http://label.dataset.localhost:8080'
17
+ const LABEL_STUDIO_TOKEN = process.env.LABEL_STUDIO_API_TOKEN || ''
18
+ const THINGS_FACTORY_GRAPHQL = process.env.THINGS_FACTORY_GRAPHQL || 'http://dataset.localhost:3000/graphql'
19
+
20
+ // Test configuration
21
+ const PROJECT_ID = process.env.TEST_PROJECT_ID || 2 // Change to your project ID
22
+ const TASK_ID = process.env.TEST_TASK_ID // Optional: specific task ID
23
+
24
+ async function testLabelStudioConnection() {
25
+ console.log('\nšŸ” Testing Label Studio connection...')
26
+ console.log(` URL: ${LABEL_STUDIO_URL}`)
27
+
28
+ try {
29
+ const response = await axios.get(`${LABEL_STUDIO_URL}/api/projects`, {
30
+ headers: {
31
+ 'Authorization': `Token ${LABEL_STUDIO_TOKEN}`
32
+ }
33
+ })
34
+
35
+ console.log(`āœ… Label Studio connected successfully`)
36
+ console.log(` Found ${response.data.results?.length || 0} projects`)
37
+ return true
38
+ } catch (error) {
39
+ console.error(`āŒ Label Studio connection failed:`, error.message)
40
+ return false
41
+ }
42
+ }
43
+
44
+ async function getTestTask() {
45
+ console.log(`\nšŸ” Getting test task from project ${PROJECT_ID}...`)
46
+
47
+ try {
48
+ // Get tasks from project
49
+ const response = await axios.get(
50
+ `${LABEL_STUDIO_URL}/api/projects/${PROJECT_ID}/tasks`,
51
+ {
52
+ headers: { 'Authorization': `Token ${LABEL_STUDIO_TOKEN}` },
53
+ params: { page_size: 10 }
54
+ }
55
+ )
56
+
57
+ const tasks = response.data.results || response.data
58
+
59
+ if (!tasks || tasks.length === 0) {
60
+ console.log(`āš ļø No tasks found in project ${PROJECT_ID}`)
61
+ return null
62
+ }
63
+
64
+ // Find a task with an image
65
+ const taskWithImage = tasks.find(task => task.data && task.data.image)
66
+
67
+ if (!taskWithImage) {
68
+ console.log(`āš ļø No tasks with images found`)
69
+ return null
70
+ }
71
+
72
+ console.log(`āœ… Found task ${taskWithImage.id}`)
73
+ console.log(` Image: ${taskWithImage.data.image}`)
74
+ console.log(` Annotations: ${taskWithImage.annotations?.length || 0}`)
75
+ console.log(` Predictions: ${taskWithImage.predictions?.length || 0}`)
76
+
77
+ return taskWithImage
78
+ } catch (error) {
79
+ console.error(`āŒ Failed to get task:`, error.message)
80
+ return null
81
+ }
82
+ }
83
+
84
+ async function testAIInference(imageUrl) {
85
+ console.log(`\nšŸ¤– Testing AI inference...`)
86
+ console.log(` Image: ${imageUrl}`)
87
+
88
+ try {
89
+ const query = `
90
+ query DetectObjects($input: InferenceRequest!) {
91
+ detectObjects(input: $input) {
92
+ className
93
+ confidence
94
+ bbox {
95
+ x
96
+ y
97
+ width
98
+ height
99
+ }
100
+ }
101
+ }
102
+ `
103
+
104
+ const response = await axios.post(THINGS_FACTORY_GRAPHQL, {
105
+ query,
106
+ variables: {
107
+ input: {
108
+ imageUrl,
109
+ confidenceThreshold: 0.5
110
+ }
111
+ }
112
+ })
113
+
114
+ if (response.data.errors) {
115
+ console.error(`āŒ GraphQL errors:`, response.data.errors)
116
+ return null
117
+ }
118
+
119
+ const objects = response.data.data.detectObjects
120
+ console.log(`āœ… AI detected ${objects.length} objects:`)
121
+ objects.forEach((obj, i) => {
122
+ console.log(` ${i + 1}. ${obj.className} (${(obj.confidence * 100).toFixed(1)}%)`)
123
+ })
124
+
125
+ return objects
126
+ } catch (error) {
127
+ console.error(`āŒ AI inference failed:`, error.message)
128
+ if (error.response?.data) {
129
+ console.error(` Response:`, JSON.stringify(error.response.data, null, 2))
130
+ }
131
+ return null
132
+ }
133
+ }
134
+
135
+ async function testPredictionGeneration(taskId, imageUrl) {
136
+ console.log(`\nšŸŽÆ Testing prediction generation for task ${taskId}...`)
137
+
138
+ try {
139
+ const mutation = `
140
+ mutation GeneratePrediction($input: GeneratePredictionRequest!) {
141
+ generatePrediction(input: $input) {
142
+ taskId
143
+ predictionId
144
+ success
145
+ objectCount
146
+ avgConfidence
147
+ error
148
+ }
149
+ }
150
+ `
151
+
152
+ const response = await axios.post(THINGS_FACTORY_GRAPHQL, {
153
+ query: mutation,
154
+ variables: {
155
+ input: {
156
+ taskId,
157
+ imageUrl,
158
+ confidenceThreshold: 0.5
159
+ }
160
+ }
161
+ })
162
+
163
+ if (response.data.errors) {
164
+ console.error(`āŒ GraphQL errors:`, response.data.errors)
165
+ return null
166
+ }
167
+
168
+ const result = response.data.data.generatePrediction
169
+
170
+ if (result.success) {
171
+ console.log(`āœ… Prediction created successfully!`)
172
+ console.log(` Prediction ID: ${result.predictionId}`)
173
+ console.log(` Objects detected: ${result.objectCount}`)
174
+ console.log(` Avg confidence: ${(result.avgConfidence * 100).toFixed(1)}%`)
175
+ } else {
176
+ console.log(`āš ļø Prediction generation reported error:`, result.error)
177
+ }
178
+
179
+ return result
180
+ } catch (error) {
181
+ console.error(`āŒ Prediction generation failed:`, error.message)
182
+ if (error.response?.data) {
183
+ console.error(` Response:`, JSON.stringify(error.response.data, null, 2))
184
+ }
185
+ return null
186
+ }
187
+ }
188
+
189
+ async function verifyPrediction(taskId) {
190
+ console.log(`\nāœ“ Verifying prediction in Label Studio...`)
191
+
192
+ try {
193
+ const response = await axios.get(
194
+ `${LABEL_STUDIO_URL}/api/tasks/${taskId}`,
195
+ {
196
+ headers: { 'Authorization': `Token ${LABEL_STUDIO_TOKEN}` }
197
+ }
198
+ )
199
+
200
+ const task = response.data
201
+ const predictionCount = task.predictions?.length || 0
202
+
203
+ console.log(`āœ… Task has ${predictionCount} prediction(s)`)
204
+
205
+ if (predictionCount > 0) {
206
+ const latestPrediction = task.predictions[task.predictions.length - 1]
207
+ console.log(` Latest prediction:`)
208
+ console.log(` - ID: ${latestPrediction.id}`)
209
+ console.log(` - Model: ${latestPrediction.model_version}`)
210
+ console.log(` - Score: ${(latestPrediction.score * 100).toFixed(1)}%`)
211
+ console.log(` - Objects: ${latestPrediction.result?.length || 0}`)
212
+ }
213
+
214
+ return true
215
+ } catch (error) {
216
+ console.error(`āŒ Failed to verify prediction:`, error.message)
217
+ return false
218
+ }
219
+ }
220
+
221
+ async function main() {
222
+ console.log('šŸš€ Starting AI Prediction Integration Test\n')
223
+ console.log('=' .repeat(60))
224
+
225
+ // Step 1: Test Label Studio connection
226
+ const lsConnected = await testLabelStudioConnection()
227
+ if (!lsConnected) {
228
+ console.log('\nāŒ Cannot proceed without Label Studio connection')
229
+ process.exit(1)
230
+ }
231
+
232
+ // Step 2: Get a test task
233
+ const task = await getTestTask()
234
+ if (!task) {
235
+ console.log('\nāŒ Cannot proceed without a test task')
236
+ console.log('šŸ’” Please create a task with an image in Label Studio project', PROJECT_ID)
237
+ process.exit(1)
238
+ }
239
+
240
+ // Step 3: Test AI inference (optional - direct test)
241
+ const objects = await testAIInference(task.data.image)
242
+ if (!objects) {
243
+ console.log('\nāš ļø AI inference test failed, but continuing with prediction generation...')
244
+ }
245
+
246
+ // Step 4: Generate prediction via GraphQL
247
+ const result = await testPredictionGeneration(task.id, task.data.image)
248
+ if (!result || !result.success) {
249
+ console.log('\nāŒ Prediction generation failed')
250
+ process.exit(1)
251
+ }
252
+
253
+ // Step 5: Verify prediction was created
254
+ await verifyPrediction(task.id)
255
+
256
+ console.log('\n' + '='.repeat(60))
257
+ console.log('✨ Test completed successfully!')
258
+ console.log(`\nšŸ“ Next steps:`)
259
+ console.log(` 1. Open Label Studio: ${LABEL_STUDIO_URL}/projects/${PROJECT_ID}`)
260
+ console.log(` 2. Open task ${task.id} to see the AI-generated prediction`)
261
+ console.log(` 3. The prediction should appear as pre-labeled regions`)
262
+ }
263
+
264
+ // Run the test
265
+ main().catch(error => {
266
+ console.error('\nšŸ’„ Test failed with error:', error)
267
+ process.exit(1)
268
+ })