Bluestrikeai commited on
Commit
eb7f853
·
verified ·
1 Parent(s): 23678fc

Delete fronend

Browse files
fronend/index.html DELETED
@@ -1,14 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚀</text></svg>" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>NEXUS Builder — AI Full-Stack App Maker</title>
8
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
9
- </head>
10
- <body>
11
- <div id="root"></div>
12
- <script type="module" src="/src/main.jsx"></script>
13
- </body>
14
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/package.json DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "name": "nexus-builder-ui",
3
- "private": true,
4
- "version": "1.0.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview"
10
- },
11
- "dependencies": {
12
- "react": "^18.3.1",
13
- "react-dom": "^18.3.1",
14
- "lucide-react": "^0.469.0"
15
- },
16
- "devDependencies": {
17
- "@types/react": "^18.3.18",
18
- "@types/react-dom": "^18.3.5",
19
- "@vitejs/plugin-react": "^4.3.4",
20
- "autoprefixer": "^10.4.20",
21
- "postcss": "^8.4.49",
22
- "tailwindcss": "^3.4.17",
23
- "vite": "^6.0.5"
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/postcss.config.js DELETED
@@ -1,6 +0,0 @@
1
- export default {
2
- plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {},
5
- },
6
- }
 
 
 
 
 
 
 
