标注平台是一个完整的数据标注管理系统,支持从项目创建、任务分配到人员标注的完整工作流程。系统采用前后端分离架构:
graph TB
subgraph "Frontend (web/apps/lq_label)"
A[React App] --> B[Layout Component]
B --> C[Project View]
B --> D[Task View]
B --> E[Annotation View]
A --> F[Jotai State Management]
E --> G[@humansignal/editor]
A --> H[@humansignal/ui Components]
end
subgraph "Backend (backend/)"
I[FastAPI Server] --> J[Project API]
I --> K[Task API]
I --> L[Annotation API]
J --> M[SQLite Database]
K --> M
L --> M
end
A -->|HTTP/REST| I
graph TB
subgraph "App Structure"
A[main.tsx] --> B[App.tsx]
B --> C[Layout]
C --> D[Sidebar Navigation]
C --> E[Main Content Area]
E --> F[Router]
F --> G[ProjectListView]
F --> H[ProjectDetailView]
F --> I[TaskListView]
F --> J[AnnotationView]
end
subgraph "State Management"
K[projectsAtom] --> G
K --> H
L[tasksAtom] --> I
M[currentAnnotationAtom] --> J
end
subgraph "Shared Components"
N[@humansignal/ui]
O[Custom Components]
end
graph TB
subgraph "API Layer"
A[main.py] --> B[Project Router]
A --> C[Task Router]
A --> D[Annotation Router]
end
subgraph "Data Layer"
E[Database Models]
F[SQLite Connection]
E --> F
end
subgraph "Business Logic"
G[Project Service]
H[Task Service]
I[Annotation Service]
end
B --> G
C --> H
D --> I
G --> E
H --> E
I --> E
Location: web/apps/lq_label/src/components/Layout/Layout.tsx
Responsibility: 提供后台管理平台样式的主布局
Interface:
interface LayoutProps {
children: React.ReactNode;
}
Structure:
Location: web/apps/lq_label/src/components/Layout/Sidebar.tsx
Responsibility: 导航菜单
Interface:
interface SidebarProps {
activeRoute: string;
}
interface MenuItem {
id: string;
label: string;
icon: React.ReactNode;
path: string;
}
Menu Items:
Location: web/apps/lq_label/src/views/ProjectListView/ProjectListView.tsx
Responsibility: 显示项目列表,支持创建、编辑、删除项目
Interface:
interface ProjectListViewProps {}
interface Project {
id: string;
name: string;
description: string;
config: string;
created_at: string;
task_count: number;
}
Features:
Location: web/apps/lq_label/src/views/ProjectDetailView/ProjectDetailView.tsx
Responsibility: 显示项目详情和关联任务列表
Interface:
interface ProjectDetailViewProps {
projectId: string;
}
Features:
Location: web/apps/lq_label/src/views/TaskListView/TaskListView.tsx
Responsibility: 显示任务列表,支持筛选和操作
Interface:
interface TaskListViewProps {}
interface Task {
id: string;
project_id: string;
name: string;
data: any;
status: 'pending' | 'in_progress' | 'completed';
assigned_to: string | null;
created_at: string;
progress: number;
}
Features:
Location: web/apps/lq_label/src/views/AnnotationView/AnnotationView.tsx
Responsibility: 标注界面,集成 LabelStudio 编辑器
Interface:
interface AnnotationViewProps {
taskId: string;
}
interface AnnotationData {
id: string;
task_id: string;
user_id: string;
result: any;
created_at: string;
updated_at: string;
}
Features:
Location: web/apps/lq_label/src/components/ProjectForm/ProjectForm.tsx
Responsibility: 项目创建和编辑表单
Interface:
interface ProjectFormProps {
project?: Project;
onSubmit: (data: ProjectFormData) => void;
onCancel: () => void;
}
interface ProjectFormData {
name: string;
description: string;
config: string;
}
Location: web/apps/lq_label/src/components/TaskForm/TaskForm.tsx
Responsibility: 任务创建表单
Interface:
interface TaskFormProps {
projectId: string;
onSubmit: (data: TaskFormData) => void;
onCancel: () => void;
}
interface TaskFormData {
name: string;
data: any;
assigned_to: string | null;
}
Router: backend/routers/project.py
Endpoints:
GET /api/projects # List all projects
POST /api/projects # Create project
GET /api/projects/{id} # Get project by ID
PUT /api/projects/{id} # Update project
DELETE /api/projects/{id} # Delete project
Models:
class ProjectCreate(BaseModel):
name: str
description: str
config: str
class ProjectUpdate(BaseModel):
name: Optional[str]
description: Optional[str]
config: Optional[str]
class ProjectResponse(BaseModel):
id: str
name: str
description: str
config: str
created_at: datetime
task_count: int
Router: backend/routers/task.py
Endpoints:
GET /api/tasks # List all tasks (with filters)
POST /api/tasks # Create task
GET /api/tasks/{id} # Get task by ID
PUT /api/tasks/{id} # Update task
DELETE /api/tasks/{id} # Delete task
GET /api/projects/{id}/tasks # Get tasks by project
Models:
class TaskCreate(BaseModel):
project_id: str
name: str
data: dict
assigned_to: Optional[str]
class TaskUpdate(BaseModel):
name: Optional[str]
data: Optional[dict]
status: Optional[str]
assigned_to: Optional[str]
class TaskResponse(BaseModel):
id: str
project_id: str
name: str
data: dict
status: str
assigned_to: Optional[str]
created_at: datetime
progress: float
Router: backend/routers/annotation.py
Endpoints:
GET /api/annotations # List annotations (with filters)
POST /api/annotations # Create annotation
GET /api/annotations/{id} # Get annotation by ID
PUT /api/annotations/{id} # Update annotation
GET /api/tasks/{id}/annotations # Get annotations by task
Models:
class AnnotationCreate(BaseModel):
task_id: str
user_id: str
result: dict
class AnnotationUpdate(BaseModel):
result: dict
class AnnotationResponse(BaseModel):
id: str
task_id: str
user_id: str
result: dict
created_at: datetime
updated_at: datetime
-- Projects table
CREATE TABLE projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
config TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Tasks table
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
name TEXT NOT NULL,
data TEXT NOT NULL, -- JSON string
status TEXT DEFAULT 'pending',
assigned_to TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);
-- Annotations table
CREATE TABLE annotations (
id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
user_id TEXT NOT NULL,
result TEXT NOT NULL, -- JSON string
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
);
Location: web/apps/lq_label/src/atoms/
// projectAtoms.ts
export const projectsAtom = atom<Project[]>([]);
export const currentProjectAtom = atom<Project | null>(null);
export const projectLoadingAtom = atom<boolean>(false);
export const projectErrorAtom = atom<string | null>(null);
// taskAtoms.ts
export const tasksAtom = atom<Task[]>([]);
export const currentTaskAtom = atom<Task | null>(null);
export const taskLoadingAtom = atom<boolean>(false);
export const taskErrorAtom = atom<string | null>(null);
export const taskFilterAtom = atom<TaskFilter>({
status: null,
projectId: null,
});
// annotationAtoms.ts
export const currentAnnotationAtom = atom<AnnotationData | null>(null);
export const annotationLoadingAtom = atom<boolean>(false);
export const annotationErrorAtom = atom<string | null>(null);
export const lsfInstanceAtom = atom<any>(null);
A property is a characteristic or behavior that should hold true across all valid executions of a system—essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.
For any valid project data (non-empty name and config), creating a project should result in the project appearing in the projects list with a unique ID.
Validates: Requirements 1.3
For any project creation attempt with an empty or whitespace-only name, the frontend should prevent submission and display a validation error.
Validates: Requirements 1.4
For any project with associated tasks, deleting the project should also delete all associated tasks and their annotations.
Validates: Requirements 1.7
For any valid task data with a valid project_id, creating a task should result in the task being associated with that project and appearing in the project's task list.
Validates: Requirements 2.2
For any task status filter value, the displayed task list should only contain tasks matching that status.
Validates: Requirements 2.4
For any task where all data items have been annotated, the task status should automatically update to 'completed'.
Validates: Requirements 2.7
For any user, the task list view should only display tasks assigned to that user.
Validates: Requirements 3.1
For any annotation save operation, the associated task's progress should be updated to reflect the number of completed annotations.
Validates: Requirements 3.3
For any annotation submission attempt with empty or null result data, the frontend should prevent submission and display an error message.
Validates: Requirements 3.4
For any annotation view load, the LabelStudio editor should be initialized with the project's annotation config.
Validates: Requirements 3.7
For any invalid API request (missing required fields, invalid IDs, etc.), the backend should return a 4xx status code with a descriptive error message.
Validates: Requirements 5.6
For any task creation request, if the project_id does not exist in the database, the backend should reject the request with a 404 error.
Validates: Requirements 6.4
For any annotation creation request, if the task_id does not exist in the database, the backend should reject the request with a 404 error.
Validates: Requirements 6.5
For any valid annotation result object, serializing to JSON and deserializing should produce an equivalent object.
Validates: Requirements 6.7
For any active route, the corresponding menu item in the sidebar should be visually highlighted.
Validates: Requirements 7.3
For any LabelStudio editor instance, when the annotation view unmounts, all editor resources should be properly cleaned up (event listeners, DOM references, MST subscriptions).
Validates: Requirements 8.8
API Request Errors
Form Validation Errors
Editor Errors
Error Boundary
Validation Errors
Not Found Errors
Database Errors
CORS Errors
Frontend:
Backend:
Example Unit Tests:
Configuration:
Frontend Property Tests:
Backend Property Tests:
Integration Property Tests:
Test Tag Format:
// Frontend example
test('Feature: annotation-platform, Property 2: Empty project name rejection', () => {
fc.assert(
fc.property(fc.string(), (name) => {
// Test that whitespace-only names are rejected
})
);
});
# Backend example
@given(st.text())
def test_property_12_project_id_validation(project_id):
"""Feature: annotation-platform, Property 12: Project ID validation on task creation"""
# Test that invalid project IDs are rejected
Initialize Nx App
cd web
nx generate @nx/react:application lq_label --style=scss --bundler=webpack
Project Structure
web/apps/lq_label/
├── src/
│ ├── app/
│ │ ├── App.tsx
│ │ └── App.module.scss
│ ├── components/
│ │ ├── Layout/
│ │ ├── ProjectForm/
│ │ └── TaskForm/
│ ├── views/
│ │ ├── ProjectListView/
│ │ ├── ProjectDetailView/
│ │ ├── TaskListView/
│ │ └── AnnotationView/
│ ├── atoms/
│ │ ├── projectAtoms.ts
│ │ ├── taskAtoms.ts
│ │ └── annotationAtoms.ts
│ ├── services/
│ │ └── api.ts
│ ├── utils/
│ │ └── helpers.ts
│ ├── main.tsx
│ └── index.html
Key Dependencies
Styling Approach
Project Structure
backend/
├── main.py
├── database.py
├── models.py
├── routers/
│ ├── project.py
│ ├── task.py
│ └── annotation.py
├── services/
│ ├── project_service.py
│ ├── task_service.py
│ └── annotation_service.py
├── schemas/
│ ├── project.py
│ ├── task.py
│ └── annotation.py
└── requirements.txt
Key Dependencies
Database Initialization
CORS Configuration
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:4200"], # Frontend dev server
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Dynamic Import
Instance Lifecycle
Config Loading
Annotation Serialization
nx build lq_label --proddist/apps/lq_labeluvicorn main:app --host 0.0.0.0 --port 8000