@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,54 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import './label-studio-wrapper.js';
3
+ import { css, html } from 'lit';
4
+ import { customElement, property } from 'lit/decorators.js';
5
+ import { PageView } from '@things-factory/shell/client';
6
+ let LabelStudioLabelPage = class LabelStudioLabelPage extends PageView {
7
+ constructor() {
8
+ super(...arguments);
9
+ this.projectId = '';
10
+ }
11
+ static { this.styles = css `
12
+ :host {
13
+ display: flex;
14
+ flex-direction: column;
15
+ height: 100%;
16
+ overflow: hidden;
17
+ }
18
+
19
+ .label-studio-wrapper {
20
+ flex: 1;
21
+ display: flex;
22
+ flex-direction: column;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .error {
27
+ padding: 20px;
28
+ color: var(--md-sys-color-error);
29
+ text-align: center;
30
+ }
31
+ `; }
32
+ async pageUpdated(changes, lifecycle, changedBefore) {
33
+ if (this.active && lifecycle.resourceId) {
34
+ this.projectId = lifecycle.resourceId;
35
+ }
36
+ }
37
+ render() {
38
+ const projectId = this.projectId;
39
+ if (!projectId) {
40
+ return html `<div class="error">Error: No project ID provided</div>`;
41
+ }
42
+ const path = `/projects/${projectId}`;
43
+ return html ` <label-studio-wrapper .path=${path}></label-studio-wrapper> `;
44
+ }
45
+ };
46
+ __decorate([
47
+ property({ type: String }),
48
+ __metadata("design:type", String)
49
+ ], LabelStudioLabelPage.prototype, "projectId", void 0);
50
+ LabelStudioLabelPage = __decorate([
51
+ customElement('label-studio-label-page')
52
+ ], LabelStudioLabelPage);
53
+ export { LabelStudioLabelPage };
54
+ //# sourceMappingURL=label-studio-label-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-studio-label-page.js","sourceRoot":"","sources":["../client/label-studio-label-page.ts"],"names":[],"mappings":";AAAA,OAAO,2BAA2B,CAAA;AAElC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAc,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,aAAa,EAAS,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAGlE,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAA;AAGhD,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,QAAQ;IAA3C;;QAuBuB,cAAS,GAAW,EAAE,CAAA;IAmBpD,CAAC;aAzCQ,WAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;GAoBlB,AApBY,CAoBZ;IAID,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,aAAa;QACjD,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;YACxC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,UAAU,CAAA;QACvC,CAAC;IACH,CAAC;IAED,MAAM;QACJ,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAA;QAEhC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,IAAI,CAAA,wDAAwD,CAAA;QACrE,CAAC;QAED,MAAM,IAAI,GAAG,aAAa,SAAS,EAAE,CAAA;QAErC,OAAO,IAAI,CAAA,gCAAgC,IAAI,2BAA2B,CAAA;IAC5E,CAAC;;AAlB2B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uDAAuB;AAvBvC,oBAAoB;IADhC,aAAa,CAAC,yBAAyB,CAAC;GAC5B,oBAAoB,CA0ChC","sourcesContent":["import './label-studio-wrapper.js'\n\nimport { css, html, LitElement } from 'lit'\nimport { customElement, state, property } from 'lit/decorators.js'\nimport { gql } from 'graphql-tag'\nimport { client } from '@operato/graphql'\nimport { PageView } from '@things-factory/shell/client'\n\n@customElement('label-studio-label-page')\nexport class LabelStudioLabelPage extends PageView {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n\n .label-studio-wrapper {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .error {\n padding: 20px;\n color: var(--md-sys-color-error);\n text-align: center;\n }\n `\n\n @property({ type: String }) projectId: string = ''\n\n async pageUpdated(changes, lifecycle, changedBefore) {\n if (this.active && lifecycle.resourceId) {\n this.projectId = lifecycle.resourceId\n }\n }\n\n render() {\n const projectId = this.projectId\n\n if (!projectId) {\n return html`<div class=\"error\">Error: No project ID provided</div>`\n }\n\n const path = `/projects/${projectId}`\n\n return html` <label-studio-wrapper .path=${path}></label-studio-wrapper> `\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import '@material/web/button/filled-button.js';
2
+ import '@material/web/button/text-button.js';
3
+ import '@material/web/textfield/filled-text-field.js';
4
+ import { PageView } from '@things-factory/shell/client';
5
+ export declare class LabelStudioProjectCreate extends PageView {
6
+ static styles: import("lit").CSSResult[];
7
+ title: string;
8
+ description: string;
9
+ labelConfig: string;
10
+ saving: boolean;
11
+ error: string;
12
+ render(): import("lit-html").TemplateResult<1>;
13
+ useTemplate(type: string): void;
14
+ save(): Promise<void>;
15
+ cancel(): void;
16
+ }
@@ -0,0 +1,235 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/button/filled-button.js';
3
+ import '@material/web/button/text-button.js';
4
+ import '@material/web/textfield/filled-text-field.js';
5
+ import { css, html } from 'lit';
6
+ import { customElement, state } from 'lit/decorators.js';
7
+ import { client, PageView, navigate } from '@things-factory/shell/client';
8
+ import gql from 'graphql-tag';
9
+ let LabelStudioProjectCreate = class LabelStudioProjectCreate extends PageView {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.title = '';
13
+ this.description = '';
14
+ this.labelConfig = '';
15
+ this.saving = false;
16
+ this.error = '';
17
+ }
18
+ static { this.styles = [
19
+ css `
20
+ :host {
21
+ display: flex;
22
+ flex-direction: column;
23
+ padding: 20px;
24
+ max-width: 800px;
25
+ margin: 0 auto;
26
+ }
27
+
28
+ h2 {
29
+ margin: 0 0 20px 0;
30
+ color: var(--md-sys-color-on-surface);
31
+ }
32
+
33
+ .form-field {
34
+ margin-bottom: 20px;
35
+ }
36
+
37
+ .form-field label {
38
+ display: block;
39
+ margin-bottom: 8px;
40
+ color: var(--md-sys-color-on-surface);
41
+ font-weight: 500;
42
+ }
43
+
44
+ md-filled-text-field {
45
+ width: 100%;
46
+ }
47
+
48
+ textarea {
49
+ width: 100%;
50
+ min-height: 200px;
51
+ padding: 12px;
52
+ border: 1px solid var(--md-sys-color-outline);
53
+ border-radius: 4px;
54
+ font-family: monospace;
55
+ font-size: 14px;
56
+ resize: vertical;
57
+ }
58
+
59
+ .actions {
60
+ display: flex;
61
+ gap: 10px;
62
+ justify-content: flex-end;
63
+ margin-top: 20px;
64
+ }
65
+
66
+ .template-examples {
67
+ margin-top: 20px;
68
+ padding: 15px;
69
+ background: var(--md-sys-color-surface-variant);
70
+ border-radius: 8px;
71
+ }
72
+
73
+ .template-examples h4 {
74
+ margin: 0 0 10px 0;
75
+ }
76
+
77
+ .template-btn {
78
+ margin: 5px;
79
+ }
80
+
81
+ .error {
82
+ color: var(--md-sys-color-error);
83
+ margin: 10px 0;
84
+ }
85
+ `
86
+ ]; }
87
+ render() {
88
+ return html `
89
+ <h2>Create Label Studio Project</h2>
90
+
91
+ <div class="form-field">
92
+ <label>Project Title *</label>
93
+ <md-filled-text-field
94
+ .value=${this.title}
95
+ @input=${(e) => (this.title = e.target.value)}
96
+ placeholder="Enter project title"
97
+ ></md-filled-text-field>
98
+ </div>
99
+
100
+ <div class="form-field">
101
+ <label>Description</label>
102
+ <md-filled-text-field
103
+ .value=${this.description}
104
+ @input=${(e) => (this.description = e.target.value)}
105
+ placeholder="Enter project description"
106
+ ></md-filled-text-field>
107
+ </div>
108
+
109
+ <div class="form-field">
110
+ <label>Label Configuration (XML) *</label>
111
+ <textarea
112
+ .value=${this.labelConfig}
113
+ @input=${(e) => (this.labelConfig = e.target.value)}
114
+ placeholder='<View>&#10; <Text name="text" value="$text"/>&#10; <Choices name="label" toName="text">&#10; <Choice value="Positive"/>&#10; <Choice value="Negative"/>&#10; </Choices>&#10;</View>'
115
+ ></textarea>
116
+ </div>
117
+
118
+ <div class="template-examples">
119
+ <h4>Quick Templates</h4>
120
+ <md-filled-button class="template-btn" @click=${() => this.useTemplate('text-classification')}>
121
+ Text Classification
122
+ </md-filled-button>
123
+ <md-filled-button class="template-btn" @click=${() => this.useTemplate('image-classification')}>
124
+ Image Classification
125
+ </md-filled-button>
126
+ <md-filled-button class="template-btn" @click=${() => this.useTemplate('ner')}>
127
+ Named Entity Recognition
128
+ </md-filled-button>
129
+ </div>
130
+
131
+ ${this.error ? html `<div class="error">${this.error}</div>` : ''}
132
+
133
+ <div class="actions">
134
+ <md-text-button @click=${this.cancel}>Cancel</md-text-button>
135
+ <md-filled-button @click=${this.save} ?disabled=${this.saving}>
136
+ ${this.saving ? 'Creating...' : 'Create Project'}
137
+ </md-filled-button>
138
+ </div>
139
+ `;
140
+ }
141
+ useTemplate(type) {
142
+ const templates = {
143
+ 'text-classification': `<View>
144
+ <Text name="text" value="$text"/>
145
+ <Choices name="sentiment" toName="text" choice="single">
146
+ <Choice value="Positive"/>
147
+ <Choice value="Negative"/>
148
+ <Choice value="Neutral"/>
149
+ </Choices>
150
+ </View>`,
151
+ 'image-classification': `<View>
152
+ <Image name="image" value="$image"/>
153
+ <Choices name="choice" toName="image" choice="single">
154
+ <Choice value="Cat"/>
155
+ <Choice value="Dog"/>
156
+ <Choice value="Bird"/>
157
+ </Choices>
158
+ </View>`,
159
+ ner: `<View>
160
+ <Text name="text" value="$text"/>
161
+ <Labels name="label" toName="text">
162
+ <Label value="Person" background="red"/>
163
+ <Label value="Organization" background="darkorange"/>
164
+ <Label value="Location" background="blue"/>
165
+ </Labels>
166
+ </View>`
167
+ };
168
+ this.labelConfig = templates[type] || '';
169
+ }
170
+ async save() {
171
+ if (!this.title || !this.labelConfig) {
172
+ this.error = 'Title and Label Configuration are required';
173
+ return;
174
+ }
175
+ try {
176
+ this.saving = true;
177
+ this.error = '';
178
+ const response = await client.mutate({
179
+ mutation: gql `
180
+ mutation CreateProject($input: CreateProjectInput!) {
181
+ createLabelStudioProject(input: $input) {
182
+ id
183
+ title
184
+ }
185
+ }
186
+ `,
187
+ variables: {
188
+ input: {
189
+ title: this.title,
190
+ description: this.description || undefined,
191
+ labelConfig: this.labelConfig
192
+ }
193
+ }
194
+ });
195
+ const project = response.data.createLabelStudioProject;
196
+ // Navigate to project list
197
+ navigate('label-studio-project-list');
198
+ }
199
+ catch (error) {
200
+ console.error('Failed to create project:', error);
201
+ this.error = error.message || 'Failed to create project';
202
+ }
203
+ finally {
204
+ this.saving = false;
205
+ }
206
+ }
207
+ cancel() {
208
+ navigate('label-studio-project-list');
209
+ }
210
+ };
211
+ __decorate([
212
+ state(),
213
+ __metadata("design:type", String)
214
+ ], LabelStudioProjectCreate.prototype, "title", void 0);
215
+ __decorate([
216
+ state(),
217
+ __metadata("design:type", String)
218
+ ], LabelStudioProjectCreate.prototype, "description", void 0);
219
+ __decorate([
220
+ state(),
221
+ __metadata("design:type", String)
222
+ ], LabelStudioProjectCreate.prototype, "labelConfig", void 0);
223
+ __decorate([
224
+ state(),
225
+ __metadata("design:type", Boolean)
226
+ ], LabelStudioProjectCreate.prototype, "saving", void 0);
227
+ __decorate([
228
+ state(),
229
+ __metadata("design:type", String)
230
+ ], LabelStudioProjectCreate.prototype, "error", void 0);
231
+ LabelStudioProjectCreate = __decorate([
232
+ customElement('label-studio-project-create')
233
+ ], LabelStudioProjectCreate);
234
+ export { LabelStudioProjectCreate };
235
+ //# sourceMappingURL=label-studio-project-create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-studio-project-create.js","sourceRoot":"","sources":["../client/label-studio-project-create.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\nimport '@material/web/textfield/filled-text-field.js'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { client, PageView, navigate } from '@things-factory/shell/client'\nimport gql from 'graphql-tag'\n\n@customElement('label-studio-project-create')\nexport class LabelStudioProjectCreate extends PageView {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n padding: 20px;\n max-width: 800px;\n margin: 0 auto;\n }\n\n h2 {\n margin: 0 0 20px 0;\n color: var(--md-sys-color-on-surface);\n }\n\n .form-field {\n margin-bottom: 20px;\n }\n\n .form-field label {\n display: block;\n margin-bottom: 8px;\n color: var(--md-sys-color-on-surface);\n font-weight: 500;\n }\n\n md-filled-text-field {\n width: 100%;\n }\n\n textarea {\n width: 100%;\n min-height: 200px;\n padding: 12px;\n border: 1px solid var(--md-sys-color-outline);\n border-radius: 4px;\n font-family: monospace;\n font-size: 14px;\n resize: vertical;\n }\n\n .actions {\n display: flex;\n gap: 10px;\n justify-content: flex-end;\n margin-top: 20px;\n }\n\n .template-examples {\n margin-top: 20px;\n padding: 15px;\n background: var(--md-sys-color-surface-variant);\n border-radius: 8px;\n }\n\n .template-examples h4 {\n margin: 0 0 10px 0;\n }\n\n .template-btn {\n margin: 5px;\n }\n\n .error {\n color: var(--md-sys-color-error);\n margin: 10px 0;\n }\n `\n ]\n\n @state() title: string = ''\n @state() description: string = ''\n @state() labelConfig: string = ''\n @state() saving: boolean = false\n @state() error: string = ''\n\n render() {\n return html`\n <h2>Create Label Studio Project</h2>\n\n <div class=\"form-field\">\n <label>Project Title *</label>\n <md-filled-text-field\n .value=${this.title}\n @input=${(e: any) => (this.title = e.target.value)}\n placeholder=\"Enter project title\"\n ></md-filled-text-field>\n </div>\n\n <div class=\"form-field\">\n <label>Description</label>\n <md-filled-text-field\n .value=${this.description}\n @input=${(e: any) => (this.description = e.target.value)}\n placeholder=\"Enter project description\"\n ></md-filled-text-field>\n </div>\n\n <div class=\"form-field\">\n <label>Label Configuration (XML) *</label>\n <textarea\n .value=${this.labelConfig}\n @input=${(e: any) => (this.labelConfig = (e.target as HTMLTextAreaElement).value)}\n placeholder='<View>&#10; <Text name=\"text\" value=\"$text\"/>&#10; <Choices name=\"label\" toName=\"text\">&#10; <Choice value=\"Positive\"/>&#10; <Choice value=\"Negative\"/>&#10; </Choices>&#10;</View>'\n ></textarea>\n </div>\n\n <div class=\"template-examples\">\n <h4>Quick Templates</h4>\n <md-filled-button class=\"template-btn\" @click=${() => this.useTemplate('text-classification')}>\n Text Classification\n </md-filled-button>\n <md-filled-button class=\"template-btn\" @click=${() => this.useTemplate('image-classification')}>\n Image Classification\n </md-filled-button>\n <md-filled-button class=\"template-btn\" @click=${() => this.useTemplate('ner')}>\n Named Entity Recognition\n </md-filled-button>\n </div>\n\n ${this.error ? html`<div class=\"error\">${this.error}</div>` : ''}\n\n <div class=\"actions\">\n <md-text-button @click=${this.cancel}>Cancel</md-text-button>\n <md-filled-button @click=${this.save} ?disabled=${this.saving}>\n ${this.saving ? 'Creating...' : 'Create Project'}\n </md-filled-button>\n </div>\n `\n }\n\n useTemplate(type: string) {\n const templates = {\n 'text-classification': `<View>\n <Text name=\"text\" value=\"$text\"/>\n <Choices name=\"sentiment\" toName=\"text\" choice=\"single\">\n <Choice value=\"Positive\"/>\n <Choice value=\"Negative\"/>\n <Choice value=\"Neutral\"/>\n </Choices>\n</View>`,\n 'image-classification': `<View>\n <Image name=\"image\" value=\"$image\"/>\n <Choices name=\"choice\" toName=\"image\" choice=\"single\">\n <Choice value=\"Cat\"/>\n <Choice value=\"Dog\"/>\n <Choice value=\"Bird\"/>\n </Choices>\n</View>`,\n ner: `<View>\n <Text name=\"text\" value=\"$text\"/>\n <Labels name=\"label\" toName=\"text\">\n <Label value=\"Person\" background=\"red\"/>\n <Label value=\"Organization\" background=\"darkorange\"/>\n <Label value=\"Location\" background=\"blue\"/>\n </Labels>\n</View>`\n }\n\n this.labelConfig = templates[type] || ''\n }\n\n async save() {\n if (!this.title || !this.labelConfig) {\n this.error = 'Title and Label Configuration are required'\n return\n }\n\n try {\n this.saving = true\n this.error = ''\n\n const response = await client.mutate({\n mutation: gql`\n mutation CreateProject($input: CreateProjectInput!) {\n createLabelStudioProject(input: $input) {\n id\n title\n }\n }\n `,\n variables: {\n input: {\n title: this.title,\n description: this.description || undefined,\n labelConfig: this.labelConfig\n }\n }\n })\n\n const project = response.data.createLabelStudioProject\n\n // Navigate to project list\n navigate('label-studio-project-list')\n } catch (error: any) {\n console.error('Failed to create project:', error)\n this.error = error.message || 'Failed to create project'\n } finally {\n this.saving = false\n }\n }\n\n cancel() {\n navigate('label-studio-project-list')\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import '@material/web/button/filled-button.js';
2
+ import '@material/web/button/outlined-button.js';
3
+ import '@material/web/icon/icon.js';
4
+ import '@material/web/iconbutton/icon-button.js';
5
+ import { PageView } from '@things-factory/shell/client';
6
+ export declare class LabelStudioProjectList extends PageView {
7
+ static styles: import("lit").CSSResult[];
8
+ projects: any[];
9
+ loading: boolean;
10
+ error: string;
11
+ pageUpdated(changes: any, lifecycle: any, changedBefore: any): Promise<void>;
12
+ loadProjects(): Promise<void>;
13
+ createProject(): void;
14
+ openProject(projectId: number): void;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ }
@@ -0,0 +1,222 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@material/web/button/filled-button.js';
3
+ import '@material/web/button/outlined-button.js';
4
+ import '@material/web/icon/icon.js';
5
+ import '@material/web/iconbutton/icon-button.js';
6
+ import { css, html } from 'lit';
7
+ import { customElement, state } from 'lit/decorators.js';
8
+ import { client, PageView, navigate } from '@things-factory/shell/client';
9
+ import gql from 'graphql-tag';
10
+ let LabelStudioProjectList = class LabelStudioProjectList extends PageView {
11
+ constructor() {
12
+ super(...arguments);
13
+ this.projects = [];
14
+ this.loading = true;
15
+ this.error = '';
16
+ }
17
+ static { this.styles = [
18
+ css `
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ height: 100%;
23
+ padding: 20px;
24
+ background-color: var(--md-sys-color-surface);
25
+ }
26
+
27
+ .header {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ margin-bottom: 20px;
32
+ }
33
+
34
+ .header h2 {
35
+ margin: 0;
36
+ color: var(--md-sys-color-on-surface);
37
+ }
38
+
39
+ .project-grid {
40
+ display: grid;
41
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
42
+ gap: 20px;
43
+ overflow-y: auto;
44
+ }
45
+
46
+ .project-card {
47
+ background: var(--md-sys-color-surface-container);
48
+ border-radius: 12px;
49
+ padding: 20px;
50
+ cursor: pointer;
51
+ transition: all 0.3s ease;
52
+ }
53
+
54
+ .project-card:hover {
55
+ background: var(--md-sys-color-surface-container-high);
56
+ transform: translateY(-2px);
57
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
58
+ }
59
+
60
+ .project-card h3 {
61
+ margin: 0 0 10px 0;
62
+ color: var(--md-sys-color-on-surface);
63
+ }
64
+
65
+ .project-card p {
66
+ margin: 5px 0;
67
+ color: var(--md-sys-color-on-surface-variant);
68
+ font-size: 14px;
69
+ }
70
+
71
+ .project-stats {
72
+ display: flex;
73
+ gap: 15px;
74
+ margin-top: 15px;
75
+ }
76
+
77
+ .stat-item {
78
+ display: flex;
79
+ flex-direction: column;
80
+ }
81
+
82
+ .stat-label {
83
+ font-size: 12px;
84
+ color: var(--md-sys-color-on-surface-variant);
85
+ }
86
+
87
+ .stat-value {
88
+ font-size: 20px;
89
+ font-weight: bold;
90
+ color: var(--md-sys-color-primary);
91
+ }
92
+
93
+ .progress-bar {
94
+ width: 100%;
95
+ height: 8px;
96
+ background: var(--md-sys-color-surface-variant);
97
+ border-radius: 4px;
98
+ margin-top: 10px;
99
+ overflow: hidden;
100
+ }
101
+
102
+ .progress-fill {
103
+ height: 100%;
104
+ background: var(--md-sys-color-primary);
105
+ transition: width 0.3s ease;
106
+ }
107
+
108
+ .loading {
109
+ display: flex;
110
+ justify-content: center;
111
+ align-items: center;
112
+ height: 100%;
113
+ font-size: 18px;
114
+ color: var(--md-sys-color-on-surface-variant);
115
+ }
116
+
117
+ .error {
118
+ color: var(--md-sys-color-error);
119
+ padding: 20px;
120
+ text-align: center;
121
+ }
122
+ `
123
+ ]; }
124
+ async pageUpdated(changes, lifecycle, changedBefore) {
125
+ if (this.active) {
126
+ this.loadProjects();
127
+ }
128
+ }
129
+ async loadProjects() {
130
+ try {
131
+ this.loading = true;
132
+ const response = await client.query({
133
+ query: gql `
134
+ query {
135
+ labelStudioProjects {
136
+ id
137
+ title
138
+ description
139
+ taskCount
140
+ completedTaskCount
141
+ completionRate
142
+ createdAt
143
+ updatedAt
144
+ }
145
+ }
146
+ `
147
+ });
148
+ this.projects = response.data.labelStudioProjects;
149
+ this.loading = false;
150
+ }
151
+ catch (error) {
152
+ console.error('Failed to load projects:', error);
153
+ this.error = error.message || 'Failed to load projects';
154
+ this.loading = false;
155
+ }
156
+ }
157
+ createProject() {
158
+ navigate('label-studio-project-create');
159
+ }
160
+ openProject(projectId) {
161
+ // Open Label Studio viewer with project
162
+ navigate(`label-studio-label/${projectId}`);
163
+ }
164
+ render() {
165
+ if (this.loading) {
166
+ return html `<div class="loading">Loading projects...</div>`;
167
+ }
168
+ if (this.error) {
169
+ return html `<div class="error">${this.error}</div>`;
170
+ }
171
+ return html `
172
+ <div class="header">
173
+ <h2>Label Studio Projects</h2>
174
+ <md-filled-button @click=${this.createProject}>
175
+ <md-icon slot="icon">add</md-icon>
176
+ New Project
177
+ </md-filled-button>
178
+ </div>
179
+
180
+ <div class="project-grid">
181
+ ${this.projects.map(project => html `
182
+ <div class="project-card" @click=${() => this.openProject(project.id)}>
183
+ <h3>${project.title}</h3>
184
+ <p>${project.description || 'No description'}</p>
185
+
186
+ <div class="project-stats">
187
+ <div class="stat-item">
188
+ <span class="stat-label">Tasks</span>
189
+ <span class="stat-value">${project.taskCount}</span>
190
+ </div>
191
+ <div class="stat-item">
192
+ <span class="stat-label">Completed</span>
193
+ <span class="stat-value">${project.completedTaskCount}</span>
194
+ </div>
195
+ </div>
196
+
197
+ <div class="progress-bar">
198
+ <div class="progress-fill" style="width: ${(project.completionRate * 100).toFixed(0)}%"></div>
199
+ </div>
200
+ </div>
201
+ `)}
202
+ </div>
203
+ `;
204
+ }
205
+ };
206
+ __decorate([
207
+ state(),
208
+ __metadata("design:type", Array)
209
+ ], LabelStudioProjectList.prototype, "projects", void 0);
210
+ __decorate([
211
+ state(),
212
+ __metadata("design:type", Boolean)
213
+ ], LabelStudioProjectList.prototype, "loading", void 0);
214
+ __decorate([
215
+ state(),
216
+ __metadata("design:type", String)
217
+ ], LabelStudioProjectList.prototype, "error", void 0);
218
+ LabelStudioProjectList = __decorate([
219
+ customElement('label-studio-project-list')
220
+ ], LabelStudioProjectList);
221
+ export { LabelStudioProjectList };
222
+ //# sourceMappingURL=label-studio-project-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"label-studio-project-list.js","sourceRoot":"","sources":["../client/label-studio-project-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/outlined-button.js'\nimport '@material/web/icon/icon.js'\nimport '@material/web/iconbutton/icon-button.js'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { client, PageView, navigate } from '@things-factory/shell/client'\nimport gql from 'graphql-tag'\n\n@customElement('label-studio-project-list')\nexport class LabelStudioProjectList extends PageView {\n static styles = [\n css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n padding: 20px;\n background-color: var(--md-sys-color-surface);\n }\n\n .header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 20px;\n }\n\n .header h2 {\n margin: 0;\n color: var(--md-sys-color-on-surface);\n }\n\n .project-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 20px;\n overflow-y: auto;\n }\n\n .project-card {\n background: var(--md-sys-color-surface-container);\n border-radius: 12px;\n padding: 20px;\n cursor: pointer;\n transition: all 0.3s ease;\n }\n\n .project-card:hover {\n background: var(--md-sys-color-surface-container-high);\n transform: translateY(-2px);\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n }\n\n .project-card h3 {\n margin: 0 0 10px 0;\n color: var(--md-sys-color-on-surface);\n }\n\n .project-card p {\n margin: 5px 0;\n color: var(--md-sys-color-on-surface-variant);\n font-size: 14px;\n }\n\n .project-stats {\n display: flex;\n gap: 15px;\n margin-top: 15px;\n }\n\n .stat-item {\n display: flex;\n flex-direction: column;\n }\n\n .stat-label {\n font-size: 12px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .stat-value {\n font-size: 20px;\n font-weight: bold;\n color: var(--md-sys-color-primary);\n }\n\n .progress-bar {\n width: 100%;\n height: 8px;\n background: var(--md-sys-color-surface-variant);\n border-radius: 4px;\n margin-top: 10px;\n overflow: hidden;\n }\n\n .progress-fill {\n height: 100%;\n background: var(--md-sys-color-primary);\n transition: width 0.3s ease;\n }\n\n .loading {\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100%;\n font-size: 18px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .error {\n color: var(--md-sys-color-error);\n padding: 20px;\n text-align: center;\n }\n `\n ]\n\n @state() projects: any[] = []\n @state() loading: boolean = true\n @state() error: string = ''\n\n async pageUpdated(changes, lifecycle, changedBefore) {\n if (this.active) {\n this.loadProjects()\n }\n }\n\n async loadProjects() {\n try {\n this.loading = true\n\n const response = await client.query({\n query: gql`\n query {\n labelStudioProjects {\n id\n title\n description\n taskCount\n completedTaskCount\n completionRate\n createdAt\n updatedAt\n }\n }\n `\n })\n\n this.projects = response.data.labelStudioProjects\n this.loading = false\n } catch (error: any) {\n console.error('Failed to load projects:', error)\n this.error = error.message || 'Failed to load projects'\n this.loading = false\n }\n }\n\n createProject() {\n navigate('label-studio-project-create')\n }\n\n openProject(projectId: number) {\n // Open Label Studio viewer with project\n navigate(`label-studio-label/${projectId}`)\n }\n\n render() {\n if (this.loading) {\n return html`<div class=\"loading\">Loading projects...</div>`\n }\n\n if (this.error) {\n return html`<div class=\"error\">${this.error}</div>`\n }\n\n return html`\n <div class=\"header\">\n <h2>Label Studio Projects</h2>\n <md-filled-button @click=${this.createProject}>\n <md-icon slot=\"icon\">add</md-icon>\n New Project\n </md-filled-button>\n </div>\n\n <div class=\"project-grid\">\n ${this.projects.map(\n project => html`\n <div class=\"project-card\" @click=${() => this.openProject(project.id)}>\n <h3>${project.title}</h3>\n <p>${project.description || 'No description'}</p>\n\n <div class=\"project-stats\">\n <div class=\"stat-item\">\n <span class=\"stat-label\">Tasks</span>\n <span class=\"stat-value\">${project.taskCount}</span>\n </div>\n <div class=\"stat-item\">\n <span class=\"stat-label\">Completed</span>\n <span class=\"stat-value\">${project.completedTaskCount}</span>\n </div>\n </div>\n\n <div class=\"progress-bar\">\n <div class=\"progress-fill\" style=\"width: ${(project.completionRate * 100).toFixed(0)}%\"></div>\n </div>\n </div>\n `\n )}\n </div>\n `\n }\n}\n"]}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Label Studio Wrapper Component for Things Factory
3
+ *
4
+ * This component embeds Label Studio within Things Factory using an iframe.
5
+ * It uses subdomain-based cookie sharing for SSO authentication, eliminating
6
+ * the need for proxying.
7
+ *
8
+ * ## Key Features:
9
+ * - Direct Label Studio access (no proxy needed)
10
+ * - Automatic SSO cookie setup via shared domain
11
+ * - Hidden header for seamless integration
12
+ * - Floating Action Buttons (FAB) for Reload and Fullscreen
13
+ *
14
+ * ## Architecture:
15
+ * 1. Client calls /label-studio/sso/setup
16
+ * 2. Backend sets cookie with shared domain (e.g., .nubison.localhost)
17
+ * 3. Browser → Label Studio (direct, with cookie)
18
+ *
19
+ * ## Configuration Requirements:
20
+ * - Label Studio and Things Factory must use subdomain pattern (e.g., label.example.com, app.example.com)
21
+ * - Label Studio .env must include:
22
+ * - CSRF_TRUSTED_ORIGINS: Include Things Factory frontend domain
23
+ * - ALLOWED_HOSTS: Include subdomain patterns
24
+ * - JWT_SSO_* settings for SSO authentication
25
+ * - Things Factory config must include:
26
+ * - labelStudio.serverUrl: Label Studio URL
27
+ * - labelStudio.cookieDomain: Shared cookie domain (e.g., .example.com)
28
+ *
29
+ * ## Usage:
30
+ * ```html
31
+ * <label-studio-wrapper></label-studio-wrapper>
32
+ * ```
33
+ *
34
+ * The component automatically:
35
+ * 1. Calls SSO setup endpoint to establish authentication
36
+ * 2. Builds iframe URL pointing directly to Label Studio
37
+ * 3. Adds ?hideHeader=true to hide Label Studio's header
38
+ * 4. Provides FAB buttons for reload and fullscreen
39
+ */
40
+ import { LitElement } from 'lit';
41
+ export declare class LabelStudioWrapper extends LitElement {
42
+ static styles: import("lit").CSSResult;
43
+ private loading;
44
+ private error;
45
+ private iframeUrl;
46
+ private path;
47
+ private iframeLoaded;
48
+ private labelStudioUrl;
49
+ private iframe;
50
+ updated(changes: any): Promise<void>;
51
+ buildIframeUrl(): Promise<void>;
52
+ handleIframeLoad(): void;
53
+ handleIframeError(): void;
54
+ handleReload(): void;
55
+ handleFullscreen(): void;
56
+ render(): import("lit-html").TemplateResult<1>;
57
+ }