chore: install frontend dependencies.

This commit is contained in:
2026-03-12 17:00:42 -04:00
commit 18c8815166
63 changed files with 14100 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
/**
* API client for the Foxy Dev Team backend.
*/
const BASE_URL = '';
// ─── Types ──────────────────────────────────────────────────────────────────
export interface Project {
id: number;
name: string;
slug: string;
description: string;
status: string;
workflow_type: string;
test_mode: boolean;
gitea_repo?: string;
deployment_target?: string;
created_at: string;
updated_at: string;
tasks: Task[];
audit_logs: AuditLog[];
agent_executions: AgentExecution[];
}
export interface ProjectSummary {
id: number;
name: string;
slug: string;
status: string;
workflow_type: string;
test_mode: boolean;
task_count: number;
tasks_done: number;
created_at: string;
updated_at: string;
}
export interface Task {
id: number;
project_id: number;
task_id: string;
type: string;
title: string;
priority: string;
assigned_to?: string;
status: string;
dependencies: string[];
acceptance_criteria?: string;
agent_payloads: Record<string, unknown>;
created_at: string;
updated_at: string;
}
export interface AuditLog {
id: number;
project_id: number;
timestamp: string;
agent: string;
action: string;
target?: string;
message?: string;
source: string;
}
export interface AgentExecution {
id: number;
project_id: number;
agent_name: string;
status: string;
pid?: number;
started_at: string;
finished_at?: string;
exit_code?: number;
error_output?: string;
}
export interface AgentStatus {
name: string;
display_name: string;
model: string;
current_status: string;
current_project?: string;
total_executions: number;
success_count: number;
failure_count: number;
}
export interface WorkflowDef {
type: string;
label: string;
steps: {
agent: string;
label: string;
model: string;
awaiting_status: string;
running_status: string;
}[];
}
export interface AppConfig {
OPENCLAW_WORKSPACE: string;
GITEA_SERVER: string;
DEPLOYMENT_SERVER: string;
DEPLOYMENT_USER: string;
TELEGRAM_CHAT_ID: string;
LOG_LEVEL: string;
GITEA_OPENCLAW_TOKEN: string;
DEPLOYMENT_PWD: string;
TELEGRAM_BOT_TOKEN: string;
}
// ─── HTTP Helpers ───────────────────────────────────────────────────────────
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${BASE_URL}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers,
},
});
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: res.statusText }));
throw new Error(err.detail || `HTTP ${res.status}`);
}
return res.json();
}
// ─── Projects ───────────────────────────────────────────────────────────────
export const api = {
// Projects
listProjects: (params?: Record<string, string>) => {
const qs = params ? '?' + new URLSearchParams(params).toString() : '';
return request<ProjectSummary[]>(`/api/projects${qs}`);
},
getProject: (id: number) => request<Project>(`/api/projects/${id}`),
createProject: (data: { name: string; description: string; workflow_type: string; test_mode?: boolean }) =>
request<Project>('/api/projects', { method: 'POST', body: JSON.stringify(data) }),
startProject: (id: number) => request<{ status: string }>(`/api/projects/${id}/start`, { method: 'POST' }),
pauseProject: (id: number) => request<{ status: string }>(`/api/projects/${id}/pause`, { method: 'POST' }),
stopProject: (id: number) => request<{ status: string }>(`/api/projects/${id}/stop`, { method: 'POST' }),
resetProject: (id: number) => request<{ status: string }>(`/api/projects/${id}/reset`, { method: 'POST' }),
deleteProject: (id: number) => request<{ message: string }>(`/api/projects/${id}`, { method: 'DELETE' }),
getProgress: (id: number) => request<{ current_step: number; total_steps: number; percentage: number }>(`/api/projects/${id}/progress`),
// Agents
listAgents: () => request<AgentStatus[]>('/api/agents'),
getAgentHistory: (name: string) => request<AgentExecution[]>(`/api/agents/${name}/history`),
// Logs
listLogs: (params?: Record<string, string>) => {
const qs = params ? '?' + new URLSearchParams(params).toString() : '';
return request<AuditLog[]>(`/api/logs${qs}`);
},
// Workflows
listWorkflows: () => request<WorkflowDef[]>('/api/workflows'),
// Config
getConfig: () => request<AppConfig>('/api/config'),
updateConfig: (data: Record<string, string>) =>
request<{ message: string }>('/api/config', { method: 'PUT', body: JSON.stringify(data) }),
// Health
health: () => request<{ status: string; version: string }>('/api/health'),
};
+64
View File
@@ -0,0 +1,64 @@
/**
* WebSocket hook for real-time updates from the backend.
*/
import { useEffect, useRef, useCallback, useState } from 'react';
export interface WSMessage {
type: 'agent_status' | 'log' | 'project_update' | 'pong';
data: Record<string, unknown>;
}
type WSCallback = (msg: WSMessage) => void;
export function useWebSocket(onMessage: WSCallback) {
const wsRef = useRef<WebSocket | null>(null);
const [connected, setConnected] = useState(false);
const reconnectTimer = useRef<ReturnType<typeof setTimeout>>();
const connect = useCallback(() => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/ws/live`;
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setConnected(true);
// Start ping interval
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) ws.send('ping');
}, 30000);
ws.addEventListener('close', () => clearInterval(pingInterval));
};
ws.onmessage = (evt) => {
try {
const msg = JSON.parse(evt.data) as WSMessage;
onMessage(msg);
} catch {
// ignore parse errors
}
};
ws.onclose = () => {
setConnected(false);
// Auto-reconnect after 3s
reconnectTimer.current = setTimeout(connect, 3000);
};
ws.onerror = () => {
ws.close();
};
}, [onMessage]);
useEffect(() => {
connect();
return () => {
clearTimeout(reconnectTimer.current);
wsRef.current?.close();
};
}, [connect]);
return { connected };
}