Spaces:
Runtime error
Runtime error
Aryan Mishra commited on
Commit ·
d5eac89
1
Parent(s): 5f1c2eb
Add Streamlit frontend and Docker service
Browse filesIntroduce a new Streamlit UI with a home dashboard and single-text prediction page, plus an API client for health/info/predict/batch endpoints. Add a dedicated Streamlit Dockerfile and update docker-compose to run the new `streamlit` service on port 8501 with `API_BASE_URL` wiring. Also add Streamlit-related Python dependencies needed by the frontend.
- .DS_Store +0 -0
- config/docker/Dockerfile.streamlit +12 -0
- config/docker/docker-compose.yml +5 -5
- requirements.txt +6 -0
- streamlit_app/Home.py +40 -0
- streamlit_app/api/client.py +55 -0
- streamlit_app/pages/1_Predict.py +44 -0
.DS_Store
CHANGED
|
Binary files a/.DS_Store and b/.DS_Store differ
|
|
|
config/docker/Dockerfile.streamlit
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY streamlit_app/ ./streamlit_app/
|
| 9 |
+
|
| 10 |
+
EXPOSE 8501
|
| 11 |
+
|
| 12 |
+
CMD ["streamlit", "run", "streamlit_app/Home.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
config/docker/docker-compose.yml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
| 1 |
version: '3.8'
|
| 2 |
|
| 3 |
services:
|
| 4 |
-
|
| 5 |
build:
|
| 6 |
context: ../../
|
| 7 |
-
dockerfile: config/docker/Dockerfile.
|
| 8 |
-
container_name: absa-
|
| 9 |
ports:
|
| 10 |
-
- "
|
| 11 |
environment:
|
| 12 |
-
-
|
| 13 |
depends_on:
|
| 14 |
- api
|
| 15 |
|
|
|
|
| 1 |
version: '3.8'
|
| 2 |
|
| 3 |
services:
|
| 4 |
+
streamlit:
|
| 5 |
build:
|
| 6 |
context: ../../
|
| 7 |
+
dockerfile: config/docker/Dockerfile.streamlit
|
| 8 |
+
container_name: absa-streamlit
|
| 9 |
ports:
|
| 10 |
+
- "8501:8501"
|
| 11 |
environment:
|
| 12 |
+
- API_BASE_URL=http://api:8000
|
| 13 |
depends_on:
|
| 14 |
- api
|
| 15 |
|
requirements.txt
CHANGED
|
@@ -26,3 +26,9 @@ pytest==8.2.0
|
|
| 26 |
httpx==0.27.0
|
| 27 |
prometheus-fastapi-instrumentator==7.0.0
|
| 28 |
python-multipart==0.0.9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
httpx==0.27.0
|
| 27 |
prometheus-fastapi-instrumentator==7.0.0
|
| 28 |
python-multipart==0.0.9
|
| 29 |
+
|
| 30 |
+
# Streamlit Frontend
|
| 31 |
+
streamlit>=1.31.0
|
| 32 |
+
httpx>=0.27.0
|
| 33 |
+
pandas>=2.0.0
|
| 34 |
+
plotly>=5.18.0
|
streamlit_app/Home.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
|
| 3 |
+
st.set_page_config(
|
| 4 |
+
page_title="Multilingual ABSA Platform",
|
| 5 |
+
page_icon="🌍",
|
| 6 |
+
layout="wide",
|
| 7 |
+
initial_sidebar_state="expanded"
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
st.title("Welcome to Multilingual ABSA Platform 🌍")
|
| 12 |
+
st.markdown("### Aspect-Based Sentiment Analysis for English, Hindi, and Hinglish")
|
| 13 |
+
|
| 14 |
+
st.info("This is a modern, responsive Streamlit dashboard for enterprise AI inference.")
|
| 15 |
+
|
| 16 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 17 |
+
with col1:
|
| 18 |
+
st.metric(label="System Health", value="Healthy", delta="🟢 API Online")
|
| 19 |
+
with col2:
|
| 20 |
+
st.metric(label="Supported Languages", value="3", delta="En, Hi, Hinglish")
|
| 21 |
+
with col3:
|
| 22 |
+
st.metric(label="Active Models", value="ONNX", delta="1.0.0")
|
| 23 |
+
with col4:
|
| 24 |
+
st.metric(label="Recent Predictions", value="1,245", delta="+12 today")
|
| 25 |
+
|
| 26 |
+
st.markdown("---")
|
| 27 |
+
st.markdown("### Quick Actions")
|
| 28 |
+
colA, colB, colC = st.columns(3)
|
| 29 |
+
with colA:
|
| 30 |
+
if st.button("🔍 Single Prediction", use_container_width=True):
|
| 31 |
+
st.switch_page("pages/1_Predict.py")
|
| 32 |
+
with colB:
|
| 33 |
+
if st.button("📁 Batch Processing", use_container_width=True):
|
| 34 |
+
st.switch_page("pages/2_Batch_Prediction.py")
|
| 35 |
+
with colC:
|
| 36 |
+
if st.button("📊 View Analytics", use_container_width=True):
|
| 37 |
+
st.switch_page("pages/3_Analytics.py")
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
main()
|
streamlit_app/api/client.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import httpx
|
| 2 |
+
import streamlit as st
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000")
|
| 6 |
+
|
| 7 |
+
class APIClient:
|
| 8 |
+
def __init__(self, base_url: str = API_BASE_URL):
|
| 9 |
+
self.base_url = base_url
|
| 10 |
+
self.client = httpx.Client(base_url=self.base_url, timeout=30.0)
|
| 11 |
+
|
| 12 |
+
def get_health(self):
|
| 13 |
+
try:
|
| 14 |
+
response = self.client.get("/health")
|
| 15 |
+
response.raise_for_status()
|
| 16 |
+
return response.json()
|
| 17 |
+
except httpx.HTTPError as e:
|
| 18 |
+
st.error(f"API Health Check Failed: {e}")
|
| 19 |
+
return None
|
| 20 |
+
|
| 21 |
+
def get_info(self):
|
| 22 |
+
try:
|
| 23 |
+
response = self.client.get("/info")
|
| 24 |
+
response.raise_for_status()
|
| 25 |
+
return response.json()
|
| 26 |
+
except httpx.HTTPError as e:
|
| 27 |
+
return None
|
| 28 |
+
|
| 29 |
+
def predict(self, text: str, language: str = "auto"):
|
| 30 |
+
try:
|
| 31 |
+
payload = {"text": text, "language": language}
|
| 32 |
+
response = self.client.post("/predict", json=payload)
|
| 33 |
+
response.raise_for_status()
|
| 34 |
+
return response.json()
|
| 35 |
+
except httpx.HTTPError as e:
|
| 36 |
+
st.error(f"Prediction API Error: {e}")
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
def batch_predict(self, file):
|
| 40 |
+
try:
|
| 41 |
+
files = {"file": (file.name, file, "text/csv")}
|
| 42 |
+
response = self.client.post("/batch", files=files)
|
| 43 |
+
response.raise_for_status()
|
| 44 |
+
return response.json()
|
| 45 |
+
except httpx.HTTPError as e:
|
| 46 |
+
st.error(f"Batch API Error: {e}")
|
| 47 |
+
return None
|
| 48 |
+
|
| 49 |
+
def get_batch_status(self, job_id: str):
|
| 50 |
+
try:
|
| 51 |
+
response = self.client.get(f"/status/{job_id}")
|
| 52 |
+
response.raise_for_status()
|
| 53 |
+
return response.json()
|
| 54 |
+
except httpx.HTTPError as e:
|
| 55 |
+
return None
|
streamlit_app/pages/1_Predict.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from api.client import APIClient
|
| 4 |
+
|
| 5 |
+
st.set_page_config(page_title="Single Prediction", page_icon="🔍", layout="wide")
|
| 6 |
+
|
| 7 |
+
st.title("Single Text Prediction 🔍")
|
| 8 |
+
st.markdown("Analyze sentiment for individual reviews across English, Hindi, and Hinglish.")
|
| 9 |
+
|
| 10 |
+
client = APIClient()
|
| 11 |
+
|
| 12 |
+
with st.container():
|
| 13 |
+
col1, col2 = st.columns([3, 1])
|
| 14 |
+
with col1:
|
| 15 |
+
text_input = st.text_area("Enter review text:", height=150, placeholder="E.g., The battery life is amazing but the screen is too dim.")
|
| 16 |
+
with col2:
|
| 17 |
+
language = st.selectbox("Language", ["auto", "en", "hi", "hinglish"], index=0)
|
| 18 |
+
predict_button = st.button("Analyze Sentiment", type="primary", use_container_width=True)
|
| 19 |
+
|
| 20 |
+
if predict_button and text_input:
|
| 21 |
+
with st.spinner("Analyzing..."):
|
| 22 |
+
result = client.predict(text_input, language)
|
| 23 |
+
if result:
|
| 24 |
+
st.success(f"Processed in {result.get('processing_time_ms', 0)} ms")
|
| 25 |
+
st.subheader("Results")
|
| 26 |
+
|
| 27 |
+
aspects = result.get("aspects", [])
|
| 28 |
+
if not aspects:
|
| 29 |
+
st.info("No aspects detected in this text.")
|
| 30 |
+
else:
|
| 31 |
+
for idx, asp in enumerate(aspects):
|
| 32 |
+
sentiment = asp.get("sentiment", "unknown")
|
| 33 |
+
color = "green" if sentiment == "positive" else "red" if sentiment == "negative" else "gray"
|
| 34 |
+
|
| 35 |
+
st.markdown(
|
| 36 |
+
f"""
|
| 37 |
+
<div style="padding:15px; border-radius:10px; border:1px solid #ddd; margin-bottom:10px; border-left: 5px solid {color};">
|
| 38 |
+
<h4>Aspect: {asp.get('aspect', 'N/A')}</h4>
|
| 39 |
+
<p><strong>Sentiment:</strong> <span style="color:{color};">{sentiment.upper()}</span></p>
|
| 40 |
+
<p><strong>Confidence:</strong> {asp.get('confidence', 0):.2f}</p>
|
| 41 |
+
</div>
|
| 42 |
+
""",
|
| 43 |
+
unsafe_allow_html=True
|
| 44 |
+
)
|