milindkamat0507 commited on
Commit
abe397e
·
verified ·
1 Parent(s): 886d8e6

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -326
app.py DELETED
@@ -1,326 +0,0 @@
1
- """
2
- app.py — BERTopic Topic Modelling Agent UI.
3
-
4
- Three UX features:
5
- 1. Phase banner — large prominent display of current B&C phase
6
- 2. Dynamic prompts — phase-appropriate suggested next actions
7
- 3. Auto-populated review table — loads from tool checkpoint files
8
-
9
- 9-column review table: #, Topic Label, Top Evidence, Sentences, Papers,
10
- Approve, Rename To, Move To, Reasoning.
11
- """
12
-
13
- import gradio as gr
14
- import pandas as pd
15
- import json
16
- import os
17
- import re
18
- import tempfile
19
- from datetime import datetime
20
- from pathlib import Path
21
- from agent import run as agent_run
22
-
23
- THREAD_ID = f"bertopic-{datetime.now().strftime('%Y%m%d%H%M%S')}"
24
-
25
- REVIEW_COLS = [
26
- "#", "Topic Label", "Top Evidence", "Sentences", "Papers",
27
- "Approve", "Rename To", "Move To", "Reasoning",
28
- ]
29
-
30
- EMPTY_TABLE = pd.DataFrame(
31
- {"#": ["-"], "Topic Label": ["No results yet — run analysis first"],
32
- "Top Evidence": [""], "Sentences": [""], "Papers": [""],
33
- "Approve": [""], "Rename To": [""], "Move To": [""], "Reasoning": [""]},
34
- )
35
-
36
- PHASE_INFO = {
37
- 0: ("Getting started", "⬜⬜⬜⬜⬜⬜",
38
- "Upload a CSV file, then click **Analyze my Scopus CSV** and press Send"),
39
- 1: ("Phase 1 — Familiarisation", "🟦⬜⬜⬜⬜⬜",
40
- "Click **Run abstract analysis** or **Run title analysis** and press Send"),
41
- 2: ("Phase 2 — Initial Codes", "🟦🟦⬜⬜⬜⬜",
42
- "Review clusters in the **Results table** below. Edit Approve / Rename / Move, "
43
- "then click **Submit Review**"),
44
- 3: ("Phase 3 — Themes", "🟦🟦🟦⬜⬜⬜",
45
- "Review merged themes. Edit the table, then click **Submit Review**"),
46
- 4: ("Phase 4 — Saturation", "🟦🟦🟦🟦⬜⬜",
47
- "Review saturation metrics. Click **Submit Review** to confirm"),
48
- 5: ("Phase 5 — Naming", "🟦🟦🟦🟦🟦⬜",
49
- "Review theme profiles. Edit names, then **Submit Review**"),
50
- 6: ("Phase 6 — Report", "🟦🟦🟦🟦🟦🟦",
51
- "Review comparison and narrative. **Submit Review** to finalise"),
52
- }
53
-
54
- _path = lambda file: str(
55
- (hasattr(file, "name") and file.name)
56
- or (isinstance(file, str) and file)
57
- or ""
58
- )
59
- _name = lambda file: os.path.basename(_path(file))
60
-
61
-
62
- def _extract_phase(text: str) -> int:
63
- """Extract phase number from agent response. Returns 0 if not found."""
64
- found = re.findall(r"Phase (\d)", str(text))
65
- return int((found or ["0"])[0])
66
-
67
-
68
- def _phase_banner(num: int) -> str:
69
- """Generate prominent phase banner with progress bar and next step."""
70
- name, progress, instruction = PHASE_INFO.get(num, PHASE_INFO[0])
71
- return (
72
- f"## {progress} {name}\n\n"
73
- f"**NEXT STEP →** {instruction}"
74
- )
75
-
76
-
77
- def _load_review_table(base_dir: str) -> pd.DataFrame:
78
- """Load latest checkpoint file into the 9-column review table.
79
-
80
- Scans base_dir for topic_labels.json, themes.json, taxonomy_alignment.json,
81
- summaries.json. Loads the most recently modified one and formats it.
82
- Returns EMPTY_TABLE if nothing found.
83
- """
84
- base = Path(str(base_dir or "/tmp/nonexistent_dir_placeholder"))
85
- candidates = (
86
- base_dir and base.exists() and sorted(
87
- (
88
- list(base.glob("topic_labels.json"))
89
- + list(base.glob("themes.json"))
90
- + list(base.glob("taxonomy_alignment.json"))
91
- + list(base.glob("summaries.json"))
92
- ),
93
- key=lambda p: p.stat().st_mtime,
94
- reverse=True,
95
- )
96
- ) or []
97
-
98
- latest = (candidates[:1] or [None])[0]
99
- return (latest and [_format_checkpoint(latest)] or [EMPTY_TABLE.copy()])[0]
100
-
101
-
102
- def _format_checkpoint(path) -> pd.DataFrame:
103
- """Format a checkpoint JSON file into review table rows.
104
-
105
- Merges data from multiple checkpoint files when available:
106
- topic_labels.json has labels but no sizes — summaries.json has sizes.
107
- """
108
- raw = json.loads(Path(path).read_text())
109
- base = Path(path).parent
110
-
111
- data = (isinstance(raw, dict) and raw.get("clusters", raw.get("per_theme", []))) or \
112
- (isinstance(raw, list) and raw) or []
113
-
114
- summaries_data = {}
115
- summaries_path = base / "summaries.json"
116
- summaries_raw = (
117
- summaries_path.exists() and json.loads(summaries_path.read_text()) or {}
118
- )
119
- summaries_list = (
120
- isinstance(summaries_raw, dict) and summaries_raw.get("clusters", [])
121
- ) or (isinstance(summaries_raw, list) and summaries_raw) or []
122
- list(map(
123
- lambda s: summaries_data.update({s.get("topic_id", -999): s}),
124
- summaries_list,
125
- ))
126
-
127
- def _row(item: dict) -> dict:
128
- """Map one JSON item to review table columns, merging summaries data."""
129
- tid = item.get("topic_id", item.get("theme_id", 0))
130
- summary = summaries_data.get(tid, {})
131
- return {
132
- "#": tid,
133
- "Topic Label": item.get("label", item.get("theme_label", "")),
134
- "Top Evidence": str(
135
- item.get("representative", "")
136
- or summary.get("representative", "")
137
- or item.get("notes", "")
138
- )[:150],
139
- "Sentences": item.get("size", 0) or summary.get("size", 0)
140
- or item.get("total_papers", 0),
141
- "Papers": item.get("size", 0) or summary.get("size", 0)
142
- or item.get("total_papers", 0),
143
- "Approve": "Yes",
144
- "Rename To": "",
145
- "Move To": "",
146
- "Reasoning": str(item.get("rationale", item.get("notes", ""))),
147
- }
148
-
149
- rows = list(map(_row, data[:200]))
150
- return (rows and [pd.DataFrame(rows, columns=REVIEW_COLS)] or [EMPTY_TABLE.copy()])[0]
151
-
152
-
153
- def on_file_upload(file):
154
- """Extract CSV stats and store base directory."""
155
- path = _path(file)
156
- result = (not path) and ("Upload a CSV to begin.", "", _phase_banner(0))
157
- return result or _do_file_upload(path, file)
158
-
159
-
160
- def _do_file_upload(path: str, file) -> tuple:
161
- """Actual file processing after path validation."""
162
- df = pd.read_csv(path)
163
- rows, cols = df.shape
164
- base = str(Path(path).parent)
165
- info = (
166
- f"**Loaded:** `{_name(file)}`\n\n"
167
- f"**Shape:** {rows:,} rows x {cols} columns\n\n"
168
- f"**Columns:** {', '.join(df.columns[:6].tolist())}\n\n"
169
- f"*Click a prompt below and press Send to begin.*"
170
- )
171
- return info, base, _phase_banner(1)
172
-
173
-
174
- def on_send(user_msg, history, file, base_dir):
175
- """Pass user message to agent. Update phase banner and review table."""
176
- msg = (user_msg or "").strip() or "help"
177
- csv_tag = f"[CSV: {_path(file)}]\n" * bool(file)
178
-
179
- history = list(history or [])
180
- history.append({"role": "user", "content": msg})
181
- history.append({"role": "assistant", "content": "Thinking..."})
182
- yield history, "", gr.skip(), gr.skip(), gr.skip()
183
-
184
- reply = agent_run(csv_tag + msg, thread_id=THREAD_ID)
185
- history[-1] = {"role": "assistant", "content": reply}
186
-
187
- phase = _extract_phase(reply)
188
- banner = _phase_banner(phase)
189
- table = _load_review_table(base_dir)
190
-
191
- yield history, "", banner, table, base_dir
192
-
193
-
194
- def on_submit_review(table_df, history, base_dir):
195
- """Serialise review table edits to agent."""
196
- history = list(history or [])
197
- edits = table_df.to_json(orient="records", indent=2)
198
-
199
- history.append({"role": "user", "content": "[REVIEW SUBMITTED]"})
200
- history.append({"role": "assistant", "content": "Processing review..."})
201
-
202
- reply = agent_run(
203
- "Reviewer submitted table edits.\n\n"
204
- f"```json\n{edits}\n```\n\n"
205
- "Process: Approve/Reject decisions, Rename To values, "
206
- "Move To reassignments (call reassign_sentences if moves exist), "
207
- "Reasoning notes. Then check STOP gates and proceed.",
208
- thread_id=THREAD_ID,
209
- )
210
- history[-1] = {"role": "assistant", "content": reply}
211
-
212
- phase = _extract_phase(reply)
213
- return history, _phase_banner(phase), _load_review_table(base_dir)
214
-
215
-
216
- def on_download(table_df, history):
217
- """Export review CSV and chat TXT."""
218
- csv_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", prefix="review_")
219
- table_df.to_csv(csv_tmp.name, index=False)
220
-
221
- txt_tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", prefix="chat_")
222
- txt_tmp.write(
223
- "\n\n".join(
224
- list(map(
225
- lambda m: f"{m.get('role', '').upper()}: {m.get('content', '')}",
226
- history or [],
227
- ))
228
- ).encode("utf-8")
229
- )
230
- txt_tmp.close()
231
- return [csv_tmp.name, txt_tmp.name]
232
-
233
-
234
- with gr.Blocks(title="BERTopic Agent") as demo:
235
-
236
- base_dir_state = gr.State(value="")
237
-
238
- gr.Markdown("# BERTopic Modelling Agent")
239
- gr.Markdown(
240
- "**Braun & Clarke 6-Phase Thematic Analysis** "
241
- "| 10 Tools | 6 STOP Gates | Cosine Agglomerative Clustering"
242
- )
243
-
244
- phase_banner = gr.Markdown(value=_phase_banner(0))
245
-
246
- gr.Markdown("---\n### Section 1 — Data input")
247
- with gr.Row():
248
- with gr.Column(scale=3):
249
- file_input = gr.File(
250
- label="Upload Scopus CSV",
251
- file_types=[".csv"],
252
- file_count="single",
253
- )
254
- with gr.Column(scale=5):
255
- file_info = gr.Markdown("Upload a CSV to begin.")
256
-
257
- gr.Markdown("---\n### Section 2 — Agent conversation")
258
- chatbot = gr.Chatbot(label="BERTopic Agent", height=200)
259
- with gr.Row():
260
- msg_box = gr.Textbox(
261
- placeholder="Type a message or click a prompt below, then press Send",
262
- show_label=False, scale=7, lines=1,
263
- )
264
- send_btn = gr.Button("Send", variant="primary", scale=1)
265
-
266
- gr.Examples(
267
- examples=[
268
- "Analyze my Scopus CSV",
269
- "Run abstract analysis",
270
- "Run title analysis",
271
- "Proceed to next phase",
272
- "Show corpus statistics",
273
- ],
274
- inputs=msg_box,
275
- label="Quick prompts (click to fill, then press Send)",
276
- )
277
-
278
- gr.Markdown("---\n### Section 3 — Results (auto-populated from tool outputs)")
279
- gr.Markdown(
280
- "This table fills automatically when the agent runs tools. "
281
- "Edit **Approve**, **Rename To**, **Move To**, **Reasoning** columns, "
282
- "then click **Submit Review**."
283
- )
284
- review_table = gr.Dataframe(
285
- value=EMPTY_TABLE,
286
- headers=REVIEW_COLS,
287
- datatype=["number", "str", "str", "number", "number",
288
- "str", "str", "str", "str"],
289
- column_count=(9, "fixed"),
290
- interactive=True,
291
- wrap=True,
292
- max_height=400,
293
- )
294
- with gr.Row():
295
- clear_btn = gr.Button("Clear table", variant="secondary", scale=2)
296
- sub_btn = gr.Button("Submit Review", variant="primary", scale=4)
297
-
298
- with gr.Accordion("Download", open=False):
299
- dl_btn = gr.Button("Generate downloads", variant="primary")
300
- dl_files = gr.File(label="Downloads", file_count="multiple",
301
- interactive=False)
302
-
303
- file_input.change(
304
- on_file_upload,
305
- inputs=[file_input],
306
- outputs=[file_info, base_dir_state, phase_banner],
307
- )
308
- send_btn.click(
309
- on_send,
310
- inputs=[msg_box, chatbot, file_input, base_dir_state],
311
- outputs=[chatbot, msg_box, phase_banner, review_table, base_dir_state],
312
- )
313
- msg_box.submit(
314
- on_send,
315
- inputs=[msg_box, chatbot, file_input, base_dir_state],
316
- outputs=[chatbot, msg_box, phase_banner, review_table, base_dir_state],
317
- )
318
- clear_btn.click(lambda: EMPTY_TABLE.copy(), outputs=[review_table])
319
- sub_btn.click(
320
- on_submit_review,
321
- inputs=[review_table, chatbot, base_dir_state],
322
- outputs=[chatbot, phase_banner, review_table],
323
- )
324
- dl_btn.click(on_download, inputs=[review_table, chatbot], outputs=[dl_files])
325
-
326
- demo.launch(ssr_mode=False, theme=gr.themes.Soft())