FlagshipAi / AIDesign /app /static /index.html
Ali2206's picture
Add loading states to all agent generate buttons with proper try-catch blocks
5dcd1ab
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>AI UX Prototyper</title>
<link rel="stylesheet" href="/static/suite.css">
<style>
:root{--bg:#f7f7f8;--surface:#ffffff;--text:#0f172a;--muted:#6b7280;--border:#e5e7eb;--brand:#111827;--radius:12px;--space-1:8px;--space-2:12px;--space-3:16px;--space-4:24px;--space-5:32px;--shadow-1:0 1px 2px rgba(0,0,0,.05),0 1px 3px rgba(0,0,0,.08)}
*{box-sizing:border-box}
body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);margin:0}
.container{max-width:1400px;margin:0 auto;padding:var(--space-5) var(--space-5)}
.header{margin-bottom:var(--space-4)}
.header h1{margin:0}
.header p{margin:.5rem 0 0;color:var(--muted)}
.card,.pane{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-1)}
.card{padding:var(--space-4)}
label{display:block;margin:var(--space-2) 0 var(--space-1);font-weight:600}
input,textarea{width:100%;padding:var(--space-2);border:1px solid var(--border);border-radius:8px;font-size:16px;background:#fff}
select{width:100%;padding:var(--space-2);border:1px solid var(--border);border-radius:8px;font-size:16px;background:#fff}
input[type=color]{width:56px;height:40px;padding:0;border:1px solid var(--border);border-radius:8px;background:#fff}
.actions{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-2)}
.btn{appearance:none;border:0;border-radius:8px;padding:10px 16px;font-weight:600;cursor:pointer}
.btn--primary{background:var(--brand);color:#fff}
.btn--ghost{background:transparent;color:var(--brand);border:1px solid var(--border)}
.grid{display:grid;grid-template-columns:1fr;gap:var(--space-3);margin-top:var(--space-3)}
@media (min-width:900px){.grid{grid-template-columns:1fr 1fr}}
@media (min-width:1200px){.grid{grid-template-columns:1.25fr 1fr}}
.pane{padding:var(--space-3);min-height:240px;overflow:auto}
.pane h3{margin-top:0}
.toolbar{display:flex;gap:var(--space-2);align-items:center;margin-bottom:var(--space-2)}
pre{white-space:pre-wrap;background:#fafafa;border:1px dashed var(--border);padding:var(--space-2);border-radius:8px}
details{margin-top:var(--space-3)}
img{max-width:100%;border:1px solid var(--border);border-radius:8px}
/* Shared suite header */
.site-header{position:sticky;top:0;background:var(--surface);border-bottom:1px solid var(--border);z-index:50}
.site-header .nav{display:flex;align-items:center;justify-content:space-between;max-width:1400px;margin:0 auto;padding:12px 24px}
.site-header .brand a{text-decoration:none;color:var(--text)}
.site-header .links{display:flex;gap:16px}
.site-header .links a{text-decoration:none;color:var(--muted);padding:6px 10px;border-radius:8px}
.site-header .links a:hover{background:#f3f4f6;color:var(--text)}
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
</head>
<body>
<div id="suite-shared-header"></div>
<script src="/static/header.js"></script>
<div class="container">
<header class="header">
<h1>AI UX Prototyper</h1>
<p>Describe a feature to generate a clickable wireframe and a flow diagram.</p>
</header>
<section class="card">
<form id="form">
<label>Feature</label>
<input id="feature" placeholder="e.g., voice assistant for claims" required />
<label>Constraints (optional)</label>
<textarea id="constraints" rows="2" placeholder="e.g., mobile first; privacy by design"></textarea>
<div class="grid" style="grid-template-columns:1fr 1fr;gap:12px;margin-top:8px">
<div>
<label>Company (text only)</label>
<input id="company" placeholder="e.g., Flagship" />
</div>
<div>
<label>Accent color</label>
<input id="accent" type="color" value="#3b82f6" />
</div>
<div>
<label>Palette</label>
<select id="palette">
<option value="neutral" selected>Neutral</option>
<option value="cool">Cool</option>
<option value="warm">Warm</option>
<option value="dark">Dark</option>
</select>
</div>
<div>
<label>Tone</label>
<select id="tone">
<option value="professional" selected>Professional</option>
<option value="friendly">Friendly</option>
<option value="formal">Formal</option>
<option value="playful">Playful</option>
</select>
</div>
</div>
<div class="actions">
<button type="submit" class="btn btn--primary">Generate Prototype</button>
</div>
</form>
</section>
<section class="grid">
<div class="pane">
<h3>Wireframe</h3>
<div class="toolbar">
<button id="openNew" class="btn btn--ghost">Open in new tab</button>
</div>
<div id="wire"></div>
<div id="imgWrap" style="display:none; margin-top:8px"><img id="wireImg" alt="Wireframe image"/></div>
</div>
<div class="pane">
<h3>Flow</h3>
<div id="flowImgWrap" style="display:none; margin-top:8px"><img id="flowImg" alt="Flow image"/></div>
<div id="mermaidSvg" style="display:none"></div>
<pre id="mermaid"></pre>
</div>
</section>
<details><summary>Raw JSON</summary><pre id="raw"></pre></details>
</div>
<script>
const form = document.getElementById('form');
const feature = document.getElementById('feature');
const constraints = document.getElementById('constraints');
const wire = document.getElementById('wire');
let lastWireHtml = '';
const mm = document.getElementById('mermaid');
const mmSvg = document.getElementById('mermaidSvg');
const raw = document.getElementById('raw');
const openNew = document.getElementById('openNew');
const imgWrap = document.getElementById('imgWrap');
const wireImg = document.getElementById('wireImg');
const flowImgWrap = document.getElementById('flowImgWrap');
const flowImg = document.getElementById('flowImg');
const company = document.getElementById('company');
const accent = document.getElementById('accent');
const paletteSel = document.getElementById('palette');
const toneSel = document.getElementById('tone');
form.addEventListener('submit', async (e)=>{
e.preventDefault();
// Set loading state
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.textContent = 'Generating...';
submitBtn.disabled = true;
wire.innerHTML = '(loading)';
mm.textContent = '(loading)';
mmSvg.style.display='none';
mmSvg.innerHTML='';
flowImgWrap.style.display='none';
raw.textContent='(loading)';
try{
const savedKey = (function(){ try { return (localStorage.getItem('OPENAI_API_KEY')||'').trim(); } catch(e){ return ''; } })();
const r = await fetch('/ux/prototype', {method:'POST', headers:{'Content-Type':'application/json', 'Authorization': savedKey ? ('Bearer ' + savedKey) : undefined, 'x-openai-key': savedKey || ''}, body: JSON.stringify({ feature: feature.value.trim(), constraints: constraints.value.trim(), company: (company.value||'').trim() || undefined, accent_color: accent.value, palette: paletteSel.value, tone: toneSel.value, api_key: savedKey || null })});
const text = await r.text();
let data = {};
try{ data = text ? JSON.parse(text) : {}; }catch{ data = { error: text } }
raw.textContent = JSON.stringify(data, null, 2);
if(!r.ok){ wire.textContent='Error'; mm.textContent='Error'; flowImgWrap.style.display='none'; mm.style.display='block'; return; }
// Isolate prototype CSS/JS in an iframe so it cannot affect the host page
lastWireHtml = data.wireframe_html || '';
console.log('Wireframe HTML length:', lastWireHtml.length);
console.log('Wireframe HTML preview:', lastWireHtml.substring(0, 200));
wire.innerHTML = '';
if (lastWireHtml) {
const frame = document.createElement('iframe');
frame.setAttribute('title','Wireframe Preview');
frame.setAttribute('referrerpolicy','no-referrer');
// Keep strict sandbox (no same-origin) so prototype CSS/JS cannot touch host page
frame.setAttribute('sandbox','allow-scripts allow-forms allow-modals allow-popups');
frame.style.width = '100%';
frame.style.height = '800px';
frame.style.border = '1px solid var(--border)';
frame.srcdoc = lastWireHtml;
wire.appendChild(frame);
}
mm.textContent = data.flow_mermaid;
if (data.wireframe_image_data_url) {
wireImg.onerror = ()=>{ imgWrap.style.display='none'; };
wireImg.src = data.wireframe_image_data_url;
imgWrap.style.display='block';
} else { imgWrap.style.display='none'; }
if (data.flow_image_data_url) {
flowImg.onerror = ()=>{ flowImgWrap.style.display='none'; mm.style.display='block'; };
flowImg.src = data.flow_image_data_url;
flowImgWrap.style.display='block';
mm.style.display='none';
} else {
flowImgWrap.style.display='none';
// Render Mermaid to SVG if possible
try {
if (window.mermaid && data.flow_mermaid && data.flow_mermaid.trim().startsWith('graph')){
window.mermaid.initialize({ startOnLoad:false, theme:'base' });
const id = 'mmd-' + Math.random().toString(36).slice(2);
window.mermaid.render(id, data.flow_mermaid)
.then(({ svg })=>{
mmSvg.innerHTML = svg;
mmSvg.style.display='block';
mm.style.display='none';
})
.catch(()=>{
mmSvg.style.display='none';
mm.style.display='block';
});
} else {
mmSvg.style.display='none';
mm.style.display='block';
}
} catch (e){
mmSvg.style.display='none';
mm.style.display='block';
}
}
}catch(err){ wire.textContent=String(err); mm.textContent=''; raw.textContent=''; }
finally {
// Reset button state
submitBtn.textContent = originalText;
submitBtn.disabled = false;
}
});
openNew.addEventListener('click', ()=>{
if (!lastWireHtml) {
alert('No wireframe content available. Please generate a prototype first.');
return;
}
// Use the wireframe HTML directly - it already contains complete HTML structure
const fullHtml = lastWireHtml;
try {
// Create a blob URL
const blob = new Blob([fullHtml], { type: 'text/html' });
const url = URL.createObjectURL(blob);
// Create a temporary link and click it
const link = document.createElement('a');
link.href = url;
link.target = '_blank';
// Remove download attribute to open in new tab instead of downloading
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Clean up the blob URL after a short delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
} catch (err) {
console.error('Error creating new tab:', err);
// Fallback: try window.open with user gesture
try {
const w = window.open('', '_blank');
if (w) {
w.document.open();
w.document.write(fullHtml);
w.document.close();
} else {
alert('Unable to open new tab. Please check your browser settings or try copying the content manually.');
}
} catch (fallbackErr) {
alert('Error opening new tab: ' + fallbackErr.message);
}
}
});
// Removed non-working image toggle
</script>
</body>
</html>