chore: install frontend dependencies.
This commit is contained in:
@@ -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'),
|
||||
};
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user