"""
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"""""", 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"""""", 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('', 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
)