Spaces:
Running
Running
| let characters=[]; | |
| // Tab switching | |
| document.querySelectorAll(".tab-btn").forEach(btn=>{ | |
| btn.addEventListener("click",()=>{ | |
| document.querySelectorAll(".tab-btn").forEach(b=>b.classList.remove("active")); | |
| document.querySelectorAll(".tab-content").forEach(c=>c.classList.add("hidden")); | |
| btn.classList.add("active"); | |
| document.getElementById(btn.dataset.tab).classList.remove("hidden"); | |
| }); | |
| }); | |
| // Add character | |
| document.getElementById("add-character").addEventListener("click",()=>{ | |
| const container=document.createElement("div"); | |
| container.className="border p-4 rounded"; | |
| container.innerHTML=` | |
| <input class="char-name w-full p-2 border rounded mb-1" placeholder="Name"> | |
| <textarea class="char-desc w-full p-2 border rounded mb-1" placeholder="Character description (appearance, outfit, build, etc.)"></textarea> | |
| <input type="file" class="char-img mb-1" accept="image/png, image/jpeg"> | |
| `; | |
| document.getElementById("character-list").appendChild(container); | |
| characters.push({name:"",desc:"",img:null}); | |
| }); | |
| // Convert file to Base64 | |
| function imageFileToBase64(file){ | |
| return new Promise((resolve,reject)=>{ | |
| const reader=new FileReader(); | |
| reader.onload=()=>resolve(reader.result); | |
| reader.onerror=err=>reject(err); | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| // Make speech bubbles draggable | |
| function makeDraggable(el){ | |
| let offsetX,offsetY; | |
| el.onmousedown=e=>{ | |
| offsetX=e.clientX-el.offsetLeft; offsetY=e.clientY-el.offsetTop; | |
| document.onmousemove=e=>{el.style.left=(e.clientX-offsetX)+"px"; el.style.top=(e.clientY-offsetY)+"px";} | |
| document.onmouseup=()=>document.onmousemove=null; | |
| }; | |
| } | |
| // Comic sound effects | |
| const comicEffects=["BAM!","KAPOW!","KA-BOOM!","WHAM!","ZAP!","BOOM!"]; | |
| // Generate panels | |
| document.getElementById("generate-btn").addEventListener("click", async()=>{ | |
| const story=document.getElementById("story-input").value.trim(); | |
| const panels=parseInt(document.getElementById("panel-count").value); | |
| const apiKey=document.getElementById("api-key").value.trim(); | |
| const artStyle=document.getElementById("art-style").value; | |
| const explicit=document.getElementById("explicit-toggle").checked; | |
| const model=document.getElementById("custom-model").value.trim() || "stabilityai/stable-diffusion-2"; | |
| const bgDesc=document.getElementById("bg-desc").value.trim(); | |
| const preview=document.getElementById("comic-preview"); | |
| preview.innerHTML=""; | |
| // Collect characters | |
| const charPrompts=[]; | |
| const charImages=[]; | |
| const charDivs=document.querySelectorAll("#character-list .border"); | |
| for(const div of charDivs){ | |
| const name=div.querySelector(".char-name").value; | |
| const desc=div.querySelector(".char-desc").value; | |
| const file=div.querySelector(".char-img").files[0]; | |
| if(name && desc) charPrompts.push(`${name}: ${desc}`); | |
| if(file) charImages.push(await imageFileToBase64(file)); | |
| } | |
| for(let i=0;i<panels;i++){ | |
| const panelDiv=document.createElement("div"); | |
| panelDiv.className="comic-panel"; | |
| panelDiv.innerText="⏳ Generating..."; | |
| preview.appendChild(panelDiv); | |
| try{ | |
| const prompt=`Panel ${i+1} of story: ${story}. Style: ${artStyle}${explicit ? ", mature, graphic" : ""}. Background: ${bgDesc}. Characters: ${charPrompts.join(", ")}`; | |
| // Generate image with ControlNet reference | |
| const imgResp=await fetch(`https://api-inference.huggingface.co/models/${model}`,{ | |
| method:"POST", | |
| headers:{Authorization:`Bearer ${apiKey}`, "Content-Type":"application/json"}, | |
| body: JSON.stringify({ | |
| inputs: prompt, | |
| parameters: { | |
| guidance_scale: 7.5, | |
| num_inference_steps: 30, | |
| height:512, | |
| width:512, | |
| controlnet_reference_images: charImages | |
| } | |
| }) | |
| }); | |
| const imgBlob=await imgResp.blob(); | |
| const imgUrl=URL.createObjectURL(imgBlob); | |
| panelDiv.innerHTML=`<img src="${imgUrl}" class="comic-img">`; | |
| // Generate short dialogue for speech bubble | |
| const textPrompt=`Generate short comic dialogue for panel ${i+1} with characters: ${charPrompts.join(", ")} including sound effects like BAM, POW, KAPOW.`; | |
| const textResp=await fetch(`https://api-inference.huggingface.co/models/gpt2`,{ | |
| method:"POST", | |
| headers:{Authorization:`Bearer ${apiKey}`, "Content-Type":"application/json"}, | |
| body:JSON.stringify({inputs:textPrompt,max_new_tokens:40}) | |
| }).then(r=>r.json()); | |
| // Add draggable speech bubble | |
| const bubble=document.createElement("div"); | |
| bubble.className="speech-bubble"; | |
| bubble.contentEditable=true; | |
| bubble.innerText=textResp[0]?.generated_text?.split(".")[0] || comicEffects[Math.floor(Math.random()*comicEffects.length)]; | |
| makeDraggable(bubble); | |
| panelDiv.appendChild(bubble); | |
| // Add editable caption | |
| const caption=document.createElement("div"); | |
| caption.className="caption"; | |
| caption.contentEditable=true; | |
| caption.innerText=textResp[0]?.generated_text || ""; | |
| panelDiv.appendChild(caption); | |
| }catch(err){ | |
| console.error(err); | |
| panelDiv.innerText="❌ Error generating panel"; | |
| } | |
| } | |
| }); | |
| // Export PDF | |
| document.getElementById("download-btn").addEventListener("click",()=>{ | |
| const {jsPDF}=window.jspdf; | |
| const pdf=new jsPDF(); | |
| const panels=document.querySelectorAll(".comic-panel img"); | |
| panels.forEach((img,i)=>{ | |
| if(i>0) pdf.addPage(); | |
| pdf.addImage(img.src,"JPEG",10,10,180,160); | |
| }); | |
| pdf.save("comic-book.pdf"); | |
| }); | |