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>
  );
}