VyLala commited on
Commit
da25f9a
Β·
verified Β·
1 Parent(s): 6be4ec1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +511 -511
app.py CHANGED
@@ -1,512 +1,512 @@
1
- import gradio as gr
2
- import mtdna_backend
3
- import json
4
- from iterate3 import data_preprocess, model, pipeline
5
- import os
6
- import hashlib
7
- import threading
8
- # Gradio UI
9
- #stop_flag = gr.State(value=False)
10
- class StopFlag:
11
- def __init__(self):
12
- self.value = False
13
- global_stop_flag = StopFlag() # Shared between run + stop
14
-
15
- with gr.Blocks() as interface:
16
- gr.Markdown("# 🧬 mtDNA Location Classifier (MVP)")
17
-
18
- #inputMode = gr.Radio(choices=["Single Accession", "Batch Input"], value="Single Accession", label="Choose Input Mode")
19
- user_email = gr.Textbox(label="πŸ“§ Your email (used to track free quota)")
20
- usage_display = gr.Markdown("", visible=False)
21
-
22
- # with gr.Group() as single_input_group:
23
- # single_accession = gr.Textbox(label="Enter Single Accession (e.g., KU131308)")
24
-
25
- # with gr.Group(visible=False) as batch_input_group:
26
- # raw_text = gr.Textbox(label="🧬 Paste Accession Numbers (e.g., MF362736.1,MF362738.1,KU131308,MW291678)")
27
- # resume_file = gr.File(label="πŸ—ƒοΈ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
28
- # gr.HTML("""<a href="https://drive.google.com/file/d/1t-TFeIsGVu5Jh3CUZS-VE9jQWzNFCs_c/view?usp=sharing" download target="_blank">Download Example CSV Format</a>""")
29
- # gr.HTML("""<a href="https://docs.google.com/spreadsheets/d/1lKqPp17EfHsshJGZRWEpcNOZlGo3F5qU/edit?usp=sharing&ouid=112390323314156876153&rtpof=true&sd=true" download target="_blank">Download Example Excel Format</a>""")
30
- # file_upload = gr.File(label="πŸ“ Or Upload CSV/Excel File", file_types=[".csv", ".xlsx"], interactive=True, elem_id="file-upload-box")
31
- raw_text = gr.Textbox(label="🧚 Input Accession Number(s) (single (KU131308) or comma-separated (e.g., MF362736.1,MF362738.1,KU131308,MW291678))")
32
- #resume_file = gr.File(label="πŸ—ƒοΈ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
33
- gr.HTML("""<a href="https://docs.google.com/spreadsheets/d/1lKqPp17EfHsshJGZRWEpcNOZlGo3F5qU/edit?usp=sharing" download target="_blank">Download Example Excel Format</a>""")
34
- file_upload = gr.File(label="πŸ“ Or Upload CSV/Excel File", file_types=[".csv", ".xlsx"], interactive=True)
35
-
36
- with gr.Row():
37
- run_button = gr.Button("πŸ” Submit and Classify")
38
- stop_button = gr.Button("❌ Stop Batch", visible=True)
39
- reset_button = gr.Button("πŸ”„ Reset")
40
-
41
- status = gr.Markdown(visible=False)
42
-
43
- with gr.Group(visible=False) as results_group:
44
- # with gr.Accordion("Open to See the Result", open=False) as results:
45
- # with gr.Row():
46
- # output_summary = gr.Markdown(elem_id="output-summary")
47
- # output_flag = gr.Markdown(elem_id="output-flag")
48
-
49
- # gr.Markdown("---")
50
-
51
- with gr.Accordion("Open to See the Output Table", open=False) as table_accordion:
52
- output_table = gr.HTML(render=True)
53
-
54
- with gr.Row():
55
- output_type = gr.Dropdown(choices=["Excel", "JSON", "TXT"], label="Select Output Format", value="Excel")
56
- download_button = gr.Button("⬇️ Download Output")
57
- #download_file = gr.File(label="Download File Here",visible=False)
58
- download_file = gr.File(label="Download File Here", visible=False, interactive=True)
59
- progress_box = gr.Textbox(label="Live Processing Log", lines=20, interactive=False)
60
-
61
- gr.Markdown("---")
62
-
63
- gr.Markdown("### πŸ’¬ Feedback (required)")
64
- q1 = gr.Textbox(label="1️⃣ Was the inferred location accurate or helpful? Please explain.")
65
- q2 = gr.Textbox(label="2️⃣ What would improve your experience with this tool?")
66
- contact = gr.Textbox(label="πŸ“§ Your email or institution (optional)")
67
- submit_feedback = gr.Button("βœ… Submit Feedback")
68
- feedback_status = gr.Markdown()
69
-
70
- # Functions
71
- # def toggle_input_mode(mode):
72
- # if mode == "Single Accession":
73
- # return gr.update(visible=True), gr.update(visible=False)
74
- # else:
75
- # return gr.update(visible=False), gr.update(visible=True)
76
-
77
- def classify_with_loading():
78
- return gr.update(value="⏳ Please wait... processing...",visible=True) # Show processing message
79
-
80
- # def classify_dynamic(single_accession, file, text, resume, email, mode):
81
- # if mode == "Single Accession":
82
- # return classify_main(single_accession) + (gr.update(visible=False),)
83
- # else:
84
- # #return summarize_batch(file, text) + (gr.update(visible=False),) # Hide processing message
85
- # return classify_mulAcc(file, text, resume) + (gr.update(visible=False),) # Hide processing message
86
- # Logging helpers defined early to avoid NameError
87
-
88
-
89
- # def classify_dynamic(single_accession, file, text, resume, email, mode):
90
- # if mode == "Single Accession":
91
- # return classify_main(single_accession) + (gr.update(value="", visible=False),)
92
- # else:
93
- # return classify_mulAcc(file, text, resume, email, log_callback=real_time_logger, log_collector=log_collector)
94
-
95
- # for single accession
96
- # def classify_main(accession):
97
- # #table, summary, labelAncient_Modern, explain_label = mtdna_backend.summarize_results(accession)
98
- # table = mtdna_backend.summarize_results(accession)
99
- # #flag_output = f"### 🏺 Ancient/Modern Flag\n**{labelAncient_Modern}**\n\n_Explanation:_ {explain_label}"
100
- # return (
101
- # #table,
102
- # make_html_table(table),
103
- # # summary,
104
- # # flag_output,
105
- # gr.update(visible=True),
106
- # gr.update(visible=False),
107
- # gr.update(visible=False)
108
- # )
109
-
110
- #stop_flag = gr.State(value=False)
111
- #stop_flag = StopFlag()
112
-
113
- # def stop_batch(stop_flag):
114
- # stop_flag.value = True
115
- # return gr.update(value="❌ Stopping...", visible=True), stop_flag
116
- def stop_batch():
117
- global_stop_flag.value = True
118
- return gr.update(value="❌ Stopping...", visible=True)
119
-
120
- # def threaded_batch_runner(file, text, email):
121
- # global_stop_flag.value = False
122
- # log_lines = []
123
-
124
- # def update_log(line):
125
- # log_lines.append(line)
126
- # yield (
127
- # gr.update(visible=False), # output_table (not yet)
128
- # gr.update(visible=False), # results_group
129
- # gr.update(visible=False), # download_file
130
- # gr.update(visible=False), # usage_display
131
- # gr.update(value="⏳ Still processing...", visible=True), # status
132
- # gr.update(value="\n".join(log_lines)) # progress_box
133
- # )
134
-
135
- # # Start a dummy update to say "Starting..."
136
- # yield from update_log("πŸš€ Starting batch processing...")
137
-
138
- # rows, file_path, count, final_log, warning = mtdna_backend.summarize_batch(
139
- # file=file,
140
- # raw_text=text,
141
- # resume_file=None,
142
- # user_email=email,
143
- # stop_flag=global_stop_flag,
144
- # yield_callback=lambda line: (yield from update_log(line))
145
- # )
146
-
147
- # html = make_html_table(rows)
148
- # file_update = gr.update(value=file_path, visible=True) if os.path.exists(file_path) else gr.update(visible=False)
149
- # usage_or_warning_text = f"**{count}** samples used by this email." if email.strip() else warning
150
-
151
- # yield (
152
- # html,
153
- # gr.update(visible=True), # results_group
154
- # file_update, # download_file
155
- # gr.update(value=usage_or_warning_text, visible=True),
156
- # gr.update(value="βœ… Done", visible=True),
157
- # gr.update(value=final_log)
158
- # )
159
-
160
- def threaded_batch_runner(file=None, text="", email=""):
161
- print("πŸ“§ EMAIL RECEIVED:", email)
162
- import tempfile
163
- from mtdna_backend import (
164
- extract_accessions_from_input,
165
- summarize_results,
166
- save_to_excel,
167
- hash_user_id,
168
- increment_usage,
169
- )
170
- import os
171
-
172
- global_stop_flag.value = False # reset stop flag
173
-
174
- tmp_dir = tempfile.mkdtemp()
175
- output_file_path = os.path.join(tmp_dir, "batch_output_live.xlsx")
176
- limited_acc = 50 + (10 if email.strip() else 0)
177
-
178
- # Step 1: Parse input
179
- accessions, error = extract_accessions_from_input(file, text)
180
- if error:
181
- yield (
182
- "", # output_table
183
- gr.update(visible=False), # results_group
184
- gr.update(visible=False), # download_file
185
- "", # usage_display
186
- "❌ Error", # status
187
- str(error) # progress_box
188
- )
189
- return
190
-
191
- total = len(accessions)
192
- if total > limited_acc:
193
- accessions = accessions[:limited_acc]
194
- warning = f"⚠️ Only processing first {limited_acc} accessions."
195
- else:
196
- warning = f"βœ… All {total} accessions will be processed."
197
-
198
- all_rows = []
199
- log_lines = []
200
-
201
- # Step 2: Loop through accessions
202
- for i, acc in enumerate(accessions):
203
- if global_stop_flag.value:
204
- log_lines.append(f"πŸ›‘ Stopped at {acc} ({i+1}/{total})")
205
- usage_text = ""
206
- if email.strip():
207
- # user_hash = hash_user_id(email)
208
- # usage_count = increment_usage(user_hash, len(all_rows))
209
- usage_count = increment_usage(email, len(all_rows))
210
- usage_text = f"**{usage_count}** samples used by this email. Ten more samples are added first (you now have 60 limited accessions), then wait we will contact you via this email."
211
- else:
212
- usage_text = f"The limited accession is 50. The user has used {len(all_rows)}, and only {50-len(all_rows)} left."
213
- yield (
214
- make_html_table(all_rows),
215
- gr.update(visible=True),
216
- gr.update(value=output_file_path, visible=True),
217
- gr.update(value=usage_text, visible=True),
218
- "πŸ›‘ Stopped",
219
- "\n".join(log_lines)
220
- )
221
- return
222
-
223
- log_lines.append(f"[{i+1}/{total}] Processing {acc}")
224
- yield (
225
- make_html_table(all_rows),
226
- gr.update(visible=True),
227
- gr.update(visible=False),
228
- "",
229
- "⏳ Processing...",
230
- "\n".join(log_lines)
231
- )
232
-
233
- try:
234
- rows = summarize_results(acc)
235
- all_rows.extend(rows)
236
- save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
237
- log_lines.append(f"βœ… Processed {acc} ({i+1}/{total})")
238
- except Exception as e:
239
- log_lines.append(f"❌ Failed to process {acc}: {e}")
240
-
241
- yield (
242
- make_html_table(all_rows),
243
- gr.update(visible=True),
244
- gr.update(visible=False),
245
- "",
246
- "⏳ Processing...",
247
- "\n".join(log_lines)
248
- )
249
-
250
- # Final update
251
- usage_text = ""
252
- if email.strip():
253
- # user_hash = hash_user_id(email)
254
- # usage_count = increment_usage(user_hash, len(all_rows))
255
- usage_count = increment_usage(email, len(all_rows))
256
- usage_text = f"**{usage_count}** samples used by this email. Ten more samples are added first (you now have 60 limited accessions), then wait we will contact you via this email."
257
- else:
258
- usage_text = f"The limited accession is 50. The user has used {len(all_rows)}, and only {50-len(all_rows)} left."
259
- yield (
260
- make_html_table(all_rows),
261
- gr.update(visible=True),
262
- gr.update(value=output_file_path, visible=True),
263
- gr.update(value=usage_text, visible=True),
264
- "βœ… Done",
265
- "\n".join(log_lines)
266
- )
267
-
268
- # def threaded_batch_runner(file=None, text="", email=""):
269
- # global_stop_flag.value = False
270
-
271
- # # Dummy test output that matches expected schema
272
- # return (
273
- # "<div>βœ… Dummy output table</div>", # HTML string
274
- # gr.update(visible=True), # Group visibility
275
- # gr.update(visible=False), # Download file
276
- # "**0** samples used.", # Markdown
277
- # "βœ… Done", # Status string
278
- # "Processing finished." # Progress string
279
- # )
280
-
281
-
282
- # def classify_mulAcc(file, text, resume, email, log_callback=None, log_collector=None):
283
- # stop_flag.value = False
284
- # return threaded_batch_runner(file, text, resume, email, status, stop_flag, log_callback=log_callback, log_collector=log_collector)
285
-
286
-
287
- def make_html_table(rows):
288
- html = """
289
- <div style='overflow-x: auto; padding: 10px;'>
290
- <div style='max-height: 400px; overflow-y: auto; border: 1px solid #444; border-radius: 8px;'>
291
- <table style='width:100%; border-collapse: collapse; table-layout: auto; font-size: 14px; color: #f1f1f1; background-color: #1e1e1e;'>
292
- <thead style='position: sticky; top: 0; background-color: #2c2c2c; z-index: 1;'>
293
- <tr>
294
- """
295
- headers = ["Sample ID", "Predicted Country", "Country Explanation", "Predicted Sample Type", "Sample Type Explanation", "Sources", "Time cost"]
296
- html += "".join(
297
- f"<th style='padding: 10px; border: 1px solid #555; text-align: left; white-space: nowrap;'>{h}</th>"
298
- for h in headers
299
- )
300
- html += "</tr></thead><tbody>"
301
-
302
- for row in rows:
303
- html += "<tr>"
304
- for i, col in enumerate(row):
305
- header = headers[i]
306
- style = "padding: 10px; border: 1px solid #555; vertical-align: top;"
307
-
308
- # For specific columns like Haplogroup, force nowrap
309
- if header in ["Country Explanation", "Sample Type Explanation"]:
310
- style += " max-width: 400px; word-wrap: break-word; white-space: normal;"
311
- elif header in ["Sample ID", "Predicted Country", "Predicted Sample Type", "Time cost"]:
312
- style += " white-space: nowrap; text-overflow: ellipsis; max-width: 200px; overflow: hidden;"
313
-
314
- # if header == "Sources" and isinstance(col, str) and col.strip().lower().startswith("http"):
315
- # col = f"<a href='{col}' target='_blank' style='color: #4ea1f3; text-decoration: underline;'>{col}</a>"
316
-
317
- #html += f"<td style='{style}'>{col}</td>"
318
- if header == "Sources" and isinstance(col, str):
319
- links = [f"<a href='{url.strip()}' target='_blank' style='color: #4ea1f3; text-decoration: underline;'>{url.strip()}</a>" for url in col.strip().split("\n") if url.strip()]
320
- col = "- "+"<br>- ".join(links)
321
- elif isinstance(col, str):
322
- # lines = []
323
- # for line in col.split("\n"):
324
- # line = line.strip()
325
- # if not line:
326
- # continue
327
- # if line.lower().startswith("rag_llm-"):
328
- # content = line[len("rag_llm-"):].strip()
329
- # line = f"{content} (Method: RAG_LLM)"
330
- # lines.append(f"- {line}")
331
- col = col.replace("\n", "<br>")
332
- #col = col.replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;")
333
- #col = "<br>".join(lines)
334
-
335
- html += f"<td style='{style}'>{col}</td>"
336
- html += "</tr>"
337
-
338
- html += "</tbody></table></div></div>"
339
- return html
340
-
341
-
342
- # def reset_fields():
343
- # global_stop_flag.value = False # πŸ’‘ Add this to reset the flag
344
- # return (
345
- # #gr.update(value=""), # single_accession
346
- # gr.update(value=""), # raw_text
347
- # gr.update(value=None), # file_upload
348
- # #gr.update(value=None), # resume_file
349
- # #gr.update(value="Single Accession"), # inputMode
350
- # gr.update(value=[], visible=True), # output_table
351
- # # gr.update(value="", visible=True), # output_summary
352
- # # gr.update(value="", visible=True), # output_flag
353
- # gr.update(visible=False), # status
354
- # gr.update(visible=False), # results_group
355
- # gr.update(value="", visible=False), # usage_display
356
- # gr.update(value="", visible=False), # progress_box
357
- # )
358
- def reset_fields():
359
- global_stop_flag.value = False # Reset the stop flag
360
-
361
- return (
362
- gr.update(value=""), # raw_text
363
- gr.update(value=None), # file_upload
364
- gr.update(value=[], visible=True), # output_table
365
- gr.update(value="", visible=True), # status β€” reset and make visible again
366
- gr.update(visible=False), # results_group
367
- gr.update(value="", visible=True), # usage_display β€” reset and make visible again
368
- gr.update(value="", visible=True), # progress_box β€” reset AND visible!
369
- )
370
- #inputMode.change(fn=toggle_input_mode, inputs=inputMode, outputs=[single_input_group, batch_input_group])
371
- #run_button.click(fn=classify_with_loading, inputs=[], outputs=[status])
372
- # run_button.click(
373
- # fn=classify_dynamic,
374
- # inputs=[single_accession, file_upload, raw_text, resume_file,user_email,inputMode],
375
- # outputs=[output_table,
376
- # #output_summary, output_flag,
377
- # results_group, download_file, usage_display,status, progress_box]
378
- # )
379
-
380
- # run_button.click(
381
- # fn=threaded_batch_runner,
382
- # #inputs=[file_upload, raw_text, resume_file, user_email],
383
- # inputs=[file_upload, raw_text, user_email],
384
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box]
385
- # )
386
- # run_button.click(
387
- # fn=threaded_batch_runner,
388
- # inputs=[file_upload, raw_text, user_email],
389
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
390
- # every=0.5 # <-- this tells Gradio to expect streaming
391
- # )
392
- # output_table = gr.HTML()
393
- # results_group = gr.Group(visible=False)
394
- # download_file = gr.File(visible=False)
395
- # usage_display = gr.Markdown(visible=False)
396
- # status = gr.Markdown(visible=False)
397
- # progress_box = gr.Textbox(visible=False)
398
-
399
- # run_button.click(
400
- # fn=threaded_batch_runner,
401
- # inputs=[file_upload, raw_text, user_email],
402
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
403
- # every=0.5, # streaming enabled
404
- # show_progress="full"
405
- # )
406
- print("🎯 DEBUG COMPONENT TYPES")
407
- print(type(output_table))
408
- print(type(results_group))
409
- print(type(download_file))
410
- print(type(usage_display))
411
- print(type(status))
412
- print(type(progress_box))
413
-
414
-
415
- # interface.stream(
416
- # fn=threaded_batch_runner,
417
- # inputs=[file_upload, raw_text, user_email],
418
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
419
- # trigger=run_button,
420
- # every=0.5,
421
- # show_progress="full",
422
- # )
423
- interface.queue() # No arguments here!
424
-
425
- run_button.click(
426
- fn=threaded_batch_runner,
427
- inputs=[file_upload, raw_text, user_email],
428
- outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
429
- concurrency_limit=1, # βœ… correct in Gradio 5.x
430
- queue=True, # βœ… ensure the queue is used
431
- #every=0.5
432
- )
433
-
434
-
435
-
436
-
437
- stop_button.click(fn=stop_batch, inputs=[], outputs=[status])
438
-
439
- # reset_button.click(
440
- # #fn=reset_fields,
441
- # fn=lambda: (
442
- # gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(value=None), gr.update(value="Single Accession"),
443
- # gr.update(value=[], visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(value="", visible=False), gr.update(value="", visible=False)
444
- # ),
445
- # inputs=[],
446
- # outputs=[
447
- # single_accession, raw_text, file_upload, resume_file,inputMode,
448
- # output_table,# output_summary, output_flag,
449
- # status, results_group, usage_display, progress_box
450
- # ]
451
- # )
452
- #stop_button.click(fn=lambda sf: (gr.update(value="❌ Stopping...", visible=True), setattr(sf, "value", True) or sf), inputs=[gr.State(stop_flag)], outputs=[status, gr.State(stop_flag)])
453
-
454
- reset_button.click(
455
- fn=reset_fields,
456
- inputs=[],
457
- #outputs=[raw_text, file_upload, resume_file, output_table, status, results_group, usage_display, progress_box]
458
- outputs=[raw_text, file_upload, output_table, status, results_group, usage_display, progress_box]
459
- )
460
-
461
- download_button.click(
462
- fn=mtdna_backend.save_batch_output,
463
- #inputs=[output_table, output_summary, output_flag, output_type],
464
- inputs=[output_table, output_type],
465
- outputs=[download_file])
466
-
467
- # submit_feedback.click(
468
- # fn=mtdna_backend.store_feedback_to_google_sheets,
469
- # inputs=[single_accession, q1, q2, contact], outputs=feedback_status
470
- # )
471
- submit_feedback.click(
472
- fn=mtdna_backend.store_feedback_to_google_sheets,
473
- inputs=[raw_text, q1, q2, contact],
474
- outputs=[feedback_status]
475
- )
476
- # # Custom CSS styles
477
- # gr.HTML("""
478
- # <style>
479
- # /* Ensures both sections are equally spaced with the same background size */
480
- # #output-summary, #output-flag {
481
- # background-color: #f0f4f8; /* Light Grey for both */
482
- # padding: 20px;
483
- # border-radius: 10px;
484
- # margin-top: 10px;
485
- # width: 100%; /* Ensure full width */
486
- # min-height: 150px; /* Ensures both have a minimum height */
487
- # box-sizing: border-box; /* Prevents padding from increasing size */
488
- # display: flex;
489
- # flex-direction: column;
490
- # justify-content: space-between;
491
- # }
492
-
493
- # /* Specific background colors */
494
- # #output-summary {
495
- # background-color: #434a4b;
496
- # }
497
-
498
- # #output-flag {
499
- # background-color: #141616;
500
- # }
501
-
502
- # /* Ensuring they are in a row and evenly spaced */
503
- # .gradio-row {
504
- # display: flex;
505
- # justify-content: space-between;
506
- # width: 100%;
507
- # }
508
- # </style>
509
- # """)
510
-
511
-
512
  interface.launch(share=True,debug=True)
 
