""" KhidmatAI — AI Service Orchestrator for Informal Economy """ import streamlit as st import json, time, os, sys from datetime import datetime from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from agents import intent as intent_agent from agents import discovery as discovery_agent from agents import recommendation as recommendation_agent from agents import booking as booking_agent # ════════════════════════════════════════════════════════════════════ # PAGE CONFIG # ════════════════════════════════════════════════════════════════════ st.set_page_config(page_title="KhidmatAI", page_icon="🔧", layout="centered") st.markdown(""" """, unsafe_allow_html=True) # ════════════════════════════════════════════════════════════════════ # SESSION STATE # ════════════════════════════════════════════════════════════════════ for k, v in [("result", None), ("trace_log", None)]: if k not in st.session_state: st.session_state[k] = v # ════════════════════════════════════════════════════════════════════ # HERO # ════════════════════════════════════════════════════════════════════ st.markdown("""
🤖 Google Antigravity · Gemini API · Streamlit

🔧 KhidmatAI

AI Service Orchestrator for Pakistan's Informal Economy
Type your request in Urdu · Roman Urdu · English

""", unsafe_allow_html=True) # ════════════════════════════════════════════════════════════════════ # API KEY STATUS BANNER # ════════════════════════════════════════════════════════════════════ api_key = os.environ.get("GEMINI_API_KEY", "GEMINI_API_KEY").strip() key_valid = bool(api_key and api_key != "YOUR_GEMINI_API_KEY_HERE" and len(api_key) > 20) if not key_valid: st.markdown("""

⚠️ Gemini API Key Not Set — Running in Demo Mode

The app still works using a rule-based parser, but Gemini AI gives better results.

To enable full AI mode:

1️⃣ Get a free key at aistudio.google.com/apikey
2️⃣ In your HuggingFace Space → Settings → Variables and Secrets
3️⃣ Click New secret → Name: GEMINI_API_KEY → paste your key → Save
4️⃣ Restart the Space (Settings → Factory reboot)

