MohitGupta41 commited on
Commit
0dd614a
·
1 Parent(s): 1acb85e

Initial Project commit

Browse files
Files changed (4) hide show
  1. .env +1 -0
  2. Dockerfile +13 -0
  3. app.py +204 -0
  4. requirements.txt +3 -0
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ BACKEND_URL = https://mohitg012-bi-assistant-backend.hf.space/
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ RUN apt-get update && apt-get install -y --no-install-recommends \
4
+ libgl1 libglib2.0-0 && \
5
+ rm -rf /var/lib/apt/lists/*
6
+
7
+ WORKDIR /app
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ COPY app.py .
12
+ ENV PORT=7860
13
+ CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
app.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, io, base64, json, time
2
+ from typing import Optional, Tuple
3
+ import requests
4
+ from PIL import Image, ImageOps, ImageDraw
5
+ import streamlit as st
6
+
7
+ # -----------------------
8
+ # Config
9
+ # -----------------------
10
+ BACKEND = os.getenv("BACKEND_URL", "http://localhost:8000").rstrip("/")
11
+ TIMEOUT = 60
12
+
13
+ st.set_page_config(page_title="Realtime BI Assistant (Frontend)", layout="centered")
14
+
15
+ # -----------------------
16
+ # Helpers
17
+ # -----------------------
18
+ def ping_backend() -> Tuple[bool, Optional[dict]]:
19
+ try:
20
+ r = requests.get(f"{BACKEND}/", timeout=10)
21
+ return r.ok, (r.json() if r.ok else None)
22
+ except Exception as e:
23
+ return False, {"error": str(e)}
24
+
25
+ def pil_from_upload(file) -> Optional[Image.Image]:
26
+ try:
27
+ return Image.open(file).convert("RGB")
28
+ except Exception:
29
+ return None
30
+
31
+ def compress_and_b64(img: Image.Image, max_side: int = 1280, quality: int = 85) -> str:
32
+ img = ImageOps.exif_transpose(img) # respect EXIF orientation
33
+ w, h = img.size
34
+ scale = max(w, h) / max_side if max(w, h) > max_side else 1.0
35
+ if scale > 1.0:
36
+ img = img.resize((int(w/scale), int(h/scale)))
37
+ buf = io.BytesIO()
38
+ img.save(buf, format="JPEG", quality=quality, optimize=True)
39
+ return base64.b64encode(buf.getvalue()).decode()
40
+
41
+ def draw_bbox(img: Image.Image, bbox: list[int], color=(0, 255, 0), width: int = 4) -> Image.Image:
42
+ out = img.copy()
43
+ draw = ImageDraw.Draw(out)
44
+ x1, y1, x2, y2 = bbox
45
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=width)
46
+ return out
47
+
48
+ def post_json(path: str, payload: dict) -> requests.Response:
49
+ return requests.post(f"{BACKEND}{path}", json=payload, timeout=TIMEOUT)
50
+
51
+ def post_multipart(path: str, files: dict, params: dict) -> requests.Response:
52
+ return requests.post(f"{BACKEND}{path}", files=files, params=params, timeout=TIMEOUT)
53
+
54
+ # -----------------------
55
+ # UI
56
+ # -----------------------
57
+ st.title("Realtime BI Assistant (Demo Frontend)")
58
+ st.caption("Face upsert/identify + BI Q&A (text) via your FastAPI backend")
59
+
60
+ ok, info = ping_backend()
61
+ status_col, url_col = st.columns([1,3])
62
+ with status_col:
63
+ st.metric("Backend", "Online ✅" if ok else "Offline ❌")
64
+ with url_col:
65
+ st.code(BACKEND, language="text")
66
+
67
+ if not ok and info:
68
+ st.warning(f"Backend unreachable: {info}")
69
+
70
+ # Persist chosen user
71
+ if "user_name" not in st.session_state:
72
+ st.session_state.user_name = "mohit"
73
+
74
+ # -----------------------
75
+ # 1) Enroll / Upsert face
76
+ # -----------------------
77
+ with st.expander("1) Enroll / Upsert face (optional)", expanded=False):
78
+ st.session_state.user_name = st.text_input("User name", value=st.session_state.user_name, key="user_name_input")
79
+ c1, c2 = st.columns(2)
80
+ with c1:
81
+ upload_img = st.file_uploader("Upload a face image (jpg/png)", type=["jpg","jpeg","png"])
82
+ with c2:
83
+ cam_img = st.camera_input("Or capture from camera")
84
+
85
+ chosen = None
86
+ if cam_img is not None:
87
+ chosen = pil_from_upload(cam_img)
88
+ elif upload_img is not None:
89
+ chosen = pil_from_upload(upload_img)
90
+
91
+ if st.button("Upsert to local index"):
92
+ if not chosen:
93
+ st.error("Please provide an image (upload or camera).")
94
+ elif not st.session_state.user_name.strip():
95
+ st.error("Please enter a user name.")
96
+ else:
97
+ buf = io.BytesIO()
98
+ chosen.save(buf, format="JPEG", quality=90)
99
+ buf.seek(0)
100
+ try:
101
+ with st.spinner("Upserting…"):
102
+ resp = post_multipart(
103
+ "/index/upsert_image",
104
+ files={"image": ("face.jpg", buf, "image/jpeg")},
105
+ params={"user": st.session_state.user_name.strip()},
106
+ )
107
+ if resp.ok:
108
+ st.success("Face vector upserted ✅")
109
+ st.json(resp.json())
110
+ else:
111
+ st.error(f"Upsert failed: {resp.status_code}")
112
+ st.text(resp.text)
113
+ except Exception as e:
114
+ st.error(f"Request error: {e}")
115
+
116
+ # -----------------------
117
+ # 2) Identify user
118
+ # -----------------------
119
+ with st.expander("2) Identify from image (optional)", expanded=False):
120
+ col_u, col_c = st.columns(2)
121
+ with col_u:
122
+ test_upload = st.file_uploader("Upload test image", type=["jpg","jpeg","png"], key="test_upload")
123
+ with col_c:
124
+ test_cam = st.camera_input("Or capture from camera", key="test_cam")
125
+
126
+ test_img = None
127
+ src_lbl = None
128
+ if test_cam is not None:
129
+ test_img, src_lbl = pil_from_upload(test_cam), "camera"
130
+ elif test_upload is not None:
131
+ test_img, src_lbl = pil_from_upload(test_upload), "upload"
132
+
133
+ if st.button("Identify"):
134
+ if test_img is None:
135
+ st.warning("Please provide an image first.")
136
+ else:
137
+ b64 = compress_and_b64(test_img)
138
+ try:
139
+ with st.spinner("Identifying…"):
140
+ r = post_json("/identify", {"image_b64": b64, "top_k": 3})
141
+ if not r.ok:
142
+ st.error(f"Identify failed: {r.status_code}")
143
+ st.text(r.text)
144
+ else:
145
+ data = r.json()
146
+ st.success(f"Decision: {data.get('decision')} | best_score={data.get('best_score'):.3f} | margin={data.get('margin'):.3f}")
147
+ bbox = data.get("bbox")
148
+ if bbox and isinstance(bbox, list) and len(bbox) == 4:
149
+ st.caption("Detected face:")
150
+ st.image(draw_bbox(test_img, bbox), use_column_width=True)
151
+ else:
152
+ st.image(test_img, use_column_width=True)
153
+
154
+ if data.get("topk"):
155
+ st.caption("Top-k candidates:")
156
+ st.json(data["topk"])
157
+ except Exception as e:
158
+ st.error(f"Request error: {e}")
159
+
160
+ # -----------------------
161
+ # 3) Ask a BI question
162
+ # -----------------------
163
+ st.subheader("3) Ask a BI question")
164
+ bi_col, filt_col = st.columns([3,1])
165
+
166
+ with bi_col:
167
+ default_q = "What is revenue for region NCR on 2025-09-06?"
168
+ q_text = st.text_area("Your question", value=default_q, height=100)
169
+
170
+ with filt_col:
171
+ region = st.text_input("region", value="NCR")
172
+ day = st.text_input("day (YYYY-MM-DD)", value="2025-09-06")
173
+
174
+ if st.button("Ask"):
175
+ payload = {
176
+ "user_id": st.session_state.user_name or None,
177
+ "text": q_text.strip(),
178
+ "filters": {"region": region.strip(), "day": day.strip()}
179
+ }
180
+ try:
181
+ with st.spinner("Querying…"):
182
+ r = post_json("/query", payload)
183
+ if r.ok:
184
+ resp = r.json()
185
+ st.success(resp.get("answer_text", ""))
186
+ if resp.get("metrics"):
187
+ with st.expander("Metrics", expanded=True):
188
+ st.json(resp["metrics"])
189
+ if resp.get("citations"):
190
+ with st.expander("Citations", expanded=False):
191
+ st.json(resp["citations"])
192
+ if resp.get("chart_refs"):
193
+ with st.expander("Charts", expanded=False):
194
+ st.json(resp["chart_refs"])
195
+ if "uncertainty" in resp:
196
+ st.caption(f"Uncertainty: {resp['uncertainty']:.2f}")
197
+ else:
198
+ st.error(f"Backend error: {r.status_code}")
199
+ st.text(r.text)
200
+ except Exception as e:
201
+ st.error(f"Request error: {e}")
202
+
203
+ st.markdown("---")
204
+ st.caption("Tip: Set `BACKEND_URL` in your Space Variables to point this UI to your FastAPI backend.")
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ streamlit
2
+ requests
3
+ Pillow