1
+ import gradio as gr
2
+ import mtdna_backend
3
+ import json
4
+ import data_preprocess, model, pipeline
5
+ import os
6
+ import hashlib
7
+ import threading
8
+ # Gradio UI
9
+ #stop_flag = gr.State(value=False)
10
+ class StopFlag:
11
+ def __init__(self):
12
+ self.value = False
13
+ global_stop_flag = StopFlag() # Shared between run + stop
14
+
15
+ with gr.Blocks() as interface:
16
+ gr.Markdown("# 🧬 mtDNA Location Classifier (MVP)")
17
+
18
+ #inputMode = gr.Radio(choices=["Single Accession", "Batch Input"], value="Single Accession", label="Choose Input Mode")
19
+ user_email = gr.Textbox(label="πŸ“§ Your email (used to track free quota)")
20
+ usage_display = gr.Markdown("", visible=False)
21
+
22
+ # with gr.Group() as single_input_group:
23
+ # single_accession = gr.Textbox(label="Enter Single Accession (e.g., KU131308)")
24
+
25
+ # with gr.Group(visible=False) as batch_input_group:
26
+ # raw_text = gr.Textbox(label="🧬 Paste Accession Numbers (e.g., MF362736.1,MF362738.1,KU131308,MW291678)")
27
+ # resume_file = gr.File(label="πŸ—ƒοΈ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
28
+ # gr.HTML("""<a href="https://drive.google.com/file/d/1t-TFeIsGVu5Jh3CUZS-VE9jQWzNFCs_c/view?usp=sharing" download target="_blank">Download Example CSV Format</a>""")
29
+ # gr.HTML("""<a href="https://docs.google.com/spreadsheets/d/1lKqPp17EfHsshJGZRWEpcNOZlGo3F5qU/edit?usp=sharing&ouid=112390323314156876153&rtpof=true&sd=true" download target="_blank">Download Example Excel Format</a>""")
30
+ # file_upload = gr.File(label="πŸ“ Or Upload CSV/Excel File", file_types=[".csv", ".xlsx"], interactive=True, elem_id="file-upload-box")
31
+ raw_text = gr.Textbox(label="🧚 Input Accession Number(s) (single (KU131308) or comma-separated (e.g., MF362736.1,MF362738.1,KU131308,MW291678))")
32
+ #resume_file = gr.File(label="πŸ—ƒοΈ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
33
+ gr.HTML("""<a href="https://docs.google.com/spreadsheets/d/1lKqPp17EfHsshJGZRWEpcNOZlGo3F5qU/edit?usp=sharing" download target="_blank">Download Example Excel Format</a>""")
34
+ file_upload = gr.File(label="πŸ“ Or Upload CSV/Excel File", file_types=[".csv", ".xlsx"], interactive=True)
35
+
36
+ with gr.Row():
37
+ run_button = gr.Button("πŸ” Submit and Classify")
38
+ stop_button = gr.Button("❌ Stop Batch", visible=True)
39
+ reset_button = gr.Button("πŸ”„ Reset")
40
+
41
+ status = gr.Markdown(visible=False)
42
+
43
+ with gr.Group(visible=False) as results_group:
44
+ # with gr.Accordion("Open to See the Result", open=False) as results:
45
+ # with gr.Row():
46
+ # output_summary = gr.Markdown(elem_id="output-summary")
47
+ # output_flag = gr.Markdown(elem_id="output-flag")
48
+
49
+ # gr.Markdown("---")
50
+
51
+ with gr.Accordion("Open to See the Output Table", open=False) as table_accordion:
52
+ output_table = gr.HTML(render=True)
53
+
54
+ with gr.Row():
55
+ output_type = gr.Dropdown(choices=["Excel", "JSON", "TXT"], label="Select Output Format", value="Excel")
56
+ download_button = gr.Button("⬇️ Download Output")
57
+ #download_file = gr.File(label="Download File Here",visible=False)
58
+ download_file = gr.File(label="Download File Here", visible=False, interactive=True)
59
+ progress_box = gr.Textbox(label="Live Processing Log", lines=20, interactive=False)
60
+
61
+ gr.Markdown("---")
62
+
63
+ gr.Markdown("### πŸ’¬ Feedback (required)")
64
+ q1 = gr.Textbox(label="1️⃣ Was the inferred location accurate or helpful? Please explain.")
65
+ q2 = gr.Textbox(label="2️⃣ What would improve your experience with this tool?")
66
+ contact = gr.Textbox(label="πŸ“§ Your email or institution (optional)")
67
+ submit_feedback = gr.Button("βœ… Submit Feedback")
68
+ feedback_status = gr.Markdown()
69
+
70
+ # Functions
71
+ # def toggle_input_mode(mode):
72
+ # if mode == "Single Accession":
73
+ # return gr.update(visible=True), gr.update(visible=False)
74
+ # else:
75
+ # return gr.update(visible=False), gr.update(visible=True)
76
+
77
+ def classify_with_loading():
78
+ return gr.update(value="⏳ Please wait... processing...",visible=True) # Show processing message
79
+
80
+ # def classify_dynamic(single_accession, file, text, resume, email, mode):
81
+ # if mode == "Single Accession":
82
+ # return classify_main(single_accession) + (gr.update(visible=False),)
83
+ # else:
84
+ # #return summarize_batch(file, text) + (gr.update(visible=False),) # Hide processing message
85
+ # return classify_mulAcc(file, text, resume) + (gr.update(visible=False),) # Hide processing message
86
+ # Logging helpers defined early to avoid NameError
87
+
88
+
89
+ # def classify_dynamic(single_accession, file, text, resume, email, mode):
90
+ # if mode == "Single Accession":
91
+ # return classify_main(single_accession) + (gr.update(value="", visible=False),)
92
+ # else:
93
+ # return classify_mulAcc(file, text, resume, email, log_callback=real_time_logger, log_collector=log_collector)
94
+
95
+ # for single accession
96
+ # def classify_main(accession):
97
+ # #table, summary, labelAncient_Modern, explain_label = mtdna_backend.summarize_results(accession)
98
+ # table = mtdna_backend.summarize_results(accession)
99
+ # #flag_output = f"### 🏺 Ancient/Modern Flag\n**{labelAncient_Modern}**\n\n_Explanation:_ {explain_label}"
100
+ # return (
101
+ # #table,
102
+ # make_html_table(table),
103
+ # # summary,
104
+ # # flag_output,
105
+ # gr.update(visible=True),
106
+ # gr.update(visible=False),
107
+ # gr.update(visible=False)
108
+ # )
109
+
110
+ #stop_flag = gr.State(value=False)
111
+ #stop_flag = StopFlag()
112
+
113
+ # def stop_batch(stop_flag):
114
+ # stop_flag.value = True
115
+ # return gr.update(value="❌ Stopping...", visible=True), stop_flag
116
+ def stop_batch():
117
+ global_stop_flag.value = True
118
+ return gr.update(value="❌ Stopping...", visible=True)
119
+
120
+ # def threaded_batch_runner(file, text, email):
121
+ # global_stop_flag.value = False
122
+ # log_lines = []
123
+
124
+ # def update_log(line):
125
+ # log_lines.append(line)
126
+ # yield (
127
+ # gr.update(visible=False), # output_table (not yet)
128
+ # gr.update(visible=False), # results_group
129
+ # gr.update(visible=False), # download_file
130
+ # gr.update(visible=False), # usage_display
131
+ # gr.update(value="⏳ Still processing...", visible=True), # status
132
+ # gr.update(value="\n".join(log_lines)) # progress_box
133
+ # )
134
+
135
+ # # Start a dummy update to say "Starting..."
136
+ # yield from update_log("πŸš€ Starting batch processing...")
137
+
138
+ # rows, file_path, count, final_log, warning = mtdna_backend.summarize_batch(
139
+ # file=file,
140
+ # raw_text=text,
141
+ # resume_file=None,
142
+ # user_email=email,
143
+ # stop_flag=global_stop_flag,
144
+ # yield_callback=lambda line: (yield from update_log(line))
145
+ # )
146
+
147
+ # html = make_html_table(rows)
148
+ # file_update = gr.update(value=file_path, visible=True) if os.path.exists(file_path) else gr.update(visible=False)
149
+ # usage_or_warning_text = f"**{count}** samples used by this email." if email.strip() else warning
150
+
151
+ # yield (
152
+ # html,
153
+ # gr.update(visible=True), # results_group
154
+ # file_update, # download_file
155
+ # gr.update(value=usage_or_warning_text, visible=True),
156
+ # gr.update(value="βœ… Done", visible=True),
157
+ # gr.update(value=final_log)
158
+ # )
159
+
160
+ def threaded_batch_runner(file=None, text="", email=""):
161
+ print("πŸ“§ EMAIL RECEIVED:", email)
162
+ import tempfile
163
+ from mtdna_backend import (
164
+ extract_accessions_from_input,
165
+ summarize_results,
166
+ save_to_excel,
167
+ hash_user_id,
168
+ increment_usage,
169
+ )
170
+ import os
171
+
172
+ global_stop_flag.value = False # reset stop flag
173
+
174
+ tmp_dir = tempfile.mkdtemp()
175
+ output_file_path = os.path.join(tmp_dir, "batch_output_live.xlsx")
176
+ limited_acc = 50 + (10 if email.strip() else 0)
177
+
178
+ # Step 1: Parse input
179
+ accessions, error = extract_accessions_from_input(file, text)
180
+ if error:
181
+ yield (
182
+ "", # output_table
183
+ gr.update(visible=False), # results_group
184
+ gr.update(visible=False), # download_file
185
+ "", # usage_display
186
+ "❌ Error", # status
187
+ str(error) # progress_box
188
+ )
189
+ return
190
+
191
+ total = len(accessions)
192
+ if total > limited_acc:
193
+ accessions = accessions[:limited_acc]
194
+ warning = f"⚠️ Only processing first {limited_acc} accessions."
195
+ else:
196
+ warning = f"βœ… All {total} accessions will be processed."
197
+
198
+ all_rows = []
199
+ log_lines = []
200
+
201
+ # Step 2: Loop through accessions
202
+ for i, acc in enumerate(accessions):
203
+ if global_stop_flag.value:
204
+ log_lines.append(f"πŸ›‘ Stopped at {acc} ({i+1}/{total})")
205
+ usage_text = ""
206
+ if email.strip():
207
+ # user_hash = hash_user_id(email)
208
+ # usage_count = increment_usage(user_hash, len(all_rows))
209
+ usage_count = increment_usage(email, len(all_rows))
210
+ usage_text = f"**{usage_count}** samples used by this email. Ten more samples are added first (you now have 60 limited accessions), then wait we will contact you via this email."
211
+ else:
212
+ usage_text = f"The limited accession is 50. The user has used {len(all_rows)}, and only {50-len(all_rows)} left."
213
+ yield (
214
+ make_html_table(all_rows),
215
+ gr.update(visible=True),
216
+ gr.update(value=output_file_path, visible=True),
217
+ gr.update(value=usage_text, visible=True),
218
+ "πŸ›‘ Stopped",
219
+ "\n".join(log_lines)
220
+ )
221
+ return
222
+
223
+ log_lines.append(f"[{i+1}/{total}] Processing {acc}")
224
+ yield (
225
+ make_html_table(all_rows),
226
+ gr.update(visible=True),
227
+ gr.update(visible=False),
228
+ "",
229
+ "⏳ Processing...",
230
+ "\n".join(log_lines)
231
+ )
232
+
233
+ try:
234
+ rows = summarize_results(acc)
235
+ all_rows.extend(rows)
236
+ save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
237
+ log_lines.append(f"βœ… Processed {acc} ({i+1}/{total})")
238
+ except Exception as e:
239
+ log_lines.append(f"❌ Failed to process {acc}: {e}")
240
+
241
+ yield (
242
+ make_html_table(all_rows),
243
+ gr.update(visible=True),
244
+ gr.update(visible=False),
245
+ "",
246
+ "⏳ Processing...",
247
+ "\n".join(log_lines)
248
+ )
249
+
250
+ # Final update
251
+ usage_text = ""
252
+ if email.strip():
253
+ # user_hash = hash_user_id(email)
254
+ # usage_count = increment_usage(user_hash, len(all_rows))
255
+ usage_count = increment_usage(email, len(all_rows))
256
+ usage_text = f"**{usage_count}** samples used by this email. Ten more samples are added first (you now have 60 limited accessions), then wait we will contact you via this email."
257
+ else:
258
+ usage_text = f"The limited accession is 50. The user has used {len(all_rows)}, and only {50-len(all_rows)} left."
259
+ yield (
260
+ make_html_table(all_rows),
261
+ gr.update(visible=True),
262
+ gr.update(value=output_file_path, visible=True),
263
+ gr.update(value=usage_text, visible=True),
264
+ "βœ… Done",
265
+ "\n".join(log_lines)
266
+ )
267
+
268
+ # def threaded_batch_runner(file=None, text="", email=""):
269
+ # global_stop_flag.value = False
270
+
271
+ # # Dummy test output that matches expected schema
272
+ # return (
273
+ # "<div>βœ… Dummy output table</div>", # HTML string
274
+ # gr.update(visible=True), # Group visibility
275
+ # gr.update(visible=False), # Download file
276
+ # "**0** samples used.", # Markdown
277
+ # "βœ… Done", # Status string
278
+ # "Processing finished." # Progress string
279
+ # )
280
+
281
+
282
+ # def classify_mulAcc(file, text, resume, email, log_callback=None, log_collector=None):
283
+ # stop_flag.value = False
284
+ # return threaded_batch_runner(file, text, resume, email, status, stop_flag, log_callback=log_callback, log_collector=log_collector)
285
+
286
+
287
+ def make_html_table(rows):
288
+ html = """
289
+ <div style='overflow-x: auto; padding: 10px;'>
290
+ <div style='max-height: 400px; overflow-y: auto; border: 1px solid #444; border-radius: 8px;'>
291
+ <table style='width:100%; border-collapse: collapse; table-layout: auto; font-size: 14px; color: #f1f1f1; background-color: #1e1e1e;'>
292
+ <thead style='position: sticky; top: 0; background-color: #2c2c2c; z-index: 1;'>
293
+ <tr>
294
+ """
295
+ headers = ["Sample ID", "Predicted Country", "Country Explanation", "Predicted Sample Type", "Sample Type Explanation", "Sources", "Time cost"]
296
+ html += "".join(
297
+ f"<th style='padding: 10px; border: 1px solid #555; text-align: left; white-space: nowrap;'>{h}</th>"
298
+ for h in headers
299
+ )
300
+ html += "</tr></thead><tbody>"
301
+
302
+ for row in rows:
303
+ html += "<tr>"
304
+ for i, col in enumerate(row):
305
+ header = headers[i]
306
+ style = "padding: 10px; border: 1px solid #555; vertical-align: top;"
307
+
308
+ # For specific columns like Haplogroup, force nowrap
309
+ if header in ["Country Explanation", "Sample Type Explanation"]:
310
+ style += " max-width: 400px; word-wrap: break-word; white-space: normal;"
311
+ elif header in ["Sample ID", "Predicted Country", "Predicted Sample Type", "Time cost"]:
312
+ style += " white-space: nowrap; text-overflow: ellipsis; max-width: 200px; overflow: hidden;"
313
+
314
+ # if header == "Sources" and isinstance(col, str) and col.strip().lower().startswith("http"):
315
+ # col = f"<a href='{col}' target='_blank' style='color: #4ea1f3; text-decoration: underline;'>{col}</a>"
316
+
317
+ #html += f"<td style='{style}'>{col}</td>"
318
+ if header == "Sources" and isinstance(col, str):
319
+ links = [f"<a href='{url.strip()}' target='_blank' style='color: #4ea1f3; text-decoration: underline;'>{url.strip()}</a>" for url in col.strip().split("\n") if url.strip()]
320
+ col = "- "+"<br>- ".join(links)
321
+ elif isinstance(col, str):
322
+ # lines = []
323
+ # for line in col.split("\n"):
324
+ # line = line.strip()
325
+ # if not line:
326
+ # continue
327
+ # if line.lower().startswith("rag_llm-"):
328
+ # content = line[len("rag_llm-"):].strip()
329
+ # line = f"{content} (Method: RAG_LLM)"
330
+ # lines.append(f"- {line}")
331
+ col = col.replace("\n", "<br>")
332
+ #col = col.replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;")
333
+ #col = "<br>".join(lines)
334
+
335
+ html += f"<td style='{style}'>{col}</td>"
336
+ html += "</tr>"
337
+
338
+ html += "</tbody></table></div></div>"
339
+ return html
340
+
341
+
342
+ # def reset_fields():
343
+ # global_stop_flag.value = False # πŸ’‘ Add this to reset the flag
344
+ # return (
345
+ # #gr.update(value=""), # single_accession
346
+ # gr.update(value=""), # raw_text
347
+ # gr.update(value=None), # file_upload
348
+ # #gr.update(value=None), # resume_file
349
+ # #gr.update(value="Single Accession"), # inputMode
350
+ # gr.update(value=[], visible=True), # output_table
351
+ # # gr.update(value="", visible=True), # output_summary
352
+ # # gr.update(value="", visible=True), # output_flag
353
+ # gr.update(visible=False), # status
354
+ # gr.update(visible=False), # results_group
355
+ # gr.update(value="", visible=False), # usage_display
356
+ # gr.update(value="", visible=False), # progress_box
357
+ # )
358
+ def reset_fields():
359
+ global_stop_flag.value = False # Reset the stop flag
360
+
361
+ return (
362
+ gr.update(value=""), # raw_text
363
+ gr.update(value=None), # file_upload
364
+ gr.update(value=[], visible=True), # output_table
365
+ gr.update(value="", visible=True), # status β€” reset and make visible again
366
+ gr.update(visible=False), # results_group
367
+ gr.update(value="", visible=True), # usage_display β€” reset and make visible again
368
+ gr.update(value="", visible=True), # progress_box β€” reset AND visible!
369
+ )
370
+ #inputMode.change(fn=toggle_input_mode, inputs=inputMode, outputs=[single_input_group, batch_input_group])
371
+ #run_button.click(fn=classify_with_loading, inputs=[], outputs=[status])
372
+ # run_button.click(
373
+ # fn=classify_dynamic,
374
+ # inputs=[single_accession, file_upload, raw_text, resume_file,user_email,inputMode],
375
+ # outputs=[output_table,
376
+ # #output_summary, output_flag,
377
+ # results_group, download_file, usage_display,status, progress_box]
378
+ # )
379
+
380
+ # run_button.click(
381
+ # fn=threaded_batch_runner,
382
+ # #inputs=[file_upload, raw_text, resume_file, user_email],
383
+ # inputs=[file_upload, raw_text, user_email],
384
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box]
385
+ # )
386
+ # run_button.click(
387
+ # fn=threaded_batch_runner,
388
+ # inputs=[file_upload, raw_text, user_email],
389
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
390
+ # every=0.5 # <-- this tells Gradio to expect streaming
391
+ # )
392
+ # output_table = gr.HTML()
393
+ # results_group = gr.Group(visible=False)
394
+ # download_file = gr.File(visible=False)
395
+ # usage_display = gr.Markdown(visible=False)
396
+ # status = gr.Markdown(visible=False)
397
+ # progress_box = gr.Textbox(visible=False)
398
+
399
+ # run_button.click(
400
+ # fn=threaded_batch_runner,
401
+ # inputs=[file_upload, raw_text, user_email],
402
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
403
+ # every=0.5, # streaming enabled
404
+ # show_progress="full"
405
+ # )
406
+ print("🎯 DEBUG COMPONENT TYPES")
407
+ print(type(output_table))
408
+ print(type(results_group))
409
+ print(type(download_file))
410
+ print(type(usage_display))
411
+ print(type(status))
412
+ print(type(progress_box))
413
+
414
+
415
+ # interface.stream(
416
+ # fn=threaded_batch_runner,
417
+ # inputs=[file_upload, raw_text, user_email],
418
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
419
+ # trigger=run_button,
420
+ # every=0.5,
421
+ # show_progress="full",
422
+ # )
423
+ interface.queue() # No arguments here!
424
+
425
+ run_button.click(
426
+ fn=threaded_batch_runner,
427
+ inputs=[file_upload, raw_text, user_email],
428
+ outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
429
+ concurrency_limit=1, # βœ… correct in Gradio 5.x
430
+ queue=True, # βœ… ensure the queue is used
431
+ #every=0.5
432
+ )
433
+
434
+
435
+
436
+
437
+ stop_button.click(fn=stop_batch, inputs=[], outputs=[status])
438
+
439
+ # reset_button.click(
440
+ # #fn=reset_fields,
441
+ # fn=lambda: (
442
+ # gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(value=None), gr.update(value="Single Accession"),
443
+ # gr.update(value=[], visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(value="", visible=False), gr.update(value="", visible=False)
444
+ # ),
445
+ # inputs=[],
446
+ # outputs=[
447
+ # single_accession, raw_text, file_upload, resume_file,inputMode,
448
+ # output_table,# output_summary, output_flag,
449
+ # status, results_group, usage_display, progress_box
450
+ # ]
451
+ # )
452
+ #stop_button.click(fn=lambda sf: (gr.update(value="❌ Stopping...", visible=True), setattr(sf, "value", True) or sf), inputs=[gr.State(stop_flag)], outputs=[status, gr.State(stop_flag)])
453
+
454
+ reset_button.click(
455
+ fn=reset_fields,
456
+ inputs=[],
457
+ #outputs=[raw_text, file_upload, resume_file, output_table, status, results_group, usage_display, progress_box]
458
+ outputs=[raw_text, file_upload, output_table, status, results_group, usage_display, progress_box]
459
+ )
460
+
461
+ download_button.click(
462
+ fn=mtdna_backend.save_batch_output,
463
+ #inputs=[output_table, output_summary, output_flag, output_type],
464
+ inputs=[output_table, output_type],
465
+ outputs=[download_file])
466
+
467
+ # submit_feedback.click(
468
+ # fn=mtdna_backend.store_feedback_to_google_sheets,
469
+ # inputs=[single_accession, q1, q2, contact], outputs=feedback_status
470
+ # )
471
+ submit_feedback.click(
472
+ fn=mtdna_backend.store_feedback_to_google_sheets,
473
+ inputs=[raw_text, q1, q2, contact],
474
+ outputs=[feedback_status]
475
+ )
476
+ # # Custom CSS styles
477
+ # gr.HTML("""
478
+ # <style>
479
+ # /* Ensures both sections are equally spaced with the same background size */
480
+ # #output-summary, #output-flag {
481
+ # background-color: #f0f4f8; /* Light Grey for both */
482
+ # padding: 20px;
483
+ # border-radius: 10px;
484
+ # margin-top: 10px;
485
+ # width: 100%; /* Ensure full width */
486
+ # min-height: 150px; /* Ensures both have a minimum height */
487
+ # box-sizing: border-box; /* Prevents padding from increasing size */
488
+ # display: flex;
489
+ # flex-direction: column;
490
+ # justify-content: space-between;
491
+ # }
492
+
493
+ # /* Specific background colors */
494
+ # #output-summary {
495
+ # background-color: #434a4b;
496
+ # }
497
+
498
+ # #output-flag {
499
+ # background-color: #141616;
500
+ # }
501
+
502
+ # /* Ensuring they are in a row and evenly spaced */
503
+ # .gradio-row {
504
+ # display: flex;
505
+ # justify-content: space-between;
506
+ # width: 100%;
507
+ # }
508
+ # </style>
509
+ # """)
510
+
511
+
512
  interface.launch(share=True,debug=True)