Spaces:
Running
Running
Add AI gardening advisor (Qwen2.5-3B-Instruct via HF Inference)
Browse filesAdds a per-plant "Ask the assistant" panel backed by modules/advisor.py,
grounded in each plant's care profile (sunlight, soil, watering, fertilization).
- app.py +27 -4
- modules/advisor.py +48 -0
- 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
|