File size: 5,475 Bytes
6e15cca
306b7e2
6e15cca
 
 
7d06466
6e15cca
 
15a6e11
cc0e613
39721f8
15a6e11
 
 
 
cc0e613
39721f8
 
 
 
 
15a6e11
cc0e613
15a6e11
39721f8
 
15a6e11
 
 
39721f8
 
15a6e11
 
 
cc0e613
15a6e11
39721f8
 
 
 
cc0e613
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15a6e11
 
 
 
7d06466
6e15cca
7d06466
6e15cca
 
9277452
 
7d06466
6e15cca
9277452
6e15cca
 
306b7e2
 
 
 
 
 
9277452
7d06466
 
9277452
6e15cca
9277452
 
 
240a85e
6e15cca
7d06466
9277452
6e15cca
 
7d06466
9277452
6f43b48
9277452
6f43b48
6e15cca
7d06466
9277452
 
6f43b48
6e15cca
6f43b48
 
 
 
 
 
 
240a85e
6f43b48
6e15cca
 
39721f8
 
 
 
 
 
306b7e2
 
 
 
6e15cca
 
 
39721f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e15cca
7d06466
9277452
 
39721f8
7d06466
 
9277452
 
cc0e613
 
6e15cca
9277452
39721f8
 
 
 
 
306b7e2
 
 
 
cc0e613
306b7e2
39721f8
306b7e2
 
 
cc0e613
306b7e2
7d06466
 
39721f8
 
 
 
 
 
7d06466
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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()