| import gradio as gr |
| import os |
| import logging |
|
|
| |
| |
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(asctime)s [%(levelname)s] %(message)s", |
| datefmt="%Y-%m-%d %H:%M:%S", |
| ) |
| logger = logging.getLogger("SpoutSpoon") |
|
|
| |
| |
| |
| HF_MODEL = "Qwen/Qwen2.5-Coder-3B-Instruct:nscale" |
| HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "") |
|
|
| |
| |
| |
| SYSTEM_PROMPT = """You are Sprout & Spoon, a concise and helpful assistant for cooking and gardening advice. |
| |
| Rules you MUST follow: |
| - Do NOT include any conversational filler. No greetings, no 'Hello', no 'Hope this helps', no 'Let me know if...'. |
| - Use strict Markdown formatting with **bold headers** and bullet points where appropriate. |
| - Keep answers short, direct, and easy to read. |
| - Use large, easy-to-read text structure (short paragraphs, clear separation).""" |
|
|
|
|
| |
| |
| |
| def call_local_model(prompt: str) -> str: |
| prompt_preview = prompt.strip()[:60].replace("\n", " ") |
| logger.info("Received question: \"%s\"", prompt_preview) |
|
|
| if not HF_API_TOKEN: |
| logger.warning("HF_API_TOKEN not set - using fallback responses") |
| return _fallback_response(prompt) |
|
|
| logger.info( |
| "Sending request to Hugging Face Inference API (model=%s)", HF_MODEL |
| ) |
|
|
| try: |
| from huggingface_hub import InferenceClient |
|
|
| client = InferenceClient(token=HF_API_TOKEN) |
|
|
| messages = [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": prompt}, |
| ] |
|
|
| stream = client.chat.completions.create( |
| model=HF_MODEL, |
| messages=messages, |
| max_tokens=512, |
| temperature=0.3, |
| top_p=0.9, |
| stream=False, |
| ) |
|
|
| answer = stream.choices[0].message.content.strip() |
| logger.info( |
| "API call succeeded (response_length=%s chars)", len(answer) |
| ) |
| return answer |
|
|
| except ImportError: |
| logger.warning( |
| "huggingface_hub not installed - falling back to keyword response" |
| ) |
| return _fallback_response(prompt) |
| except Exception as exc: |
| logger.error( |
| "API request failed: %s - falling back to keyword response", exc |
| ) |
| return _fallback_response(prompt) |
|
|
|
|
| |
| |
| |
| def _fallback_response(prompt: str) -> str: |
| lower = prompt.lower() |
|
|
| if "tomato" in lower or "tomatoes" in lower: |
| return ( |
| "**Watering**\n" |
| "- Water deeply 2-3 times per week, early in the morning.\n" |
| "- Avoid wetting the leaves to prevent blight.\n\n" |
| "**Feeding**\n" |
| "- Apply a balanced 10-10-10 fertiliser every 2 weeks.\n\n" |
| "**Support**\n" |
| "- Use stakes or cages once the plant is 12 inches tall.\n" |
| "- Tie main stem loosely with soft garden twine." |
| ) |
|
|
| if "chicken" in lower or "leftover" in lower: |
| return ( |
| "**Quick Chicken Salad**\n" |
| "- Shred leftover chicken and mix with Greek yoghurt, diced celery, " |
| "grapes, and a pinch of salt.\n\n" |
| "**Chicken and Veggie Stir-Fry**\n" |
| "- Slice chicken, stir-fry with broccoli, bell peppers, and soy " |
| "sauce for 5 minutes.\n\n" |
| "**Warming Soup**\n" |
| "- Simmer chicken with broth, carrots, onions, and egg noodles " |
| "for 20 minutes." |
| ) |
|
|
| if "rose" in lower or "prune" in lower: |
| return ( |
| "**When to Prune**\n" |
| "- Late winter or early spring, just before new growth begins.\n\n" |
| "**How to Prune**\n" |
| "- Remove dead, damaged, or crossing branches first.\n" |
| "- Cut at a 45 degree angle 1/4 inch above an outward-facing bud.\n" |
| "- Open the centre of the plant for airflow.\n\n" |
| "**Aftercare**\n" |
| "- Apply a layer of mulch and water thoroughly." |
| ) |
|
|
| return ( |
| "**Quick Tips**\n" |
| "- Keep your workspace clean and organised.\n" |
| "- Prep all ingredients before you start cooking.\n" |
| "- In the garden, water deeply and less often for stronger roots." |
| ) |
|
|
|
|
| |
| |
| |
| CUSTOM_CSS = """ |
| .gradio-container { max-width: 800px; margin: auto; } |
| label { font-size: 1.2rem !important; } |
| button { font-size: 1.1rem !important; } |
| .md_output p, .md_output li { font-size: 1.4rem !important; line-height: 1.6; } |
| """ |
|
|
| with gr.Blocks(title="Sprout & Spoon") as demo: |
|
|
| gr.Markdown("# \U0001f373 Sprout & Spoon\nAsk a cooking or gardening question below.") |
|
|
| user_input = gr.Textbox( |
| label="Your question", |
| placeholder="e.g. How do I store fresh basil?", |
| lines=3, |
| ) |
|
|
| with gr.Row(): |
| submit_btn = gr.Button("Submit", variant="primary") |
| clear_btn = gr.Button("Clear") |
|
|
| |
| output = gr.Markdown( |
| value="_No answer yet._", |
| label="Answer", |
| elem_classes="md_output", |
| ) |
|
|
| |
| raw_holder = gr.Textbox( |
| value="", |
| label="", |
| visible=False, |
| elem_id="raw-text-holder", |
| ) |
|
|
| gr.Markdown("### Try an example") |
| with gr.Row(): |
| example_tomato = gr.Button("\U0001f345 Help with my Tomatoes") |
| example_chicken = gr.Button("\U0001f357 Leftover Chicken Recipe") |
| example_rose = gr.Button("\U0001f339 How to prune Roses") |
|
|
| def respond(message: str): |
| if not message or not message.strip(): |
| empty = "_Please enter a question._" |
| return empty, empty |
| answer = call_local_model(message) |
| return answer, answer |
|
|
| submit_btn.click( |
| fn=respond, inputs=user_input, outputs=[output, raw_holder] |
| ) |
| user_input.submit( |
| fn=respond, inputs=user_input, outputs=[output, raw_holder] |
| ) |
|
|
| def clear_all(): |
| return "", "_No answer yet._", "" |
|
|
| clear_btn.click( |
| fn=clear_all, |
| inputs=[], |
| outputs=[user_input, output, raw_holder], |
| ) |
|
|
| for btn, text in [ |
| (example_tomato, "Help with my Tomatoes"), |
| (example_chicken, "Leftover Chicken Recipe"), |
| (example_rose, "How to prune Roses"), |
| ]: |
| btn.click( |
| fn=lambda q=text: q, |
| inputs=[], |
| outputs=user_input, |
| ).then( |
| fn=respond, |
| inputs=user_input, |
| outputs=[output, raw_holder], |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch(share=True, theme=gr.themes.Soft(), css=CUSTOM_CSS) |