import os import asyncio import pandas as pd import json import datetime import gradio as gr from fastapi.responses import HTMLResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from fastapi import Form, UploadFile, File, FastAPI from pydantic import BaseModel # Set backend before imports to trigger appropriate load try: import llama_cpp default_backend = "llama_cpp" except ImportError: default_backend = "mock" os.environ["BACKEND"] = os.environ.get("BACKEND", default_backend) import src.llm as llm import src.rag as rag import src.contacts as contacts import src.ledger as ledger import src.db as db # Initialize SQLite Database on startup db.init_db() # Preloaded data files PROFILE_PATH = os.path.dirname(os.path.abspath(__file__)) + "/src/data/user_profile.json" # Translatable strings database STRINGS = { "en": { "title": "ЁЯЪЬ Kisan-Sathi (Farmer Friend)", "subtitle": "Your personal offline agricultural assistant", "save_success": "Entry saved successfully!", "save_fail": "Error saving entry." }, "hi": { "title": "ЁЯЪЬ рдХрд┐рд╕рд╛рди-рд╕рд╛рдереА (Kisan-Sathi)", "subtitle": "рдЖрдкрдХрд╛ рдЕрдкрдирд╛ рдСрдлрд▓рд╛рдЗрди рдХреГрд╖рд┐ рдорд┐рддреНрд░ рдФрд░ рдмрд╣реАрдЦрд╛рддрд╛ рд╕рд╣рд╛рдпрдХ", "save_success": "рд▓реЗрдирджреЗрди рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕рд╣реЗрдЬрд╛ рдЧрдпрд╛!", "save_fail": "рдмрдЪрдд рдХрд░рдиреЗ рдореЗрдВ рддреНрд░реБрдЯрд┐ред" } } # User Profile Management def load_user_profile(): return db.get_profile() def save_user_profile(name, state, district): return db.save_profile(name, state, district) # Helper function to compute nearest pending task from all DB calendars for a crop def get_nearest_pending_task(crop_name): conn = db.get_db() cursor = conn.cursor() cursor.execute( "SELECT id, sow_date, crop, variety FROM calendars WHERE lower(crop) = ? ORDER BY id DESC", (crop_name.lower(),) ) cals = cursor.fetchall() if not cals: conn.close() return None nearest_task = None for cal in cals: cursor.execute( """ SELECT id, stage, action, intervention, target_date, status FROM tasks WHERE calendar_id = ? AND status = 'pending' ORDER BY target_date ASC """, (cal["id"],) ) tasks = cursor.fetchall() for t in tasks: t_date = datetime.datetime.strptime(t["target_date"], "%Y-%m-%d").date() today = datetime.date.today() diff_days = (t_date - today).days if nearest_task is None or t_date < datetime.datetime.strptime(nearest_task["task"]["target_date"], "%Y-%m-%d").date(): nearest_task = { "task": dict(t), "calendar": dict(cal), "diff_days": diff_days } conn.close() return nearest_task # Fallback general month-based advisory def get_fallback_nudge(crop, lang): today = datetime.date.today() month = today.month day = today.day if lang == "hi": crop_display = "рдЧреЗрд╣реВрдВ" if crop.lower() == "wheat" else "рдЖрд▓реВ" elif lang == "hinglish": crop_display = "Wheat" if crop.lower() == "wheat" else "Aloo" else: crop_display = "Wheat" if crop.lower() == "wheat" else "Potato" if crop.lower() == "wheat": if month == 11: stage_en = "Sowing (рдмреБрд╡рд╛рдИ) - Nov 15 to Dec 15" stage_hi = "рдмреБрд╡рд╛рдИ (Sowing) - резрел рдирд╡рдВрдмрд░ рд╕реЗ резрел рджрд┐рд╕рдВрдмрд░" advice_en = "Sow at 4-5 cm depth. Seed rate: 40-50 kg/acre. Apply DAP." advice_hi = "рек-рел рд╕реЗрдореА рдЧрд╣рд░рд╛рдИ рдкрд░ рдмреЛрдПрдВред рдпреВрд░рд┐рдпрд╛ рдФрд░ рдбреАрдПрдкреА рдХрд╛ рдкреНрд░рдпреЛрдЧ рдХрд░реЗрдВред" elif month == 12: stage_en = "CRI Irrigation (рд╕рд┐рдВрдЪрд╛рдИ - CRI) - Dec 05 to Dec 15" stage_hi = "рддрд╛рдЬ рдЬрдбрд╝ рд╡рд┐рдХрд╛рд╕ рд╕рд┐рдВрдЪрд╛рдИ (CRI Stage) - режрел рджрд┐рд╕рдВрдмрд░ рд╕реЗ резрел рджрд┐рд╕рдВрдмрд░" advice_en = "21-25 days after sowing. Critical root development stage." advice_hi = "рдмреБрд╡рд╛рдИ рдХреЗ реирез-реирел рджрд┐рди рдмрд╛рджред рдЬрдбрд╝ рд╡рд┐рдХрд╛рд╕ рдХреЗ рд▓рд┐рдП рд╕рдмрд╕реЗ рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╕рд┐рдВрдЪрд╛рдИред" elif month == 1: stage_en = "Tillering (рдХрд▓реНрд▓реЗ рдлреВрдЯрдирд╛) - Dec 25 to Jan 10" stage_hi = "рдХрд▓реНрд▓реЗ рдлреВрдЯрдирд╛ (Tillering Stage) - реирел рджрд┐рд╕рдВрдмрд░ рд╕реЗ резреж рдЬрдирд╡рд░реА" advice_en = "Apply first top dressing of Urea (40 kg/acre) and perform weeding." advice_hi = "рдпреВрд░рд┐рдпрд╛ рдХреА рдкрд╣рд▓реА рдЯреЙрдк рдбреНрд░реЗрд╕рд┐рдВрдЧ (рекреж рдХрд┐рдЧреНрд░рд╛/рдПрдХрдбрд╝) рдХрд░реЗрдВ рдФрд░ рдирд┐рд░рд╛рдИ рдХрд░реЗрдВред" elif month == 2: stage_en = "Flowering (рдлреВрд▓ рдЖрдирд╛) - Jan 25 to Feb 15" stage_hi = "рдлреВрд▓ рдЖрдирд╛ (Flowering Stage) - реирел рдЬрдирд╡рд░реА рд╕реЗ резрел рдлрд░рд╡рд░реА" advice_en = "Maintain light moisture. Crucial for grain yield." advice_hi = "рд╣рд▓реНрдХреА рдирдореА рдмрдирд╛рдП рд░рдЦреЗрдВред рджрд╛рдирд╛ рдмрдирдиреЗ рдХреА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреЗ рд▓рд┐рдП рдорд╣рддреНрд╡рдкреВрд░реНрдгред" elif month in [4, 5]: stage_en = "Harvesting (рдХрдЯрд╛рдИ) - Apr 01 to Apr 30" stage_hi = "рдХрдЯрд╛рдИ (Harvesting) - режрез рдЕрдкреНрд░реИрд▓ рд╕реЗ рейреж рдЕрдкреНрд░реИрд▓" advice_en = "Harvest when grains are dry and golden (moisture < 14%)." advice_hi = "рдлрд╕рд▓ рд╕реБрдирд╣рд░реА рд╣реЛрдиреЗ рдФрд░ рджрд╛рдиреЗ рдореЗрдВ резрек% рд╕реЗ рдХрдо рдирдореА рд╣реЛрдиреЗ рдкрд░ рдХрдЯрд╛рдИ рдХрд░реЗрдВред" else: stage_en = "Upcoming: Sowing (рдмреБрд╡рд╛рдИ) starts Nov 15" stage_hi = "рдЖрдЧрд╛рдореА рдЪрд░рдг: рдмреБрд╡рд╛рдИ (Sowing) резрел рдирд╡рдВрдмрд░ рд╕реЗ рд╢реБрд░реВ" advice_en = "Prepare field by deep ploughing and testing soil during dry months." advice_hi = "рдЧрд░реНрдорд┐рдпреЛрдВ рдореЗрдВ рдЧрд╣рд░реА рдЬреБрддрд╛рдИ рдХрд░реЗрдВ рдФрд░ рдорд┐рдЯреНрдЯреА рдХрд╛ рдкрд░реАрдХреНрд╖рдг рдХрд░рд╡рд╛рдПрдВред" else: # Potato if month in [10, 11] and day <= 10: stage_en = "Sowing (рдмреБрд╡рд╛рдИ) - Oct 15 to Nov 10" stage_hi = "рдмреБрд╡рд╛рдИ (Sowing) - резрел рдЕрдХреНрдЯреВрдмрд░ рд╕реЗ резреж рдирд╡рдВрдмрд░" advice_en = "Use certified seed tubers. Spacing 60x20 cm. Apply NPK." advice_hi = "рдкреНрд░рдорд╛рдгрд┐рдд рдмреАрдЬ рдХрдВрджреЛрдВ рдХрд╛ рдкреНрд░рдпреЛрдЧ рдХрд░реЗрдВред ремрежxреиреж рд╕реЗрдореА рджреВрд░реА рд░рдЦреЗрдВред" elif month == 11: stage_en = "Earthing Up (рдорд┐рдЯреНрдЯреА рдЪрдврд╝рд╛рдирд╛) - Nov 15 to Nov 30" stage_hi = "рдорд┐рдЯреНрдЯреА рдЪрдврд╝рд╛рдирд╛ (Earthing Up) - резрел рдирд╡рдВрдмрд░ рд╕реЗ рейреж рдирд╡рдВрдмрд░" advice_en = "Apply Urea and mound soil around stems 25 days post-sowing." advice_hi = "рдмреБрд╡рд╛рдИ рдХреЗ реирел рджрд┐рди рдмрд╛рдж рдпреВрд░рд┐рдпрд╛ рдбрд╛рд▓реЗрдВ рдФрд░ рдкреМрдзреЛрдВ рдХреЗ рддрдиреЗ рдХреЗ рдЪрд╛рд░реЛрдВ рдУрд░ рдорд┐рдЯреНрдЯреА рдЪрдврд╝рд╛рдПрдВред" elif month in [12, 1] and (month == 12 or day <= 15): stage_en = "Blight Monitoring (рдЭреБрд▓рд╕рд╛ рдирд┐рдЧрд░рд╛рдиреА) - Dec 01 to Jan 15" stage_hi = "рдЭреБрд▓рд╕рд╛ рд░реЛрдЧ рдирд┐рдЧрд░рд╛рдиреА (Blight Monitoring) - режрез рджрд┐рд╕рдВрдмрд░ рд╕реЗ резрел рдЬрдирд╡рд░реА" advice_en = "Watch for dark spots. Spray Mancozeb (2g/L) on cloudy/foggy days." advice_hi = "рдкрддреНрддрд┐рдпреЛрдВ рдкрд░ рдХрд╛рд▓реЗ рдзрдмреНрдмреЛрдВ рдХреА рдирд┐рдЧрд░рд╛рдиреА рдХрд░реЗрдВред рдлрдлреВрдВрджрдирд╛рд╢рдХ рдХрд╛ рдЫрд┐рдбрд╝рдХрд╛рд╡ рдХрд░реЗрдВред" elif month in [2, 3]: stage_en = "Harvesting (рдХрдЯрд╛рдИ) - Feb 15 to Mar 15" stage_hi = "рдХрдЯрд╛рдИ (Harvesting) - резрел рдлрд░рд╡рд░реА рд╕реЗ резрел рдорд╛рд░реНрдЪ" advice_en = "Cut foliage 10 days before harvest to thicken potato skin." advice_hi = "рдЦреБрджрд╛рдИ рд╕реЗ резреж рджрд┐рди рдкрд╣рд▓реЗ рд╕рд┐рдВрдЪрд╛рдИ рд░реЛрдХреЗрдВ рдФрд░ рдбрдВрдард▓ рдХрд╛рдЯ рд▓реЗрдВ рддрд╛рдХрд┐ рдЫрд┐рд▓рдХрд╛ рд╕рдЦреНрдд рд╣реЛред" else: stage_en = "Upcoming: Sowing (рдмреБрд╡рд╛рдИ) starts Oct 15" stage_hi = "рдЖрдЧрд╛рдореА рдЪрд░рдг: рдмреБрд╡рд╛рдИ (Sowing) резрел рдЕрдХреНрдЯреВрдмрд░ рд╕реЗ рд╢реБрд░реВ" advice_en = "Procure certified seeds from cold storage and keep them in shade." advice_hi = "рдХреЛрд▓реНрдб рд╕реНрдЯреЛрд░реЗрдЬ рд╕реЗ рдкреНрд░рдорд╛рдгрд┐рдд рдмреАрдЬ рдЦрд░реАрджреЗрдВ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдЫрд╛рдпрд╛ рдореЗрдВ рд░рдЦреЗрдВред" if lang == "hi": return f"ЁЯТб **рдЗрд╕ рд╕рдкреНрддрд╛рд╣ рдЖрдкрдХреЗ рдЦреЗрдд рдкрд░ ({crop_display}):** {stage_hi}\nЁЯУМ **рд╕рд▓рд╛рд╣:** {advice_hi}" elif lang == "hinglish": return f"ЁЯТб **Is week aapke khet par ({crop_display}):** {stage_hi}\nЁЯУМ **Salah:** {advice_hi}" else: return f"ЁЯТб **This week on your farm ({crop_display}):** {stage_en}\nЁЯУМ **Advice:** {advice_en}" # Proactive Nudge logic based on calendar and season def get_proactive_nudge(crop, lang): calendars = db.get_calendars() if not calendars: return get_fallback_nudge(crop, lang) nudges = [] for cal in calendars: # Find nearest pending task for this calendar pending_tasks = [t for t in cal["tasks"] if t["status"] == "pending"] if not pending_tasks: continue # Sort pending tasks by target_date pending_tasks.sort(key=lambda t: t["target_date"]) t = pending_tasks[0] t_date = datetime.datetime.strptime(t["target_date"], "%Y-%m-%d").date() today = datetime.date.today() diff_days = (t_date - today).days # Decode action action_dict = t["action"] if isinstance(action_dict, str): try: action_dict = json.loads(action_dict) except: action_dict = {"en": t["action"], "hi": t["action"], "hinglish": t["action"]} action_text = action_dict.get(lang, action_dict.get("en", "")) sow_dt = datetime.datetime.strptime(cal["sow_date"], "%Y-%m-%d") sow_display = sow_dt.strftime("%d %b %Y") # Get localized crop name crop_display = cal["crop"] if cal["crop"].lower() in db.CROP_TEMPLATES: crop_display = db.CROP_TEMPLATES[cal["crop"].lower()]["display"].get(lang, cal["crop"]) variety_suffix = f" ({cal['variety']})" if cal["variety"] else "" if diff_days == 0: if lang == "hi": nudge = f"ЁЯТб **рдЖрдЬ рдХрд╛ рдХрд╛рдо ({crop_display}{variety_suffix}):** {action_text} (рдмреБрд╡рд╛рдИ: {sow_display})" elif lang == "hinglish": nudge = f"ЁЯТб **Aaj ka kaam ({crop_display}{variety_suffix}):** {action_text} (Sowing: {sow_display})" else: nudge = f"ЁЯТб **Today's Action ({crop_display}{variety_suffix}):** {action_text} (Sown: {sow_display})" elif diff_days > 0: if lang == "hi": nudge = f"ЁЯТб **рдЕрдЧрд▓рд╛ рдХрд╛рдо ({crop_display}{variety_suffix}):** {diff_days} рджрд┐рди рдореЗрдВ {action_text} ({sow_display} рдХреЛ рдмреЛрдпрд╛)" elif lang == "hinglish": nudge = f"ЁЯТб **Agla kaam ({crop_display}{variety_suffix}):** {diff_days} din me {action_text} ({sow_display} ko boya)" else: nudge = f"ЁЯТб **Next Action ({crop_display}{variety_suffix}):** {action_text} in {diff_days} days (Sown on {sow_display})" else: # overdue overdue_days = abs(diff_days) if lang == "hi": nudge = f"тЪая╕П **рд▓рдВрдмрд┐рдд рдХрд╛рдо ({crop_display}{variety_suffix}):** {overdue_days} рджрд┐рди рдкрд╣рд▓реЗ рд╣реЛрдирд╛ рдерд╛ - {action_text} ({sow_display} рдХреЛ рдмреЛрдпрд╛)" elif lang == "hinglish": nudge = f"тЪая╕П **Overdue kaam ({crop_display}{variety_suffix}):** {overdue_days} din pehle hona tha - {action_text} ({sow_display} ko boya)" else: nudge = f"тЪая╕П **Overdue Action ({crop_display}{variety_suffix}):** {action_text} was due {overdue_days} days ago (Sown on {sow_display})" nudges.append(nudge) if nudges: return "\n\n".join(nudges) return get_fallback_nudge(crop, lang) # Load crop calendar data helper def load_calendar(crop): crop_key = crop.lower() if crop_key in db.CROP_TEMPLATES: t = db.CROP_TEMPLATES[crop_key] records = [] for stage in t["stages"]: records.append({ "Crop": crop, "Stage (рдЪрд░рдг)": stage["action"]["hi"], "Timing (рд╕рдордп рд╕реАрдорд╛)": f"Day {stage['day_offset']}", "Action & Advice (рдХрд╛рд░реНрдп рдФрд░ рд╕рд▓рд╛рд╣)": stage["intervention"]["hi"] }) return pd.DataFrame(records) return pd.DataFrame() def parse_ledger_text(text, lang): """Parses natural language transaction using LLM helper.""" if not text.strip(): return "today", "", "", 0, "sale" extracted = ledger.parse_transaction(text, lambda p, system, stream: llm.generate(p, system, stream=stream)) return ( extracted.get("date", "today"), extracted.get("item", ""), extracted.get("qty", ""), extracted.get("price", 0), extracted.get("type", "sale") ) # Pydantic Schemas for JSON endpoints class ProfileSaveRequest(BaseModel): name: str state: str district: str class ParseLedgerRequest(BaseModel): text: str lang: str class SaveLedgerRequest(BaseModel): date: str item: str qty: str price: float type: str lang: str class CalendarAddRequest(BaseModel): crop: str variety: str = "" sow_date: str location: str = "" class CalendarDeleteRequest(BaseModel): calendar_id: int class CalendarUpdateRequest(BaseModel): calendar_id: int sow_date: str class TaskToggleRequest(BaseModel): task_id: int status: str class LedgerDeleteRequest(BaseModel): entry_id: int # Instantiate FastAPI App app = FastAPI() # Mount static files folder app.mount("/assets", StaticFiles(directory="assets"), name="assets") # Root path handler to serve the custom frontend Single Page App @app.get("/", response_class=HTMLResponse) def get_homepage(): index_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "index.html") with open(index_path, "r", encoding="utf-8") as f: return f.read() # Profile Endpoints @app.get("/api/profile") def get_profile(): profile = load_user_profile() return profile if profile else {} @app.post("/api/profile") def post_profile(req: ProfileSaveRequest): success = save_user_profile(req.name, req.state, req.district) return {"success": success} # Proactive Nudge Endpoint @app.get("/api/nudge/{crop}/{lang}") def get_nudge(crop: str, lang: str): nudge = get_proactive_nudge(crop, lang) return {"nudge": nudge} # Calendar Endpoint @app.get("/api/calendar/{crop}") def get_calendar(crop: str): calendar_df = load_calendar(crop) if calendar_df.empty: return [] return calendar_df.to_dict(orient="records") # Contacts Endpoint @app.get("/api/contacts") def get_contacts(): return contacts.load_contacts() # Personalized Calendar Endpoints @app.get("/api/calendars") def get_calendars(): return db.get_calendars() @app.post("/api/calendar/add") def post_calendar_add(req: CalendarAddRequest): try: cal_id = db.add_calendar(req.crop, req.variety, req.sow_date, req.location) return {"success": True, "calendar_id": cal_id} except Exception as e: return {"success": False, "error": str(e)} @app.post("/api/calendar/delete") def post_calendar_delete(req: CalendarDeleteRequest): success = db.delete_calendar(req.calendar_id) return {"success": success} @app.post("/api/calendar/update") def post_calendar_update(req: CalendarUpdateRequest): try: success = db.update_calendar_sow_date(req.calendar_id, req.sow_date) return {"success": success} except Exception as e: return {"success": False, "error": str(e)} @app.post("/api/task/toggle") def post_task_toggle(req: TaskToggleRequest): success = db.update_task_status(req.task_id, req.status) return {"success": success} # Ledger Endpoints @app.post("/api/ledger/parse") def post_ledger_parse(req: ParseLedgerRequest): parsed = parse_ledger_text(req.text, req.lang) return { "date": parsed[0], "item": parsed[1], "qty": parsed[2], "price": parsed[3], "type": parsed[4] } @app.post("/api/ledger/save") def post_ledger_save(req: SaveLedgerRequest): success = ledger.add_entry(req.date, req.item, req.qty, req.price, req.type) msg = STRINGS[req.lang]["save_success"] if success else STRINGS[req.lang]["save_fail"] return {"success": success, "message": msg} @app.post("/api/ledger/delete") def post_ledger_delete(req: LedgerDeleteRequest): success = db.delete_ledger_entry(req.entry_id) return {"success": success} @app.post("/api/ledger/clear") def post_ledger_clear(): success = db.clear_ledger() return {"success": success} @app.post("/api/reset") def post_reset(): success = db.clear_all_data() return {"success": success} @app.get("/api/ledger/export") def get_ledger_export(): import io import csv entries, _ = db.get_ledger_entries() output = io.StringIO() writer = csv.writer(output) writer.writerow(["Date", "Type", "Item", "Quantity", "Price", "Timestamp"]) for r in entries: writer.writerow([r["date"], r["type"], r["item"], r["qty"], r["price"], r["created_at"]]) output.seek(0) return StreamingResponse( io.BytesIO(output.getvalue().encode("utf-8")), media_type="text/csv", headers={"Content-Disposition": "attachment; filename=ledger_export.csv"} ) @app.get("/api/ledger/stats") def get_ledger_stats(): df, summary = ledger.get_ledger_data() transactions = [] if not df.empty: for r in df.to_dict(orient="records"): # Map localized or standard keys date_val = r.get("Date", "") item_val = r.get("Item", "") qty_val = r.get("Quantity", "") price_val = r.get("Price", 0) raw_type = str(r.get("Type", "")).lower() tx_id = r.get("Id") type_val = "Sale" if "sale" in raw_type or "рдмрд┐рдХреНрд░реА" in raw_type else "Purchase" transactions.append({ "Id": tx_id, "Date": date_val, "Item": item_val, "Quantity": qty_val, "Price": price_val, "Type": type_val }) return { "summary": summary, "transactions": transactions } # Chatbot Multimodal Streaming API @app.post("/api/ask") async def post_ask( message: str = Form(""), lang: str = Form("hi"), crop: str = Form("Wheat"), history: str = Form("[]"), image: UploadFile = File(None), audio: UploadFile = File(None) ): # Save uploaded media files locally in a temp folder image_path = None audio_path = None # Load profile details dynamically to customize prompt metadata profile = load_user_profile() or {} user_name = profile.get("name", "Ramesh Kumar") user_state = profile.get("state", "Uttar Pradesh") user_district = profile.get("district", "Kanpur Dehat") history_list = [] if history: try: history_list = json.loads(history) except Exception as e: print(f"[app.py] Error parsing history: {e}") temp_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp") os.makedirs(temp_dir, exist_ok=True) if image and image.filename: image_path = os.path.join(temp_dir, image.filename) with open(image_path, "wb") as f: f.write(await image.read()) if audio and audio.filename: audio_path = os.path.join(temp_dir, audio.filename) with open(audio_path, "wb") as f: f.write(await audio.read()) async def event_generator(): # Resolve user message query check_text = message if audio_path: check_text = llm.transcribe_audio(audio_path, message) # Emergency safety bypass router is_emergency, warning_msg = contacts.check_emergency_query(check_text) if is_emergency: yield warning_msg # Cleanup temp files if image_path and os.path.exists(image_path): try: os.remove(image_path) except: pass if audio_path and os.path.exists(audio_path): try: os.remove(audio_path) except: pass return # Construct language instruction if lang == "hi": lang_instruction = "You MUST write your entire response in Hindi language using Devanagari script (рд╣рд┐рдиреНрджреА)." elif lang == "hinglish": lang_instruction = "You MUST write your entire response in Hinglish (Hindi language written in Roman/Latin script using English alphabetic characters, e.g., 'Aloo ki kheti ke liye dhyan dein...')." else: lang_instruction = "You MUST write your entire response in English." # Local agricultural RAG context retrieval grounding_text, sources = rag.retrieve_guides(check_text, crop, lang) if grounding_text: source_cite = "Sources used: " + ", ".join(sources) system_prompt = ( f"You are Kisan-Sathi, a friendly agricultural expert helping {user_name} in {user_district}, {user_state}.\n" f"{lang_instruction}\n" f"Here is verified agricultural guide context regarding the question:\n{grounding_text}\n" f"Provide a short, direct answer in a friendly, conversational tone. Ground your answer in this context.\n" f"CRITICAL: Keep your response extremely short, crisp, and precise (maximum 120-150 words, or 3-4 bullet points max). Do not write a long essay or generic introduction. Focus only on direct, actionable advice.\n" f"Cite the source at the very end as: '{source_cite}'." ) else: system_prompt = ( f"You are Kisan-Sathi, a friendly agricultural expert helping {user_name} in {user_district}, {user_state}.\n" f"{lang_instruction}\n" f"Advise them on {crop} crop management using best practices for {user_district}, {user_state}.\n" f"CRITICAL: Keep your response extremely short, crisp, and precise (maximum 120-150 words, or 3-4 bullet points max). Do not write a long essay or generic introduction. Focus only on direct, actionable advice.\n" f"Suggest contacting a local agricultural officer for specific localized chemical treatments if unsure." ) # Run backend LLM generation response_stream = llm.generate( check_text, system=system_prompt, image_path=image_path, audio_path=audio_path, history=history_list, stream=True ) import re for chunk in response_stream: if chunk.startswith("JSON_OUTPUT:"): continue cleaned_chunk = re.sub(r'\|?<\|im_(?:end|start)\|>', '', chunk) yield cleaned_chunk await asyncio.sleep(0.01) # Clean up temp files if image_path and os.path.exists(image_path): try: os.remove(image_path) except: pass if audio_path and os.path.exists(audio_path): try: os.remove(audio_path) except: pass return StreamingResponse(event_generator(), media_type="text/plain") # Fallback Gradio interface def gradio_chat_respond(message, history): formatted_history = [] if history: for turn in history: if isinstance(turn, dict): formatted_history.append(turn) elif isinstance(turn, (list, tuple)) and len(turn) == 2: formatted_history.append({"role": "user", "content": turn[0]}) formatted_history.append({"role": "assistant", "content": turn[1]}) response_stream = llm.generate( message, system="You are Kisan-Sathi, a friendly agricultural expert.", history=formatted_history, stream=True ) response_accumulator = "" for chunk in response_stream: if chunk.startswith("JSON_OUTPUT:"): continue response_accumulator += chunk yield response_accumulator demo = gr.ChatInterface( gradio_chat_respond, title="рдХрд┐рд╕рд╛рди-рд╕рд╛рдереА (Kisan-Sathi) Fallback", description="Ask Sathi anything about farming, or use the custom home page at http://localhost:7860/" ) app = gr.mount_gradio_app(app, demo, path="/gradio") if __name__ == "__main__": import uvicorn uvicorn.run("app:app", host="0.0.0.0", port=7860)