Spaces:
Running
Running
File size: 9,824 Bytes
24f95f0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 'use client';
import { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { motion, AnimatePresence } from 'framer-motion';
import { Zap, Target, ListOrdered, Share2, RefreshCw } from 'lucide-react';
import { apiClient } from '@/lib/api';
interface SimulationSummary {
simulation_id: string;
user_input: string;
status: string;
scenarios: number;
elapsed_seconds: number;
created_at: number;
}
export default function SimulationPage() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [simulations, setSimulations] = useState<SimulationSummary[]>([]);
const [refreshing, setRefreshing] = useState(false);
const [form, setForm] = useState({ title: '', seedText: '', predictionGoal: '' });
const loadSimulations = useCallback(async () => {
setRefreshing(true);
try {
const data = await apiClient.getSimulations();
const summaries: SimulationSummary[] = (Array.isArray(data) ? data : []).map((sim: any) => ({
simulation_id: sim.simulation_id,
user_input: sim.user_input || sim.title || '',
status: sim.status,
scenarios: sim.scenarios || 0,
elapsed_seconds: sim.elapsed_seconds || 0,
created_at: sim.created_at || 0,
}));
setSimulations(summaries);
} catch { setSimulations([]); }
finally { setRefreshing(false); }
}, []);
useEffect(() => { loadSimulations(); }, [loadSimulations]);
const handleSubmit = async () => {
if (!form.title || !form.seedText || !form.predictionGoal) {
setError('All simulation parameters are required.');
return;
}
setLoading(true); setError(null);
try {
const userInput = `${form.title}. ${form.seedText}. Goal: ${form.predictionGoal}`;
const result = await apiClient.runNativeSimulation(userInput, {
title: form.title, seed_text: form.seedText, prediction_goal: form.predictionGoal,
});
if (result.simulation_id) {
router.push(`/simulation/${result.simulation_id}`);
} else {
setError('Simulation completed but no ID returned.');
setLoading(false);
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to run simulation.');
setLoading(false);
}
};
return (
<div className="h-full flex flex-col overflow-hidden">
{/* Header */}
<div className="shrink-0 px-6 pt-6 pb-4 border-b border-white/[0.04]">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="relative w-9 h-9 rounded-xl bg-amber-500/10 border border-amber-500/15 flex items-center justify-center">
<Zap size={16} className="text-amber-400" />
{loading && <div className="absolute inset-0 rounded-xl border-2 border-amber-400/40 border-t-transparent animate-spin" />}
</div>
<div>
<h1 className="text-lg font-light text-gray-100">Simulation Lab</h1>
<p className="text-[11px] text-gray-600">Scenario modeling and prediction engine</p>
</div>
</div>
<button onClick={loadSimulations} disabled={refreshing} className="flex items-center gap-2 px-3 py-1.5 rounded-xl border border-white/[0.06] hover:border-white/[0.12] text-[11px] text-gray-500 hover:text-gray-300 transition-all disabled:opacity-40">
<RefreshCw size={12} className={refreshing ? 'animate-spin' : ''} /> Refresh
</button>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-6">
<div className="max-w-5xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Form */}
<div className="lg:col-span-7">
<div className="card p-6 space-y-5">
<h2 className="text-[11px] text-amber-400 uppercase tracking-wider flex items-center gap-2">
<Zap size={12} /> New Simulation
</h2>
{error && (
<div className="p-3 rounded-xl bg-red-500/10 border border-red-500/15 text-[12px] text-red-400">{error}</div>
)}
<div className="space-y-1.5">
<label className="text-[10px] text-gray-500 uppercase tracking-wider">Scenario Title</label>
<div className="relative">
<Target size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-600" />
<input
value={form.title}
onChange={e => setForm({ ...form, title: e.target.value })}
disabled={loading}
placeholder="e.g. US Debt Default Simulation"
className="w-full py-2.5 pl-9 pr-4 text-[13px] text-gray-200 rounded-xl border border-white/[0.06] focus:border-amber-500/30 focus:outline-none transition-colors placeholder:text-gray-700"
style={{ background: 'rgba(0,0,0,0.3)' }}
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] text-gray-500 uppercase tracking-wider">Initial Context</label>
<div className="relative">
<ListOrdered size={14} className="absolute left-3 top-3 text-gray-600" />
<textarea
value={form.seedText}
onChange={e => setForm({ ...form, seedText: e.target.value })}
disabled={loading}
placeholder="Background facts, market state, constraints..."
rows={3}
className="w-full py-2.5 pl-9 pr-4 text-[13px] text-gray-200 rounded-xl border border-white/[0.06] focus:border-amber-500/30 focus:outline-none transition-colors placeholder:text-gray-700 resize-none"
style={{ background: 'rgba(0,0,0,0.3)' }}
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] text-gray-500 uppercase tracking-wider">Prediction Goal</label>
<div className="relative">
<Share2 size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-600" />
<input
value={form.predictionGoal}
onChange={e => setForm({ ...form, predictionGoal: e.target.value })}
disabled={loading}
placeholder="e.g. Impact on 10-year treasury yields?"
className="w-full py-2.5 pl-9 pr-4 text-[13px] text-gray-200 rounded-xl border border-white/[0.06] focus:border-amber-500/30 focus:outline-none transition-colors placeholder:text-gray-700"
style={{ background: 'rgba(0,0,0,0.3)' }}
/>
</div>
</div>
<button
onClick={handleSubmit}
disabled={loading}
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-amber-500/10 border border-amber-500/20 hover:bg-amber-500/20 disabled:opacity-40 transition-all text-[12px] text-amber-300"
>
<Zap size={14} className={loading ? 'animate-pulse' : ''} />
{loading ? 'Running Simulation...' : 'Launch Simulation'}
</button>
</div>
</div>
{/* Recent simulations */}
<div className="lg:col-span-5 space-y-3">
<div className="flex items-center justify-between mb-1">
<span className="text-[11px] text-gray-500 uppercase tracking-wider">Recent Executions</span>
<span className="text-[10px] text-gray-600">{simulations.length} total</span>
</div>
{simulations.length === 0 ? (
<div className="card text-center py-12">
<Zap size={24} className="text-gray-700 mx-auto mb-3" />
<p className="text-[12px] text-gray-600">No simulations yet.</p>
</div>
) : (
simulations.map((sim, i) => (
<motion.div
key={sim.simulation_id}
initial={{ opacity: 0, x: 12 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.04 }}
onClick={() => router.push(`/simulation/${sim.simulation_id}`)}
className="card hover:border-amber-500/20 cursor-pointer group"
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<div className={`w-1.5 h-1.5 rounded-full ${sim.status === 'completed' ? 'bg-emerald-400' : sim.status === 'failed' ? 'bg-red-400' : 'bg-amber-400 animate-pulse'}`} />
<span className="text-[10px] text-gray-500 uppercase tracking-wider">{sim.status}</span>
</div>
<span className="text-[10px] text-gray-600">{sim.simulation_id.slice(0, 8)}</span>
</div>
<h4 className="text-[13px] text-gray-300 group-hover:text-amber-300 transition-colors line-clamp-1 mb-1">
{sim.user_input}
</h4>
<div className="flex items-center gap-3 text-[10px] text-gray-600">
<span>{sim.scenarios} scenarios</span>
<span>·</span>
<span>{sim.elapsed_seconds}s</span>
</div>
</motion.div>
))
)}
</div>
</div>
</div>
</div>
);
}
|