FredinVázquez commited on
Commit
d5a2be2
·
1 Parent(s): c8a3c7e

add corrections

Browse files
app.py CHANGED
@@ -157,9 +157,10 @@ def main() -> None:
157
  # log.info("Starting Cook With Me (mock=%s, gpu=%s)", config.is_mock(), config.is_gpu_enabled())
158
  demo = build_ui()
159
  demo.launch(
160
- server_name="0.0.0.0",
161
  server_port=int(__import__("os").environ.get("PORT", 7860)),
162
  show_error=True,
 
163
  )
164
 
165
 
 
157
  # log.info("Starting Cook With Me (mock=%s, gpu=%s)", config.is_mock(), config.is_gpu_enabled())
158
  demo = build_ui()
159
  demo.launch(
160
+ server_name="localhost",
161
  server_port=int(__import__("os").environ.get("PORT", 7860)),
162
  show_error=True,
163
+ inbrowser=True,
164
  )
165
 
166
 
requirements.txt CHANGED
@@ -1,6 +1,6 @@
1
- gradio>=4.44
2
- huggingface_hub>=0.24
3
- llama-cpp-python>=0.3.2
4
  # numpy
5
  # pandas
6
  # Pillow
 
1
+ # gradio>=4.44
2
+ # huggingface_hub>=0.24
3
+ # llama-cpp-python>=0.3.2
4
  # numpy
5
  # pandas
6
  # Pillow
src/agents/mise_en_place.py CHANGED
@@ -27,8 +27,7 @@ def _img_to_data_url(img) -> str:
27
 
28
 
29
  _MOCK_INGREDIENTS = [
30
- "chicken", "onion", "tomato", "garlic", "olive oil",
31
- "rice", "bell pepper", "cilantro", "lime", "cumin",
32
  ]
33
 
34
 
@@ -57,6 +56,7 @@ def identify_ingredients(image: Optional[object]) -> list[str]:
57
  temperature=0.2,
58
  response_format={"type": "json_object"},
59
  )
 
60
  raw = out["choices"][0]["message"]["content"]
61
  data = json.loads(raw)
62
  ingredients = [str(x).lower().strip() for x in data.get("ingredients", [])]
 
27
 
28
 
29
  _MOCK_INGREDIENTS = [
30
+ "nothing",
 
31
  ]
32
 
33
 
 
56
  temperature=0.2,
57
  response_format={"type": "json_object"},
58
  )
59
+ print(out)
60
  raw = out["choices"][0]["message"]["content"]
61
  data = json.loads(raw)
62
  ingredients = [str(x).lower().strip() for x in data.get("ingredients", [])]
src/config.py CHANGED
@@ -17,9 +17,9 @@ for _d in (DATA_DIR, ASSETS_DIR, CACHE_DIR, FLUX_CACHE, AUDIO_CACHE):
17
 
18
 
19
  # --- Model identifiers ------------------------------------------------------
20
- VISION_REPO = "openbmb/MiniCPM-V-4_6-gguf"
21
- VISION_MODEL_FILE = "MiniCPM-V-4.6-Q4_K_M.gguf"
22
- VISION_MMPROJ_FILE = "mmproj-MiniCPM-V-4.6-F16.gguf"
23
 
24
  PLANNER_REPO = "openbmb/MiniCPM-V-4-gguf"
25
  PLANNER_MODEL_FILE = "Model-Q4_K_M.gguf"
 
17
 
18
 
19
  # --- Model identifiers ------------------------------------------------------
20
+ VISION_REPO = "openbmb/MiniCPM-V-4_6-GGUF"
21
+ VISION_MODEL_FILE = "MiniCPM-V-4_6-Q4_K_M.gguf"
22
+ VISION_MMPROJ_FILE = "mmproj-model-f16.gguf"
23
 
24
  PLANNER_REPO = "openbmb/MiniCPM-V-4-gguf"
25
  PLANNER_MODEL_FILE = "Model-Q4_K_M.gguf"
