import { Resolver, Query, Mutation, Arg, Ctx, Int, Directive } from 'type-graphql' import { labelStudioApi } from '../../utils/label-studio-api-client.js' import { LabelConfigBuilder } from '../../utils/label-config-builder.js' import { LabelStudioProject, CreateProjectInput, CreateProjectWithSpecInput, ProjectMetrics, AnnotatorStats } from '../../types/label-studio-types.js' @Resolver() export class ProjectManagement { /** * Get JWT token for Label Studio SSO */ @Query(returns => String, { description: 'Get JWT token for Label Studio authentication', nullable: true }) async labelStudioToken(@Ctx() context: ResolverContext): Promise { try { const { user } = context.state if (!user) { return null } // Generate JWT token using User.sign() method const token = await user.sign() return token } catch (error) { console.error('Failed to generate Label Studio token:', error) return null } } /** * Get all Label Studio projects */ @Query(returns => [LabelStudioProject], { description: 'Get all Label Studio projects accessible to the user' }) @Directive('@privilege(category: "label-studio", privilege: "query")') async labelStudioProjects(@Ctx() context: ResolverContext): Promise { try { const projects = await labelStudioApi.getProjects() return projects.map(project => ({ id: project.id, title: project.title, description: project.description || '', labelConfig: project.label_config || '', expertInstruction: project.expert_instruction || '', taskCount: project.task_number || 0, completedTaskCount: project.finished_task_number || 0, completionRate: project.task_number > 0 ? (project.finished_task_number || 0) / project.task_number : 0, createdAt: new Date(project.created_at), updatedAt: project.updated_at ? new Date(project.updated_at) : new Date(project.created_at) })) } catch (error) { console.error('Failed to fetch Label Studio projects:', error) throw new Error(`Failed to fetch projects: ${error.message}`) } } /** * Get single Label Studio project by ID */ @Query(returns => LabelStudioProject, { description: 'Get a single Label Studio project by ID', nullable: true }) @Directive('@privilege(category: "label-studio", privilege: "query")') async labelStudioProject( @Arg('projectId', type => Int) projectId: number, @Ctx() context: ResolverContext ): Promise { try { const project = await labelStudioApi.getProject(projectId) if (!project) { return null } return { id: project.id, title: project.title, description: project.description || '', labelConfig: project.label_config || '', expertInstruction: project.expert_instruction || '', taskCount: project.task_number || 0, completedTaskCount: project.finished_task_number || 0, completionRate: project.task_number > 0 ? (project.finished_task_number || 0) / project.task_number : 0, createdAt: new Date(project.created_at), updatedAt: project.updated_at ? new Date(project.updated_at) : new Date(project.created_at) } } catch (error) { console.error(`Failed to fetch Label Studio project ${projectId}:`, error) return null } } /** * Create new Label Studio project */ @Mutation(returns => LabelStudioProject, { description: 'Create a new Label Studio project' }) @Directive('@privilege(category: "label-studio", privilege: "mutation")') async createLabelStudioProject( @Arg('input') input: CreateProjectInput, @Ctx() context: ResolverContext ): Promise { try { const project = await labelStudioApi.createProject({ title: input.title, description: input.description, label_config: input.labelConfig, expert_instruction: input.expertInstruction }) return { id: project.id, title: project.title, description: project.description || '', labelConfig: project.label_config || '', expertInstruction: project.expert_instruction || '', taskCount: 0, completedTaskCount: 0, completionRate: 0, createdAt: new Date(project.created_at), updatedAt: new Date(project.updated_at) } } catch (error) { console.error('Failed to create Label Studio project:', error) throw new Error(`Failed to create project: ${error.message}`) } } /** * Update Label Studio project */ @Mutation(returns => LabelStudioProject, { description: 'Update an existing Label Studio project' }) @Directive('@privilege(category: "label-studio", privilege: "mutation")') async updateLabelStudioProject( @Arg('projectId', type => Int) projectId: number, @Arg('input') input: CreateProjectInput, @Ctx() context: ResolverContext ): Promise { try { const project = await labelStudioApi.updateProject(projectId, { title: input.title, description: input.description, label_config: input.labelConfig, expert_instruction: input.expertInstruction }) return { id: project.id, title: project.title, description: project.description || '', labelConfig: project.label_config || '', expertInstruction: project.expert_instruction || '', taskCount: project.task_number || 0, completedTaskCount: project.finished_task_number || 0, completionRate: project.task_number > 0 ? (project.finished_task_number || 0) / project.task_number : 0, createdAt: new Date(project.created_at), updatedAt: project.updated_at ? new Date(project.updated_at) : new Date(project.created_at) } } catch (error) { console.error(`Failed to update Label Studio project ${projectId}:`, error) throw new Error(`Failed to update project: ${error.message}`) } } /** * Delete Label Studio project */ @Mutation(returns => Boolean, { description: 'Delete a Label Studio project' }) @Directive('@privilege(category: "label-studio", privilege: "mutation")') async deleteLabelStudioProject( @Arg('projectId', type => Int) projectId: number, @Ctx() context: ResolverContext ): Promise { try { await labelStudioApi.deleteProject(projectId) return true } catch (error) { console.error(`Failed to delete Label Studio project ${projectId}:`, error) throw new Error(`Failed to delete project: ${error.message}`) } } /** * Get project metrics and statistics */ @Query(returns => ProjectMetrics, { description: 'Get metrics and statistics for a Label Studio project' }) @Directive('@privilege(category: "label-studio", privilege: "query")') async labelStudioProjectMetrics( @Arg('projectId', type => Int) projectId: number, @Ctx() context: ResolverContext ): Promise { try { const stats = await labelStudioApi.getProjectStats(projectId) const annotatorStats = await labelStudioApi.getAnnotatorStats(projectId) return { totalTasks: stats.totalTasks, completedTasks: stats.completedTasks, totalAnnotations: stats.totalAnnotations, avgAnnotationsPerTask: stats.avgAnnotationsPerTask, completionRate: stats.completionRate, avgTimePerTask: null, // 계산 필요 annotatorStats: annotatorStats.map((stat: any) => ({ email: stat.email, annotationCount: stat.annotationCount, avgTime: stat.avgTime, lastAnnotationDate: new Date(stat.lastAnnotationDate) })) } } catch (error) { console.error(`Failed to fetch project metrics for ${projectId}:`, error) throw new Error(`Failed to fetch project metrics: ${error.message}`) } } /** * Create project with flexible config specification * Uses LabelConfigBuilder for declarative template generation */ @Mutation(returns => LabelStudioProject, { description: 'Create Label Studio project with flexible config specification' }) @Directive('@privilege(category: "label-studio", privilege: "mutation")') async createLabelStudioProjectWithSpec( @Arg('input') input: CreateProjectWithSpecInput, @Ctx() context: ResolverContext ): Promise { try { // Parse controls JSON const controls = JSON.parse(input.labelConfigSpec.controls) // Build label config using LabelConfigBuilder const labelConfig = LabelConfigBuilder.build({ dataType: input.labelConfigSpec.dataType as any, dataName: input.labelConfigSpec.dataName, controls }) // Create project const project = await labelStudioApi.createProject({ title: input.title, description: input.description, label_config: labelConfig, expert_instruction: input.expertInstruction }) return { id: project.id, title: project.title, description: project.description || '', labelConfig: project.label_config || '', expertInstruction: project.expert_instruction || '', taskCount: 0, completedTaskCount: 0, completionRate: 0, createdAt: new Date(project.created_at), updatedAt: new Date(project.updated_at) } } catch (error) { console.error('Failed to create Label Studio project with spec:', error) throw new Error(`Failed to create project: ${error.message}`) } } }