Skip to content
This repository was archived by the owner on Mar 8, 2024. It is now read-only.

Commit 9fcf044

Browse files
author
JWittmeyer
committed
Adds heursitc crowd page
1 parent 235be46 commit 9fcf044

20 files changed

+990
-52
lines changed

src/app/app-routing.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { RecordIDEComponent } from './record-ide/components/record-ide.component
1818
import { ConfigComponent } from './config/components/config.component';
1919
import { ModelDownloadComponent } from './model-download/pages/model-download/model-download.component';
2020
import { ModelCallbackComponent } from './model-callbacks/components/model-callbacks.component';
21+
import { CrowdLabelerDetailsComponent } from './weak-supervision/components/crowd-labeler-details/component/crowd-labeler-details.component';
2122

2223
const routes: Routes = [
2324
{
@@ -46,11 +47,14 @@ const routes: Routes = [
4647
{
4748
path: 'zero-shot/:informationSourceId',
4849
component: ZeroShotDetailsComponent, data: { name: 'ZeroShotDetailsComponent' }
50+
}, {
51+
path: 'crowd-labeler/:informationSourceId',
52+
component: CrowdLabelerDetailsComponent, data: { name: 'CrowdLabelerDetailsComponent' }
4953
},
5054
{ path: 'data', component: DataBrowserComponent, data: { name: 'DataBrowserComponent' } },
5155
{ path: 'settings', component: ProjectSettingsComponent, data: { name: 'ProjectSettingsComponent' } },
5256
{ path: 'labeling', component: LabelingComponent, data: { name: 'LabelingComponent' } },
53-
{ path: 'labeling/:sessionId', component: LabelingComponent, data: { name: 'LabelingComponent' } },
57+
{ path: 'labeling/:id', component: LabelingComponent, data: { name: 'LabelingComponent' } },
5458
{ path: 'record-ide/:sessionId', component: RecordIDEComponent, data: { name: 'RecordIDEComponent' } },
5559
{ path: 'knowledge-base', component: KnowledgeBasesComponent, data: { name: 'KnowledgeBasesComponent' } },
5660
{

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { GraphQLModule } from './graphql.module';
1515
import { ProjectOverviewModule } from './project-overview/project-overview.module';
1616
import { WeakSupervisionModule } from './weak-supervision/weak-supervision.module';
1717
import { ZeroShotModule } from './zero-shot-details/zero-shot-details.module';
18+
import { CrowdLabelerModule } from './weak-supervision/components/crowd-labeler-details/crowd-labeler-details.module';
1819
import { MonacoEditorModule } from 'ngx-monaco-editor';
1920
import { LabelingModule } from './labeling/labeling.module';
2021
import { KnowledgeBasesModule } from './knowledge-bases/knowledge-bases.module';
@@ -39,6 +40,7 @@ import { ModelCallbackModule } from './model-callbacks/model-callbacks.module';
3940
HttpClientModule,
4041
ProjectOverviewModule,
4142
WeakSupervisionModule,
43+
CrowdLabelerModule,
4244
ZeroShotModule,
4345
LabelingModule,
4446
RecordIDEModule,

src/app/base/components/dropdown/dropdown.component.html

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<div class="relative" [ngClass]="dropdownOptions.buttonVersion == 'default' ? ' w-max' : '' ">
2-
<button #dropdownOpenButton type="button" [attr.data-tip]="dropdownOptions.buttonTooltip" *ngIf="dropdownOptions.buttonVersion == 'default'"
2+
<button #dropdownOpenButton type="button" [attr.data-tip]="dropdownOptions.buttonTooltip"
3+
*ngIf="dropdownOptions.buttonVersion == 'default'"
34
class="inline-flex rounded-md border shadow-sm px-4 items-center text-xs font-semibold cursor-pointer focus:ring-offset-2 focus:ring-offset-gray-400"
45
id="menu-button" aria-expanded="true" aria-haspopup="true" [disabled]="dropdownOptions.isDisabled"
56
[ngClass]="buttonClassList" (isMenuOpen)="toggleVisible($event, dropdownOptionsDiv)" appDropdown>
@@ -18,29 +19,28 @@
1819
</svg>
1920
</button>
2021

21-
<svg *ngIf="dropdownOptions.buttonVersion== '...'" (isMenuOpen)="toggleVisible($event, dropdownOptionsDiv)" appDropdown
22-
xmlns="http://www.w3.org/2000/svg"
23-
class="h-5 w-5 float-right cursor-pointer text-gray-500" viewBox="0 0 20 20"
24-
fill="currentColor">
25-
<path
26-
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
22+
<svg *ngIf="dropdownOptions.buttonVersion== '...'" (isMenuOpen)="toggleVisible($event, dropdownOptionsDiv)"
23+
appDropdown xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 float-right cursor-pointer text-gray-500"
24+
viewBox="0 0 20 20" fill="currentColor">
25+
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
2726
</svg>
2827
<a *ngIf="dropdownOptions.buttonVersion== 'userIcon'" tabindex="0" class="w-full cursor-pointer">
29-
<div data-intercom-target="User Avatar Button" (isMenuOpen)="toggleVisible($event, dropdownOptionsDiv)" appDropdown>
28+
<div data-intercom-target="User Avatar Button" (isMenuOpen)="toggleVisible($event, dropdownOptionsDiv)"
29+
appDropdown>
3030
<img class="h-8 w-8" [src]="dropdownOptions.avatarUri">
3131
</div>
3232
</a>
3333

3434
<div #dropdownOptionsDiv
3535
class="origin-top-right absolute mt-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none hidden"
36-
[ngClass]="[dropdownClassList, dropdownOptions.isButtonSampleProjects ? 'width-sample-projects' : '', dropdownOptions.buttonVersion != '...' ? 'min-w-full': '']"
36+
[ngClass]="[dropdownClassList, dropdownOptions.isButtonSampleProjects ? 'width-sample-projects' : '', dropdownOptions.buttonVersion != '...' ? 'min-w-full': '']"
3737
role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
3838
<div class="py-1 cursor-pointer" role="none">
3939
<div *ngFor="let caption of dropdownOptionCaptions;let i = index" (click)="performActionOnOption($event,i)"
4040
[ngClass]="[dropdownOptions.textSize,dropdownOptions.textColor,dropdownOptions.optionDescriptions ? '' : 'flex' ,dropdownOptions.isOptionDisabled?.length && dropdownOptions.isOptionDisabled[i]?'opacity-50 cursor-not-allowed':'',!dropdownOptions.hasCheckboxes ? dropdownOptions.hoverColor + ' ' +dropdownOptions.textHoverColor:'', dropdownOptions.isModelDownloaded?.length && dropdownOptions.isModelDownloaded[i] ? 'text-green-700 hover:text-lime-300': '']"
4141
[ngStyle]="{'border-bottom': dropdownOptions.isButtonSampleProjects && i%2 == 0 && i!=dropdownOptionCaptions.length-1 ? '1px dashed #e2e8f0' : (dropdownOptions.isButtonSampleProjects && i%2 != 0 && i!=dropdownOptionCaptions.length-1 ? '1px solid #e2e8f0' : null) }"
42-
class="block px-2 py-1.5 flex-row flex-nowrap items-center gap-x-2 w-max min-w-full"
43-
role="menuitem" tabindex="-1" id="menu-item-0">
42+
class="block px-2 py-1.5 flex-row flex-nowrap items-center gap-x-2 w-max min-w-full" role="menuitem"
43+
tabindex="-1" id="menu-item-0">
4444
<ng-template [ngIf]="dropdownOptions.hasCheckboxes">
4545
<div class="h-4 w-4 border-gray-300 border rounded hover:bg-gray-200"
4646
[ngStyle]="{'background-color':getActiveNegateGroupColor(dropdownOptions.optionArray[i]), 'border-color':getActiveNegateGroupColor(dropdownOptions.optionArray[i])}">
@@ -53,7 +53,8 @@
5353
</ng-template>
5454
{{ caption }}
5555

56-
<p *ngIf="dropdownOptions.optionDescriptions && dropdownOptions.optionDescriptions[i] != ''" class="mt-2">
56+
<p *ngIf="dropdownOptions.optionDescriptions && dropdownOptions.optionDescriptions[i] != ''"
57+
class="mt-2">
5758
{{dropdownOptions.optionDescriptions[i]}}
5859
</p>
5960
</div>
@@ -67,8 +68,8 @@
6768
<ng-template ngSwitchDefault>
6869
</ng-template>
6970
<ng-template ngSwitchCase="clickbait">
70-
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-fish-hook inline-block" width="20"
71-
height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
71+
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-fish-hook inline-block"
72+
width="20" height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
7273
stroke-linecap="round" stroke-linejoin="round">
7374
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
7475
<path d="M16 9v6a5 5 0 0 1 -10 0v-4l3 3"></path>
@@ -77,8 +78,8 @@
7778
</svg>
7879
</ng-template>
7980
<ng-template ngSwitchCase="conversational-ai">
80-
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message-circle inline-block" width="20"
81-
height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
81+
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message-circle inline-block"
82+
width="20" height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
8283
stroke-linecap="round" stroke-linejoin="round">
8384
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
8485
<path d="M3 20l1.3 -3.9a9 8 0 1 1 3.4 2.9l-4.7 1"></path>
@@ -88,9 +89,9 @@
8889
</svg>
8990
</ng-template>
9091
<ng-template ngSwitchCase="ag-news">
91-
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-news inline-block" width="20" height="20"
92-
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
93-
stroke-linejoin="round">
92+
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-news inline-block" width="20"
93+
height="20" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
94+
stroke-linecap="round" stroke-linejoin="round">
9495
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
9596
<path
9697
d="M16 6h3a1 1 0 0 1 1 1v11a2 2 0 0 1 -4 0v-13a1 1 0 0 0 -1 -1h-10a1 1 0 0 0 -1 1v12a3 3 0 0 0 3 3h11">
@@ -159,38 +160,48 @@
159160
</svg>
160161
</ng-template>
161162
<ng-template ngSwitchCase="delete-selected">
162-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" fill="none"
163-
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
163+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" fill="none" viewBox="0 0 24 24"
164+
stroke="currentColor" stroke-width="2">
164165
<path stroke-linecap="round" stroke-linejoin="round"
165166
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
166167
</svg>
167168
</ng-template>
168169
<ng-template ngSwitchCase="labeling-function">
169-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block"
170-
viewBox="0 0 20 20" fill="currentColor">
170+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" viewBox="0 0 20 20"
171+
fill="currentColor">
171172
<path fill-rule="evenodd"
172173
d="M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z"
173174
clip-rule="evenodd" />
174175
</svg>
175176
</ng-template>
176177

177178
<ng-template ngSwitchCase="active-learning">
178-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block"
179-
viewBox="0 0 20 20" fill="currentColor">
179+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" viewBox="0 0 20 20"
180+
fill="currentColor">
180181
<path fill-rule="evenodd"
181182
d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z"
182183
clip-rule="evenodd" />
183184
</svg>
184185
</ng-template>
185186

186187
<ng-template ngSwitchCase="zero-shot">
187-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block"
188-
viewBox="0 0 20 20" fill="currentColor">
188+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" viewBox="0 0 20 20"
189+
fill="currentColor">
189190
<path fill-rule="evenodd"
190191
d="M5 2a1 1 0 011 1v1h1a1 1 0 010 2H6v1a1 1 0 01-2 0V6H3a1 1 0 010-2h1V3a1 1 0 011-1zm0 10a1 1 0 011 1v1h1a1 1 0 110 2H6v1a1 1 0 11-2 0v-1H3a1 1 0 110-2h1v-1a1 1 0 011-1zM12 2a1 1 0 01.967.744L14.146 7.2 17.5 9.134a1 1 0 010 1.732l-3.354 1.935-1.18 4.455a1 1 0 01-1.933 0L9.854 12.8 6.5 10.866a1 1 0 010-1.732l3.354-1.935 1.18-4.455A1 1 0 0112 2z"
191192
clip-rule="evenodd" />
192193
</svg>
193194
</ng-template>
195+
<ng-template ngSwitchCase="crowd-labeling">
196+
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block" viewBox="0 0 24 24" stroke-width="2"
197+
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
198+
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
199+
<circle cx="9" cy="7" r="4"></circle>
200+
<path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"></path>
201+
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
202+
<path d="M21 21v-2a4 4 0 0 0 -3 -3.85"></path>
203+
</svg>
204+
</ng-template>
194205
</ng-container>
195206

196207
</ng-template>

src/app/base/enum/graphql-enums.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export enum InformationSourceType {
2121
LABELING_FUNCTION = "LABELING_FUNCTION",
2222
ACTIVE_LEARNING = "ACTIVE_LEARNING",
2323
PRE_COMPUTED = "PRE_COMPUTED",
24-
ZERO_SHOT = "ZERO_SHOT"
24+
ZERO_SHOT = "ZERO_SHOT",
25+
CROWD_LABELER = "CROWD_LABELER"
2526
}
2627

2728
export function informationSourceTypeToString(source: InformationSourceType, short: boolean, forDisplay: boolean = true) {

src/app/base/services/organization/organization-apollo.service.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ export class OrganizationApolloService {
5555
.pipe(map((result) => result['data']['userInfo']));
5656
}
5757

58-
getOrganizationUsers() {
58+
getOrganizationUsers(userRole: string = null) {
5959
return this.apollo
6060
.query({
6161
query: queries.GET_ORGANIZATION_USERS,
62+
variables: {
63+
userRole: userRole
64+
}
6265
})
6366
.pipe(map((result) => result['data']['allUsers']));
6467
}

src/app/base/services/organization/organization-queries.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ export const queries = {
2222
}
2323
`,
2424
GET_ORGANIZATION_USERS: gql`
25-
query{
26-
allUsers{
27-
id
28-
mail
29-
firstName
30-
lastName
31-
}
25+
query($userRole:String){
26+
allUsers(userRole:$userRole) {
27+
id
28+
mail
29+
firstName
30+
lastName
31+
role
3232
}
33+
}
3334
`,
3435
GET_ORGANIZATION_USERS_WITH_COUNT: gql`
3536
query($projectId:ID!){

src/app/base/services/project/project-apollo.service.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,12 +876,13 @@ export class ProjectApolloService {
876876
});
877877
}
878878

879-
getDataSlices(projectId: string) {
879+
getDataSlices(projectId: string, sliceType: string = null) {
880880
const query = this.apollo
881881
.watchQuery({
882882
query: queries.DATA_SLICES,
883883
variables: {
884884
projectId: projectId,
885+
sliceType: sliceType
885886
},
886887
fetchPolicy: 'network-only',
887888
});
@@ -930,4 +931,42 @@ export class ProjectApolloService {
930931
});
931932
}
932933

934+
getAccessLink(projectId: string, linkId: string) {
935+
return this.apollo
936+
.query({
937+
query: queries.GET_ACCESS_LINK,
938+
variables: {
939+
projectId: projectId,
940+
linkId: linkId
941+
},
942+
fetchPolicy: 'cache-first', //this shouldnt change often (also default value)
943+
})
944+
.pipe(map((result) => result['data']['accessLink']));
945+
}
946+
947+
createAccessLink(
948+
projectId: string,
949+
type: string,
950+
id: string): Observable<any> {
951+
return this.apollo.mutate({
952+
mutation: mutations.CREATE_ACCESS_LINK,
953+
variables: {
954+
projectId: projectId,
955+
type: type,
956+
id: id,
957+
},
958+
});
959+
}
960+
removeAccessLink(
961+
projectId: string,
962+
linkId: string): Observable<any> {
963+
return this.apollo.mutate({
964+
mutation: mutations.REMOVE_ACCESS_LINK,
965+
variables: {
966+
projectId: projectId,
967+
linkId: linkId
968+
},
969+
});
970+
}
971+
933972
}

src/app/base/services/project/project-mutations.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,23 @@ export const mutations = {
191191
}
192192
`,
193193

194+
CREATE_ACCESS_LINK: gql`
195+
mutation ($projectId: ID!, $id: ID!, $type: String!) {
196+
generateAccessLink(projectId: $projectId, id: $id, type: $type) {
197+
link {
198+
id
199+
link
200+
}
201+
}
202+
}
203+
`,
204+
205+
REMOVE_ACCESS_LINK: gql`
206+
mutation ($projectId: ID!, $linkId: ID!) {
207+
removeAccessLink(projectId: $projectId, linkId: $linkId) {
208+
ok
209+
}
210+
}
211+
`
212+
194213
};

src/app/base/services/project/project-queries.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ export const queries = {
302302
}
303303
`,
304304
DATA_SLICES: gql`
305-
query($projectId:ID!){
306-
dataSlices(projectId:$projectId){
305+
query($projectId:ID!,$sliceType:String){
306+
dataSlices(projectId:$projectId, sliceType:$sliceType){
307307
id
308308
name
309309
filterRaw
@@ -363,5 +363,13 @@ export const queries = {
363363
finishedAt
364364
}
365365
}
366-
`
366+
`,
367+
GET_ACCESS_LINK: gql`
368+
query ($projectId: ID!, $linkId: ID!) {
369+
accessLink(projectId: $projectId, linkId: $linkId) {
370+
id
371+
link
372+
}
373+
}
374+
`
367375
};

src/app/data/components/data-browser/data-browser.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,8 @@
11201120
<div class="float-right">
11211121
<label class="text-green-700 hover:text-green-500 text-sm font-medium cursor-pointer"
11221122
(click)="storePreliminaryRecordIds(i+1)"
1123-
[routerLink]="['../labeling/' + extendedRecords.sessionId]" [queryParams]="{pos: i+1}">
1123+
[routerLink]="['../labeling/' + extendedRecords.sessionId]"
1124+
[queryParams]="{pos: i+1,type:'SESSION'}">
11241125
<span class="leading-5">Continue with this record
11251126
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 inline-block" fill="none"
11261127
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ActivatedRoute } from "@angular/router";
2+
3+
export type labelingLinkData = {
4+
projectId: string
5+
id: string;
6+
requestedPos: number;
7+
linkType: labelingLinkType;
8+
};
9+
10+
export enum labelingLinkType {
11+
SESSION = "SESSION",
12+
DATA_SLICE = "DATA_SLICE",
13+
HEURISTIC = "HEURISTIC"
14+
}
15+
16+
function linkTypeFromStr(str: string): labelingLinkType {
17+
switch (str.toUpperCase()) {
18+
case "DATA_SLICE":
19+
return labelingLinkType.DATA_SLICE;
20+
case "HEURISTIC":
21+
return labelingLinkType.HEURISTIC;
22+
case "SESSION":
23+
default:
24+
return labelingLinkType.SESSION;
25+
}
26+
}
27+
28+
29+
export function parseLabelingLinkData(route: ActivatedRoute): labelingLinkData {
30+
const projectId = route.parent.snapshot.paramMap.get('projectId');
31+
const id = route.snapshot.paramMap.get("id");
32+
const requestedPosStr = route.snapshot.queryParamMap.get("pos")
33+
const isPosNumber = !Number.isNaN(Number(requestedPosStr));
34+
const type = linkTypeFromStr(route.snapshot.queryParamMap.get("type"));
35+
36+
return {
37+
projectId: projectId,
38+
id: id,
39+
requestedPos: isPosNumber ? Number(requestedPosStr) : 0,
40+
linkType: type
41+
}
42+
}

0 commit comments

Comments
 (0)