Commit ·
0e118b0
1
Parent(s): 951f367
fixing broken things.. ;)
Browse files- client/src/App.jsx +52 -102
- client/src/apiService.js +8 -4
client/src/App.jsx
CHANGED
|
@@ -8,13 +8,16 @@ const getFileIcon = (filename) => {
|
|
| 8 |
if (filename.endsWith('.js') || filename.endsWith('.jsx')) return <i className="fab fa-js" style={{ color: '#f7df1e' }}></i>;
|
| 9 |
if (filename.endsWith('.json')) return <i className="fas fa-brackets-curly" style={{ color: '#cb3837' }}></i>;
|
| 10 |
if (filename.endsWith('.py')) return <i className="fab fa-python" style={{ color: '#306998' }}></i>;
|
|
|
|
| 11 |
return <i className="fas fa-file-code" style={{ color: '#8b949e' }}></i>;
|
| 12 |
};
|
| 13 |
|
| 14 |
const getLanguage = (filename) => {
|
| 15 |
if (filename.endsWith('.html')) return 'html';
|
| 16 |
if (filename.endsWith('.css')) return 'css';
|
| 17 |
-
if (filename.endsWith('.js')) return 'javascript';
|
|
|
|
|
|
|
| 18 |
return 'plaintext';
|
| 19 |
};
|
| 20 |
|
|
@@ -22,30 +25,25 @@ function App() {
|
|
| 22 |
const [files, setFiles] = useState({
|
| 23 |
'index.html': { name: 'index.html', language: 'html', value: '\n<h1>Hello Shantanu ✨</h1>' },
|
| 24 |
'style.css': { name: 'style.css', language: 'css', value: '/* Type your CSS here */\nbody {\n background-color: #1e1e1e;\n color: white;\n}' },
|
| 25 |
-
'script.js': { name: 'script.js', language: 'javascript', value: 'console.log("Ethrix
|
| 26 |
});
|
| 27 |
|
| 28 |
const [activeTab, setActiveTab] = useState('index.html');
|
| 29 |
const [saveStatus, setSaveStatus] = useState('☁️ Synced');
|
| 30 |
|
| 31 |
-
|
| 32 |
-
const [showTerminal, setShowTerminal] = useState(true);
|
| 33 |
-
const [isTerminalMinimized, setIsTerminalMinimized] = useState(false);
|
| 34 |
const [showExplorer, setShowExplorer] = useState(true);
|
| 35 |
const [websiteTheme, setWebsiteTheme] = useState('Auto Theme');
|
| 36 |
-
const [activeMode, setActiveMode] = useState('
|
| 37 |
|
| 38 |
-
// File Creation & Rename States
|
| 39 |
const [isCreatingFile, setIsCreatingFile] = useState(false);
|
| 40 |
const [newFileName, setNewFileName] = useState('');
|
| 41 |
const [renamingFile, setRenamingFile] = useState(null);
|
| 42 |
const [renameInput, setRenameInput] = useState('');
|
| 43 |
|
| 44 |
-
|
| 45 |
-
const [chatMessages, setChatMessages] = useState([{ role: 'ai', text: 'Hi Shantanu! 🖤 Main Shanvika hu. Aaj kis theme ki website banani hai?' }]);
|
| 46 |
const [aiInput, setAiInput] = useState('');
|
| 47 |
const [aiWorkflowStatus, setAiWorkflowStatus] = useState('');
|
| 48 |
-
const [isTyping, setIsTyping] = useState(false);
|
| 49 |
|
| 50 |
const filesRef = useRef(files);
|
| 51 |
const terminalRef = useRef(null);
|
|
@@ -121,7 +119,6 @@ function App() {
|
|
| 121 |
if (activeTab === fileName) setActiveTab(Object.keys(newFiles)[0] || '');
|
| 122 |
};
|
| 123 |
|
| 124 |
-
// 🚀 THE NEW AGENTIC WORKFLOW SUBMIT LOGIC 🚀
|
| 125 |
const handleAISubmit = async (e) => {
|
| 126 |
if (e.key === 'Enter' && aiInput.trim() !== '') {
|
| 127 |
const prompt = aiInput.trim();
|
|
@@ -130,35 +127,29 @@ function App() {
|
|
| 130 |
setAiWorkflowStatus('🧠 Reading context & planning architecture...');
|
| 131 |
|
| 132 |
try {
|
| 133 |
-
// ✨ Context Builder: Saari current files ko array mein convert kar rahe hain
|
| 134 |
const existingFilesArray = Object.values(filesRef.current).map(f => ({
|
| 135 |
filename: f.name,
|
| 136 |
-
language: f.language,
|
| 137 |
code: f.value
|
| 138 |
}));
|
| 139 |
|
| 140 |
-
const
|
| 141 |
-
|
| 142 |
-
// apiService mein naya update bhej rahe hain
|
| 143 |
-
const generatedFiles = await api.generateCode(agenticPrompt, existingFilesArray, "gemini");
|
| 144 |
|
| 145 |
if (generatedFiles && generatedFiles.length > 0) {
|
| 146 |
-
setAiWorkflowStatus(`📄
|
| 147 |
|
| 148 |
const updatedFiles = { ...filesRef.current };
|
| 149 |
generatedFiles.forEach(f => {
|
| 150 |
-
updatedFiles[f.filename] = { name: f.filename, language: f.
|
| 151 |
});
|
| 152 |
|
| 153 |
setTimeout(() => {
|
| 154 |
setFiles(updatedFiles);
|
| 155 |
-
// Open HTML or the first changed file
|
| 156 |
const fileToOpen = generatedFiles.find(f => f.filename.endsWith('.html'))?.filename || generatedFiles[0].filename;
|
| 157 |
setActiveTab(fileToOpen);
|
| 158 |
-
|
| 159 |
if (!showExplorer) setShowExplorer(true);
|
| 160 |
setAiWorkflowStatus('');
|
| 161 |
-
setChatMessages(prev => [...prev, { role: 'ai', text: `✅
|
| 162 |
}, 1000);
|
| 163 |
}
|
| 164 |
} catch (err) {
|
|
@@ -168,37 +159,23 @@ function App() {
|
|
| 168 |
}
|
| 169 |
};
|
| 170 |
|
| 171 |
-
// ✨ FIX 1: Terminal Expand/Minimize Logic
|
| 172 |
-
const toggleTerminalSize = () => {
|
| 173 |
-
setIsTerminalMinimized(!isTerminalMinimized);
|
| 174 |
-
};
|
| 175 |
-
|
| 176 |
-
// Jab terminal minimize se wapas bada ho, usko resize karo taaki UI na fate
|
| 177 |
-
useEffect(() => {
|
| 178 |
-
if (!isTerminalMinimized && fitAddonRef.current) {
|
| 179 |
-
setTimeout(() => {
|
| 180 |
-
fitAddonRef.current.fit();
|
| 181 |
-
}, 350); // CSS transition ke baad resize
|
| 182 |
-
}
|
| 183 |
-
}, [isTerminalMinimized]);
|
| 184 |
-
|
| 185 |
const clearTerminal = () => {
|
| 186 |
if (xtermInstance.current) { xtermInstance.current.clear(); xtermInstance.current.write('user@ethrix:~$ '); }
|
| 187 |
};
|
| 188 |
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
};
|
| 194 |
|
| 195 |
-
// Real Terminal Lifecycle
|
| 196 |
useEffect(() => {
|
| 197 |
if (!showTerminal || !terminalRef.current) return;
|
|
|
|
|
|
|
| 198 |
|
| 199 |
-
// Agar terminal already nahi bana hai toh naya banao
|
| 200 |
if (!xtermInstance.current) {
|
| 201 |
-
const term = new window.Terminal({ theme: { background: '#0d1117', foreground: '#c9d1d9' }, fontFamily: '"Fira Code", monospace', fontSize: 13 });
|
| 202 |
const fitAddon = new window.FitAddon.FitAddon();
|
| 203 |
fitAddonRef.current = fitAddon;
|
| 204 |
|
|
@@ -207,7 +184,7 @@ function App() {
|
|
| 207 |
fitAddon.fit();
|
| 208 |
xtermInstance.current = term;
|
| 209 |
|
| 210 |
-
term.writeln('\x1b[1;
|
| 211 |
term.write('user@ethrix:~$ ');
|
| 212 |
|
| 213 |
let inputBuffer = '';
|
|
@@ -223,16 +200,12 @@ function App() {
|
|
| 223 |
} else { inputBuffer += key; term.write(key); }
|
| 224 |
});
|
| 225 |
|
| 226 |
-
const resizeObserver = new ResizeObserver(() => {
|
| 227 |
-
if (fitAddonRef.current && !isTerminalMinimized) fitAddonRef.current.fit();
|
| 228 |
-
});
|
| 229 |
resizeObserver.observe(terminalRef.current);
|
| 230 |
-
|
| 231 |
return () => resizeObserver.disconnect();
|
| 232 |
}
|
| 233 |
}, [showTerminal]);
|
| 234 |
|
| 235 |
-
// Cleanup jab terminal close (X) dabaya jaye
|
| 236 |
useEffect(() => {
|
| 237 |
if (!showTerminal && xtermInstance.current) {
|
| 238 |
xtermInstance.current.dispose();
|
|
@@ -244,20 +217,17 @@ function App() {
|
|
| 244 |
return (
|
| 245 |
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh', backgroundColor: '#0d1117', color: '#c9d1d9', fontFamily: 'sans-serif', overflow: 'hidden' }}>
|
| 246 |
|
| 247 |
-
{/* 🚀 MAIN CONTENT AREA (3 Panels) */}
|
| 248 |
<div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
|
| 249 |
|
| 250 |
-
{/*
|
| 251 |
{showExplorer && (
|
| 252 |
-
<div style={{ width: '250px', backgroundColor: '#010409', borderRight: '1px solid #30363d', display: 'flex', flexDirection: 'column' }}>
|
| 253 |
<div style={{ padding: '12px 15px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #30363d', backgroundColor: '#161b22' }}>
|
| 254 |
<span style={{ fontSize: '11px', fontWeight: 'bold', color: '#8b949e', letterSpacing: '1px' }}>EXPLORER</span>
|
| 255 |
<div style={{ display: 'flex', gap: '12px', color: '#c9d1d9', fontSize: '13px' }}>
|
| 256 |
-
<i className="fas fa-file-plus" onClick={() => setIsCreatingFile(true)} style={{ cursor: 'pointer' }} title="New File"></i>
|
| 257 |
-
<i className="fas fa-folder-plus" onClick={() => alert("Folder logic connecting soon!")} style={{ cursor: 'pointer' }} title="New Folder"></i>
|
| 258 |
<i className="fas fa-upload" onClick={() => alert("Upload connected soon!")} style={{ cursor: 'pointer' }} title="Upload"></i>
|
| 259 |
<i className="fas fa-download" onClick={() => alert("Download connected soon!")} style={{ cursor: 'pointer' }} title="Download"></i>
|
| 260 |
-
{/* ✨ FIX 3: GitHub Icon Wapas Aa Gaya! ✨ */}
|
| 261 |
<i className="fab fa-github" onClick={() => alert("GitHub Sync soon!")} style={{ cursor: 'pointer' }} title="GitHub"></i>
|
| 262 |
</div>
|
| 263 |
</div>
|
|
@@ -266,7 +236,7 @@ function App() {
|
|
| 266 |
{isCreatingFile && (
|
| 267 |
<div style={{ padding: '5px 10px', display: 'flex', alignItems: 'center', gap: '8px', backgroundColor: '#161b22', borderRadius: '4px', marginBottom: '5px' }}>
|
| 268 |
<i className="fas fa-file" style={{ color: '#8b949e' }}></i>
|
| 269 |
-
<input autoFocus type="text" value={newFileName} onChange={(e) => setNewFileName(e.target.value)} onKeyDown={handleCreateFile} onBlur={() => setIsCreatingFile(false)} placeholder="
|
| 270 |
</div>
|
| 271 |
)}
|
| 272 |
|
|
@@ -277,7 +247,7 @@ function App() {
|
|
| 277 |
{renamingFile === fileName ? (
|
| 278 |
<input autoFocus type="text" value={renameInput} onChange={(e) => setRenameInput(e.target.value)} onKeyDown={(e) => handleRenameFile(e, fileName)} onBlur={() => setRenamingFile(null)} style={{ background: '#161b22', border: '1px solid #58a6ff', color: '#fff', outline: 'none', width: '100%', fontSize: '13px', padding: '2px 4px' }} />
|
| 279 |
) : (
|
| 280 |
-
<span style={{ fontSize: '13px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{fileName}</span>
|
| 281 |
)}
|
| 282 |
</div>
|
| 283 |
{renamingFile !== fileName && (
|
|
@@ -292,26 +262,25 @@ function App() {
|
|
| 292 |
</div>
|
| 293 |
)}
|
| 294 |
|
| 295 |
-
{/*
|
| 296 |
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
| 297 |
|
| 298 |
-
<div style={{ height: '45px', backgroundColor: '#0d1117', borderBottom: '1px solid #30363d', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 15px' }}>
|
| 299 |
-
<div style={{ display: 'flex', alignItems: 'center' }}>
|
| 300 |
-
<div onClick={() => setShowExplorer(!showExplorer)} style={{ padding: '0 15px 0 0', cursor: 'pointer', color: showExplorer ? '#58a6ff' : '#8b949e', borderRight: '1px solid #30363d', marginRight: '10px' }}>
|
| 301 |
<i className="fas fa-bars" style={{ fontSize: '15px' }}></i>
|
| 302 |
</div>
|
| 303 |
{Object.keys(files).map(fileName => (
|
| 304 |
-
<div key={fileName} onClick={() => setActiveTab(fileName)} style={{ padding: '10px
|
| 305 |
-
{getFileIcon(fileName)} {fileName}
|
| 306 |
</div>
|
| 307 |
))}
|
| 308 |
</div>
|
| 309 |
-
<button onClick={runLivePreview} style={{ backgroundColor: '#238636', color: '#fff', border: 'none', padding: '6px 16px', borderRadius: '6px', cursor: 'pointer', fontSize: '13px', fontWeight: 'bold' }}>
|
| 310 |
-
<i className="fas fa-external-link-alt" style={{ marginRight: '6px' }}></i> Run
|
| 311 |
</button>
|
| 312 |
</div>
|
| 313 |
|
| 314 |
-
{/* ✨ FIXED EDITOR PANE (Scroll Bug Fixed) ✨ */}
|
| 315 |
<div style={{ flex: 1, backgroundColor: '#0d1117', position: 'relative', overflow: 'hidden' }}>
|
| 316 |
<Editor
|
| 317 |
height="100%"
|
|
@@ -319,56 +288,42 @@ function App() {
|
|
| 319 |
theme="vs-dark"
|
| 320 |
value={files[activeTab]?.value || ''}
|
| 321 |
onChange={(val) => setFiles({ ...files, [activeTab]: { ...files[activeTab], value: val } })}
|
| 322 |
-
options={{
|
| 323 |
-
minimap: { enabled: false },
|
| 324 |
-
fontSize: 14,
|
| 325 |
-
automaticLayout: true, // 🚀 YEH HAI ASLI JADOO (Fixes Canvas Bug)
|
| 326 |
-
scrollBeyondLastLine: false, // Extra khali space hatane ke liye
|
| 327 |
-
wordWrap: 'on' // Code screen ke bahar na jaye
|
| 328 |
-
}}
|
| 329 |
/>
|
| 330 |
</div>
|
| 331 |
|
| 332 |
-
{/*
|
| 333 |
{showTerminal && (
|
| 334 |
-
<div style={{ height:
|
| 335 |
-
<div style={{ padding: '
|
| 336 |
<div style={{ display: 'flex', gap: '15px' }}>
|
| 337 |
-
<span style={{
|
| 338 |
-
|
| 339 |
</div>
|
| 340 |
-
|
| 341 |
<div style={{ display: 'flex', gap: '15px', fontSize: '14px' }}>
|
| 342 |
-
<i className="fas fa-trash-alt" onClick={clearTerminal} style={{ cursor: 'pointer', color: '#c9d1d9' }} title="Clear
|
| 343 |
-
<i className={`fas ${isTerminalMinimized ? 'fa-chevron-up' : 'fa-chevron-down'}`} onClick={toggleTerminalSize} style={{ cursor: 'pointer', color: '#c9d1d9' }} title={isTerminalMinimized ? "Expand" : "Minimize"}></i>
|
| 344 |
<i className="fas fa-times" onClick={() => setShowTerminal(false)} style={{ cursor: 'pointer', color: '#c9d1d9' }} title="Close"></i>
|
| 345 |
</div>
|
| 346 |
</div>
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
| 350 |
-
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, opacity: isTerminalMinimized ? 0 : 1, pointerEvents: isTerminalMinimized ? 'none' : 'auto', transition: 'opacity 0.2s' }}>
|
| 351 |
-
<div ref={terminalRef} style={{ width: '100%', height: '100%', padding: '10px' }}></div>
|
| 352 |
-
</div>
|
| 353 |
</div>
|
| 354 |
</div>
|
| 355 |
)}
|
| 356 |
</div>
|
| 357 |
|
| 358 |
-
{/*
|
| 359 |
-
<div style={{ width: '320px', backgroundColor: '#010409', borderLeft: '1px solid #30363d', display: 'flex', flexDirection: 'column' }}>
|
| 360 |
<div style={{ padding: '15px', borderBottom: '1px solid #30363d', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
| 361 |
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
| 362 |
<div style={{ width: '30px', height: '30px', borderRadius: '50%', background: 'linear-gradient(45deg, #ff00cc, #3333ff)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontWeight: 'bold' }}>S</div>
|
| 363 |
<span style={{ fontSize: '14px', fontWeight: 'bold', color: '#c9d1d9' }}>Shanvika AI</span>
|
| 364 |
</div>
|
| 365 |
-
|
| 366 |
<select value={websiteTheme} onChange={(e) => setWebsiteTheme(e.target.value)} style={{ backgroundColor: '#161b22', color: '#8b949e', border: '1px solid #30363d', padding: '4px 8px', borderRadius: '4px', outline: 'none', fontSize: '11px', cursor: 'pointer' }}>
|
| 367 |
<option>Auto Theme</option>
|
| 368 |
<option>Professional</option>
|
| 369 |
<option>Sci-Fi</option>
|
| 370 |
<option>Fantasy</option>
|
| 371 |
-
<option>Minimalist</option>
|
| 372 |
</select>
|
| 373 |
</div>
|
| 374 |
|
|
@@ -383,7 +338,6 @@ function App() {
|
|
| 383 |
</div>
|
| 384 |
</div>
|
| 385 |
))}
|
| 386 |
-
|
| 387 |
{aiWorkflowStatus && (
|
| 388 |
<div style={{ alignSelf: 'flex-start', maxWidth: '85%' }}>
|
| 389 |
<div style={{ padding: '8px 12px', borderRadius: '8px', fontSize: '12px', backgroundColor: 'rgba(88, 166, 255, 0.1)', color: '#58a6ff', border: '1px solid rgba(88, 166, 255, 0.2)', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
@@ -396,7 +350,7 @@ function App() {
|
|
| 396 |
|
| 397 |
<div style={{ padding: '15px', borderTop: '1px solid #30363d', backgroundColor: '#0d1117' }}>
|
| 398 |
<div style={{ position: 'relative' }}>
|
| 399 |
-
<textarea value={aiInput} onChange={(e) => setAiInput(e.target.value)} onKeyDown={handleAISubmit} disabled={!!aiWorkflowStatus} placeholder="
|
| 400 |
<button onClick={() => handleAISubmit({ key: 'Enter' })} disabled={!!aiWorkflowStatus || !aiInput.trim()} style={{ position: 'absolute', bottom: '10px', right: '10px', background: 'transparent', border: 'none', color: '#58a6ff', cursor: 'pointer', fontSize: '16px', opacity: (aiWorkflowStatus || !aiInput.trim()) ? 0.5 : 1 }}>
|
| 401 |
<i className="fas fa-paper-plane"></i>
|
| 402 |
</button>
|
|
@@ -406,15 +360,12 @@ function App() {
|
|
| 406 |
|
| 407 |
</div>
|
| 408 |
|
| 409 |
-
{/*
|
| 410 |
-
<div style={{ height: '24px', backgroundColor: '#010409', borderTop: '1px solid #30363d', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 15px', fontSize: '11px', color: '#c9d1d9', zIndex: 10 }}>
|
| 411 |
<div style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
|
| 412 |
-
|
| 413 |
-
{/* ✨ Yahan Se Terminal Wapas Aayega ✨ */}
|
| 414 |
-
<div onClick={reopenTerminal} style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '5px', color: showTerminal ? '#58a6ff' : '#8b949e', transition: 'color 0.2s' }} title="Reopen Terminal">
|
| 415 |
<i className="fas fa-terminal"></i> Terminal
|
| 416 |
</div>
|
| 417 |
-
|
| 418 |
<div style={{ display: 'flex', alignItems: 'center', gap: '5px', color: saveStatus.includes('Error') ? '#f85149' : '#3fb950' }}>
|
| 419 |
<i className="fas fa-cloud"></i> {saveStatus}
|
| 420 |
</div>
|
|
@@ -423,8 +374,7 @@ function App() {
|
|
| 423 |
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
|
| 424 |
<select value={activeMode} onChange={(e) => setActiveMode(e.target.value)} style={{ background: 'transparent', border: 'none', color: '#8b949e', outline: 'none', fontSize: '11px', cursor: 'pointer' }}>
|
| 425 |
<option>Copilot Mode</option>
|
| 426 |
-
<option>
|
| 427 |
-
<option>Debug Mode</option>
|
| 428 |
</select>
|
| 429 |
<div><i className="fas fa-code-branch" style={{ marginRight: '5px' }}></i> main</div>
|
| 430 |
<div><i className="fas fa-check-double" style={{ marginRight: '5px', color: '#3fb950' }}></i> Ethrix Prettier</div>
|
|
|
|
| 8 |
if (filename.endsWith('.js') || filename.endsWith('.jsx')) return <i className="fab fa-js" style={{ color: '#f7df1e' }}></i>;
|
| 9 |
if (filename.endsWith('.json')) return <i className="fas fa-brackets-curly" style={{ color: '#cb3837' }}></i>;
|
| 10 |
if (filename.endsWith('.py')) return <i className="fab fa-python" style={{ color: '#306998' }}></i>;
|
| 11 |
+
if (filename.includes('/')) return <i className="fas fa-folder-open" style={{ color: '#e3b341' }}></i>;
|
| 12 |
return <i className="fas fa-file-code" style={{ color: '#8b949e' }}></i>;
|
| 13 |
};
|
| 14 |
|
| 15 |
const getLanguage = (filename) => {
|
| 16 |
if (filename.endsWith('.html')) return 'html';
|
| 17 |
if (filename.endsWith('.css')) return 'css';
|
| 18 |
+
if (filename.endsWith('.js') || filename.endsWith('.jsx')) return 'javascript';
|
| 19 |
+
if (filename.endsWith('.json')) return 'json';
|
| 20 |
+
if (filename.endsWith('.py')) return 'python';
|
| 21 |
return 'plaintext';
|
| 22 |
};
|
| 23 |
|
|
|
|
| 25 |
const [files, setFiles] = useState({
|
| 26 |
'index.html': { name: 'index.html', language: 'html', value: '\n<h1>Hello Shantanu ✨</h1>' },
|
| 27 |
'style.css': { name: 'style.css', language: 'css', value: '/* Type your CSS here */\nbody {\n background-color: #1e1e1e;\n color: white;\n}' },
|
| 28 |
+
'script.js': { name: 'script.js', language: 'javascript', value: 'console.log("Ethrix God Mode Active!");' }
|
| 29 |
});
|
| 30 |
|
| 31 |
const [activeTab, setActiveTab] = useState('index.html');
|
| 32 |
const [saveStatus, setSaveStatus] = useState('☁️ Synced');
|
| 33 |
|
| 34 |
+
const [showTerminal, setShowTerminal] = useState(false);
|
|
|
|
|
|
|
| 35 |
const [showExplorer, setShowExplorer] = useState(true);
|
| 36 |
const [websiteTheme, setWebsiteTheme] = useState('Auto Theme');
|
| 37 |
+
const [activeMode, setActiveMode] = useState('Agentic Mode');
|
| 38 |
|
|
|
|
| 39 |
const [isCreatingFile, setIsCreatingFile] = useState(false);
|
| 40 |
const [newFileName, setNewFileName] = useState('');
|
| 41 |
const [renamingFile, setRenamingFile] = useState(null);
|
| 42 |
const [renameInput, setRenameInput] = useState('');
|
| 43 |
|
| 44 |
+
const [chatMessages, setChatMessages] = useState([{ role: 'ai', text: 'Hi Shantanu! 🖤 Main Shanvika hu. Agentic Workflow is 100% online. Kya banana hai aaj?' }]);
|
|
|
|
| 45 |
const [aiInput, setAiInput] = useState('');
|
| 46 |
const [aiWorkflowStatus, setAiWorkflowStatus] = useState('');
|
|
|
|
| 47 |
|
| 48 |
const filesRef = useRef(files);
|
| 49 |
const terminalRef = useRef(null);
|
|
|
|
| 119 |
if (activeTab === fileName) setActiveTab(Object.keys(newFiles)[0] || '');
|
| 120 |
};
|
| 121 |
|
|
|
|
| 122 |
const handleAISubmit = async (e) => {
|
| 123 |
if (e.key === 'Enter' && aiInput.trim() !== '') {
|
| 124 |
const prompt = aiInput.trim();
|
|
|
|
| 127 |
setAiWorkflowStatus('🧠 Reading context & planning architecture...');
|
| 128 |
|
| 129 |
try {
|
|
|
|
| 130 |
const existingFilesArray = Object.values(filesRef.current).map(f => ({
|
| 131 |
filename: f.name,
|
| 132 |
+
language: f.language || 'plaintext',
|
| 133 |
code: f.value
|
| 134 |
}));
|
| 135 |
|
| 136 |
+
const generatedFiles = await api.generateCode(prompt, existingFilesArray, "gemini");
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
if (generatedFiles && generatedFiles.length > 0) {
|
| 139 |
+
setAiWorkflowStatus(`📄 Updating ${generatedFiles.length} files...`);
|
| 140 |
|
| 141 |
const updatedFiles = { ...filesRef.current };
|
| 142 |
generatedFiles.forEach(f => {
|
| 143 |
+
updatedFiles[f.filename] = { name: f.filename, language: getLanguage(f.filename), value: f.code };
|
| 144 |
});
|
| 145 |
|
| 146 |
setTimeout(() => {
|
| 147 |
setFiles(updatedFiles);
|
|
|
|
| 148 |
const fileToOpen = generatedFiles.find(f => f.filename.endsWith('.html'))?.filename || generatedFiles[0].filename;
|
| 149 |
setActiveTab(fileToOpen);
|
|
|
|
| 150 |
if (!showExplorer) setShowExplorer(true);
|
| 151 |
setAiWorkflowStatus('');
|
| 152 |
+
setChatMessages(prev => [...prev, { role: 'ai', text: `✅ Code generated beautifully! ✨ Press Run in New Tab to see it. Pata hai, main changes sirf unhi files mein karti hu jinki zarurat hoti hai!` }]);
|
| 153 |
}, 1000);
|
| 154 |
}
|
| 155 |
} catch (err) {
|
|
|
|
| 159 |
}
|
| 160 |
};
|
| 161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
const clearTerminal = () => {
|
| 163 |
if (xtermInstance.current) { xtermInstance.current.clear(); xtermInstance.current.write('user@ethrix:~$ '); }
|
| 164 |
};
|
| 165 |
|
| 166 |
+
useEffect(() => {
|
| 167 |
+
if (showTerminal && fitAddonRef.current) {
|
| 168 |
+
setTimeout(() => { fitAddonRef.current.fit(); }, 50);
|
| 169 |
+
}
|
| 170 |
+
}, [showTerminal]);
|
| 171 |
|
|
|
|
| 172 |
useEffect(() => {
|
| 173 |
if (!showTerminal || !terminalRef.current) return;
|
| 174 |
+
// Check if CDN loaded
|
| 175 |
+
if (!window.Terminal || !window.FitAddon) return;
|
| 176 |
|
|
|
|
| 177 |
if (!xtermInstance.current) {
|
| 178 |
+
const term = new window.Terminal({ theme: { background: '#0d1117', foreground: '#c9d1d9', cursor: '#58a6ff' }, fontFamily: '"Fira Code", monospace', fontSize: 13, cursorBlink: true });
|
| 179 |
const fitAddon = new window.FitAddon.FitAddon();
|
| 180 |
fitAddonRef.current = fitAddon;
|
| 181 |
|
|
|
|
| 184 |
fitAddon.fit();
|
| 185 |
xtermInstance.current = term;
|
| 186 |
|
| 187 |
+
term.writeln('\x1b[1;36mEthrix Secure Cloud Terminal 1.0\x1b[0m');
|
| 188 |
term.write('user@ethrix:~$ ');
|
| 189 |
|
| 190 |
let inputBuffer = '';
|
|
|
|
| 200 |
} else { inputBuffer += key; term.write(key); }
|
| 201 |
});
|
| 202 |
|
| 203 |
+
const resizeObserver = new ResizeObserver(() => { if (fitAddonRef.current) fitAddonRef.current.fit(); });
|
|
|
|
|
|
|
| 204 |
resizeObserver.observe(terminalRef.current);
|
|
|
|
| 205 |
return () => resizeObserver.disconnect();
|
| 206 |
}
|
| 207 |
}, [showTerminal]);
|
| 208 |
|
|
|
|
| 209 |
useEffect(() => {
|
| 210 |
if (!showTerminal && xtermInstance.current) {
|
| 211 |
xtermInstance.current.dispose();
|
|
|
|
| 217 |
return (
|
| 218 |
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh', backgroundColor: '#0d1117', color: '#c9d1d9', fontFamily: 'sans-serif', overflow: 'hidden' }}>
|
| 219 |
|
|
|
|
| 220 |
<div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
|
| 221 |
|
| 222 |
+
{/* EXPLORER */}
|
| 223 |
{showExplorer && (
|
| 224 |
+
<div style={{ width: '250px', backgroundColor: '#010409', borderRight: '1px solid #30363d', display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
|
| 225 |
<div style={{ padding: '12px 15px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid #30363d', backgroundColor: '#161b22' }}>
|
| 226 |
<span style={{ fontSize: '11px', fontWeight: 'bold', color: '#8b949e', letterSpacing: '1px' }}>EXPLORER</span>
|
| 227 |
<div style={{ display: 'flex', gap: '12px', color: '#c9d1d9', fontSize: '13px' }}>
|
| 228 |
+
<i className="fas fa-file-plus" onClick={() => setIsCreatingFile(true)} style={{ cursor: 'pointer' }} title="New File/Folder"></i>
|
|
|
|
| 229 |
<i className="fas fa-upload" onClick={() => alert("Upload connected soon!")} style={{ cursor: 'pointer' }} title="Upload"></i>
|
| 230 |
<i className="fas fa-download" onClick={() => alert("Download connected soon!")} style={{ cursor: 'pointer' }} title="Download"></i>
|
|
|
|
| 231 |
<i className="fab fa-github" onClick={() => alert("GitHub Sync soon!")} style={{ cursor: 'pointer' }} title="GitHub"></i>
|
| 232 |
</div>
|
| 233 |
</div>
|
|
|
|
| 236 |
{isCreatingFile && (
|
| 237 |
<div style={{ padding: '5px 10px', display: 'flex', alignItems: 'center', gap: '8px', backgroundColor: '#161b22', borderRadius: '4px', marginBottom: '5px' }}>
|
| 238 |
<i className="fas fa-file" style={{ color: '#8b949e' }}></i>
|
| 239 |
+
<input autoFocus type="text" value={newFileName} onChange={(e) => setNewFileName(e.target.value)} onKeyDown={handleCreateFile} onBlur={() => setIsCreatingFile(false)} placeholder="components/nav.js" style={{ background: 'transparent', border: 'none', color: '#fff', outline: 'none', width: '100%', fontSize: '13px' }} />
|
| 240 |
</div>
|
| 241 |
)}
|
| 242 |
|
|
|
|
| 247 |
{renamingFile === fileName ? (
|
| 248 |
<input autoFocus type="text" value={renameInput} onChange={(e) => setRenameInput(e.target.value)} onKeyDown={(e) => handleRenameFile(e, fileName)} onBlur={() => setRenamingFile(null)} style={{ background: '#161b22', border: '1px solid #58a6ff', color: '#fff', outline: 'none', width: '100%', fontSize: '13px', padding: '2px 4px' }} />
|
| 249 |
) : (
|
| 250 |
+
<span style={{ fontSize: '13px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} title={fileName}>{fileName}</span>
|
| 251 |
)}
|
| 252 |
</div>
|
| 253 |
{renamingFile !== fileName && (
|
|
|
|
| 262 |
</div>
|
| 263 |
)}
|
| 264 |
|
| 265 |
+
{/* MIDDLE PANE */}
|
| 266 |
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
| 267 |
|
| 268 |
+
<div style={{ height: '45px', backgroundColor: '#0d1117', borderBottom: '1px solid #30363d', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 15px', flexShrink: 0 }}>
|
| 269 |
+
<div style={{ display: 'flex', alignItems: 'center', overflowX: 'auto' }}>
|
| 270 |
+
<div onClick={() => setShowExplorer(!showExplorer)} style={{ padding: '0 15px 0 0', cursor: 'pointer', color: showExplorer ? '#58a6ff' : '#8b949e', borderRight: '1px solid #30363d', marginRight: '10px', flexShrink: 0 }}>
|
| 271 |
<i className="fas fa-bars" style={{ fontSize: '15px' }}></i>
|
| 272 |
</div>
|
| 273 |
{Object.keys(files).map(fileName => (
|
| 274 |
+
<div key={fileName} onClick={() => setActiveTab(fileName)} style={{ padding: '10px 15px', fontSize: '13px', cursor: 'pointer', backgroundColor: activeTab === fileName ? '#161b22' : 'transparent', borderTop: activeTab === fileName ? '2px solid #58a6ff' : '2px solid transparent', color: activeTab === fileName ? '#c9d1d9' : '#8b949e', display: 'flex', alignItems: 'center', gap: '8px', whiteSpace: 'nowrap' }}>
|
| 275 |
+
{getFileIcon(fileName)} {fileName.split('/').pop()}
|
| 276 |
</div>
|
| 277 |
))}
|
| 278 |
</div>
|
| 279 |
+
<button onClick={runLivePreview} style={{ backgroundColor: '#238636', color: '#fff', border: 'none', padding: '6px 16px', borderRadius: '6px', cursor: 'pointer', fontSize: '13px', fontWeight: 'bold', flexShrink: 0, marginLeft: '10px' }}>
|
| 280 |
+
<i className="fas fa-external-link-alt" style={{ marginRight: '6px' }}></i> Run
|
| 281 |
</button>
|
| 282 |
</div>
|
| 283 |
|
|
|
|
| 284 |
<div style={{ flex: 1, backgroundColor: '#0d1117', position: 'relative', overflow: 'hidden' }}>
|
| 285 |
<Editor
|
| 286 |
height="100%"
|
|
|
|
| 288 |
theme="vs-dark"
|
| 289 |
value={files[activeTab]?.value || ''}
|
| 290 |
onChange={(val) => setFiles({ ...files, [activeTab]: { ...files[activeTab], value: val } })}
|
| 291 |
+
options={{ minimap: { enabled: false }, fontSize: 14, automaticLayout: true, wordWrap: 'on' }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
/>
|
| 293 |
</div>
|
| 294 |
|
| 295 |
+
{/* TERMINAL */}
|
| 296 |
{showTerminal && (
|
| 297 |
+
<div style={{ height: '250px', backgroundColor: '#010409', borderTop: '1px solid #30363d', display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
|
| 298 |
+
<div style={{ height: '35px', padding: '0 15px', fontSize: '11px', color: '#8b949e', borderBottom: '1px solid #30363d', display: 'flex', justifyContent: 'space-between', alignItems: 'center', backgroundColor: '#161b22', userSelect: 'none' }}>
|
| 299 |
<div style={{ display: 'flex', gap: '15px' }}>
|
| 300 |
+
<span style={{ color: '#c9d1d9', fontWeight: 'bold' }}>TERMINAL</span>
|
| 301 |
+
<span style={{ cursor: 'pointer' }}>OUTPUT</span>
|
| 302 |
</div>
|
|
|
|
| 303 |
<div style={{ display: 'flex', gap: '15px', fontSize: '14px' }}>
|
| 304 |
+
<i className="fas fa-trash-alt" onClick={clearTerminal} style={{ cursor: 'pointer', color: '#c9d1d9' }} title="Clear"></i>
|
|
|
|
| 305 |
<i className="fas fa-times" onClick={() => setShowTerminal(false)} style={{ cursor: 'pointer', color: '#c9d1d9' }} title="Close"></i>
|
| 306 |
</div>
|
| 307 |
</div>
|
| 308 |
+
<div style={{ flex: 1, minHeight: 0 }}>
|
| 309 |
+
<div ref={terminalRef} style={{ width: '100%', height: '100%', padding: '10px' }}></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
)}
|
| 313 |
</div>
|
| 314 |
|
| 315 |
+
{/* AI PANEL */}
|
| 316 |
+
<div style={{ width: '320px', backgroundColor: '#010409', borderLeft: '1px solid #30363d', display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
|
| 317 |
<div style={{ padding: '15px', borderBottom: '1px solid #30363d', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
| 318 |
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
| 319 |
<div style={{ width: '30px', height: '30px', borderRadius: '50%', background: 'linear-gradient(45deg, #ff00cc, #3333ff)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontWeight: 'bold' }}>S</div>
|
| 320 |
<span style={{ fontSize: '14px', fontWeight: 'bold', color: '#c9d1d9' }}>Shanvika AI</span>
|
| 321 |
</div>
|
|
|
|
| 322 |
<select value={websiteTheme} onChange={(e) => setWebsiteTheme(e.target.value)} style={{ backgroundColor: '#161b22', color: '#8b949e', border: '1px solid #30363d', padding: '4px 8px', borderRadius: '4px', outline: 'none', fontSize: '11px', cursor: 'pointer' }}>
|
| 323 |
<option>Auto Theme</option>
|
| 324 |
<option>Professional</option>
|
| 325 |
<option>Sci-Fi</option>
|
| 326 |
<option>Fantasy</option>
|
|
|
|
| 327 |
</select>
|
| 328 |
</div>
|
| 329 |
|
|
|
|
| 338 |
</div>
|
| 339 |
</div>
|
| 340 |
))}
|
|
|
|
| 341 |
{aiWorkflowStatus && (
|
| 342 |
<div style={{ alignSelf: 'flex-start', maxWidth: '85%' }}>
|
| 343 |
<div style={{ padding: '8px 12px', borderRadius: '8px', fontSize: '12px', backgroundColor: 'rgba(88, 166, 255, 0.1)', color: '#58a6ff', border: '1px solid rgba(88, 166, 255, 0.2)', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
|
|
| 350 |
|
| 351 |
<div style={{ padding: '15px', borderTop: '1px solid #30363d', backgroundColor: '#0d1117' }}>
|
| 352 |
<div style={{ position: 'relative' }}>
|
| 353 |
+
<textarea value={aiInput} onChange={(e) => setAiInput(e.target.value)} onKeyDown={handleAISubmit} disabled={!!aiWorkflowStatus} placeholder="Request a new site or changes..." style={{ width: '100%', height: '80px', backgroundColor: '#010409', border: '1px solid #30363d', borderRadius: '8px', color: '#c9d1d9', padding: '10px', fontSize: '13px', resize: 'none', outline: 'none', opacity: aiWorkflowStatus ? 0.5 : 1 }} />
|
| 354 |
<button onClick={() => handleAISubmit({ key: 'Enter' })} disabled={!!aiWorkflowStatus || !aiInput.trim()} style={{ position: 'absolute', bottom: '10px', right: '10px', background: 'transparent', border: 'none', color: '#58a6ff', cursor: 'pointer', fontSize: '16px', opacity: (aiWorkflowStatus || !aiInput.trim()) ? 0.5 : 1 }}>
|
| 355 |
<i className="fas fa-paper-plane"></i>
|
| 356 |
</button>
|
|
|
|
| 360 |
|
| 361 |
</div>
|
| 362 |
|
| 363 |
+
{/* STATUS BAR */}
|
| 364 |
+
<div style={{ height: '24px', backgroundColor: '#010409', borderTop: '1px solid #30363d', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 15px', fontSize: '11px', color: '#c9d1d9', zIndex: 10, flexShrink: 0 }}>
|
| 365 |
<div style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
|
| 366 |
+
<div onClick={() => setShowTerminal(!showTerminal)} style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '5px', color: showTerminal ? '#58a6ff' : '#8b949e', transition: 'color 0.2s' }} title="Toggle Terminal">
|
|
|
|
|
|
|
| 367 |
<i className="fas fa-terminal"></i> Terminal
|
| 368 |
</div>
|
|
|
|
| 369 |
<div style={{ display: 'flex', alignItems: 'center', gap: '5px', color: saveStatus.includes('Error') ? '#f85149' : '#3fb950' }}>
|
| 370 |
<i className="fas fa-cloud"></i> {saveStatus}
|
| 371 |
</div>
|
|
|
|
| 374 |
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
|
| 375 |
<select value={activeMode} onChange={(e) => setActiveMode(e.target.value)} style={{ background: 'transparent', border: 'none', color: '#8b949e', outline: 'none', fontSize: '11px', cursor: 'pointer' }}>
|
| 376 |
<option>Copilot Mode</option>
|
| 377 |
+
<option>Agentic Mode</option>
|
|
|
|
| 378 |
</select>
|
| 379 |
<div><i className="fas fa-code-branch" style={{ marginRight: '5px' }}></i> main</div>
|
| 380 |
<div><i className="fas fa-check-double" style={{ marginRight: '5px', color: '#3fb950' }}></i> Ethrix Prettier</div>
|
client/src/apiService.js
CHANGED
|
@@ -5,28 +5,32 @@ export const api = {
|
|
| 5 |
// 1. AI Generation (Secure Gateway)
|
| 6 |
// apiService.js ke andar bas yeh function update karna hai
|
| 7 |
generateCode: async (prompt, existingFiles, provider) => {
|
| 8 |
-
//
|
| 9 |
const API_URL = "https://YOUR-HUGGINGFACE-SPACE-URL.hf.space";
|
|
|
|
| 10 |
try {
|
|
|
|
| 11 |
const response = await fetch(`${API_URL}/api/agent/generate`, {
|
| 12 |
method: 'POST',
|
| 13 |
headers: { 'Content-Type': 'application/json' },
|
| 14 |
body: JSON.stringify({
|
| 15 |
prompt: prompt,
|
| 16 |
-
existing_files: existingFiles, //
|
| 17 |
model_preference: provider || "gemini"
|
| 18 |
})
|
| 19 |
});
|
|
|
|
| 20 |
if (!response.ok) {
|
| 21 |
const err = await response.json();
|
| 22 |
throw new Error(err.detail || "AI Gateway Error");
|
| 23 |
}
|
|
|
|
| 24 |
const data = await response.json();
|
| 25 |
-
return data.files; //
|
| 26 |
} catch (error) {
|
| 27 |
throw error;
|
| 28 |
}
|
| 29 |
-
}
|
| 30 |
|
| 31 |
// 2. MongoDB Workspace Sync
|
| 32 |
saveWorkspace: async (name, filesObject) => {
|
|
|
|
| 5 |
// 1. AI Generation (Secure Gateway)
|
| 6 |
// apiService.js ke andar bas yeh function update karna hai
|
| 7 |
generateCode: async (prompt, existingFiles, provider) => {
|
| 8 |
+
// Yahan tumne jo apna Hugging Face space url dala hai, wahi rehne dena
|
| 9 |
const API_URL = "https://YOUR-HUGGINGFACE-SPACE-URL.hf.space";
|
| 10 |
+
|
| 11 |
try {
|
| 12 |
+
// DHYAAN DO: Naya endpoint aur naya body format
|
| 13 |
const response = await fetch(`${API_URL}/api/agent/generate`, {
|
| 14 |
method: 'POST',
|
| 15 |
headers: { 'Content-Type': 'application/json' },
|
| 16 |
body: JSON.stringify({
|
| 17 |
prompt: prompt,
|
| 18 |
+
existing_files: existingFiles, // Ab AI ko tumhari files dikhengi!
|
| 19 |
model_preference: provider || "gemini"
|
| 20 |
})
|
| 21 |
});
|
| 22 |
+
|
| 23 |
if (!response.ok) {
|
| 24 |
const err = await response.json();
|
| 25 |
throw new Error(err.detail || "AI Gateway Error");
|
| 26 |
}
|
| 27 |
+
|
| 28 |
const data = await response.json();
|
| 29 |
+
return data.files; // Naya return format
|
| 30 |
} catch (error) {
|
| 31 |
throw error;
|
| 32 |
}
|
| 33 |
+
},
|
| 34 |
|
| 35 |
// 2. MongoDB Workspace Sync
|
| 36 |
saveWorkspace: async (name, filesObject) => {
|