Update app.py
Browse files
app.py
CHANGED
|
@@ -50,7 +50,6 @@ def parse_numbered_steps(text):
|
|
| 50 |
# 2. SESSION STATE SETUP
|
| 51 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 52 |
|
| 53 |
-
# Central dictionary for session state management
|
| 54 |
if "app_state" not in st.session_state:
|
| 55 |
st.session_state.app_state = {
|
| 56 |
"steps": [], "images": {}, "tools_list": [], "current_step": 1,
|
|
@@ -76,30 +75,23 @@ def reset_state():
|
|
| 76 |
st.success("β
Reset complete!")
|
| 77 |
st.rerun()
|
| 78 |
|
| 79 |
-
def
|
| 80 |
-
"""Helper to send requests
|
| 81 |
try:
|
| 82 |
-
chat = client.chats.create(
|
| 83 |
-
model=model_name,
|
| 84 |
-
config=types.GenerateContentConfig(response_modalities=["Text"]) # Assuming text response for these tasks
|
| 85 |
-
)
|
| 86 |
response = chat.send_message([prompt, image])
|
| 87 |
-
# Combine all text parts from the response
|
| 88 |
response_text = "".join(part.text for part in response.candidates[0].content.parts if part.text)
|
| 89 |
return response_text.strip()
|
| 90 |
except Exception as e:
|
| 91 |
-
st.error(f"Error
|
| 92 |
return None
|
| 93 |
|
| 94 |
def initial_analysis(uploaded_file, context_text):
|
| 95 |
-
"""
|
| 96 |
-
First pass with AI: get category, then get title, description, and initial plan.
|
| 97 |
-
"""
|
| 98 |
image = Image.open(uploaded_file)
|
| 99 |
st.session_state.app_state['user_image'] = image
|
| 100 |
|
| 101 |
with st.spinner("π€ Analyzing your project and preparing a plan..."):
|
| 102 |
-
# Step 1: Detect Category using CATEGORY_MODEL
|
| 103 |
category_prompt = (
|
| 104 |
"You are an expert DIY assistant. Analyze the user's image and context. "
|
| 105 |
f"Context: '{context_text}'. "
|
|
@@ -108,32 +100,28 @@ def initial_analysis(uploaded_file, context_text):
|
|
| 108 |
"Upcycling & Sustainable Crafts, or DIY Project Creation. "
|
| 109 |
"Reply with ONLY the category name."
|
| 110 |
)
|
| 111 |
-
category =
|
| 112 |
if not category: return
|
| 113 |
st.session_state.app_state['category'] = category
|
| 114 |
|
| 115 |
-
# Step 2: Generate Title, Description, and Plan using GENERATION_MODEL
|
| 116 |
plan_prompt = f"""
|
| 117 |
-
You are an expert DIY assistant
|
| 118 |
User Context: "{context_text if context_text else 'No context provided.'}"
|
| 119 |
-
|
| 120 |
Based on the image and context, perform the following:
|
| 121 |
1. **Title:** Create a short, clear title for this project.
|
| 122 |
2. **Description:** Write a brief, one-paragraph description of the goal.
|
| 123 |
3. **Initial Plan:**
|
| 124 |
-
- If
|
| 125 |
- For all other cases, briefly outline the main stages of the proposed solution.
|
| 126 |
-
|
| 127 |
Structure your response EXACTLY like this:
|
| 128 |
TITLE: [Your title]
|
| 129 |
DESCRIPTION: [Your description]
|
| 130 |
INITIAL PLAN:
|
| 131 |
[Your plan or 3 options]
|
| 132 |
"""
|
| 133 |
-
plan_response =
|
| 134 |
if not plan_response: return
|
| 135 |
|
| 136 |
-
# Parse the second response
|
| 137 |
try:
|
| 138 |
st.session_state.app_state['project_title'] = re.search(r"TITLE:\s*(.*)", plan_response).group(1).strip()
|
| 139 |
st.session_state.app_state['project_description'] = re.search(r"DESCRIPTION:\s*(.*)", plan_response, re.DOTALL).group(1).strip()
|
|
@@ -148,16 +136,15 @@ def initial_analysis(uploaded_file, context_text):
|
|
| 148 |
st.session_state.app_state['prompt_sent'] = True
|
| 149 |
if context_text:
|
| 150 |
st.session_state.app_state['plan_approved'] = True
|
| 151 |
-
|
| 152 |
else:
|
| 153 |
st.session_state.app_state['plan_approved'] = False
|
| 154 |
except AttributeError:
|
| 155 |
st.error("The AI response was not in the expected format. Please try again.")
|
| 156 |
st.session_state.app_state['prompt_sent'] = False
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
"""Generates the detailed, step-by-step guide using GENERATION_MODEL."""
|
| 161 |
image = st.session_state.app_state.get('user_image')
|
| 162 |
if not image:
|
| 163 |
st.error("Image not found. Please start over."); return
|
|
@@ -169,39 +156,55 @@ def generate_detailed_steps(selected_option=None):
|
|
| 169 |
detailed_prompt = f"""
|
| 170 |
You are a DIY expert. The user wants to proceed with the project titled "{st.session_state.app_state['project_title']}".
|
| 171 |
{context}
|
| 172 |
-
Provide a detailed guide
|
|
|
|
| 173 |
|
| 174 |
TOOLS AND MATERIALS:
|
| 175 |
- Tool A
|
| 176 |
- Material B
|
| 177 |
|
| 178 |
STEPS:
|
| 179 |
-
1. First step instructions.
|
| 180 |
2. Second step instructions...
|
| 181 |
"""
|
| 182 |
-
with st.spinner("π οΈ Generating your detailed
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
def render_sidebar_navigation():
|
| 207 |
st.sidebar.markdown("## Steps Navigation")
|
|
@@ -228,7 +231,14 @@ def render_step(idx, text):
|
|
| 228 |
total = len(st.session_state.app_state['steps'])
|
| 229 |
st.markdown(f"### Step {idx} of {total}")
|
| 230 |
st.write(text)
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
done = st.checkbox("β
Mark this step as completed", value=st.session_state.app_state['done_flags'].get(idx, False), key=f"done_{idx}")
|
| 233 |
st.session_state.app_state['done_flags'][idx] = done
|
| 234 |
notes = st.text_area("π Your notes for this step:", value=st.session_state.app_state['notes'].get(idx, ""), height=100, key=f"notes_{idx}")
|
|
@@ -253,12 +263,11 @@ with st.expander("βΉοΈ How it works", expanded=False):
|
|
| 253 |
st.write("""
|
| 254 |
1. **Upload a photo** of your project.
|
| 255 |
2. **(Optional) Describe your goal** for more accurate results.
|
| 256 |
-
3. **Review the Plan.** The AI will propose a plan. If you didn't provide a description, you'll be asked to approve it.
|
| 257 |
-
4. **Get Your Guide** with tools and step-by-step instructions.
|
| 258 |
5. **Follow the Steps** using the interactive checklist.
|
| 259 |
""")
|
| 260 |
|
| 261 |
-
# --- Main UI ---
|
| 262 |
if not st.session_state.app_state['prompt_sent']:
|
| 263 |
st.markdown("---")
|
| 264 |
col1, col2 = st.columns([3, 1])
|
|
@@ -275,8 +284,6 @@ if not st.session_state.app_state['prompt_sent']:
|
|
| 275 |
st.warning("β οΈ Please upload an image first!")
|
| 276 |
if st.button("π Start Over", use_container_width=True):
|
| 277 |
reset_state()
|
| 278 |
-
|
| 279 |
-
# --- Results and Steps UI ---
|
| 280 |
else:
|
| 281 |
render_sidebar_navigation()
|
| 282 |
st.markdown("---")
|
|
@@ -290,14 +297,14 @@ else:
|
|
| 290 |
st.markdown("#### The AI has suggested a few projects. Please choose one:")
|
| 291 |
for i, option in enumerate(st.session_state.app_state['upcycling_options']):
|
| 292 |
if st.button(option, key=f"option_{i}"):
|
| 293 |
-
|
| 294 |
st.rerun()
|
| 295 |
elif not st.session_state.app_state['plan_approved']:
|
| 296 |
st.markdown("#### The AI has proposed the following plan:")
|
| 297 |
st.success(st.session_state.app_state['initial_plan'])
|
| 298 |
if st.button("β
Looks good, proceed with this plan", type="primary"):
|
| 299 |
st.session_state.app_state['plan_approved'] = True
|
| 300 |
-
|
| 301 |
st.rerun()
|
| 302 |
else:
|
| 303 |
render_tools_list()
|
|
|
|
| 50 |
# 2. SESSION STATE SETUP
|
| 51 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 52 |
|
|
|
|
| 53 |
if "app_state" not in st.session_state:
|
| 54 |
st.session_state.app_state = {
|
| 55 |
"steps": [], "images": {}, "tools_list": [], "current_step": 1,
|
|
|
|
| 75 |
st.success("β
Reset complete!")
|
| 76 |
st.rerun()
|
| 77 |
|
| 78 |
+
def send_text_request(model_name, prompt, image):
|
| 79 |
+
"""Helper to send requests that expect only a text response."""
|
| 80 |
try:
|
| 81 |
+
chat = client.chats.create(model=model_name)
|
|
|
|
|
|
|
|
|
|
| 82 |
response = chat.send_message([prompt, image])
|
|
|
|
| 83 |
response_text = "".join(part.text for part in response.candidates[0].content.parts if part.text)
|
| 84 |
return response_text.strip()
|
| 85 |
except Exception as e:
|
| 86 |
+
st.error(f"Error with model {model_name}: {str(e)}")
|
| 87 |
return None
|
| 88 |
|
| 89 |
def initial_analysis(uploaded_file, context_text):
|
| 90 |
+
"""First pass with AI: get category, then title, description, and initial plan."""
|
|
|
|
|
|
|
| 91 |
image = Image.open(uploaded_file)
|
| 92 |
st.session_state.app_state['user_image'] = image
|
| 93 |
|
| 94 |
with st.spinner("π€ Analyzing your project and preparing a plan..."):
|
|
|
|
| 95 |
category_prompt = (
|
| 96 |
"You are an expert DIY assistant. Analyze the user's image and context. "
|
| 97 |
f"Context: '{context_text}'. "
|
|
|
|
| 100 |
"Upcycling & Sustainable Crafts, or DIY Project Creation. "
|
| 101 |
"Reply with ONLY the category name."
|
| 102 |
)
|
| 103 |
+
category = send_text_request(CATEGORY_MODEL, category_prompt, image)
|
| 104 |
if not category: return
|
| 105 |
st.session_state.app_state['category'] = category
|
| 106 |
|
|
|
|
| 107 |
plan_prompt = f"""
|
| 108 |
+
You are an expert DIY assistant in the category: {category}.
|
| 109 |
User Context: "{context_text if context_text else 'No context provided.'}"
|
|
|
|
| 110 |
Based on the image and context, perform the following:
|
| 111 |
1. **Title:** Create a short, clear title for this project.
|
| 112 |
2. **Description:** Write a brief, one-paragraph description of the goal.
|
| 113 |
3. **Initial Plan:**
|
| 114 |
+
- If 'Upcycling & Sustainable Crafts' AND no specific project is mentioned, propose three distinct project options as a numbered list under "UPCYCLING OPTIONS:".
|
| 115 |
- For all other cases, briefly outline the main stages of the proposed solution.
|
|
|
|
| 116 |
Structure your response EXACTLY like this:
|
| 117 |
TITLE: [Your title]
|
| 118 |
DESCRIPTION: [Your description]
|
| 119 |
INITIAL PLAN:
|
| 120 |
[Your plan or 3 options]
|
| 121 |
"""
|
| 122 |
+
plan_response = send_text_request(GENERATION_MODEL, plan_prompt, image)
|
| 123 |
if not plan_response: return
|
| 124 |
|
|
|
|
| 125 |
try:
|
| 126 |
st.session_state.app_state['project_title'] = re.search(r"TITLE:\s*(.*)", plan_response).group(1).strip()
|
| 127 |
st.session_state.app_state['project_description'] = re.search(r"DESCRIPTION:\s*(.*)", plan_response, re.DOTALL).group(1).strip()
|
|
|
|
| 136 |
st.session_state.app_state['prompt_sent'] = True
|
| 137 |
if context_text:
|
| 138 |
st.session_state.app_state['plan_approved'] = True
|
| 139 |
+
generate_detailed_guide_with_images()
|
| 140 |
else:
|
| 141 |
st.session_state.app_state['plan_approved'] = False
|
| 142 |
except AttributeError:
|
| 143 |
st.error("The AI response was not in the expected format. Please try again.")
|
| 144 |
st.session_state.app_state['prompt_sent'] = False
|
| 145 |
|
| 146 |
+
def generate_detailed_guide_with_images(selected_option=None):
|
| 147 |
+
"""Generates the detailed guide with steps and illustrations."""
|
|
|
|
| 148 |
image = st.session_state.app_state.get('user_image')
|
| 149 |
if not image:
|
| 150 |
st.error("Image not found. Please start over."); return
|
|
|
|
| 156 |
detailed_prompt = f"""
|
| 157 |
You are a DIY expert. The user wants to proceed with the project titled "{st.session_state.app_state['project_title']}".
|
| 158 |
{context}
|
| 159 |
+
Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
|
| 160 |
+
Format your response EXACTLY like this:
|
| 161 |
|
| 162 |
TOOLS AND MATERIALS:
|
| 163 |
- Tool A
|
| 164 |
- Material B
|
| 165 |
|
| 166 |
STEPS:
|
| 167 |
+
1. First step instructions.
|
| 168 |
2. Second step instructions...
|
| 169 |
"""
|
| 170 |
+
with st.spinner("π οΈ Generating your detailed guide with illustrations..."):
|
| 171 |
+
try:
|
| 172 |
+
chat = client.chats.create(
|
| 173 |
+
model=GENERATION_MODEL,
|
| 174 |
+
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 175 |
+
)
|
| 176 |
+
full_resp = chat.send_message([detailed_prompt, image])
|
| 177 |
+
gen_parts = full_resp.candidates[0].content.parts
|
| 178 |
+
|
| 179 |
+
combined_text = ""
|
| 180 |
+
inline_images = []
|
| 181 |
+
for part in gen_parts:
|
| 182 |
+
if part.text is not None:
|
| 183 |
+
combined_text += part.text + "\n"
|
| 184 |
+
if part.inline_data is not None:
|
| 185 |
+
img = Image.open(BytesIO(part.inline_data.data))
|
| 186 |
+
inline_images.append(img)
|
| 187 |
+
combined_text = combined_text.strip()
|
| 188 |
+
|
| 189 |
+
tools_section = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*STEPS:", combined_text, re.DOTALL).group(1).strip()
|
| 190 |
+
steps_section = re.search(r"STEPS:\s*(.*)", combined_text, re.DOTALL).group(1).strip()
|
| 191 |
+
parsed_steps = parse_numbered_steps(steps_section)
|
| 192 |
+
|
| 193 |
+
st.session_state.app_state['tools_list'] = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip()]
|
| 194 |
+
st.session_state.app_state['steps'] = parsed_steps
|
| 195 |
+
st.session_state.app_state['images'] = {idx: inline_images[idx - 1] for idx, _ in parsed_steps if idx - 1 < len(inline_images)}
|
| 196 |
+
|
| 197 |
+
for idx, step_text in parsed_steps:
|
| 198 |
+
st.session_state.app_state['done_flags'][idx] = False
|
| 199 |
+
st.session_state.app_state['notes'][idx] = ""
|
| 200 |
+
timer_match = re.search(r"wait\s+for\s+(\d+)\s+(seconds?|minutes?)", step_text.lower())
|
| 201 |
+
if timer_match:
|
| 202 |
+
val, unit = int(timer_match.group(1)), timer_match.group(2)
|
| 203 |
+
st.session_state.app_state['timers'][idx] = val * (60 if "minute" in unit else 1)
|
| 204 |
+
else:
|
| 205 |
+
st.session_state.app_state['timers'][idx] = 0
|
| 206 |
+
except Exception as e:
|
| 207 |
+
st.error(f"Failed to generate or parse the illustrated guide: {str(e)}")
|
| 208 |
|
| 209 |
def render_sidebar_navigation():
|
| 210 |
st.sidebar.markdown("## Steps Navigation")
|
|
|
|
| 231 |
total = len(st.session_state.app_state['steps'])
|
| 232 |
st.markdown(f"### Step {idx} of {total}")
|
| 233 |
st.write(text)
|
| 234 |
+
|
| 235 |
+
if idx in st.session_state.app_state['images']:
|
| 236 |
+
st.image(
|
| 237 |
+
st.session_state.app_state['images'][idx],
|
| 238 |
+
caption=f"Illustration for step {idx}",
|
| 239 |
+
use_container_width=True
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
done = st.checkbox("β
Mark this step as completed", value=st.session_state.app_state['done_flags'].get(idx, False), key=f"done_{idx}")
|
| 243 |
st.session_state.app_state['done_flags'][idx] = done
|
| 244 |
notes = st.text_area("π Your notes for this step:", value=st.session_state.app_state['notes'].get(idx, ""), height=100, key=f"notes_{idx}")
|
|
|
|
| 263 |
st.write("""
|
| 264 |
1. **Upload a photo** of your project.
|
| 265 |
2. **(Optional) Describe your goal** for more accurate results.
|
| 266 |
+
3. **Review the Plan.** The AI will propose a plan. If you didn't provide a description, you'll be asked to approve it.
|
| 267 |
+
4. **Get Your Guide** with tools and illustrated step-by-step instructions.
|
| 268 |
5. **Follow the Steps** using the interactive checklist.
|
| 269 |
""")
|
| 270 |
|
|
|
|
| 271 |
if not st.session_state.app_state['prompt_sent']:
|
| 272 |
st.markdown("---")
|
| 273 |
col1, col2 = st.columns([3, 1])
|
|
|
|
| 284 |
st.warning("β οΈ Please upload an image first!")
|
| 285 |
if st.button("π Start Over", use_container_width=True):
|
| 286 |
reset_state()
|
|
|
|
|
|
|
| 287 |
else:
|
| 288 |
render_sidebar_navigation()
|
| 289 |
st.markdown("---")
|
|
|
|
| 297 |
st.markdown("#### The AI has suggested a few projects. Please choose one:")
|
| 298 |
for i, option in enumerate(st.session_state.app_state['upcycling_options']):
|
| 299 |
if st.button(option, key=f"option_{i}"):
|
| 300 |
+
generate_detailed_guide_with_images(selected_option=option)
|
| 301 |
st.rerun()
|
| 302 |
elif not st.session_state.app_state['plan_approved']:
|
| 303 |
st.markdown("#### The AI has proposed the following plan:")
|
| 304 |
st.success(st.session_state.app_state['initial_plan'])
|
| 305 |
if st.button("β
Looks good, proceed with this plan", type="primary"):
|
| 306 |
st.session_state.app_state['plan_approved'] = True
|
| 307 |
+
generate_detailed_guide_with_images()
|
| 308 |
st.rerun()
|
| 309 |
else:
|
| 310 |
render_tools_list()
|