diff --git "a/app.py" "b/app.py"
--- "a/app.py"
+++ "b/app.py"
@@ -1,1466 +1,1504 @@
-import gradio as gr
-import mtdna_backend
-import json
-import data_preprocess, model, pipeline
-import os
-import hashlib
-import threading
-
-# Gradio UI
-#stop_flag = gr.State(value=False)
-class StopFlag:
- def __init__(self):
- self.value = False
-global_stop_flag = StopFlag() # Shared between run + stop
-
-with open("offer.html", "r", encoding="utf-8") as f:
- pricing_html = f.read()
-
-with open("mtdna_tool_explainer_updated.html", "r", encoding="utf-8") as f:
- flow_chart = f.read()
-
-# css = """
-# /* NPS container for a unified background */
-# #nps-container {
-# background-color: #333;
-# padding: 20px;
-# border-radius: 8px;
-# display: flex;
-# flex-direction: column;
-# width: 100%;
-# }
-
-# /* Question markdown styling */
-# #nps-container .gr-markdown h3 {
-# margin-bottom: 20px; /* Adds space between the question and the numbers */
-# }
-
-# /* The container for the radio buttons */
-# #nps-radio-container .gr-radio-group {
-# display: flex;
-# flex-direction: row;
-# justify-content: space-between;
-# gap: 5px;
-# flex-wrap: nowrap;
-# width: 100%;
-# }
-
-# /* Styling for each individual button */
-# #nps-radio-container .gr-radio-label {
-# display: flex;
-# justify-content: center;
-# align-items: center;
-# width: 35px;
-# height: 35px;
-# border-radius: 4px;
-# background-color: #555;
-# color: white;
-# font-weight: bold;
-# cursor: pointer;
-# transition: background-color 0.2s ease;
-# font-size: 14px;
-# }
-
-# #nps-radio-container .gr-radio-label:hover {
-# background-color: #777;
-# }
-# #nps-radio-container input[type="radio"]:checked + .gr-radio-label {
-# background-color: #999;
-# border: 2px solid white;
-# }
-# #nps-radio-container .gr-radio-input {
-# display: none;
-# }
-
-# /* Adjusting the text labels for "Not likely" and "Extremely likely" */
-# #nps-labels-row {
-# display: flex;
-# justify-content: space-between;
-# margin-top: 15px; /* Adds more space below the numbers */
-# color: #ccc;
-# width: 100%;
-# }
-
-# #nps-labels-row p {
-# margin: 0;
-# font-size: 1.0em;
-# white-space: nowrap;
-# width: 50%; /* Ensures each label takes up half the row */
-# }
-# #nps-labels-row p:first-child {
-# text-align: left;
-# }
-# #nps-labels-row p:last-child {
-# text-align: right;
-# }
-
-# #nps-submit-button {
-# margin-top: 25px; /* Adds a larger space above the submit button */
-# width: 100%;
-# }
-
-# #nps-submit-button:active {
-# border-color: white !important;
-# box-shadow: 0 0 5px white inset;
-# }"""
-
-css = """
-/* The main container for the entire NPS section */
-#nps-container {
- background-color: #333;
- padding: 20px;
- border-radius: 8px;
- display: flex;
- flex-direction: column;
- width: 100%;
-}
-
-/* Ensure the question text is properly spaced */
-#nps-container h3 {
- color: #fff;
- margin-bottom: 20px; /* Space between question and buttons */
- text-align: center; /* Center the question text */
-}
-
-/* Flexbox container for the radio buttons */
-#nps-radio-container {
- width: 100%;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-/* Ensure the inner Gradio radio group stretches to fill the container */
-#nps-radio-container > div.gr-radio-group {
- width: 100% !important;
- display: flex !important;
- justify-content: space-between !important;
-}
-
-/* Styling for each individual button */
-#nps-radio-container .gr-radio-label {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 35px;
- height: 35px;
- border-radius: 4px;
- background-color: #555;
- color: white;
- font-weight: bold;
- cursor: pointer;
- transition: background-color 0.2s ease;
- font-size: 14px;
- margin: 0; /* Remove default button margins */
-}
-
-#nps-radio-container .gr-radio-label:hover {
- background-color: #777;
-}
-
-#nps-radio-container input[type="radio"]:checked + .gr-radio-label {
- background-color: #999;
- border: 2px solid white;
-}
-
-#nps-radio-container .gr-radio-input {
- display: none;
-}
-
-/* The row for the "Not likely" and "Extremely likely" labels */
-#nps-labels-row {
- display: flex;
- justify-content: space-between;
- margin-top: 15px; /* Adds space below the number buttons */
- width: 100%; /* Force labels row to take full width */
-}
-
-#nps-labels-row .gr-markdown p {
- margin: 0;
- font-size: 1.0em;
- color: #ccc;
- white-space: nowrap;
- width: 50%;
-}
-
-#nps-labels-row .gr-markdown:first-child p {
- text-align: left;
-}
-
-#nps-labels-row .gr-markdown:last-child p {
- text-align: right;
-}
-
-/* Submit button styling */
-#nps-submit-button {
- margin-top: 25px; /* Adds space above the submit button */
- width: 100%;
-}
-
-#nps-submit-button:active {
- border-color: white !important;
- box-shadow: 0 0 5px white inset;
-}
-"""
-
-with gr.Blocks() as interface:
- # with gr.Tab("CURIOUS ABOUT THIS PRODUCT?"):
- # gr.HTML(value=pricing_html)
- with gr.Tab("๐งฌ Classifier"):
- gr.Markdown("# ๐งฌ mtDNA Location Classifier (MVP)")
- #inputMode = gr.Radio(choices=["Single Accession", "Batch Input"], value="Single Accession", label="Choose Input Mode")
- user_email = gr.Textbox(label="๐ง Your email (used to track free quota). ",
- placeholder="Enter your email and click Submit and Classify button below to run accessions.\nYou'll get +20 extra free queries and Excel-formatted results.")
-
- # sign_in_button = gr.Button("Sign in to Download")
- # user_email = gr.Textbox(
- # label="๐ง Your email (used to track free quota)",
- # visible=False
- # )
- # # The output will be used to display a message to the user
- # output_message = gr.Textbox(visible=False, interactive=False)
- usage_display = gr.Markdown("", visible=False)
-
- # with gr.Group() as single_input_group:
- # single_accession = gr.Textbox(label="Enter Single Accession (e.g., KU131308)")
-
- # with gr.Group(visible=False) as batch_input_group:
- # raw_text = gr.Textbox(label="๐งฌ Paste Accession Numbers (e.g., MF362736.1,MF362738.1,KU131308,MW291678)")
- # resume_file = gr.File(label="๐๏ธ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
- # gr.HTML("""Download Example CSV Format""")
- # gr.HTML("""Download Example Excel Format""")
- # file_upload = gr.File(label="๐ Or Upload CSV/Excel File", file_types=[".csv", ".xlsx"], interactive=True, elem_id="file-upload-box")
- raw_text = gr.Textbox(label="๐ง Input Accession Number(s) (single (KU131308) or comma-separated (e.g., MF362736.1,MF362738.1,KU131308,MW291678))")
- #resume_file = gr.File(label="๐๏ธ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
- gr.HTML("""Example Excel Input Template""")
- file_upload = gr.File(label="๐ Or Upload Excel File", file_types=[".xlsx"], interactive=True)
- processed_info = gr.Markdown(visible=False) # new placeholder for processed list
-
- with gr.Row():
- run_button = gr.Button("๐ Submit and Classify", elem_id="run-btn")
- stop_button = gr.Button("โ Stop Batch", visible=False, elem_id="stop-btn")
- reset_button = gr.Button("๐ Reset", elem_id="reset-btn")
-
-
- status = gr.Markdown(visible=False)
-
- # with gr.Group(visible=False, elem_id="nps-overlay") as nps_modal:
- # with gr.Column(elem_id="nps-box"):
- # gr.Markdown("### How likely are you to recommend this tool to a colleague or peer?")
- # nps_slider = gr.Slider(minimum=0, maximum=10, step=1, label="Select score: 0-10 (0-6: not likely or low; 7-8: neutral; 9-10: likely or highly)")
- # nps_submit = gr.Button("Submit")
- # nps_output = gr.Textbox(label="", interactive=False, visible=True) # Start empty
-
- with gr.Group(visible=False) as results_group:
- # with gr.Accordion("Open to See the Result", open=False) as results:
- # with gr.Row():
- # output_summary = gr.Markdown(elem_id="output-summary")
- # output_flag = gr.Markdown(elem_id="output-flag")
-
- # gr.Markdown("---")
-
- with gr.Accordion("Open to See the Output Table", open=True) as table_accordion:
- output_table = gr.HTML(render=True)
- #with gr.Row():
- #output_type = gr.Dropdown(choices=["Excel", "JSON", "TXT"], label="Select Output Format", value="Excel")
- #download_button = gr.Button("โฌ๏ธ Download Output")
- #download_file = gr.File(label="Download File Here",visible=False)
- # Use gr.Markdown to add a visual space
- gr.Markdown(" ") # A simple blank markdown can create space
-
- report_button = gr.Button("Report inaccurate output to receive 1 extra free query",elem_id="run-btn")
- report_textbox = gr.Textbox(
- label="Describe the issue",
- lines=4,
- placeholder="e.g. DQ981467: it gives me unknown when I can in fact search it on NCBI \n DQ981467: cannot find the result in batch output when the live processing did show already processed",
- visible=False)
- submit_report_button = gr.Button("Submit", visible=False, elem_id="run-btn")
- status_report = gr.Markdown(visible=False)
-
- # Use gr.Markdown to add a visual space
- gr.Markdown(" ") # A simple blank markdown can create space
-
- download_file = gr.File(label="Download File Here", visible=False, interactive=True)
-
- gr.Markdown(" ") # A simple blank markdown can create space
-
- with gr.Group(visible=True, elem_id="nps-overlay") as nps_modal:
- #with gr.Column(elem_id="nps-box"):
- with gr.Group(elem_id="nps-container"):
- gr.Markdown("### How likely are you to recommend this tool to a colleague or peer?")
- # # Use gr.Radio to create clickable buttons
- with gr.Column(elem_id="nps-radio-container"):
- nps_radio = gr.Radio(
- choices=[str(i) for i in range(11)],
- label="Select score:",
- interactive=True,
- container=False
- )
-
- # The "Not likely" and "Extremely likely" labels
- with gr.Row(elem_id="nps-labels-row"):
- gr.Markdown("Not likely")
- gr.Markdown("Extremely likely")
-
- nps_submit = gr.Button("Submit", elem_id="nps-submit-button")
- nps_output = gr.Textbox(label="", interactive=False, visible=True)
- gr.Markdown(" ") # A simple blank markdown can create space
-
- progress_box = gr.Textbox(label="Live Processing Log", lines=20, interactive=False)
-
- gr.Markdown("---")
-
- # gr.Markdown("### ๐ฌ Feedback (required)")
- # q1 = gr.Textbox(label="1๏ธโฃ Was the inferred location accurate or helpful? Please explain.")
- # q2 = gr.Textbox(label="2๏ธโฃ What would improve your experience with this tool?")
- # contact = gr.Textbox(label="๐ง Your email or institution (optional)")
- # submit_feedback = gr.Button("โ
Submit Feedback")
- # feedback_status = gr.Markdown()
-
- # Functions
- # def toggle_input_mode(mode):
- # if mode == "Single Accession":
- # return gr.update(visible=True), gr.update(visible=False)
- # else:
- # return gr.update(visible=False), gr.update(visible=True)
- # def show_email_textbox():
- # # Return a gr.update() to make the textbox visible and set the message
- # return gr.update(visible=True), gr.update(value="Give your email to download excel output and 20+ more free samples", visible=True)
-
- def classify_with_loading():
- return gr.update(value="โณ Please wait... processing...",visible=True) # Show processing message
-
- # def classify_dynamic(single_accession, file, text, resume, email, mode):
- # if mode == "Single Accession":
- # return classify_main(single_accession) + (gr.update(visible=False),)
- # else:
- # #return summarize_batch(file, text) + (gr.update(visible=False),) # Hide processing message
- # return classify_mulAcc(file, text, resume) + (gr.update(visible=False),) # Hide processing message
- # Logging helpers defined early to avoid NameError
-
-
- # def classify_dynamic(single_accession, file, text, resume, email, mode):
- # if mode == "Single Accession":
- # return classify_main(single_accession) + (gr.update(value="", visible=False),)
- # else:
- # return classify_mulAcc(file, text, resume, email, log_callback=real_time_logger, log_collector=log_collector)
-
- # for single accession
- # def classify_main(accession):
- # #table, summary, labelAncient_Modern, explain_label = mtdna_backend.summarize_results(accession)
- # table = mtdna_backend.summarize_results(accession)
- # #flag_output = f"### ๐บ Ancient/Modern Flag\n**{labelAncient_Modern}**\n\n_Explanation:_ {explain_label}"
- # return (
- # #table,
- # make_html_table(table),
- # # summary,
- # # flag_output,
- # gr.update(visible=True),
- # gr.update(visible=False),
- # gr.update(visible=False)
- # )
-
- #stop_flag = gr.State(value=False)
- #stop_flag = StopFlag()
-
- # def stop_batch(stop_flag):
- # stop_flag.value = True
- # return gr.update(value="โ Stopping...", visible=True), stop_flag
- def stop_batch():
- global_stop_flag.value = True
- return gr.update(value="โ Stopping...", visible=True)
-
- # def threaded_batch_runner(file, text, email):
- # global_stop_flag.value = False
- # log_lines = []
-
- # def update_log(line):
- # log_lines.append(line)
- # yield (
- # gr.update(visible=False), # output_table (not yet)
- # gr.update(visible=False), # results_group
- # gr.update(visible=False), # download_file
- # gr.update(visible=False), # usage_display
- # gr.update(value="โณ Still processing...", visible=True), # status
- # gr.update(value="\n".join(log_lines)) # progress_box
- # )
-
- # # Start a dummy update to say "Starting..."
- # yield from update_log("๐ Starting batch processing...")
-
- # rows, file_path, count, final_log, warning = mtdna_backend.summarize_batch(
- # file=file,
- # raw_text=text,
- # resume_file=None,
- # user_email=email,
- # stop_flag=global_stop_flag,
- # yield_callback=lambda line: (yield from update_log(line))
- # )
-
- # html = make_html_table(rows)
- # file_update = gr.update(value=file_path, visible=True) if os.path.exists(file_path) else gr.update(visible=False)
- # usage_or_warning_text = f"**{count}** samples used by this email." if email.strip() else warning
-
- # yield (
- # html,
- # gr.update(visible=True), # results_group
- # file_update, # download_file
- # gr.update(value=usage_or_warning_text, visible=True),
- # gr.update(value="โ
Done", visible=True),
- # gr.update(value=final_log)
- # )
-
- # def threaded_batch_runner(file=None, text="", email=""):
- # print("๐ง EMAIL RECEIVED:", email)
- # import tempfile
- # from mtdna_backend import (
- # extract_accessions_from_input,
- # summarize_results,
- # save_to_excel,
- # hash_user_id,
- # increment_usage,
- # )
- # import os
-
- # global_stop_flag.value = False # reset stop flag
-
- # tmp_dir = tempfile.mkdtemp()
- # output_file_path = os.path.join(tmp_dir, "batch_output_live.xlsx")
- # limited_acc = 50 + (10 if email.strip() else 0)
-
- # # Step 1: Parse input
- # accessions, error = extract_accessions_from_input(file, text)
- # print(accessions)
- # if error:
- # yield (
- # "", # output_table
- # gr.update(visible=False), # results_group
- # gr.update(visible=False), # download_file
- # "", # usage_display
- # "โ Error", # status
- # str(error) # progress_box
- # )
- # return
-
- # total = len(accessions)
- # if total > limited_acc:
- # accessions = accessions[:limited_acc]
- # warning = f"โ ๏ธ Only processing first {limited_acc} accessions."
- # else:
- # warning = f"โ
All {total} accessions will be processed."
-
- # all_rows = []
- # processed_accessions = 0 # โ
tracks how many accessions were processed
- # email_tracked = False
- # log_lines = []
-
- # # Step 2: Loop through accessions
- # for i, acc in enumerate(accessions):
- # if global_stop_flag.value:
- # log_lines.append(f"๐ Stopped at {acc} ({i+1}/{total})")
- # usage_text = ""
- # if email.strip() and not email_tracked:
- # # user_hash = hash_user_id(email)
- # # usage_count = increment_usage(user_hash, len(all_rows))
- # print("print(processed_accessions at stop) ",processed_accessions)
- # usage_count = increment_usage(email, processed_accessions)
- # email_tracked = True
- # 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."
- # else:
- # usage_text = f"The limited accession is 50. The user has used {processed_accessions}, and only {50-processed_accessions} left."
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(value=output_file_path, visible=True),
- # gr.update(value=usage_text, visible=True),
- # "๐ Stopped",
- # "\n".join(log_lines)
- # )
- # return
-
- # log_lines.append(f"[{i+1}/{total}] Processing {acc}")
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(visible=False),
- # "",
- # "โณ Processing...",
- # "\n".join(log_lines)
- # )
-
- # try:
- # print(acc)
- # rows = summarize_results(acc)
- # all_rows.extend(rows)
- # processed_accessions += 1 # โ
count only successful accessions
- # save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
- # log_lines.append(f"โ
Processed {acc} ({i+1}/{total})")
- # except Exception as e:
- # log_lines.append(f"โ Failed to process {acc}: {e}")
-
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(visible=False),
- # "",
- # "โณ Processing...",
- # "\n".join(log_lines)
- # )
-
- # # Final update
- # usage_text = ""
-
- # if email.strip() and not email_tracked:
- # # user_hash = hash_user_id(email)
- # # usage_count = increment_usage(user_hash, len(all_rows))
- # print("print(processed_accessions final) ",processed_accessions)
- # usage_count = increment_usage(email, processed_accessions)
- # 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."
- # elif not email.strip():
- # usage_text = f"The limited accession is 50. The user has used {processed_accessions}, and only {50-processed_accessions} left."
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(value=output_file_path, visible=True),
- # gr.update(value=usage_text, visible=True),
- # "โ
Done",
- # "\n".join(log_lines)
- # )
- def submit_nps(email,nps_score):
- if nps_score is None:
- return "โ Please select a score before submitting."
- log_submission_to_gsheet(email, [], nps_score)
- return "โ
Thanks for submitting your feedback!"
-
- def log_submission_to_gsheet(email, samples, nps_score=None):
- from datetime import datetime, timezone
- import json, os, gspread
- from oauth2client.service_account import ServiceAccountCredentials
- import uuid
-
- timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
- if not email.strip():
- email = f"anonymous_{str(uuid.uuid4())[:8]}"
-
- try:
- creds_dict = json.loads(os.environ["GCP_CREDS_JSON"])
- scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
- creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope)
- client = gspread.authorize(creds)
-
- sheet = client.open("user_usage_log")
- worksheet = sheet.sheet1 # Main sheet
-
- data = worksheet.get_all_values()
- headers = data[0]
- email_col = headers.index("email")
- samples_col = headers.index("samples")
- recent_time_col = headers.index("recent_time")
- nps_col = headers.index("nps_score") if "nps_score" in headers else -1
- print("this is nps col: ", nps_col)
- # Step 1: Find row matching the email
- for i, row in enumerate(data[1:], start=2): # start=2 for correct row indexing
- if row[email_col].strip().lower() == email.strip().lower():
- old_samples = row[samples_col].strip() if len(row) > samples_col else ""
- old_sample_list = [s.strip() for s in old_samples.split(",") if s.strip()]
- all_samples = list(dict.fromkeys(old_sample_list + samples)) # deduplicate while preserving order
- new_sample_string = ", ".join(all_samples)
-
- # Update recent_time to store history
- old_timestamp = row[recent_time_col].strip() if len(row) > recent_time_col else ""
- if old_timestamp:
- new_timestamp = f"{old_timestamp}, {timestamp}"
- else:
- new_timestamp = timestamp
-
- worksheet.update_cell(i, samples_col + 1, new_sample_string)
- worksheet.update_cell(i, recent_time_col + 1, str(new_timestamp))
- if nps_score is not None:
- print("this is nps score:", nps_score)
- old_nps = row[nps_col].strip() if len(row) > nps_col else ""
- if old_nps:
- new_nps = f"{old_nps},{nps_score}"
- else:
- new_nps = str(nps_score)
- worksheet.update_cell(i, nps_col + 1, str(new_nps))
-
- print(f"โ
Updated existing user row for: {email}")
- return
-
- # Step 2: If email not found, add new row
- new_row = [""] * len(headers)
- new_row[email_col] = email
- new_row[samples_col] = ", ".join(samples)
- new_row[recent_time_col] = timestamp
- if nps_col != -1:
- if len(new_row) <= nps_col:
- new_row.extend([""] * (nps_col + 1 - len(new_row)))
- new_row[nps_col] = str(nps_score) if nps_score is not None else ""
- worksheet.append_row(new_row)
- print(f"โ
Appended new user row for: {email}")
-
- except Exception as e:
- print(f"โ Failed to log submission to Google Sheets: {e}")
-
-
- import multiprocessing
- import time
-
- def run_with_timeout(func, args=(), kwargs={}, timeout=30, stop_value=None):
- """
- Runs func in a separate process with optional timeout.
- If stop_value is provided and becomes True during execution, the process is killed early.
- """
- def wrapper(q, *args, **kwargs):
- try:
- result = func(*args, **kwargs)
- q.put((True, result))
- except Exception as e:
- q.put((False, e))
-
- q = multiprocessing.Queue()
- p = multiprocessing.Process(target=wrapper, args=(q, *args), kwargs=kwargs)
- p.start()
-
- start_time = time.time()
- while p.is_alive():
- # Timeout check
- if timeout is not None and (time.time() - start_time) > timeout:
- p.terminate()
- p.join()
- print(f"โฑ๏ธ Timeout exceeded ({timeout} sec) โ function killed.")
- return False, None
-
- # Stop flag check
- # if stop_value is not None and stop_value.value:
- # p.terminate()
- # p.join()
- # print("๐ Stop flag detected โ function killed early.")
- # return False, None
- if stop_value is not None and stop_value.value:
- print("๐ Stop flag detected โ waiting for child to exit gracefully.")
- p.join(timeout=3) # short wait for graceful exit
- if p.is_alive():
- print("โ ๏ธ Child still alive, forcing termination.")
- p.terminate()
- p.join()
- return False, None
- time.sleep(0.1) # avoid busy waiting
-
- # Process finished naturally
- if not q.empty():
- success, result = q.get()
- if success:
- return True, result
- else:
- raise result
-
- return False, None
-
- def threaded_batch_runner(file=None, text="", email=""):
- print("๐ง EMAIL RECEIVED:", repr(email))
- import tempfile
- from mtdna_backend import (
- extract_accessions_from_input,
- summarize_results,
- save_to_excel,
- increment_usage,
- )
- import os
-
- global_stop_flag.value = False # reset stop flag
-
- tmp_dir = tempfile.mkdtemp()
- output_file_path = os.path.join(tmp_dir, "batch_output_live.xlsx")
- #output_file_path = "/mnt/data/batch_output_live.xlsx"
- all_rows = []
- processed_accessions = 0 # โ
track successful accessions
- email_tracked = False
- log_lines = []
- usage_text = ""
- processed_info = ""
- if not email.strip():
- output_file_path = None#"Write your email so that you can download the outputs."
- log_lines.append("๐ฅ Provide your email to receive a downloadable Excel report and get 20 more free queries.")
- limited_acc = 30
- if email.strip():
- usage_count, max_allowed = increment_usage(email, processed_accessions)
- if int(usage_count) >= int(max_allowed):
- log_lines.append("โ You have reached your quota. Please contact us to unlock more.")
-
- # Minimal blank yield to trigger UI rendering
- yield (
- make_html_table([]), # 1 output_table
- gr.update(visible=True), # 2 results_group
- gr.update(visible=False), # 3 download_file
- gr.update(value="", visible=True), # 4 usage_display
- "โ๏ธ Quota limit", # 5 status
- "โ๏ธ Quota limit", # 6 progress_box
- gr.update(visible=True), # 7 run_button
- gr.update(visible=False), # 8 stop_button
- gr.update(visible=True), # 9 reset_button
- gr.update(visible=True), # 10 raw_text
- gr.update(visible=True), # 11 file_upload
- gr.update(value=processed_info, visible=False), # 12 processed_info
- gr.update(visible=False) # 13 nps_modal
- )
-
- # Actual warning frame
- yield (
- make_html_table([]),
- gr.update(visible=False),
- gr.update(visible=False),
- gr.update(value="โ You have reached your quota. Please contact us to unlock more.", visible=True),
- "โ Quota Exceeded",
- "\n".join(log_lines),
- gr.update(visible=True),
- gr.update(visible=False),
- gr.update(visible=True),
- gr.update(visible=True),
- gr.update(visible=True),
- gr.update(value="", visible=False),
- gr.update(visible=False)
- )
- return
- limited_acc = int(max_allowed-usage_count)
-
- # Step 1: Parse input
- accessions, error = extract_accessions_from_input(file, text)
- total = len(accessions)
- print("total len original accessions: ", total)
- if total > limited_acc:
- accessions = accessions[:limited_acc]
- warning = f"โ ๏ธ Only processing first {limited_acc} accessions."
- else:
- warning = f"โ
All {total} accessions will be processed."
- if len(accessions) == 1:
- processed_info = warning + "\n" +f"Processed accessions: {accessions[0]}"
- else:
- processed_info = warning + "\n" +f"Processed accessions: {accessions[0]}...{accessions[-1]}"
- ### NEW: Hide inputs, show processed_info at start
- yield (
- make_html_table(all_rows), # output_table
- gr.update(visible=False), # results_group
- gr.update(visible=False), # download_file
- "", # usage_display
- "โณ Processing...", # status
- "", # progess_box
- gr.update(visible=False), # run_button,
- gr.update(visible=True), # show stop button
- gr.update(visible=True), # show reset button
- gr.update(visible=True), # hide raw_text
- gr.update(visible=True), # hide file_upload
- gr.update(value=processed_info, visible=True), # processed_info
- gr.update(visible=False) # hide NPS modal at start
- )
-
- log_submission_to_gsheet(email, accessions)
-
- print("๐งช Accessions received:", accessions)
- if error:
- yield (
- "", # 1 output_table
- gr.update(visible=False), # 2 results_group
- gr.update(visible=False), # 3 download_file
- "", # 4 usage_display
- "โ Error", # 5 status
- str(error), # 6 progress_box
- gr.update(visible=True), # 7 run_button
- gr.update(visible=False), # 8 stop_button
- gr.update(visible=True), # 9 reset_button
- gr.update(visible=True), # 10 raw_text
- gr.update(visible=True), # 11 file_upload
- gr.update(value="", visible=False), # 12 processed_info
- gr.update(visible=False) # 13 nps_modal
- )
- return
-
- # all_rows = []
- # processed_accessions = 0 # โ
track successful accessions
- # email_tracked = False
- # log_lines = []
- # if not email.strip():
- # output_file_path = None#"Write your email so that you can download the outputs."
- # log_lines.append("๐ฅ Provide your email to receive a downloadable Excel report and get 20 more free queries.")
- # if email.strip():
- # usage_count, max_allowed = increment_usage(email, processed_accessions)
- # if int(usage_count) > int(max_allowed):
- # log_lines.append("โ You have reached your quota. Please contact us to unlock more.")
-
- # # Minimal blank yield to trigger UI rendering
- # yield (
- # make_html_table([]),
- # gr.update(visible=True),
- # gr.update(visible=False),
- # gr.update(value="", visible=True),
- # "โ๏ธ Quota limit",
- # "โ๏ธ Quota limit"
- # )
-
- # # Actual warning frame
- # yield (
- # make_html_table([]),
- # gr.update(visible=False),
- # gr.update(visible=False),
- # gr.update(value="โ You have reached your quota. Please contact us to unlock more.", visible=True),
- # "โ Quota Exceeded",
- # "\n".join(log_lines)
- # )
- # return
-
- # Step 2: Loop through accessions
- for i, acc in enumerate(accessions):
- try:
- if global_stop_flag.value:
- log_lines.append(f"๐ Stopped at {acc} ({i+1}/{total})")
- usage_text = ""
-
- if email.strip() and not email_tracked:
- print(f"๐งช increment_usage at STOP: {email=} {processed_accessions=}")
- usage_count, max_allowed = increment_usage(email, processed_accessions)
- email_tracked = True
- usage_text = f"**{usage_count}**/{max_allowed} allowed 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."
- else:
- usage_text = f"The limited accession is 30. The user has used {processed_accessions}, and only {30 - processed_accessions} left."
-
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # #gr.update(value=output_file_path, visible=True),
- # gr.update(value=output_file_path, visible=bool(output_file_path)),
- # gr.update(value=usage_text, visible=True),
- # "๐ Stopped",
- # "\n".join(log_lines)
- # )
- yield (
- make_html_table(all_rows),
- gr.update(visible=True), # results_group
- gr.update(value=output_file_path, visible=bool(output_file_path)), # download_file
- gr.update(value=usage_text, visible=True), # usage_display
- "๐ Stopped", # "โ
Done" or "๐ Stopped"
- "\n".join(log_lines),
- gr.update(visible=False), # run_button
- gr.update(visible=False), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # raw_text
- gr.update(visible=True), # file_upload
- gr.update(value=processed_info, visible=False), # processed_info
- gr.update(visible=True) # NPS modal now visible
- )
-
- return
-
- log_lines.append(f"[{i+1}/{total}] Processing {acc}")
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(visible=False),
- # "",
- # "โณ Processing...",
- # "\n".join(log_lines)
- # )
- # Hide inputs, show processed_info at start
- yield (
- make_html_table(all_rows), # output_table
- gr.update(visible=True), # results_group
- gr.update(visible=False), # download_file
- "", # usage_display
- "โณ Processing...", # status
- "\n".join(log_lines), # progress_box
- gr.update(visible=False), # run_button
- gr.update(visible=True), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # hide raw_text
- gr.update(visible=True), # hide file_upload
- gr.update(value=processed_info, visible=True), # processed_info
- gr.update(visible=False) # hide NPS modal at start
- )
-
-
- # try:
- # print("๐ Processing accession:", acc)
- # rows = summarize_results(acc)
- # all_rows.extend(rows)
- # processed_accessions += 1 # โ
only count success
- # if email.strip():
- # save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
- # log_lines.append(f"โ
Processed {acc} ({i+1}/{total})")
- print("๐ Processing accession:", acc)
- # --- Before calling summarize_results ---
- samples_left = total - i # including current one
- estimated_seconds_left = samples_left * 100 # your observed average per sample
-
- log_lines.append(
- f"Running... usually ~100s per sample"
- )
- log_lines.append(
- f"โณ Estimated time left: ~{estimated_seconds_left} seconds ({samples_left} sample{'s' if samples_left > 1 else ''} remaining)"
- )
-
- # Yield update to UI before the heavy pipeline call
- yield (
- make_html_table(all_rows),
- gr.update(visible=True), # results_group
- gr.update(visible=False), # download_file
- "", # usage_display
- "โณ Processing...", # status
- "\n".join(log_lines), # progress_box
- gr.update(visible=False), # run_button
- gr.update(visible=True), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # raw_text
- gr.update(visible=True), # file_upload
- gr.update(value=processed_info, visible=True), # processed_info
- gr.update(visible=False) # hide NPS modal
- )
-
- # Run summarize_results in a separate process with stop flag support
- success, rows = run_with_timeout(
- summarize_results,
- args=(acc,),
- timeout=None, # or set max seconds per sample if you want
- stop_value=global_stop_flag
- )
-
- # If stop was pressed during this accession
- if not success and global_stop_flag.value:
- log_lines.append(f"๐ Cancelled {acc} before completion")
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(visible=False),
- # "",
- # "๐ Stopped",
- # "\n".join(log_lines)
- # )
- yield (
- make_html_table(all_rows),
- gr.update(visible=True), # results_group
- gr.update(value=output_file_path, visible=bool(output_file_path)), # download_file
- gr.update(value=usage_text, visible=True), # usage_display
- "๐ Stopped", # "โ
Done" or "๐ Stopped"
- "\n".join(log_lines),
- gr.update(visible=False), # run_button
- gr.update(visible=False), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # raw_text
- gr.update(visible=True), # file_upload
- gr.update(value="", visible=False), # processed_info
- gr.update(visible=True) # NPS modal now visible
- )
-
- break # stop processing entirely
-
- # If it finished normally
- if success and rows:
- all_rows.extend(rows)
- processed_accessions += 1
- if email.strip():
- save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
- log_lines.append(f"โ
Processed {acc} ({i+1}/{total})")
- else:
- # If it failed due to timeout or other error
- if not global_stop_flag.value:
- log_lines.append(f"โ ๏ธ Skipped {acc} due to timeout or error")
-
- # Always yield updated logs after each attempt
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # gr.update(visible=False),
- # "",
- # "โณ Processing...",
- # "\n".join(log_lines)
- # )
- yield (
- make_html_table(all_rows), # output_table
- gr.update(visible=True), # results_group
- gr.update(visible=False), # download_file
- "", # usage_display
- "โณ Processing...", # status
- "\n".join(log_lines), # progress_box
- gr.update(visible=False), # run_button
- gr.update(visible=True), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # hide raw_text
- gr.update(visible=True), # hide file_upload
- gr.update(value=processed_info, visible=True), # processed_info
- gr.update(visible=False) # hide NPS modal at start
- )
-
-
- except Exception as e:
- log_lines.append(f"โ Failed to process {acc}: {e}. Report on the box above so that we won't count this bad one for you (email required).")
- yield (
- make_html_table(all_rows), # output_table
- gr.update(visible=True), # results_group
- gr.update(visible=False), # download_file
- "", # usage_display
- "โณ Processing...", # status
- "\n".join(log_lines), # progress_box
- gr.update(visible=False), # run_button
- gr.update(visible=True), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # hide raw_text
- gr.update(visible=True), # hide file_upload
- gr.update(value=processed_info, visible=True), # processed_info
- gr.update(visible=False) # hide NPS modal at start
- )
-
- # Step 3: Final usage update
- usage_text = ""
- if email.strip() and not email_tracked:
- print(f"๐งช increment_usage at END: {email=} {processed_accessions=}")
- usage_count, max_allowed = increment_usage(email, processed_accessions)
- email_tracked = True
- usage_text = f"**{usage_count}**/{max_allowed} allowed 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."
- elif not email.strip():
- usage_text = f"The limited accession is 30. The user has used {processed_accessions}, and only {30 - processed_accessions} left."
-
- # yield (
- # make_html_table(all_rows),
- # gr.update(visible=True),
- # #gr.update(value=output_file_path, visible=True),
- # gr.update(value=output_file_path, visible=bool(output_file_path)),
- # gr.update(value=usage_text, visible=True),
- # "โ
Done",
- # "\n".join(log_lines)
- # )
- yield (
- make_html_table(all_rows),
- gr.update(visible=True), # results_group
- gr.update(value=output_file_path, visible=bool(output_file_path)), # download_file
- gr.update(value=usage_text, visible=True), # usage_display
- "โ
Done", # "โ
Done" or "๐ Stopped"
- "\n".join(log_lines),
- gr.update(visible=False), # run_button
- gr.update(visible=False), # stop_button
- gr.update(visible=True), # reset_button
- gr.update(visible=True), # raw_text
- gr.update(visible=True), # file_upload
- gr.update(value=processed_info, visible=True), # processed_info
- gr.update(visible=True) # NPS modal now visible
- )
-
- # SUBMIT REPORT UI
- # 1. Google Sheets setup
- def get_worksheet(sheet_name="Report"):
- import os, json
- import gspread
- from oauth2client.service_account import ServiceAccountCredentials
- try:
- creds_dict = json.loads(os.environ["GCP_CREDS_JSON"])
- scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
- creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope)
- client = gspread.authorize(creds)
- sheet = client.open(sheet_name).sheet1
- return sheet
- except Exception as e:
- print(f"โ Error loading Google Sheet '{sheet_name}':", e)
- return None
-
- # 2. Submit function to send report to the Google Sheet
- def submit_report(report_text,user_email=""):
- try:
- sheet = get_worksheet()
- # โ
Parse the report_text (each line like 'ACCESSION: message')
- lines = report_text.strip().split('\n')
- user = ""
- if user_email.strip():
- user = user_email
- for line in lines:
- if ':' in line:
- accession, message = line.split(':', 1)
- sheet.append_row([accession.strip(), message.strip(), user.strip()])
- return "โ
Report submitted successfully!"
- except Exception as e:
- return f"โ Error submitting report: {str(e)}"
- def show_report_ui():
- return gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)
-
- def handle_submission(text,user_email):
- msg = submit_report(text, user_email)
- return gr.update(value=msg, visible=True), gr.update(visible=False), gr.update(visible=False)
- # def threaded_batch_runner(file=None, text="", email=""):
- # global_stop_flag.value = False
-
- # # Dummy test output that matches expected schema
- # return (
- # "
โ
Dummy output table
", # HTML string
- # gr.update(visible=True), # Group visibility
- # gr.update(visible=False), # Download file
- # "**0** samples used.", # Markdown
- # "โ
Done", # Status string
- # "Processing finished." # Progress string
- # )
-
-
- # def classify_mulAcc(file, text, resume, email, log_callback=None, log_collector=None):
- # stop_flag.value = False
- # return threaded_batch_runner(file, text, resume, email, status, stop_flag, log_callback=log_callback, log_collector=log_collector)
-
-
- def make_html_table(rows):
- # html = """
- #
- #
- #
- #
- #
- # """
- html = """
-
-
-
- """
-
- headers = ["No.", "Sample ID", "Predicted Country", "Country Explanation", "Predicted Sample Type", "Sample Type Explanation", "Sources", "Time cost"]
- html += "".join(
- f"{h} | "
- for h in headers
- )
- html += ""
-
- for idx, row in enumerate(rows, 1): # start numbering from 1
- html += ""
- html += f"{idx} | " # "No." column
- for i, col in enumerate(row):
- header = headers[i]
- style = "padding: 10px; border: 1px solid #555; vertical-align: top;"
-
- # For specific columns like Haplogroup, force nowrap
- if header in ["Country Explanation", "Sample Type Explanation"]:
- style += " max-width: 400px; word-wrap: break-word; white-space: normal;"
- elif header in ["Sample ID", "Predicted Country", "Predicted Sample Type", "Time cost"]:
- style += " white-space: nowrap; text-overflow: ellipsis; max-width: 200px; overflow: hidden;"
-
- # if header == "Sources" and isinstance(col, str) and col.strip().lower().startswith("http"):
- # col = f"{col}"
-
- #html += f"{col} | "
- if header == "Sources" and isinstance(col, str):
- links = [f"{url.strip()}" for url in col.strip().split("\n") if url.strip()]
- col = "- "+"
- ".join(links)
- elif isinstance(col, str):
- # lines = []
- # for line in col.split("\n"):
- # line = line.strip()
- # if not line:
- # continue
- # if line.lower().startswith("rag_llm-"):
- # content = line[len("rag_llm-"):].strip()
- # line = f"{content} (Method: RAG_LLM)"
- # lines.append(f"- {line}")
- col = col.replace("\n", "
")
- #col = col.replace("\t", " ")
- #col = "
".join(lines)
-
- html += f"{col} | "
- html += "
"
-
- html += "
"
- return html
-
-
- # def reset_fields():
- # global_stop_flag.value = False # ๐ก Add this to reset the flag
- # return (
- # #gr.update(value=""), # single_accession
- # gr.update(value=""), # raw_text
- # gr.update(value=None), # file_upload
- # #gr.update(value=None), # resume_file
- # #gr.update(value="Single Accession"), # inputMode
- # gr.update(value=[], visible=True), # output_table
- # # gr.update(value="", visible=True), # output_summary
- # # gr.update(value="", visible=True), # output_flag
- # gr.update(visible=False), # status
- # gr.update(visible=False), # results_group
- # gr.update(value="", visible=False), # usage_display
- # gr.update(value="", visible=False), # progress_box
- # )
- # def reset_fields():
- # global_stop_flag.value = True # Reset the stop flag
-
- # return (
- # gr.update(value=""), # raw_text
- # gr.update(value=None), # file_upload
- # gr.update(value=[], visible=True), # output_table
- # gr.update(value="", visible=True), # status โ reset and make visible again
- # gr.update(visible=False), # results_group
- # gr.update(value="", visible=True), # usage_display โ reset and make visible again
- # gr.update(value="", visible=True), # progress_box โ reset AND visible!
- # # report-related reset below
- # gr.update(value="", visible=False), # report_textbox
- # gr.update(visible=False), # submit_report_button
- # gr.update(value="", visible=False), # status_report
- # gr.update(value=0), # nps_slider
- # gr.update(value="", visible=False) # nps_output
-
- # )
- def reset_fields():
- global_stop_flag.value = True # Stop any running job
-
- return (
- gr.update(value="", visible=True), # raw_text
- gr.update(value=None, visible=True), # file_upload
- gr.update(value=[], visible=True), # output_table
- gr.update(value="", visible=True), # status
- gr.update(visible=False), # results_group
- gr.update(value="", visible=True), # usage_display
- gr.update(value="", visible=True), # progress_box
- gr.update(value="", visible=False), # report_textbox
- gr.update(visible=False), # submit_report_button
- gr.update(value="", visible=False), # status_report
- gr.update(value="", visible=False), # processed_info
- gr.update(visible=False), # hide NPS modal
- gr.update(visible=True), # run_button โ
restore
- gr.update(visible=False) # stop button
- )
-
-
- #inputMode.change(fn=toggle_input_mode, inputs=inputMode, outputs=[single_input_group, batch_input_group])
- #run_button.click(fn=classify_with_loading, inputs=[], outputs=[status])
- # run_button.click(
- # fn=classify_dynamic,
- # inputs=[single_accession, file_upload, raw_text, resume_file,user_email,inputMode],
- # outputs=[output_table,
- # #output_summary, output_flag,
- # results_group, download_file, usage_display,status, progress_box]
- # )
-
- # run_button.click(
- # fn=threaded_batch_runner,
- # #inputs=[file_upload, raw_text, resume_file, user_email],
- # inputs=[file_upload, raw_text, user_email],
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box]
- # )
- # run_button.click(
- # fn=threaded_batch_runner,
- # inputs=[file_upload, raw_text, user_email],
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
- # every=0.5 # <-- this tells Gradio to expect streaming
- # )
- # output_table = gr.HTML()
- # results_group = gr.Group(visible=False)
- # download_file = gr.File(visible=False)
- # usage_display = gr.Markdown(visible=False)
- # status = gr.Markdown(visible=False)
- # progress_box = gr.Textbox(visible=False)
-
- # run_button.click(
- # fn=threaded_batch_runner,
- # inputs=[file_upload, raw_text, user_email],
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
- # every=0.5, # streaming enabled
- # show_progress="full"
- # )
-
- # interface.stream(
- # fn=threaded_batch_runner,
- # inputs=[file_upload, raw_text, user_email],
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
- # trigger=run_button,
- # every=0.5,
- # show_progress="full",
- # )
- interface.queue() # No arguments here!
-
- # run_button.click(
- # fn=threaded_batch_runner,
- # inputs=[file_upload, raw_text, user_email],
- # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
- # concurrency_limit=1, # โ
correct in Gradio 5.x
- # queue=True, # โ
ensure the queue is used
- # #every=0.5
- # )
- # Link the button to the function
- # sign_in_button.click(
- # fn=show_email_textbox,
- # outputs=[user_email, output_message] # The outputs are the components to be updated
- # )
-
- run_button.click(
- fn=threaded_batch_runner,
- inputs=[file_upload, raw_text, user_email],
- outputs=[
- output_table, # 1
- results_group, # 2
- download_file, # 3
- usage_display, # 4
- status, # 5
- progress_box, # 6
- run_button, # 7
- stop_button, # 8
- reset_button, # 9
- raw_text, # 10
- file_upload, # 11
- processed_info, # 12
- nps_modal # 13
- ],
- concurrency_limit=1,
- queue=True
- )
-
-
-
-
-
- stop_button.click(fn=stop_batch, inputs=[], outputs=[status])
-
- # reset_button.click(
- # #fn=reset_fields,
- # fn=lambda: (
- # gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(value=None), gr.update(value="Single Accession"),
- # gr.update(value=[], visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(value="", visible=False), gr.update(value="", visible=False)
- # ),
- # inputs=[],
- # outputs=[
- # single_accession, raw_text, file_upload, resume_file,inputMode,
- # output_table,# output_summary, output_flag,
- # status, results_group, usage_display, progress_box
- # ]
- # )
- #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)])
-
- # reset_button.click(
- # fn=reset_fields,
- # inputs=[],
- # #outputs=[raw_text, file_upload, resume_file, output_table, status, results_group, usage_display, progress_box]
- # outputs=[raw_text, file_upload, output_table, status, results_group, usage_display, progress_box,
- # report_textbox,
- # submit_report_button,
- # status_report, nps_slider, nps_output]
- # )
- reset_button.click(
- fn=reset_fields,
- inputs=[],
- outputs=[
- raw_text,
- file_upload,
- output_table,
- status,
- results_group,
- usage_display,
- progress_box,
- report_textbox,
- submit_report_button,
- status_report,
- processed_info,
- nps_modal,
- run_button,
- stop_button
- ]
- )
-
-
- # download_button.click(
- # fn=mtdna_backend.save_batch_output,
- # #inputs=[output_table, output_summary, output_flag, output_type],
- # inputs=[output_table, output_type],
- # outputs=[download_file])
-
- # submit_feedback.click(
- # fn=mtdna_backend.store_feedback_to_google_sheets,
- # inputs=[single_accession, q1, q2, contact], outputs=feedback_status
- # )
- report_button.click(fn=show_report_ui, outputs=[report_textbox, submit_report_button, status_report])
- submit_report_button.click(fn=handle_submission, inputs=[report_textbox, user_email], outputs=[status_report, report_textbox, submit_report_button])
-
- # submit_feedback.click(
- # fn=mtdna_backend.store_feedback_to_google_sheets,
- # inputs=[raw_text, q1, q2, contact],
- # outputs=[feedback_status]
- # )
- nps_submit.click(fn=submit_nps, inputs=[user_email, nps_radio], outputs=[nps_output])
- # Link each button to submit function
-
- gr.HTML("""
-
- """)
-
- # # Custom CSS styles
- # gr.HTML("""
- #
- # """)
- with gr.Tab("CURIOUS ABOUT THIS PRODUCT?"):
- gr.HTML(value=flow_chart)
-
- with gr.Tab("PRICING"):
- gr.HTML(value=pricing_html)
-
-
-
-
+import gradio as gr
+import mtdna_backend
+import json
+import data_preprocess, model, pipeline
+import os
+import hashlib
+import threading
+
+# Gradio UI
+#stop_flag = gr.State(value=False)
+class StopFlag:
+ def __init__(self):
+ self.value = False
+global_stop_flag = StopFlag() # Shared between run + stop
+
+with open("offer.html", "r", encoding="utf-8") as f:
+ pricing_html = f.read()
+
+with open("mtdna_tool_explainer_updated.html", "r", encoding="utf-8") as f:
+ flow_chart = f.read()
+
+# css = """
+# /* NPS container for a unified background */
+# #nps-container {
+# background-color: #333;
+# padding: 20px;
+# border-radius: 8px;
+# display: flex;
+# flex-direction: column;
+# width: 100%;
+# }
+
+# /* Question markdown styling */
+# #nps-container .gr-markdown h3 {
+# margin-bottom: 20px; /* Adds space between the question and the numbers */
+# }
+
+# /* The container for the radio buttons */
+# #nps-radio-container .gr-radio-group {
+# display: flex;
+# flex-direction: row;
+# justify-content: space-between;
+# gap: 5px;
+# flex-wrap: nowrap;
+# width: 100%;
+# }
+
+# /* Styling for each individual button */
+# #nps-radio-container .gr-radio-label {
+# display: flex;
+# justify-content: center;
+# align-items: center;
+# width: 35px;
+# height: 35px;
+# border-radius: 4px;
+# background-color: #555;
+# color: white;
+# font-weight: bold;
+# cursor: pointer;
+# transition: background-color 0.2s ease;
+# font-size: 14px;
+# }
+
+# #nps-radio-container .gr-radio-label:hover {
+# background-color: #777;
+# }
+# #nps-radio-container input[type="radio"]:checked + .gr-radio-label {
+# background-color: #999;
+# border: 2px solid white;
+# }
+# #nps-radio-container .gr-radio-input {
+# display: none;
+# }
+
+# /* Adjusting the text labels for "Not likely" and "Extremely likely" */
+# #nps-labels-row {
+# display: flex;
+# justify-content: space-between;
+# margin-top: 15px; /* Adds more space below the numbers */
+# color: #ccc;
+# width: 100%;
+# }
+
+# #nps-labels-row p {
+# margin: 0;
+# font-size: 1.0em;
+# white-space: nowrap;
+# width: 50%; /* Ensures each label takes up half the row */
+# }
+# #nps-labels-row p:first-child {
+# text-align: left;
+# }
+# #nps-labels-row p:last-child {
+# text-align: right;
+# }
+
+# #nps-submit-button {
+# margin-top: 25px; /* Adds a larger space above the submit button */
+# width: 100%;
+# }
+
+# #nps-submit-button:active {
+# border-color: white !important;
+# box-shadow: 0 0 5px white inset;
+# }"""
+
+css = """
+/* The main container for the entire NPS section */
+#nps-container {
+ background-color: #333;
+ padding: 20px;
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+/* Ensure the question text is properly spaced */
+#nps-container h3 {
+ color: #fff;
+ margin-bottom: 20px; /* Space between question and buttons */
+ text-align: center; /* Center the question text */
+}
+
+/* Flexbox container for the radio buttons */
+#nps-radio-container {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+/* Ensure the inner Gradio radio group stretches to fill the container */
+#nps-radio-container > div.gr-radio-group {
+ width: 100% !important;
+ display: flex !important;
+ justify-content: space-between !important;
+}
+
+/* Styling for each individual button */
+#nps-radio-container .gr-radio-label {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 35px;
+ height: 35px;
+ border-radius: 4px;
+ background-color: #555;
+ color: white;
+ font-weight: bold;
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+ font-size: 14px;
+ margin: 0; /* Remove default button margins */
+}
+
+#nps-radio-container .gr-radio-label:hover {
+ background-color: #777;
+}
+
+#nps-radio-container input[type="radio"]:checked + .gr-radio-label {
+ background-color: #999;
+ border: 2px solid white;
+}
+
+#nps-radio-container .gr-radio-input {
+ display: none;
+}
+
+/* The row for the "Not likely" and "Extremely likely" labels */
+#nps-labels-row {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 15px; /* Adds space below the number buttons */
+ width: 100%; /* Force labels row to take full width */
+}
+
+#nps-labels-row .gr-markdown p {
+ margin: 0;
+ font-size: 1.0em;
+ color: #ccc;
+ white-space: nowrap;
+ width: 50%;
+}
+
+#nps-labels-row .gr-markdown:first-child p {
+ text-align: left;
+}
+
+#nps-labels-row .gr-markdown:last-child p {
+ text-align: right;
+}
+
+/* Submit button styling */
+#nps-submit-button {
+ margin-top: 25px; /* Adds space above the submit button */
+ width: 100%;
+}
+
+#nps-submit-button:active {
+ border-color: white !important;
+ box-shadow: 0 0 5px white inset;
+}
+"""
+
+with gr.Blocks() as interface:
+ # with gr.Tab("CURIOUS ABOUT THIS PRODUCT?"):
+ # gr.HTML(value=pricing_html)
+ with gr.Tab("๐งฌ Classifier"):
+ gr.Markdown("# ๐งฌ mtDNA Location Classifier (MVP)")
+ #inputMode = gr.Radio(choices=["Single Accession", "Batch Input"], value="Single Accession", label="Choose Input Mode")
+ user_email = gr.Textbox(label="๐ง Your email (used to track free quota). ",
+ placeholder="Enter your email and click Submit and Classify button below to run accessions.\nYou'll get +20 extra free queries and Excel-formatted results.")
+
+ # sign_in_button = gr.Button("Sign in to Download")
+ # user_email = gr.Textbox(
+ # label="๐ง Your email (used to track free quota)",
+ # visible=False
+ # )
+ # # The output will be used to display a message to the user
+ # output_message = gr.Textbox(visible=False, interactive=False)
+ usage_display = gr.Markdown("", visible=False)
+
+ # with gr.Group() as single_input_group:
+ # single_accession = gr.Textbox(label="Enter Single Accession (e.g., KU131308)")
+
+ # with gr.Group(visible=False) as batch_input_group:
+ # raw_text = gr.Textbox(label="๐งฌ Paste Accession Numbers (e.g., MF362736.1,MF362738.1,KU131308,MW291678)")
+ # resume_file = gr.File(label="๐๏ธ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
+ # gr.HTML("""Download Example CSV Format""")
+ # gr.HTML("""Download Example Excel Format""")
+ # file_upload = gr.File(label="๐ Or Upload CSV/Excel File", file_types=[".csv", ".xlsx"], interactive=True, elem_id="file-upload-box")
+ raw_text = gr.Textbox(label="๐ง Input Accession Number(s) (single (KU131308) or comma-separated (e.g., MF362736.1,MF362738.1,KU131308,MW291678))")
+ #resume_file = gr.File(label="๐๏ธ Previously saved Excel output (optional)", file_types=[".xlsx"], interactive=True)
+ gr.HTML("""Example Excel Input Template""")
+ file_upload = gr.File(label="๐ Or Upload Excel File", file_types=[".xlsx"], interactive=True)
+ processed_info = gr.Markdown(visible=False) # new placeholder for processed list
+
+ with gr.Row():
+ run_button = gr.Button("๐ Submit and Classify", elem_id="run-btn")
+ stop_button = gr.Button("โ Stop Batch", visible=False, elem_id="stop-btn")
+ reset_button = gr.Button("๐ Reset", elem_id="reset-btn")
+
+
+ status = gr.Markdown(visible=False)
+
+ # with gr.Group(visible=False, elem_id="nps-overlay") as nps_modal:
+ # with gr.Column(elem_id="nps-box"):
+ # gr.Markdown("### How likely are you to recommend this tool to a colleague or peer?")
+ # nps_slider = gr.Slider(minimum=0, maximum=10, step=1, label="Select score: 0-10 (0-6: not likely or low; 7-8: neutral; 9-10: likely or highly)")
+ # nps_submit = gr.Button("Submit")
+ # nps_output = gr.Textbox(label="", interactive=False, visible=True) # Start empty
+
+ with gr.Group(visible=False) as results_group:
+ # with gr.Accordion("Open to See the Result", open=False) as results:
+ # with gr.Row():
+ # output_summary = gr.Markdown(elem_id="output-summary")
+ # output_flag = gr.Markdown(elem_id="output-flag")
+
+ # gr.Markdown("---")
+
+ with gr.Accordion("Open to See the Output Table", open=True) as table_accordion:
+ output_table = gr.HTML(render=True)
+ #with gr.Row():
+ #output_type = gr.Dropdown(choices=["Excel", "JSON", "TXT"], label="Select Output Format", value="Excel")
+ #download_button = gr.Button("โฌ๏ธ Download Output")
+ #download_file = gr.File(label="Download File Here",visible=False)
+ # Use gr.Markdown to add a visual space
+ gr.Markdown(" ") # A simple blank markdown can create space
+
+ report_button = gr.Button("Report inaccurate output to receive 1 extra free query",elem_id="run-btn")
+ report_textbox = gr.Textbox(
+ label="Describe the issue",
+ lines=4,
+ placeholder="e.g. DQ981467: it gives me unknown when I can in fact search it on NCBI \n DQ981467: cannot find the result in batch output when the live processing did show already processed",
+ visible=False)
+ submit_report_button = gr.Button("Submit", visible=False, elem_id="run-btn")
+ status_report = gr.Markdown(visible=False)
+
+ # Use gr.Markdown to add a visual space
+ gr.Markdown(" ") # A simple blank markdown can create space
+
+ download_file = gr.File(label="Download File Here", visible=False, interactive=True)
+
+ gr.Markdown(" ") # A simple blank markdown can create space
+
+ with gr.Group(visible=True, elem_id="nps-overlay") as nps_modal:
+ #with gr.Column(elem_id="nps-box"):
+ with gr.Group(elem_id="nps-container"):
+ gr.Markdown("### How likely are you to recommend this tool to a colleague or peer?")
+ # # Use gr.Radio to create clickable buttons
+ with gr.Column(elem_id="nps-radio-container"):
+ nps_radio = gr.Radio(
+ choices=[str(i) for i in range(11)],
+ label="Select score:",
+ interactive=True,
+ container=False
+ )
+
+ # The "Not likely" and "Extremely likely" labels
+ with gr.Row(elem_id="nps-labels-row"):
+ gr.Markdown("Not likely")
+ gr.Markdown("Extremely likely")
+
+ nps_submit = gr.Button("Submit", elem_id="nps-submit-button")
+ nps_output = gr.Textbox(label="", interactive=False, visible=True)
+ gr.Markdown(" ") # A simple blank markdown can create space
+
+ progress_box = gr.Textbox(label="Live Processing Log", lines=20, interactive=False)
+
+ gr.Markdown("---")
+
+ # gr.Markdown("### ๐ฌ Feedback (required)")
+ # q1 = gr.Textbox(label="1๏ธโฃ Was the inferred location accurate or helpful? Please explain.")
+ # q2 = gr.Textbox(label="2๏ธโฃ What would improve your experience with this tool?")
+ # contact = gr.Textbox(label="๐ง Your email or institution (optional)")
+ # submit_feedback = gr.Button("โ
Submit Feedback")
+ # feedback_status = gr.Markdown()
+
+ # Functions
+ # def toggle_input_mode(mode):
+ # if mode == "Single Accession":
+ # return gr.update(visible=True), gr.update(visible=False)
+ # else:
+ # return gr.update(visible=False), gr.update(visible=True)
+ # def show_email_textbox():
+ # # Return a gr.update() to make the textbox visible and set the message
+ # return gr.update(visible=True), gr.update(value="Give your email to download excel output and 20+ more free samples", visible=True)
+
+ def classify_with_loading():
+ return gr.update(value="โณ Please wait... processing...",visible=True) # Show processing message
+
+ # def classify_dynamic(single_accession, file, text, resume, email, mode):
+ # if mode == "Single Accession":
+ # return classify_main(single_accession) + (gr.update(visible=False),)
+ # else:
+ # #return summarize_batch(file, text) + (gr.update(visible=False),) # Hide processing message
+ # return classify_mulAcc(file, text, resume) + (gr.update(visible=False),) # Hide processing message
+ # Logging helpers defined early to avoid NameError
+
+
+ # def classify_dynamic(single_accession, file, text, resume, email, mode):
+ # if mode == "Single Accession":
+ # return classify_main(single_accession) + (gr.update(value="", visible=False),)
+ # else:
+ # return classify_mulAcc(file, text, resume, email, log_callback=real_time_logger, log_collector=log_collector)
+
+ # for single accession
+ # def classify_main(accession):
+ # #table, summary, labelAncient_Modern, explain_label = mtdna_backend.summarize_results(accession)
+ # table = mtdna_backend.summarize_results(accession)
+ # #flag_output = f"### ๐บ Ancient/Modern Flag\n**{labelAncient_Modern}**\n\n_Explanation:_ {explain_label}"
+ # return (
+ # #table,
+ # make_html_table(table),
+ # # summary,
+ # # flag_output,
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # gr.update(visible=False)
+ # )
+
+ #stop_flag = gr.State(value=False)
+ #stop_flag = StopFlag()
+
+ # def stop_batch(stop_flag):
+ # stop_flag.value = True
+ # return gr.update(value="โ Stopping...", visible=True), stop_flag
+ active_processes = []
+ def stop_batch():
+ global_stop_flag.value = True
+ return gr.update(value="โ Stopping...", visible=True)
+
+ # def threaded_batch_runner(file, text, email):
+ # global_stop_flag.value = False
+ # log_lines = []
+
+ # def update_log(line):
+ # log_lines.append(line)
+ # yield (
+ # gr.update(visible=False), # output_table (not yet)
+ # gr.update(visible=False), # results_group
+ # gr.update(visible=False), # download_file
+ # gr.update(visible=False), # usage_display
+ # gr.update(value="โณ Still processing...", visible=True), # status
+ # gr.update(value="\n".join(log_lines)) # progress_box
+ # )
+
+ # # Start a dummy update to say "Starting..."
+ # yield from update_log("๐ Starting batch processing...")
+
+ # rows, file_path, count, final_log, warning = mtdna_backend.summarize_batch(
+ # file=file,
+ # raw_text=text,
+ # resume_file=None,
+ # user_email=email,
+ # stop_flag=global_stop_flag,
+ # yield_callback=lambda line: (yield from update_log(line))
+ # )
+
+ # html = make_html_table(rows)
+ # file_update = gr.update(value=file_path, visible=True) if os.path.exists(file_path) else gr.update(visible=False)
+ # usage_or_warning_text = f"**{count}** samples used by this email." if email.strip() else warning
+
+ # yield (
+ # html,
+ # gr.update(visible=True), # results_group
+ # file_update, # download_file
+ # gr.update(value=usage_or_warning_text, visible=True),
+ # gr.update(value="โ
Done", visible=True),
+ # gr.update(value=final_log)
+ # )
+
+ # def threaded_batch_runner(file=None, text="", email=""):
+ # print("๐ง EMAIL RECEIVED:", email)
+ # import tempfile
+ # from mtdna_backend import (
+ # extract_accessions_from_input,
+ # summarize_results,
+ # save_to_excel,
+ # hash_user_id,
+ # increment_usage,
+ # )
+ # import os
+
+ # global_stop_flag.value = False # reset stop flag
+
+ # tmp_dir = tempfile.mkdtemp()
+ # output_file_path = os.path.join(tmp_dir, "batch_output_live.xlsx")
+ # limited_acc = 50 + (10 if email.strip() else 0)
+
+ # # Step 1: Parse input
+ # accessions, error = extract_accessions_from_input(file, text)
+ # print(accessions)
+ # if error:
+ # yield (
+ # "", # output_table
+ # gr.update(visible=False), # results_group
+ # gr.update(visible=False), # download_file
+ # "", # usage_display
+ # "โ Error", # status
+ # str(error) # progress_box
+ # )
+ # return
+
+ # total = len(accessions)
+ # if total > limited_acc:
+ # accessions = accessions[:limited_acc]
+ # warning = f"โ ๏ธ Only processing first {limited_acc} accessions."
+ # else:
+ # warning = f"โ
All {total} accessions will be processed."
+
+ # all_rows = []
+ # processed_accessions = 0 # โ
tracks how many accessions were processed
+ # email_tracked = False
+ # log_lines = []
+
+ # # Step 2: Loop through accessions
+ # for i, acc in enumerate(accessions):
+ # if global_stop_flag.value:
+ # log_lines.append(f"๐ Stopped at {acc} ({i+1}/{total})")
+ # usage_text = ""
+ # if email.strip() and not email_tracked:
+ # # user_hash = hash_user_id(email)
+ # # usage_count = increment_usage(user_hash, len(all_rows))
+ # print("print(processed_accessions at stop) ",processed_accessions)
+ # usage_count = increment_usage(email, processed_accessions)
+ # email_tracked = True
+ # 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."
+ # else:
+ # usage_text = f"The limited accession is 50. The user has used {processed_accessions}, and only {50-processed_accessions} left."
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(value=output_file_path, visible=True),
+ # gr.update(value=usage_text, visible=True),
+ # "๐ Stopped",
+ # "\n".join(log_lines)
+ # )
+ # return
+
+ # log_lines.append(f"[{i+1}/{total}] Processing {acc}")
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # "",
+ # "โณ Processing...",
+ # "\n".join(log_lines)
+ # )
+
+ # try:
+ # print(acc)
+ # rows = summarize_results(acc)
+ # all_rows.extend(rows)
+ # processed_accessions += 1 # โ
count only successful accessions
+ # save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
+ # log_lines.append(f"โ
Processed {acc} ({i+1}/{total})")
+ # except Exception as e:
+ # log_lines.append(f"โ Failed to process {acc}: {e}")
+
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # "",
+ # "โณ Processing...",
+ # "\n".join(log_lines)
+ # )
+
+ # # Final update
+ # usage_text = ""
+
+ # if email.strip() and not email_tracked:
+ # # user_hash = hash_user_id(email)
+ # # usage_count = increment_usage(user_hash, len(all_rows))
+ # print("print(processed_accessions final) ",processed_accessions)
+ # usage_count = increment_usage(email, processed_accessions)
+ # 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."
+ # elif not email.strip():
+ # usage_text = f"The limited accession is 50. The user has used {processed_accessions}, and only {50-processed_accessions} left."
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(value=output_file_path, visible=True),
+ # gr.update(value=usage_text, visible=True),
+ # "โ
Done",
+ # "\n".join(log_lines)
+ # )
+ def submit_nps(email,nps_score):
+ if nps_score is None:
+ return "โ Please select a score before submitting."
+ log_submission_to_gsheet(email, [], nps_score)
+ return "โ
Thanks for submitting your feedback!"
+
+ def log_submission_to_gsheet(email, samples, nps_score=None):
+ from datetime import datetime, timezone
+ import json, os, gspread
+ from oauth2client.service_account import ServiceAccountCredentials
+ import uuid
+
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
+ if not email.strip():
+ email = f"anonymous_{str(uuid.uuid4())[:8]}"
+
+ try:
+ creds_dict = json.loads(os.environ["GCP_CREDS_JSON"])
+ scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
+ creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope)
+ client = gspread.authorize(creds)
+
+ sheet = client.open("user_usage_log")
+ worksheet = sheet.sheet1 # Main sheet
+
+ data = worksheet.get_all_values()
+ headers = data[0]
+ email_col = headers.index("email")
+ samples_col = headers.index("samples")
+ recent_time_col = headers.index("recent_time")
+ nps_col = headers.index("nps_score") if "nps_score" in headers else -1
+ print("this is nps col: ", nps_col)
+ # Step 1: Find row matching the email
+ for i, row in enumerate(data[1:], start=2): # start=2 for correct row indexing
+ if row[email_col].strip().lower() == email.strip().lower():
+ old_samples = row[samples_col].strip() if len(row) > samples_col else ""
+ old_sample_list = [s.strip() for s in old_samples.split(",") if s.strip()]
+ all_samples = list(dict.fromkeys(old_sample_list + samples)) # deduplicate while preserving order
+ new_sample_string = ", ".join(all_samples)
+
+ # Update recent_time to store history
+ old_timestamp = row[recent_time_col].strip() if len(row) > recent_time_col else ""
+ if old_timestamp:
+ new_timestamp = f"{old_timestamp}, {timestamp}"
+ else:
+ new_timestamp = timestamp
+
+ worksheet.update_cell(i, samples_col + 1, new_sample_string)
+ worksheet.update_cell(i, recent_time_col + 1, str(new_timestamp))
+ if nps_score is not None:
+ print("this is nps score:", nps_score)
+ old_nps = row[nps_col].strip() if len(row) > nps_col else ""
+ if old_nps:
+ new_nps = f"{old_nps},{nps_score}"
+ else:
+ new_nps = str(nps_score)
+ worksheet.update_cell(i, nps_col + 1, str(new_nps))
+
+ print(f"โ
Updated existing user row for: {email}")
+ return
+
+ # Step 2: If email not found, add new row
+ new_row = [""] * len(headers)
+ new_row[email_col] = email
+ new_row[samples_col] = ", ".join(samples)
+ new_row[recent_time_col] = timestamp
+ if nps_col != -1:
+ if len(new_row) <= nps_col:
+ new_row.extend([""] * (nps_col + 1 - len(new_row)))
+ new_row[nps_col] = str(nps_score) if nps_score is not None else ""
+ worksheet.append_row(new_row)
+ print(f"โ
Appended new user row for: {email}")
+
+ except Exception as e:
+ print(f"โ Failed to log submission to Google Sheets: {e}")
+
+
+ import multiprocessing
+ import time
+
+ def run_with_timeout(func, args=(), kwargs={}, timeout=30, stop_value=None):
+ """
+ Runs func in a separate process with optional timeout.
+ If stop_value is provided and becomes True during execution, the process is killed early.
+ """
+ def wrapper(q, *args, **kwargs):
+ try:
+ result = func(*args, **kwargs)
+ q.put((True, result))
+ except Exception as e:
+ q.put((False, e))
+
+ q = multiprocessing.Queue()
+ p = multiprocessing.Process(target=wrapper, args=(q, *args), kwargs=kwargs)
+ active_processes.append(p) # โ
track it
+ p.start()
+
+ start_time = time.time()
+ while p.is_alive():
+ # Timeout check
+ if timeout is not None and (time.time() - start_time) > timeout:
+ p.terminate()
+ p.join()
+ print(f"โฑ๏ธ Timeout exceeded ({timeout} sec) โ function killed.")
+ return False, None
+
+ # Stop flag check
+ # if stop_value is not None and stop_value.value:
+ # p.terminate()
+ # p.join()
+ # print("๐ Stop flag detected โ function killed early.")
+ # return False, None
+ if stop_value is not None and stop_value.value:
+ print("๐ Stop flag detected โ waiting for child to exit gracefully.")
+ p.join(timeout=3) # short wait for graceful exit
+ if p.is_alive():
+ print("โ ๏ธ Child still alive, forcing termination.")
+ p.terminate()
+ p.join(timeout=2)
+ return False, None
+ time.sleep(0.1) # avoid busy waiting
+
+ # Process finished naturally
+ if not q.empty():
+ success, result = q.get()
+ if success:
+ return True, result
+ else:
+ raise result
+
+ return False, None
+ def cleanup_processes():
+ global active_processes
+ print("inside cleanup process and number of active process: ", len(active_processes))
+ for p in active_processes:
+ if p.is_alive():
+ try:
+ p.terminate()
+ p.join(timeout=2)
+ except Exception:
+ pass
+ active_processes = []
+
+
+ def threaded_batch_runner(file=None, text="", email=""):
+ print("clean everything remain before running")
+ cleanup_processes()
+ print("๐ง EMAIL RECEIVED:", repr(email))
+ import tempfile
+ from mtdna_backend import (
+ extract_accessions_from_input,
+ summarize_results,
+ save_to_excel,
+ increment_usage,
+ )
+ import os
+
+ global_stop_flag.value = False # reset stop flag
+ #active_processes = []
+
+ tmp_dir = tempfile.mkdtemp()
+ output_file_path = os.path.join(tmp_dir, "batch_output_live.xlsx")
+ #output_file_path = "/mnt/data/batch_output_live.xlsx"
+ all_rows = []
+ processed_accessions = 0 # โ
track successful accessions
+ email_tracked = False
+ log_lines = []
+ usage_text = ""
+ processed_info = ""
+ if not email.strip():
+ output_file_path = None#"Write your email so that you can download the outputs."
+ log_lines.append("๐ฅ Provide your email to receive a downloadable Excel report and get 20 more free queries.")
+ limited_acc = 30
+ if email.strip():
+ usage_count, max_allowed = increment_usage(email, processed_accessions)
+ if int(usage_count) >= int(max_allowed):
+ log_lines.append("โ You have reached your quota. Please contact us to unlock more.")
+
+ # Minimal blank yield to trigger UI rendering
+ yield (
+ make_html_table([]), # 1 output_table
+ gr.update(visible=True), # 2 results_group
+ gr.update(visible=False), # 3 download_file
+ gr.update(value="", visible=True), # 4 usage_display
+ "โ๏ธ Quota limit", # 5 status
+ "โ๏ธ Quota limit", # 6 progress_box
+ gr.update(visible=True), # 7 run_button
+ gr.update(visible=False), # 8 stop_button
+ gr.update(visible=True), # 9 reset_button
+ gr.update(visible=True), # 10 raw_text
+ gr.update(visible=True), # 11 file_upload
+ gr.update(value=processed_info, visible=False), # 12 processed_info
+ gr.update(visible=False) # 13 nps_modal
+ )
+
+ # Actual warning frame
+ yield (
+ make_html_table([]),
+ gr.update(visible=False),
+ gr.update(visible=False),
+ gr.update(value="โ You have reached your quota. Please contact us to unlock more.", visible=True),
+ "โ Quota Exceeded",
+ "\n".join(log_lines),
+ gr.update(visible=True),
+ gr.update(visible=False),
+ gr.update(visible=True),
+ gr.update(visible=True),
+ gr.update(visible=True),
+ gr.update(value="", visible=False),
+ gr.update(visible=False)
+ )
+ return
+ limited_acc = int(max_allowed-usage_count)
+
+ # Step 1: Parse input
+ accessions, invalid_accessions, error = extract_accessions_from_input(file, text)
+ total = len(accessions)
+ print("total len original accessions: ", total)
+ if total > 0:
+ if total > limited_acc:
+ accessions = accessions[:limited_acc]
+ if invalid_accessions:
+ warning = f"โ ๏ธ Only processing first {limited_acc} accessions. โ ๏ธ Invalid accessions: {', '.join(invalid_accessions)}."
+
+ else:
+ warning = f"โ ๏ธ Only processing first {limited_acc} accessions."
+ else:
+ if invalid_accessions:
+ warning = f"โ
All {total} accessions will be processed. โ ๏ธ Invalid accessions: {', '.join(invalid_accessions)}."
+ else:
+ warning = f"โ
All {total} accessions will be processed."
+ else:
+ if invalid_accessions:
+ warning = f"โ ๏ธ Invalid accessions: {', '.join(invalid_accessions)}."
+ else:
+ warning = "Nothing to processing"
+ if len(accessions) == 1:
+ processed_info = warning + "\n" +f"Processed accessions: {accessions[0]}"
+ else:
+ if len(accessions) > 0:
+ processed_info = warning + "\n" +f"Processed accessions: {accessions[0]}...{accessions[-1]}"
+ elif len(accessions) == 0:
+ processed_info = warning
+ else:
+ processed_info = "โ ๏ธ Cannot process the input"
+ ### NEW: Hide inputs, show processed_info at start
+ yield (
+ make_html_table(all_rows), # output_table
+ gr.update(visible=False), # results_group
+ gr.update(visible=False), # download_file
+ "", # usage_display
+ "โณ Processing...", # status
+ "", # progess_box
+ gr.update(visible=False), # run_button,
+ gr.update(visible=True), # show stop button
+ gr.update(visible=True), # show reset button
+ gr.update(visible=True), # hide raw_text
+ gr.update(visible=True), # hide file_upload
+ gr.update(value=processed_info, visible=True), # processed_info
+ gr.update(visible=False) # hide NPS modal at start
+ )
+
+ log_submission_to_gsheet(email, accessions)
+
+ print("๐งช Accessions received:", accessions)
+ if error:
+ yield (
+ "", # 1 output_table
+ gr.update(visible=False), # 2 results_group
+ gr.update(visible=False), # 3 download_file
+ "", # 4 usage_display
+ "โ Error", # 5 status
+ str(error), # 6 progress_box
+ gr.update(visible=True), # 7 run_button
+ gr.update(visible=False), # 8 stop_button
+ gr.update(visible=True), # 9 reset_button
+ gr.update(visible=True), # 10 raw_text
+ gr.update(visible=True), # 11 file_upload
+ gr.update(value="", visible=False), # 12 processed_info
+ gr.update(visible=False) # 13 nps_modal
+ )
+ return
+
+ # all_rows = []
+ # processed_accessions = 0 # โ
track successful accessions
+ # email_tracked = False
+ # log_lines = []
+ # if not email.strip():
+ # output_file_path = None#"Write your email so that you can download the outputs."
+ # log_lines.append("๐ฅ Provide your email to receive a downloadable Excel report and get 20 more free queries.")
+ # if email.strip():
+ # usage_count, max_allowed = increment_usage(email, processed_accessions)
+ # if int(usage_count) > int(max_allowed):
+ # log_lines.append("โ You have reached your quota. Please contact us to unlock more.")
+
+ # # Minimal blank yield to trigger UI rendering
+ # yield (
+ # make_html_table([]),
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # gr.update(value="", visible=True),
+ # "โ๏ธ Quota limit",
+ # "โ๏ธ Quota limit"
+ # )
+
+ # # Actual warning frame
+ # yield (
+ # make_html_table([]),
+ # gr.update(visible=False),
+ # gr.update(visible=False),
+ # gr.update(value="โ You have reached your quota. Please contact us to unlock more.", visible=True),
+ # "โ Quota Exceeded",
+ # "\n".join(log_lines)
+ # )
+ # return
+
+ # Step 2: Loop through accessions
+ for i, acc in enumerate(accessions):
+ try:
+ if global_stop_flag.value:
+ log_lines.append(f"๐ Stopped at {acc} ({i+1}/{total})")
+ usage_text = ""
+
+ if email.strip() and not email_tracked:
+ print(f"๐งช increment_usage at STOP: {email=} {processed_accessions=}")
+ usage_count, max_allowed = increment_usage(email, processed_accessions)
+ email_tracked = True
+ usage_text = f"**{usage_count}**/{max_allowed} allowed 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."
+ else:
+ usage_text = f"The limited accession is 30. The user has used {processed_accessions}, and only {30 - processed_accessions} left."
+
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # #gr.update(value=output_file_path, visible=True),
+ # gr.update(value=output_file_path, visible=bool(output_file_path)),
+ # gr.update(value=usage_text, visible=True),
+ # "๐ Stopped",
+ # "\n".join(log_lines)
+ # )
+ cleanup_processes() # โ
hard kill anything left
+ yield (
+ make_html_table(all_rows),
+ gr.update(visible=True), # results_group
+ gr.update(value=output_file_path, visible=bool(output_file_path)), # download_file
+ gr.update(value=usage_text, visible=True), # usage_display
+ "๐ Stopped", # "โ
Done" or "๐ Stopped"
+ "\n".join(log_lines),
+ gr.update(visible=False), # run_button
+ gr.update(visible=False), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # raw_text
+ gr.update(visible=True), # file_upload
+ gr.update(value=processed_info, visible=False), # processed_info
+ gr.update(visible=True) # NPS modal now visible
+ )
+
+ return
+
+ log_lines.append(f"[{i+1}/{total}] Processing {acc}")
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # "",
+ # "โณ Processing...",
+ # "\n".join(log_lines)
+ # )
+ # Hide inputs, show processed_info at start
+ yield (
+ make_html_table(all_rows), # output_table
+ gr.update(visible=True), # results_group
+ gr.update(visible=False), # download_file
+ "", # usage_display
+ "โณ Processing...", # status
+ "\n".join(log_lines), # progress_box
+ gr.update(visible=False), # run_button
+ gr.update(visible=True), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # hide raw_text
+ gr.update(visible=True), # hide file_upload
+ gr.update(value=processed_info, visible=True), # processed_info
+ gr.update(visible=False) # hide NPS modal at start
+ )
+
+
+ # try:
+ # print("๐ Processing accession:", acc)
+ # rows = summarize_results(acc)
+ # all_rows.extend(rows)
+ # processed_accessions += 1 # โ
only count success
+ # if email.strip():
+ # save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
+ # log_lines.append(f"โ
Processed {acc} ({i+1}/{total})")
+ print("๐ Processing accession:", acc)
+ # --- Before calling summarize_results ---
+ samples_left = total - i # including current one
+ estimated_seconds_left = samples_left * 100 # your observed average per sample
+
+ log_lines.append(
+ f"Running... usually ~100s per sample"
+ )
+ log_lines.append(
+ f"โณ Estimated time left: ~{estimated_seconds_left} seconds ({samples_left} sample{'s' if samples_left > 1 else ''} remaining)"
+ )
+
+ # Yield update to UI before the heavy pipeline call
+ yield (
+ make_html_table(all_rows),
+ gr.update(visible=True), # results_group
+ gr.update(visible=False), # download_file
+ "", # usage_display
+ "โณ Processing...", # status
+ "\n".join(log_lines), # progress_box
+ gr.update(visible=False), # run_button
+ gr.update(visible=True), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # raw_text
+ gr.update(visible=True), # file_upload
+ gr.update(value=processed_info, visible=True), # processed_info
+ gr.update(visible=False) # hide NPS modal
+ )
+
+ # Run summarize_results in a separate process with stop flag support
+ success, rows = run_with_timeout(
+ summarize_results,
+ args=(acc,),
+ timeout=None, # or set max seconds per sample if you want
+ stop_value=global_stop_flag
+ )
+
+ # If stop was pressed during this accession
+ if not success and global_stop_flag.value:
+ log_lines.append(f"๐ Cancelled {acc} before completion")
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # "",
+ # "๐ Stopped",
+ # "\n".join(log_lines)
+ # )
+ cleanup_processes() # โ
hard kill anything left
+ yield (
+ make_html_table(all_rows),
+ gr.update(visible=True), # results_group
+ gr.update(value=output_file_path, visible=bool(output_file_path)), # download_file
+ gr.update(value=usage_text, visible=True), # usage_display
+ "๐ Stopped", # "โ
Done" or "๐ Stopped"
+ "\n".join(log_lines),
+ gr.update(visible=False), # run_button
+ gr.update(visible=False), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # raw_text
+ gr.update(visible=True), # file_upload
+ gr.update(value="", visible=False), # processed_info
+ gr.update(visible=True) # NPS modal now visible
+ )
+
+ break # stop processing entirely
+
+ # If it finished normally
+ if success and rows:
+ all_rows.extend(rows)
+ processed_accessions += 1
+ if email.strip():
+ save_to_excel(all_rows, "", "", output_file_path, is_resume=False)
+ log_lines.append(f"โ
Processed {acc} ({i+1}/{total})")
+ else:
+ # If it failed due to timeout or other error
+ if not global_stop_flag.value:
+ log_lines.append(f"โ ๏ธ Skipped {acc} due to timeout or error")
+
+ # Always yield updated logs after each attempt
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # gr.update(visible=False),
+ # "",
+ # "โณ Processing...",
+ # "\n".join(log_lines)
+ # )
+ yield (
+ make_html_table(all_rows), # output_table
+ gr.update(visible=True), # results_group
+ gr.update(visible=False), # download_file
+ "", # usage_display
+ "โณ Processing...", # status
+ "\n".join(log_lines), # progress_box
+ gr.update(visible=False), # run_button
+ gr.update(visible=True), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # hide raw_text
+ gr.update(visible=True), # hide file_upload
+ gr.update(value=processed_info, visible=True), # processed_info
+ gr.update(visible=False) # hide NPS modal at start
+ )
+
+
+ except Exception as e:
+ log_lines.append(f"โ Failed to process {acc}: {e}. Report on the box above so that we won't count this bad one for you (email required).")
+ yield (
+ make_html_table(all_rows), # output_table
+ gr.update(visible=True), # results_group
+ gr.update(visible=False), # download_file
+ "", # usage_display
+ "โณ Processing...", # status
+ "\n".join(log_lines), # progress_box
+ gr.update(visible=False), # run_button
+ gr.update(visible=True), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # hide raw_text
+ gr.update(visible=True), # hide file_upload
+ gr.update(value=processed_info, visible=True), # processed_info
+ gr.update(visible=False) # hide NPS modal at start
+ )
+
+ # Step 3: Final usage update
+ usage_text = ""
+ if email.strip() and not email_tracked:
+ print(f"๐งช increment_usage at END: {email=} {processed_accessions=}")
+ usage_count, max_allowed = increment_usage(email, processed_accessions)
+ email_tracked = True
+ usage_text = f"**{usage_count}**/{max_allowed} allowed 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."
+ elif not email.strip():
+ usage_text = f"The limited accession is 30. The user has used {processed_accessions}, and only {30 - processed_accessions} left."
+
+ # yield (
+ # make_html_table(all_rows),
+ # gr.update(visible=True),
+ # #gr.update(value=output_file_path, visible=True),
+ # gr.update(value=output_file_path, visible=bool(output_file_path)),
+ # gr.update(value=usage_text, visible=True),
+ # "โ
Done",
+ # "\n".join(log_lines)
+ # )
+ yield (
+ make_html_table(all_rows),
+ gr.update(visible=True), # results_group
+ gr.update(value=output_file_path, visible=bool(output_file_path)), # download_file
+ gr.update(value=usage_text, visible=True), # usage_display
+ "โ
Done", # "โ
Done" or "๐ Stopped"
+ "\n".join(log_lines),
+ gr.update(visible=False), # run_button
+ gr.update(visible=False), # stop_button
+ gr.update(visible=True), # reset_button
+ gr.update(visible=True), # raw_text
+ gr.update(visible=True), # file_upload
+ gr.update(value=processed_info, visible=True), # processed_info
+ gr.update(visible=True) # NPS modal now visible
+ )
+
+ # SUBMIT REPORT UI
+ # 1. Google Sheets setup
+ def get_worksheet(sheet_name="Report"):
+ import os, json
+ import gspread
+ from oauth2client.service_account import ServiceAccountCredentials
+ try:
+ creds_dict = json.loads(os.environ["GCP_CREDS_JSON"])
+ scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
+ creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_dict, scope)
+ client = gspread.authorize(creds)
+ sheet = client.open(sheet_name).sheet1
+ return sheet
+ except Exception as e:
+ print(f"โ Error loading Google Sheet '{sheet_name}':", e)
+ return None
+
+ # 2. Submit function to send report to the Google Sheet
+ def submit_report(report_text,user_email=""):
+ try:
+ sheet = get_worksheet()
+ # โ
Parse the report_text (each line like 'ACCESSION: message')
+ lines = report_text.strip().split('\n')
+ user = ""
+ if user_email.strip():
+ user = user_email
+ for line in lines:
+ if ':' in line:
+ accession, message = line.split(':', 1)
+ sheet.append_row([accession.strip(), message.strip(), user.strip()])
+ return "โ
Report submitted successfully!"
+ except Exception as e:
+ return f"โ Error submitting report: {str(e)}"
+ def show_report_ui():
+ return gr.update(visible=True), gr.update(visible=True), gr.update(visible=False)
+
+ def handle_submission(text,user_email):
+ msg = submit_report(text, user_email)
+ return gr.update(value=msg, visible=True), gr.update(visible=False), gr.update(visible=False)
+ # def threaded_batch_runner(file=None, text="", email=""):
+ # global_stop_flag.value = False
+
+ # # Dummy test output that matches expected schema
+ # return (
+ # "โ
Dummy output table
", # HTML string
+ # gr.update(visible=True), # Group visibility
+ # gr.update(visible=False), # Download file
+ # "**0** samples used.", # Markdown
+ # "โ
Done", # Status string
+ # "Processing finished." # Progress string
+ # )
+
+
+ # def classify_mulAcc(file, text, resume, email, log_callback=None, log_collector=None):
+ # stop_flag.value = False
+ # return threaded_batch_runner(file, text, resume, email, status, stop_flag, log_callback=log_callback, log_collector=log_collector)
+
+
+ def make_html_table(rows):
+ # html = """
+ #
+ #
+ #
+ #
+ #
+ # """
+ html = """
+
+
+
+ """
+
+ headers = ["No.", "Sample ID", "Predicted Country", "Country Explanation", "Predicted Sample Type", "Sample Type Explanation", "Sources", "Time cost"]
+ html += "".join(
+ f"{h} | "
+ for h in headers
+ )
+ html += ""
+
+ for idx, row in enumerate(rows, 1): # start numbering from 1
+ html += ""
+ html += f"{idx} | " # "No." column
+ for i, col in enumerate(row):
+ header = headers[i]
+ style = "padding: 10px; border: 1px solid #555; vertical-align: top;"
+
+ # For specific columns like Haplogroup, force nowrap
+ if header in ["Country Explanation", "Sample Type Explanation"]:
+ style += " max-width: 400px; word-wrap: break-word; white-space: normal;"
+ elif header in ["Sample ID", "Predicted Country", "Predicted Sample Type", "Time cost"]:
+ style += " white-space: nowrap; text-overflow: ellipsis; max-width: 200px; overflow: hidden;"
+
+ # if header == "Sources" and isinstance(col, str) and col.strip().lower().startswith("http"):
+ # col = f"{col}"
+
+ #html += f"{col} | "
+ if header == "Sources" and isinstance(col, str):
+ links = [f"{url.strip()}" for url in col.strip().split("\n") if url.strip()]
+ col = "- "+"
- ".join(links)
+ elif isinstance(col, str):
+ # lines = []
+ # for line in col.split("\n"):
+ # line = line.strip()
+ # if not line:
+ # continue
+ # if line.lower().startswith("rag_llm-"):
+ # content = line[len("rag_llm-"):].strip()
+ # line = f"{content} (Method: RAG_LLM)"
+ # lines.append(f"- {line}")
+ col = col.replace("\n", "
")
+ #col = col.replace("\t", " ")
+ #col = "
".join(lines)
+
+ html += f"{col} | "
+ html += "
"
+
+ html += "
"
+ return html
+
+
+ # def reset_fields():
+ # global_stop_flag.value = False # ๐ก Add this to reset the flag
+ # return (
+ # #gr.update(value=""), # single_accession
+ # gr.update(value=""), # raw_text
+ # gr.update(value=None), # file_upload
+ # #gr.update(value=None), # resume_file
+ # #gr.update(value="Single Accession"), # inputMode
+ # gr.update(value=[], visible=True), # output_table
+ # # gr.update(value="", visible=True), # output_summary
+ # # gr.update(value="", visible=True), # output_flag
+ # gr.update(visible=False), # status
+ # gr.update(visible=False), # results_group
+ # gr.update(value="", visible=False), # usage_display
+ # gr.update(value="", visible=False), # progress_box
+ # )
+ # def reset_fields():
+ # global_stop_flag.value = True # Reset the stop flag
+
+ # return (
+ # gr.update(value=""), # raw_text
+ # gr.update(value=None), # file_upload
+ # gr.update(value=[], visible=True), # output_table
+ # gr.update(value="", visible=True), # status โ reset and make visible again
+ # gr.update(visible=False), # results_group
+ # gr.update(value="", visible=True), # usage_display โ reset and make visible again
+ # gr.update(value="", visible=True), # progress_box โ reset AND visible!
+ # # report-related reset below
+ # gr.update(value="", visible=False), # report_textbox
+ # gr.update(visible=False), # submit_report_button
+ # gr.update(value="", visible=False), # status_report
+ # gr.update(value=0), # nps_slider
+ # gr.update(value="", visible=False) # nps_output
+
+ # )
+ def reset_fields():
+ global_stop_flag.value = True # Stop any running job
+ cleanup_processes() # โ
same cleanup here
+
+ return (
+ gr.update(value="", visible=True), # raw_text
+ gr.update(value=None, visible=True), # file_upload
+ gr.update(value=[], visible=True), # output_table
+ gr.update(value="", visible=True), # status
+ gr.update(visible=False), # results_group
+ gr.update(value="", visible=True), # usage_display
+ gr.update(value="", visible=True), # progress_box
+ gr.update(value="", visible=False), # report_textbox
+ gr.update(visible=False), # submit_report_button
+ gr.update(value="", visible=False), # status_report
+ gr.update(value="", visible=False), # processed_info
+ gr.update(visible=False), # hide NPS modal
+ gr.update(visible=True), # run_button โ
restore
+ gr.update(visible=False) # stop button
+ )
+
+
+ #inputMode.change(fn=toggle_input_mode, inputs=inputMode, outputs=[single_input_group, batch_input_group])
+ #run_button.click(fn=classify_with_loading, inputs=[], outputs=[status])
+ # run_button.click(
+ # fn=classify_dynamic,
+ # inputs=[single_accession, file_upload, raw_text, resume_file,user_email,inputMode],
+ # outputs=[output_table,
+ # #output_summary, output_flag,
+ # results_group, download_file, usage_display,status, progress_box]
+ # )
+
+ # run_button.click(
+ # fn=threaded_batch_runner,
+ # #inputs=[file_upload, raw_text, resume_file, user_email],
+ # inputs=[file_upload, raw_text, user_email],
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box]
+ # )
+ # run_button.click(
+ # fn=threaded_batch_runner,
+ # inputs=[file_upload, raw_text, user_email],
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
+ # every=0.5 # <-- this tells Gradio to expect streaming
+ # )
+ # output_table = gr.HTML()
+ # results_group = gr.Group(visible=False)
+ # download_file = gr.File(visible=False)
+ # usage_display = gr.Markdown(visible=False)
+ # status = gr.Markdown(visible=False)
+ # progress_box = gr.Textbox(visible=False)
+
+ # run_button.click(
+ # fn=threaded_batch_runner,
+ # inputs=[file_upload, raw_text, user_email],
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
+ # every=0.5, # streaming enabled
+ # show_progress="full"
+ # )
+
+ # interface.stream(
+ # fn=threaded_batch_runner,
+ # inputs=[file_upload, raw_text, user_email],
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
+ # trigger=run_button,
+ # every=0.5,
+ # show_progress="full",
+ # )
+ interface.queue() # No arguments here!
+
+ # run_button.click(
+ # fn=threaded_batch_runner,
+ # inputs=[file_upload, raw_text, user_email],
+ # outputs=[output_table, results_group, download_file, usage_display, status, progress_box],
+ # concurrency_limit=1, # โ
correct in Gradio 5.x
+ # queue=True, # โ
ensure the queue is used
+ # #every=0.5
+ # )
+ # Link the button to the function
+ # sign_in_button.click(
+ # fn=show_email_textbox,
+ # outputs=[user_email, output_message] # The outputs are the components to be updated
+ # )
+
+ run_button.click(
+ fn=threaded_batch_runner,
+ inputs=[file_upload, raw_text, user_email],
+ outputs=[
+ output_table, # 1
+ results_group, # 2
+ download_file, # 3
+ usage_display, # 4
+ status, # 5
+ progress_box, # 6
+ run_button, # 7
+ stop_button, # 8
+ reset_button, # 9
+ raw_text, # 10
+ file_upload, # 11
+ processed_info, # 12
+ nps_modal # 13
+ ],
+ concurrency_limit=1,
+ queue=True
+ )
+
+
+
+
+
+ stop_button.click(fn=stop_batch, inputs=[], outputs=[status])
+
+ # reset_button.click(
+ # #fn=reset_fields,
+ # fn=lambda: (
+ # gr.update(value=""), gr.update(value=""), gr.update(value=None), gr.update(value=None), gr.update(value="Single Accession"),
+ # gr.update(value=[], visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(value="", visible=False), gr.update(value="", visible=False)
+ # ),
+ # inputs=[],
+ # outputs=[
+ # single_accession, raw_text, file_upload, resume_file,inputMode,
+ # output_table,# output_summary, output_flag,
+ # status, results_group, usage_display, progress_box
+ # ]
+ # )
+ #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)])
+
+ # reset_button.click(
+ # fn=reset_fields,
+ # inputs=[],
+ # #outputs=[raw_text, file_upload, resume_file, output_table, status, results_group, usage_display, progress_box]
+ # outputs=[raw_text, file_upload, output_table, status, results_group, usage_display, progress_box,
+ # report_textbox,
+ # submit_report_button,
+ # status_report, nps_slider, nps_output]
+ # )
+ reset_button.click(
+ fn=reset_fields,
+ inputs=[],
+ outputs=[
+ raw_text,
+ file_upload,
+ output_table,
+ status,
+ results_group,
+ usage_display,
+ progress_box,
+ report_textbox,
+ submit_report_button,
+ status_report,
+ processed_info,
+ nps_modal,
+ run_button,
+ stop_button
+ ]
+ )
+
+
+ # download_button.click(
+ # fn=mtdna_backend.save_batch_output,
+ # #inputs=[output_table, output_summary, output_flag, output_type],
+ # inputs=[output_table, output_type],
+ # outputs=[download_file])
+
+ # submit_feedback.click(
+ # fn=mtdna_backend.store_feedback_to_google_sheets,
+ # inputs=[single_accession, q1, q2, contact], outputs=feedback_status
+ # )
+ report_button.click(fn=show_report_ui, outputs=[report_textbox, submit_report_button, status_report])
+ submit_report_button.click(fn=handle_submission, inputs=[report_textbox, user_email], outputs=[status_report, report_textbox, submit_report_button])
+
+ # submit_feedback.click(
+ # fn=mtdna_backend.store_feedback_to_google_sheets,
+ # inputs=[raw_text, q1, q2, contact],
+ # outputs=[feedback_status]
+ # )
+ nps_submit.click(fn=submit_nps, inputs=[user_email, nps_radio], outputs=[nps_output])
+ # Link each button to submit function
+
+ gr.HTML("""
+
+ """)
+
+ # # Custom CSS styles
+ # gr.HTML("""
+ #
+ # """)
+ with gr.Tab("CURIOUS ABOUT THIS PRODUCT?"):
+ gr.HTML(value=flow_chart)
+
+ with gr.Tab("PRICING"):
+ gr.HTML(value=pricing_html)
+
+
+
+
interface.launch(share=True,debug=True)
\ No newline at end of file