This commit is contained in:
Ionel Andrei Cataon
2026-02-18 17:44:59 +02:00
parent 03281d71e4
commit 1607cb2ffc

View File

@@ -11,14 +11,11 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style> <style>
:root { --primary: #3b82f6; --bg: #020617; --sidebar: #0f172a; --card: #1e293b; --accent: #60a5fa; --text: #ffffff; } :root { --primary: #3b82f6; --bg: #020617; --sidebar: #0f172a; --card: #1e293b; --accent: #60a5fa; --text: #ffffff; }
body.theme-dark { --bg: #020617; --sidebar: #0f172a; --primary: #3b82f6; } body.theme-dark { --bg: #020617; --sidebar: #0f172a; --primary: #3b82f6; }
body.theme-emerald { --bg: #064e3b; --sidebar: #065f46; --primary: #10b981; } body.theme-emerald { --bg: #064e3b; --sidebar: #065f46; --primary: #10b981; }
body.theme-midnight { --bg: #1e1b4b; --sidebar: #312e81; --primary: #818cf8; } body.theme-midnight { --bg: #1e1b4b; --sidebar: #312e81; --primary: #818cf8; }
body.theme-titanium { --bg: #111827; --sidebar: #1f2937; --primary: #94a3b8; --card: #374151; --accent: #f8fafc; } body.theme-titanium { --bg: #111827; --sidebar: #1f2937; --primary: #94a3b8; --card: #374151; --accent: #f8fafc; }
body { background-color: var(--bg); color: var(--text); font-family: 'Outfit', sans-serif; height: 100dvh; overflow: hidden; transition: 0.3s; } body { background-color: var(--bg); color: var(--text); font-family: 'Outfit', sans-serif; height: 100dvh; overflow: hidden; transition: 0.3s; }
.main-wrapper { display: flex; height: 100dvh; flex-direction: row; } .main-wrapper { display: flex; height: 100dvh; flex-direction: row; }
@media (max-width: 991px) { @media (max-width: 991px) {
.main-wrapper { flex-direction: column; overflow-y: auto; height: auto; } .main-wrapper { flex-direction: column; overflow-y: auto; height: auto; }
@@ -26,28 +23,21 @@
.chat-section { height: 600px !important; } .chat-section { height: 600px !important; }
body { overflow-y: auto; } body { overflow-y: auto; }
} }
.sidebar { width: 360px; background: var(--sidebar); border-right: 1px solid rgba(255,255,255,0.1); padding: 1.5rem; overflow-y: auto; flex-shrink: 0; } .sidebar { width: 360px; background: var(--sidebar); border-right: 1px solid rgba(255,255,255,0.1); padding: 1.5rem; overflow-y: auto; flex-shrink: 0; }
.chat-section { flex-grow: 1; display: flex; flex-direction: column; background: rgba(0,0,0,0.2); position: relative; } .chat-section { flex-grow: 1; display: flex; flex-direction: column; background: rgba(0,0,0,0.2); position: relative; }
.label-pro { color: var(--accent); font-weight: 800; font-size: 0.75rem; text-transform: uppercase; margin-bottom: 5px; display: block; } .label-pro { color: var(--accent); font-weight: 800; font-size: 0.75rem; text-transform: uppercase; margin-bottom: 5px; display: block; }
.glass-input { background: #fff !important; color: #000 !important; border: 3px solid var(--primary) !important; border-radius: 12px; font-weight: 800 !important; } .glass-input { background: #fff !important; color: #000 !important; border: 3px solid var(--primary) !important; border-radius: 12px; font-weight: 800 !important; }
.stat-panel { background: linear-gradient(145deg, var(--primary), #1e3a8a); border-radius: 20px; padding: 1rem; margin: 10px 0; text-align: center; } .stat-panel { background: linear-gradient(145deg, var(--primary), #1e3a8a); border-radius: 20px; padding: 1rem; margin: 10px 0; text-align: center; }
.water-hub { background: rgba(59, 130, 246, 0.1); border: 2px solid var(--primary); border-radius: 15px; padding: 12px; margin: 10px 0; text-align: center; } .water-hub { background: rgba(59, 130, 246, 0.1); border: 2px solid var(--primary); border-radius: 15px; padding: 12px; margin: 10px 0; text-align: center; }
.macro-hub { background: rgba(255,255,255,0.05); border-radius: 20px; padding: 15px; margin-top: 10px; } .macro-hub { background: rgba(255,255,255,0.05); border-radius: 20px; padding: 15px; margin-top: 10px; }
.chat-container { flex-grow: 1; overflow-y: auto; padding: 1.5rem; display: flex; flex-direction: column; } .chat-container { flex-grow: 1; overflow-y: auto; padding: 1.5rem; display: flex; flex-direction: column; }
.msg { padding: 1.2rem; border-radius: 1.2rem; margin-bottom: 1rem; font-size: 1rem; max-width: 95%; color: #fff !important; word-wrap: break-word; } .msg { padding: 1.2rem; border-radius: 1.2rem; margin-bottom: 1rem; font-size: 1rem; max-width: 95%; color: #fff !important; word-wrap: break-word; }
.ai { background: var(--card); border-left: 6px solid var(--primary); align-self: flex-start; } .ai { background: var(--card); border-left: 6px solid var(--primary); align-self: flex-start; }
.user { background: var(--primary); align-self: flex-end; } .user { background: var(--primary); align-self: flex-end; }
.top-ctrl { padding: 10px; display: flex; justify-content: flex-end; gap: 8px; background: rgba(0,0,0,0.3); } .top-ctrl { padding: 10px; display: flex; justify-content: flex-end; gap: 8px; background: rgba(0,0,0,0.3); }
.input-area { padding: 15px; background: var(--sidebar); border-top: 1px solid rgba(255,255,255,0.1); display: flex; gap: 10px; } .input-area { padding: 15px; background: var(--sidebar); border-top: 1px solid rgba(255,255,255,0.1); display: flex; gap: 10px; }
.btn-shop { background: #f59e0b; color: #000; font-weight: 800; border: none; border-radius: 12px; padding: 0 15px; white-space: nowrap; } .btn-shop { background: #f59e0b; color: #000; font-weight: 800; border: none; border-radius: 12px; padding: 0 15px; white-space: nowrap; }
.btn-reset { background: rgba(239, 68, 68, 0.15); color: #fca5a5; border: 1px solid #ef4444; border-radius: 12px; padding: 8px; font-weight: 700; width: 100%; margin-top: 15px; } .btn-reset { background: rgba(239, 68, 68, 0.15); color: #fca5a5; border: 1px solid #ef4444; border-radius: 12px; padding: 8px; font-weight: 700; width: 100%; margin-top: 15px; }
table { width: 100%; display: block; overflow-x: auto; color: #fff; border-collapse: collapse; margin: 10px 0; background: rgba(0,0,0,0.2); border-radius: 8px; } table { width: 100%; display: block; overflow-x: auto; color: #fff; border-collapse: collapse; margin: 10px 0; background: rgba(0,0,0,0.2); border-radius: 8px; }
th, td { border: 1px solid rgba(255,255,255,0.1); padding: 8px; text-align: left; min-width: 80px; } th, td { border: 1px solid rgba(255,255,255,0.1); padding: 8px; text-align: left; min-width: 80px; }
th { background: rgba(255,255,255,0.05); font-weight: 800; color: var(--accent); } th { background: rgba(255,255,255,0.05); font-weight: 800; color: var(--accent); }
@@ -79,14 +69,11 @@
<input type="range" id="agg" class="form-range" min="-1000" max="1000" step="50" value="0" oninput="update()"> <input type="range" id="agg" class="form-range" min="-1000" max="1000" step="50" value="0" oninput="update()">
</div> </div>
</div> </div>
<div class="stat-panel"><h1 class="fw-bold mb-0" id="kcal">0</h1><p class="text-uppercase fw-bold m-0 opacity-75 small">Target Calories</p></div> <div class="stat-panel"><h1 class="fw-bold mb-0" id="kcal">0</h1><p class="text-uppercase fw-bold m-0 opacity-75 small">Target Calories</p></div>
<div class="water-hub"> <div class="water-hub">
<h2 class="fw-bold text-info mb-0" id="h2o">0.0L</h2> <h2 class="fw-bold text-info mb-0" id="h2o">0.0L</h2>
<span class="label-pro" id="txtH2O"><i class="fas fa-droplet me-1"></i> Daily Hydration</span> <span class="label-pro" id="txtH2O"><i class="fas fa-droplet me-1"></i> Daily Hydration</span>
</div> </div>
<div class="macro-hub"> <div class="macro-hub">
<canvas id="mChart" style="max-height: 120px;"></canvas> <canvas id="mChart" style="max-height: 120px;"></canvas>
<div class="row mt-2 text-center fw-bold small"> <div class="row mt-2 text-center fw-bold small">
@@ -112,14 +99,12 @@
</ul> </ul>
</div> </div>
</div> </div>
<div id="chat" class="chat-container"> <div id="chat" class="chat-container">
<div class="msg ai shadow"> <div class="msg ai shadow">
<h5 class="fw-bold text-primary">System Online.</h5> <h5 class="fw-bold text-primary">System Online.</h5>
Meal plans are structured by day and meal, featuring raw ingredient macros and comprehensive totals. Mathematical verification is now active. All meal plans are audited for calorie accuracy.
</div> </div>
</div> </div>
<div class="input-area"> <div class="input-area">
<div class="input-group input-group-lg flex-grow-1"> <div class="input-group input-group-lg flex-grow-1">
<input type="text" id="uIn" class="form-control bg-dark text-white border-primary" placeholder="Type and press ENTER..." onkeydown="if(event.key === 'Enter') talk()"> <input type="text" id="uIn" class="form-control bg-dark text-white border-primary" placeholder="Type and press ENTER..." onkeydown="if(event.key === 'Enter') talk()">
@@ -146,7 +131,6 @@
if(w.value) w.value = isMetric ? (w.value/2.205).toFixed(0) : (w.value*2.205).toFixed(0); if(w.value) w.value = isMetric ? (w.value/2.205).toFixed(0) : (w.value*2.205).toFixed(0);
if(t.value) t.value = isMetric ? (t.value/2.205).toFixed(0) : (t.value*2.205).toFixed(0); if(t.value) t.value = isMetric ? (t.value/2.205).toFixed(0) : (t.value*2.205).toFixed(0);
if(h.value) h.value = isMetric ? (h.value*2.54).toFixed(0) : (h.value/2.54).toFixed(0); if(h.value) h.value = isMetric ? (h.value*2.54).toFixed(0) : (h.value/2.54).toFixed(0);
document.getElementById('txtW').innerText = isMetric ? "Weight (kg)" : "Weight (lb)"; document.getElementById('txtW').innerText = isMetric ? "Weight (kg)" : "Weight (lb)";
document.getElementById('txtT').innerText = isMetric ? "Target (kg)" : "Target (lb)"; document.getElementById('txtT').innerText = isMetric ? "Target (kg)" : "Target (lb)";
document.getElementById('txtH').innerText = isMetric ? "Height (cm)" : "Height (in)"; document.getElementById('txtH').innerText = isMetric ? "Height (cm)" : "Height (in)";
@@ -161,11 +145,9 @@
const av = parseFloat(document.getElementById('age').value)||0, act = parseFloat(document.getElementById('act').value)||1; const av = parseFloat(document.getElementById('age').value)||0, act = parseFloat(document.getElementById('act').value)||1;
const agg = parseInt(document.getElementById('agg').value); const agg = parseInt(document.getElementById('agg').value);
if(!wv || !hv || !av) return; if(!wv || !hv || !av) return;
const wc = isMetric ? wv : wv/2.205, hc = isMetric ? hv : hv*2.54; const wc = isMetric ? wv : wv/2.205, hc = isMetric ? hv : hv*2.54;
const bmr = (10 * wc) + (6.25 * hc) - (5 * av) + 5; const bmr = (10 * wc) + (6.25 * hc) - (5 * av) + 5;
const total = Math.round((bmr * act) + agg); const total = Math.round((bmr * act) + agg);
document.getElementById('kcal').innerText = total; document.getElementById('kcal').innerText = total;
document.getElementById('h2o').innerText = isMetric ? (wc * 0.035).toFixed(1) + "L" : (wv * 0.6).toFixed(0) + "oz"; document.getElementById('h2o').innerText = isMetric ? (wc * 0.035).toFixed(1) + "L" : (wv * 0.6).toFixed(0) + "oz";
document.getElementById('aggDisp').innerText = (agg>0?'+':'')+agg+" kcal"; document.getElementById('aggDisp').innerText = (agg>0?'+':'')+agg+" kcal";
@@ -186,35 +168,31 @@
async function talk(custom) { async function talk(custom) {
const box = document.getElementById('chat'), input = document.getElementById('uIn'); const box = document.getElementById('chat'), input = document.getElementById('uIn');
const isShopReq = custom === 'GENERATE_SHOPPING_LIST'; const text = custom === 'GENERATE_SHOPPING_LIST' ? "GENERATE_SHOPPING_LIST: Provide a shopping list based on the active meal plan." : (custom || input.value);
const text = isShopReq ? "GENERATE_SHOPPING_LIST: Provide a shopping list based on the active meal plan." : (custom || input.value);
if(!text) return; if(!text) return;
if(!custom) { box.innerHTML += `<div class="msg user shadow-sm">${text}</div>`; input.value=""; } if(!custom) { box.innerHTML += `<div class="msg user shadow-sm">${text}</div>`; input.value=""; }
const id = 'ai-'+Date.now(); box.innerHTML += `<div id="${id}" class="msg ai shadow">Analyzing nutrition data...</div>`; const id = 'ai-'+Date.now(); box.innerHTML += `<div id="${id}" class="msg ai shadow">Auditing calories...</div>`;
box.scrollTop = box.scrollHeight; box.scrollTop = box.scrollHeight;
const sysPrompt = {role:"system", content: `Expert Dietitian. TARGET: ${document.getElementById('kcal').innerText} kcal. SYSTEM: ${isMetric ? 'METRIC' : 'IMPERIAL'}. const sysPrompt = {role:"system", content: `Expert Dietitian. TARGET: ${document.getElementById('kcal').innerText} kcal. SYSTEM: ${isMetric ? 'METRIC' : 'IMPERIAL'}.
CRITICAL MATHEMATICAL AUDIT: MATH VERIFICATION RULE:
1. You MUST ensure that (Meal 1 + Meal 2 + Meal 3 + Snacks) equals EXACTLY ${document.getElementById('kcal').innerText} kcal. 1. You are forbidden from hallucinating totals.
2. If the total is too low, you MUST increase the "Weight (RAW)" of primary ingredients (oats, rice, meat, etc.) until the sum is correct. 2. Before writing the 'Daily Total', you MUST manually sum the calories of every ingredient you listed.
3. DO NOT fake the total. Every row in your table must mathematically add up to the 'Meal Total', and all 'Meal Totals' must add up to the 'Daily Total'. 3. If the sum is less than ${document.getElementById('kcal').innerText}, you MUST increase the Weight (RAW) of starch/protein sources (e.g., 100g -> 250g) until the math is perfect.
4. Meal Total MUST = Sum of ingredient calories.
5. Daily Total MUST = Sum of all Meal Totals.
STRUCTURE: OUTPUT FORMAT:
- Use ### [Day] and #### [Meal Name]. - Table: | Ingredient | Weight (RAW) | Calories | P(g) | C(g) | F(g) |
- Table columns: | Ingredient | Weight (RAW) | Calories | P(g) | C(g) | F(g) |. - After each table: "Meal Total: [Kcal] | [P] | [C] | [F]"
- Provide: "Meal Total: [Kcal] | [P] | [C] | [F]" after every table. - Final line: **Daily Total: [Sum] kcal**.`};
- Final line per day: Bold **Daily Total: [Sum of all meals]**.
MANDATORY: All calculations must be based on RAW/DRY weights.`};
conversationHistory.push({role: "user", content: text}); conversationHistory.push({role: "user", content: text});
try { try {
const res = await fetch("https://api.groq.com/openai/v1/chat/completions", { const res = await fetch("https://api.groq.com/openai/v1/chat/completions", {
method: "POST", headers: {"Content-Type":"application/json", "Authorization":`Bearer ${API}`}, method: "POST", headers: {"Content-Type":"application/json", "Authorization":`Bearer ${API}`},
body: JSON.stringify({ model:"llama-3.3-70b-versatile", messages:[sysPrompt, ...conversationHistory]}) body: JSON.stringify({ model:"llama-3.3-70b-versatile", messages:[sysPrompt, ...conversationHistory], temperature: 0.1})
}); });
const d = await res.json(); const d = await res.json();
const aiMsg = d.choices[0].message.content; const aiMsg = d.choices[0].message.content;