Complete implementation across all 13 phases: - vault-core: types, YAML frontmatter parsing, entity classification, filesystem ops, config, prompt composition, validation, search - vault-watch: filesystem watcher with daemon write filtering, event classification - vault-scheduler: cron engine, process executor, task runner with retry logic and concurrency limiting - vault-api: Axum REST API (15 route modules), WebSocket with broadcast, AI assistant proxy, validation, templates - Dashboard: React + TypeScript + Tailwind v4 with kanban, CodeMirror editor, dynamic view system, AI chat sidebar - Nix flake with dev shell and NixOS module - Graceful shutdown, inotify overflow recovery, tracing instrumentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
import { useAgentTasks } from '../hooks/useApi';
|
|
import { StatusBadge } from '../components/StatusBadge';
|
|
import type { AgentTask, AgentTaskStatus } from '../api/types';
|
|
|
|
const COLUMNS: { id: AgentTaskStatus; label: string }[] = [
|
|
{ id: 'queued', label: 'Queued' },
|
|
{ id: 'running', label: 'Running' },
|
|
{ id: 'done', label: 'Done' },
|
|
{ id: 'failed', label: 'Failed' },
|
|
];
|
|
|
|
export function AgentQueuePage() {
|
|
const { data: tasks, isLoading } = useAgentTasks();
|
|
|
|
const byStatus = (s: AgentTaskStatus) => (tasks || []).filter((t) => t.status === s);
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<h1 className="mb-4 text-lg font-semibold">Agent Queue</h1>
|
|
|
|
{isLoading ? (
|
|
<div className="text-text-muted">Loading...</div>
|
|
) : (
|
|
<div className="flex gap-4">
|
|
{COLUMNS.map((col) => {
|
|
const items = byStatus(col.id);
|
|
return (
|
|
<div key={col.id} className="w-72 shrink-0">
|
|
<div className="mb-2 flex items-center justify-between px-1">
|
|
<h3 className="text-sm font-semibold text-text-secondary">{col.label}</h3>
|
|
<span className="text-xs text-text-muted">{items.length}</span>
|
|
</div>
|
|
<div className="space-y-2 rounded-lg border border-border bg-surface p-2">
|
|
{items.length === 0 ? (
|
|
<div className="py-4 text-center text-xs text-text-muted">Empty</div>
|
|
) : (
|
|
items.map((task) => <AgentTaskCard key={task.id} task={task} />)
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function AgentTaskCard({ task }: { task: AgentTask }) {
|
|
return (
|
|
<div className="rounded-lg border border-border bg-surface-raised p-3">
|
|
<div className="mb-1 text-sm font-medium text-text-primary">{task.title}</div>
|
|
<div className="flex items-center gap-2 text-xs text-text-secondary">
|
|
<span>{task.agent}</span>
|
|
<StatusBadge value={task.priority} />
|
|
{task.type && <span className="text-text-muted">{task.type}</span>}
|
|
</div>
|
|
{task.error && (
|
|
<div className="mt-2 truncate rounded bg-danger/10 px-2 py-1 text-xs text-danger">
|
|
{task.error}
|
|
</div>
|
|
)}
|
|
{task.started && (
|
|
<div className="mt-1 text-xs text-text-muted">
|
|
started: {new Date(task.started).toLocaleTimeString()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|