Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
<script setup lang="ts">
|
2026-03-15 20:02:01 +02:00
|
|
|
import { ref, onMounted, computed, watch } from 'vue'
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
2026-03-15 17:44:16 +02:00
|
|
|
import { api, type ProjectDetail, type AuditResult } from '../api'
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
import Badge from '../components/Badge.vue'
|
|
|
|
|
import Modal from '../components/Modal.vue'
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{ id: string }>()
|
2026-03-15 20:02:01 +02:00
|
|
|
const route = useRoute()
|
|
|
|
|
const router = useRouter()
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
|
|
|
|
|
const project = ref<ProjectDetail | null>(null)
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
const error = ref('')
|
|
|
|
|
const activeTab = ref<'tasks' | 'decisions' | 'modules'>('tasks')
|
|
|
|
|
|
|
|
|
|
// Filters
|
2026-03-15 23:22:49 +02:00
|
|
|
const ALL_TASK_STATUSES = ['pending', 'in_progress', 'review', 'blocked', 'decomposed', 'done', 'cancelled']
|
|
|
|
|
|
|
|
|
|
function initStatusFilter(): string[] {
|
|
|
|
|
const q = route.query.status as string
|
|
|
|
|
if (q) return q.split(',').filter((s: string) => s)
|
|
|
|
|
const stored = localStorage.getItem(`kin-task-statuses-${props.id}`)
|
|
|
|
|
if (stored) { try { return JSON.parse(stored) } catch {} }
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectedStatuses = ref<string[]>(initStatusFilter())
|
|
|
|
|
|
|
|
|
|
function toggleStatus(s: string) {
|
|
|
|
|
const idx = selectedStatuses.value.indexOf(s)
|
|
|
|
|
if (idx >= 0) selectedStatuses.value.splice(idx, 1)
|
|
|
|
|
else selectedStatuses.value.push(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearStatusFilter() {
|
|
|
|
|
selectedStatuses.value = []
|
|
|
|
|
}
|
|
|
|
|
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
const decisionTypeFilter = ref('')
|
|
|
|
|
const decisionSearch = ref('')
|
|
|
|
|
|
2026-03-15 17:35:08 +02:00
|
|
|
// Auto/Review mode (persisted per project)
|
|
|
|
|
const autoMode = ref(false)
|
|
|
|
|
|
|
|
|
|
function loadMode() {
|
2026-03-15 20:02:01 +02:00
|
|
|
if (project.value?.execution_mode) {
|
|
|
|
|
autoMode.value = project.value.execution_mode === 'auto'
|
|
|
|
|
} else {
|
|
|
|
|
autoMode.value = localStorage.getItem(`kin-mode-${props.id}`) === 'auto'
|
|
|
|
|
}
|
2026-03-15 17:35:08 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-15 20:02:01 +02:00
|
|
|
async function toggleMode() {
|
2026-03-15 17:35:08 +02:00
|
|
|
autoMode.value = !autoMode.value
|
|
|
|
|
localStorage.setItem(`kin-mode-${props.id}`, autoMode.value ? 'auto' : 'review')
|
2026-03-15 20:02:01 +02:00
|
|
|
try {
|
|
|
|
|
await api.patchProject(props.id, { execution_mode: autoMode.value ? 'auto' : 'review' })
|
|
|
|
|
if (project.value) project.value = { ...project.value, execution_mode: autoMode.value ? 'auto' : 'review' }
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
error.value = e.message
|
|
|
|
|
}
|
2026-03-15 17:35:08 +02:00
|
|
|
}
|
|
|
|
|
|
2026-03-15 17:44:16 +02:00
|
|
|
// Audit
|
|
|
|
|
const auditLoading = ref(false)
|
|
|
|
|
const auditResult = ref<AuditResult | null>(null)
|
|
|
|
|
const showAuditModal = ref(false)
|
|
|
|
|
const auditApplying = ref(false)
|
|
|
|
|
|
|
|
|
|
async function runAudit() {
|
|
|
|
|
auditLoading.value = true
|
|
|
|
|
auditResult.value = null
|
|
|
|
|
try {
|
|
|
|
|
const res = await api.auditProject(props.id)
|
|
|
|
|
auditResult.value = res
|
|
|
|
|
showAuditModal.value = true
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
error.value = e.message
|
|
|
|
|
} finally {
|
|
|
|
|
auditLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function applyAudit() {
|
|
|
|
|
if (!auditResult.value?.already_done?.length) return
|
|
|
|
|
auditApplying.value = true
|
|
|
|
|
try {
|
|
|
|
|
const ids = auditResult.value.already_done.map(t => t.id)
|
|
|
|
|
await api.auditApply(props.id, ids)
|
|
|
|
|
showAuditModal.value = false
|
|
|
|
|
auditResult.value = null
|
|
|
|
|
await load()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
error.value = e.message
|
|
|
|
|
} finally {
|
|
|
|
|
auditApplying.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
// Add task modal
|
|
|
|
|
const showAddTask = ref(false)
|
|
|
|
|
const taskForm = ref({ title: '', priority: 5, route_type: '' })
|
|
|
|
|
const taskFormError = ref('')
|
|
|
|
|
|
|
|
|
|
// Add decision modal
|
|
|
|
|
const showAddDecision = ref(false)
|
|
|
|
|
const decForm = ref({ type: 'decision', title: '', description: '', category: '', tags: '' })
|
|
|
|
|
const decFormError = ref('')
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
try {
|
|
|
|
|
loading.value = true
|
|
|
|
|
project.value = await api.project(props.id)
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
error.value = e.message
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 23:22:49 +02:00
|
|
|
watch(selectedStatuses, (val) => {
|
|
|
|
|
localStorage.setItem(`kin-task-statuses-${props.id}`, JSON.stringify(val))
|
|
|
|
|
router.replace({ query: { ...route.query, status: val.length ? val.join(',') : undefined } })
|
|
|
|
|
}, { deep: true })
|
2026-03-15 20:02:01 +02:00
|
|
|
|
2026-03-15 17:35:08 +02:00
|
|
|
onMounted(() => { load(); loadMode() })
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
|
|
|
|
|
const filteredTasks = computed(() => {
|
|
|
|
|
if (!project.value) return []
|
|
|
|
|
let tasks = project.value.tasks
|
2026-03-15 23:22:49 +02:00
|
|
|
if (selectedStatuses.value.length > 0) tasks = tasks.filter(t => selectedStatuses.value.includes(t.status))
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
return tasks
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const filteredDecisions = computed(() => {
|
|
|
|
|
if (!project.value) return []
|
|
|
|
|
let decs = project.value.decisions
|
|
|
|
|
if (decisionTypeFilter.value) decs = decs.filter(d => d.type === decisionTypeFilter.value)
|
|
|
|
|
if (decisionSearch.value) {
|
|
|
|
|
const q = decisionSearch.value.toLowerCase()
|
|
|
|
|
decs = decs.filter(d => d.title.toLowerCase().includes(q) || d.description.toLowerCase().includes(q))
|
|
|
|
|
}
|
|
|
|
|
return decs
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function taskStatusColor(s: string) {
|
|
|
|
|
const m: Record<string, string> = {
|
|
|
|
|
pending: 'gray', in_progress: 'blue', review: 'purple',
|
2026-03-15 18:22:17 +02:00
|
|
|
done: 'green', blocked: 'red', decomposed: 'yellow', cancelled: 'gray',
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
}
|
|
|
|
|
return m[s] || 'gray'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function decTypeColor(t: string) {
|
|
|
|
|
const m: Record<string, string> = {
|
|
|
|
|
decision: 'blue', gotcha: 'red', workaround: 'yellow',
|
|
|
|
|
rejected_approach: 'gray', convention: 'purple',
|
|
|
|
|
}
|
|
|
|
|
return m[t] || 'gray'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function modTypeColor(t: string) {
|
|
|
|
|
const m: Record<string, string> = {
|
|
|
|
|
frontend: 'blue', backend: 'green', shared: 'purple', infra: 'orange',
|
|
|
|
|
}
|
|
|
|
|
return m[t] || 'gray'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const decTypes = computed(() => {
|
|
|
|
|
if (!project.value) return []
|
|
|
|
|
const s = new Set(project.value.decisions.map(d => d.type))
|
|
|
|
|
return Array.from(s).sort()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function addTask() {
|
|
|
|
|
taskFormError.value = ''
|
|
|
|
|
try {
|
|
|
|
|
await api.createTask({
|
|
|
|
|
project_id: props.id,
|
|
|
|
|
title: taskForm.value.title,
|
|
|
|
|
priority: taskForm.value.priority,
|
|
|
|
|
route_type: taskForm.value.route_type || undefined,
|
|
|
|
|
})
|
|
|
|
|
showAddTask.value = false
|
|
|
|
|
taskForm.value = { title: '', priority: 5, route_type: '' }
|
|
|
|
|
await load()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
taskFormError.value = e.message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 15:29:05 +02:00
|
|
|
async function runTask(taskId: string, event: Event) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
event.stopPropagation()
|
|
|
|
|
if (!confirm(`Run pipeline for ${taskId}?`)) return
|
|
|
|
|
try {
|
2026-03-15 23:22:49 +02:00
|
|
|
await api.runTask(taskId)
|
2026-03-15 15:29:05 +02:00
|
|
|
await load()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
error.value = e.message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
async function addDecision() {
|
|
|
|
|
decFormError.value = ''
|
|
|
|
|
try {
|
|
|
|
|
const tags = decForm.value.tags ? decForm.value.tags.split(',').map(s => s.trim()).filter(Boolean) : undefined
|
|
|
|
|
const body = {
|
|
|
|
|
project_id: props.id,
|
|
|
|
|
type: decForm.value.type,
|
|
|
|
|
title: decForm.value.title,
|
|
|
|
|
description: decForm.value.description,
|
|
|
|
|
category: decForm.value.category || undefined,
|
|
|
|
|
tags,
|
|
|
|
|
}
|
2026-03-15 17:13:37 +02:00
|
|
|
const res = await fetch('/api/decisions', {
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify(body),
|
|
|
|
|
})
|
|
|
|
|
if (!res.ok) throw new Error('Failed')
|
|
|
|
|
showAddDecision.value = false
|
|
|
|
|
decForm.value = { type: 'decision', title: '', description: '', category: '', tags: '' }
|
|
|
|
|
await load()
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
decFormError.value = e.message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div v-if="loading" class="text-gray-500 text-sm">Loading...</div>
|
|
|
|
|
<div v-else-if="error" class="text-red-400 text-sm">{{ error }}</div>
|
|
|
|
|
<div v-else-if="project">
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<div class="flex items-center gap-2 mb-1">
|
|
|
|
|
<router-link to="/" class="text-gray-600 hover:text-gray-400 text-sm no-underline">← back</router-link>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-3 mb-2">
|
|
|
|
|
<h1 class="text-xl font-bold text-gray-100">{{ project.id }}</h1>
|
|
|
|
|
<span class="text-gray-400">{{ project.name }}</span>
|
|
|
|
|
<Badge :text="project.status" :color="project.status === 'active' ? 'green' : 'gray'" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex gap-2 flex-wrap mb-2" v-if="project.tech_stack?.length">
|
|
|
|
|
<Badge v-for="t in project.tech_stack" :key="t" :text="t" color="purple" />
|
|
|
|
|
</div>
|
|
|
|
|
<p class="text-xs text-gray-600">{{ project.path }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Tabs -->
|
|
|
|
|
<div class="flex gap-1 mb-4 border-b border-gray-800">
|
|
|
|
|
<button v-for="tab in (['tasks', 'decisions', 'modules'] as const)" :key="tab"
|
|
|
|
|
@click="activeTab = tab"
|
|
|
|
|
class="px-4 py-2 text-sm border-b-2 transition-colors"
|
|
|
|
|
:class="activeTab === tab
|
|
|
|
|
? 'text-gray-200 border-blue-500'
|
|
|
|
|
: 'text-gray-500 border-transparent hover:text-gray-300'">
|
|
|
|
|
{{ tab.charAt(0).toUpperCase() + tab.slice(1) }}
|
|
|
|
|
<span class="text-xs text-gray-600 ml-1">
|
|
|
|
|
{{ tab === 'tasks' ? project.tasks.length
|
|
|
|
|
: tab === 'decisions' ? project.decisions.length
|
|
|
|
|
: project.modules.length }}
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Tasks Tab -->
|
|
|
|
|
<div v-if="activeTab === 'tasks'">
|
|
|
|
|
<div class="flex items-center justify-between mb-3">
|
2026-03-15 23:22:49 +02:00
|
|
|
<div class="flex gap-1 flex-wrap items-center">
|
|
|
|
|
<button v-for="s in ALL_TASK_STATUSES" :key="s"
|
|
|
|
|
:data-status="s"
|
|
|
|
|
@click="toggleStatus(s)"
|
|
|
|
|
class="px-2 py-0.5 text-xs rounded border transition-colors"
|
|
|
|
|
:class="selectedStatuses.includes(s)
|
|
|
|
|
? 'bg-blue-900/40 text-blue-300 border-blue-700'
|
|
|
|
|
: 'bg-gray-900 text-gray-600 border-gray-800 hover:text-gray-400 hover:border-gray-700'"
|
|
|
|
|
>{{ s }}</button>
|
|
|
|
|
<button v-if="selectedStatuses.length" data-action="clear-status" @click="clearStatusFilter"
|
|
|
|
|
class="px-1.5 py-0.5 text-xs text-gray-600 hover:text-red-400 rounded">✕</button>
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
</div>
|
2026-03-15 17:35:08 +02:00
|
|
|
<div class="flex gap-2">
|
|
|
|
|
<button @click="toggleMode"
|
|
|
|
|
class="px-2 py-1 text-xs border rounded transition-colors"
|
|
|
|
|
:class="autoMode
|
|
|
|
|
? 'bg-yellow-900/30 text-yellow-400 border-yellow-800 hover:bg-yellow-900/50'
|
|
|
|
|
: 'bg-gray-800/50 text-gray-400 border-gray-700 hover:bg-gray-800'"
|
|
|
|
|
:title="autoMode ? 'Auto mode: agents can write files' : 'Review mode: agents read-only'">
|
|
|
|
|
{{ autoMode ? '🔓 Auto' : '🔒 Review' }}
|
|
|
|
|
</button>
|
2026-03-15 17:44:16 +02:00
|
|
|
<button @click="runAudit" :disabled="auditLoading"
|
|
|
|
|
class="px-2 py-1 text-xs bg-purple-900/30 text-purple-400 border border-purple-800 rounded hover:bg-purple-900/50 disabled:opacity-50"
|
|
|
|
|
title="Check which pending tasks are already done">
|
|
|
|
|
<span v-if="auditLoading" class="inline-block w-3 h-3 border-2 border-purple-400 border-t-transparent rounded-full animate-spin mr-1"></span>
|
|
|
|
|
{{ auditLoading ? 'Auditing...' : 'Audit backlog' }}
|
|
|
|
|
</button>
|
2026-03-15 17:35:08 +02:00
|
|
|
<button @click="showAddTask = true"
|
|
|
|
|
class="px-3 py-1 text-xs bg-gray-800 text-gray-300 border border-gray-700 rounded hover:bg-gray-700">
|
|
|
|
|
+ Task
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
</div>
|
|
|
|
|
<div v-if="filteredTasks.length === 0" class="text-gray-600 text-sm">No tasks.</div>
|
|
|
|
|
<div v-else class="space-y-1">
|
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
|
|
|
<router-link v-for="t in filteredTasks" :key="t.id"
|
2026-03-15 23:22:49 +02:00
|
|
|
:to="{ path: `/task/${t.id}`, query: selectedStatuses.length ? { back_status: selectedStatuses.join(',') } : undefined }"
|
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
|
|
|
class="flex items-center justify-between px-3 py-2 border border-gray-800 rounded text-sm hover:border-gray-600 no-underline block transition-colors">
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
|
|
|
<span class="text-gray-500 shrink-0 w-24">{{ t.id }}</span>
|
|
|
|
|
<Badge :text="t.status" :color="taskStatusColor(t.status)" />
|
|
|
|
|
<span class="text-gray-300 truncate">{{ t.title }}</span>
|
2026-03-15 20:02:01 +02:00
|
|
|
<span v-if="t.execution_mode === 'auto'"
|
|
|
|
|
class="text-[10px] px-1 py-0.5 bg-yellow-900/40 text-yellow-400 border border-yellow-800 rounded shrink-0"
|
|
|
|
|
title="Auto mode">🔓</span>
|
Add follow-up task generation on approve
When approving a task, PM agent analyzes pipeline output and creates
follow-up tasks automatically (e.g. security audit → 8 fix tasks).
core/followup.py:
generate_followups() — collects pipeline output, runs followup agent,
parses JSON task list, creates tasks with parent_task_id linkage.
Handles: bare arrays, {tasks:[...]} wrappers, invalid JSON, empty.
agents/prompts/followup.md — PM prompt for analyzing results and
creating actionable follow-up tasks with priority from severity.
CLI: kin approve <task_id> [--followup] [--decision "text"]
API: POST /api/tasks/{id}/approve {create_followups: true}
Returns {status, decision, followup_tasks: [...]}
Frontend (TaskDetail approve modal):
- Checkbox "Create follow-up tasks" (default ON)
- Loading state during generation
- Results view: list of created tasks with links to /task/:id
ProjectView: tasks show "from VDOL-001" for follow-ups.
13 new tests (followup), 125 total, all passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 15:02:58 +02:00
|
|
|
<span v-if="t.parent_task_id" class="text-[10px] text-gray-600 shrink-0">from {{ t.parent_task_id }}</span>
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-2 text-xs text-gray-600 shrink-0">
|
|
|
|
|
<span v-if="t.assigned_role">{{ t.assigned_role }}</span>
|
|
|
|
|
<span>pri {{ t.priority }}</span>
|
2026-03-15 15:29:05 +02:00
|
|
|
<button v-if="t.status === 'pending'"
|
|
|
|
|
@click="runTask(t.id, $event)"
|
|
|
|
|
class="px-2 py-0.5 bg-blue-900/40 text-blue-400 border border-blue-800 rounded hover:bg-blue-900 text-[10px]"
|
|
|
|
|
title="Run pipeline">▶</button>
|
|
|
|
|
<span v-if="t.status === 'in_progress'"
|
|
|
|
|
class="inline-block w-2 h-2 bg-blue-500 rounded-full animate-pulse" title="Running"></span>
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
</div>
|
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
|
|
|
</router-link>
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Decisions Tab -->
|
|
|
|
|
<div v-if="activeTab === 'decisions'">
|
|
|
|
|
<div class="flex items-center justify-between mb-3">
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
<select v-model="decisionTypeFilter"
|
|
|
|
|
class="bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-300">
|
|
|
|
|
<option value="">All types</option>
|
|
|
|
|
<option v-for="t in decTypes" :key="t" :value="t">{{ t }}</option>
|
|
|
|
|
</select>
|
|
|
|
|
<input v-model="decisionSearch" placeholder="Search..."
|
|
|
|
|
class="bg-gray-800 border border-gray-700 rounded px-2 py-1 text-xs text-gray-300 placeholder-gray-600 w-48" />
|
|
|
|
|
</div>
|
|
|
|
|
<button @click="showAddDecision = true"
|
|
|
|
|
class="px-3 py-1 text-xs bg-gray-800 text-gray-300 border border-gray-700 rounded hover:bg-gray-700">
|
|
|
|
|
+ Decision
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="filteredDecisions.length === 0" class="text-gray-600 text-sm">No decisions.</div>
|
|
|
|
|
<div v-else class="space-y-2">
|
|
|
|
|
<div v-for="d in filteredDecisions" :key="d.id"
|
|
|
|
|
class="px-3 py-2 border border-gray-800 rounded hover:border-gray-700">
|
|
|
|
|
<div class="flex items-center gap-2 mb-1">
|
|
|
|
|
<span class="text-gray-600 text-xs">#{{ d.id }}</span>
|
|
|
|
|
<Badge :text="d.type" :color="decTypeColor(d.type)" />
|
|
|
|
|
<Badge v-if="d.category" :text="d.category" color="gray" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="text-sm text-gray-300">{{ d.title }}</div>
|
|
|
|
|
<div v-if="d.description !== d.title" class="text-xs text-gray-500 mt-1">{{ d.description }}</div>
|
|
|
|
|
<div v-if="d.tags?.length" class="flex gap-1 mt-1">
|
|
|
|
|
<Badge v-for="tag in d.tags" :key="tag" :text="tag" color="purple" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Modules Tab -->
|
|
|
|
|
<div v-if="activeTab === 'modules'">
|
|
|
|
|
<div v-if="project.modules.length === 0" class="text-gray-600 text-sm">No modules.</div>
|
|
|
|
|
<div v-else class="space-y-1">
|
|
|
|
|
<div v-for="m in project.modules" :key="m.id"
|
|
|
|
|
class="flex items-center justify-between px-3 py-2 border border-gray-800 rounded text-sm hover:border-gray-700">
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-gray-300 font-medium">{{ m.name }}</span>
|
|
|
|
|
<Badge :text="m.type" :color="modTypeColor(m.type)" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center gap-3 text-xs text-gray-600">
|
|
|
|
|
<span>{{ m.path }}</span>
|
|
|
|
|
<span v-if="m.owner_role">{{ m.owner_role }}</span>
|
|
|
|
|
<span v-if="m.description">{{ m.description }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Add Task Modal -->
|
|
|
|
|
<Modal v-if="showAddTask" title="Add Task" @close="showAddTask = false">
|
|
|
|
|
<form @submit.prevent="addTask" class="space-y-3">
|
|
|
|
|
<input v-model="taskForm.title" placeholder="Task title" required
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 placeholder-gray-600" />
|
|
|
|
|
<select v-model="taskForm.route_type"
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-300">
|
|
|
|
|
<option value="">No type</option>
|
|
|
|
|
<option value="debug">debug</option>
|
|
|
|
|
<option value="feature">feature</option>
|
|
|
|
|
<option value="refactor">refactor</option>
|
|
|
|
|
<option value="hotfix">hotfix</option>
|
|
|
|
|
</select>
|
|
|
|
|
<input v-model.number="taskForm.priority" type="number" min="1" max="10" placeholder="Priority"
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 placeholder-gray-600" />
|
|
|
|
|
<p v-if="taskFormError" class="text-red-400 text-xs">{{ taskFormError }}</p>
|
|
|
|
|
<button type="submit"
|
|
|
|
|
class="w-full py-2 bg-blue-900/50 text-blue-400 border border-blue-800 rounded text-sm hover:bg-blue-900">
|
|
|
|
|
Create
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
<!-- Add Decision Modal -->
|
|
|
|
|
<Modal v-if="showAddDecision" title="Add Decision" @close="showAddDecision = false">
|
|
|
|
|
<form @submit.prevent="addDecision" class="space-y-3">
|
|
|
|
|
<select v-model="decForm.type" required
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-300">
|
|
|
|
|
<option value="decision">decision</option>
|
|
|
|
|
<option value="gotcha">gotcha</option>
|
|
|
|
|
<option value="workaround">workaround</option>
|
|
|
|
|
<option value="convention">convention</option>
|
|
|
|
|
<option value="rejected_approach">rejected_approach</option>
|
|
|
|
|
</select>
|
|
|
|
|
<input v-model="decForm.title" placeholder="Title" required
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 placeholder-gray-600" />
|
|
|
|
|
<textarea v-model="decForm.description" placeholder="Description" rows="3" required
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 placeholder-gray-600 resize-y"></textarea>
|
|
|
|
|
<input v-model="decForm.category" placeholder="Category (e.g. ui, api, security)"
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 placeholder-gray-600" />
|
|
|
|
|
<input v-model="decForm.tags" placeholder="Tags (comma-separated)"
|
|
|
|
|
class="w-full bg-gray-800 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 placeholder-gray-600" />
|
|
|
|
|
<p v-if="decFormError" class="text-red-400 text-xs">{{ decFormError }}</p>
|
|
|
|
|
<button type="submit"
|
|
|
|
|
class="w-full py-2 bg-blue-900/50 text-blue-400 border border-blue-800 rounded text-sm hover:bg-blue-900">
|
|
|
|
|
Create
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</Modal>
|
2026-03-15 17:44:16 +02:00
|
|
|
|
|
|
|
|
<!-- Audit Modal -->
|
|
|
|
|
<Modal v-if="showAuditModal && auditResult" title="Backlog Audit Results" @close="showAuditModal = false">
|
|
|
|
|
<div v-if="!auditResult.success" class="text-red-400 text-sm">
|
|
|
|
|
Audit failed: {{ auditResult.error }}
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="space-y-4">
|
|
|
|
|
<div v-if="auditResult.already_done?.length">
|
|
|
|
|
<h3 class="text-sm font-semibold text-green-400 mb-2">Already done ({{ auditResult.already_done.length }})</h3>
|
|
|
|
|
<div v-for="item in auditResult.already_done" :key="item.id"
|
|
|
|
|
class="px-3 py-2 border border-green-900/50 rounded text-xs mb-1">
|
|
|
|
|
<span class="text-green-400 font-medium">{{ item.id }}</span>
|
|
|
|
|
<span class="text-gray-400 ml-2">{{ item.reason }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="auditResult.still_pending?.length">
|
|
|
|
|
<h3 class="text-sm font-semibold text-gray-400 mb-2">Still pending ({{ auditResult.still_pending.length }})</h3>
|
|
|
|
|
<div v-for="item in auditResult.still_pending" :key="item.id"
|
|
|
|
|
class="px-3 py-2 border border-gray-800 rounded text-xs mb-1">
|
|
|
|
|
<span class="text-gray-300 font-medium">{{ item.id }}</span>
|
|
|
|
|
<span class="text-gray-500 ml-2">{{ item.reason }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="auditResult.unclear?.length">
|
|
|
|
|
<h3 class="text-sm font-semibold text-yellow-400 mb-2">Unclear ({{ auditResult.unclear.length }})</h3>
|
|
|
|
|
<div v-for="item in auditResult.unclear" :key="item.id"
|
|
|
|
|
class="px-3 py-2 border border-yellow-900/50 rounded text-xs mb-1">
|
|
|
|
|
<span class="text-yellow-400 font-medium">{{ item.id }}</span>
|
|
|
|
|
<span class="text-gray-400 ml-2">{{ item.reason }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="auditResult.cost_usd || auditResult.duration_seconds" class="text-xs text-gray-600">
|
|
|
|
|
<span v-if="auditResult.duration_seconds">{{ auditResult.duration_seconds }}s</span>
|
|
|
|
|
<span v-if="auditResult.cost_usd" class="ml-2">${{ auditResult.cost_usd?.toFixed(4) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<button v-if="auditResult.already_done?.length" @click="applyAudit" :disabled="auditApplying"
|
|
|
|
|
class="w-full py-2 bg-green-900/50 text-green-400 border border-green-800 rounded text-sm hover:bg-green-900 disabled:opacity-50">
|
|
|
|
|
{{ auditApplying ? 'Applying...' : `Mark ${auditResult.already_done.length} tasks as done` }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</Modal>
|
Add web GUI: FastAPI API + Vue 3 frontend with dark theme
API (web/api.py):
GET /api/projects, /api/projects/{id}, /api/tasks/{id}
GET /api/decisions?project=X, /api/cost?days=7, /api/support/tickets
POST /api/projects, /api/tasks, /api/decisions, /api/bootstrap
CORS for localhost:5173, all queries via models.py
Frontend (web/frontend/):
Vue 3 + TypeScript + Vite + Tailwind CSS v3
Dashboard: project cards with task counters, cost, status badges
ProjectView: tabs for Tasks/Decisions/Modules with filters
Modals: Add Project, Add Task, Add Decision, Bootstrap
Dark theme, monospace font, minimal clean design
Startup:
API: cd web && uvicorn api:app --reload --port 8420
Web: cd web/frontend && npm install && npm run dev
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:50:15 +02:00
|
|
|
</div>
|
|
|
|
|
</template>
|