Spaces:
Sleeping
Sleeping
| """ | |
| Streamlit frontend for the BottleCapAI text summarization service. | |
| Calls the FastAPI backend for the summarization endpoint. | |
| """ | |
| import os | |
| import sentry_sdk | |
| import streamlit as st | |
| import requests | |
| from dotenv import load_dotenv | |
| from pathlib import Path | |
| load_dotenv() | |
| # Monitor errors using Sentry | |
| sentry_sdk.init( | |
| dsn=os.environ.get("SENTRY_DSN"), | |
| send_default_pii=True, | |
| ) | |
| sentry_sdk.set_tag("service", "frontend") | |
| # Backend API (FastAPI) - same host when running in Docker, or localhost:8000 for local dev | |
| API_URL = "http://localhost:8000" | |
| LOGO_PATH = Path(__file__).resolve().parent / "img" / "logo.png" | |
| FAVICON_PATH = Path(__file__).resolve().parent / "img" / "favicon.svg" | |
| st.set_page_config( | |
| page_title="BottleCapAI Text Summarizer", | |
| page_icon=str(FAVICON_PATH) if FAVICON_PATH.exists() else "📝", | |
| layout="centered", | |
| ) | |
| # --- Header with Bottlecap logo and title (centered) --- | |
| _, center_col, _ = st.columns([1, 2, 1]) | |
| with center_col: | |
| if LOGO_PATH.exists(): | |
| st.image(str(LOGO_PATH), width="stretch") | |
| else: | |
| st.caption("(Logo: img/logo.png)") | |
| st.title("Text Summarizer") | |
| # --- Backend health check (sidebar) --- | |
| try: | |
| health = requests.get(f"{API_URL}/health", timeout=5) | |
| if health.status_code == 200: | |
| data = health.json() | |
| device = data.get("device", "unknown") | |
| st.sidebar.success(f"Backend online · {device.upper()}") | |
| else: | |
| st.sidebar.warning(f"Backend returned {health.status_code}") | |
| except requests.exceptions.RequestException: | |
| st.sidebar.error( | |
| "Backend offline — start the FastAPI service to use summarization." | |
| ) | |
| # --- Main interface (assignment: text in → summary out) --- | |
| st.sidebar.header("Settings") | |
| max_length = st.sidebar.slider( | |
| "Max summary length (tokens)", | |
| min_value=20, | |
| max_value=150, | |
| value=80, | |
| help="Optional: backend may use this if supported.", | |
| ) | |
| DEFAULT_TEXT = ( | |
| "The man known as Kazu, or 'King Kazu' by some fans, will stay with the second division side past his 49th birthday." | |
| "Kazuyoshi first played for Brazilian side Santos in 1986, so his deal will see his career span over 30 years." | |
| '"I\'m thankful to the club staff and supporters who always offer me support," said Miura, who scored 55 goals in 89 appearances for Japan.' | |
| '"I\'ll continue to give everything I have and strive," added the former Genoa and Dinamo Zagreb striker.' | |
| "Perhaps unsurprisingly, Miura holds the record as the oldest scorer in Japanese football - a winner in a second division match four months after his 48th birthday." | |
| "He was particularly prolific in guiding Japan to the 1998 World Cup, scoring 14 goals in qualifying, and last played for the national side in 2000." | |
| "His career, which started when he moved to Brazil to play youth football aged 15, is one of the longest in football history." | |
| "Last week ex-England striker Teddy Sheringham registered himself as a player for Stevenage - where he is manager - at the age of 49 but opted out of playing in a local cup competition." | |
| ) | |
| input_text = st.text_area( | |
| "Enter English text to summarize", | |
| value=DEFAULT_TEXT, | |
| placeholder="Paste or type the text you want summarized…", | |
| height=200, | |
| ) | |
| if "summary_result" not in st.session_state: | |
| st.session_state.summary_result = ( | |
| None # {"summary": str, "used_device": str} or None | |
| ) | |
| if st.button("Summarize", type="primary"): | |
| if not input_text.strip(): | |
| st.warning("Please enter some text first.") | |
| st.session_state.summary_result = None | |
| else: | |
| with st.spinner("Summarizing…"): | |
| try: | |
| payload = { | |
| "text": input_text.strip(), | |
| "max_length": max_length, | |
| } | |
| response = requests.post( | |
| f"{API_URL}/summarize", | |
| json=payload, | |
| timeout=60, | |
| ) | |
| if response.status_code == 200: | |
| data = response.json() | |
| summary = data.get("summary", data.get("response", str(data))) | |
| st.session_state.summary_result = { | |
| "summary": summary, | |
| "used_device": data.get("used_device"), | |
| } | |
| elif response.status_code == 507: | |
| st.session_state.summary_result = None | |
| st.error( | |
| "Resource limit exceeded. Try shorter input or lower max length." | |
| ) | |
| else: | |
| st.session_state.summary_result = None | |
| st.error(f"Error {response.status_code}: {response.text}") | |
| except requests.exceptions.ConnectionError: | |
| st.session_state.summary_result = None | |
| st.error( | |
| "Could not reach the backend. Start the FastAPI server (e.g. on port 8000)." | |
| ) | |
| except Exception as e: | |
| st.session_state.summary_result = None | |
| st.exception(e) | |
| if st.session_state.summary_result: | |
| st.subheader("Summary") | |
| st.write(st.session_state.summary_result["summary"]) | |
| if st.session_state.summary_result.get("used_device"): | |
| st.caption( | |
| f"Generated on: {st.session_state.summary_result['used_device'].upper()}" | |
| ) | |
| st.divider() | |
| st.caption( | |
| "Created by [Jan Rudolf](https://www.janrudolf.com) for [BottleCap AI](https://www.bottlecapai.com) in 2026" | |
| ) | |