better-chatbot / src /components /eternity-dashboard.tsx
Bot
Fix unused RefreshCw import compilation error
795d888
Raw
History Blame Contribute Delete
13.9 kB
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "ui/skeleton";
import { ScrollArea } from "ui/scroll-area";
import { toast } from "sonner";
import { Loader2, Plus, Play, Pause, AlertCircle } from "lucide-react";
import { cn } from "lib/utils";
import dynamic from "next/dynamic";
import { Label } from "ui/label";
import { Input } from "ui/input";
import { Textarea } from "ui/textarea";
const LightRays = dynamic(() => import("@/components/ui/light-rays"), {
ssr: false,
});
interface EternityProject {
project_name: string;
goal: string;
deadline: string;
current_mode: string;
is_active: boolean;
priority: string;
latest_brief: string | null;
created_at: string;
time_remaining_str: string;
}
export default function EternityDashboard() {
const [projects, setProjects] = useState<EternityProject[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
// Form states
const [projName, setProjName] = useState("");
const [projGoal, setProjGoal] = useState("");
const [projDeadline, setProjDeadline] = useState(1.0);
const [projPriority, setProjPriority] = useState("low");
const [isSubmitting, setIsSubmitting] = useState(false);
const fetchProjects = async (silent = false) => {
if (!silent) setIsRefreshing(true);
try {
const res = await fetch("/api/eternity");
if (!res.ok) {
throw new Error(await res.text());
}
const data = await res.json();
setProjects(data.projects || []);
} catch (err: any) {
toast.error(`Failed to load projects: ${err.message}`);
} finally {
setIsLoading(false);
setIsRefreshing(false);
}
};
useEffect(() => {
fetchProjects();
const interval = setInterval(() => fetchProjects(true), 5000);
return () => clearInterval(interval);
}, []);
const handleToggleActive = async (name: string, currentStatus: boolean) => {
try {
const res = await fetch("/api/eternity", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "toggle",
project_name: name,
is_active: !currentStatus,
}),
});
if (!res.ok) throw new Error("Failed to toggle status");
toast.success(`Project ${!currentStatus ? "resumed" : "paused"}`);
fetchProjects(true);
} catch (err: any) {
toast.error(err.message);
}
};
const handleSetPriority = async (name: string, priority: string) => {
try {
const res = await fetch("/api/eternity", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "set-priority",
project_name: name,
priority: priority,
}),
});
if (!res.ok) throw new Error("Failed to set priority");
toast.success(`Priority updated to ${priority}`);
fetchProjects(true);
} catch (err: any) {
toast.error(err.message);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!projGoal.trim()) {
toast.error("Please enter a goal statement.");
return;
}
setIsSubmitting(true);
try {
const res = await fetch("/api/eternity", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "init",
project_name: projName.trim(),
goal: projGoal.trim(),
deadline_hours: projDeadline,
priority: projPriority,
}),
});
if (!res.ok) throw new Error(await res.text());
toast.success("Eternity R&D Goal Started successfully!");
setIsModalOpen(false);
setProjName("");
setProjGoal("");
fetchProjects(true);
} catch (err: any) {
toast.error(`Init failed: ${err.message}`);
} finally {
setIsSubmitting(false);
}
};
return (
<>
<div className="absolute opacity-30 pointer-events-none top-0 left-0 w-full h-full z-10">
<LightRays className="bg-transparent" />
</div>
<ScrollArea className="h-full w-full z-45">
<div className="pt-8 flex-1 relative flex flex-col gap-6 px-8 max-w-3xl h-full mx-auto pb-8">
<div className="flex items-center pb-4 border-b border-border/40">
<div>
<h1 className="text-2xl font-bold flex items-center gap-2">
Eternity R&D Lab
{isRefreshing && <Loader2 className="size-4 animate-spin text-muted-foreground" />}
</h1>
<p className="text-xs text-muted-foreground mt-1">Autonomous multi-agent loop orchestrator panel</p>
</div>
<div className="flex-1" />
<Button className="font-semibold gap-1 bg-primary text-primary-foreground" onClick={() => setIsModalOpen(true)}>
<Plus className="size-4" />
New R&D Goal
</Button>
</div>
{isLoading ? (
<div className="flex flex-col gap-4">
<Skeleton className="h-32 w-full" />
<Skeleton className="h-32 w-full" />
</div>
) : projects.length === 0 ? (
<div className="flex flex-col items-center justify-center space-y-4 my-20 text-center">
<AlertCircle className="size-12 text-muted-foreground/50" />
<h3 className="text-xl font-semibold">No R&D goals configured</h3>
<p className="text-muted-foreground max-w-md text-sm">
Click "New R&D Goal" to initialize your first eternity loop.
</p>
</div>
) : (
<div className="flex flex-col gap-6">
{projects.map((p) => {
const isBuild = p.current_mode === "build";
return (
<div key={p.project_name} className="border rounded-xl p-6 bg-card text-card-foreground shadow-sm flex flex-col gap-4 hover:border-accent transition-all relative overflow-hidden">
<div className="flex items-start justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<h3 className="text-lg font-bold font-mono text-foreground">{p.project_name}</h3>
<span className={cn(
"px-2 py-0.5 rounded text-[10px] font-bold border",
p.is_active
? "bg-green-500/10 text-green-400 border-green-500/20"
: "bg-muted text-muted-foreground border-border"
)}>
{p.is_active ? "ACTIVE" : "PAUSED"}
</span>
<span className={cn(
"px-2 py-0.5 rounded text-[10px] font-bold border",
p.priority === "supreme"
? "bg-red-500/10 text-red-400 border-red-500/20"
: "bg-blue-500/10 text-blue-400 border-blue-500/20"
)}>
{p.priority.toUpperCase()}
</span>
</div>
<p className="text-[10px] text-muted-foreground">
Started on {new Date(p.created_at).toLocaleString()}
</p>
</div>
<div className="flex items-center gap-3">
<select
value={p.priority}
onChange={(e) => handleSetPriority(p.project_name, e.target.value)}
className="bg-background border border-input rounded px-2.5 py-1 text-xs text-foreground focus:outline-none cursor-pointer"
>
<option value="supreme">Supreme</option>
<option value="low">Low</option>
</select>
<Button
variant="outline"
size="sm"
onClick={() => handleToggleActive(p.project_name, p.is_active)}
className={cn(
"text-xs px-3 py-1 font-semibold gap-1",
p.is_active
? "border-red-500/30 text-red-400 bg-red-500/5 hover:bg-red-500/10"
: "border-green-500/30 text-green-400 bg-green-500/5 hover:bg-green-500/10"
)}
>
{p.is_active ? <Pause className="size-3" /> : <Play className="size-3" />}
{p.is_active ? "Pause" : "Resume"}
</Button>
</div>
</div>
<div className="space-y-1.5">
<div className="text-xs font-semibold text-muted-foreground">Goal Description:</div>
<div className="text-sm text-foreground bg-muted/30 p-3 rounded border leading-relaxed">
{p.goal}
</div>
</div>
<div className="grid grid-cols-2 gap-4 text-xs">
<div className="bg-muted/40 p-3 rounded border space-y-1">
<div className="text-[10px] text-muted-foreground">Mode Status</div>
<div className={cn(
"font-bold uppercase",
isBuild ? "text-amber-500" : "text-green-500"
)}>
{p.current_mode} Mode
</div>
</div>
<div className="bg-muted/40 p-3 rounded border space-y-1">
<div className="text-[10px] text-muted-foreground">Deadline Countdown</div>
<div className="font-bold text-foreground font-mono">
{p.time_remaining_str}
</div>
</div>
</div>
{p.latest_brief && (
<div className="bg-primary/5 border border-primary/20 rounded p-4 text-xs text-foreground leading-relaxed space-y-1">
<div className="font-bold text-primary flex items-center gap-1">
📘 Latest Research Brief Summary
</div>
<div className="mt-1 text-muted-foreground">{p.latest_brief}</div>
</div>
)}
</div>
);
})}
</div>
)}
</div>
</ScrollArea>
{/* MODAL */}
{isModalOpen && (
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center z-50 animate-in fade-in duration-200">
<form onSubmit={handleSubmit} className="bg-card border border-border p-6 rounded-xl max-w-md w-full space-y-4 shadow-xl">
<h3 className="text-lg font-bold text-card-foreground">Initialize R&D Goal</h3>
<div className="space-y-3 text-sm">
<div className="space-y-1">
<Label htmlFor="modal-name">Project Name (Slugified automatically if blank)</Label>
<Input
id="modal-name"
type="text"
placeholder="e.g. stoichiometry-solver"
value={projName}
onChange={(e) => setProjName(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label htmlFor="modal-goal">Problem Statement / Goal</Label>
<Textarea
id="modal-goal"
rows={4}
placeholder="Describe the goal in detail..."
value={projGoal}
onChange={(e) => setProjGoal(e.target.value)}
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<Label htmlFor="modal-deadline">Deadline (Hours)</Label>
<Input
id="modal-deadline"
type="number"
step="0.01"
value={projDeadline}
onChange={(e) => setProjDeadline(parseFloat(e.target.value) || 1.0)}
/>
</div>
<div className="space-y-1">
<Label htmlFor="modal-priority">Priority</Label>
<select
id="modal-priority"
value={projPriority}
onChange={(e) => setProjPriority(e.target.value)}
className="w-full bg-background border border-input rounded p-2 text-foreground"
>
<option value="supreme">Supreme Priority</option>
<option value="low">Low Priority</option>
</select>
</div>
</div>
</div>
<div className="flex justify-end gap-3 text-sm pt-2">
<Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Starting..." : "Start R&D"}
</Button>
</div>
</form>
</div>
)}
</>
);
}