Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,9 @@ const handleSubmit = async () => {
isInitialStage: true,
isFinalStage: false, // Annotation stage is never final - completion stage is always final
inputDataSourceId: form.annotationInputDataSourceId as number,
targetDataSourceId: form.includeRevision
? (form.revisionInputDataSourceId as number || undefined)
: (form.completionInputDataSourceId as number || undefined),
assignedProjectMemberIds: form.annotationMembers
});

Expand All @@ -907,6 +910,7 @@ const handleSubmit = async () => {
isInitialStage: false,
isFinalStage: false,
inputDataSourceId: form.revisionInputDataSourceId as number || undefined,
targetDataSourceId: form.completionInputDataSourceId as number || undefined,
assignedProjectMemberIds: form.revisionMembers
});
}
Expand All @@ -920,6 +924,7 @@ const handleSubmit = async () => {
isInitialStage: false,
isFinalStage: true,
inputDataSourceId: form.completionInputDataSourceId as number || undefined,
targetDataSourceId: undefined, // Final stage has no target
assignedProjectMemberIds: form.completionMembers
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class BoundingBoxToolHandler implements ToolHandler {
coordinates: boundingBoxCoordinates,
assetId: Number(store.currentAssetId),
taskId: Number(store.currentTaskId),
isGroundTruth: true
};

store.addAnnotation(newAnnotation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class LineToolHandler implements ToolHandler {
coordinates: lineCoordinates,
assetId: Number(store.currentAssetId),
taskId: Number(store.currentTaskId),
isGroundTruth: true
};

store.addAnnotation(newAnnotation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export class PointToolHandler implements ToolHandler {
coordinates: pointCoordinates,
assetId: Number(store.currentAssetId),
taskId: Number(store.currentTaskId),
isGroundTruth: true
};

store.addAnnotation(newAnnotation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export class PolygonToolHandler implements ToolHandler {
coordinates: polygonCoordinates,
assetId: Number(store.currentAssetId),
taskId: Number(store.currentTaskId),
isGroundTruth: true
};

store.addAnnotation(newAnnotation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export class PolylineToolHandler implements ToolHandler {
coordinates: polylineCoordinates,
assetId: Number(store.currentAssetId),
taskId: Number(store.currentTaskId),
isGroundTruth: true
};

store.addAnnotation(newAnnotation);
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/services/project/export/export.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Export metadata response
*/
export interface ExportMetadata {
completedTasksCount: number
annotationsCount: number
annotatedAssetsCount: number
categoriesCount: number
workflowStageName: string
projectName: string
availableFormats: string[]
}

/**
* COCO export request configuration
*/
export interface CocoExportRequest {
includeGroundTruth?: boolean
includePredictions?: boolean
fileName?: string
description?: string
contributor?: string
taskIds?: number[]
labelIds?: number[]
}

/**
* Export format types
*/
export enum ExportFormat {
COCO = 'COCO'
}

/**
* Export download response
*/
export interface ExportDownloadResponse {
data: Blob
filename: string
contentType: string
}
158 changes: 158 additions & 0 deletions frontend/src/services/project/export/exportService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { BaseProjectService } from '../baseProjectService'
import apiClient from '@/services/apiClient'
import type {
ExportMetadata,
CocoExportRequest,
ExportDownloadResponse
} from './export.types'

/**
* Service for exporting annotated data in various formats
*/
export class ExportService extends BaseProjectService {
constructor() {
super('ExportService')
}

/**
* Gets export metadata for a workflow stage
*/
async getExportMetadata(projectId: number, workflowStageId: number): Promise<ExportMetadata> {
const url = this.buildProjectUrl(projectId, `export/workflow-stages/${workflowStageId}/metadata`)
return this.get<ExportMetadata>(url)
}

/**
* Exports workflow stage data in COCO format (simple GET request)
*/
async exportCoco(
projectId: number,
workflowStageId: number,
includeGroundTruth: boolean = true,
includePredictions: boolean = false
): Promise<ExportDownloadResponse> {
const url = this.buildProjectUrl(projectId, `export/workflow-stages/${workflowStageId}/coco`)
const params = {
includeGroundTruth: includeGroundTruth.toString(),
includePredictions: includePredictions.toString()
}

try {
const response = await apiClient.get(url, {
params,
responseType: 'blob'
})

// Extract filename from Content-Disposition header
const contentDisposition = response.headers['content-disposition'] || response.headers['Content-Disposition']
let filename = 'coco_export.json'

if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, '')
}
}

return {
data: response.data as Blob,
filename,
contentType: 'application/json'
}
} catch (error) {
this.logger.error('Failed to export COCO data', error)
throw error
}
}

/**
* Exports workflow stage data in COCO format with advanced options (POST request)
*/
async exportCocoWithOptions(
projectId: number,
workflowStageId: number,
exportRequest: CocoExportRequest
): Promise<ExportDownloadResponse> {
const url = this.buildProjectUrl(projectId, `export/workflow-stages/${workflowStageId}/coco`)

try {
const response = await apiClient.post(url, exportRequest, {
responseType: 'blob'
})

// Extract filename from Content-Disposition header
const contentDisposition = response.headers['content-disposition'] || response.headers['Content-Disposition']
let filename = exportRequest.fileName || 'coco_export.json'

if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, '')
}
}

if (!filename.toLowerCase().endsWith('.json')) {
filename += '.json'
}

return {
data: response.data as Blob,
filename,
contentType: 'application/json'
}
} catch (error) {
this.logger.error('Failed to export COCO data with options', error)
throw error
}
}

