Spaces:
Running
Running
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +127 -31
src/streamlit_app.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
import streamlit as st
|
2 |
import requests
|
3 |
import time
|
|
|
|
|
4 |
|
5 |
-
#
|
6 |
-
API_KEY = st.secrets["API_KEY"]
|
7 |
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
|
8 |
|
9 |
API_URLS = {
|
@@ -11,48 +13,142 @@ API_URLS = {
|
|
11 |
"Sentiment": "https://api-inference.huggingface.co/models/finiteautomata/bertweet-base-sentiment-analysis"
|
12 |
}
|
13 |
|
|
|
14 |
def query(api_url, payload):
|
|
|
15 |
try:
|
16 |
-
|
17 |
-
if
|
18 |
-
return {"error": f"HTTP {
|
19 |
-
return
|
20 |
-
except
|
21 |
-
return {"error":
|
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
st.set_page_config(page_title="NLP Toolkit", page_icon="🧠", layout="centered")
|
24 |
-
st.
|
25 |
-
st.write("Summarization & Sentiment Analysis
|
26 |
|
27 |
tab1, tab2 = st.tabs(["📄 Summarizer", "📝 Sentiment Analysis"])
|
28 |
|
|
|
29 |
with tab1:
|
30 |
-
text = st.text_area("Enter text to summarize:", height=
|
31 |
-
if st.button("Summarize"):
|
32 |
if not text.strip():
|
33 |
-
st.warning("
|
34 |
else:
|
35 |
with st.spinner("Generating summary..."):
|
36 |
-
time.sleep(
|
37 |
-
|
38 |
-
if "error" in
|
39 |
-
st.error(
|
|
|
|
|
|
|
40 |
else:
|
41 |
-
st.
|
42 |
-
st.write(res[0]['summary_text'])
|
43 |
|
|
|
44 |
with tab2:
|
45 |
-
|
46 |
-
if st.button("Analyze Sentiment"):
|
47 |
-
if not
|
48 |
-
st.warning("
|
49 |
else:
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
if "error" in res:
|
54 |
-
st.error(res["error"])
|
55 |
else:
|
56 |
-
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import requests
|
3 |
import time
|
4 |
+
import re
|
5 |
+
import pandas as pd
|
6 |
|
7 |
+
# ==== CONFIG ====
|
8 |
+
API_KEY = st.secrets["API_KEY"] # Reads from Hugging Face / .streamlit/secrets.toml
|
9 |
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
|
10 |
|
11 |
API_URLS = {
|
|
|
13 |
"Sentiment": "https://api-inference.huggingface.co/models/finiteautomata/bertweet-base-sentiment-analysis"
|
14 |
}
|
15 |
|
16 |
+
# ==== HELPERS ====
|
17 |
def query(api_url, payload):
|
18 |
+
"""Call Hugging Face inference API and return JSON or {'error':...}"""
|
19 |
try:
|
20 |
+
resp = requests.post(api_url, headers=HEADERS, json=payload, timeout=60)
|
21 |
+
if resp.status_code != 200:
|
22 |
+
return {"error": f"HTTP {resp.status_code}: {resp.text}"}
|
23 |
+
return resp.json()
|
24 |
+
except requests.exceptions.RequestException as e:
|
25 |
+
return {"error": f"Request failed: {e}"}
|
26 |
|
27 |
+
def split_sentences(text: str):
|
28 |
+
"""
|
29 |
+
Split text into sentences/phrases by ., ?, !, ;, :, comma and newlines.
|
30 |
+
Keeps things simple and avoids external tokenizers.
|
31 |
+
"""
|
32 |
+
# Split on end punctuation + whitespace, OR on newline, OR on comma followed by space
|
33 |
+
parts = re.split(r'(?<=[.!?;:])\s+|\n+|(?<=,)\s+', text.strip())
|
34 |
+
# Filter empties and strip
|
35 |
+
return [p.strip() for p in parts if p and p.strip()]
|
36 |
+
|
37 |
+
def extract_scores_from_api_response(res):
|
38 |
+
"""
|
39 |
+
Accepts HF response shapes like:
|
40 |
+
- [{'label': 'NEG', 'score': 0.86}, ...] OR
|
41 |
+
- [[{'label': 'NEG','score':...}, ...]] (nested list)
|
42 |
+
Returns a dict with numeric neg, neu, pos (floats 0..1) or None on unexpected.
|
43 |
+
"""
|
44 |
+
sentiments = None
|
45 |
+
if isinstance(res, list):
|
46 |
+
# nested list (common with some HF endpoints)
|
47 |
+
if len(res) > 0 and isinstance(res[0], list):
|
48 |
+
sentiments = res[0]
|
49 |
+
# flat list
|
50 |
+
elif len(res) > 0 and isinstance(res[0], dict):
|
51 |
+
sentiments = res
|
52 |
+
if sentiments is None:
|
53 |
+
return None
|
54 |
+
|
55 |
+
neg = neu = pos = 0.0
|
56 |
+
for item in sentiments:
|
57 |
+
lab = item.get("label", "").upper()
|
58 |
+
sc = float(item.get("score", 0) or 0)
|
59 |
+
if "NEG" in lab:
|
60 |
+
neg = sc
|
61 |
+
elif "NEU" in lab:
|
62 |
+
neu = sc
|
63 |
+
elif "POS" in lab:
|
64 |
+
pos = sc
|
65 |
+
return {"neg": neg, "neu": neu, "pos": pos}
|
66 |
+
|
67 |
+
# ==== STREAMLIT UI ====
|
68 |
st.set_page_config(page_title="NLP Toolkit", page_icon="🧠", layout="centered")
|
69 |
+
st.markdown("<h1 style='text-align: center; color: cyan;'>🧠 AI NLP Toolkit</h1>", unsafe_allow_html=True)
|
70 |
+
st.write("Summarization & sentence-wise Sentiment Analysis.")
|
71 |
|
72 |
tab1, tab2 = st.tabs(["📄 Summarizer", "📝 Sentiment Analysis"])
|
73 |
|
74 |
+
# --- Summarizer (unchanged) ---
|
75 |
with tab1:
|
76 |
+
text = st.text_area("Enter text to summarize:", height=220)
|
77 |
+
if st.button("Summarize📝"):
|
78 |
if not text.strip():
|
79 |
+
st.warning("Please enter text.")
|
80 |
else:
|
81 |
with st.spinner("Generating summary..."):
|
82 |
+
time.sleep(0.8)
|
83 |
+
out = query(API_URLS["Summarizer"], {"inputs": text})
|
84 |
+
if "error" in out:
|
85 |
+
st.error(out["error"])
|
86 |
+
elif isinstance(out, list) and "summary_text" in out[0]:
|
87 |
+
st.success("Summary ready")
|
88 |
+
st.write(out[0]["summary_text"])
|
89 |
else:
|
90 |
+
st.error("Unexpected response from summarizer.")
|
|
|
91 |
|
92 |
+
# --- Sentiment Analysis (sentence-wise, table + average) ---
|
93 |
with tab2:
|
94 |
+
text_sent = st.text_area("Enter text for sentiment analysis:", height=220, key="sent_text2")
|
95 |
+
if st.button("Analyze Sentiment🧠"):
|
96 |
+
if not text_sent.strip():
|
97 |
+
st.warning("Please enter text.")
|
98 |
else:
|
99 |
+
sentences = split_sentences(text_sent)
|
100 |
+
if len(sentences) == 0:
|
101 |
+
st.warning("No sentences found after splitting.")
|
|
|
|
|
102 |
else:
|
103 |
+
rows = []
|
104 |
+
total_neg = total_neu = total_pos = 0.0
|
105 |
+
error_happened = False
|
106 |
+
|
107 |
+
with st.spinner("Analyzing sentences..."):
|
108 |
+
time.sleep(0.5)
|
109 |
+
for i, s in enumerate(sentences, start=1):
|
110 |
+
res = query(API_URLS["Sentiment"], {"inputs": s})
|
111 |
+
if isinstance(res, dict) and "error" in res:
|
112 |
+
st.error(f"API error for sentence {i}: {res['error']}")
|
113 |
+
error_happened = True
|
114 |
+
break
|
115 |
+
|
116 |
+
scores = extract_scores_from_api_response(res)
|
117 |
+
if scores is None:
|
118 |
+
st.error(f"Unexpected response format for sentence {i}.")
|
119 |
+
error_happened = True
|
120 |
+
break
|
121 |
+
|
122 |
+
neg_pct = round(scores["neg"] * 100)
|
123 |
+
neu_pct = round(scores["neu"] * 100)
|
124 |
+
pos_pct = round(scores["pos"] * 100)
|
125 |
+
|
126 |
+
rows.append({
|
127 |
+
"#": i,
|
128 |
+
"Sentence": f'"{s}"',
|
129 |
+
"Negative": f"{neg_pct}%",
|
130 |
+
"Neutral": f"{neu_pct}%",
|
131 |
+
"Positive": f"{pos_pct}%"
|
132 |
+
})
|
133 |
+
|
134 |
+
total_neg += scores["neg"]
|
135 |
+
total_neu += scores["neu"]
|
136 |
+
total_pos += scores["pos"]
|
137 |
+
|
138 |
+
if not error_happened:
|
139 |
+
n = len(sentences)
|
140 |
+
avg_neg = round((total_neg / n) * 100)
|
141 |
+
avg_neu = round((total_neu / n) * 100)
|
142 |
+
avg_pos = round((total_pos / n) * 100)
|
143 |
+
|
144 |
+
# Append average row
|
145 |
+
rows.append({
|
146 |
+
"#": "Avg",
|
147 |
+
"Sentence": "—",
|
148 |
+
"Negative": f"{avg_neg}%",
|
149 |
+
"Neutral": f"{avg_neu}%",
|
150 |
+
"Positive": f"{avg_pos}%"
|
151 |
+
})
|
152 |
+
|
153 |
+
df = pd.DataFrame(rows, columns=["#", "Sentence", "Negative", "Neutral", "Positive"])
|
154 |
+
st.table(df)
|