Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>ColabMind Coder — ML & Python Code Generator</title> | |
| <link rel="stylesheet" href="/static/style.css" /> | |
| <style> | |
| /* Minimal inline CSS to ensure the page looks fine if /static/style.css missing */ | |
| body { font-family: Inter, system-ui, Arial; background:#f7fafc; color:#0f172a; margin:0; } | |
| .container { max-width:1000px; margin:32px auto; padding:20px; background:white; box-shadow:0 6px 20px rgba(2,6,23,0.08); border-radius:12px; } | |
| h1{margin:0 0 12px 0;font-size:1.6rem} | |
| textarea#prompt{width:100%;min-height:120px;padding:12px;border-radius:8px;border:1px solid #e6edf3;resize:vertical;font-family:monospace} | |
| .btn-row{display:flex;gap:8px;margin:12px 0} | |
| button{padding:10px 14px;border-radius:8px;border:0;background:#2563eb;color:white;cursor:pointer} | |
| button.secondary{background:#ff8a00} | |
| button.ghost{background:#e6edf3;color:#0f172a} | |
| pre#codeOutput{background:#0b1220;color:#dbeafe;padding:12px;border-radius:8px;overflow:auto;min-height:220px} | |
| .small{font-size:0.9rem;color:#475569} | |
| .toast{position:fixed;right:18px;bottom:18px;padding:10px 14px;border-radius:8px;background:#111827;color:white;opacity:0;transition:opacity .25s} | |
| .toast.show{opacity:0.95} | |
| /* modal */ | |
| .modal{position:fixed;left:0;top:0;width:100%;height:100%;display:none;align-items:center;justify-content:center;background:rgba(2,6,23,0.6)} | |
| .modal.show{display:flex} | |
| .modal-card{background:white;padding:16px;border-radius:8px;max-width:900px;width:95%;max-height:80vh;overflow:auto} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>ColabMind Coder — ML & Python Code Generator</h1> | |
| <p class="small">Type a prompt (task or question) and press Generate. Use Explain to get inline explanations.</p> | |
| <label for="prompt">Prompt</label> | |
| <textarea id="prompt" placeholder="e.g., Write a PyTorch training loop for binary classification on tabular data with DataLoader and evaluation."></textarea> | |
| <div class="btn-row"> | |
| <button id="generateBtn" data-original="Generate Code">Generate Code</button> | |
| <button id="explainBtn" class="secondary" data-original="Explain Code">Explain Code</button> | |
| <button id="clearBtn" class="ghost" data-original="Clear">Clear</button> | |
| <div style="flex:1"></div> | |
| <div style="display:flex;gap:8px"> | |
| <button id="copyBtn" class="ghost" data-original="Copy">Copy</button> | |
| <button id="downloadBtn" class="ghost" data-original="Download">Download</button> | |
| </div> | |
| </div> | |
| <label for="codeOutput">Generated Code</label> | |
| <pre id="codeOutput" aria-label="Generated Code">// Generated code appears here</pre> | |
| <div style="margin-top:12px; display:flex; gap:12px; align-items:center;"> | |
| <div class="small">History:</div> | |
| <select id="historySelect"></select> | |
| <button id="loadHistory" class="ghost">Load</button> | |
| <button id="clearHistory" class="ghost">Clear History</button> | |
| <div style="flex:1"></div> | |
| <div class="small" id="statusText"></div> | |
| </div> | |
| </div> | |
| <!-- explanation modal --> | |
| <div id="explanationModal" class="modal" role="dialog" aria-modal="true"> | |
| <div class="modal-card"> | |
| <div style="display:flex;justify-content:space-between;align-items:center"> | |
| <h3>Explanation</h3> | |
| <button id="closeModal" class="ghost">Close</button> | |
| </div> | |
| <pre id="explanationContent" style="white-space:pre-wrap;background:#f8fafc;padding:12px;border-radius:6px"></pre> | |
| </div> | |
| </div> | |
| <div id="toast" class="toast"><span id="toastMessage"></span></div> | |
| <script> | |
| // ============ Config ============ | |
| // Relative endpoints work when frontend and backend are in same Space repo. | |
| const PREDICT_URL = "/run/predict"; | |
| const EXPLAIN_URL = "/run/explain"; | |
| // History stored in sessionStorage (client-side) + server-side persistence handled by backend. | |
| function loadLocalHistory(){ | |
| try{ | |
| const raw = sessionStorage.getItem('colabmind_history'); | |
| return raw ? JSON.parse(raw) : []; | |
| }catch(e){ return []; } | |
| } | |
| function saveLocalHistory(hist){ | |
| try{ sessionStorage.setItem('colabmind_history', JSON.stringify(hist)); }catch(e){} | |
| } | |
| // DOM | |
| const promptEl = document.getElementById('prompt'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const explainBtn = document.getElementById('explainBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const codeOutput = document.getElementById('codeOutput'); | |
| const toast = document.getElementById('toast'); | |
| const toastMessage = document.getElementById('toastMessage'); | |
| const explanationModal = document.getElementById('explanationModal'); | |
| const explanationContent = document.getElementById('explanationContent'); | |
| const closeModal = document.getElementById('closeModal'); | |
| const historySelect = document.getElementById('historySelect'); | |
| const loadHistoryBtn = document.getElementById('loadHistory'); | |
| const clearHistoryBtn = document.getElementById('clearHistory'); | |
| const statusText = document.getElementById('statusText'); | |
| let history = loadLocalHistory(); | |
| refreshHistoryUI(); | |
| // ============ helpers ============ | |
| function showToast(msg, kind='info'){ | |
| toastMessage.textContent = msg; | |
| toast.className = 'toast show'; | |
| setTimeout(()=> toast.classList.remove('show'), 3000); | |
| } | |
| function setLoading(btn, on=true){ | |
| if(on){ | |
| btn.disabled = true; | |
| btn.dataset.orig = btn.innerHTML; | |
| btn.innerHTML = '⏳ Loading...'; | |
| } else { | |
| btn.disabled = false; | |
| if(btn.dataset.orig) btn.innerHTML = btn.dataset.orig; | |
| } | |
| } | |
| // update history dropdown UI | |
| function refreshHistoryUI(){ | |
| historySelect.innerHTML = ''; | |
| for(let i=0;i<history.length;i++){ | |
| const opt = document.createElement('option'); | |
| opt.value = i; | |
| const p = history[i].prompt.replace(/\n/g,' ').slice(0,120); | |
| opt.textContent = `[${i}] ${history[i].time} — ${p}`; | |
| historySelect.appendChild(opt); | |
| } | |
| } | |
| // append new history entry, keep newest first | |
| function pushHistory(prompt, code){ | |
| const entry = { time: new Date().toLocaleString(), prompt: prompt, code: code }; | |
| history.unshift(entry); | |
| if(history.length>200) history.pop(); | |
| saveLocalHistory(history); | |
| refreshHistoryUI(); | |
| // notify backend to append persistent history (best-effort) | |
| fetch('/run/history_append', { | |
| method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({prompt:prompt, code:code}) | |
| }).catch(()=>{/* ignore errors */}); | |
| } | |
| // ============ API helpers ============ | |
| async function apiPredict(prompt, max_tokens=512, temperature=0.2, top_p=0.9){ | |
| const body = { data: [prompt, max_tokens, temperature, top_p] }; | |
| const res = await fetch(PREDICT_URL, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) }); | |
| if(!res.ok) throw new Error('Server error: '+res.status); | |
| const j = await res.json(); | |
| return j?.data?.[0] ?? null; | |
| } | |
| async function apiExplain(code, max_tokens=512, temperature=0.2, top_p=0.9){ | |
| const body = { data: [code, max_tokens, temperature, top_p] }; | |
| const res = await fetch(EXPLAIN_URL, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) }); | |
| if(!res.ok) throw new Error('Server error: '+res.status); | |
| const j = await res.json(); | |
| return j?.data?.[0] ?? null; | |
| } | |
| // ============ UI events ============ | |
| generateBtn.addEventListener('click', async ()=>{ | |
| const prompt = promptEl.value.trim(); | |
| if(!prompt){ showToast('Enter a prompt first','error'); return; } | |
| setLoading(generateBtn,true); | |
| codeOutput.textContent = '// Generating…'; | |
| try{ | |
| const out = await apiPredict(prompt, 512, 0.2, 0.9); | |
| codeOutput.textContent = out; | |
| pushHistory(prompt, out); | |
| showToast('Code generated'); | |
| }catch(err){ | |
| console.error(err); | |
| codeOutput.textContent = '// Error generating code — check console'; | |
| showToast('Generation failed','error'); | |
| }finally{ setLoading(generateBtn,false); } | |
| }); | |
| explainBtn.addEventListener('click', async ()=>{ | |
| const code = codeOutput.textContent || ''; | |
| if(!code){ showToast('No code to explain','error'); return; } | |
| setLoading(explainBtn,true); | |
| explanationContent.textContent = 'Explaining…'; | |
| try{ | |
| const out = await apiExplain(code, 512, 0.2, 0.9); | |
| explanationContent.textContent = out; | |
| explanationModal.classList.add('show'); | |
| }catch(err){ | |
| console.error(err); | |
| explanationContent.textContent = 'Error explaining code'; | |
| }finally{ setLoading(explainBtn,false); } | |
| }); | |
| clearBtn.addEventListener('click', ()=>{ | |
| promptEl.value=''; codeOutput.textContent='// Generated code will appear here'; | |
| showToast('Cleared'); | |
| }); | |
| copyBtn.addEventListener('click', async ()=>{ | |
| const code = codeOutput.textContent || ''; | |
| if(!code){ showToast('No code to copy','error'); return; } | |
| try{ await navigator.clipboard.writeText(code); showToast('Copied to clipboard'); }catch(e){ showToast('Copy failed','error'); } | |
| }); | |
| downloadBtn.addEventListener('click', ()=>{ | |
| const code = codeOutput.textContent || ''; | |
| if(!code){ showToast('No code to download','error'); return; } | |
| const blob = new Blob([code], {type:'text/plain'}); const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); a.href=url; a.download='generated_code.py'; document.body.appendChild(a); a.click(); a.remove(); | |
| URL.revokeObjectURL(url); | |
| showToast('Download started'); | |
| }); | |
| loadHistoryBtn.addEventListener('click', ()=>{ | |
| const idx = historySelect.value; | |
| if(idx===''|| idx==null){ showToast('Pick a history item','error'); return; } | |
| const entry = history[Number(idx)]; | |
| if(!entry){ showToast('Invalid index','error'); return; } | |
| promptEl.value = entry.prompt; | |
| codeOutput.textContent = entry.code; | |
| showToast('Loaded from history'); | |
| }); | |
| clearHistoryBtn.addEventListener('click', ()=>{ | |
| if(!confirm('Clear local history?')) return; | |
| history = []; saveLocalHistory(history); refreshHistoryUI(); | |
| fetch('/run/history_clear', {method:'POST'}).catch(()=>{/* ignore server errors */}); | |
| showToast('History cleared'); | |
| }); | |
| // modal close | |
| closeModal.addEventListener('click', ()=> explanationModal.classList.remove('show')); | |
| explanationModal.addEventListener('click', (e)=> { if(e.target===explanationModal) explanationModal.classList.remove('show'); }); | |
| // health ping (optional) | |
| async function pingStatus(){ | |
| try{ | |
| const r = await fetch('/health'); if(r.ok){ const j = await r.json(); statusText.textContent = `status: ${j.status}`; }else{ statusText.textContent='status:down'; } | |
| }catch(e){ statusText.textContent='status:down'; } | |
| } | |
| pingStatus(); | |
| </script> | |
| </body> | |
| </html> | |