cesjavi commited on
Commit
d83ce16
·
1 Parent(s): fe875af

Fix: Stable deployment without binary mascot (Phase 9)

Browse files
frontend/src/App.tsx CHANGED
@@ -46,6 +46,7 @@ const App: React.FC = () => {
46
  const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
47
  const [initialTaskId, setInitialTaskId] = useState<string | null>(null);
48
  const [projectDetailReturnTab, setProjectDetailReturnTab] = useState<AppTab>('dashboard');
 
49
  const [uiMode, setUiMode] = useState<UiMode>(() => getUiMode());
50
  const [isSidebarOpen, setIsSidebarOpen] = useState(() => typeof window === 'undefined' || window.innerWidth >= 900);
51
  const [showSplash, setShowSplash] = useState(true);
@@ -231,7 +232,10 @@ const App: React.FC = () => {
231
  <section className="animate-fade-in app-content">
232
  {activeTab === 'dashboard' && (
233
  <Dashboard
234
- onNewProject={() => navigateTo('new-project')}
 
 
 
235
  onOpenProject={(projectId) => openProjectDetail(projectId)}
236
  />
237
  )}
@@ -261,7 +265,7 @@ const App: React.FC = () => {
261
  {activeTab === 'monitoring' && uiMode === 'expert' && <MonitoringView />}
262
  {activeTab === 'teams' && uiMode === 'expert' && <TeamsView />}
263
  {activeTab === 'audit' && uiMode === 'expert' && <AuditView />}
264
- {activeTab === 'new-project' && <NewProject uiMode={uiMode} onCreated={() => navigateTo('dashboard')} />}
265
  {activeTab === 'settings' && <SettingsView uiMode={uiMode} onUiModeChange={updateUiMode} />}
266
  </section>
267
 
 
46
  const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
47
  const [initialTaskId, setInitialTaskId] = useState<string | null>(null);
48
  const [projectDetailReturnTab, setProjectDetailReturnTab] = useState<AppTab>('dashboard');
49
+ const [initialProjectData, setInitialProjectData] = useState<any>(null);
50
  const [uiMode, setUiMode] = useState<UiMode>(() => getUiMode());
51
  const [isSidebarOpen, setIsSidebarOpen] = useState(() => typeof window === 'undefined' || window.innerWidth >= 900);
52
  const [showSplash, setShowSplash] = useState(true);
 
232
  <section className="animate-fade-in app-content">
233
  {activeTab === 'dashboard' && (
234
  <Dashboard
235
+ onNewProject={(data?: any) => {
236
+ setInitialProjectData(data || null);
237
+ navigateTo('new-project');
238
+ }}
239
  onOpenProject={(projectId) => openProjectDetail(projectId)}
240
  />
241
  )}
 
265
  {activeTab === 'monitoring' && uiMode === 'expert' && <MonitoringView />}
266
  {activeTab === 'teams' && uiMode === 'expert' && <TeamsView />}
267
  {activeTab === 'audit' && uiMode === 'expert' && <AuditView />}
268
+ {activeTab === 'new-project' && <NewProject uiMode={uiMode} initialData={initialProjectData} onCreated={() => { setInitialProjectData(null); navigateTo('dashboard'); }} />}
269
  {activeTab === 'settings' && <SettingsView uiMode={uiMode} onUiModeChange={updateUiMode} />}
270
  </section>
271
 
frontend/src/components/Dashboard.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
  import { FolderOpen, Play, RefreshCw, Search, SlidersHorizontal, Trash2, X } from 'lucide-react';
 
3
  import { motion } from 'framer-motion';
4
  import { supabase } from '../services/supabase';
5
  import { useAuth } from '../context/useAuth';
@@ -34,6 +35,9 @@ const Dashboard: React.FC<DashboardProps> = ({ onNewProject, onOpenProject }) =>
34
  const [statusFilter, setStatusFilter] = useState('all');
35
  const [progressFilter, setProgressFilter] = useState('all');
36
  const [sortBy, setSortBy] = useState('newest');
 
 
 
37
 
38
  const loadDashboard = useCallback(async () => {
39
  if (!user) return;
@@ -88,6 +92,38 @@ const Dashboard: React.FC<DashboardProps> = ({ onNewProject, onOpenProject }) =>
88
  }
89
  };
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  const taskCounts = useMemo(() => {
92
  return tasks.reduce<Record<string, { done: number; total: number }>>((acc, task) => {
93
  if (!acc[task.project_id]) acc[task.project_id] = { done: 0, total: 0 };
@@ -145,23 +181,106 @@ const Dashboard: React.FC<DashboardProps> = ({ onNewProject, onOpenProject }) =>
145
 
146
  return (
147
  <>
148
- <div className="page-heading dashboard-heading">
149
  <div>
150
  <h2>Project Dashboard</h2>
151
  <p style={{ color: 'var(--text-dim)' }}>Monitor and manage your autonomous AI agent workflows.</p>
152
  </div>
153
  <div className="button-row">
154
  <button className="btn btn-glass" onClick={loadDashboard} disabled={loading}>
155
- <RefreshCw size={18} />
156
  {loading ? 'Refreshing...' : 'Refresh'}
157
  </button>
158
- <button className="btn btn-primary" onClick={onNewProject}>
159
  <FolderOpen size={18} />
160
  New Project
161
  </button>
162
  </div>
163
  </div>
164
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  {error && <div className="inline-status">{error}</div>}
166
 
167
  {projects.length > 0 && (
 
1
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
  import { FolderOpen, Play, RefreshCw, Search, SlidersHorizontal, Trash2, X } from 'lucide-react';
3
+ import { getApiUrl } from '../services/runtimeConfig';
4
  import { motion } from 'framer-motion';
5
  import { supabase } from '../services/supabase';
6
  import { useAuth } from '../context/useAuth';
 
35
  const [statusFilter, setStatusFilter] = useState('all');
36
  const [progressFilter, setProgressFilter] = useState('all');
37
  const [sortBy, setSortBy] = useState('newest');
38
+ const [aiPrompt, setAiPrompt] = useState('');
39
+ const [isGenerating, setIsGenerating] = useState(false);
40
+ const [magicFiles, setMagicFiles] = useState<File[]>([]);
41
 
42
  const loadDashboard = useCallback(async () => {
43
  if (!user) return;
 
92
  }
93
  };
94
 
95
+ const handleMagicGenerate = async () => {
96
+ if (!aiPrompt.trim()) return;
97
+ setIsGenerating(true);
98
+
99
+ try {
100
+ const formData = new FormData();
101
+ formData.append('prompt', aiPrompt);
102
+ magicFiles.forEach(file => {
103
+ formData.append('files', file);
104
+ });
105
+
106
+ const apiUrl = getApiUrl();
107
+
108
+ const response = await fetch(`${apiUrl}/generator/generate-project`, {
109
+ method: 'POST',
110
+ body: formData
111
+ });
112
+
113
+ if (!response.ok) throw new Error('AI Generation failed');
114
+
115
+ const data = await response.json();
116
+ onNewProject(data); // Open NewProject wizard with pre-filled data
117
+ setAiPrompt('');
118
+ setMagicFiles([]);
119
+ } catch (err: any) {
120
+ console.error('Magic Generate Error:', err);
121
+ setError(`AI Error: ${err.message}`);
122
+ } finally {
123
+ setIsGenerating(false);
124
+ }
125
+ };
126
+
127
  const taskCounts = useMemo(() => {
128
  return tasks.reduce<Record<string, { done: number; total: number }>>((acc, task) => {
129
  if (!acc[task.project_id]) acc[task.project_id] = { done: 0, total: 0 };
 
181
 
182
  return (
183
  <>
184
+ <div className="page-heading dashboard-heading" style={{ marginBottom: 'var(--space-md)' }}>
185
  <div>
186
  <h2>Project Dashboard</h2>
187
  <p style={{ color: 'var(--text-dim)' }}>Monitor and manage your autonomous AI agent workflows.</p>
188
  </div>
189
  <div className="button-row">
190
  <button className="btn btn-glass" onClick={loadDashboard} disabled={loading}>
191
+ <RefreshCw size={18} className={loading ? 'spin' : ''} />
192
  {loading ? 'Refreshing...' : 'Refresh'}
193
  </button>
194
+ <button className="btn btn-primary" onClick={() => onNewProject()}>
195
  <FolderOpen size={18} />
196
  New Project
197
  </button>
198
  </div>
199
  </div>
200
 
201
+ {/* AI Magic Bar (Aubix) */}
202
+ <section className="glass-panel magic-box" style={{
203
+ marginBottom: 'var(--space-xl)',
204
+ padding: '12px 20px',
205
+ border: '1px solid var(--accent)',
206
+ boxShadow: '0 0 30px rgba(110, 89, 255, 0.15)',
207
+ display: 'flex',
208
+ alignItems: 'center',
209
+ gap: 'var(--space-lg)',
210
+ background: 'rgba(110, 89, 255, 0.05)'
211
+ }}>
212
+ <div style={{
213
+ width: '64px',
214
+ height: '64px',
215
+ background: 'var(--accent)',
216
+ borderRadius: '16px',
217
+ display: 'flex',
218
+ alignItems: 'center',
219
+ justifyContent: 'center',
220
+ boxShadow: '0 0 20px rgba(110, 89, 255, 0.3)'
221
+ }}>
222
+ <Bot size={32} color="white" />
223
+ </div>
224
+ <div style={{ flex: 1 }}>
225
+ <div style={{ display: 'flex', gap: 'var(--space-sm)', position: 'relative' }}>
226
+ <input
227
+ type="text"
228
+ placeholder="Ask Aubix to build a project... (e.g. 'Audit the Aubm codebase using MD files')"
229
+ value={aiPrompt}
230
+ onChange={(e) => setAiPrompt(e.target.value)}
231
+ onKeyDown={(e) => e.key === 'Enter' && handleMagicGenerate()}
232
+ style={{
233
+ width: '100%',
234
+ padding: '12px 20px',
235
+ paddingRight: '140px',
236
+ background: 'rgba(0,0,0,0.2)',
237
+ border: '1px solid var(--border)',
238
+ borderRadius: 'var(--radius-md)',
239
+ color: 'white',
240
+ fontSize: '1rem',
241
+ outline: 'none'
242
+ }}
243
+ />
244
+ <div style={{ position: 'absolute', right: '6px', top: '50%', transform: 'translateY(-50%)', display: 'flex', gap: '6px' }}>
245
+ <button
246
+ className="btn btn-sm btn-glass"
247
+ title="Reference Files"
248
+ onClick={() => {
249
+ const input = document.createElement('input');
250
+ input.type = 'file';
251
+ input.multiple = true;
252
+ input.onchange = (e) => setMagicFiles(Array.from((e.target as HTMLInputElement).files || []));
253
+ input.click();
254
+ }}
255
+ style={{ padding: '8px' }}
256
+ >
257
+ <Search size={16} style={{ transform: 'rotate(45deg)' }} />
258
+ {magicFiles.length > 0 && <span className="count-badge" style={{ position: 'absolute', top: '-5px', right: '-5px', background: 'var(--accent)', fontSize: '0.6rem', padding: '2px 4px', borderRadius: '50%' }}>{magicFiles.length}</span>}
259
+ </button>
260
+ <button
261
+ className="btn btn-sm btn-primary"
262
+ onClick={handleMagicGenerate}
263
+ disabled={isGenerating || !aiPrompt.trim()}
264
+ style={{ minWidth: '80px' }}
265
+ >
266
+ {isGenerating ? <RefreshCw className="spin" size={16} /> : <Play size={16} />}
267
+ {isGenerating ? '...' : 'Generate'}
268
+ </button>
269
+ </div>
270
+ </div>
271
+ {magicFiles.length > 0 && (
272
+ <div style={{ display: 'flex', gap: '8px', marginTop: '8px', flexWrap: 'wrap' }}>
273
+ {magicFiles.map((f, i) => (
274
+ <span key={i} style={{ fontSize: '0.7rem', background: 'rgba(255,255,255,0.1)', padding: '2px 8px', borderRadius: '4px', display: 'flex', alignItems: 'center', gap: '6px', border: '1px solid rgba(255,255,255,0.1)' }}>
275
+ {f.name}
276
+ <X size={10} style={{ cursor: 'pointer', opacity: 0.5 }} onClick={() => setMagicFiles(prev => prev.filter((_, idx) => idx !== i))} />
277
+ </span>
278
+ ))}
279
+ </div>
280
+ )}
281
+ </div>
282
+ </section>
283
+
284
  {error && <div className="inline-status">{error}</div>}
285
 
286
  {projects.length > 0 && (
frontend/src/components/NewProject.tsx CHANGED
@@ -4,6 +4,7 @@ import { motion } from 'framer-motion';
4
  import { supabase } from '../services/supabase';
5
  import { useAuth } from '../context/useAuth';
6
  import type { UiMode } from '../services/uiMode';
 
7
 
8
  type ProjectSource =
9
  | {
@@ -115,7 +116,7 @@ const expertAccessStep = {
115
  description: 'Decide whether this project is personal or belongs to a team workspace.'
116
  };
117
 
118
- const NewProject: React.FC<{ uiMode: UiMode; onCreated?: () => void }> = ({ uiMode, onCreated }) => {
119
  const { user } = useAuth();
120
  const fileInputRef = useRef<HTMLInputElement | null>(null);
121
  const [name, setName] = useState('');
 
4
  import { supabase } from '../services/supabase';
5
  import { useAuth } from '../context/useAuth';
6
  import type { UiMode } from '../services/uiMode';
7
+ import { getApiUrl } from '../services/runtimeConfig';
8
 
9
  type ProjectSource =
10
  | {
 
116
  description: 'Decide whether this project is personal or belongs to a team workspace.'
117
  };
118
 
119
+ const NewProject: React.FC<{ uiMode: UiMode; initialData?: any; onCreated?: () => void }> = ({ uiMode, initialData, onCreated }) => {
120
  const { user } = useAuth();
121
  const fileInputRef = useRef<HTMLInputElement | null>(null);
122
  const [name, setName] = useState('');
frontend/src/components/SplashScreen.tsx CHANGED
@@ -31,19 +31,21 @@ const SplashScreen: React.FC = () => {
31
  damping: 20,
32
  delay: 0.2
33
  }}
34
- style={{
35
- width: '120px',
36
- height: '120px',
37
- borderRadius: '30px',
38
- background: 'linear-gradient(135deg, var(--accent) 0%, var(--primary) 100%)',
39
- display: 'flex',
40
- alignItems: 'center',
41
- justifyContent: 'center',
42
- marginBottom: 'var(--space-xl)',
43
- boxShadow: '0 20px 40px rgba(110, 89, 255, 0.3)',
44
- }}
45
- >
46
- <Bot size={60} color="white" />
 
 
47
  </motion.div>
48
 
49
  <motion.h1
 
31
  damping: 20,
32
  delay: 0.2
33
  }}
34
+ <motion.div
35
+ animate={{ y: [0, -10, 0] }}
36
+ transition={{ repeat: Infinity, duration: 3, ease: "easeInOut" }}
37
+ style={{
38
+ background: 'linear-gradient(135deg, var(--accent) 0%, var(--primary) 100%)',
39
+ padding: '40px',
40
+ borderRadius: '40px',
41
+ boxShadow: '0 20px 40px rgba(110, 89, 255, 0.4)',
42
+ display: 'flex',
43
+ alignItems: 'center',
44
+ justifyContent: 'center'
45
+ }}
46
+ >
47
+ <Bot size={100} color="white" />
48
+ </motion.div>
49
  </motion.div>
50
 
51
  <motion.h1