';box.append(r)});box.scrollTop=box.scrollHeight;box.querySelectorAll('[data-copy]').forEach(b=>b.onclick=()=>navigator.clipboard.writeText(c.messages[+b.dataset.copy].content).then(()=>toast('Copied')));box.querySelectorAll('[data-retry]').forEach(b=>b.onclick=()=>send(c.messages[+b.dataset.retry-1]?.content||'',true))}
function newChat(){id=null;draw();history();$('#prompt').focus()}function ensure(text){if(!id){let c={id:uid(),title:text.slice(0,42)+(text.length>42?'…':''),messages:[],createdAt:Date.now()};chats.unshift(c);id=c.id}return active()}
-async function send(text,retry=false){if(controller||!text.trim())return;let c=ensure(text);if(!retry)c.messages.push({role:'user',content:text});else c.messages=c.messages.slice(0,-1);let answer={role:'assistant',content:''};c.messages.push(answer);$('#prompt').value='';draw();history();controller=new AbortController();$('#send').textContent='Stop ■';$('#send').classList.add('stop');status('Generating…');try{if(!cfg.apiKey)throw Error('Add an API key and endpoint in Settings first.');let r=await fetch(cfg.endpoint||'https://api.openai.com/v1/chat/completions',{method:'POST',signal:controller.signal,headers:{'Content-Type':'application/json','Authorization':'Bearer '+cfg.apiKey},body:JSON.stringify({model:$('#model').value,messages:c.messages.filter(m=>m.content).map(m=>({role:m.role,content:m.content})),stream:true})});if(!r.ok)throw Error('Provider returned '+r.status+'. Check your key, model, or endpoint.');let reader=r.body?.getReader(),decoder=new TextDecoder(),buffer='';if(!reader)throw Error('Provider did not return a readable stream.');while(true){let {done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:true});let lines=buffer.split('\\n');buffer=lines.pop();for(let line of lines){if(!line.startsWith('data:'))continue;let payload=line.slice(5).trim();if(payload==='[DONE]')break;try{let d=JSON.parse(payload),delta=d.choices?.[0]?.delta?.content||d.candidates?.[0]?.content?.parts?.[0]?.text||'';if(delta){answer.content+=delta;draw()}}catch{}}}status('');}catch(e){if(e.name==='AbortError'){status('Generation stopped')}else{answer.content='Could not generate a response. '+e.message;status('Provider error',true)}}finally{controller=null;$('#send').textContent='Send ↑';$('#send').classList.remove('stop');save();draw();history()}}
+async function send(text,retry=false){if(controller||!text.trim())return;let c=ensure(text);if(!retry)c.messages.push({role:'user',content:text});else c.messages=c.messages.slice(0,-1);let answer={role:'assistant',content:''};c.messages.push(answer);$('#prompt').value='';draw();history();controller=new AbortController();$('#send').textContent='Stop ■';$('#send').classList.add('stop');status('Generating…');try{if(!cfg.apiKey)throw Error('Add an API key and endpoint in Settings first.');let r=await fetch(cfg.endpoint||'https://api.openai.com/v1/chat/completions',{method:'POST',signal:controller.signal,headers:{'Content-Type':'application/json','Authorization':'Bearer '+cfg.apiKey},body:JSON.stringify({model:$('#model').value,messages:c.messages.filter(m=>m.content).map(m=>({role:m.role,content:m.content})),stream:true})});if(!r.ok)throw Error('Provider returned '+r.status+'. Check your key, model, or endpoint.');let reader=r.body?.getReader(),decoder=new TextDecoder(),buffer='';if(!reader)throw Error('Provider did not return a readable stream.');while(true){let {done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:true});let lines=buffer.split('\n');buffer=lines.pop();for(let line of lines){if(!line.startsWith('data:'))continue;let payload=line.slice(5).trim();if(payload==='[DONE]')break;try{let d=JSON.parse(payload),delta=d.choices?.[0]?.delta?.content||d.candidates?.[0]?.content?.parts?.[0]?.text||'';if(delta){answer.content+=delta;draw()}}catch{}}}status('');}catch(e){if(e.name==='AbortError'){status('Generation stopped')}else{answer.content='Could not generate a response. '+e.message;status('Provider error',true)}}finally{controller=null;$('#send').textContent='Send ↑';$('#send').classList.remove('stop');save();draw();history()}}
$('#form').onsubmit=e=>{e.preventDefault();if(controller)controller.abort();else send($('#prompt').value)};$('#prompt').onkeydown=e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();$('#form').requestSubmit()}};$('#new').onclick=newChat;$('#search').oninput=history;$('#open').onclick=()=>$('#sidebar').classList.add('open');$('#close').onclick=()=>$('#sidebar').classList.remove('open');function openSettings(){ $('#endpoint').value=cfg.endpoint;$('#key').value=cfg.apiKey;$('#theme').value=cfg.theme;$('#modal').classList.add('open')}$('#settings').onclick=$('#topSettings').onclick=openSettings;$('#closeSettings').onclick=$('#cancel').onclick=()=>$('#modal').classList.remove('open');$('#saveSettings').onclick=()=>{cfg={endpoint:$('#endpoint').value.trim(),apiKey:$('#key').value.trim(),theme:$('#theme').value};localStorage.setItem(CFG,JSON.stringify(cfg));applyTheme();$('#modal').classList.remove('open');toast('Settings saved locally')};$('#theme').onchange=applyTheme;function applyTheme(){let t=cfg.theme==='system'?(matchMedia('(prefers-color-scheme: light)').matches?'light':'dark'):cfg.theme;document.documentElement.dataset.theme=t}$('#rename').onclick=()=>{let c=active();if(!c)return;let n=prompt('Conversation name',c.title);if(n?.trim()){c.title=n.trim();save();draw();history()}};$('#export').onclick=()=>{let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([JSON.stringify(chats,null,2)],{type:'application/json'}));a.download='free-ai-rock-chats.json';a.click()};$('#clear').onclick=()=>{if(confirm('Delete all saved conversations?')){chats=[];id=null;save();draw();history();$('#modal').classList.remove('open')}};applyTheme();history();draw();