mtDNALocation / app.py
linh-hk's picture
Add No. column to output
f679b73 verified
raw
history blame
70 kB
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", "&nbsp;&nbsp;&nbsp;&nbsp;")
#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)