Spaces:
Running
Running
| 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("""<a href="https://drive.google.com/file/d/1t-TFeIsGVu5Jh3CUZS-VE9jQWzNFCs_c/view?usp=sharing" download target="_blank">Download Example CSV Format</a>""") | |
| # gr.HTML("""<a href="https://docs.google.com/spreadsheets/d/1lKqPp17EfHsshJGZRWEpcNOZlGo3F5qU/edit?usp=sharing&ouid=112390323314156876153&rtpof=true&sd=true" download target="_blank">Download Example Excel Format</a>""") | |
| # 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("""<a href="https://docs.google.com/spreadsheets/d/1lKqPp17EfHsshJGZRWEpcNOZlGo3F5qU/edit?usp=sharing" download target="_blank">Example Excel Input Template</a>""") | |
| 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 an unsatisfactory output for a free credit.",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) | |
| with gr.Group(visible=True, elem_id="nps-overlay") as nps_modal: | |
| with gr.Group(elem_id="nps-container"): | |
| gr.Markdown("### How likely are you to recommend this tool to a colleague or peer?") | |
| # Score options (0-10) | |
| nps_radio = gr.Radio( | |
| choices=[str(i) for i in range(11)], | |
| label="Select score:", | |
| interactive=True, | |
| container=False, | |
| elem_id="nps-radio-container" | |
| ) | |
| # Row for labels under the ends | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("Not likely") | |
| with gr.Column(scale=8): | |
| gr.Markdown("") # spacer | |
| with gr.Column(scale=1): | |
| 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 ( | |
| # "<div>✅ Dummy output table</div>", # 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 = """ | |
| <div style='overflow-x: auto; padding: 10px;'> | |
| <div style='max-height: 400px; overflow-y: auto; border: 1px solid #444; border-radius: 8px;'> | |
| <table style='width:100%; border-collapse: collapse; table-layout: auto; font-size: 14px; color: #f1f1f1; background-color: #1e1e1e;'> | |
| <thead style='position: sticky; top: 0; background-color: #2c2c2c; z-index: 1;'> | |
| <tr> | |
| """ | |
| headers = ["No.", "Sample ID", "Predicted Country", "Country Explanation", "Predicted Sample Type", "Sample Type Explanation", "Sources", "Time cost"] | |
| html += "".join( | |
| f"<th style='padding: 10px; border: 1px solid #555; text-align: left; white-space: nowrap;'>{h}</th>" | |
| for h in headers | |
| ) | |
| html += "</tr></thead><tbody>" | |
| for idx, row in enumerate(rows, 1): # start numbering from 1 | |
| html += "<tr>" | |
| html += f"<td style='padding: 10px; border: 1px solid #555;'>{idx}</td>" # "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: 200px;" | |
| # " white-space: normal;" | |
| # " word-wrap: break-word;" | |
| # " overflow-wrap: break-word;" | |
| # " word-break: break-word;" | |
| # " text-overflow: clip;" # disable ellipsis | |
| # ) | |
| # if 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"<a href='{col}' target='_blank' style='color: #4ea1f3; text-decoration: underline;'>{col}</a>" | |
| #html += f"<td style='{style}'>{col}</td>" | |
| if header == "Sources" and isinstance(col, str): | |
| links = [f"<a href='{url.strip()}' target='_blank' style='color: #4ea1f3; text-decoration: underline;'>{url.strip()}</a>" for url in col.strip().split("\n") if url.strip()] | |
| col = "- "+"<br>- ".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", "<br>") | |
| #col = col.replace("\t", " ") | |
| #col = "<br>".join(lines) | |
| html += f"<td style='{style}'>{col}</td>" | |
| html += "</tr>" | |
| html += "</tbody></table></div></div>" | |
| 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(""" | |
| <style> | |
| body, html { | |
| background-color: #121212 !important; | |
| color: #ffffff !important; | |
| } | |
| .gradio-container, .gr-block, .gr-box, textarea, input, select, .prose, .prose * { | |
| background-color: #1e1e1e !important; | |
| color: #ffffff !important; | |
| border-color: #333 !important; | |
| } | |
| textarea::placeholder, | |
| input::placeholder { | |
| color: #aaa !important; | |
| } | |
| button { | |
| background-color: #2d2d2d !important; | |
| color: #fff !important; | |
| border: 1px solid #444 !important; | |
| } | |
| a { | |
| color: #4ea1f3 !important; | |
| } | |
| /* Shared hover style for the three main buttons */ | |
| #run-btn:hover, #stop-btn:hover, #reset-btn:hover { | |
| border-color: white !important; | |
| box-shadow: 0 0 5px white; | |
| transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out; | |
| } | |
| /* Active click style */ | |
| #run-btn:active, #stop-btn:active, #reset-btn:active { | |
| border-color: white !important; | |
| box-shadow: 0 0 5px white inset; | |
| } | |
| </style> | |
| """) | |
| # # Custom CSS styles | |
| # gr.HTML(""" | |
| # <style> | |
| # /* Ensures both sections are equally spaced with the same background size */ | |
| # #output-summary, #output-flag { | |
| # background-color: #f0f4f8; /* Light Grey for both */ | |
| # padding: 20px; | |
| # border-radius: 10px; | |
| # margin-top: 10px; | |
| # width: 100%; /* Ensure full width */ | |
| # min-height: 150px; /* Ensures both have a minimum height */ | |
| # box-sizing: border-box; /* Prevents padding from increasing size */ | |
| # display: flex; | |
| # flex-direction: column; | |
| # justify-content: space-between; | |
| # } | |
| # /* Specific background colors */ | |
| # #output-summary { | |
| # background-color: #434a4b; | |
| # } | |
| # #output-flag { | |
| # background-color: #141616; | |
| # } | |
| # /* Ensuring they are in a row and evenly spaced */ | |
| # .gradio-row { | |
| # display: flex; | |
| # justify-content: space-between; | |
| # width: 100%; | |
| # } | |
| # </style> | |
| # """) | |
| 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) |