Spaces:
Sleeping
Sleeping
Fix: replace GSheetsConnection with gspread for Docker compatibility
Browse files- requirements.txt +2 -4
- streamlit_app.py +50 -50
requirements.txt
CHANGED
|
@@ -4,8 +4,6 @@ torch
|
|
| 4 |
torchvision
|
| 5 |
safetensors
|
| 6 |
pandas
|
| 7 |
-
st-gsheets-connection
|
| 8 |
plotly
|
| 9 |
-
gspread
|
| 10 |
-
google-auth
|
| 11 |
-
oauth2client
|
|
|
|
| 4 |
torchvision
|
| 5 |
safetensors
|
| 6 |
pandas
|
|
|
|
| 7 |
plotly
|
| 8 |
+
gspread
|
| 9 |
+
google-auth
|
|
|
streamlit_app.py
CHANGED
|
@@ -6,11 +6,12 @@ import time
|
|
| 6 |
import plotly.graph_objects as go
|
| 7 |
import json
|
| 8 |
import os
|
| 9 |
-
|
| 10 |
-
|
|
|
|
| 11 |
# --- PAGE CONFIG ---
|
| 12 |
st.set_page_config(page_title="Sentiment Analyzer AI | Bilingual Engine", page_icon="π", layout="wide")
|
| 13 |
-
|
| 14 |
# --- PROFESSIONAL NEUMORPHIC / GLASS CSS ---
|
| 15 |
st.markdown("""
|
| 16 |
<style>
|
|
@@ -22,36 +23,41 @@ st.markdown("""
|
|
| 22 |
[data-testid="stMetricValue"] { color: #00f2fe; font-weight: 800; }
|
| 23 |
</style>
|
| 24 |
""", unsafe_allow_html=True)
|
| 25 |
-
|
| 26 |
-
# ---
|
| 27 |
-
# β
FIX: Docker Spaces inject secrets as ENV VARS, not st.secrets
|
| 28 |
def get_connection():
|
| 29 |
try:
|
| 30 |
-
#
|
| 31 |
json_secrets = os.environ.get("GSHEETS_JSON")
|
| 32 |
sheet_url = os.environ.get("GSHEETS_URL")
|
| 33 |
-
|
| 34 |
-
# Fallback
|
| 35 |
if not json_secrets:
|
| 36 |
json_secrets = st.secrets.get("GSHEETS_JSON")
|
| 37 |
if not sheet_url:
|
| 38 |
sheet_url = st.secrets.get("GSHEETS_URL")
|
| 39 |
-
|
| 40 |
if not json_secrets or not sheet_url:
|
| 41 |
st.error("β Secrets not found. Please add GSHEETS_JSON and GSHEETS_URL in Space Settings β Secrets.")
|
| 42 |
st.stop()
|
| 43 |
-
|
| 44 |
creds_dict = json.loads(json_secrets)
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
except json.JSONDecodeError:
|
| 49 |
st.error("β GSHEETS_JSON is not valid JSON. Please re-paste your service account key.")
|
| 50 |
st.stop()
|
| 51 |
except Exception as e:
|
| 52 |
st.error(f"β Connection Failed: {e}")
|
| 53 |
st.stop()
|
| 54 |
-
|
| 55 |
# --- INITIALIZATION ---
|
| 56 |
if 'conn' not in st.session_state or 'url' not in st.session_state:
|
| 57 |
conn, GSHEETS_URL = get_connection()
|
|
@@ -60,75 +66,69 @@ if 'conn' not in st.session_state or 'url' not in st.session_state:
|
|
| 60 |
else:
|
| 61 |
conn = st.session_state.conn
|
| 62 |
GSHEETS_URL = st.session_state.url
|
| 63 |
-
|
| 64 |
# --- SAVE TO GOOGLE SHEETS ---
|
| 65 |
def save_to_cloud(text, ai_label, ai_score, corrected_label=None):
|
| 66 |
try:
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
"Text": text,
|
| 77 |
-
"AI_Label": ai_label,
|
| 78 |
-
"Confidence": f"{ai_score:.2%}",
|
| 79 |
-
"Correction": corrected_label if corrected_label else "N/A"
|
| 80 |
-
}])
|
| 81 |
-
|
| 82 |
-
updated_df = pd.concat([existing_data, new_entry], ignore_index=True)
|
| 83 |
-
conn.update(spreadsheet=GSHEETS_URL, worksheet="Sheet1", data=updated_df)
|
| 84 |
return True
|
| 85 |
except Exception as e:
|
| 86 |
st.error(f"Cloud Save Failed: {e}")
|
| 87 |
return False
|
| 88 |
-
|
| 89 |
# --- MODEL ENGINE ---
|
| 90 |
MODEL_PATH = "SumedhGajbhiye/Sentiment-Analyzer"
|
| 91 |
-
|
| 92 |
@st.cache_resource
|
| 93 |
def load_engine(path):
|
| 94 |
return pipeline("sentiment-analysis", model=path, tokenizer=path)
|
| 95 |
-
|
| 96 |
# --- UI LAYOUT ---
|
| 97 |
col_h1, col_h2 = st.columns([3, 1])
|
| 98 |
with col_h1:
|
| 99 |
st.title("Sentiment Analyzer")
|
| 100 |
st.caption("Advanced Bilingual Sentiment Analysis for English, Hindi & Hinglish")
|
| 101 |
-
|
| 102 |
# --- SIDEBAR STATS ---
|
| 103 |
with st.sidebar:
|
| 104 |
st.markdown("### π οΈ ENGINE STATUS")
|
| 105 |
try:
|
| 106 |
-
|
|
|
|
|
|
|
| 107 |
st.metric("Total Ingested", len(df_log))
|
| 108 |
st.divider()
|
| 109 |
st.download_button("π€ Export Dataset", df_log.to_csv(index=False), "engine_feedback.csv", "text/csv")
|
| 110 |
-
except Exception
|
|
|
|
| 111 |
st.info("Engine is connecting to cloud...")
|
| 112 |
-
|
| 113 |
-
|
| 114 |
# --- MAIN LOGIC ---
|
| 115 |
classifier = load_engine(MODEL_PATH)
|
| 116 |
-
|
| 117 |
if classifier:
|
| 118 |
user_input = st.text_input("QUERY INPUT:", placeholder="Enter sentence...", key="main_input", label_visibility="collapsed")
|
| 119 |
-
|
| 120 |
if user_input:
|
| 121 |
with st.status("Initializing Neural Weights...", expanded=False) as status:
|
| 122 |
time.sleep(0.4)
|
| 123 |
result = classifier(user_input)[0]
|
| 124 |
status.update(label="Analysis Complete", state="complete", expanded=False)
|
| 125 |
-
|
| 126 |
label = result['label']
|
| 127 |
score = result['score']
|
| 128 |
-
|
| 129 |
emoji_map = {"Positive": "π’", "Neutral": "π‘", "Negative": "π΄"}
|
| 130 |
color = "#00ff88" if "POS" in label.upper() else "#ff4b4b" if "NEG" in label.upper() else "#ffaa00"
|
| 131 |
-
|
| 132 |
st.markdown(f'''
|
| 133 |
<div class="glass-card">
|
| 134 |
<h4 style="color: #888; margin:0;">CLASSIFICATION RESULT</h4>
|
|
@@ -136,7 +136,7 @@ if classifier:
|
|
| 136 |
<p style="color: #aaa; margin-top: 10px;">Deep linguistic scan detected {label.lower()} intent with {score:.1%} confidence.</p>
|
| 137 |
</div>
|
| 138 |
''', unsafe_allow_html=True)
|
| 139 |
-
|
| 140 |
col_chart, col_feed = st.columns([1, 2])
|
| 141 |
with col_chart:
|
| 142 |
fig = go.Figure(go.Indicator(
|
|
@@ -145,7 +145,7 @@ if classifier:
|
|
| 145 |
))
|
| 146 |
fig.update_layout(height=280, margin=dict(t=50, b=50, l=40, r=40), paper_bgcolor='rgba(0,0,0,0)', font={'color': "#fff"})
|
| 147 |
st.plotly_chart(fig, use_container_width=True)
|
| 148 |
-
|
| 149 |
with col_feed:
|
| 150 |
st.markdown("### βοΈ HUMAN VERIFICATION")
|
| 151 |
c1, c2 = st.columns(2)
|
|
@@ -162,11 +162,11 @@ if classifier:
|
|
| 162 |
st.toast(f"β
Engine forced to {correction}")
|
| 163 |
time.sleep(1.0)
|
| 164 |
st.rerun()
|
| 165 |
-
|
| 166 |
# --- RECENT LOGS ---
|
| 167 |
try:
|
| 168 |
if not df_log.empty:
|
| 169 |
with st.expander("π VIEW SYSTEM LOGS"):
|
| 170 |
st.dataframe(df_log.tail(10), use_container_width=True)
|
| 171 |
except Exception:
|
| 172 |
-
pass
|
|
|
|
| 6 |
import plotly.graph_objects as go
|
| 7 |
import json
|
| 8 |
import os
|
| 9 |
+
import gspread
|
| 10 |
+
from google.oauth2.service_account import Credentials
|
| 11 |
+
|
| 12 |
# --- PAGE CONFIG ---
|
| 13 |
st.set_page_config(page_title="Sentiment Analyzer AI | Bilingual Engine", page_icon="π", layout="wide")
|
| 14 |
+
|
| 15 |
# --- PROFESSIONAL NEUMORPHIC / GLASS CSS ---
|
| 16 |
st.markdown("""
|
| 17 |
<style>
|
|
|
|
| 23 |
[data-testid="stMetricValue"] { color: #00f2fe; font-weight: 800; }
|
| 24 |
</style>
|
| 25 |
""", unsafe_allow_html=True)
|
| 26 |
+
|
| 27 |
+
# --- GOOGLE SHEETS CONNECTION (gspread - works in Docker) ---
|
|
|
|
| 28 |
def get_connection():
|
| 29 |
try:
|
| 30 |
+
# Docker Spaces inject secrets as ENV VARS
|
| 31 |
json_secrets = os.environ.get("GSHEETS_JSON")
|
| 32 |
sheet_url = os.environ.get("GSHEETS_URL")
|
| 33 |
+
|
| 34 |
+
# Fallback for local development
|
| 35 |
if not json_secrets:
|
| 36 |
json_secrets = st.secrets.get("GSHEETS_JSON")
|
| 37 |
if not sheet_url:
|
| 38 |
sheet_url = st.secrets.get("GSHEETS_URL")
|
| 39 |
+
|
| 40 |
if not json_secrets or not sheet_url:
|
| 41 |
st.error("β Secrets not found. Please add GSHEETS_JSON and GSHEETS_URL in Space Settings β Secrets.")
|
| 42 |
st.stop()
|
| 43 |
+
|
| 44 |
creds_dict = json.loads(json_secrets)
|
| 45 |
+
scopes = [
|
| 46 |
+
"https://spreadsheets.google.com/feeds",
|
| 47 |
+
"https://www.googleapis.com/auth/drive"
|
| 48 |
+
]
|
| 49 |
+
creds = Credentials.from_service_account_info(creds_dict, scopes=scopes)
|
| 50 |
+
client = gspread.authorize(creds)
|
| 51 |
+
sheet = client.open_by_url(sheet_url).worksheet("Sheet1")
|
| 52 |
+
return sheet, sheet_url
|
| 53 |
+
|
| 54 |
except json.JSONDecodeError:
|
| 55 |
st.error("β GSHEETS_JSON is not valid JSON. Please re-paste your service account key.")
|
| 56 |
st.stop()
|
| 57 |
except Exception as e:
|
| 58 |
st.error(f"β Connection Failed: {e}")
|
| 59 |
st.stop()
|
| 60 |
+
|
| 61 |
# --- INITIALIZATION ---
|
| 62 |
if 'conn' not in st.session_state or 'url' not in st.session_state:
|
| 63 |
conn, GSHEETS_URL = get_connection()
|
|
|
|
| 66 |
else:
|
| 67 |
conn = st.session_state.conn
|
| 68 |
GSHEETS_URL = st.session_state.url
|
| 69 |
+
|
| 70 |
# --- SAVE TO GOOGLE SHEETS ---
|
| 71 |
def save_to_cloud(text, ai_label, ai_score, corrected_label=None):
|
| 72 |
try:
|
| 73 |
+
sheet = st.session_state.conn
|
| 74 |
+
new_row = [
|
| 75 |
+
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 76 |
+
text,
|
| 77 |
+
ai_label,
|
| 78 |
+
f"{ai_score:.2%}",
|
| 79 |
+
corrected_label if corrected_label else "N/A"
|
| 80 |
+
]
|
| 81 |
+
sheet.append_row(new_row)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return True
|
| 83 |
except Exception as e:
|
| 84 |
st.error(f"Cloud Save Failed: {e}")
|
| 85 |
return False
|
| 86 |
+
|
| 87 |
# --- MODEL ENGINE ---
|
| 88 |
MODEL_PATH = "SumedhGajbhiye/Sentiment-Analyzer"
|
| 89 |
+
|
| 90 |
@st.cache_resource
|
| 91 |
def load_engine(path):
|
| 92 |
return pipeline("sentiment-analysis", model=path, tokenizer=path)
|
| 93 |
+
|
| 94 |
# --- UI LAYOUT ---
|
| 95 |
col_h1, col_h2 = st.columns([3, 1])
|
| 96 |
with col_h1:
|
| 97 |
st.title("Sentiment Analyzer")
|
| 98 |
st.caption("Advanced Bilingual Sentiment Analysis for English, Hindi & Hinglish")
|
| 99 |
+
|
| 100 |
# --- SIDEBAR STATS ---
|
| 101 |
with st.sidebar:
|
| 102 |
st.markdown("### π οΈ ENGINE STATUS")
|
| 103 |
try:
|
| 104 |
+
sheet = st.session_state.conn
|
| 105 |
+
all_rows = sheet.get_all_records()
|
| 106 |
+
df_log = pd.DataFrame(all_rows)
|
| 107 |
st.metric("Total Ingested", len(df_log))
|
| 108 |
st.divider()
|
| 109 |
st.download_button("π€ Export Dataset", df_log.to_csv(index=False), "engine_feedback.csv", "text/csv")
|
| 110 |
+
except Exception:
|
| 111 |
+
df_log = pd.DataFrame()
|
| 112 |
st.info("Engine is connecting to cloud...")
|
| 113 |
+
|
|
|
|
| 114 |
# --- MAIN LOGIC ---
|
| 115 |
classifier = load_engine(MODEL_PATH)
|
| 116 |
+
|
| 117 |
if classifier:
|
| 118 |
user_input = st.text_input("QUERY INPUT:", placeholder="Enter sentence...", key="main_input", label_visibility="collapsed")
|
| 119 |
+
|
| 120 |
if user_input:
|
| 121 |
with st.status("Initializing Neural Weights...", expanded=False) as status:
|
| 122 |
time.sleep(0.4)
|
| 123 |
result = classifier(user_input)[0]
|
| 124 |
status.update(label="Analysis Complete", state="complete", expanded=False)
|
| 125 |
+
|
| 126 |
label = result['label']
|
| 127 |
score = result['score']
|
| 128 |
+
|
| 129 |
emoji_map = {"Positive": "π’", "Neutral": "π‘", "Negative": "π΄"}
|
| 130 |
color = "#00ff88" if "POS" in label.upper() else "#ff4b4b" if "NEG" in label.upper() else "#ffaa00"
|
| 131 |
+
|
| 132 |
st.markdown(f'''
|
| 133 |
<div class="glass-card">
|
| 134 |
<h4 style="color: #888; margin:0;">CLASSIFICATION RESULT</h4>
|
|
|
|
| 136 |
<p style="color: #aaa; margin-top: 10px;">Deep linguistic scan detected {label.lower()} intent with {score:.1%} confidence.</p>
|
| 137 |
</div>
|
| 138 |
''', unsafe_allow_html=True)
|
| 139 |
+
|
| 140 |
col_chart, col_feed = st.columns([1, 2])
|
| 141 |
with col_chart:
|
| 142 |
fig = go.Figure(go.Indicator(
|
|
|
|
| 145 |
))
|
| 146 |
fig.update_layout(height=280, margin=dict(t=50, b=50, l=40, r=40), paper_bgcolor='rgba(0,0,0,0)', font={'color': "#fff"})
|
| 147 |
st.plotly_chart(fig, use_container_width=True)
|
| 148 |
+
|
| 149 |
with col_feed:
|
| 150 |
st.markdown("### βοΈ HUMAN VERIFICATION")
|
| 151 |
c1, c2 = st.columns(2)
|
|
|
|
| 162 |
st.toast(f"β
Engine forced to {correction}")
|
| 163 |
time.sleep(1.0)
|
| 164 |
st.rerun()
|
| 165 |
+
|
| 166 |
# --- RECENT LOGS ---
|
| 167 |
try:
|
| 168 |
if not df_log.empty:
|
| 169 |
with st.expander("π VIEW SYSTEM LOGS"):
|
| 170 |
st.dataframe(df_log.tail(10), use_container_width=True)
|
| 171 |
except Exception:
|
| 172 |
+
pass
|