fronend/src/App.jsx DELETED
@@ -1,221 +0,0 @@
1
- import React, { useState, useCallback, useRef, useEffect } from 'react'
2
- import Header from './components/Header'
3
- import ControlPanel from './components/ControlPanel'
4
- import LivePreview from './components/LivePreview'
5
- import StatusBar from './components/StatusBar'
6
- import { useSSE } from './hooks/useSSE'
7
- import { generateProject, getProjectStatus, getProjectFiles, exportProject, fixBug } from './utils/api'
8
-
9
- const INITIAL_AGENTS = {
10
- research: { name: 'GLM 4.5 Air', status: 'idle', icon: '🌐' },
11
- orchestrator: { name: 'Trinity Large', status: 'idle', icon: '🧠' },
12
- frontend: { name: 'Qwen3 Coder', status: 'idle', icon: '🎨' },
13
- backend: { name: 'MiniMax M2.5', status: 'idle', icon: '🔐' },
14
- }
15
-
16
- export default function App() {
17
- const [sessionId, setSessionId] = useState(null)
18
- const [status, setStatus] = useState('idle')
19
- const [agents, setAgents] = useState(INITIAL_AGENTS)
20
- const [messages, setMessages] = useState([])
21
- const [agentFeed, setAgentFeed] = useState([])
22
- const [files, setFiles] = useState({})
23
- const [fileTree, setFileTree] = useState([])
24
- const [selectedFile, setSelectedFile] = useState(null)
25
- const [previewSystem, setPreviewSystem] = useState('preview')
26
- const [viewMode, setViewMode] = useState('preview') // preview | code
27
- const [errors, setErrors] = useState([])
28
-
29
- // Handle SSE events from the pipeline
30
- const handleSSEEvent = useCallback((event) => {
31
- const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data
32
-
33
- switch (event.type || data.event_type) {
34
- case 'agent_start':
35
- setAgents(prev => ({
36
- ...prev,
37
- [data.agent]: { ...prev[data.agent], status: 'active' }
38
- }))
39
- setAgentFeed(prev => [...prev, {
40
- agent: data.agent,
41
- content: data.content,
42
- time: new Date().toLocaleTimeString(),
43
- type: 'start'
44
- }])
45
- setStatus(data.content || 'Working...')
46
- break
47
-
48
- case 'token':
49
- setAgentFeed(prev => {
50
- const last = prev[prev.length - 1]
51
- if (last && last.agent === data.agent && last.type === 'stream') {
52
- return [...prev.slice(0, -1), { ...last, content: last.content + data.content }]
53
- }
54
- return [...prev, {
55
- agent: data.agent,
56
- content: data.content,
57
- time: new Date().toLocaleTimeString(),
58
- type: 'stream'
59
- }]
60
- })
61
- break
62
-
63
- case 'file_created':
64
- setAgentFeed(prev => [...prev, {
65
- agent: data.agent,
66
- content: data.content,
67
- time: new Date().toLocaleTimeString(),
68
- type: 'file',
69
- filePath: data.file_path
70
- }])
71
- if (data.file_path) {
72
- setFileTree(prev => {
73
- const newTree = [...new Set([...prev, data.file_path])]
74
- return newTree.sort()
75
- })
76
- }
77
- break
78
-
79
- case 'agent_done':
80
- setAgents(prev => ({
81
- ...prev,
82
- [data.agent]: { ...prev[data.agent], status: 'done' }
83
- }))
84
- setAgentFeed(prev => [...prev, {
85
- agent: data.agent,
86
- content: data.content,
87
- time: new Date().toLocaleTimeString(),
88
- type: 'done'
89
- }])
90
- break
91
-
92
- case 'error':
93
- setErrors(prev => [...prev, data.content])
94
- setAgents(prev => ({
95
- ...prev,
96
- [data.agent]: { ...prev[data.agent], status: 'error' }
97
- }))
98
- break
99
-
100
- case 'done':
101
- setStatus(data.status === 'completed' ? 'completed' : 'error')
102
- // Fetch final files
103
- if (data.session_id) {
104
- getProjectFiles(data.session_id).then(res => {
105
- if (res.files) {
106
- setFiles(res.files)
107
- setFileTree(Object.keys(res.files).sort())
108
- }
109
- })
110
- }
111
- break
112
-
113
- default:
114
- break
115
- }
116
- }, [])
117
-
118
- const { connect, disconnect } = useSSE(handleSSEEvent)
119
-
120
- // Start generation
121
- const handleGenerate = useCallback(async (prompt, appType) => {
122
- // Reset state
123
- setAgents(INITIAL_AGENTS)
124
- setAgentFeed([])
125
- setFiles({})
126
- setFileTree([])
127
- setErrors([])
128
- setSelectedFile(null)
129
- setStatus('starting')
130
-
131
- setMessages(prev => [...prev, { role: 'user', content: prompt }])
132
-
133
- try {
134
- const res = await generateProject(prompt, appType)
135
- setSessionId(res.session_id)
136
- setStatus('connected')
137
-
138
- // Connect to SSE stream
139
- connect(res.session_id)
140
-
141
- setMessages(prev => [...prev, {
142
- role: 'assistant',
143
- content: `🚀 Project generation started! Session: ${res.session_id}\n\nI'm coordinating 4 AI agents to build your application...`
144
- }])
145
- } catch (err) {
146
- setStatus('error')
147
- setErrors(prev => [...prev, err.message])
148
- setMessages(prev => [...prev, {
149
- role: 'assistant',
150
- content: `❌ Error starting generation: ${err.message}`
151
- }])
152
- }
153
- }, [connect])
154
-
155
- // Export
156
- const handleExport = useCallback(async () => {
157
- if (!sessionId) return
158
- try {
159
- await exportProject(sessionId)
160
- } catch (err) {
161
- setErrors(prev => [...prev, `Export failed: ${err.message}`])
162
- }
163
- }, [sessionId])
164
-
165
- // Fix bug
166
- const handleFix = useCallback(async (errorMessage, filePath) => {
167
- if (!sessionId) return
168
- try {
169
- await fixBug(sessionId, errorMessage, filePath)
170
- connect(sessionId) // Reconnect SSE for fix updates
171
- } catch (err) {
172
- setErrors(prev => [...prev, `Fix failed: ${err.message}`])
173
- }
174
- }, [sessionId, connect])
175
-
176
- return (
177
- <div className="h-screen w-screen flex flex-col overflow-hidden" style={{ background: 'var(--bg)' }}>
178
- <Header
179
- agents={agents}
180
- sessionId={sessionId}
181
- status={status}
182
- />
183
-
184
- <div className="flex flex-1 overflow-hidden">
185
- {/* Left Panel — Control Center */}
186
- <ControlPanel
187
- messages={messages}
188
- agentFeed={agentFeed}
189
- fileTree={fileTree}
190
- files={files}
191
- selectedFile={selectedFile}
192
- onSelectFile={setSelectedFile}
193
- onGenerate={handleGenerate}
194
- onFix={handleFix}
195
- status={status}
196
- />
197
-
198
- {/* Right Panel — Preview */}
199
- <LivePreview
200
- sessionId={sessionId}
201
- files={files}
202
- selectedFile={selectedFile}
203
- previewSystem={previewSystem}
204
- onChangeSystem={setPreviewSystem}
205
- viewMode={viewMode}
206
- onChangeViewMode={setViewMode}
207
- onExport={handleExport}
208
- status={status}
209
- />
210
- </div>
211
-
212
- <StatusBar
213
- status={status}
214
- sessionId={sessionId}
215
- agents={agents}
216
- errorCount={errors.length}
217
- fileCount={fileTree.length}
218
- />
219
- </div>
220
- )
221
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/AgentFeed.jsx DELETED
@@ -1,92 +0,0 @@
1
- import React, { useRef, useEffect } from 'react'
2
- import { Bot, FileCode2, CheckCircle2, AlertCircle, Zap } from 'lucide-react'
3
-
4
- const AGENT_COLORS = {
5
- research: '#00D9FF',
6
- orchestrator: '#6C63FF',
7
- frontend: '#22D3A8',
8
- backend: '#FFB547',
9
- system: '#8888AA',
10
- }
11
-
12
- const AGENT_ICONS = {
13
- research: '🌐',
14
- orchestrator: '🧠',
15
- frontend: '🎨',
16
- backend: '🔐',
17
- system: '⚙️',
18
- }
19
-
20
- export default function AgentFeed({ feed }) {
21
- const feedEndRef = useRef(null)
22
-
23
- useEffect(() => {
24
- feedEndRef.current?.scrollIntoView({ behavior: 'smooth' })
25
- }, [feed])
26
-
27
- if (feed.length === 0) {
28
- return (
29
- <div className="flex flex-col items-center justify-center h-full text-center px-6">
30
- <Bot className="w-12 h-12 mb-3" style={{ color: 'var(--text-secondary)', opacity: 0.4 }} />
31
- <p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
32
- Agent activity will appear here once you start generating.
33
- </p>
34
- </div>
35
- )
36
- }
37
-
38
- return (
39
- <div className="h-full overflow-y-auto p-3 space-y-2">
40
- {feed.map((item, i) => (
41
- <div
42
- key={i}
43
- className="flex items-start gap-2.5 p-2.5 rounded-lg animate-fade-in text-xs"
44
- style={{
45
- background: item.type === 'done' ? `${AGENT_COLORS[item.agent]}11` : 'var(--surface)',
46
- border: `1px solid ${item.type === 'done' ? AGENT_COLORS[item.agent] + '33' : 'var(--border)'}`,
47
- }}
48
- >
49
- {/* Agent icon */}
50
- <span className="text-base flex-shrink-0 mt-0.5">
51
- {AGENT_ICONS[item.agent] || '⚙️'}
52
- </span>
53
-
54
- <div className="flex-1 min-w-0">
55
- {/* Agent name + time */}
56
- <div className="flex items-center justify-between mb-1">
57
- <span className="font-semibold capitalize" style={{ color: AGENT_COLORS[item.agent] }}>
58
- {item.agent}
59
- </span>
60
- <span className="text-[10px]" style={{ color: 'var(--text-secondary)' }}>
61
- {item.time}
62
- </span>
63
- </div>
64
-
65
- {/* Content */}
66
- <div style={{ color: 'var(--text-secondary)' }}>
67
- {item.type === 'file' ? (
68
- <div className="flex items-center gap-1.5">
69
- <FileCode2 className="w-3 h-3" style={{ color: 'var(--success)' }} />
70
- <span className="font-mono text-[11px]">{item.content}</span>
71
- </div>
72
- ) : item.type === 'done' ? (
73
- <div className="flex items-center gap-1.5">
74
- <CheckCircle2 className="w-3 h-3" style={{ color: AGENT_COLORS[item.agent] }} />
75
- <span>{item.content}</span>
76
- </div>
77
- ) : item.type === 'stream' ? (
78
- <pre className="whitespace-pre-wrap font-mono text-[11px] max-h-24 overflow-y-auto leading-relaxed">
79
- {item.content.slice(-300)}
80
- <span className="typing-cursor" />
81
- </pre>
82
- ) : (
83
- <span>{item.content}</span>
84
- )}
85
- </div>
86
- </div>
87
- </div>
88
- ))}
89
- <div ref={feedEndRef} />
90
- </div>
91
- )
92
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/ChatInterface.jsx DELETED
@@ -1,195 +0,0 @@
1
- import React, { useState, useRef, useEffect } from 'react'
2
- import { Send, Sparkles, Loader2 } from 'lucide-react'
3
-
4
- const APP_TYPES = [
5
- { id: 'saas', label: 'SaaS Platform', emoji: '💼' },
6
- { id: 'ecommerce', label: 'E-Commerce', emoji: '🛒' },
7
- { id: 'marketplace', label: 'Marketplace', emoji: '🏪' },
8
- { id: 'social', label: 'Social Network', emoji: '👥' },
9
- { id: 'education', label: 'EdTech / LMS', emoji: '📚' },
10
- { id: 'health', label: 'HealthTech', emoji: '🏥' },
11
- { id: 'finance', label: 'FinTech', emoji: '💰' },
12
- { id: 'custom', label: 'Custom', emoji: '⚡' },
13
- ]
14
-
15
- const EXAMPLE_PROMPTS = [
16
- "Build a project management SaaS like Linear with team workspaces, sprint boards, and issue tracking",
17
- "Create an online course marketplace where instructors can sell video courses with progress tracking",
18
- "Build a subscription-based fitness app with workout plans, progress photos, and meal tracking",
19
- ]
20
-
21
- export default function ChatInterface({ messages, onGenerate, onFix, status }) {
22
- const [input, setInput] = useState('')
23
- const [appType, setAppType] = useState('saas')
24
- const [showTypes, setShowTypes] = useState(false)
25
- const chatEndRef = useRef(null)
26
- const inputRef = useRef(null)
27
-
28
- const isGenerating = !['idle', 'completed', 'error'].includes(status)
29
-
30
- useEffect(() => {
31
- chatEndRef.current?.scrollIntoView({ behavior: 'smooth' })
32
- }, [messages])
33
-
34
- const handleSubmit = (e) => {
35
- e.preventDefault()
36
- if (!input.trim() || isGenerating) return
37
- onGenerate(input.trim(), appType)
38
- setInput('')
39
- }
40
-
41
- const handleExampleClick = (prompt) => {
42
- setInput(prompt)
43
- inputRef.current?.focus()
44
- }
45
-
46
- return (
47
- <div className="flex flex-col h-full">
48
- {/* Messages area */}
49
- <div className="flex-1 overflow-y-auto p-4 space-y-4">
50
- {messages.length === 0 ? (
51
- /* Welcome screen */
52
- <div className="flex flex-col items-center justify-center h-full text-center px-4">
53
- <div className="text-5xl mb-4">🚀</div>
54
- <h2 className="text-xl font-bold gradient-text mb-2">
55
- Welcome to Nexus Builder
56
- </h2>
57
- <p className="text-sm mb-6" style={{ color: 'var(--text-secondary)' }}>
58
- Describe your app idea and 4 AI agents will build it for you —
59
- complete with auth, payments, analytics, and admin panel.
60
- </p>
61
-
62
- {/* App type selector */}
63
- <div className="w-full mb-4">
64
- <button
65
- onClick={() => setShowTypes(!showTypes)}
66
- className="w-full text-left text-xs font-medium px-3 py-2 rounded-lg border transition-all"
67
- style={{
68
- borderColor: 'var(--border)',
69
- background: 'var(--surface)',
70
- color: 'var(--text-secondary)',
71
- }}
72
- >
73
- App Type: {APP_TYPES.find(t => t.id === appType)?.emoji}{' '}
74
- {APP_TYPES.find(t => t.id === appType)?.label}
75
- </button>
76
- {showTypes && (
77
- <div className="mt-1 grid grid-cols-2 gap-1 p-2 rounded-lg border animate-fade-in"
78
- style={{ background: 'var(--surface)', borderColor: 'var(--border)' }}>
79
- {APP_TYPES.map(type => (
80
- <button
81
- key={type.id}
82
- onClick={() => { setAppType(type.id); setShowTypes(false) }}
83
- className="text-left text-xs px-2.5 py-2 rounded-md transition-all hover:scale-[1.02]"
84
- style={{
85
- background: appType === type.id ? 'var(--accent)22' : 'transparent',
86
- color: appType === type.id ? 'var(--accent)' : 'var(--text-secondary)',
87
- }}
88
- >
89
- {type.emoji} {type.label}
90
- </button>
91
- ))}
92
- </div>
93
- )}
94
- </div>
95
-
96
- {/* Example prompts */}
97
- <div className="w-full space-y-2">
98
- <p className="text-xs font-medium" style={{ color: 'var(--text-secondary)' }}>
99
- Try an example:
100
- </p>
101
- {EXAMPLE_PROMPTS.map((prompt, i) => (
102
- <button
103
- key={i}
104
- onClick={() => handleExampleClick(prompt)}
105
- className="w-full text-left text-xs p-3 rounded-lg border transition-all hover:scale-[1.01]"
106
- style={{
107
- borderColor: 'var(--border)',
108
- background: 'var(--surface)',
109
- color: 'var(--text-secondary)',
110
- }}
111
- >
112
- <Sparkles className="w-3 h-3 inline mr-1.5" style={{ color: 'var(--accent)' }} />
113
- {prompt}
114
- </button>
115
- ))}
116
- </div>
117
- </div>
118
- ) : (
119
- /* Chat messages */
120
- messages.map((msg, i) => (
121
- <div
122
- key={i}
123
- className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'} animate-slide-up`}
124
- >
125
- <div
126
- className="max-w-[85%] rounded-2xl px-4 py-3 text-sm leading-relaxed"
127
- style={{
128
- background: msg.role === 'user' ? 'var(--accent)' : 'var(--surface)',
129
- color: msg.role === 'user' ? 'white' : 'var(--text-primary)',
130
- border: msg.role === 'user' ? 'none' : '1px solid var(--border)',
131
- }}
132
- >
133
- <pre className="whitespace-pre-wrap font-sans">{msg.content}</pre>
134
- </div>
135
- </div>
136
- ))
137
- )}
138
-
139
- {isGenerating && (
140
- <div className="flex justify-start animate-slide-up">
141
- <div className="rounded-2xl px-4 py-3 text-sm flex items-center gap-2"
142
- style={{ background: 'var(--surface)', border: '1px solid var(--border)' }}>
143
- <Loader2 className="w-4 h-4 animate-spin" style={{ color: 'var(--accent)' }} />
144
- <span style={{ color: 'var(--text-secondary)' }}>Agents are working...</span>
145
- <span className="typing-cursor" />
146
- </div>
147
- </div>
148
- )}
149
-
150
- <div ref={chatEndRef} />
151
- </div>
152
-
153
- {/* Input area */}
154
- <form onSubmit={handleSubmit} className="p-3 border-t" style={{ borderColor: 'var(--border)' }}>
155
- <div className="flex items-end gap-2">
156
- <div className="flex-1 relative">
157
- <textarea
158
- ref={inputRef}
159
- value={input}
160
- onChange={(e) => setInput(e.target.value)}
161
- onKeyDown={(e) => {
162
- if (e.key === 'Enter' && !e.shiftKey) {
163
- e.preventDefault()
164
- handleSubmit(e)
165
- }
166
- }}
167
- placeholder="Describe the app you want to build..."
168
- rows={2}
169
- className="w-full resize-none rounded-xl px-4 py-3 text-sm outline-none transition-all"
170
- style={{
171
- background: 'var(--surface)',
172
- color: 'var(--text-primary)',
173
- border: '1px solid var(--border)',
174
- }}
175
- disabled={isGenerating}
176
- />
177
- </div>
178
- <button
179
- type="submit"
180
- disabled={!input.trim() || isGenerating}
181
- className="p-3 rounded-xl transition-all hover:scale-105 disabled:opacity-40 disabled:hover:scale-100"
182
- style={{
183
- background: 'var(--accent)',
184
- color: 'white',
185
- }}
186
- >
187
- {isGenerating
188
- ? <Loader2 className="w-5 h-5 animate-spin" />
189
- : <Send className="w-5 h-5" />}
190
- </button>
191
- </div>
192
- </form>
193
- </div>
194
- )
195
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/CodeView.jsx DELETED
@@ -1,128 +0,0 @@
1
- import React, { useMemo } from 'react'
2
- import { Copy, Check } from 'lucide-react'
3
-
4
- // Basic syntax highlighting without external deps
5
- function highlightCode(code, ext) {
6
- if (!code) return ''
7
-
8
- // Simple keyword-based highlighting
9
- const keywords = {
10
- js: /\b(const|let|var|function|return|if|else|for|while|class|import|export|from|default|async|await|try|catch|throw|new|this|typeof|instanceof|switch|case|break|continue)\b/g,
11
- py: /\b(def|class|import|from|return|if|elif|else|for|while|try|except|raise|with|as|yield|async|await|lambda|pass|break|continue|and|or|not|in|is|True|False|None)\b/g,
12
- sql: /\b(SELECT|FROM|WHERE|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|ALTER|DROP|INDEX|JOIN|LEFT|RIGHT|INNER|OUTER|ON|AND|OR|NOT|NULL|PRIMARY|KEY|FOREIGN|REFERENCES|CASCADE|UNIQUE|DEFAULT|CHECK|CONSTRAINT|GRANT|REVOKE|BEGIN|COMMIT|ROLLBACK|TRIGGER|FUNCTION|PROCEDURE|VIEW|ENABLE|ROW|LEVEL|SECURITY|POLICY|USING|WITH)\b/gi,
13
- }
14
-
15
- const lang = ['jsx', 'tsx', 'js', 'ts'].includes(ext) ? 'js'
16
- : ['py'].includes(ext) ? 'py'
17
- : ['sql'].includes(ext) ? 'sql'
18
- : 'js'
19
-
20
- let highlighted = code
21
- .replace(/&/g, '&amp;')
22
- .replace(/</g, '&lt;')
23
- .replace(/>/g, '&gt;')
24
-
25
- // Strings
26
- highlighted = highlighted.replace(
27
- /(["'`])(?:(?=(\\?))\2[\s\S])*?\1/g,
28
- '<span style="color:#22D3A8">$&</span>'
29
- )
30
-
31
- // Comments
32
- highlighted = highlighted.replace(
33
- /(\/\/.*$|#.*$)/gm,
34
- '<span style="color:#555577">$&</span>'
35
- )
36
- highlighted = highlighted.replace(
37
- /(\/\*[\s\S]*?\*\/|--.*$)/gm,
38
- '<span style="color:#555577">$&</span>'
39
- )
40
-
41
- // Keywords
42
- if (keywords[lang]) {
43
- highlighted = highlighted.replace(
44
- keywords[lang],
45
- '<span style="color:#6C63FF;font-weight:600">$&</span>'
46
- )
47
- }
48
-
49
- // Numbers
50
- highlighted = highlighted.replace(
51
- /\b(\d+\.?\d*)\b/g,
52
- '<span style="color:#FFB547">$&</span>'
53
- )
54
-
55
- return highlighted
56
- }
57
-
58
- export default function CodeView({ content, fileName }) {
59
- const [copied, setCopied] = React.useState(false)
60
-
61
- const ext = fileName?.split('.').pop()?.toLowerCase() || ''
62
- const highlighted = useMemo(
63
- () => highlightCode(content || '', ext),
64
- [content, ext]
65
- )
66
- const lineCount = (content || '').split('\n').length
67
-
68
- const handleCopy = async () => {
69
- if (!content) return
70
- await navigator.clipboard.writeText(content)
71
- setCopied(true)
72
- setTimeout(() => setCopied(false), 2000)
73
- }
74
-
75
- if (!content) {
76
- return (
77
- <div className="w-full h-full flex items-center justify-center">
78
- <p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
79
- Select a file from the tree to view its code
80
- </p>
81
- </div>
82
- )
83
- }
84
-
85
- return (
86
- <div className="w-full h-full flex flex-col rounded-lg overflow-hidden border"
87
- style={{ borderColor: 'var(--border)' }}>
88
- {/* File header */}
89
- <div className="flex items-center justify-between px-4 py-2 border-b"
90
- style={{ background: 'var(--bg)', borderColor: 'var(--border)' }}>
91
- <span className="text-xs font-mono" style={{ color: 'var(--text-secondary)' }}>
92
- {fileName}
93
- </span>
94
- <div className="flex items-center gap-2">
95
- <span className="text-[10px]" style={{ color: 'var(--text-secondary)' }}>
96
- {lineCount} lines
97
- </span>
98
- <button
99
- onClick={handleCopy}
100
- className="p-1 rounded transition-all hover:scale-110"
101
- style={{ color: copied ? 'var(--success)' : 'var(--text-secondary)' }}
102
- title="Copy code"
103
- >
104
- {copied ? <Check className="w-3.5 h-3.5" /> : <Copy className="w-3.5 h-3.5" />}
105
- </button>
106
- </div>
107
- </div>
108
-
109
- {/* Code content */}
110
- <div className="flex-1 overflow-auto" style={{ background: '#0D0D14' }}>
111
- <div className="flex">
112
- {/* Line numbers */}
113
- <div className="flex-shrink-0 text-right pr-4 pl-4 py-3 select-none"
114
- style={{ color: '#333355', fontSize: '12px', lineHeight: '1.6' }}>
115
- {Array.from({ length: lineCount }, (_, i) => (
116
- <div key={i}>{i + 1}</div>
117
- ))}
118
- </div>
119
- {/* Code */}
120
- <pre className="flex-1 py-3 pr-4 overflow-x-auto"
121
- style={{ fontSize: '12px', lineHeight: '1.6' }}>
122
- <code dangerouslySetInnerHTML={{ __html: highlighted }} />
123
- </pre>
124
- </div>
125
- </div>
126
- </div>
127
- )
128
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/ControlPanel.jsx DELETED
@@ -1,84 +0,0 @@
1
- import React, { useState } from 'react'
2
- import ChatInterface from './ChatInterface'
3
- import AgentFeed from './AgentFeed'
4
- import FileTree from './FileTree'
5
- import { MessageSquare, Activity, FolderTree } from 'lucide-react'
6
-
7
- const TABS = [
8
- { id: 'chat', label: 'Chat', icon: MessageSquare },
9
- { id: 'agents', label: 'Agents', icon: Activity },
10
- { id: 'files', label: 'Files', icon: FolderTree },
11
- ]
12
-
13
- export default function ControlPanel({
14
- messages, agentFeed, fileTree, files, selectedFile,
15
- onSelectFile, onGenerate, onFix, status
16
- }) {
17
- const [activeTab, setActiveTab] = useState('chat')
18
-
19
- return (
20
- <div className="w-full md:w-[420px] lg:w-[480px] flex flex-col border-r"
21
- style={{ borderColor: 'var(--border)', background: 'var(--bg)' }}>
22
- {/* Tab bar */}
23
- <div className="flex border-b" style={{ borderColor: 'var(--border)' }}>
24
- {TABS.map(tab => {
25
- const Icon = tab.icon
26
- const isActive = activeTab === tab.id
27
- return (
28
- <button
29
- key={tab.id}
30
- onClick={() => setActiveTab(tab.id)}
31
- className="flex-1 flex items-center justify-center gap-2 py-2.5 text-xs font-medium transition-all relative"
32
- style={{
33
- color: isActive ? 'var(--accent)' : 'var(--text-secondary)',
34
- background: isActive ? 'var(--surface)' : 'transparent',
35
- }}
36
- >
37
- <Icon className="w-3.5 h-3.5" />
38
- {tab.label}
39
- {tab.id === 'agents' && agentFeed.length > 0 && (
40
- <span className="ml-1 w-4 h-4 text-[10px] flex items-center justify-center rounded-full"
41
- style={{ background: 'var(--accent)33', color: 'var(--accent)' }}>
42
- {agentFeed.length}
43
- </span>
44
- )}
45
- {tab.id === 'files' && fileTree.length > 0 && (
46
- <span className="ml-1 w-4 h-4 text-[10px] flex items-center justify-center rounded-full"
47
- style={{ background: 'var(--success)33', color: 'var(--success)' }}>
48
- {fileTree.length}
49
- </span>
50
- )}
51
- {isActive && (
52
- <div className="absolute bottom-0 left-0 right-0 h-0.5"
53
- style={{ background: 'var(--accent)' }} />
54
- )}
55
- </button>
56
- )
57
- })}
58
- </div>
59
-
60
- {/* Tab content */}
61
- <div className="flex-1 overflow-hidden">
62
- {activeTab === 'chat' && (
63
- <ChatInterface
64
- messages={messages}
65
- onGenerate={onGenerate}
66
- onFix={onFix}
67
- status={status}
68
- />
69
- )}
70
- {activeTab === 'agents' && (
71
- <AgentFeed feed={agentFeed} />
72
- )}
73
- {activeTab === 'files' && (
74
- <FileTree
75
- tree={fileTree}
76
- files={files}
77
- selectedFile={selectedFile}
78
- onSelect={onSelectFile}
79
- />
80
- )}
81
- </div>
82
- </div>
83
- )
84
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/FileTree.jsx DELETED
@@ -1,141 +0,0 @@
1
- import React, { useMemo } from 'react'
2
- import {
3
- Folder, FolderOpen, FileCode2, FileJson, FileType2,
4
- Database, FileText, Settings
5
- } from 'lucide-react'
6
-
7
- const FILE_ICONS = {
8
- jsx: { icon: FileCode2, color: '#61DAFB' },
9
- tsx: { icon: FileCode2, color: '#3178C6' },
10
- js: { icon: FileCode2, color: '#F7DF1E' },
11
- ts: { icon: FileCode2, color: '#3178C6' },
12
- py: { icon: FileCode2, color: '#3776AB' },
13
- css: { icon: FileType2, color: '#1572B6' },
14
- html: { icon: FileCode2, color: '#E34F26' },
15
- json: { icon: FileJson, color: '#A8B9CC' },
16
- sql: { icon: Database, color: '#336791' },
17
- md: { icon: FileText, color: '#083FA1' },
18
- yml: { icon: Settings, color: '#CB171E' },
19
- yaml: { icon: Settings, color: '#CB171E' },
20
- env: { icon: Settings, color: '#ECD53F' },
21
- txt: { icon: FileText, color: '#8888AA' },
22
- }
23
-
24
- function buildTree(paths) {
25
- const root = {}
26
- for (const path of paths) {
27
- const parts = path.split('/')
28
- let current = root
29
- for (let i = 0; i < parts.length; i++) {
30
- const part = parts[i]
31
- if (i === parts.length - 1) {
32
- current[part] = path // leaf = full path
33
- } else {
34
- if (!current[part] || typeof current[part] === 'string') {
35
- current[part] = {}
36
- }
37
- current = current[part]
38
- }
39
- }
40
- }
41
- return root
42
- }
43
-
44
- function TreeNode({ name, node, selectedFile, onSelect, depth = 0 }) {
45
- const [open, setOpen] = React.useState(depth < 2)
46
- const isFile = typeof node === 'string'
47
- const isSelected = isFile && node === selectedFile
48
-
49
- const ext = isFile ? name.split('.').pop()?.toLowerCase() : ''
50
- const fileConfig = FILE_ICONS[ext] || { icon: FileText, color: 'var(--text-secondary)' }
51
- const Icon = isFile ? fileConfig.icon : (open ? FolderOpen : Folder)
52
- const iconColor = isFile ? fileConfig.color : 'var(--accent)'
53
-
54
- if (isFile) {
55
- return (
56
- <button
57
- onClick={() => onSelect(node)}
58
- className="w-full flex items-center gap-2 py-1 px-2 rounded text-xs transition-all hover:scale-[1.01] text-left"
59
- style={{
60
- paddingLeft: `${depth * 16 + 8}px`,
61
- background: isSelected ? 'var(--accent)15' : 'transparent',
62
- color: isSelected ? 'var(--accent)' : 'var(--text-secondary)',
63
- }}
64
- >
65
- <Icon className="w-3.5 h-3.5 flex-shrink-0" style={{ color: iconColor }} />
66
- <span className="truncate font-mono">{name}</span>
67
- </button>
68
- )
69
- }
70
-
71
- const entries = Object.entries(node).sort(([a, av], [b, bv]) => {
72
- const aIsFile = typeof av === 'string'
73
- const bIsFile = typeof bv === 'string'
74
- if (aIsFile !== bIsFile) return aIsFile ? 1 : -1
75
- return a.localeCompare(b)
76
- })
77
-
78
- return (
79
- <div>
80
- <button
81
- onClick={() => setOpen(!open)}
82
- className="w-full flex items-center gap-2 py-1 px-2 rounded text-xs font-medium transition-all text-left"
83
- style={{
84
- paddingLeft: `${depth * 16 + 8}px`,
85
- color: 'var(--text-primary)',
86
- }}
87
- >
88
- <Icon className="w-3.5 h-3.5 flex-shrink-0" style={{ color: iconColor }} />
89
- <span className="truncate">{name}</span>
90
- </button>
91
- {open && entries.map(([childName, childNode]) => (
92
- <TreeNode
93
- key={childName}
94
- name={childName}
95
- node={childNode}
96
- selectedFile={selectedFile}
97
- onSelect={onSelect}
98
- depth={depth + 1}
99
- />
100
- ))}
101
- </div>
102
- )
103
- }
104
-
105
- export default function FileTree({ tree, files, selectedFile, onSelect }) {
106
- const treeStructure = useMemo(() => buildTree(tree), [tree])
107
-
108
- if (tree.length === 0) {
109
- return (
110
- <div className="flex flex-col items-center justify-center h-full text-center px-6">
111
- <Folder className="w-12 h-12 mb-3" style={{ color: 'var(--text-secondary)', opacity: 0.4 }} />
112
- <p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
113
- Generated files will appear here.
114
- </p>
115
- </div>
116
- )
117
- }
118
-
119
- return (
120
- <div className="h-full overflow-y-auto py-2">
121
- <div className="px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider"
122
- style={{ color: 'var(--text-secondary)' }}>
123
- Project Files ({tree.length})
124
- </div>
125
- {Object.entries(treeStructure).sort(([a, av], [b, bv]) => {
126
- const aIsFile = typeof av === 'string'
127
- const bIsFile = typeof bv === 'string'
128
- if (aIsFile !== bIsFile) return aIsFile ? 1 : -1
129
- return a.localeCompare(b)
130
- }).map(([name, node]) => (
131
- <TreeNode
132
- key={name}
133
- name={name}
134
- node={node}
135
- selectedFile={selectedFile}
136
- onSelect={onSelect}
137
- />
138
- ))}
139
- </div>
140
- )
141
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/Header.jsx DELETED
@@ -1,80 +0,0 @@
1
- import React from 'react'
2
- import { useTheme } from './ThemeProvider'
3
- import { Sun, Moon, Settings, Zap } from 'lucide-react'
4
-
5
- const AGENT_LABELS = {
6
- research: { icon: '🌐', label: 'Research' },
7
- orchestrator: { icon: '🧠', label: 'Orchestrator' },
8
- frontend: { icon: '🎨', label: 'Frontend' },
9
- backend: { icon: '🔐', label: 'Backend' },
10
- }
11
-
12
- const STATUS_COLORS = {
13
- idle: 'bg-gray-500',
14
- active: 'bg-cyan-400 agent-active',
15
- done: 'bg-emerald-400',
16
- error: 'bg-red-400',
17
- }
18
-
19
- export default function Header({ agents, sessionId, status }) {
20
- const { theme, toggleTheme } = useTheme()
21
-
22
- return (
23
- <header className="glass flex items-center justify-between px-4 py-2.5 border-b z-50"
24
- style={{ borderColor: 'var(--border)' }}>
25
- {/* Logo */}
26
- <div className="flex items-center gap-3">
27
- <div className="flex items-center gap-2">
28
- <div className="relative">
29
- <Zap className="w-6 h-6" style={{ color: 'var(--accent)' }} />
30
- <div className="absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full bg-emerald-400 agent-active" />
31
- </div>
32
- <span className="text-xl font-bold tracking-tight font-mono gradient-text">
33
- NEXUS
34
- </span>
35
- </div>
36
- <span className="text-xs px-2 py-0.5 rounded-full font-medium"
37
- style={{ background: 'var(--accent)22', color: 'var(--accent)' }}>
38
- BUILDER
39
- </span>
40
- </div>
41
-
42
- {/* Agent Status Indicators */}
43
- <div className="hidden md:flex items-center gap-4">
44
- {Object.entries(agents).map(([key, agent]) => (
45
- <div key={key} className="flex items-center gap-2 text-xs" title={`${agent.name}: ${agent.status}`}>
46
- <span>{AGENT_LABELS[key]?.icon}</span>
47
- <div className={`w-2 h-2 rounded-full ${STATUS_COLORS[agent.status] || STATUS_COLORS.idle}`} />
48
- <span style={{ color: 'var(--text-secondary)' }}>
49
- {AGENT_LABELS[key]?.label}
50
- </span>
51
- </div>
52
- ))}
53
- </div>
54
-
55
- {/* Right controls */}
56
- <div className="flex items-center gap-3">
57
- {sessionId && (
58
- <span className="text-xs font-mono px-2 py-1 rounded"
59
- style={{ background: 'var(--surface)', color: 'var(--text-secondary)', border: '1px solid var(--border)' }}>
60
- {sessionId}
61
- </span>
62
- )}
63
-
64
- <button
65
- onClick={toggleTheme}
66
- className="p-2 rounded-lg transition-all hover:scale-110"
67
- style={{ color: 'var(--text-secondary)' }}
68
- title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
69
- >
70
- {theme === 'dark' ? <Sun className="w-4 h-4" /> : <Moon className="w-4 h-4" />}
71
- </button>
72
-
73
- <button className="p-2 rounded-lg transition-all"
74
- style={{ color: 'var(--text-secondary)' }}>
75
- <Settings className="w-4 h-4" />
76
- </button>
77
- </div>
78
- </header>
79
- )
80
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/LivePreview.jsx DELETED
@@ -1,159 +0,0 @@
1
- import React, { useState } from 'react'
2
- import SystemTabs from './SystemTabs'
3
- import CodeView from './CodeView'
4
- import {
5
- Eye, Code2, Download, Monitor, Tablet, Smartphone,
6
- ExternalLink, RefreshCw, Maximize2
7
- } from 'lucide-react'
8
-
9
- const VIEWPORT_SIZES = {
10
- desktop: { width: '100%', label: 'Desktop', icon: Monitor },
11
- tablet: { width: '768px', label: 'Tablet', icon: Tablet },
12
- mobile: { width: '375px', label: 'Mobile', icon: Smartphone },
13
- }
14
-
15
- export default function LivePreview({
16
- sessionId, files, selectedFile, previewSystem, onChangeSystem,
17
- viewMode, onChangeViewMode, onExport, status
18
- }) {
19
- const [viewport, setViewport] = useState('desktop')
20
- const [iframeKey, setIframeKey] = useState(0)
21
-
22
- const previewUrl = sessionId
23
- ? (previewSystem === 'preview'
24
- ? `/api/preview/${sessionId}`
25
- : `/api/preview/${sessionId}/${previewSystem}`)
26
- : null
27
-
28
- const selectedFileContent = selectedFile && files[selectedFile]
29
- ? files[selectedFile]
30
- : null
31
-
32
- const showCode = viewMode === 'code' || selectedFileContent
33
-
34
- return (
35
- <div className="flex-1 flex flex-col overflow-hidden" style={{ background: 'var(--bg)' }}>
36
- {/* System tabs */}
37
- <SystemTabs active={previewSystem} onChange={onChangeSystem} />
38
-
39
- {/* Toolbar */}
40
- <div className="flex items-center justify-between px-3 py-2 border-b"
41
- style={{ borderColor: 'var(--border)' }}>
42
- <div className="flex items-center gap-1">
43
- {/* View mode toggle */}
44
- <button
45
- onClick={() => onChangeViewMode('preview')}
46
- className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all"
47
- style={{
48
- background: viewMode === 'preview' ? 'var(--accent)15' : 'transparent',
49
- color: viewMode === 'preview' ? 'var(--accent)' : 'var(--text-secondary)',
50
- }}
51
- >
52
- <Eye className="w-3.5 h-3.5" />
53
- Preview
54
- </button>
55
- <button
56
- onClick={() => onChangeViewMode('code')}
57
- className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all"
58
- style={{
59
- background: viewMode === 'code' ? 'var(--accent)15' : 'transparent',
60
- color: viewMode === 'code' ? 'var(--accent)' : 'var(--text-secondary)',
61
- }}
62
- >
63
- <Code2 className="w-3.5 h-3.5" />
64
- Code
65
- </button>
66
- </div>
67
-
68
- <div className="flex items-center gap-1">
69
- {/* Viewport toggle */}
70
- {viewMode === 'preview' && Object.entries(VIEWPORT_SIZES).map(([key, val]) => {
71
- const Icon = val.icon
72
- return (
73
- <button
74
- key={key}
75
- onClick={() => setViewport(key)}
76
- className="p-1.5 rounded-md transition-all"
77
- style={{
78
- color: viewport === key ? 'var(--accent)' : 'var(--text-secondary)',
79
- background: viewport === key ? 'var(--accent)11' : 'transparent',
80
- }}
81
- title={val.label}
82
- >
83
- <Icon className="w-3.5 h-3.5" />
84
- </button>
85
- )
86
- })}
87
-
88
- {/* Refresh */}
89
- <button
90
- onClick={() => setIframeKey(k => k + 1)}
91
- className="p-1.5 rounded-md transition-all"
92
- style={{ color: 'var(--text-secondary)' }}
93
- title="Refresh preview"
94
- >
95
- <RefreshCw className="w-3.5 h-3.5" />
96
- </button>
97
-
98
- {/* Export */}
99
- {sessionId && (
100
- <button
101
- onClick={onExport}
102
- className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all hover:scale-105"
103
- style={{ background: 'var(--accent)', color: 'white' }}
104
- >
105
- <Download className="w-3.5 h-3.5" />
106
- Export ZIP
107
- </button>
108
- )}
109
- </div>
110
- </div>
111
-
112
- {/* Preview area */}
113
- <div className="flex-1 overflow-hidden flex items-center justify-center p-4"
114
- style={{ background: viewMode === 'code' ? 'var(--surface)' : '#0D0D12' }}>
115
- {showCode ? (
116
- <CodeView
117
- content={selectedFileContent || _getFirstFile(files)}
118
- fileName={selectedFile || Object.keys(files)[0] || ''}
119
- />
120
- ) : previewUrl ? (
121
- <div
122
- className="h-full rounded-lg overflow-hidden border transition-all duration-300"
123
- style={{
124
- width: VIEWPORT_SIZES[viewport].width,
125
- maxWidth: '100%',
126
- borderColor: 'var(--border)',
127
- }}
128
- >
129
- <iframe
130
- key={iframeKey}
131
- src={previewUrl}
132
- className="w-full h-full border-0"
133
- style={{ background: 'white', borderRadius: '8px' }}
134
- title="App Preview"
135
- sandbox="allow-scripts allow-same-origin"
136
- />
137
- </div>
138
- ) : (
139
- /* Empty state */
140
- <div className="text-center">
141
- <div className="text-6xl mb-4 opacity-20">🖥️</div>
142
- <h3 className="text-lg font-semibold mb-2" style={{ color: 'var(--text-primary)' }}>
143
- Live Preview
144
- </h3>
145
- <p className="text-sm" style={{ color: 'var(--text-secondary)' }}>
146
- Your generated app will be previewed here in real-time.
147
- <br />Start by describing your app idea in the chat.
148
- </p>
149
- </div>
150
- )}
151
- </div>
152
- </div>
153
- )
154
- }
155
-
156
- function _getFirstFile(files) {
157
- const keys = Object.keys(files)
158
- return keys.length > 0 ? files[keys[0]] : ''
159
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/StatusBar.jsx DELETED
@@ -1,104 +0,0 @@
1
- import React from 'react'
2
- import { Wifi, WifiOff, AlertTriangle, Files, Cpu } from 'lucide-react'
3
-
4
- const STATUS_LABELS = {
5
- idle: { label: 'Ready', color: 'var(--text-secondary)' },
6
- starting: { label: 'Starting...', color: 'var(--warning)' },
7
- connected: { label: 'Connected', color: 'var(--success)' },
8
- researching: { label: '🔍 Researching...', color: 'var(--accent-2)' },
9
- orchestrating: { label: '🧠 Creating Blueprint...', color: 'var(--accent)' },
10
- building: { label: '🚀 Building...', color: 'var(--accent)' },
11
- building_frontend: { label: '🎨 Building Frontend...', color: 'var(--success)' },
12
- building_backend: { label: '🔐 Building Backend...', color: 'var(--warning)' },
13
- merging: { label: '📦 Merging...', color: 'var(--accent-2)' },
14
- fixing: { label: '🔧 Fixing...', color: 'var(--warning)' },
15
- completed: { label: '✅ Complete', color: 'var(--success)' },
16
- error: { label: '❌ Error', color: 'var(--error)' },
17
- }
18
-
19
- export default function StatusBar({ status, sessionId, agents, errorCount, fileCount }) {
20
- const statusConfig = STATUS_LABELS[status] || STATUS_LABELS.idle
21
- const isActive = !['idle', 'completed', 'error'].includes(status)
22
-
23
- return (
24
- <footer
25
- className="flex items-center justify-between px-4 py-1.5 text-[11px] border-t"
26
- style={{
27
- borderColor: 'var(--border)',
28
- background: 'var(--surface)',
29
- color: 'var(--text-secondary)',
30
- }}
31
- >
32
- {/* Left: status */}
33
- <div className="flex items-center gap-3">
34
- <div className="flex items-center gap-1.5">
35
- {isActive ? (
36
- <Wifi className="w-3 h-3" style={{ color: 'var(--success)' }} />
37
- ) : (
38
- <WifiOff className="w-3 h-3" />
39
- )}
40
- <span style={{ color: statusConfig.color, fontWeight: 500 }}>
41
- {statusConfig.label}
42
- </span>
43
- </div>
44
-
45
- {/* Build progress */}
46
- {isActive && (
47
- <div className="hidden sm:flex items-center gap-1">
48
- {['researching', 'orchestrating', 'building', 'merging'].map((step, i) => {
49
- const stepStatuses = {
50
- researching: ['researching'],
51
- orchestrating: ['orchestrating'],
52
- building: ['building', 'building_frontend', 'building_backend'],
53
- merging: ['merging', 'completed'],
54
- }
55
- const isCurrentOrPast = stepStatuses[step]?.includes(status) ||
56
- ['researching', 'orchestrating', 'building', 'merging'].indexOf(step) <
57
- ['researching', 'orchestrating', 'building', 'merging'].findIndex(s =>
58
- stepStatuses[s]?.includes(status)
59
- )
60
- return (
61
- <React.Fragment key={step}>
62
- <div
63
- className="w-1.5 h-1.5 rounded-full"
64
- style={{
65
- background: stepStatuses[step]?.includes(status)
66
- ? 'var(--accent)'
67
- : isCurrentOrPast ? 'var(--success)' : 'var(--border)',
68
- }}
69
- />
70
- {i < 3 && (
71
- <div className="w-4 h-px" style={{ background: 'var(--border)' }} />
72
- )}
73
- </React.Fragment>
74
- )
75
- })}
76
- </div>
77
- )}
78
- </div>
79
-
80
- {/* Right: metrics */}
81
- <div className="flex items-center gap-4">
82
- {fileCount > 0 && (
83
- <div className="flex items-center gap-1">
84
- <Files className="w-3 h-3" />
85
- <span>{fileCount} files</span>
86
- </div>
87
- )}
88
- {errorCount > 0 && (
89
- <div className="flex items-center gap-1" style={{ color: 'var(--error)' }}>
90
- <AlertTriangle className="w-3 h-3" />
91
- <span>{errorCount} errors</span>
92
- </div>
93
- )}
94
- <div className="flex items-center gap-1">
95
- <Cpu className="w-3 h-3" />
96
- <span>HuggingFace CPU</span>
97
- </div>
98
- {sessionId && (
99
- <span className="font-mono">{sessionId}</span>
100
- )}
101
- </div>
102
- </footer>
103
- )
104
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/SystemTabs.jsx DELETED
@@ -1,37 +0,0 @@
1
- import React from 'react'
2
- import { Layout, Globe, Megaphone, BarChart3, Shield } from 'lucide-react'
3
-
4
- const SYSTEMS = [
5
- { id: 'preview', label: 'Overview', icon: Layout },
6
- { id: 'client_portal', label: 'Portal', icon: Layout },
7
- { id: 'public_landing', label: 'Landing', icon: Globe },
8
- { id: 'marketing_cms', label: 'Marketing', icon: Megaphone },
9
- { id: 'analytics_dashboard', label: 'Analytics', icon: BarChart3 },
10
- { id: 'admin_panel', label: 'Admin', icon: Shield },
11
- ]
12
-
13
- export default function SystemTabs({ active, onChange }) {
14
- return (
15
- <div className="flex items-center gap-0.5 overflow-x-auto px-2 py-1.5 border-b"
16
- style={{ borderColor: 'var(--border)' }}>
17
- {SYSTEMS.map(sys => {
18
- const Icon = sys.icon
19
- const isActive = active === sys.id
20
- return (
21
- <button
22
- key={sys.id}
23
- onClick={() => onChange(sys.id)}
24
- className="flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium whitespace-nowrap transition-all"
25
- style={{
26
- background: isActive ? 'var(--accent)15' : 'transparent',
27
- color: isActive ? 'var(--accent)' : 'var(--text-secondary)',
28
- }}
29
- >
30
- <Icon className="w-3.5 h-3.5" />
31
- {sys.label}
32
- </button>
33
- )
34
- })}
35
- </div>
36
- )
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/components/ThemeProvider.jsx DELETED
@@ -1,31 +0,0 @@
1
- import React, { createContext, useContext, useState, useEffect } from 'react'
2
-
3
- const ThemeContext = createContext()
4
-
5
- export function ThemeProvider({ children }) {
6
- const [theme, setTheme] = useState(() => {
7
- if (typeof window !== 'undefined') {
8
- return localStorage.getItem('nexus-theme') || 'dark'
9
- }
10
- return 'dark'
11
- })
12
-
13
- useEffect(() => {
14
- document.documentElement.setAttribute('data-theme', theme)
15
- localStorage.setItem('nexus-theme', theme)
16
- }, [theme])
17
-
18
- const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark')
19
-
20
- return (
21
- <ThemeContext.Provider value={{ theme, toggleTheme }}>
22
- {children}
23
- </ThemeContext.Provider>
24
- )
25
- }
26
-
27
- export function useTheme() {
28
- const ctx = useContext(ThemeContext)
29
- if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
30
- return ctx
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/hooks/useSSE.js DELETED
@@ -1,61 +0,0 @@
1
- import { useRef, useCallback } from 'react'
2
-
3
- export function useSSE(onEvent) {
4
- const eventSourceRef = useRef(null)
5
-
6
- const connect = useCallback((sessionId) => {
7
- // Close existing connection
8
- if (eventSourceRef.current) {
9
- eventSourceRef.current.close()
10
- }
11
-
12
- const url = `/api/stream/${sessionId}`
13
- const eventSource = new EventSource(url)
14
- eventSourceRef.current = eventSource
15
-
16
- const eventTypes = [
17
- 'agent_start', 'token', 'code_block', 'agent_done',
18
- 'error', 'file_created', 'done'
19
- ]
20
-
21
- eventTypes.forEach(type => {
22
- eventSource.addEventListener(type, (event) => {
23
- onEvent({ type, data: event.data })
24
- })
25
- })
26
-
27
- // Also handle generic messages
28
- eventSource.onmessage = (event) => {
29
- try {
30
- const data = JSON.parse(event.data)
31
- onEvent({ type: data.event_type || 'message', data: event.data })
32
- } catch {
33
- // ignore parse errors
34
- }
35
- }
36
-
37
- eventSource.onerror = (err) => {
38
- console.error('SSE error:', err)
39
- // Don't auto-reconnect on error to avoid infinite loops
40
- if (eventSource.readyState === EventSource.CLOSED) {
41
- onEvent({
42
- type: 'error',
43
- data: JSON.stringify({
44
- event_type: 'error',
45
- agent: 'system',
46
- content: 'Connection lost. Check the status and try again.'
47
- })
48
- })
49
- }
50
- }
51
- }, [onEvent])
52
-
53
- const disconnect = useCallback(() => {
54
- if (eventSourceRef.current) {
55
- eventSourceRef.current.close()
56
- eventSourceRef.current = null
57
- }
58
- }, [])
59
-
60
- return { connect, disconnect }
61
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/hooks/useTheme.js DELETED
@@ -1,2 +0,0 @@
1
- // Re-exported from ThemeProvider for convenience
2
- export { useTheme } from '../components/ThemeProvider'
 
 
 
fronend/src/index.css DELETED
@@ -1,126 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- :root {
6
- --bg: #0A0A0F;
7
- --surface: #111118;
8
- --border: #1E1E2E;
9
- --accent: #6C63FF;
10
- --accent-2: #00D9FF;
11
- --text-primary: #F0F0FF;
12
- --text-secondary: #8888AA;
13
- --success: #22D3A8;
14
- --error: #FF4D6D;
15
- --warning: #FFB547;
16
- }
17
-
18
- [data-theme="light"] {
19
- --bg: #F8F8FC;
20
- --surface: #FFFFFF;
21
- --border: #E0E0EF;
22
- --accent: #5B53E8;
23
- --accent-2: #0099CC;
24
- --text-primary: #0A0A1A;
25
- --text-secondary: #666688;
26
- --success: #16A085;
27
- --error: #E74C6F;
28
- --warning: #E6A030;
29
- }
30
-
31
- * {
32
- margin: 0;
33
- padding: 0;
34
- box-sizing: border-box;
35
- }
36
-
37
- html, body, #root {
38
- height: 100%;
39
- width: 100%;
40
- overflow: hidden;
41
- }
42
-
43
- body {
44
- font-family: 'Inter', system-ui, sans-serif;
45
- background-color: var(--bg);
46
- color: var(--text-primary);
47
- -webkit-font-smoothing: antialiased;
48
- -moz-osx-font-smoothing: grayscale;
49
- }
50
-
51
- /* Custom scrollbar */
52
- ::-webkit-scrollbar {
53
- width: 6px;
54
- height: 6px;
55
- }
56
- ::-webkit-scrollbar-track {
57
- background: transparent;
58
- }
59
- ::-webkit-scrollbar-thumb {
60
- background: var(--border);
61
- border-radius: 3px;
62
- }
63
- ::-webkit-scrollbar-thumb:hover {
64
- background: var(--text-secondary);
65
- }
66
-
67
- /* Code blocks */
68
- pre, code {
69
- font-family: 'JetBrains Mono', 'Fira Code', monospace;
70
- }
71
-
72
- /* Selection */
73
- ::selection {
74
- background: var(--accent);
75
- color: white;
76
- }
77
-
78
- /* Focus styles */
79
- *:focus-visible {
80
- outline: 2px solid var(--accent);
81
- outline-offset: 2px;
82
- }
83
-
84
- /* Glassmorphism utility */
85
- .glass {
86
- background: rgba(17, 17, 24, 0.7);
87
- backdrop-filter: blur(12px);
88
- -webkit-backdrop-filter: blur(12px);
89
- border: 1px solid var(--border);
90
- }
91
-
92
- [data-theme="light"] .glass {
93
- background: rgba(255, 255, 255, 0.75);
94
- }
95
-
96
- /* Agent status dots */
97
- @keyframes agentPulse {
98
- 0%, 100% { opacity: 1; transform: scale(1); }
99
- 50% { opacity: 0.5; transform: scale(1.3); }
100
- }
101
- .agent-active {
102
- animation: agentPulse 1.5s ease-in-out infinite;
103
- }
104
-
105
- /* Gradient text */
106
- .gradient-text {
107
- background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
108
- -webkit-background-clip: text;
109
- -webkit-text-fill-color: transparent;
110
- background-clip: text;
111
- }
112
-
113
- /* Typing indicator */
114
- @keyframes blink {
115
- 0%, 50% { opacity: 1; }
116
- 51%, 100% { opacity: 0; }
117
- }
118
- .typing-cursor {
119
- display: inline-block;
120
- width: 2px;
121
- height: 1.1em;
122
- background: var(--accent);
123
- margin-left: 2px;
124
- animation: blink 1s infinite;
125
- vertical-align: text-bottom;
126
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/main.jsx DELETED
@@ -1,13 +0,0 @@
1
- import React from 'react'
2
- import ReactDOM from 'react-dom/client'
3
- import App from './App'
4
- import { ThemeProvider } from './components/ThemeProvider'
5
- import './index.css'
6
-
7
- ReactDOM.createRoot(document.getElementById('root')).render(
8
- <React.StrictMode>
9
- <ThemeProvider>
10
- <App />
11
- </ThemeProvider>
12
- </React.StrictMode>,
13
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/src/utils/api.js DELETED
@@ -1,80 +0,0 @@
1
- const BASE = '' // Same origin
2
-
3
- export async function generateProject(prompt, appType = 'saas', systems = null) {
4
- const res = await fetch(`${BASE}/api/generate`, {
5
- method: 'POST',
6
- headers: { 'Content-Type': 'application/json' },
7
- body: JSON.stringify({ prompt, app_type: appType, systems }),
8
- })
9
- if (!res.ok) {
10
- const err = await res.text()
11
- throw new Error(`Generation failed: ${err}`)
12
- }
13
- return res.json()
14
- }
15
-
16
- export async function getProjectStatus(sessionId) {
17
- const res = await fetch(`${BASE}/api/status/${sessionId}`)
18
- if (!res.ok) throw new Error('Failed to get status')
19
- return res.json()
20
- }
21
-
22
- export async function getProjectFiles(sessionId) {
23
- const res = await fetch(`${BASE}/api/files/${sessionId}`)
24
- if (!res.ok) throw new Error('Failed to get files')
25
- return res.json()
26
- }
27
-
28
- export async function getFileContent(sessionId, path) {
29
- const res = await fetch(`${BASE}/api/file/${sessionId}/${path}`)
30
- if (!res.ok) throw new Error('Failed to get file')
31
- return res.json()
32
- }
33
-
34
- export async function exportProject(sessionId) {
35
- const res = await fetch(`${BASE}/api/export/${sessionId}`)
36
- if (!res.ok) throw new Error('Export failed')
37
- const blob = await res.blob()
38
- const url = URL.createObjectURL(blob)
39
- const a = document.createElement('a')
40
- a.href = url
41
- a.download = `nexus-project-${sessionId}.zip`
42
- document.body.appendChild(a)
43
- a.click()
44
- document.body.removeChild(a)
45
- URL.revokeObjectURL(url)
46
- }
47
-
48
- export async function fixBug(sessionId, errorMessage, filePath = null) {
49
- const res = await fetch(`${BASE}/api/fix/${sessionId}`, {
50
- method: 'POST',
51
- headers: { 'Content-Type': 'application/json' },
52
- body: JSON.stringify({ error_message: errorMessage, file_path: filePath }),
53
- })
54
- if (!res.ok) throw new Error('Fix request failed')
55
- return res.json()
56
- }
57
-
58
- // Keepalive ping to prevent HuggingFace sleep
59
- let keepaliveInterval = null
60
-
61
- export function startKeepalive() {
62
- if (keepaliveInterval) return
63
- keepaliveInterval = setInterval(async () => {
64
- try {
65
- await fetch(`${BASE}/api/health`)
66
- } catch {
67
- // ignore
68
- }
69
- }, 30000) // every 30 seconds
70
- }
71
-
72
- export function stopKeepalive() {
73
- if (keepaliveInterval) {
74
- clearInterval(keepaliveInterval)
75
- keepaliveInterval = null
76
- }
77
- }
78
-
79
- // Start keepalive on load
80
- startKeepalive()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/tailwind.config.js DELETED
@@ -1,48 +0,0 @@
1
- /** @type {import('tailwindcss').Config} */
2
- export default {
3
- content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4
- darkMode: 'class',
5
- theme: {
6
- extend: {
7
- colors: {
8
- nexus: {
9
- bg: 'var(--bg)',
10
- surface: 'var(--surface)',
11
- border: 'var(--border)',
12
- accent: 'var(--accent)',
13
- 'accent-2': 'var(--accent-2)',
14
- 'text-primary': 'var(--text-primary)',
15
- 'text-secondary': 'var(--text-secondary)',
16
- success: 'var(--success)',
17
- error: 'var(--error)',
18
- warning: 'var(--warning)',
19
- },
20
- },
21
- fontFamily: {
22
- sans: ['Inter', 'system-ui', 'sans-serif'],
23
- mono: ['JetBrains Mono', 'Fira Code', 'monospace'],
24
- },
25
- animation: {
26
- 'pulse-slow': 'pulse 3s ease-in-out infinite',
27
- 'fade-in': 'fadeIn 0.5s ease forwards',
28
- 'slide-up': 'slideUp 0.4s ease forwards',
29
- 'glow': 'glow 2s ease-in-out infinite alternate',
30
- },
31
- keyframes: {
32
- fadeIn: {
33
- '0%': { opacity: '0', transform: 'translateY(8px)' },
34
- '100%': { opacity: '1', transform: 'translateY(0)' },
35
- },
36
- slideUp: {
37
- '0%': { opacity: '0', transform: 'translateY(20px)' },
38
- '100%': { opacity: '1', transform: 'translateY(0)' },
39
- },
40
- glow: {
41
- '0%': { boxShadow: '0 0 5px var(--accent)33' },
42
- '100%': { boxShadow: '0 0 20px var(--accent)55' },
43
- },
44
- },
45
- },
46
- },
47
- plugins: [],
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fronend/vite.config.js DELETED
@@ -1,15 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- build: {
7
- outDir: 'dist',
8
- sourcemap: false,
9
- },
10
- server: {
11
- proxy: {
12
- '/api': 'http://localhost:7860',
13
- },
14
- },
15
- })