""", unsafe_allow_html=True) else: st.success("✅ Gemini API key detected — AI mode active") # ════════════════════════════════════════════════════════════════════ # EXAMPLE BUTTONS # ════════════════════════════════════════════════════════════════════ st.markdown("**Try an example:**") examples = [ "Mujhe kal subah G-13 mein AC technician chahiye", "I need a plumber in F-10 today", "G-9 mein electrician chahiye aaj evening", "Maths tutor chahiye G-11 mein kal", "Home maid service chahiye F-7 mein", "Driver chahiye kal airport ke liye G-10 se", ] cols = st.columns(3) selected_example = None for i, ex in enumerate(examples): if cols[i % 3].button(f"💬 {ex[:28]}…", key=f"ex_{i}", use_container_width=True): selected_example = ex # ════════════════════════════════════════════════════════════════════ # INPUT # ════════════════════════════════════════════════════════════════════ user_input = st.text_area( "Request:", value=selected_example or st.session_state.get("last_input", ""), height=90, placeholder="e.g. Mujhe kal subah G-13 mein AC technician chahiye", label_visibility="collapsed", ) c1, c2 = st.columns([3, 1]) run_btn = c1.button("🚀 Find & Book Service", type="primary", use_container_width=True) clear_btn = c2.button("🔄 Clear", use_container_width=True) if clear_btn: st.session_state.result = None st.session_state.trace_log = None st.rerun() # ════════════════════════════════════════════════════════════════════ # PIPELINE # ════════════════════════════════════════════════════════════════════ def agent_card_running(title, desc): st.markdown(f"""
⏳ {title}
{desc}
""", unsafe_allow_html=True) def agent_card_done(title, body, ms, mode=None): mode_pill = "" if mode == "gemini": mode_pill = 'Gemini AI' elif mode in ("rule_based_fallback", "rules"): mode_pill = 'Rule-based' st.markdown(f"""
✅ {title}{mode_pill} ({ms}ms)
{body}
""", unsafe_allow_html=True) def agent_card_error(title, err): st.markdown(f"""
❌ {title} Failed
{err}
""", unsafe_allow_html=True) def run_pipeline(message: str): trace = [] st.markdown("---") st.markdown("### 🤖 Agent Pipeline") # Agent 1 — Intent ph1 = st.empty() with ph1: agent_card_running("Agent 1 — Intent Agent", "Parsing your request...") t0 = time.time() try: intent = intent_agent.run(message) ms = int((time.time()-t0)*1000) warning = intent.pop("_warning", None) mode = intent.get("mode", "rules") trace.append({"agent":"IntentAgent","step":1, "input":{"message":message},"output":intent, "duration_ms":ms, "reasoning":f"Language={intent.get('language')}, service={intent.get('service_type')}, " f"location={intent.get('location')}, time={intent.get('time')} [mode={mode}]"}) ph1.empty() agent_card_done( "Agent 1 — Intent Agent", f"🏷️ {intent.get('service_type','?')}  |  " f"📍 {intent.get('location','?')}  |  " f"🕐 {intent.get('time','?')}  |  " f"🌐 {intent.get('language','?')}", ms, mode ) if warning: st.caption(f"ℹ️ {warning}") except Exception as e: ph1.empty() agent_card_error("Agent 1 — Intent Agent", str(e)) return None, trace # Agent 2 — Discovery ph2 = st.empty() with ph2: agent_card_running("Agent 2 — Discovery Agent", "Searching nearby providers...") t0 = time.time() try: disc = discovery_agent.run(intent) ms = int((time.time()-t0)*1000) trace.append({"agent":"DiscoveryAgent","step":2, "input":intent,"output":{"total_found":disc["total_found"]}, "duration_ms":ms, "reasoning":f"Filtered providers.json for '{intent.get('service_type')}' near {disc['user_location']}. " f"Found {disc['total_found']} provider(s). Distance via haversine formula."}) ph2.empty() agent_card_done( "Agent 2 — Discovery Agent", f"Found {disc['total_found']} provider(s) for " f"{intent.get('service_type')} near {disc['user_location']}.", ms ) except Exception as e: ph2.empty() agent_card_error("Agent 2 — Discovery Agent", str(e)) return None, trace if disc["total_found"] == 0: st.warning("😕 No providers found. Try a different service or location.") return None, trace # Agent 3 — Recommendation ph3 = st.empty() with ph3: agent_card_running("Agent 3 — Recommendation Agent", "Ranking providers...") t0 = time.time() try: rec = recommendation_agent.run(disc) ms = int((time.time()-t0)*1000) trace.append({"agent":"RecommendationAgent","step":3, "input":{"providers_found":disc["total_found"]}, "output":{"selected":rec["best_provider"]["name"],"score":rec["best_provider"]["score"]}, "duration_ms":ms, "reasoning":rec["reasoning"]}) ph3.empty() agent_card_done("Agent 3 — Recommendation Agent", rec["reasoning"], ms) except Exception as e: ph3.empty() agent_card_error("Agent 3 — Recommendation Agent", str(e)) return None, trace # Agent 4 — Booking ph4 = st.empty() with ph4: agent_card_running("Agent 4 — Booking Agent", "Confirming your booking...") t0 = time.time() try: bk = booking_agent.run(rec, intent) ms = int((time.time()-t0)*1000) trace.append({"agent":"BookingAgent","step":4, "input":{"provider":rec["best_provider"]["name"]}, "output":{"booking_id":bk["booking_id"],"slot":bk["confirmed_slot"]}, "duration_ms":ms, "reasoning":f"Slot {bk['confirmed_slot']} selected. Booking ID: {bk['booking_id']}. Reminder: {bk['reminder_time']}."}) ph4.empty() agent_card_done( "Agent 4 — Booking Agent", f"Booking {bk['booking_id']} confirmed · Slot {bk['confirmed_slot']} · " f"Reminder at {bk['reminder_time']}.", ms ) except Exception as e: ph4.empty() agent_card_error("Agent 4 — Booking Agent", str(e)) return None, trace return {"intent":intent, "discovery":disc, "recommendation":rec, "booking":bk}, trace # ════════════════════════════════════════════════════════════════════ # TRIGGER # ════════════════════════════════════════════════════════════════════ if run_btn: if not user_input.strip(): st.warning("Please type a service request first.") else: st.session_state["last_input"] = user_input.strip() st.session_state.result = None st.session_state.trace_log = None result, trace = run_pipeline(user_input.strip()) st.session_state.result = result st.session_state.trace_log = trace # ════════════════════════════════════════════════════════════════════ # RESULTS # ════════════════════════════════════════════════════════════════════ if st.session_state.result: result = st.session_state.result rec = result["recommendation"] bk = result["booking"] disc = result["discovery"] st.markdown("---") tab1, tab2, tab3 = st.tabs(["📋 Providers", "✅ Booking Receipt", "🔍 Agent Trace"]) with tab1: st.markdown(f"**{disc['total_found']} provider(s) found** — ranked by distance · rating · availability") for i, p in enumerate(rec["all_ranked"]): is_best = i == 0 badge = '⭐ TOP PICK' if is_best else "" avail = "🟢 Available" if p.get("available") else "🔴 Unavailable" sw = int(p["score"]*100) st.markdown(f"""
#{i+1} {p['name']}{badge}
📍 {p['location']['area']}  ·  📏 {p['distance_km']} km  ·  ⭐ {p['rating']} ({p['total_reviews']} reviews)  ·  {avail}  ·  💰 {p.get('price_range','N/A')}
Score: {p['score']}
""", unsafe_allow_html=True) with tab2: st.markdown(f"""
BOOKING ID: {bk['booking_id']}
🎉 {bk['provider_name']}
Booking Confirmed ✓
🔧 Service{bk['service']}
📍 Location{bk['location']}
📅 Date{bk['booked_date']}
⏰ Slot{bk['confirmed_slot']}
📞 Contact{bk['provider_phone']}
🔔 Reminder set for {bk['reminder_time']} — 1 hour before appointment
""", unsafe_allow_html=True) with tab3: st.markdown('
Full Agent Decision Log
', unsafe_allow_html=True) for step in st.session_state.trace_log: with st.expander(f"Step {step['step']} — {step['agent']} ({step['duration_ms']}ms)"): st.markdown(f"**Reasoning:** {step['reasoning']}") ca, cb = st.columns(2) with ca: st.markdown("**Input**"); st.json(step["input"]) with cb: st.markdown("**Output**"); st.json(step["output"]) st.markdown("**Full JSON:**") st.json(st.session_state.trace_log) # ════════════════════════════════════════════════════════════════════ # FOOTER # ════════════════════════════════════════════════════════════════════ st.markdown("---") st.markdown( "
" "KhidmatAI · Google Antigravity · Gemini API · Streamlit · Hackathon 2025" "
", unsafe_allow_html=True )