Files
SousChefAI/dist/assets/index-CrgoFxjd.js

49 lines
10 KiB
JavaScript
Raw Normal View History

2026-04-29 11:50:44 -05:00
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const l of o.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&s(l)}).observe(document,{childList:!0,subtree:!0});function n(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function s(i){if(i.ep)return;i.ep=!0;const o=n(i);fetch(i.href,o)}})();let g="",y="";const B=document.getElementById("login-btn"),$=document.getElementById("login-overlay"),S=document.getElementById("app-content");B.addEventListener("click",async()=>{const e=document.getElementById("login-username").value,t=document.getElementById("login-password").value,n=document.getElementById("login-btn");n.disabled=!0,n.innerText="Verifying...";try{(await fetch("/api/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:t})})).ok?(g=e,y=t,$.style.display="none",S.style.display="block"):document.getElementById("login-error").style.display="block"}catch{document.getElementById("login-error").innerText="Network error. Please try again.",document.getElementById("login-error").style.display="block"}n.disabled=!1,n.innerText="Sign in"});const r=[],v=document.getElementById("ing-wrapper"),c=document.getElementById("ing-input"),P=document.getElementById("gen-btn");P.addEventListener("click",C);v.addEventListener("click",()=>c.focus());c.addEventListener("keydown",e=>{(e.key==="Enter"||e.key===",")&&c.value.trim()?(e.preventDefault(),b(c.value.trim().replace(/,$/,"")),c.value=""):e.key==="Backspace"&&!c.value&&r.length&&O(r.length-1)});function b(e){if(!e||r.includes(e.toLowerCase()))return;r.push(e.toLowerCase());const t=document.createElement("div");t.className="tag",t.innerHTML=e+'<span class="tag-x">×</span>',t.querySelector(".tag-x").addEventListener("click",n=>{n.stopPropagation(),M(e.toLowerCase())}),v.insertBefore(t,c)}function M(e){const t=r.indexOf(e);t>-1&&(r.splice(t,1),I())}function O(e){r.splice(e,1),I()}function I(){v.querySelectorAll(".tag").forEach(t=>t.remove());const e=[...r];r.length=0,e.forEach(t=>b(t))}document.querySelectorAll(".check-pill").forEach(e=>{e.addEventListener("click",()=>{const t=e.querySelector("input");t.checked=!t.checked,e.classList.toggle("checked",t.checked)})});async function C(){const e=document.getElementById("goal").value.trim();if(!e&&r.length===0){E("Please add some ingredients or describe what you want to make.");return}const t=[...document.querySelectorAll(".check-pill input:checked")].map(a=>a.value),n=document.getElementById("cuisine").value,s=document.getElementById("time").value,i=document.getElementById("skill").value,o=document.getElementById("servings").value,l=document.getElementById("notes").value.trim(),d=document.getElementById("gen-btn");d.disabled=!0,d.innerHTML='<span class="spinner"></span>Generating…',document.getElementById("error-area").innerHTML="",document.getElementById("recipe-area").innerHTML='<div class="status">Crafting your recipe with care…</div>';const L=[r.length?`Ingredients available: ${r.join(", ")}`:"",e?`Food goal: ${e}`:"",t.length?`Dietary restrictions: ${t.join(", ")}`:"",n?`Preferred cuisine: ${n}`:"",s?`Time constraint: ${s}`:"",i?`Skill level: ${i}`:"",`Servings: ${o}`,l?`Additional preferences: ${l}`:""].filter(Boolean).join(`
`),T=`You are an expert chef and culinary writer. Create a complete, precise recipe based on the user's inputs.
CRITICAL: Instructions must be specific never vague. Include exact temperatures, times, visual cues, sounds, smells, and textures. Example: instead of "cook the onions", write "cook the onions in the butter over medium heat, stirring occasionally, for 810 minutes until they turn translucent and just begin to turn golden at the edges."
Respond with ONLY a valid JSON object. Use this exact structure:
{"title":"string","description":"string","servings":2,"prepTime":"string","cookTime":"string","ingredients":[{"amount":"string","unit":"string","name":"string"}],"steps":[{"title":"string","instruction":"string"}],"notes":"string or null"}
Rules: use provided ingredients as the base, add pantry staples as needed, 510 steps, each instruction 13 richly detailed sentences, respect all dietary restrictions strictly.`;try{const a=await fetch("/api/generate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:g,password:y,systemPrompt:T,userPrompt:L})}),w=await a.json();if(!a.ok)throw new Error(w.error||`Server error ${a.status}`);const k=w.candidates[0].content.parts[0].text,x=JSON.parse(k);f(x)}catch(a){document.getElementById("recipe-area").innerHTML="",E("Error: "+(a.message||"Unknown error. Please try again."))}d.disabled=!1,d.innerHTML="Generate my recipe"}function E(e){document.getElementById("error-area").innerHTML=`<div class="error-msg">${e}</div>`}let u=null,m=null;const h=document.getElementById("saved-menu"),p=document.getElementById("saved-list");document.getElementById("open-menu-btn").addEventListener("click",async()=>{h.classList.add("open"),await R()});document.getElementById("close-menu-btn").addEventListener("click",()=>{h.classList.remove("open")});async function R(){p.innerHTML='<p style="font-size: 13px; color: var(--color-text-secondary); text-align: center;">Loading...</p>';try{const e=await fetch("/api/recipes",{headers:{"x-username":g,"x-password":y}});if(!e.ok)throw new Error("Failed to fetch");const t=await e.json();if(t.length===0){p.innerHTML='<p style="font-size: 13px; color: var(--color-text-secondary); text-align: center;">No saved recipes yet.</p>';return}p.innerHTML=t.reverse().map(n=>`
<div class="saved-recipe-item" onclick="loadSavedRecipe('${n.id}')">
<h3>${n.title}</h3>
<p>${n.description}</p>
</div>
`).join("")}catch{p.innerHTML='<p style="font-size: 13px; color: var(--color-text-danger); text-align: center;">Error loading recipes.</p>'}}window.loadSavedRecipe=async function(e){h.classList.remove("open");try{const t=await fetch(`/api/recipes/${e}`);if(!t.ok)throw new Error("Failed to fetch recipe");const n=await t.json();n.id=e,f(n),document.getElementById("recipe-area").scrollIntoView({behavior:"smooth"})}catch(t){console.error(t)}};window.saveCurrentRecipe=async function(){if(!u)return;const e=document.getElementById("save-recipe-btn"),t=e.innerText;e.innerText="Saving...",e.disabled=!0;try{const n=await fetch("/api/recipes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:g,password:y,recipe:u})});if(n.ok){const s=await n.json();m=s.id,u.id=s.id,e.innerText="Saved!"}else throw new Error("Save failed")}catch{e.innerText="Error Saving",setTimeout(()=>{e.innerText=t,e.disabled=!1},2e3)}};window.shareRecipeLink=function(){if(!m){alert("Please save the recipe first to share it!");return}const e=`${window.location.origin}${window.location.pathname}#shared=${m}`;navigator.clipboard.writeText(e).then(()=>{const t=document.getElementById("share-recipe-btn"),n=t.innerText;t.innerText="Link Copied!",setTimeout(()=>t.innerText=n,2e3)})};function f(e,t=!1){u=e,m=e.id||null;const n=(e.ingredients||[]).map(i=>`<li><span class="ing-amount">${i.amount}${i.unit?" "+i.unit:""}</span><span class="ing-name">${i.name}</span></li>`).join(""),s=(e.steps||[]).map((i,o)=>`<li class="step-item">
<span class="step-num">${o+1}</span>
<div>
<div class="step-title">${i.title}</div>
<div class="step-instr">${i.instruction}</div>
</div>
</li>`).join("");document.getElementById("recipe-area").innerHTML=`
<div class="recipe-out">
<div class="recipe-title">${e.title}</div>
<div class="recipe-desc">${e.description}</div>
<div class="meta-row">
<div class="meta-chip"><strong>${e.servings}</strong> servings</div>
<div class="meta-chip">Prep <strong>${e.prepTime}</strong></div>
<div class="meta-chip">Cook <strong>${e.cookTime}</strong></div>
</div>
<div class="section-label">Ingredients</div>
<ul class="ingredients-list">${n}</ul>
<div class="section-label">Method</div>
<ol class="steps-list">${s}</ol>
${e.notes?`<div class="notes-box">${e.notes}</div>`:""}
<div class="recipe-actions">
${t?`
<button class="action-btn" style="background: var(--color-text-primary); color: white;" onclick="window.location.href=window.location.pathname">Return to Login</button>
`:`
<button class="action-btn" id="save-recipe-btn" onclick="saveCurrentRecipe()">Save to Menu</button>
<button class="action-btn" id="share-recipe-btn" onclick="shareRecipeLink()">Copy Share Link</button>
`}
</div>
</div>`}window.addEventListener("DOMContentLoaded",async()=>{if(window.location.hash.startsWith("#shared="))try{const e=window.location.hash.slice(8);document.getElementById("login-overlay").innerText="Loading Shared Recipe...",document.getElementById("login-overlay").style.color="#1a1a1a";const t=await fetch(`/api/recipes/${e}`);if(!t.ok)throw new Error("Shared recipe not found");const n=await t.json();n.id=e,document.getElementById("login-overlay").style.display="none",document.querySelectorAll("#open-menu-btn, .header, .form-grid, #gen-btn, #error-area").forEach(s=>{s&&(s.style.display="none")}),document.getElementById("app-content").style.display="block",f(n,!0)}catch(e){console.error("Failed to load shared recipe",e),document.getElementById("login-overlay").innerHTML=`
<div class="login-card" style="text-align:center;">
<h2 style="font-family: 'Lora', serif; font-size: 20px;">Recipe Not Found</h2>
<p style="font-size: 14px; margin-top: 10px;">The shared recipe link appears to be invalid or has been deleted.</p>
<button class="generate-btn" style="margin-top: 16px;" onclick="window.location.href=window.location.pathname">Return to Login</button>
</div>
`}});