src/prompts/vision_prompt.txt CHANGED
@@ -1,10 +1 @@
1
- You are an ingredient detector. Look at the fridge or pantry photo and list every edible ingredient you can identify.
2
-
3
- Return strict JSON in this exact shape:
4
- {"ingredients": ["chicken", "onion", "tomato"]}
5
-
6
- Rules:
7
- - Lowercase English words only.
8
- - No brand names, no container descriptions ("bottle of", "carton of").
9
- - Skip non-food items.
10
- - Be specific where helpful ("ground beef" not just "meat") but don't invent.
 
1
+ Describe the given image in detail, and identify any kind of food detected.
 
 
 
 
 
 
 
 
 
src/ui/components.py CHANGED
@@ -137,17 +137,32 @@ class RecipeHero(TemplatedHTML):
137
  # ---------------------------------------------------------------------------
138
  class IngredientChips(TemplatedHTML):
139
  css_template = """
140
- .cwm-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px 0; }
141
- .cwm-chip {
142
- background: #fff3d8; color: #6b4a2a; border: 1px solid #d8c9ad;
143
- border-radius: 999px; padding: 6px 14px; font-size: 14px;
144
- font-family: 'Inter', sans-serif;
145
- }
146
- .cwm-chip.missing { background: #fbe2d2; border-color: #c47a52; color: #8a3c0e; }
147
- .cwm-chips-empty {
148
- color: #b39870; font-style: italic; padding: 14px 0;
149
- }
150
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
  @classmethod
153
  def _render_body(cls, state: dict[str, Any]) -> str:
 
137
  # ---------------------------------------------------------------------------
138
  class IngredientChips(TemplatedHTML):
139
  css_template = """
