import json from typing import Any, Dict, List import gradio as gr from analysis_core import extract_chats, get_chat_name, analyze_chat THEME = gr.themes.Soft( primary_hue="fuchsia", secondary_hue="pink", neutral_hue="slate", ) CSS = """ /* full width */ .gradio-container { max-width: 100% !important; padding-left: 24px !important; padding-right: 24px !important; } /* buttons */ .btn-load button { background: linear-gradient(90deg, #d946ef, #ec4899) !important; border-radius: 16px !important; font-weight: 700 !important; } .btn-analyze button { background: linear-gradient(90deg, #22c55e, #16a34a) !important; border-radius: 16px !important; font-weight: 700 !important; } /* status */ .status-box textarea { border-radius: 14px !important; font-weight: 500; } /* plot */ .plot-container { min-height: 520px; } /* -------- Dataframe styling (match magenta theme) -------- */ .lex-table .wrap { border-radius: 16px !important; border: 1px solid rgba(236,72,153,0.28) !important; overflow: hidden !important; } .lex-table table { border-collapse: separate !important; border-spacing: 0 !important; } /* header */ .lex-table thead th { background: linear-gradient(90deg, rgba(217,70,239,0.35), rgba(236,72,153,0.25)) !important; color: rgba(255,255,255,0.92) !important; font-weight: 800 !important; border-bottom: 1px solid rgba(236,72,153,0.28) !important; } /* body cells */ .lex-table tbody td { background: rgba(255,255,255,0.02) !important; border-bottom: 1px solid rgba(236,72,153,0.10) !important; } /* zebra rows */ .lex-table tbody tr:nth-child(even) td { background: rgba(217,70,239,0.06) !important; } /* hover */ .lex-table tbody tr:hover td { background: rgba(236,72,153,0.14) !important; } /* align */ .lex-table td, .lex-table th { padding: 10px 12px !important; } /* make numbers a bit clearer */ .lex-table td:last-child { font-variant-numeric: tabular-nums; } """ def _path(file_obj) -> str: if file_obj is None: raise gr.Error("upload Telegram result.json first.") if isinstance(file_obj, str): return file_obj if isinstance(file_obj, dict) and "path" in file_obj: return file_obj["path"] if hasattr(file_obj, "name") and isinstance(file_obj.name, str): return file_obj.name raise gr.Error("could not read uploaded file path.") def lex_list_to_rows(lst: List[Dict[str, Any]]): if not lst: return [] return [[d["word"], round(float(d["score"]), 6)] for d in lst] def load_chats(file_obj): p = _path(file_obj) with open(p, "r", encoding="utf-8") as f: data = json.load(f) chats = extract_chats(data) labels = [f"{i} | {get_chat_name(c, f'Chat {i}')}" for i, c in enumerate(chats)] if not labels: raise gr.Error("no chats found. make sure you uploaded Telegram export result.json") state = {"data": data} return gr.update(choices=labels, value=labels[0]), state, f"loaded {len(labels)} chats." def run_analysis(choice: str, state: Dict[str, Any], max_bert_persian: int): if not state or "data" not in state: raise gr.Error("Upload result.json and load chats first.") if not choice: raise gr.Error("Choose a chat first.") chats = extract_chats(state["data"]) idx = int(choice.split("|", 1)[0].strip()) if idx < 0 or idx >= len(chats): raise gr.Error("Invalid chat selection.") result, fig, pos_top, neg_top = analyze_chat( chats[idx], max_bert_persian=int(max_bert_persian), ) pos_rows = lex_list_to_rows(pos_top) if pos_top else [] neg_rows = lex_list_to_rows(neg_top) if neg_top else [] return result, fig, pos_rows, neg_rows with gr.Blocks( title="Telegram Sentiment Analysis", theme=THEME, css=CSS, ) as demo: gr.Markdown( "upload Telegram result.json → load chats → choose chat → analyze. " "weekly plot includes peak/low word annotations. tables show top lex words." ) state = gr.State({}) status = gr.Textbox( label="Status", interactive=False, elem_classes=["status-box"], ) file_in = gr.File( label="Upload Telegram result.json", file_types=[".json"], ) chat_dd = gr.Dropdown( label="Choose chat", choices=[], value=None, ) max_bert = gr.Slider( minimum=0, maximum=2000, value=300, step=50, label="Max Persian messages to run BERT on (speed control)", ) load_btn = gr.Button("Load chats", elem_classes=["btn-load"]) analyze_btn = gr.Button("Analyze selected chat", elem_classes=["btn-analyze"]) out_json = gr.JSON(label="Results (JSON)") out_plot = gr.Plot( label="Weekly emotion trajectory (with peak/low word annotations)", elem_classes=["plot-container"], ) out_pos = gr.Dataframe( label="Top 5 positive lex words (word, score)", headers=["word", "score"], elem_classes=["lex-table"], ) out_neg = gr.Dataframe( label="Top 5 negative lex words (word, score)", headers=["word", "score"], elem_classes=["lex-table"], ) load_btn.click(load_chats, inputs=[file_in], outputs=[chat_dd, state, status]) analyze_btn.click( run_analysis, inputs=[chat_dd, state, max_bert], outputs=[out_json, out_plot, out_pos, out_neg], ) demo.launch()