CoolFace
Apppublic

Mosako/chef-guru

sourceHugging Facemitupdated 7mo agoView on Hugging Face
0likes
ui.html462 linesDownload Raw Back to root
1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8">
5<meta name="viewport" content="width=device-width, initial-scale=1.0">
6<title>AI Chef | Precision Cooking Engine</title>
7
8<style>
9:root {
10    --bg: #0f0f0f;
11    --card: #1b1b1b;
12    --glass: rgba(255,255,255,0.05);
13    --accent: #FF9800;
14    --accent-soft: rgba(255,152,0,0.15);
15    --text: #ffffff;
16    --muted: #aaaaaa;
17    --danger: #ff5252;
18    --success: #4CAF50;
19}
20
21* { box-sizing: border-box; }
22
23body {
24    margin: 0;
25    background: radial-gradient(circle at top, #1a1a1a 0%, #0f0f0f 60%);
26    font-family: 'Segoe UI', system-ui, sans-serif;
27    color: var(--text);
28    padding: 30px 15px;
29}
30
31.container {
32    max-width: 900px;
33    margin: auto;
34}
35
36header {
37    text-align: center;
38    margin-bottom: 40px;
39}
40
41h1 {
42    margin: 0;
43    font-size: 2.8rem;
44    letter-spacing: 2px;
45}
46
47.subtitle {
48    color: var(--accent);
49    font-weight: 600;
50    margin-top: 8px;
51}
52
53/* --- CARD STYLE --- */
54.card {
55    background: var(--card);
56    padding: 30px;
57    border-radius: 18px;
58    box-shadow: 0 15px 40px rgba(0,0,0,0.6);
59    backdrop-filter: blur(10px);
60}
61
62/* --- INPUT BUILDER --- */
63.input-row {
64    display: flex;
65    gap: 10px;
66    margin-bottom: 10px;
67    animation: fadeIn 0.3s ease;
68}
69
70@keyframes fadeIn {
71    from { opacity: 0; transform: translateY(-8px); }
72    to { opacity: 1; transform: translateY(0); }
73}
74
75input {
76    flex: 1;
77    padding: 14px;
78    border-radius: 10px;
79    border: 1px solid #333;
80    background: #262626;
81    color: white;
82    font-size: 15px;
83    transition: 0.2s;
84}
85
86input:focus {
87    border-color: var(--accent);
88    box-shadow: 0 0 0 2px var(--accent-soft);
89    outline: none;
90}
91
92/* --- BUTTONS --- */
93.btn {
94    padding: 14px 20px;
95    border-radius: 10px;
96    font-weight: 600;
97    cursor: pointer;
98    border: none;
99    transition: 0.2s ease;
100}
101
102.btn-add {
103    background: transparent;
104    border: 1px solid var(--accent);
105    color: var(--accent);
106}
107
108.btn-remove {
109    background: var(--danger);
110    color: white;
111    padding: 0 14px;
112}
113
114.btn-submit {
115    background: linear-gradient(135deg, #ff9800, #ffb74d);
116    color: black;
117    width: 100%;
118    margin-top: 15px;
119    font-size: 1.05rem;
120}
121
122.btn:hover { transform: translateY(-2px); opacity: 0.95; }
123.btn:disabled { opacity: 0.5; cursor: not-allowed; }
124
125/* --- LOADER --- */
126#loader {
127    display: none;
128    text-align: center;
129    margin: 40px 0;
130}
131
132.spinner {
133    width: 55px;
134    height: 55px;
135    border: 6px solid #333;
136    border-top: 6px solid var(--accent);
137    border-radius: 50%;
138    animation: spin 1s linear infinite;
139    margin: auto;
140}
141
142@keyframes spin {
143    to { transform: rotate(360deg); }
144}
145
146/* --- RECIPE CARD --- */
147#recipe-card {
148    display: none;
149    margin-top: 35px;
150}
151
152.recipe-title {
153    color: var(--accent);
154    font-size: 2rem;
155    margin-bottom: 10px;
156}
157
158.recipe-desc {
159    color: var(--muted);
160    line-height: 1.6;
161    margin-bottom: 25px;
162}
163
164.section-title {
165    margin-top: 30px;
166    margin-bottom: 15px;
167    font-size: 0.9rem;
168    text-transform: uppercase;
169    letter-spacing: 1px;
170    color: var(--accent);
171    border-bottom: 1px solid #333;
172    padding-bottom: 6px;
173}
174
175/* --- INGREDIENTS --- */
176.ing-item {
177    padding: 8px 0;
178    border-bottom: 1px solid #2a2a2a;
179}
180
181/* --- STEPS --- */
182.step-container {
183    background: #222;
184    padding: 16px;
185    border-radius: 12px;
186    margin-bottom: 12px;
187    cursor: pointer;
188    transition: 0.2s;
189    border-left: 4px solid var(--accent);
190}
191
192.step-container:hover {
193    background: #2b2b2b;
194}
195
196.step-header {
197    display: flex;
198    justify-content: space-between;
199    align-items: flex-start;
200}
201
202.step-text {
203    font-weight: 600;
204}
205
206.step-time {
207    background: #333;
208    padding: 5px 10px;
209    border-radius: 20px;
210    font-size: 0.8rem;
211    color: var(--accent);
212}
213
214.step-desc {
215    font-size: 0.9rem;
216    color: var(--muted);
217    margin-top: 8px;
218}
219
220/* --- EXPLANATION PANEL --- */
221.step-detail {
222    display: none;
223    margin-top: 15px;
224    background: #181818;
225    padding: 18px;
226    border-radius: 10px;
227    border: 1px solid #333;
228    font-size: 0.9rem;
229    line-height: 1.6;
230}
231
232.detail-section {
233    margin-bottom: 14px;
234}
235
236.detail-title {
237    color: var(--accent);
238    font-weight: 600;
239    margin-bottom: 4px;
240}
241
242.error-msg {
243    display: none;
244    background: rgba(255,82,82,0.1);
245    padding: 15px;
246    border-radius: 10px;
247    margin-top: 20px;
248    color: var(--danger);
249    text-align: center;
250}
251</style>
252</head>
253
254<body>
255
256<div class="container">
257
258<header>
259    <h1>๐Ÿ‘จโ€๐Ÿณ AI CHEF</h1>
260    <div class="subtitle">Precision Local LLM Cooking Engine</div>
261</header>
262
263<div class="card">
264    <div id="ingredients-list">
265        <div class="input-row">
266            <input type="text" class="ingredient-input" placeholder="Enter ingredient..." onkeypress="handleEnter(event)">
267        </div>
268    </div>
269
270    <div style="display:flex; gap:10px;">
271        <button class="btn btn-add" onclick="addIngredientField()" style="flex:1;">+ Add Ingredient</button>
272    </div>
273
274    <button class="btn btn-submit" onclick="submitRecipe()" id="submit-btn">Generate Recipe</button>
275</div>
276
277<div id="error-box" class="error-msg"></div>
278
279<div id="loader">
280    <div class="spinner"></div>
281    <p>Analyzing culinary compatibility...</p>
282</div>
283
284<div id="recipe-card" class="card">
285    <h2 class="recipe-title" id="r-title"></h2>
286    <p class="recipe-desc" id="r-desc"></p>
287
288    <div class="section-title">Ingredients</div>
289    <div id="r-ingredients"></div>
290
291    <div class="section-title">Instructions (Click for Deep Explanation)</div>
292    <div id="r-instructions"></div>
293</div>
294
295</div>
296
297<script>
298function addIngredientField() {
299    const container = document.getElementById('ingredients-list');
300    const div = document.createElement('div');
301    div.className = 'input-row';
302    div.innerHTML = `
303        <input type="text" class="ingredient-input" placeholder="Enter ingredient..." onkeypress="handleEnter(event)">
304        <button class="btn btn-remove" onclick="removeField(this)">ร—</button>
305    `;
306    container.appendChild(div);
307}
308
309function removeField(btn) { btn.parentElement.remove(); }
310
311function handleEnter(e) {
312    if (e.key === 'Enter') {
313        e.preventDefault();
314        addIngredientField();
315    }
316}
317
318async function submitRecipe() {
319    const inputs = document.querySelectorAll('.ingredient-input');
320    const submitBtn = document.getElementById('submit-btn');
321    const loader = document.getElementById('loader');
322    const card = document.getElementById('recipe-card');
323    const errorBox = document.getElementById('error-box');
324
325    errorBox.style.display = 'none';
326    card.style.display = 'none';
327
328    let ingredientsList = [];
329    inputs.forEach(i => { if (i.value.trim()) ingredientsList.push(i.value.trim()); });
330
331    if (!ingredientsList.length) {
332        showError("Add at least one ingredient.");
333        return;
334    }
335
336    submitBtn.disabled = true;
337    loader.style.display = 'block';
338
339    try {
340        const response = await fetch('/generate', {
341            method: 'POST',
342            headers: { 'Content-Type': 'application/json' },
343            body: JSON.stringify({ ingredients: ingredientsList })
344        });
345
346        const data = await response.json();
347        if (!response.ok) throw new Error(data.error);
348
349        document.getElementById('r-title').innerText = data.title;
350        document.getElementById('r-desc').innerText = data.description;
351
352        const ingDiv = document.getElementById('r-ingredients');
353        ingDiv.innerHTML = data.ingredients.map(i => `<div class="ing-item">โ€ข ${i}</div>`).join('');
354
355        const instDiv = document.getElementById('r-instructions');
356        instDiv.innerHTML = data.instructions.map((step, index) => `
357            <div class="step-container" onclick="toggleExplanation(this, '${index}')">
358                <div class="step-header">
359                    <div class="step-text">Step ${index+1}: ${step.step}</div>
360                    ${step.timing ? `<div class="step-time">${step.timing}</div>` : ''}
361                </div>
362                ${step.description ? `<div class="step-desc">${step.description}</div>` : ''}
363                <div id="explain-${index}" class="step-detail" data-loaded="false"></div>
364            </div>
365        `).join('');
366
367        window.currentRecipeDescription = data.description;
368        card.style.display = 'block';
369
370    } catch (e) {
371        showError(e.message);
372    } finally {
373        submitBtn.disabled = false;
374        loader.style.display = 'none';
375    }
376}
377
378async function toggleExplanation(element, index) {
379    const box = document.getElementById(`explain-${index}`);
380    const stepText = element.querySelector('.step-text').innerText.replace(`Step ${parseInt(index)+1}: `,'');
381
382    if (box.style.display === 'block') {
383        box.style.display = 'none';
384        return;
385    }
386
387    if (box.dataset.loaded === "true") {
388        box.style.display = 'block';
389        return;
390    }
391
392    box.style.display = 'block';
393    box.innerHTML = "Thinking...";
394
395    try {
396        const response = await fetch('/explain_step', {
397            method: 'POST',
398            headers: { 'Content-Type': 'application/json' },
399            body: JSON.stringify({
400                step: stepText,
401                context: window.currentRecipeDescription
402            })
403        });
404
405        const data = await response.json();
406        if (data.error) throw new Error(data.error);
407
408        const e = data.explanation || data;
409        console.log("data:", data);
410        console.log("e:", e);
411        
412        
413
414        box.innerHTML = `
415            ${renderSection("Purpose", e.purpose)}
416            ${renderSection("What Is Happening", e.what_is_happening)}
417            ${renderSection("Visual Indicators", e.visual_indicators)}
418            ${renderSection("Aroma & Sound", e.aroma_and_sound)}
419            ${renderSection("Texture Change", e.texture_transformation)}
420            ${renderSection("Heat Control", e.heat_control)}
421            ${renderList("Common Errors", e.common_errors)}
422            ${renderList("How To Fix", e.how_to_fix_errors)}
423            ${renderSection("Expert Tip", e.expert_tip)}
424            ${renderSection("Safety Note", e.safety_note)}
425        `;
426
427        box.dataset.loaded = "true";
428
429    } catch (err) {
430        box.innerHTML = `<span style="color:var(--danger)">Error: ${err.message}</span>`;
431    }
432}
433
434function renderSection(title, content) {
435    if (!content) return "";
436    return `
437        <div class="detail-section">
438            <div class="detail-title">${title}</div>
439            <div>${content}</div>
440        </div>
441    `;
442}
443
444function renderList(title, items) {
445    if (!items || !items.length) return "";
446    return `
447        <div class="detail-section">
448            <div class="detail-title">${title}</div>
449            <ul>${items.map(i => `<li>${i}</li>`).join('')}</ul>
450        </div>
451    `;
452}
453
454function showError(msg) {
455    const box = document.getElementById('error-box');
456    box.innerText = msg;
457    box.style.display = 'block';
458}
459</script>
460
461</body>
462</html>