import gradio as gr import os import logging # --------------------------------------------------------------------------- # Logging # --------------------------------------------------------------------------- logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logger = logging.getLogger("SpoutSpoon") # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- HF_MODEL = "Qwen/Qwen2.5-Coder-3B-Instruct:nscale" HF_API_TOKEN = os.environ.get("HF_API_TOKEN", "") # --------------------------------------------------------------------------- # System Prompt # --------------------------------------------------------------------------- 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).""" # --------------------------------------------------------------------------- # Real LLM call via Hugging Face InferenceClient # --------------------------------------------------------------------------- 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) # --------------------------------------------------------------------------- # Fallback responses # --------------------------------------------------------------------------- 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." ) # --------------------------------------------------------------------------- # Gradio application # --------------------------------------------------------------------------- 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") # Native Markdown output — no manual HTML conversion output = gr.Markdown( value="_No answer yet._", label="Answer", elem_classes="md_output", ) # Hidden textarea holding the raw markdown for the copy button 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)