Files
openagent/dashboard/src/app/page.tsx
2026-01-18 11:25:34 +00:00

156 lines
5.1 KiB
TypeScript

'use client';
import { useCallback, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import useSWR from 'swr';
import { toast } from '@/components/toast';
import { StatsCard } from '@/components/stats-card';
import { ConnectionStatus } from '@/components/connection-status';
import { RecentTasks } from '@/components/recent-tasks';
import { ShimmerStat } from '@/components/ui/shimmer';
import { createMission, getStats, listWorkspaces } from '@/lib/api';
import { Activity, CheckCircle, DollarSign, Zap } from 'lucide-react';
import { formatCents } from '@/lib/utils';
import { SystemMonitor } from '@/components/system-monitor';
import { NewMissionDialog } from '@/components/new-mission-dialog';
export default function OverviewPage() {
const router = useRouter();
const [creatingMission, setCreatingMission] = useState(false);
const hasShownErrorRef = useRef(false);
// SWR: poll stats every 3 seconds
const { data: stats, isLoading: statsLoading, error: statsError } = useSWR(
'stats',
getStats,
{
refreshInterval: 3000,
revalidateOnFocus: false,
onSuccess: () => {
hasShownErrorRef.current = false;
},
onError: () => {
if (!hasShownErrorRef.current) {
toast.error('Failed to connect to agent server');
hasShownErrorRef.current = true;
}
},
}
);
// SWR: fetch workspaces (shared key with workspaces page)
const { data: workspaces = [] } = useSWR('workspaces', listWorkspaces, {
revalidateOnFocus: false,
});
const isActive = (stats?.active_tasks ?? 0) > 0;
const handleNewMission = useCallback(
async (options?: { workspaceId?: string; agent?: string; backend?: string }) => {
try {
setCreatingMission(true);
const mission = await createMission({
workspaceId: options?.workspaceId,
agent: options?.agent,
backend: options?.backend,
});
toast.success('New mission created');
router.push(`/control?mission=${mission.id}`);
} catch (err) {
console.error('Failed to create mission:', err);
toast.error('Failed to create new mission');
} finally {
setCreatingMission(false);
}
},
[router]
);
return (
<div className="flex min-h-screen">
{/* Main content */}
<div className="flex-1 flex flex-col p-6">
{/* Header */}
<div className="mb-6 flex items-start justify-between">
<div>
<div className="flex items-center gap-3">
<h1 className="text-xl font-semibold text-white">
Global Monitor
</h1>
{isActive && (
<span className="flex items-center gap-1.5 rounded-md bg-emerald-500/10 border border-emerald-500/20 px-2 py-1 text-[10px] font-medium text-emerald-400">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
LIVE
</span>
)}
</div>
<p className="mt-1 text-sm text-white/50">
Real-time agent activity
</p>
</div>
{/* Quick Actions */}
<NewMissionDialog
workspaces={workspaces}
disabled={creatingMission}
onCreate={handleNewMission}
/>
</div>
{/* System Metrics Area */}
<div className="flex-1 flex items-center justify-center rounded-2xl bg-white/[0.01] border border-white/[0.04] mb-6 min-h-[300px] p-6">
<SystemMonitor className="w-full max-w-4xl" />
</div>
{/* Stats grid - at bottom */}
<div className="grid grid-cols-4 gap-4">
{statsLoading ? (
<>
<ShimmerStat />
<ShimmerStat />
<ShimmerStat />
<ShimmerStat />
</>
) : (
<>
<StatsCard
title="Total Tasks"
value={stats?.total_tasks ?? 0}
icon={Activity}
/>
<StatsCard
title="Active"
value={stats?.active_tasks ?? 0}
subtitle="running"
icon={Zap}
color={stats?.active_tasks ? 'accent' : 'default'}
/>
<StatsCard
title="Success Rate"
value={`${((stats?.success_rate ?? 1) * 100).toFixed(0)}%`}
icon={CheckCircle}
color="success"
/>
<StatsCard
title="Total Cost"
value={formatCents(stats?.total_cost_cents ?? 0)}
icon={DollarSign}
/>
</>
)}
</div>
</div>
{/* Right sidebar - no glass panel wrapper, just border */}
<div className="w-80 h-screen border-l border-white/[0.06] p-4 flex flex-col overflow-hidden">
<div className="flex-1 min-h-0 overflow-hidden">
<RecentTasks />
</div>
<div className="mt-4 flex-shrink-0">
<ConnectionStatus />
</div>
</div>
</div>
);
}