Update app.py
Browse files
app.py
CHANGED
|
@@ -55,7 +55,7 @@ def parse_numbered_steps(text):
|
|
| 55 |
if "steps" not in st.session_state:
|
| 56 |
st.session_state.steps = [] # List[(int, str)]
|
| 57 |
if "images" not in st.session_state:
|
| 58 |
-
st.session_state.images = {} # Dict[int, PIL.Image]
|
| 59 |
if "tools_list" not in st.session_state:
|
| 60 |
st.session_state.tools_list = [] # List[str]
|
| 61 |
if "current_step" not in st.session_state:
|
|
@@ -98,7 +98,7 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 98 |
1) Use Gemini chat to identify the category (appliance, automotive, gardening, upcycling).
|
| 99 |
2) Then use Gemini chat to get:
|
| 100 |
- a "tools & materials" list
|
| 101 |
-
- numbered step-by-step instructions
|
| 102 |
3) Parse everything into session_state.
|
| 103 |
"""
|
| 104 |
try:
|
|
@@ -113,15 +113,13 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 113 |
"home appliance repair, automotive maintenance, gardening & urban farming, or upcycling & sustainable crafts. "
|
| 114 |
"Reply back with exactly one category name."
|
| 115 |
)
|
| 116 |
-
# Create a chat instance that can handle both Text and Image
|
| 117 |
cat_chat = client.chats.create(
|
| 118 |
model=CATEGORY_MODEL,
|
| 119 |
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 120 |
)
|
| 121 |
-
# Send the category prompt plus the PIL.Image directly
|
| 122 |
cat_resp = cat_chat.send_message([category_prompt, image])
|
| 123 |
|
| 124 |
-
# Extract the category name (
|
| 125 |
cat_parts = cat_resp.candidates[0].content.parts
|
| 126 |
cat_text = ""
|
| 127 |
for part in cat_parts:
|
|
@@ -143,6 +141,7 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 143 |
"1. First step instructions (be specific and detailed)...\n"
|
| 144 |
"2. Second step instructions...\n"
|
| 145 |
"3. Continue with all necessary steps...\n\n"
|
|
|
|
| 146 |
"Keep each numbered step clear and actionable (2-3 sentences max). "
|
| 147 |
"Include safety warnings where appropriate."
|
| 148 |
)
|
|
@@ -152,20 +151,28 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 152 |
)
|
| 153 |
full_resp = gen_chat.send_message([detailed_prompt, image])
|
| 154 |
|
| 155 |
-
# βββ 3.3 PARSE out tools
|
| 156 |
-
# Combine all text parts from the response
|
| 157 |
gen_parts = full_resp.candidates[0].content.parts
|
| 158 |
-
|
|
|
|
|
|
|
| 159 |
for part in gen_parts:
|
| 160 |
if part.text is not None:
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
-
# Split into tools and steps sections
|
| 165 |
tools_section = ""
|
| 166 |
steps_section = ""
|
| 167 |
-
if "TOOLS AND MATERIALS:" in
|
| 168 |
-
parts =
|
| 169 |
if len(parts) > 1:
|
| 170 |
remaining = parts[1]
|
| 171 |
if "STEPS:" in remaining:
|
|
@@ -174,13 +181,13 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 174 |
steps_section = steps_part.strip()
|
| 175 |
else:
|
| 176 |
tools_section = remaining.strip()
|
| 177 |
-
elif "STEPS:" in
|
| 178 |
-
parts =
|
| 179 |
if len(parts) > 1:
|
| 180 |
steps_section = parts[1].strip()
|
| 181 |
else:
|
| 182 |
# Fallback parsing
|
| 183 |
-
lines =
|
| 184 |
tools_lines = []
|
| 185 |
steps_lines = []
|
| 186 |
current_section = "unknown"
|
|
@@ -207,7 +214,14 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 207 |
# Parse steps
|
| 208 |
parsed_steps = parse_numbered_steps(steps_section)
|
| 209 |
|
| 210 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
st.session_state.tools_list = tools
|
| 212 |
st.session_state.steps = parsed_steps
|
| 213 |
|
|
@@ -230,6 +244,7 @@ def detect_category_and_generate(uploaded_file, context_text):
|
|
| 230 |
st.error(f"Error processing request: {str(e)}")
|
| 231 |
st.error("Please check your API key and try again.")
|
| 232 |
|
|
|
|
| 233 |
def render_sidebar_navigation():
|
| 234 |
"""
|
| 235 |
A sidebar listing each step by number + a button to jump to that step.
|
|
@@ -242,13 +257,14 @@ def render_sidebar_navigation():
|
|
| 242 |
st.sidebar.progress(completed / total_steps)
|
| 243 |
st.sidebar.write(f"Progress: {completed}/{total_steps} steps")
|
| 244 |
|
| 245 |
-
for (idx,
|
| 246 |
is_done = st.session_state.done_flags.get(idx, False)
|
| 247 |
label = f"{'β' if is_done else 'Β·'} Step {idx}"
|
| 248 |
if st.sidebar.button(label, key=f"nav_{idx}"):
|
| 249 |
st.session_state.current_step = idx
|
| 250 |
st.rerun()
|
| 251 |
|
|
|
|
| 252 |
def render_tools_list():
|
| 253 |
"""Show the Tools/Materials list in an expander."""
|
| 254 |
if st.session_state.tools_list:
|
|
@@ -256,11 +272,13 @@ def render_tools_list():
|
|
| 256 |
for item in st.session_state.tools_list:
|
| 257 |
st.write(f"- {item}")
|
| 258 |
|
|
|
|
| 259 |
def render_step(idx, text):
|
| 260 |
"""
|
| 261 |
Render a single step:
|
| 262 |
- Show step number
|
| 263 |
- Show instruction text
|
|
|
|
| 264 |
- Timer if needed
|
| 265 |
- Checkbox for "Done"
|
| 266 |
- Text area for notes
|
|
@@ -271,6 +289,14 @@ def render_step(idx, text):
|
|
| 271 |
st.markdown(f"### Step {idx} of {total}")
|
| 272 |
st.write(text)
|
| 273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
# Timer functionality
|
| 275 |
seconds_left = st.session_state.timers.get(idx, 0)
|
| 276 |
if seconds_left > 0:
|
|
@@ -353,9 +379,10 @@ with st.expander("βΉοΈ How it works", expanded=False):
|
|
| 353 |
"""
|
| 354 |
1. **Upload a photo** of the item you want to fix or build (appliance, car part, plant, craft project).
|
| 355 |
2. **Add context** (optional) - describe whatβs wrong or what you want to achieve.
|
| 356 |
-
3. **Get AI guidance** - The AI will detect the category and provide step-by-step instructions.
|
| 357 |
4. **Follow the steps** - Each step includes:
|
| 358 |
- Clear instructions
|
|
|
|
| 359 |
- Progress tracking with checkboxes
|
| 360 |
- Timer functionality for waiting periods
|
| 361 |
- Note-taking area
|
|
@@ -416,8 +443,7 @@ if st.session_state.prompt_sent:
|
|
| 416 |
elif st.session_state.current_step < 1:
|
| 417 |
st.session_state.current_step = 1
|
| 418 |
|
| 419 |
-
|
| 420 |
-
step_num, step_text = step_tuple
|
| 421 |
render_step(step_num, step_text)
|
| 422 |
|
| 423 |
# Overall progress at bottom
|
|
|
|
| 55 |
if "steps" not in st.session_state:
|
| 56 |
st.session_state.steps = [] # List[(int, str)]
|
| 57 |
if "images" not in st.session_state:
|
| 58 |
+
st.session_state.images = {} # Dict[int, PIL.Image] (illustrations per step)
|
| 59 |
if "tools_list" not in st.session_state:
|
| 60 |
st.session_state.tools_list = [] # List[str]
|
| 61 |
if "current_step" not in st.session_state:
|
|
|
|
| 98 |
1) Use Gemini chat to identify the category (appliance, automotive, gardening, upcycling).
|
| 99 |
2) Then use Gemini chat to get:
|
| 100 |
- a "tools & materials" list
|
| 101 |
+
- numbered step-by-step instructions (and optional illustrations)
|
| 102 |
3) Parse everything into session_state.
|
| 103 |
"""
|
| 104 |
try:
|
|
|
|
| 113 |
"home appliance repair, automotive maintenance, gardening & urban farming, or upcycling & sustainable crafts. "
|
| 114 |
"Reply back with exactly one category name."
|
| 115 |
)
|
|
|
|
| 116 |
cat_chat = client.chats.create(
|
| 117 |
model=CATEGORY_MODEL,
|
| 118 |
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 119 |
)
|
|
|
|
| 120 |
cat_resp = cat_chat.send_message([category_prompt, image])
|
| 121 |
|
| 122 |
+
# Extract the category name (combine all text parts)
|
| 123 |
cat_parts = cat_resp.candidates[0].content.parts
|
| 124 |
cat_text = ""
|
| 125 |
for part in cat_parts:
|
|
|
|
| 141 |
"1. First step instructions (be specific and detailed)...\n"
|
| 142 |
"2. Second step instructions...\n"
|
| 143 |
"3. Continue with all necessary steps...\n\n"
|
| 144 |
+
"Additionally, for each step you may include a brief illustrative image. "
|
| 145 |
"Keep each numbered step clear and actionable (2-3 sentences max). "
|
| 146 |
"Include safety warnings where appropriate."
|
| 147 |
)
|
|
|
|
| 151 |
)
|
| 152 |
full_resp = gen_chat.send_message([detailed_prompt, image])
|
| 153 |
|
| 154 |
+
# βββ 3.3 PARSE out tools, numbered steps, and images ββββββββββββββββββββββββ
|
|
|
|
| 155 |
gen_parts = full_resp.candidates[0].content.parts
|
| 156 |
+
|
| 157 |
+
# First, gather all text into one string to parse tools/steps
|
| 158 |
+
combined_text = ""
|
| 159 |
for part in gen_parts:
|
| 160 |
if part.text is not None:
|
| 161 |
+
combined_text += part.text + "\n"
|
| 162 |
+
combined_text = combined_text.strip()
|
| 163 |
+
|
| 164 |
+
# Now identify and extract any inline images. We'll assign them sequentially to steps.
|
| 165 |
+
inline_images = []
|
| 166 |
+
for part in gen_parts:
|
| 167 |
+
if part.inline_data is not None:
|
| 168 |
+
img = Image.open(BytesIO(part.inline_data.data))
|
| 169 |
+
inline_images.append(img)
|
| 170 |
|
| 171 |
+
# Split combined_text into tools and steps sections
|
| 172 |
tools_section = ""
|
| 173 |
steps_section = ""
|
| 174 |
+
if "TOOLS AND MATERIALS:" in combined_text:
|
| 175 |
+
parts = combined_text.split("TOOLS AND MATERIALS:")
|
| 176 |
if len(parts) > 1:
|
| 177 |
remaining = parts[1]
|
| 178 |
if "STEPS:" in remaining:
|
|
|
|
| 181 |
steps_section = steps_part.strip()
|
| 182 |
else:
|
| 183 |
tools_section = remaining.strip()
|
| 184 |
+
elif "STEPS:" in combined_text:
|
| 185 |
+
parts = combined_text.split("STEPS:")
|
| 186 |
if len(parts) > 1:
|
| 187 |
steps_section = parts[1].strip()
|
| 188 |
else:
|
| 189 |
# Fallback parsing
|
| 190 |
+
lines = combined_text.split("\n")
|
| 191 |
tools_lines = []
|
| 192 |
steps_lines = []
|
| 193 |
current_section = "unknown"
|
|
|
|
| 214 |
# Parse steps
|
| 215 |
parsed_steps = parse_numbered_steps(steps_section)
|
| 216 |
|
| 217 |
+
# Assign inline_images to steps (one-to-one, up to number of steps)
|
| 218 |
+
st.session_state.images = {}
|
| 219 |
+
for idx, step in parsed_steps:
|
| 220 |
+
img_index = idx - 1 # zero-based
|
| 221 |
+
if img_index < len(inline_images):
|
| 222 |
+
st.session_state.images[idx] = inline_images[img_index]
|
| 223 |
+
|
| 224 |
+
# Store tools and steps in session_state
|
| 225 |
st.session_state.tools_list = tools
|
| 226 |
st.session_state.steps = parsed_steps
|
| 227 |
|
|
|
|
| 244 |
st.error(f"Error processing request: {str(e)}")
|
| 245 |
st.error("Please check your API key and try again.")
|
| 246 |
|
| 247 |
+
|
| 248 |
def render_sidebar_navigation():
|
| 249 |
"""
|
| 250 |
A sidebar listing each step by number + a button to jump to that step.
|
|
|
|
| 257 |
st.sidebar.progress(completed / total_steps)
|
| 258 |
st.sidebar.write(f"Progress: {completed}/{total_steps} steps")
|
| 259 |
|
| 260 |
+
for (idx, _) in st.session_state.steps:
|
| 261 |
is_done = st.session_state.done_flags.get(idx, False)
|
| 262 |
label = f"{'β' if is_done else 'Β·'} Step {idx}"
|
| 263 |
if st.sidebar.button(label, key=f"nav_{idx}"):
|
| 264 |
st.session_state.current_step = idx
|
| 265 |
st.rerun()
|
| 266 |
|
| 267 |
+
|
| 268 |
def render_tools_list():
|
| 269 |
"""Show the Tools/Materials list in an expander."""
|
| 270 |
if st.session_state.tools_list:
|
|
|
|
| 272 |
for item in st.session_state.tools_list:
|
| 273 |
st.write(f"- {item}")
|
| 274 |
|
| 275 |
+
|
| 276 |
def render_step(idx, text):
|
| 277 |
"""
|
| 278 |
Render a single step:
|
| 279 |
- Show step number
|
| 280 |
- Show instruction text
|
| 281 |
+
- Possibly show an illustration if available
|
| 282 |
- Timer if needed
|
| 283 |
- Checkbox for "Done"
|
| 284 |
- Text area for notes
|
|
|
|
| 289 |
st.markdown(f"### Step {idx} of {total}")
|
| 290 |
st.write(text)
|
| 291 |
|
| 292 |
+
# If an illustration exists for this step, display it
|
| 293 |
+
if idx in st.session_state.images:
|
| 294 |
+
st.image(
|
| 295 |
+
st.session_state.images[idx],
|
| 296 |
+
caption=f"Illustration for step {idx}",
|
| 297 |
+
use_container_width=True
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
# Timer functionality
|
| 301 |
seconds_left = st.session_state.timers.get(idx, 0)
|
| 302 |
if seconds_left > 0:
|
|
|
|
| 379 |
"""
|
| 380 |
1. **Upload a photo** of the item you want to fix or build (appliance, car part, plant, craft project).
|
| 381 |
2. **Add context** (optional) - describe whatβs wrong or what you want to achieve.
|
| 382 |
+
3. **Get AI guidance** - The AI will detect the category and provide step-by-step instructions (with possible illustrations).
|
| 383 |
4. **Follow the steps** - Each step includes:
|
| 384 |
- Clear instructions
|
| 385 |
+
- An illustrative image (if provided)
|
| 386 |
- Progress tracking with checkboxes
|
| 387 |
- Timer functionality for waiting periods
|
| 388 |
- Note-taking area
|
|
|
|
| 443 |
elif st.session_state.current_step < 1:
|
| 444 |
st.session_state.current_step = 1
|
| 445 |
|
| 446 |
+
step_num, step_text = st.session_state.steps[st.session_state.current_step - 1]
|
|
|
|
| 447 |
render_step(step_num, step_text)
|
| 448 |
|
| 449 |
# Overall progress at bottom
|