Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Client-Side AI Code Modifier</title> | |
| <!-- Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <!-- Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-dark: #0f0f11; | |
| --bg-panel: #1a1a1d; | |
| --bg-input: #252529; | |
| --accent: #6366f1; | |
| --accent-hover: #4f46e5; | |
| --text-main: #e4e4e7; | |
| --text-muted: #a1a1aa; | |
| --border: #27272a; | |
| --success: #10b981; | |
| --error: #ef4444; | |
| --code-font: 'JetBrains Mono', monospace; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| height: 100vh; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* --- Header --- */ | |
| header { | |
| background-color: var(--bg-panel); | |
| border-bottom: 1px solid var(--border); | |
| padding: 1rem 2rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| height: 64px; | |
| flex-shrink: 0; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-weight: 700; | |
| font-size: 1.2rem; | |
| color: var(--text-main); | |
| } | |
| .brand i { color: var(--accent); } | |
| .logo-link { | |
| text-decoration: none; | |
| color: var(--text-muted); | |
| font-size: 0.8rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| transition: color 0.2s; | |
| } | |
| .logo-link:hover { | |
| color: var(--accent); | |
| } | |
| .status-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 0.75rem; | |
| background: rgba(16, 185, 129, 0.1); | |
| color: var(--success); | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| border: 1px solid rgba(16, 185, 129, 0.2); | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| background-color: var(--success); | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px var(--success); | |
| } | |
| /* --- Main Layout --- */ | |
| main { | |
| display: grid; | |
| grid-template-columns: 1fr 350px; | |
| flex-grow: 1; | |
| overflow: hidden; | |
| } | |
| /* --- Editor Section --- */ | |
| .editor-container { | |
| display: flex; | |
| flex-direction: column; | |
| border-right: 1px solid var(--border); | |
| height: 100%; | |
| } | |
| .toolbar { | |
| padding: 10px 20px; | |
| background: var(--bg-panel); | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .toolbar-title { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| font-weight: 600; | |
| } | |
| .editor-area { | |
| flex-grow: 1; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| textarea.code-input { | |
| flex-grow: 1; | |
| background-color: var(--bg-dark); | |
| color: #d4d4d4; | |
| border: none; | |
| resize: none; | |
| padding: 20px; | |
| font-family: var(--code-font); | |
| font-size: 14px; | |
| line-height: 1.6; | |
| outline: none; | |
| white-space: pre; | |
| overflow: auto; | |
| } | |
| textarea.code-input::placeholder { | |
| color: #444; | |
| } | |
| /* --- Sidebar / AI Panel --- */ | |
| .ai-panel { | |
| background-color: var(--bg-panel); | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| } | |
| .panel-header { | |
| padding: 20px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .panel-header h2 { | |
| font-size: 1.1rem; | |
| margin-bottom: 5px; | |
| } | |
| .panel-header p { | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| } | |
| .control-group { | |
| padding: 20px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .label { | |
| display: block; | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| } | |
| textarea.prompt-input { | |
| width: 100%; | |
| background-color: var(--bg-input); | |
| border: 1px solid var(--border); | |
| color: var(--text-main); | |
| padding: 10px; | |
| border-radius: 6px; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 0.9rem; | |
| resize: vertical; | |
| min-height: 80px; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| textarea.prompt-input:focus { | |
| border-color: var(--accent); | |
| } | |
| .slider-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| input[type="range"] { | |
| flex-grow: 1; | |
| accent-color: var(--accent); | |
| cursor: pointer; | |
| } | |
| .slider-value { | |
| font-size: 0.8rem; | |
| font-family: var(--code-font); | |
| color: var(--accent); | |
| min-width: 40px; | |
| text-align: right; | |
| } | |
| .action-btn { | |
| width: 100%; | |
| padding: 14px; | |
| margin-top: auto; /* Push to bottom if needed, but here we stack */ | |
| background-color: var(--accent); | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| transition: all 0.2s; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3); | |
| } | |
| .action-btn:hover { | |
| background-color: var(--accent-hover); | |
| transform: translateY(-1px); | |
| } | |
| .action-btn:active { | |
| transform: translateY(0); | |
| } | |
| .action-btn:disabled { | |
| background-color: var(--border); | |
| color: var(--text-muted); | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| /* --- Console / Output Log --- */ | |
| .console-log { | |
| height: 150px; | |
| background-color: #000; | |
| padding: 15px; | |
| overflow-y: auto; | |
| font-family: var(--code-font); | |
| font-size: 0.8rem; | |
| border-top: 1px solid var(--border); | |
| } | |
| .log-entry { | |
| margin-bottom: 6px; | |
| padding-bottom: 6px; | |
| border-bottom: 1px solid #222; | |
| } | |
| .log-time { | |
| color: var(--text-muted); | |
| margin-right: 8px; | |
| } | |
| .log-info { color: #60a5fa; } | |
| .log-success { color: var(--success); } | |
| .log-warn { color: #fbbf24; } | |
| /* --- Responsive Design --- */ | |
| @media (max-width: 768px) { | |
| main { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: 1fr 1fr; | |
| overflow-y: auto; | |
| } | |
| .editor-container { | |
| border-right: none; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .ai-panel { | |
| height: 500px; | |
| } | |
| body { | |
| overflow: auto; | |
| } | |
| header { | |
| padding: 1rem; | |
| } | |
| } | |
| /* Loading Animation */ | |
| .loader { | |
| width: 18px; | |
| height: 18px; | |
| border: 3px solid #ffffff; | |
| border-bottom-color: transparent; | |
| border-radius: 50%; | |
| display: inline-block; | |
| box-sizing: border-box; | |
| animation: rotation 1s linear infinite; | |
| } | |
| @keyframes rotation { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-brain"></i> | |
| <span>Client-Side AI Studio</span> | |
| </div> | |
| <div style="display: flex; align-items: center; gap: 20px;"> | |
| <div class="status-badge"> | |
| <div class="status-dot"></div> | |
| <span>WebLLM (Transformers.js)</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="logo-link"> | |
| <i class="fa-solid fa-code"></i> Built with anycoder | |
| </a> | |
| </div> | |
| </header> | |
| <main> | |
| <!-- Left Side: Code Editor --> | |
| <section class="editor-container"> | |
| <div class="toolbar"> | |
| <span class="toolbar-title"><i class="fa-solid fa-file-code"></i> main.js</span> | |
| <div style="font-size: 0.75rem; color: var(--text-muted);"> | |
| <i class="fa-solid fa-terminal"></i> Ready | |
| </div> | |
| </div> | |
| <div class="editor-area"> | |
| <textarea id="codeEditor" class="code-input" spellcheck="false" placeholder="// Existing code appears here... const server = require('express')(); // TODO: Optimize database connection function legacyFunction(data) { // messy logic here return data; }"> | |
| // --- EXISTING CODE BASE --- | |
| // You can modify this code using the AI panel on the right. | |
| const express = require('express'); | |
| const app = express(); | |
| const PORT = process.env.PORT || 3000; | |
| // Middleware to parse JSON bodies | |
| app.use(express.json()); | |
| // Database connection simulation (Needs optimization) | |
| let database = { | |
| users: [], | |
| items: [] | |
| }; | |
| /** | |
| * Legacy function to handle user creation | |
| * @param {Object} userData | |
| */ | |
| function createUser(userData) { | |
| if (!userData.email) { | |
| throw new Error('Email is required'); | |
| } | |
| const newUser = { | |
| id: Date.now(), | |
| ...userData, | |
| createdAt: new Date().toISOString() | |
| }; | |
| database.users.push(newUser); | |
| return newUser; | |
| } | |
| // Endpoint: Get all users | |
| app.get('/api/users', (req, res) => { | |
| res.json({ success: true, data: database.users }); | |
| }); | |
| // Endpoint: Create User | |
| app.post('/api/users', (req, res) => { | |
| try { | |
| const user = createUser(req.body); | |
| res.status(201).json({ success: true, data: user }); | |
| } catch (error) { | |
| res.status(400).json({ success: false, error: error.message }); | |
| } | |
| }); | |
| // Start Server | |
| app.listen(PORT, () => { | |
| console.log(`Server running on port ${PORT}`); | |
| console.log("Ready to accept requests."); | |
| }); | |
| </textarea> | |
| </div> | |
| </section> | |
| <!-- Right Side: AI Controls --> | |
| <aside class="ai-panel"> | |
| <div class="panel-header"> | |
| <h2><i class="fa-solid fa-robot"></i> AI Assistant</h2> | |
| <p>Running DistilGPT-2 locally in your browser</p> | |
| </div> | |
| <div class="control-group"> | |
| <label class="label">Modification Request</label> | |
| <textarea id="promptInput" class="prompt-input" placeholder="Describe what you want to change... Example: 'Add input validation to the email field' or 'Refactor createUser to use async/await'"></textarea> | |
| </div> | |
| <div class="control-group"> | |
| <label class="label">Creativity (Temperature) <span id="tempValue" class="slider-value">0.7</span></label> | |
| <div class="slider-container"> | |
| <input type="range" id="temperature" min="0" max="1" step="0.1" value="0.7"> | |
| </div> | |
| <div style="margin-top: 10px;"> | |
| <label class="label">Max New Tokens</label> | |
| <div class="slider-container"> | |
| <input type="range" id="maxTokens" min="50" max="500" step="50" value="200"> | |
| <span class="slider-value" id="tokensValue">200</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="generateBtn" class="action-btn"> | |
| <span id="btnText"><i class="fa-solid fa-play"></i> Generate Code</span> | |
| <div id="btnLoader" class="loader" style="display: none;"></div> | |
| </button> | |
| <div class="console-log" id="consoleLog"> | |
| <div class="log-entry"> | |
| <span class="log-time">[System]</span> | |
| <span class="log-info">Transformers.js initialized. Model loaded: distilgpt2</span> | |
| </div> | |
| <div class="log-entry"> | |
| <span class="log-time">[System]</span> | |
| <span class="log-info">Client-side environment ready.</span> | |
| </div> | |
| </div> | |
| </aside> | |
| </main> | |
| <!-- Transformers.js Library --> | |
| <script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.13.0/dist/transformers.min.js"></script> | |
| <script> | |
| // --- Configuration & State --- | |
| const CONFIG = { | |
| modelId: 'Xenova/distilgpt2', // Lightweight GPT model for the browser | |
| progressCallback: (data) => { | |
| logToConsole(`Downloading model: ${(data.progress * 100).toFixed(2)}%`, 'info'); | |
| } | |
| }; | |
| let generator = null; | |
| let isModelLoaded = false; | |
| // --- DOM Elements --- | |
| const codeEditor = document.getElementById('codeEditor'); | |
| const promptInput = document.getElementById('promptInput'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const btnText = document.getElementById('btnText'); | |
| const btnLoader = document.getElementById('btnLoader'); | |
| const consoleLog = document.getElementById('consoleLog'); | |
| const tempInput = document.getElementById('temperature'); | |
| const tempValueDisplay = document.getElementById('tempValue'); | |
| const tokensInput = document.getElementById('maxTokens'); | |
| const tokensValueDisplay = document.getElementById('tokensValue'); | |
| // --- Helper Functions --- | |
| // Logger for the Console Panel | |
| function logToConsole(message, type = 'info') { | |
| const entry = document.createElement('div'); | |
| entry.className = 'log-entry'; | |
| const time = new Date().toLocaleTimeString([], { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); | |
| let typeClass = 'log-info'; | |
| if (type === 'success') typeClass = 'log-success'; | |
| if (type === 'warn') typeClass = 'log-warn'; | |
| entry.innerHTML = `<span class="log-time">[${time}]</span> <span class="${typeClass}">${message}</span>`; | |
| consoleLog.appendChild(entry); | |
| consoleLog.scrollTop = consoleLog.scrollHeight; | |
| } | |
| // Initialize Transformers.js | |
| async function initAI() { | |
| try { | |
| logToConsole('Initializing Transformers.js environment...', 'info'); | |
| // Create the text generation pipeline | |
| generator = await pipeline('text-generation', CONFIG.modelId, { | |
| progress_callback: CONFIG.progressCallback, | |
| dtype: 'int8', // Use int8 quantization to save memory | |
| device: 'cpu' // Explicitly force CPU for free tier stability | |
| }); | |
| isModelLoaded = true; | |
| logToConsole('Model loaded successfully: Xenova/distilgpt2', 'success'); | |
| logToConsole('System Ready. Enter a request below.', 'info'); | |
| } catch (error) { | |
| console.error(error); | |
| logToConsole(`Error loading model: ${error.message}`, 'warn'); | |
| alert("Failed to load AI model. Please check your internet connection and try again."); | |
| } | |
| } | |
| // Process the code generation | |
| async function handleCodeGeneration() { | |
| if (!isModelLoaded) { | |
| logToConsole('Model is still loading. Please wait...', 'warn'); | |
| return; | |
| } | |
| const userPrompt = promptInput.value.trim(); | |
| const currentCode = codeEditor.value; | |
| const temperature = parseFloat(tempInput.value); | |
| const maxNewTokens = parseInt(tokensInput.value); | |
| if (!userPrompt) { | |
| logToConsole('Please enter a modification request.', 'warn'); | |
| promptInput.focus(); | |
| return; | |
| } | |
| // UI State: Loading | |
| generateBtn.disabled = true; | |
| btnText.innerHTML = '<span class="loader" style="width:16px; height:16px; border-width:2px; margin-right:10px;"></span> Generating...'; | |
| logToConsole(`Processing request: "${userPrompt}"`, 'info'); | |
| // Construct the Prompt | |
| // We provide the context (existing code) and the instruction (prompt) | |
| const fullPrompt = ` | |
| <CODE> | |
| ${currentCode} | |
| </CODE> | |
| <INSTRUCTION> | |
| ${userPrompt} | |
| </INSTRUCTION> | |
| Please modify the code inside <CODE> tags to fulfill the instruction. | |
| Return only the full updated code block. | |
| `; | |
| try { | |
| // Run inference | |
| const output = await generator(fullPrompt, { | |
| max_new_tokens: maxNewTokens, | |
| temperature: temperature, | |
| do_sample: true, | |
| top_k: 50, | |
| return_full_text: false, // We want just the generated part | |
| pad_token_id: 50256 // EOS token for GPT-2 | |
| }); | |
| const generatedText = output[0].generated_text; | |
| // Parse logic: In a real app we might parse XML/Markdown. | |
| // Since GPT-2 is small, it often just continues the code stream. | |
| // We will attempt to extract just the new code block if formatted, | |
| // or append/replace intelligently. | |
| // For this demo, we will replace the editor content with the result | |
| // or append it if it looks like a function addition. | |
| logToConsole('Code generated successfully.', 'success'); | |
| // Simple cleanup for GPT-2 (it often adds quotes or extra text) | |
| let cleanCode = generatedText.trim(); | |
| // If the model returns a code block wrapper, strip it | |
| if (cleanCode.startsWith('```')) { | |
| cleanCode = cleanCode.replace(/```(javascript|js|html|css)?\n?/, '').replace(/```$/, ''); | |
| } | |
| codeEditor.value = cleanCode; | |
| logToConsole('Editor updated with new code.', 'success'); | |
| } catch (error) { | |
| console.error(error); | |
| logToConsole(`Generation error: ${error.message}`, 'warn'); | |
| } finally { | |
| // UI State: Reset | |
| generateBtn.disabled = false; | |
| btnText.innerHTML = '<i class="fa-solid fa-play"></i> Generate Code'; | |
| } | |
| } | |
| // --- Event Listeners --- | |
| // Temperature Slider | |
| tempInput.addEventListener('input', (e) => { | |
| tempValueDisplay.textContent = e.target.value; | |
| }); | |
| // Max Tokens Slider | |
| tokensInput.addEventListener('input', (e) => { | |
| tokensValueDisplay.textContent = e.target.value; | |
| }); | |
| // Generate Button | |
| generateBtn.addEventListener('click', handleCodeGeneration); | |
| // Initialize on Load | |
| window.addEventListener('DOMContentLoaded', () => { | |
| // Small delay to allow UI to paint first | |
| setTimeout(() => { | |
| initAI(); | |
| }, 500); | |
| }); | |
| </script> | |
| </body> | |
| </html> |