Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Code Creator - Qwen & DeepSeek Edition</title> | |
| <!-- Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <!-- JSZip for Exporting --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> | |
| <!-- FileSaver for Saving --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> | |
| <style> | |
| :root { | |
| --bg-dark: #0f0f0f; | |
| --bg-panel: #1a1a1a; | |
| --bg-panel-hover: #252525; | |
| --primary: #FFD700; /* Gold/Yellow */ | |
| --primary-dim: #b39700; | |
| --text-main: #ffffff; | |
| --text-muted: #a0a0a0; | |
| --border: #333; | |
| --code-bg: #111; | |
| --accent-blue: #3b82f6; | |
| --success: #10b981; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--primary) var(--bg-dark); | |
| } | |
| body { | |
| font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* --- Header --- */ | |
| header { | |
| height: 60px; | |
| background-color: var(--bg-panel); | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 20px; | |
| z-index: 10; | |
| } | |
| .logo { | |
| font-size: 1.2rem; | |
| font-weight: bold; | |
| color: var(--primary); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| text-decoration: none; | |
| } | |
| .logo span { color: white; } | |
| .header-controls { | |
| display: flex; | |
| gap: 15px; | |
| } | |
| button { | |
| background-color: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| color: var(--text-main); | |
| padding: 8px 16px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-size: 0.9rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| button:hover { | |
| background-color: var(--bg-panel-hover); | |
| border-color: var(--primary); | |
| } | |
| button.primary { | |
| background-color: var(--primary); | |
| color: #000; | |
| font-weight: bold; | |
| border: none; | |
| } | |
| button.primary:hover { | |
| background-color: var(--primary-dim); | |
| } | |
| /* --- Main Layout --- */ | |
| .app-container { | |
| display: flex; | |
| flex: 1; | |
| height: calc(100vh - 60px); | |
| } | |
| /* --- Sidebar (Files, History) --- */ | |
| .sidebar { | |
| width: 250px; | |
| background-color: var(--bg-panel); | |
| border-right: 1px solid var(--border); | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .sidebar-header { | |
| padding: 15px; | |
| border-bottom: 1px solid var(--border); | |
| font-weight: bold; | |
| color: var(--primary); | |
| } | |
| .file-list, .history-list { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 10px; | |
| } | |
| .file-item { | |
| padding: 10px; | |
| margin-bottom: 5px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 0.9rem; | |
| } | |
| .file-item:hover, .file-item.active { | |
| background-color: var(--bg-panel-hover); | |
| color: var(--primary); | |
| } | |
| /* --- Editor Area --- */ | |
| .editor-section { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| border-right: 1px solid var(--border); | |
| } | |
| .tabs { | |
| display: flex; | |
| background-color: var(--bg-dark); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .tab { | |
| padding: 12px 20px; | |
| cursor: pointer; | |
| border-right: 1px solid var(--border); | |
| color: var(--text-muted); | |
| font-size: 0.9rem; | |
| } | |
| .tab.active { | |
| background-color: var(--bg-panel); | |
| color: var(--primary); | |
| border-top: 2px solid var(--primary); | |
| } | |
| .code-area { | |
| flex: 1; | |
| background-color: var(--code-bg); | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| textarea#codeEditor { | |
| flex: 1; | |
| background-color: transparent; | |
| color: #e0e0e0; | |
| border: none; | |
| resize: none; | |
| padding: 20px; | |
| font-family: 'Consolas', 'Monaco', monospace; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| outline: none; | |
| } | |
| /* --- AI Panel --- */ | |
| .ai-panel { | |
| height: 40%; | |
| border-top: 1px solid var(--border); | |
| background-color: var(--bg-panel); | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .ai-input-container { | |
| padding: 15px; | |
| border-top: 1px solid var(--border); | |
| background-color: var(--bg-dark); | |
| } | |
| .enhance-tools { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .enhance-btn { | |
| font-size: 0.8rem; | |
| padding: 4px 10px; | |
| border-radius: 20px; | |
| background: var(--bg-panel); | |
| border: 1px solid var(--primary); | |
| color: var(--primary); | |
| } | |
| .input-wrapper { | |
| display: flex; | |
| gap: 10px; | |
| position: relative; | |
| } | |
| #aiInput { | |
| flex: 1; | |
| background-color: var(--bg-panel); | |
| border: 1px solid var(--border); | |
| color: white; | |
| padding: 12px; | |
| border-radius: 8px; | |
| outline: none; | |
| } | |
| #aiInput:focus { | |
| border-color: var(--primary); | |
| } | |
| .ai-output { | |
| flex: 1; | |
| padding: 15px; | |
| overflow-y: auto; | |
| font-family: 'Consolas', monospace; | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| } | |
| .ai-message { | |
| margin-bottom: 15px; | |
| padding: 10px; | |
| border-radius: 6px; | |
| background-color: rgba(255, 255, 255, 0.05); | |
| border-left: 3px solid var(--primary); | |
| } | |
| /* --- Preview Pane --- */ | |
| .preview-section { | |
| width: 40%; | |
| display: flex; | |
| flex-direction: column; | |
| background-color: white; | |
| } | |
| .preview-header { | |
| background-color: var(--bg-panel); | |
| padding: 10px 15px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| iframe { | |
| flex: 1; | |
| border: none; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* --- Modals --- */ | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; left: 0; width: 100%; height: 100%; | |
| background: rgba(0,0,0,0.8); | |
| z-index: 100; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background: var(--bg-panel); | |
| padding: 25px; | |
| border-radius: 10px; | |
| border: 1px solid var(--primary); | |
| width: 500px; | |
| max-width: 90%; | |
| } | |
| .modal h2 { | |
| color: var(--primary); | |
| margin-bottom: 20px; | |
| } | |
| .form-group { | |
| margin-bottom: 15px; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 5px; | |
| color: var(--text-muted); | |
| } | |
| .form-group input, .form-group select { | |
| width: 100%; | |
| padding: 10px; | |
| background: var(--bg-dark); | |
| border: 1px solid var(--border); | |
| color: white; | |
| border-radius: 4px; | |
| } | |
| .color-picker-row { | |
| display: flex; | |
| gap: 15px; | |
| align-items: center; | |
| margin-bottom: 10px; | |
| } | |
| input[type="color"] { | |
| width: 40px; | |
| height: 40px; | |
| border: none; | |
| padding: 0; | |
| background: none; | |
| } | |
| /* Loader */ | |
| .loader { | |
| border: 3px solid rgba(255,255,255,0.1); | |
| border-top: 3px solid var(--primary); | |
| border-radius: 50%; | |
| width: 20px; | |
| height: 20px; | |
| animation: spin 1s linear infinite; | |
| display: none; | |
| } | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <header> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="logo"> | |
| <i class="fas fa-microchip"></i> <span>AI Code Creator</span> <small style="font-size: 0.6em; color: #666;">Built with Anycoder</small> | |
| </a> | |
| <div class="header-controls"> | |
| <button onclick="openSettings()"><i class="fas fa-robot"></i> AI Config</button> | |
| <button onclick="openThemeTool()"><i class="fas fa-palette"></i> Theme</button> | |
| <button onclick="exportProject()"><i class="fas fa-download"></i> Export ZIP</button> | |
| <button class="primary" onclick="runAI()"><i class="fas fa-magic"></i> Generate</button> | |
| </div> | |
| </header> | |
| <!-- Main App --> | |
| <div class="app-container"> | |
| <!-- Left Sidebar --> | |
| <aside class="sidebar"> | |
| <div class="sidebar-header"> | |
| <i class="fas fa-project-diagram"></i> Project Files | |
| </div> | |
| <div class="file-list" id="fileList"> | |
| <!-- Generated by JS --> | |
| </div> | |
| <div class="sidebar-header" style="border-top: 1px solid var(--border); margin-top: auto;"> | |
| <i class="fas fa-history"></i> History | |
| </div> | |
| <div class="history-list" id="historyList"> | |
| <!-- Generated by JS --> | |
| </div> | |
| </aside> | |
| <!-- Center Editor --> | |
| <section class="editor-section"> | |
| <div class="tabs"> | |
| <div class="tab active" onclick="switchTab('index.html')">index.html</div> | |
| <div class="tab" onclick="switchTab('style.css')">style.css</div> | |
| <div class="tab" onclick="switchTab('script.js')">script.js</div> | |
| <div class="tab" style="margin-left: auto; border-left: 1px solid var(--border);" onclick="saveToHistory()"> | |
| <i class="fas fa-save"></i> Save Snapshot | |
| </div> | |
| </div> | |
| <div class="code-area"> | |
| <textarea id="codeEditor" spellcheck="false" oninput="updateFileContent()"></textarea> | |
| </div> | |
| <!-- AI Chat / Enhancer --> | |
| <div class="ai-panel"> | |
| <div style="padding: 10px; background: var(--bg-dark); border-bottom: 1px solid var(--border); display: flex; justify-content: space-between;"> | |
| <span style="color: var(--primary); font-weight: bold;">AI Assistant</span> | |
| <span id="modelStatus" style="font-size: 0.8rem; color: var(--text-muted);">Model: Mock (Ready)</span> | |
| </div> | |
| <div class="ai-output" id="aiOutput"> | |
| <div class="ai-message">System: Ready. Describe your web app, or use the enhancer tools below.</div> | |
| </div> | |
| <div class="ai-input-container"> | |
| <div class="enhance-tools"> | |
| <button class="enhance-btn" onclick="enhancePrompt('Fix Code', 'Fix syntax errors in the following code:')">Fix Code</button> | |
| <button class="enhance-btn" onclick="enhancePrompt('Optimize', 'Optimize the CSS for better performance:')">Optimize</button> | |
| <button class="enhance-btn" onclick="enhancePrompt('Add Feature', 'Add a responsive navigation bar:')">Add Feature</button> | |
| </div> | |
| <div class="input-wrapper"> | |
| <input type="text" id="aiInput" placeholder="Ask AI to generate or edit code..." autocomplete="off"> | |
| <div class="loader" id="aiLoader"></div> | |
| <button class="primary" style="padding: 8px 15px;" onclick="runAI()"><i class="fas fa-paper-plane"></i></button> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Right Preview --> | |
| <section class="preview-section"> | |
| <div class="preview-header"> | |
| <span style="color: #333; font-weight: bold;">Live Preview</span> | |
| <div style="display: flex; gap: 5px;"> | |
| <i class="fas fa-mobile-alt" style="color: #666; cursor: pointer;" onclick="setDevice('mobile')"></i> | |
| <i class="fas fa-desktop" style="color: #000; cursor: pointer;" onclick="setDevice('desktop')"></i> | |
| </div> | |
| </div> | |
| <iframe id="previewFrame" title="Live Preview"></iframe> | |
| </section> | |
| </div> | |
| <!-- Settings Modal --> | |
| <div id="settingsModal" class="modal"> | |
| <div class="modal-content"> | |
| <h2>AI Configuration</h2> | |
| <div class="form-group"> | |
| <label>Select Model Provider</label> | |
| <select id="aiProvider"> | |
| <option value="mock">Mock AI (Demo Mode)</option> | |
| <option value="openai">OpenAI Compatible (Qwen/DeepSeek)</option> | |
| <option value="huggingface">HuggingFace Inference</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label>API Endpoint / URL</label> | |
| <input type="text" id="apiEndpoint" placeholder="https://api.openai.com/v1/..."> | |
| </div> | |
| <div class="form-group"> | |
| <label>API Key</label> | |
| <input type="password" id="apiKey" placeholder="sk-..."> | |
| </div> | |
| <div style="display: flex; justify-content: flex-end; gap: 10px;"> | |
| <button onclick="closeModal('settingsModal')">Cancel</button> | |
| <button class="primary" onclick="saveSettings()">Save Config</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Theme Modal --> | |
| <div id="themeModal" class="modal"> | |
| <div class="modal-content"> | |
| <h2>Theme Customizer</h2> | |
| <div class="color-picker-row"> | |
| <label>Primary (Yellow):</label> | |
| <input type="color" id="themePrimary" value="#FFD700"> | |
| </div> | |
| <div class="color-picker-row"> | |
| <label>Background:</label> | |
| <input type="color" id="themeBg" value="#0f0f0f"> | |
| </div> | |
| <div class="color-picker-row"> | |
| <label>Text Color:</label> | |
| <input type="color" id="themeText" value="#ffffff"> | |
| </div> | |
| <div style="display: flex; justify-content: flex-end; gap: 10px;"> | |
| <button onclick="resetTheme()">Reset Default</button> | |
| <button class="primary" onclick="applyTheme()">Apply Theme</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // --- State Management --- | |
| const state = { | |
| files: { | |
| 'index.html': '<!DOCTYPE html>\n<html>\n<head>\n <title>My App</title>\n <link rel="stylesheet" href="style.css">\n</head>\n<body>\n <div class="container">\n <h1>Hello AI Creator!</h1>\n <p>Start coding or ask the AI to build something.</p>\n <button id="btn">Click Me</button>\n </div>\n <script src="script.js"><\/script>\n</body>\n</html>', | |
| 'style.css': 'body {\n background: #111;\n color: #fff;\n font-family: sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n height: 100vh;\n margin: 0;\n}\n\n.container {\n text-align: center;\n border: 2px solid #FFD700;\n padding: 40px;\n border-radius: 10px;\n}\n\nbutton {\n background: #FFD700;\n color: #000;\n border: none;\n padding: 10px 20px;\n font-size: 1.2rem;\n cursor: pointer;\n margin-top: 20px;\n border-radius: 5px;\n}', | |
| 'script.js': 'document.getElementById("btn").addEventListener("click", () => {\n alert("Button Clicked!");\n});' | |
| }, | |
| activeFile: 'index.html', | |
| history: [], | |
| config: { | |
| provider: 'mock', | |
| endpoint: '', | |
| key: '' | |
| } | |
| }; | |
| // --- Initialization --- | |
| document.addEventListener('DOMContentLoaded', () => { | |
| renderFileList(); | |
| loadEditor(); | |
| renderPreview(); | |
| loadHistory(); | |
| // Enter key for AI Input | |
| document.getElementById('aiInput').addEventListener('keypress', function (e) { | |
| if (e.key === 'Enter') { | |
| runAI(); | |
| } | |
| }); | |
| }); | |
| // --- Core Editor Functions --- | |
| function switchTab(filename) { | |
| state.activeFile = filename; | |
| // Update UI | |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
| // Find the tab element by text content (simple way) or index logic | |
| // In a real app, assign IDs to tabs. Here we just reload editor content. | |
| // Highlight active tab logic (simplified) | |
| const tabs = document.querySelectorAll('.tab'); | |
| if(filename === 'index.html') tabs[0].classList.add('active'); | |
| if(filename === 'style.css') tabs[1].classList.add('active'); | |
| if(filename === 'script.js') tabs[2].classList.add('active'); | |
| loadEditor(); | |
| } | |
| function loadEditor() { | |
| const editor = document.getElementById('codeEditor'); | |
| editor.value = state.files[state.activeFile]; | |
| } | |
| function updateFileContent() { | |
| state.files[state.activeFile] = document.getElementById('codeEditor').value; | |
| renderPreview(); | |
| } | |
| function renderFileList() { | |
| const list = document.getElementById('fileList'); | |
| list.innerHTML = ''; | |
| Object.keys(state.files).forEach(filename => { | |
| const div = document.createElement('div'); | |
| div.className = `file-item ${filename === state.activeFile ? 'active' : ''}`; | |
| div.innerHTML = `<i class="fas ${getFileIcon(filename)}"></i> ${filename}`; | |
| div.onclick = () => switchTab(filename); | |
| list.appendChild(div); | |
| }); | |
| } | |
| function getFileIcon(filename) { | |
| if(filename.endsWith('.html')) return 'fa-html5 text-orange-500'; | |
| if(filename.endsWith('.css')) return 'fa-css3-alt text-blue-500'; | |
| if(filename.endsWith('.js')) return 'fa-js text-yellow-500'; | |
| return 'fa-file-code'; | |
| } | |
| // --- Preview System --- | |
| function renderPreview() { | |
| const iframe = document.getElementById('previewFrame'); | |
| // Inject CSS into HTML for preview | |
| const css = state.files['style.css'] || ''; | |
| const html = state.files['index.html'] || ''; | |
| // Basic injection logic to ensure style.css applies even in blob/data URI scenarios if needed | |
| // For iframe srcdoc, we can just put them together | |
| const combinedHTML = ` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <style>${css}</style> | |
| </head> | |
| <body> | |
| ${html} | |
| <script>${state.files['script.js'] || ''}<\/script> | |
| </body> | |
| </html> | |
| `; | |
| iframe.srcdoc = combinedHTML; | |
| } | |
| function setDevice(type) { | |
| const iframe = document.getElementById('previewFrame'); | |
| if(type === 'mobile') { | |
| iframe.style.width = '375px'; | |
| iframe.style.margin = '0 auto'; | |
| iframe.style.border = '1px solid #ccc'; | |
| } else { | |
| iframe.style.width = '100%'; | |
| iframe.style.border = 'none'; | |
| iframe.style.margin = '0'; | |
| } | |
| } | |
| // --- AI Logic --- | |
| function enhancePrompt(action, prefix) { | |
| const input = document.getElementById('aiInput'); | |
| input.value = `${prefix} \n\nCurrent File (${state.activeFile}): \n${state.files[state.activeFile].substring(0, 100)}...`; | |
| const output = document.getElementById('aiOutput'); | |
| output.innerHTML += `<div class="ai-message"><strong>System:</strong> Prompt enhanced for "${action}". Review input and press Generate.</div>`; | |
| output.scrollTop = output.scrollHeight; | |
| } | |
| async function runAI() { | |
| const input = document.getElementById('aiInput').value; | |
| if(!input.trim()) return; | |
| const loader = document.getElementById('aiLoader'); | |
| const output = document.getElementById('aiOutput'); | |
| loader.style.display = 'block'; | |
| output.innerHTML += `<div class="ai-message"><strong>User:</strong> ${input}</div>`; | |
| try { | |
| let aiResponse = ""; | |
| if (state.config.provider === 'mock') { | |
| // Mock AI for Demo purposes | |
| await new Promise(r => setTimeout(r, 1500)); // Fake delay | |
| aiResponse = generateMockResponse(input); | |
| } else { | |
| // Real API Call (Conceptual Implementation) | |
| aiResponse = await callRealAPI(input); | |
| } | |
| output.innerHTML += `<div class="ai-message"><strong>AI:</strong> ${aiResponse}</div>`; | |
| processAIResult(aiResponse); | |
| } catch (error) { | |
| output.innerHTML += `<div class="ai-message" style="color:red"><strong>Error:</strong> ${error.message}</div>`; | |
| } finally { | |
| loader.style.display = 'none'; | |
| output.scrollTop = output.scrollHeight; | |
| } | |
| } | |
| function generateMockResponse(prompt) { | |
| // Heuristic responses for demo | |
| if (prompt.toLowerCase().includes('html')) { | |
| return "I've updated the HTML structure with a modern layout."; | |
| } | |
| if (prompt.toLowerCase().includes('css') || prompt.toLowerCase().includes('style')) { | |
| return "I've refined the CSS. Added a gradient background and hover effects."; | |
| } | |
| if (prompt.toLowerCase().includes('fix') || prompt.toLowerCase().includes('error')) { | |
| return "I detected a syntax error in the script tag. Fixed."; | |
| } | |
| return "Generated code structure based on your request. I've updated the 'index.html' file."; | |
| } | |
| async function callRealAPI(prompt) { | |
| // Placeholder for fetch to Qwen/DeepSeek/HuggingFace | |
| // In a real app, you'd construct the JSON body here based on state.config | |
| const response = await fetch(state.config.endpoint || 'https://api.example.com/generate', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${state.config.key}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ prompt: prompt }) | |
| }); | |
| if(!response.ok) throw new Error('API Request Failed'); | |
| const data = await response.json(); | |
| return data.choices[0].message.content; | |
| } | |
| function processAIResult(responseText) { | |
| // This is a very basic parser. In production, you'd want structured JSON from the AI. | |
| // For this demo, we will detect keywords or just append to index.html for simple commands. | |
| if (responseText.includes('```html')) { | |
| const htmlMatch = responseText.match(/```html([\s\S]*?)```/); | |
| if(htmlMatch) { | |
| state.files['index.html'] = htmlMatch[1].trim(); | |
| switchTab('index.html'); | |
| } | |
| } else if (responseText.includes('```css')) { | |
| const cssMatch = responseText.match(/```css([\s\S]*?)```/); | |
| if(cssMatch) { | |
| state.files['style.css'] = cssMatch[1].trim(); | |
| switchTab('style.css'); | |
| } | |
| } else { | |
| // Default: append to current file for memory/chat context | |
| const currentContent = state.files[state.activeFile]; | |
| state.files[state.activeFile] = currentContent + "\n<!-- AI Generated -->\n" + responseText; | |
| } | |
| loadEditor(); | |
| renderFileList(); | |
| renderPreview(); | |
| document.getElementById('aiInput').value = ''; | |
| } | |
| // --- History & Saving --- | |
| function saveToHistory() { | |
| const snapshot = { | |
| timestamp: new Date().toLocaleString(), | |
| files: JSON.parse(JSON.stringify(state.files)), | |
| prompt: document.getElementById('aiInput').value || "Manual Save" | |
| }; | |
| state.history.unshift(snapshot); | |
| if(state.history.length > 10) state.history.pop(); | |
| localStorage.setItem('ai_code_history', JSON.stringify(state.history)); | |
| renderHistory(); | |
| alert("Snapshot saved to History!"); | |
| } | |
| function loadHistory() { | |
| const stored = localStorage.getItem('ai_code_history'); | |
| if(stored) { | |
| state.history = JSON.parse(stored); | |
| renderHistory(); | |
| } | |
| } | |
| function renderHistory() { | |
| const list = document.getElementById('historyList'); | |
| list.innerHTML = ''; | |
| state.history.forEach((item, index) => { | |
| const div = document.createElement('div'); | |
| div.className = 'file-item'; | |
| div.innerHTML = `<i class="fas fa-history"></i> ${item.timestamp}`; | |
| div.title = item.prompt; | |
| div.onclick = () => restoreHistory(index); | |
| list.appendChild(div); | |
| }); | |
| } | |
| function restoreHistory(index) { | |
| if(confirm("Restore this snapshot? Current unsaved changes will be lost.")) { | |
| state.files = JSON.parse(JSON.stringify(state.history[index].files)); | |
| loadEditor(); | |
| renderFileList(); | |
| renderPreview(); | |
| } | |
| } | |
| function exportProject() { | |
| const zip = new JSZip(); | |
| for (const [filename, content] of Object.entries(state.files)) { | |
| zip.file(filename, content); | |
| } | |
| zip.generateAsync({type:"blob"}) | |
| .then(function(content) { | |
| saveAs(content, "ai-project.zip"); | |
| }); | |
| } | |
| // --- Theme Tool --- | |
| function openThemeTool() { | |
| document.getElementById('themeModal').style.display = 'flex'; | |
| } | |
| function applyTheme() { | |
| const primary = document.getElementById('themePrimary').value; | |
| const bg = document.getElementById('themeBg').value; | |
| const text = document.getElementById('themeText').value; | |
| const r = document.querySelector(':root'); | |
| r.style.setProperty('--primary', primary); | |
| r.style.setProperty('--bg-dark', bg); | |
| r.style.setProperty('--text-main', text); | |
| closeModal('themeModal'); | |
| } | |
| function resetTheme() { | |
| document.getElementById('themePrimary').value = '#FFD700'; | |
| document.getElementById('themeBg').value = '#0f0f0f'; | |
| document.getElementById('themeText').value = '#ffffff'; | |
| applyTheme(); | |
| } | |
| // --- Settings --- | |
| function openSettings() { | |
| document.getElementById('settingsModal').style.display = 'flex'; | |
| } | |
| function saveSettings() { | |
| state.config.provider = document.getElementById('aiProvider').value; | |
| state.config.endpoint = document.getElementById('apiEndpoint').value; | |
| state.config.key = document.getElementById('apiKey').value; | |
| const status = document.getElementById('modelStatus'); | |
| status.innerText = `Model: ${state.config.provider.toUpperCase()} (Active)`; | |
| closeModal('settingsModal'); | |
| } | |
| function closeModal(id) { | |
| document.getElementById(id).style.display = 'none'; | |
| } | |
| // Close modals on outside click | |
| window.onclick = function(event) { | |
| if (event.target.classList.contains('modal')) { | |
| event.target.style.display = "none"; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |