Crocolil commited on
Commit
63959ee
Β·
1 Parent(s): 93871cb

Add AI gardening advisor (Qwen2.5-3B-Instruct via HF Inference)

Browse files

Adds a per-plant "Ask the assistant" panel backed by modules/advisor.py,
grounded in each plant's care profile (sunlight, soil, watering, fertilization).

Files changed (3) hide show
  1. app.py +27 -4
  2. modules/advisor.py +48 -0
  3. requirements.txt +3 -0
app.py CHANGED
@@ -29,6 +29,7 @@ from modules.recommender import generate_care_notes
29
  from modules.weather_utils import did_or_will_rain, last_rained_date, weather_values
30
  from modules.watering import get_watering_frequency, should_water
31
  from modules import pixel_art
 
32
  from utils.geo import city_to_coordinates
33
 
34
  # ── Config ─────────────────────────────────────────────────────────────────────
@@ -408,6 +409,12 @@ with gr.Blocks(title="🌿 Plant Watering Planner") as app:
408
  return_btn = gr.Button("πŸ”™ Back to gallery")
409
  action_status = gr.Markdown()
410
 
 
 
 
 
 
 
411
  # ── Sidebar: watering recommendations + forecast ────────────────────
412
  with gr.Column(scale=2, elem_id="sidebar"):
413
  gr.Markdown("### πŸ’§ Watering today")
@@ -439,7 +446,7 @@ with gr.Blocks(title="🌿 Plant Watering Planner") as app:
439
 
440
  def _store_and_show(evt: gr.SelectData, user_id):
441
  """Update detail panel, reveal action buttons, and return the selected index."""
442
- return on_plant_selected(evt, user_id), evt.index, gr.Row(visible=True)
443
 
444
  add_plant_btn.click(
445
  fn=lambda: gr.Column(visible=True),
@@ -461,7 +468,7 @@ with gr.Blocks(title="🌿 Plant Watering Planner") as app:
461
  garden_gallery.select(
462
  fn=_store_and_show,
463
  inputs=[user_id_state],
464
- outputs=[plant_detail, selected_idx, action_row],
465
  )
466
 
467
  def _mark_watered_by_idx(idx, user_id):
@@ -503,8 +510,24 @@ with gr.Blocks(title="🌿 Plant Watering Planner") as app:
503
  outputs=[action_status, garden_gallery],
504
  )
505
  return_btn.click(
506
- fn=lambda: ("", None, gr.Row(visible=False)),
507
- outputs=[plant_detail, selected_idx, action_row],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  )
509
 
510
  refresh_btn.click(
 
29
  from modules.weather_utils import did_or_will_rain, last_rained_date, weather_values
30
  from modules.watering import get_watering_frequency, should_water
31
  from modules import pixel_art
32
+ from modules import advisor
33
  from utils.geo import city_to_coordinates
34
 
35
  # ── Config ─────────────────────────────────────────────────────────────────────
 
409
  return_btn = gr.Button("πŸ”™ Back to gallery")
410
  action_status = gr.Markdown()
411
 
412
+ # Ask-the-assistant panel β€” hidden until a plant is selected
413
+ with gr.Group(visible=False) as advisor_panel:
414
+ advisor_question = gr.Textbox(label="Ask about this plant", placeholder="e.g. Why are the leaves turning yellow?")
415
+ advisor_ask_btn = gr.Button("πŸ€– Ask the assistant")
416
+ advisor_answer = gr.Markdown()
417
+
418
  # ── Sidebar: watering recommendations + forecast ────────────────────
419
  with gr.Column(scale=2, elem_id="sidebar"):
420
  gr.Markdown("### πŸ’§ Watering today")
 
446
 
447
  def _store_and_show(evt: gr.SelectData, user_id):
448
  """Update detail panel, reveal action buttons, and return the selected index."""
449
+ return on_plant_selected(evt, user_id), evt.index, gr.Row(visible=True), gr.Group(visible=True), ""
450
 
451
  add_plant_btn.click(
452
  fn=lambda: gr.Column(visible=True),
 
468
  garden_gallery.select(
469
  fn=_store_and_show,
470
  inputs=[user_id_state],
471
+ outputs=[plant_detail, selected_idx, action_row, advisor_panel, advisor_answer],
472
  )
473
 
474
  def _mark_watered_by_idx(idx, user_id):
 
510
  outputs=[action_status, garden_gallery],
511
  )
512
  return_btn.click(
513
+ fn=lambda: ("", None, gr.Row(visible=False), gr.Group(visible=False), ""),
514
+ outputs=[plant_detail, selected_idx, action_row, advisor_panel, advisor_answer],
515
+ )
516
+
517
+ def ask_plant_advisor(question, idx, user_id):
518
+ if not question.strip():
519
+ return "Type a question first."
520
+ plants = _visible_plants(user_id)
521
+ if idx is None or idx >= len(plants):
522
+ return "Select a plant first."
523
+ plant = plants[idx]
524
+ info = get_plant_info(plant["genus"])
525
+ return advisor.ask_about_plant(question, info, plant_name=plant.get("nickname"), genus=plant["genus"])
526
+
527
+ advisor_ask_btn.click(
528
+ fn=ask_plant_advisor,
529
+ inputs=[advisor_question, selected_idx, user_id_state],
530
+ outputs=[advisor_answer],
531
  )
532
 
533
  refresh_btn.click(
modules/advisor.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from huggingface_hub import InferenceClient
4
+
5
+ ADVISOR_MODEL_ID = os.getenv("ADVISOR_MODEL_ID", "Qwen/Qwen2.5-3B-Instruct")
6
+
7
+ _client = None
8
+
9
+
10
+ def _get_client() -> InferenceClient:
11
+ global _client
12
+ if _client is None:
13
+ _client = InferenceClient(model=ADVISOR_MODEL_ID, token=os.getenv("HF_TOKEN"))
14
+ return _client
15
+
16
+
17
+ def _build_system_prompt(plant_info: dict, plant_name: str | None = None, genus: str | None = None) -> str:
18
+ name = plant_name or genus or "this plant"
19
+ return (
20
+ "You are an expert gardening assistant with deep knowledge of houseplant "
21
+ "and garden plant care. Be practical, encouraging, and specific.\n\n"
22
+ f"The user is asking about their plant: {name} (genus: {genus}). "
23
+ "Known care profile for this plant:\n"
24
+ f"- Sunlight: {plant_info.get('sunlight')}\n"
25
+ f"- Soil: {plant_info.get('soil')}\n"
26
+ f"- Watering frequency: every {plant_info.get('watering_frequency_days')} days\n"
27
+ f"- Fertilization: {plant_info.get('fertilization_type')}\n\n"
28
+ "Use this profile as context, but also draw on your general gardening "
29
+ "knowledge for issues it doesn't cover (pests, diseases, yellowing "
30
+ "leaves, repotting, etc.). Give concrete, actionable advice and never "
31
+ "recommend dangerous or toxic substances. Answer in 2-4 sentences, in "
32
+ "the same language as the question."
33
+ )
34
+
35
+
36
+ def ask_about_plant(question: str, plant_info: dict, plant_name: str | None = None, genus: str | None = None) -> str:
37
+ """Ask the advisor a question about a specific plant, grounded in its care data."""
38
+ try:
39
+ completion = _get_client().chat_completion(
40
+ messages=[
41
+ {"role": "system", "content": _build_system_prompt(plant_info, plant_name, genus)},
42
+ {"role": "user", "content": question},
43
+ ],
44
+ max_tokens=300,
45
+ )
46
+ return completion.choices[0].message.content
47
+ except Exception:
48
+ return "Sorry, the assistant is unavailable right now β€” please try again later."
requirements.txt CHANGED
@@ -9,6 +9,9 @@ datasets==5.0.0
9
  transformers==5.12.0
10
  accelerate==1.14.0
11
 
 
 
 
12
  # torch: left unpinned on purpose β€” Colab/HF Spaces ship a CUDA-matched build,
13
  # and pinning here would force a CPU reinstall over it. Tested with 2.12.0.
14
  torch>=2.12
 
9
  transformers==5.12.0
10
  accelerate==1.14.0
11
 
12
+ # ML β€” plant-care advisor chat via HF Inference Providers (modules/advisor.py)
13
+ huggingface_hub==1.19.0
14
+
15
  # torch: left unpinned on purpose β€” Colab/HF Spaces ship a CUDA-matched build,
16
  # and pinning here would force a CPU reinstall over it. Tested with 2.12.0.
17
  torch>=2.12