| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"/> |
| <meta name="viewport" content="width=device-width,initial-scale=1.0"/> |
| <title>Problem Solver</title> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet"/> |
| <style> |
| *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} |
|
|
| :root{ |
| --white:#ffffff; |
| --bg:#f8f9fa; |
| --bg2:#f1f3f5; |
| --border:#e9ecef; |
| --border2:#dee2e6; |
| --text:#0f1117; |
| --text2:#343a40; |
| --text3:#6c757d; |
| --text4:#adb5bd; |
| --accent:#2563eb; |
| --accent-h:#1d4ed8; |
| --accent-bg:#eff6ff; |
| --accent-border:#bfdbfe; |
| --success:#16a34a; |
| --success-bg:#f0fdf4; |
| --success-border:#bbf7d0; |
| --danger:#dc2626; |
| --radius:10px; |
| --radius-sm:7px; |
| --shadow:0 1px 3px rgba(0,0,0,.06),0 1px 2px rgba(0,0,0,.04); |
| --shadow-md:0 4px 6px rgba(0,0,0,.05),0 2px 4px rgba(0,0,0,.04); |
| } |
|
|
| html,body{height:100%;font-family:'Inter',system-ui,sans-serif;font-size:15px;line-height:1.5;color:var(--text);background:var(--bg);-webkit-font-smoothing:antialiased;} |
|
|
| /* ── Layout ── */ |
| .app{display:grid;grid-template-rows:57px 1fr;height:100vh;overflow:hidden;} |
|
|
| /* ── Nav ── */ |
| .nav{ |
| display:flex;align-items:center;padding:0 28px; |
| background:var(--white);border-bottom:1px solid var(--border); |
| box-shadow:var(--shadow);position:relative;z-index:10; |
| } |
| .nav-logo{display:flex;align-items:center;gap:9px;} |
| .nav-mark{ |
| width:28px;height:28px;background:var(--accent);border-radius:7px; |
| display:flex;align-items:center;justify-content:center;flex-shrink:0; |
| } |
| .nav-mark svg{width:14px;height:14px;stroke:#fff;fill:none;stroke-width:2.5;} |
| .nav-name{font-size:15px;font-weight:600;color:var(--text);letter-spacing:-.01em;} |
|
|
| /* ── Body ── */ |
| .body{display:grid;grid-template-columns:380px 1fr;overflow:hidden;} |
|
|
| /* ── Sidebar ── */ |
| .side{ |
| background:var(--white);border-right:1px solid var(--border); |
| display:flex;flex-direction:column;overflow:hidden; |
| } |
| .side-scroll{flex:1;overflow-y:auto;padding:28px 24px 0;} |
| .side-scroll::-webkit-scrollbar{width:0;} |
| .side-foot{padding:20px 24px;border-top:1px solid var(--border);} |
|
|
| /* Section label */ |
| .slabel{ |
| font-size:11px;font-weight:600;color:var(--text4); |
| letter-spacing:.08em;text-transform:uppercase; |
| margin-bottom:10px; |
| } |
|
|
| /* Name field */ |
| .name-field{margin-bottom:28px;} |
| .name-field label{display:block;font-size:13px;font-weight:500;color:var(--text3);margin-bottom:6px;} |
| input[type=text]{ |
| width:100%;background:var(--bg);border:1.5px solid var(--border); |
| border-radius:var(--radius-sm);color:var(--text); |
| font:inherit;font-size:15px;padding:10px 14px; |
| outline:none;transition:border-color .15s,box-shadow .15s; |
| } |
| input[type=text]:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.1);} |
| input::placeholder{color:var(--text4);} |
|
|
| /* ── Tabs ── */ |
| .tabs{ |
| display:grid;grid-template-columns:1fr 1fr; |
| gap:0;background:var(--bg2);border-radius:var(--radius); |
| padding:3px;margin-bottom:16px; |
| } |
| .tab{ |
| padding:9px 12px;text-align:center;font-size:14px;font-weight:500; |
| color:var(--text3);border-radius:8px;cursor:pointer; |
| transition:all .18s;user-select:none; |
| } |
| .tab.on{ |
| background:var(--white);color:var(--text);font-weight:600; |
| box-shadow:var(--shadow); |
| } |
|
|
| /* ── Panes ── */ |
| #pp,#up{display:none;} |
| #pp.on,#up.on{display:block;} |
|
|
| /* Upload — primary */ |
| .drop{ |
| border:2px dashed var(--border2);border-radius:var(--radius); |
| padding:36px 20px;text-align:center;cursor:pointer; |
| background:var(--bg);transition:border-color .2s,background .2s; |
| position:relative; |
| } |
| .drop:hover,.drop.over{border-color:var(--accent);background:var(--accent-bg);} |
| .drop-icon{ |
| width:44px;height:44px;border-radius:11px; |
| background:var(--white);border:1.5px solid var(--border); |
| display:flex;align-items:center;justify-content:center; |
| margin:0 auto 14px;box-shadow:var(--shadow); |
| } |
| .drop-icon svg{width:20px;height:20px;stroke:var(--text3);fill:none;stroke-width:1.8;} |
| .drop h4{font-size:15px;font-weight:600;color:var(--text2);margin-bottom:4px;} |
| .drop p{font-size:13px;color:var(--text3);line-height:1.5;} |
| .drop-chips{display:flex;gap:6px;justify-content:center;margin-top:12px;} |
| .chip{ |
| font-size:11px;font-weight:500;color:var(--text3); |
| background:var(--white);border:1px solid var(--border); |
| border-radius:4px;padding:2px 7px;letter-spacing:.03em; |
| } |
|
|
| .fbadge{ |
| display:none;align-items:center;gap:10px; |
| padding:11px 14px;background:var(--success-bg); |
| border:1.5px solid var(--success-border);border-radius:var(--radius-sm); |
| margin-top:12px; |
| } |
| .fbadge.on{display:flex;} |
| .fbadge-icon{width:28px;height:28px;background:var(--success-bg);border:1.5px solid var(--success-border);border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;} |
| .fbadge-icon svg{width:14px;height:14px;stroke:var(--success);fill:none;stroke-width:2;} |
| .fbadge-name{flex:1;font-size:13px;font-weight:500;color:var(--success);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;} |
| .fbadge-clear{cursor:pointer;color:var(--text4);font-size:16px;line-height:1;transition:color .15s;} |
| .fbadge-clear:hover{color:var(--text2);} |
| input[type=file]{display:none;} |
|
|
| /* Textarea */ |
| textarea{ |
| width:100%;background:var(--bg);border:1.5px solid var(--border); |
| border-radius:var(--radius);color:var(--text); |
| font:inherit;font-size:14px;line-height:1.7; |
| padding:14px;min-height:220px;resize:none;outline:none; |
| transition:border-color .15s,box-shadow .15s; |
| } |
| textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(37,99,235,.1);} |
| textarea::placeholder{color:var(--text4);} |
|
|
| /* ── Buttons ── */ |
| .btn-run{ |
| width:100%;background:var(--accent);color:#fff;border:none; |
| border-radius:var(--radius-sm);padding:13px 18px; |
| font:inherit;font-size:15px;font-weight:600;cursor:pointer; |
| display:flex;align-items:center;justify-content:center;gap:8px; |
| transition:background .15s,box-shadow .15s,transform .1s; |
| letter-spacing:-.01em; |
| } |
| .btn-run:hover:not(:disabled){background:var(--accent-h);box-shadow:0 4px 14px rgba(37,99,235,.35);} |
| .btn-run:active:not(:disabled){transform:scale(.985);} |
| .btn-run:disabled{opacity:.45;cursor:not-allowed;} |
| .btn-run svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2.5;} |
|
|
| .btn-pdf{ |
| width:100%;margin-top:8px;background:transparent; |
| color:var(--text3);border:1.5px solid var(--border); |
| border-radius:var(--radius-sm);padding:11px 18px; |
| font:inherit;font-size:14px;font-weight:500;cursor:pointer; |
| display:flex;align-items:center;justify-content:center;gap:8px; |
| transition:all .15s; |
| } |
| .btn-pdf:hover:not(:disabled){background:var(--success-bg);color:var(--success);border-color:var(--success-border);} |
| .btn-pdf:disabled{opacity:.3;cursor:not-allowed;} |
| .btn-pdf svg{width:13px;height:13px;stroke:currentColor;fill:none;stroke-width:2;} |
|
|
| /* ── Main output ── */ |
| .main{display:flex;flex-direction:column;overflow:hidden;background:var(--white);} |
|
|
| .bar{ |
| display:flex;align-items:center;gap:10px; |
| padding:0 28px;height:46px;border-bottom:1px solid var(--border);flex-shrink:0; |
| background:var(--white); |
| } |
| .sdot{width:7px;height:7px;border-radius:50%;background:var(--border);flex-shrink:0;transition:background .3s;} |
| .sdot.run{background:var(--accent);animation:pulse .9s infinite;} |
| .sdot.done{background:var(--success);} |
| .sdot.err{background:var(--danger);} |
| @keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}} |
| .stxt{font-size:13px;color:var(--text3);} |
|
|
| .pipe{margin-left:auto;display:flex;align-items:center;gap:4px;} |
| .pn{ |
| font-size:11.5px;font-weight:500;color:var(--text4); |
| padding:3px 9px;border-radius:5px; |
| border:1px solid transparent;background:transparent; |
| transition:all .2s; |
| } |
| .pn.run{color:var(--accent);border-color:var(--accent-border);background:var(--accent-bg);} |
| .pn.done{color:var(--success);border-color:var(--success-border);background:var(--success-bg);} |
| .ps{font-size:10px;color:var(--border);} |
|
|
| /* ── Output ── */ |
| .out{flex:1;overflow-y:auto;background:var(--white);} |
| .out::-webkit-scrollbar{width:4px;} |
| .out::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px;} |
|
|
| .empty{ |
| display:flex;flex-direction:column;align-items:center; |
| justify-content:center;height:100%;gap:14px; |
| text-align:center;padding-bottom:60px; |
| } |
| .empty-ico{ |
| width:52px;height:52px;border-radius:13px; |
| background:var(--bg);border:1.5px solid var(--border); |
| display:flex;align-items:center;justify-content:center; |
| } |
| .empty-ico svg{width:22px;height:22px;stroke:var(--text4);fill:none;stroke-width:1.6;} |
| .empty h3{font-size:17px;font-weight:600;color:var(--text2);letter-spacing:-.01em;} |
| .empty p{font-size:14px;color:var(--text3);max-width:280px;line-height:1.65;} |
|
|
| /* ── Output sections ── */ |
| .section{ |
| border-bottom:1px solid var(--border); |
| padding:24px 32px; |
| opacity:0;transform:translateY(8px); |
| transition:opacity .35s ease,transform .35s ease; |
| } |
| .section.in{opacity:1;transform:translateY(0);} |
| .section:last-child{border-bottom:none;} |
|
|
| .section-hd{display:flex;align-items:center;gap:10px;margin-bottom:16px;} |
| .section-badge{ |
| font-size:11px;font-weight:600;color:var(--text3); |
| background:var(--bg2);border:1px solid var(--border); |
| border-radius:5px;padding:2px 8px;letter-spacing:.04em; |
| text-transform:uppercase;flex-shrink:0; |
| } |
| .section-title{font-size:15px;font-weight:600;color:var(--text);letter-spacing:-.01em;flex:1;} |
| .section-spin{ |
| width:14px;height:14px;flex-shrink:0; |
| border:1.8px solid var(--text4);border-top-color:transparent; |
| border-radius:50%;animation:spin .7s linear infinite; |
| } |
| .section-tick{font-size:15px;color:var(--success);flex-shrink:0;} |
| @keyframes spin{to{transform:rotate(360deg)}} |
|
|
| /* ── Prose ── */ |
| .prose{font-size:15px;line-height:1.8;color:var(--text2);} |
| .prose h2{ |
| font-size:14px;font-weight:600;color:var(--text); |
| margin:20px 0 8px;letter-spacing:-.01em; |
| } |
| .prose h2:first-child{margin-top:0;} |
| .prose h3{font-size:14px;font-weight:600;color:var(--text2);margin:14px 0 5px;} |
| .prose strong{font-weight:600;color:var(--text);} |
| .prose ul,.prose ol{padding-left:20px;margin:6px 0 10px;} |
| .prose li{margin-bottom:4px;} |
| .prose br+br{display:none;} |
|
|
| /* ── Toast ── */ |
| .toast{ |
| position:fixed;bottom:20px;left:50%;transform:translateX(-50%); |
| background:var(--text);color:#fff;padding:10px 18px; |
| border-radius:8px;font-size:14px;display:none;z-index:999; |
| box-shadow:var(--shadow-md);white-space:nowrap; |
| } |
| .toast.err{background:var(--danger);} |
|
|
| @media(max-width:780px){ |
| .body{grid-template-columns:1fr;} |
| .side{border-right:none;border-bottom:1px solid var(--border);} |
| .main{min-height:55vh;} |
| .pipe{display:none;} |
| } |
| </style> |
| </head> |
| <body> |
| <div class="app"> |
|
|
| <nav class="nav"> |
| <div class="nav-logo"> |
| <div class="nav-mark"> |
| <svg viewBox="0 0 24 24"><path d="M9 12l2 2 4-4M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg> |
| </div> |
| <span class="nav-name">Problem Solver</span> |
| </div> |
| </nav> |
|
|
| <div class="body"> |
|
|
| |
| <aside class="side"> |
| <div class="side-scroll"> |
|
|
| <div class="slabel">Your name</div> |
| <div class="name-field"> |
| <input type="text" id="iName" placeholder="e.g. Priya Sharma"/> |
| </div> |
|
|
| <div class="slabel">Input</div> |
| <div class="tabs"> |
| <div class="tab on" id="tUpload" onclick="setTab('upload')">Upload file</div> |
| <div class="tab" id="tPaste" onclick="setTab('paste')">Paste text</div> |
| </div> |
|
|
| |
| <div id="up" class="on"> |
| <div class="drop" id="dz" |
| onclick="document.getElementById('fi').click()" |
| ondragover="dg(event,true)" ondragleave="dg(event,false)" ondrop="dp(event)"> |
| <div class="drop-icon"> |
| <svg viewBox="0 0 24 24"><path d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M16 10l-4-4-4 4M12 6v10"/></svg> |
| </div> |
| <h4>Upload a file</h4> |
| <p>Drag & drop or click to browse</p> |
| <div class="drop-chips"> |
| <span class="chip">PDF</span> |
| <span class="chip">TXT</span> |
| <span class="chip">MD</span> |
| </div> |
| </div> |
| <div class="fbadge" id="fb"> |
| <div class="fbadge-icon"> |
| <svg viewBox="0 0 24 24"><path d="M9 12l2 2 4-4"/><rect x="3" y="3" width="18" height="18" rx="3"/></svg> |
| </div> |
| <span class="fbadge-name" id="fn">—</span> |
| <span class="fbadge-clear" onclick="cf()">✕</span> |
| </div> |
| <input type="file" id="fi" accept=".pdf,.txt,.md" onchange="pf(event)"/> |
| </div> |
|
|
| |
| <div id="pp"> |
| <textarea id="tx" placeholder="Paste your transcript, meeting notes, problem description, or any text you want analyzed…"></textarea> |
| </div> |
|
|
| </div> |
|
|
| <div class="side-foot"> |
| <button class="btn-run" id="bRun" onclick="run()"> |
| <svg viewBox="0 0 24 24"><path d="M5 3l14 9-14 9V3z"/></svg> |
| Analyze |
| </button> |
| <button class="btn-pdf" id="bPdf" disabled onclick="getPdf()"> |
| <svg viewBox="0 0 24 24"><path d="M12 15V3m0 12l-4-4m4 4l4-4M3 17v2a2 2 0 002 2h14a2 2 0 002-2v-2"/></svg> |
| Download PDF |
| </button> |
| </div> |
| </aside> |
|
|
| |
| <main class="main"> |
| <div class="bar"> |
| <div class="sdot" id="sd"></div> |
| <span class="stxt" id="st">Ready</span> |
| <div class="pipe"> |
| <div class="pn" id="pn0">Analysis</div><div class="ps">›</div> |
| <div class="pn" id="pn1">Root Cause</div><div class="ps">›</div> |
| <div class="pn" id="pn2">Solutions</div><div class="ps">›</div> |
| <div class="pn" id="pn3">Action Plan</div><div class="ps">›</div> |
| <div class="pn" id="pn4">Reflection</div> |
| </div> |
| </div> |
|
|
| <div class="out" id="out"> |
| <div class="empty" id="es"> |
| <div class="empty-ico"> |
| <svg viewBox="0 0 24 24"><path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg> |
| </div> |
| <h3>Upload or paste your transcript</h3> |
| <p>Meeting notes, interview recordings, problem descriptions — five agents will break it down and hand you a clear plan.</p> |
| </div> |
| </div> |
| </main> |
|
|
| </div> |
| </div> |
| <div class="toast" id="toast"></div> |
|
|
| <script> |
| const API='https://banao-tech-problem-decoder.hf.space'; |
| const AG=[ |
| {key:'analyst', title:'Problem Analysis', n:'01'}, |
| {key:'root_cause', title:'Root Cause', n:'02'}, |
| {key:'solutions', title:'Solutions', n:'03'}, |
| {key:'action_plan', title:'Action Plan', n:'04'}, |
| {key:'thinking', title:'Reflection', n:'05'}, |
| ]; |
| let running=false,selFile=null,lastP=null,tab='upload'; |
| |
| function setTab(t){ |
| tab=t; |
| document.getElementById('tUpload').classList.toggle('on',t==='upload'); |
| document.getElementById('tPaste').classList.toggle('on',t==='paste'); |
| document.getElementById('up').classList.toggle('on',t==='upload'); |
| document.getElementById('pp').classList.toggle('on',t==='paste'); |
| } |
| function dg(e,on){e.preventDefault();document.getElementById('dz').classList.toggle('over',on);} |
| function dp(e){e.preventDefault();document.getElementById('dz').classList.remove('over');const f=e.dataTransfer.files[0];if(f)sf(f);} |
| function pf(e){const f=e.target.files[0];if(f)sf(f);} |
| function sf(f){ |
| selFile=f; |
| document.getElementById('fn').textContent=f.name; |
| document.getElementById('fb').classList.add('on'); |
| } |
| function cf(){ |
| selFile=null; |
| document.getElementById('fn').textContent='—'; |
| document.getElementById('fb').classList.remove('on'); |
| document.getElementById('fi').value=''; |
| } |
| |
| function toast(m,e=false){ |
| const t=document.getElementById('toast'); |
| t.textContent=m;t.className='toast'+(e?' err':''); |
| t.style.display='block'; |
| setTimeout(()=>t.style.display='none',3500); |
| } |
| function ss(txt,s=''){ |
| document.getElementById('st').textContent=txt; |
| document.getElementById('sd').className='sdot '+s; |
| } |
| function sp(i,s){const e=document.getElementById('pn'+i);if(e)e.className='pn '+s;} |
| function rp(){for(let i=0;i<5;i++)sp(i,'');} |
| |
| function md(s){ |
| return s |
| .replace(/^## (.+)$/gm,'<h2>$1</h2>') |
| .replace(/^### (.+)$/gm,'<h3>$1</h3>') |
| .replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>') |
| .replace(/^[-*] (.+)$/gm,'<li>$1</li>') |
| .replace(/(<li>[^]*?<\/li>\n?)+/g,x=>`<ul>${x}</ul>`) |
| .replace(/\n/g,'<br>'); |
| } |
| |
| async function rf(f){ |
| if(f.name.endsWith('.pdf')){ |
| return new Promise((res,rej)=>{ |
| const r=new FileReader(); |
| r.onload=()=>res('__PDF_BASE64__'+r.result.split(',')[1]); |
| r.onerror=rej;r.readAsDataURL(f); |
| }); |
| } |
| return new Promise((res,rej)=>{ |
| const r=new FileReader(); |
| r.onload=()=>res(r.result); |
| r.onerror=rej;r.readAsText(f); |
| }); |
| } |
| |
| async function run(){ |
| if(running)return; |
| const name=document.getElementById('iName').value.trim()||'Anonymous'; |
| let content=''; |
| if(tab==='upload'){ |
| if(!selFile){toast('Upload a file first.',true);return;} |
| try{content=await rf(selFile);}catch{toast('Could not read file.',true);return;} |
| } else { |
| content=document.getElementById('tx').value.trim(); |
| if(!content){toast('Paste something first.',true);return;} |
| } |
| |
| running=true; |
| lastP={content,user_name:name,user_role:'',user_goal:''}; |
| document.getElementById('bRun').disabled=true; |
| document.getElementById('bPdf').disabled=true; |
| rp(); |
| |
| const out=document.getElementById('out'); |
| out.innerHTML=''; |
| ss('Running…','run'); |
| |
| AG.forEach(a=>{ |
| const d=document.createElement('div'); |
| d.className='section'; |
| d.id='s-'+a.key; |
| d.innerHTML=` |
| <div class="section-hd"> |
| <span class="section-badge">${a.n}</span> |
| <span class="section-title">${a.title}</span> |
| <div class="section-spin" id="sp-${a.key}"></div> |
| </div> |
| <div class="prose" id="pr-${a.key}"></div>`; |
| out.appendChild(d); |
| }); |
| |
| try{ |
| const res=await fetch(`${API}/analyze/stream`,{ |
| method:'POST',headers:{'Content-Type':'application/json'}, |
| body:JSON.stringify(lastP), |
| }); |
| if(!res.ok){const e=await res.json().catch(()=>({detail:res.statusText}));throw new Error(e.detail);} |
| |
| const reader=res.body.getReader(),dec=new TextDecoder(); |
| let buf='',cur=null; |
| |
| while(true){ |
| const{done,value}=await reader.read(); |
| if(done)break; |
| buf+=dec.decode(value,{stream:true}); |
| const lines=buf.split('\n');buf=lines.pop(); |
| for(const line of lines){ |
| if(!line.startsWith('data: '))continue; |
| try{ |
| const m=JSON.parse(line.slice(6)); |
| if(m.event==='agent_start'){ |
| cur=m.agent; |
| const i=AG.findIndex(a=>a.key===cur); |
| sp(i,'run'); |
| document.getElementById('s-'+cur).classList.add('in'); |
| ss(m.label+'…','run'); |
| document.getElementById('s-'+cur).scrollIntoView({behavior:'smooth',block:'nearest'}); |
| } else if(m.event==='token'&&m.agent===cur){ |
| const p=document.getElementById('pr-'+m.agent); |
| if(p){p.dataset.r=(p.dataset.r||'')+m.text;p.innerHTML=md(p.dataset.r);} |
| } else if(m.event==='agent_done'){ |
| const sp2=document.getElementById('sp-'+m.agent); |
| if(sp2)sp2.outerHTML='<span class="section-tick">✓</span>'; |
| const i=AG.findIndex(a=>a.key===m.agent); |
| sp(i,'done'); |
| } else if(m.event==='done'){ |
| ss('Analysis complete','done'); |
| document.getElementById('bPdf').disabled=false; |
| } |
| }catch{} |
| } |
| } |
| }catch(e){ss('Something went wrong','err');toast(e.message,true);} |
| |
| running=false; |
| document.getElementById('bRun').disabled=false; |
| } |
| |
| async function getPdf(){ |
| if(!lastP)return; |
| const b=document.getElementById('bPdf'); |
| b.textContent='Generating…';b.disabled=true; |
| try{ |
| const res=await fetch(`${API}/analyze/pdf`,{ |
| method:'POST',headers:{'Content-Type':'application/json'}, |
| body:JSON.stringify(lastP), |
| }); |
| if(!res.ok)throw new Error('PDF generation failed'); |
| const blob=await res.blob(),a=document.createElement('a'); |
| a.href=URL.createObjectURL(blob); |
| a.download=`analysis_${Date.now()}.pdf`; |
| a.click(); |
| }catch(e){toast(e.message,true);} |
| b.innerHTML='<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 15V3m0 12l-4-4m4 4l4-4M3 17v2a2 2 0 002 2h14a2 2 0 002-2v-2"/></svg> Download PDF'; |
| b.disabled=false; |
| } |
| </script> |
| </body> |
| </html> |