140
+ .cwm-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px 0; }
141
+
142
+ /* Forzamos el color del texto y nos aseguramos de que no se herede un color claro */
143
+ .cwm-chips .cwm-chip {
144
+ background: #fbe2d2 !important;
145
+ color: #6b4a2a !important;
146
+ border: 1px solid #d8c9ad !important;
147
+ border-radius: 999px;
148
+ padding: 6px 14px;
149
+ font-size: 14px;
150
+ font-family: 'Inter', sans-serif;
151
+ display: inline-block;
152
+ }
153
+
154
+ /* Forzamos el color para los chips de ingredientes faltantes */
155
+ .cwm-chips .cwm-chip.missing {
156
+ background: #fbe2d2 !important;
157
+ border-color: #c47a52 !important;
158
+ }
159
+
160
+ .cwm-chips-empty {
161
+ color: #6b4a2a !important;
162
+ font-style: italic;
163
+ padding: 14px 0;
164
+ }
165
+ """
166
 
167
  @classmethod
168
  def _render_body(cls, state: dict[str, Any]) -> str:
src/ui/components.pyi ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ class TemplatedHTML(gr.HTML):
3
+ """A gr.HTML wrapper that re-renders a template against a state dict."""
4
+
5
+ html_template: ClassVar[str] = ""
6
+ css_template: ClassVar[str] = ""
7
+
8
+ def __init__(self, value: dict[str, Any] | None = None, **kwargs):
9
+ self._state: dict[str, Any] = value or {}
10
+ super().__init__(value=self.render(self._state), **kwargs)
11
+
12
+ @classmethod
13
+ def render(cls, state: dict[str, Any]) -> str:
14
+ body = cls._render_body(state)
15
+ return f"<style>{cls.css_template}</style>{body}"
16
+
17
+ @classmethod
18
+ def _render_body(cls, state: dict[str, Any]) -> str:
19
+ # Default: simple ${value.X} substitution against the state dict.
20
+ return substitute(cls.html_template, state)
21
+ from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
22
+ from gradio.blocks import Block
23
+ if TYPE_CHECKING:
24
+ from gradio.components import Timer
25
+ from gradio.components.base import Component
26
+
27
+
28
+ class RecipeHero(TemplatedHTML):
29
+ css_template = """
30
+ .cwm-hero {
31
+ background: #fffbf0;
32
+ border: 1px solid #d8c9ad;
33
+ border-radius: 16px;
34
+ padding: 32px;
35
+ display: grid;
36
+ grid-template-columns: 1fr 1fr;
37
+ gap: 28px;
38
+ box-shadow: 0 10px 28px rgba(107, 74, 42, 0.10);
39
+ }
40
+ .cwm-hero img {
41
+ width: 100%; height: 320px; object-fit: cover; border-radius: 12px;
42
+ background: #efe3c8;
43
+ }
44
+ .cwm-hero h1 {
45
+ font-family: 'Lora', serif; font-size: 38px; color: #6b4a2a;
46
+ margin: 0 0 8px;
47
+ }
48
+ .cwm-hero .meta {
49
+ color: #8a6a3a; font-size: 14px; letter-spacing: 0.04em;
50
+ text-transform: uppercase; margin-bottom: 18px;
51
+ }
52
+ .cwm-hero .visual {
53
+ font-family: 'Lora', serif; font-style: italic; color: #6b4a2a;
54
+ font-size: 17px; line-height: 1.55;
55
+ }
56
+ @media (max-width: 720px) { .cwm-hero { grid-template-columns: 1fr; } }
57
+ """
58
+
59
+ @classmethod
60
+ def _render_body(cls, state: dict[str, Any]) -> str:
61
+ name = html.escape(state.get("name") or "Pick a dish to get started")
62
+ cuisine = html.escape(state.get("cuisine") or "")
63
+ servings = state.get("servings") or 0
64
+ time = state.get("total_time_minutes") or 0
65
+ visual = html.escape(state.get("final_dish_visual") or "")
66
+ img = state.get("final_dish_image_path") or ""
67
+ img_tag = (
68
+ f'<img src="/file={html.escape(img)}" alt="final dish"/>'
69
+ if img else '<div class="cwm-hero" style="background:#efe3c8;border-radius:12px;height:320px;"></div>'
70
+ )
71
+ return f"""
72
+ <div class="cwm-hero">
73
+ <div>{img_tag}</div>
74
+ <div>
75
+ <div class="meta">{cuisine} � {servings} servings � {time} min</div>
76
+ <h1>{name}</h1>
77
+ <p class="visual">{visual}</p>
78
+ </div>
79
+ </div>
80
+ """
81
+ from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
82
+ from gradio.blocks import Block
83
+ if TYPE_CHECKING:
84
+ from gradio.components import Timer
85
+ from gradio.components.base import Component
86
+
87
+
88
+ class IngredientChips(TemplatedHTML):
89
+ css_template = """
90
+ .cwm-chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 14px 0; }
91
+
92
+ /* Forzamos el color del texto y nos aseguramos de que no se herede un color claro */
93
+ .cwm-chips .cwm-chip {
94
+ background: #fbe2d2 !important;
95
+ color: #6b4a2a !important;
96
+ border: 1px solid #d8c9ad !important;
97
+ border-radius: 999px;
98
+ padding: 6px 14px;
99
+ font-size: 14px;
100
+ font-family: 'Inter', sans-serif;
101
+ display: inline-block;
102
+ }
103
+
104
+ /* Forzamos el color para los chips de ingredientes faltantes */
105
+ .cwm-chips .cwm-chip.missing {
106
+ background: #fbe2d2 !important;
107
+ border-color: #c47a52 !important;
108
+ }
109
+
110
+ .cwm-chips-empty {
111
+ color: #6b4a2a !important;
112
+ font-style: italic;
113
+ padding: 14px 0;
114
+ }
115
+ """
116
+
117
+ @classmethod
118
+ def _render_body(cls, state: dict[str, Any]) -> str:
119
+ have = state.get("have") or []
120
+ missing = state.get("missing") or []
121
+ if not have and not missing:
122
+ return '<div class="cwm-chips-empty">No ingredients yet � upload a fridge photo.</div>'
123
+ parts = ['<div class="cwm-chips">']
124
+ for ing in have:
125
+ parts.append(f'<span class="cwm-chip">{html.escape(str(ing))}</span>')
126
+ for ing in missing:
127
+ parts.append(f'<span class="cwm-chip missing">missing: {html.escape(str(ing))}</span>')
128
+ parts.append('</div>')
129
+ return "".join(parts)
130
+ from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
131
+ from gradio.blocks import Block
132
+ if TYPE_CHECKING:
133
+ from gradio.components import Timer
134
+ from gradio.components.base import Component
135
+
136
+
137
+ class DishOptions(TemplatedHTML):
138
+ css_template = """
139
+ .cwm-options { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
140
+ .cwm-option {
141
+ background: #fffbf0; border: 1px solid #d8c9ad; border-radius: 12px;
142
+ padding: 18px; text-align: left;
143
+ }
144
+ .cwm-option h3 {
145
+ font-family: 'Lora', serif; font-size: 19px; color: #6b4a2a;
146
+ margin: 0 0 6px;
147
+ }
148
+ .cwm-option p { color: #7a5a35; font-size: 14px; line-height: 1.45; margin: 0; }
149
+ @media (max-width: 720px) { .cwm-options { grid-template-columns: 1fr; } }
150
+ """
151
+
152
+ @classmethod
153
+ def _render_body(cls, state: dict[str, Any]) -> str:
154
+ opts = state.get("options") or []
155
+ if not opts:
156
+ return ""
157
+ cards = []
158
+ for o in opts[:3]:
159
+ name = html.escape(str(o.get("name", "")))
160
+ why = html.escape(str(o.get("why", "")))
161
+ cards.append(f'<div class="cwm-option"><h3>{name}</h3><p>{why}</p></div>')
162
+ return f'<div class="cwm-options">{"".join(cards)}</div>'
163
+ from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
164
+ from gradio.blocks import Block
165
+ if TYPE_CHECKING:
166
+ from gradio.components import Timer
167
+ from gradio.components.base import Component
168
+
169
+
170
+ class NutritionGrid(TemplatedHTML):
171
+ css_template = """
172
+ .cwm-nutri-wrap { margin-top: 10px; }
173
+ .cwm-nutri-title {
174
+ font-family: 'Lora', serif; color: #6b4a2a; font-size: 22px; margin: 0 0 14px;
175
+ }
176
+ .cwm-nutri {
177
+ display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px;
178
+ }
179
+ .cwm-nutri-cell {
180
+ background: #fffbf0; border: 1px solid #d8c9ad; border-radius: 10px;
181
+ padding: 14px 10px; text-align: center;
182
+ }
183
+ .cwm-nutri-cell .v {
184
+ font-family: 'Lora', serif; font-size: 24px; font-weight: 700; color: #6b4a2a;
185
+ display: block;
186
+ }
187
+ .cwm-nutri-cell .l {
188
+ font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
189
+ color: #8a6a3a; margin-top: 4px;
190
+ }
191
+ @media (max-width: 720px) { .cwm-nutri { grid-template-columns: repeat(2, 1fr); } }
192
+ """
193
+
194
+ @classmethod
195
+ def _render_body(cls, state: dict[str, Any]) -> str:
196
+ nutri = state.get("nutrition") or {}
197
+ cells = [
198
+ (nutri.get("calories", 0), "kcal"),
199
+ (nutri.get("protein_g", 0), "protein"),
200
+ (nutri.get("carbs_g", 0), "carbs"),
201
+ (nutri.get("fat_g", 0), "fat"),
202
+ (nutri.get("fiber_g", 0), "fiber"),
203
+ ]
204
+ cell_html = "".join(
205
+ f'<div class="cwm-nutri-cell"><span class="v">{html.escape(str(v))}</span>'
206
+ f'<div class="l">{html.escape(label)}</div></div>'
207
+ for v, label in cells
208
+ )
209
+ return f"""
210
+ <div class="cwm-nutri-wrap">
211
+ <h3 class="cwm-nutri-title">Per serving</h3>
212
+ <div class="cwm-nutri">{cell_html}</div>
213
+ </div>
214
+ """
215
+ from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
216
+ from gradio.blocks import Block
217
+ if TYPE_CHECKING:
218
+ from gradio.components import Timer
219
+ from gradio.components.base import Component
220
+
221
+
222
+ class VerdictBadge(TemplatedHTML):
223
+ css_template = """
224
+ .cwm-verdict {
225
+ display: flex; align-items: center; gap: 18px;
226
+ background: #fffbf0; border-radius: 12px; padding: 18px 22px;
227
+ border: 1px solid #d8c9ad;
228
+ }
229
+ .cwm-verdict.go { border-left: 6px solid #4f8b4a; }
230
+ .cwm-verdict.wait { border-left: 6px solid #d4a23c; }
231
+ .cwm-verdict.fix { border-left: 6px solid #b94a3a; }
232
+ .cwm-verdict-pill {
233
+ font-family: 'Lora', serif; font-weight: 700; font-size: 16px;
234
+ text-transform: uppercase; letter-spacing: 0.08em;
235
+ padding: 8px 16px; border-radius: 999px; color: #fffbf0;
236
+ }
237
+ .cwm-verdict.go .cwm-verdict-pill { background: #4f8b4a; }
238
+ .cwm-verdict.wait .cwm-verdict-pill { background: #d4a23c; }
239
+ .cwm-verdict.fix .cwm-verdict-pill { background: #b94a3a; }
240
+ .cwm-verdict-text { font-size: 16px; color: #4a3722; line-height: 1.5; }
241
+ .cwm-verdict-text small { color: #8a6a3a; display: block; margin-top: 4px; }
242
+ .cwm-verdict-empty {
243
+ color: #b39870; font-style: italic; padding: 14px 0;
244
+ }
245
+ """
246
+
247
+ @classmethod
248
+ def _render_body(cls, state: dict[str, Any]) -> str:
249
+ v = state.get("verdict")
250
+ if not v:
251
+ return '<div class="cwm-verdict-empty">Upload a progress photo to get a verdict.</div>'
252
+ verdict = state.get("verdict", "wait")
253
+ feedback = html.escape(state.get("feedback", ""))
254
+ tip = state.get("tip")
255
+ tip_html = f"<small>{html.escape(tip)}</small>" if tip else ""
256
+ return f"""
257
+ <div class="cwm-verdict {html.escape(verdict)}">
258
+ <div class="cwm-verdict-pill">{html.escape(verdict)}</div>
259
+ <div class="cwm-verdict-text">{feedback}{tip_html}</div>
260
+ </div>
261
+ """
262
+ from typing import Callable, Literal, Sequence, Any, TYPE_CHECKING
263
+ from gradio.blocks import Block
264
+ if TYPE_CHECKING:
265
+ from gradio.components import Timer
266
+ from gradio.components.base import Component
267
+
268
+
src/ui/theme.py CHANGED
@@ -19,7 +19,6 @@ CSS = """
19
  .gradio-container .prose h3 { font-family: 'Lora', serif !important; color: #6b4a2a; }
20
  /* Generic container shared by every HTMLComponent */
21
  .cwm-card {
22
- background: #fffbf0;
23
  border: 1px solid #d8c9ad;
24
  border-radius: 14px;
25
  padding: 22px 26px;
 
19
  .gradio-container .prose h3 { font-family: 'Lora', serif !important; color: #6b4a2a; }
20
  /* Generic container shared by every HTMLComponent */
21
  .cwm-card {
 
22
  border: 1px solid #d8c9ad;
23
  border-radius: 14px;
24
  padding: 22px 26px;