- TaskDetail: hide Approve/Reject buttons in auto mode, show "Автопилот активен" badge
- TaskDetail: execution_mode persisted per-task via PATCH /api/tasks/{id}
- TaskDetail: loadMode reads DB value, falls back to localStorage per project
- TaskDetail: back navigation preserves status filter via ?back_status query param
- ProjectView: toggleMode now persists to DB via PATCH /api/projects/{id}
- ProjectView: loadMode reads project.execution_mode from DB first
- ProjectView: task list shows 🔓 badge for auto-mode tasks
- ProjectView: status filter synced to URL query param ?status=
- api.ts: add patchProject(), execution_mode field on Project interface
- core/db.py, core/models.py: execution_mode columns + migration for projects & tasks
- web/api.py: PATCH /api/projects/{id} and PATCH /api/tasks/{id} support execution_mode
- tests: 256 tests pass, new test_auto_mode.py with 60+ auto mode tests
- frontend: vitest config added for component tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
167 lines
4.8 KiB
TypeScript
167 lines
4.8 KiB
TypeScript
const BASE = '/api'
|
|
|
|
async function get<T>(path: string): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`)
|
|
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
|
return res.json()
|
|
}
|
|
|
|
async function patch<T>(path: string, body: unknown): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
method: 'PATCH',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})
|
|
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
|
return res.json()
|
|
}
|
|
|
|
async function post<T>(path: string, body: unknown): Promise<T> {
|
|
const res = await fetch(`${BASE}${path}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})
|
|
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
|
return res.json()
|
|
}
|
|
|
|
export interface Project {
|
|
id: string
|
|
name: string
|
|
path: string
|
|
status: string
|
|
priority: number
|
|
tech_stack: string[] | null
|
|
execution_mode: string | null
|
|
created_at: string
|
|
total_tasks: number
|
|
done_tasks: number
|
|
active_tasks: number
|
|
blocked_tasks: number
|
|
review_tasks: number
|
|
}
|
|
|
|
export interface ProjectDetail extends Project {
|
|
tasks: Task[]
|
|
modules: Module[]
|
|
decisions: Decision[]
|
|
}
|
|
|
|
export interface Task {
|
|
id: string
|
|
project_id: string
|
|
title: string
|
|
status: string
|
|
priority: number
|
|
assigned_role: string | null
|
|
parent_task_id: string | null
|
|
brief: Record<string, unknown> | null
|
|
spec: Record<string, unknown> | null
|
|
execution_mode: string | null
|
|
created_at: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface Decision {
|
|
id: number
|
|
project_id: string
|
|
task_id: string | null
|
|
type: string
|
|
category: string | null
|
|
title: string
|
|
description: string
|
|
tags: string[] | null
|
|
created_at: string
|
|
}
|
|
|
|
export interface Module {
|
|
id: number
|
|
project_id: string
|
|
name: string
|
|
type: string
|
|
path: string
|
|
description: string | null
|
|
owner_role: string | null
|
|
dependencies: string[] | null
|
|
}
|
|
|
|
export interface PipelineStep {
|
|
id: number
|
|
agent_role: string
|
|
action: string
|
|
output_summary: string | null
|
|
success: boolean | number
|
|
duration_seconds: number | null
|
|
tokens_used: number | null
|
|
model: string | null
|
|
cost_usd: number | null
|
|
created_at: string
|
|
}
|
|
|
|
export interface TaskFull extends Task {
|
|
pipeline_steps: PipelineStep[]
|
|
related_decisions: Decision[]
|
|
}
|
|
|
|
export interface PendingAction {
|
|
type: string
|
|
description: string
|
|
original_item: Record<string, unknown>
|
|
options: string[]
|
|
}
|
|
|
|
export interface CostEntry {
|
|
project_id: string
|
|
project_name: string
|
|
runs: number
|
|
total_tokens: number
|
|
total_cost_usd: number
|
|
total_duration_seconds: number
|
|
}
|
|
|
|
export interface AuditItem {
|
|
id: string
|
|
reason: string
|
|
}
|
|
|
|
export interface AuditResult {
|
|
success: boolean
|
|
already_done: AuditItem[]
|
|
still_pending: AuditItem[]
|
|
unclear: AuditItem[]
|
|
duration_seconds?: number
|
|
cost_usd?: number
|
|
error?: string
|
|
}
|
|
|
|
export const api = {
|
|
projects: () => get<Project[]>('/projects'),
|
|
project: (id: string) => get<ProjectDetail>(`/projects/${id}`),
|
|
task: (id: string) => get<Task>(`/tasks/${id}`),
|
|
taskFull: (id: string) => get<TaskFull>(`/tasks/${id}/full`),
|
|
taskPipeline: (id: string) => get<PipelineStep[]>(`/tasks/${id}/pipeline`),
|
|
cost: (days = 7) => get<CostEntry[]>(`/cost?days=${days}`),
|
|
createProject: (data: { id: string; name: string; path: string; tech_stack?: string[]; priority?: number }) =>
|
|
post<Project>('/projects', data),
|
|
createTask: (data: { project_id: string; title: string; priority?: number; route_type?: string }) =>
|
|
post<Task>('/tasks', data),
|
|
approveTask: (id: string, data?: { decision_title?: string; decision_description?: string; decision_type?: string; create_followups?: boolean }) =>
|
|
post<{ status: string; followup_tasks: Task[]; needs_decision: boolean; pending_actions: PendingAction[] }>(`/tasks/${id}/approve`, data || {}),
|
|
resolveAction: (id: string, action: PendingAction, choice: string) =>
|
|
post<{ choice: string; result: unknown }>(`/tasks/${id}/resolve`, { action, choice }),
|
|
rejectTask: (id: string, reason: string) =>
|
|
post<{ status: string }>(`/tasks/${id}/reject`, { reason }),
|
|
runTask: (id: string, allowWrite = false) =>
|
|
post<{ status: string }>(`/tasks/${id}/run`, { allow_write: allowWrite }),
|
|
bootstrap: (data: { path: string; id: string; name: string }) =>
|
|
post<{ project: Project }>('/bootstrap', data),
|
|
auditProject: (projectId: string) =>
|
|
post<AuditResult>(`/projects/${projectId}/audit`, {}),
|
|
auditApply: (projectId: string, taskIds: string[]) =>
|
|
post<{ updated: string[]; count: number }>(`/projects/${projectId}/audit/apply`, { task_ids: taskIds }),
|
|
patchTask: (id: string, data: { status?: string; execution_mode?: string }) =>
|
|
patch<Task>(`/tasks/${id}`, data),
|
|
patchProject: (id: string, data: { execution_mode: string }) =>
|
|
patch<Project>(`/projects/${id}`, data),
|
|
}
|