kin/web/frontend/src/api.ts
johnfrum1234 38c252fc1b Add task detail view, pipeline visualization, approve/reject workflow
API (web/api.py) — 5 new endpoints:
  GET  /api/tasks/{id}/pipeline — agent_logs as pipeline steps
  GET  /api/tasks/{id}/full — task + steps + related decisions
  POST /api/tasks/{id}/approve — mark done, optionally add decision
  POST /api/tasks/{id}/reject — return to pending with reason
  POST /api/tasks/{id}/run — launch pipeline in background (202)

Frontend:
  TaskDetail (/task/:id) — full task page with:
    - Pipeline graph: role cards with icons, arrows, status colors
    - Click step → expand output (pre-formatted, JSON detected)
    - Action bar: Approve (with optional decision), Reject, Run Pipeline
    - Polling for live pipeline updates
  Dashboard: review_tasks badge ("awaiting review" in yellow)
  ProjectView: task rows are now clickable links to /task/:id

Runner: output_summary no longer truncated (full output for GUI).
Models: get_project_summary includes review_tasks count.

13 new API tests, 105 total, all passing. Frontend builds clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:32:29 +02:00

123 lines
3.3 KiB
TypeScript

const BASE = 'http://localhost:8420/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 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
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
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 CostEntry {
project_id: string
project_name: string
runs: number
total_tokens: number
total_cost_usd: number
total_duration_seconds: number
}
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 }) =>
post<{ status: string }>(`/tasks/${id}/approve`, data || {}),
rejectTask: (id: string, reason: string) =>
post<{ status: string }>(`/tasks/${id}/reject`, { reason }),
runTask: (id: string) =>
post<{ status: string }>(`/tasks/${id}/run`, {}),
bootstrap: (data: { path: string; id: string; name: string }) =>
post<{ project: Project }>('/bootstrap', data),
}