/**
* Downloads a blob as a file
*/
private downloadBlob(blob: Blob, filename: string): void {
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
}

/**
* Downloads COCO export directly (convenience method)
*/
async downloadCocoExport(
projectId: number,
workflowStageId: number,
includeGroundTruth: boolean = true,
includePredictions: boolean = false
): Promise<void> {
const exportResponse = await this.exportCoco(
projectId,
workflowStageId,
includeGroundTruth,
includePredictions
)

this.downloadBlob(exportResponse.data, exportResponse.filename)
}

/**
* Downloads COCO export with options directly (convenience method)
*/
async downloadCocoExportWithOptions(
projectId: number,
workflowStageId: number,
exportRequest: CocoExportRequest
): Promise<void> {
const exportResponse = await this.exportCocoWithOptions(
projectId,
workflowStageId,
exportRequest
)

this.downloadBlob(exportResponse.data, exportResponse.filename)
}
}
12 changes: 12 additions & 0 deletions frontend/src/services/project/export/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ExportService } from './exportService'

export { ExportService } from './exportService'
export type {
ExportMetadata,
CocoExportRequest,
ExportDownloadResponse
} from './export.types'
export { ExportFormat } from './export.types'

// Create singleton service instance
export const exportService = new ExportService()
1 change: 1 addition & 0 deletions frontend/src/services/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { annotationService } from './annotationService';
export { assetService } from './asset';
export { dashboardService } from './dashboard';
export { dataSourceService } from './dataSource';
export { exportService } from './export';
export { labelService, labelSchemeService } from './labelScheme';
export { taskService, taskBulkOperations } from './task';
export {
Expand Down
24 changes: 0 additions & 24 deletions frontend/src/stores/projectStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export const useProjectStore = defineStore("project", {
// Load related data in parallel
await Promise.all([
this.loadTeamMembers(projectId),
this.loadProjectLabels(projectId),
]);
} catch (error) {
logger.error(`Failed to load project ${projectId}`, error);
Expand All @@ -102,23 +101,6 @@ export const useProjectStore = defineStore("project", {
}
},

async loadProjectLabels(projectId: number): Promise<void> {
const { handleError } = useErrorHandler();

this.labelsLoading = true;
try {
// TODO: This would need a proper API endpoint for project labels
// For now, we'll skip this functionality until the API is available
this.projectLabels = [];
logger.info(`Labels functionality not yet implemented for project ${projectId}`);
} catch (error) {
logger.error(`Failed to load labels for project ${projectId}`, error);
handleError(error, "Failed to load project labels");
} finally {
this.labelsLoading = false;
}
},

addTeamMember(member: ProjectMember): void {
this.teamMembers.push(member);
logger.info(`Added team member: ${member.userName || member.email}`);
Expand Down Expand Up @@ -213,12 +195,6 @@ export const useProjectStore = defineStore("project", {
}
},

async refreshProjectLabels(): Promise<void> {
if (this.currentProjectId) {
await this.loadProjectLabels(this.currentProjectId);
}
},

setCurrentStageType(stageType: string): void {
this.currentStageType = stageType;
logger.info(`Updated current stage type: ${stageType}`);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/stores/workspaceStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ export const useWorkspaceStore = defineStore("workspace", {
labelId: annotation.labelId,
isPrediction: annotation.isPrediction || false,
confidenceScore: annotation.confidenceScore,
isGroundTruth: annotation.isGroundTruth || false,
isGroundTruth: annotation.isGroundTruth,
version: annotation.version || 1,
notes: annotation.notes,
annotatorEmail: annotation.annotatorEmail,
Expand Down
Loading
Loading