This commit is contained in:
Ionel Andrei Cataon
2026-02-18 18:14:38 +02:00
parent 9e302f9778
commit c88bc6ba28

View File

@@ -33,15 +33,15 @@
.top-ctrl { padding: 12px 20px; display: flex; justify-content: flex-end; gap: 10px; background: rgba(0,0,0,0.4); border-bottom: 1px solid rgba(255,255,255,0.1); } .top-ctrl { padding: 12px 20px; display: flex; justify-content: flex-end; gap: 10px; background: rgba(0,0,0,0.4); border-bottom: 1px solid rgba(255,255,255,0.1); }
.input-area { padding: 20px; background: var(--sidebar); border-top: 1px solid rgba(255,255,255,0.1); display: flex; gap: 12px; } .input-area { padding: 20px; background: var(--sidebar); border-top: 1px solid rgba(255,255,255,0.1); display: flex; gap: 12px; }
.btn-shop { background: #f59e0b; color: #000; font-weight: 800; border: none; border-radius: 10px; padding: 0 20px; transition: 0.2s; } .btn-shop { background: #f59e0b; color: #000; font-weight: 800; border: none; border-radius: 10px; padding: 0 20px; transition: 0.2s; }
.btn-shop:hover { background: #d97706; transform: translateY(-2px); } .btn-reset { background: rgba(239, 68, 68, 0.1); color: #fca5a5; border: 1px solid #ef4444; border-radius: 10px; padding: 12px; font-weight: 700; width: 100%; margin-top: 20px; }
.btn-reset { background: rgba(239, 68, 68, 0.1); color: #fca5a5; border: 1px solid #ef4444; border-radius: 10px; padding: 12px; font-weight: 700; width: 100%; margin-top: 20px; transition: 0.2s; }
.btn-reset:hover { background: rgba(239, 68, 68, 0.2); }
table { width: 100%; border-collapse: collapse; margin: 15px 0; background: rgba(0,0,0,0.3); border-radius: 10px; overflow: hidden; } table { width: 100%; border-collapse: collapse; margin: 15px 0; background: rgba(0,0,0,0.3); border-radius: 10px; overflow: hidden; }
th, td { border: 1px solid rgba(255,255,255,0.1); padding: 12px; text-align: left; } th, td { border: 1px solid rgba(255,255,255,0.1); padding: 12px; text-align: left; }
th { background: rgba(59, 130, 246, 0.2); color: var(--accent); font-weight: 800; text-transform: uppercase; font-size: 0.75rem; } th { background: rgba(59, 130, 246, 0.2); color: var(--accent); font-weight: 800; text-transform: uppercase; font-size: 0.75rem; }
.theme-light { --bg: #f8fafc; --sidebar: #ffffff; --card: #f1f5f9; --text: #0f172a; }
.theme-light .ai { color: #0f172a !important; }
</style> </style>
</head> </head>
<body class="theme-dark"> <body id="mainBody" class="theme-dark">
<div class="main-wrapper"> <div class="main-wrapper">
<div class="sidebar"> <div class="sidebar">
@@ -59,54 +59,38 @@
<option value="1.375">Lightly Active (1-2 days/week)</option> <option value="1.375">Lightly Active (1-2 days/week)</option>
<option value="1.55">Moderately Active (3-5 days/week)</option> <option value="1.55">Moderately Active (3-5 days/week)</option>
<option value="1.725">Very Active (6-7 days/week)</option> <option value="1.725">Very Active (6-7 days/week)</option>
<option value="1.9">Extra Active (Physical Job + Training)</option> <option value="1.9">Extra Active (Athlete)</option>
</select> </select>
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="d-flex justify-content-between"><label class="label-pro">Calorie Adjustment</label><span id="aggDisp" class="badge bg-primary">0 kcal</span></div> <div class="d-flex justify-content-between"><label class="label-pro">Adjustment</label><span id="aggDisp" class="badge bg-primary">0 kcal</span></div>
<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"> <div class="water-hub"><h2 class="fw-bold text-info mb-0" id="h2o">0.0L</h2><span class="label-pro mb-0">Daily Hydration</span></div>
<h1 class="fw-bold mb-0" id="kcal">0</h1> <div class="macro-hub"><canvas id="mChart" style="max-height: 140px;"></canvas>
<p class="text-uppercase fw-bold m-0 opacity-75 small">Daily Calorie Target</p>
</div>
<div class="water-hub">
<h2 class="fw-bold text-info mb-0" id="h2o">0.0L</h2>
<span class="label-pro mb-0"><i class="fas fa-droplet me-1"></i> Daily Hydration</span>
</div>
<div class="macro-hub">
<canvas id="mChart" style="max-height: 140px;"></canvas>
<div class="row mt-3 text-center fw-bold small"> <div class="row mt-3 text-center fw-bold small">
<div class="col-4 text-info">P<br><span id="pG">0g</span></div> <div class="col-4 text-info">P: <span id="pG">0g</span></div>
<div class="col-4 text-danger">C<br><span id="cG">0g</span></div> <div class="col-4 text-danger">C: <span id="cG">0g</span></div>
<div class="col-4 text-warning">F<br><span id="fG">0g</span></div> <div class="col-4 text-warning">F: <span id="fG">0g</span></div>
</div> </div>
</div> </div>
<button class="btn-reset" onclick="resetAll()">RESET DASHBOARD</button>
<button class="btn-reset" onclick="resetAll()"><i class="fas fa-undo me-2"></i> RESET DASHBOARD</button>
</div> </div>
<div class="chat-section"> <div class="chat-section">
<div class="top-ctrl"> <div class="top-ctrl">
<button id="btnMetric" class="btn btn-primary btn-sm active px-3" onclick="setUnits('metric')">METRIC</button> <button id="btnMetric" class="btn btn-primary btn-sm active px-3" onclick="setUnits('metric')">METRIC</button>
<button id="btnImperial" class="btn btn-outline-primary btn-sm px-3" onclick="setUnits('imperial')">IMP</button> <button id="btnImperial" class="btn btn-outline-primary btn-sm px-3" onclick="setUnits('imperial')">IMP</button>
<button class="btn btn-dark btn-sm rounded-circle" style="width:32px; height:32px;"><i class="fas fa-palette"></i></button> <button class="btn btn-dark btn-sm rounded-circle" onclick="toggleTheme()"><i class="fas fa-palette"></i></button>
</div> </div>
<div id="chat" class="chat-container"> <div id="chat" class="chat-container">
<div class="msg ai shadow"> <div class="msg ai shadow">System Ready. Biometric Audit Engaged.</div>
<h5 class="fw-bold text-primary">System Ready.</h5>
The nutritional database is online. I can now handle varied ingredients, specific protein requests, and precise caloric targets.
</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="Ask for a meal plan..." onkeydown="if(event.key === 'Enter') talk()"> <input type="text" id="uIn" class="form-control bg-dark text-white border-primary" placeholder="Ask for a meal plan..." onkeydown="if(event.key==='Enter') talk()">
<button class="btn btn-primary" onclick="talk()"><i class="fas fa-paper-plane"></i></button> <button class="btn btn-primary" onclick="talk()"><i class="fas fa-paper-plane"></i></button>
</div> </div>
<button class="btn-shop shadow-sm" onclick="talk('GENERATE_SHOPPING_LIST')"> <button class="btn-shop shadow-sm" onclick="talk('GENERATE_SHOPPING_LIST')">
@@ -120,12 +104,20 @@
let isMetric = true, mChart, conversationHistory = []; let isMetric = true, mChart, conversationHistory = [];
const API = "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"; const API = "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";
function toggleTheme() {
const b = document.getElementById('mainBody');
b.classList.toggle('theme-dark');
b.classList.toggle('theme-light');
}
function setUnits(mode) { function setUnits(mode) {
if((mode==='metric' && !isMetric) || (mode==='imperial' && isMetric)) { if((mode==='metric' && !isMetric) || (mode==='imperial' && isMetric)) {
isMetric = !isMetric; isMetric = !isMetric;
const w = document.getElementById('w'), t = document.getElementById('t'), h = document.getElementById('h'); ['w','t'].forEach(id => {
if(w.value) w.value = isMetric ? (w.value/2.205).toFixed(0) : (w.value*2.205).toFixed(0); let v = document.getElementById(id).value;
if(t.value) t.value = isMetric ? (t.value/2.205).toFixed(0) : (t.value*2.205).toFixed(0); if(v) document.getElementById(id).value = isMetric ? (v/2.205).toFixed(0) : (v*2.205).toFixed(0);
});
let h = document.getElementById('h');
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 ? "Current (kg)" : "Current (lb)"; document.getElementById('txtW').innerText = isMetric ? "Current (kg)" : "Current (lb)";
document.getElementById('txtT').innerText = isMetric ? "Target (kg)" : "Target (lb)"; document.getElementById('txtT').innerText = isMetric ? "Target (kg)" : "Target (lb)";
@@ -147,41 +139,33 @@
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";
const pG = Math.round((total * 0.3)/4), cG = Math.round((total * 0.4)/4), fG = Math.round((total * 0.3)/9); const pG = Math.round((total * 0.35)/4), cG = Math.round((total * 0.35)/4), fG = Math.round((total * 0.3)/9);
document.getElementById('pG').innerText = pG+"g"; document.getElementById('cG').innerText = cG+"g"; document.getElementById('fG').innerText = fG+"g"; document.getElementById('pG').innerText = pG+"g"; document.getElementById('cG').innerText = cG+"g"; document.getElementById('fG').innerText = fG+"g";
if(mChart) { mChart.data.datasets[0].data = [pG*4, cG*4, fG*9]; mChart.update(); } if(mChart) { mChart.data.datasets[0].data = [pG*4, cG*4, fG*9]; mChart.update(); }
} }
function resetAll() { function resetAll() { location.reload(); }
['w','t','h','age','agg'].forEach(id => document.getElementById(id).value = id === 'agg' ? 0 : "");
document.getElementById('act').selectedIndex = 0;
document.getElementById('kcal').innerText = "0";
document.getElementById('pG').innerText = "0g"; document.getElementById('cG').innerText = "0g"; document.getElementById('fG').innerText = "0g";
document.getElementById('chat').innerHTML = `<div class="msg ai shadow">System Reset.</div>`;
conversationHistory = [];
update();
}
async function talk(custom) { async function talk(custom) {
const box = document.getElementById('chat'), input = document.getElementById('uIn'), targetKcal = document.getElementById('kcal').innerText; const box = document.getElementById('chat'), input = document.getElementById('uIn'), targetKcal = document.getElementById('kcal').innerText;
const text = custom === 'GENERATE_SHOPPING_LIST' ? "Provide a full shopping list based on the plan above." : (custom || input.value); const text = custom === 'GENERATE_SHOPPING_LIST' ? "Provide a full shopping list for this 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">${text}</div>`; input.value=""; }
const id = 'ai-'+Date.now(); box.innerHTML += `<div id="${id}" class="msg ai shadow">Consulting database...</div>`; const id = 'ai-'+Date.now(); box.innerHTML += `<div id="${id}" class="msg ai shadow">Verifying math...</div>`;
box.scrollTop = box.scrollHeight; box.scrollTop = box.scrollHeight;
const sysPrompt = {role:"system", content: `Expert Dietitian. TARGET: ${targetKcal} kcal. const sysPrompt = {role:"system", content: `You are a Math-Strict Clinical Dietitian. TARGET: ${targetKcal} kcal.
1. MATH: 100g RAW Brown Rice = 360 kcal. 100g RAW Oats = 380 kcal. 1. MANDATORY CALORIC DENSITY: 100g RAW Brown Rice = 360 kcal. 100g RAW Oats = 380 kcal.
2. VERSATILITY: You have full freedom to use salmon, steak, eggs, avocado, olive oil, and diverse veggies. Do not repeat chicken/rice endlessly. 2. NO HALLUCINATIONS: If the current total is below ${targetKcal}, you MUST INCREASE gram weights of ingredients. You are forbidden from inventing 'diet' rice with lower calories.
3. CUSTOMER PREFERENCE: Prioritize high-protein or specific ingredients if requested. 3. VARIETY FREEDOM: Use Salmon, Beef, Steak, Avocado, Nuts, Eggs, and Yogurt. Do not repeat chicken/rice endlessly.
4. ACCURACY: Scale portion grams (e.g., 140g, 90g) to ensure the total is EXACTLY ${targetKcal} kcal. 4. SPECIFIC REQUESTS: If the user asks for 'High Protein' or specific ingredients, prioritize them while scaling grams to hit exactly ${targetKcal}.
Output: Markdown tables with Weight (RAW), Calories, P, C, F.`}; 5. VERIFICATION: Perform (P*4)+(C*4)+(F*9) for every meal. If it doesn't match the row total, correct it.`};
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], temperature: 0.15 }) 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;