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 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 ( | |
# "<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> | |
# """ | |
html = """ | |
<div style='overflow-x: auto; padding: 10px;'> | |
<div style='max-height: 400px; overflow-y: auto; border: 1px solid #ccc; border-radius: 8px;'> | |
<table style='width:100%; border-collapse: collapse; table-layout: auto; font-size: 14px; color: inherit; background-color: inherit;'> | |
""" | |
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: 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"<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 | |
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) |