#!/usr/bin/env python3 import os, sys, subprocess, shutil, importlib import gradio as gr TARGET_DIR = "/home/user/app/external_core" def run(cmd, **kw): subprocess.check_call(cmd, **kw) def git_probe(url): try: subprocess.check_output(["git", "ls-remote", "--heads", url], stderr=subprocess.STDOUT) return True except subprocess.CalledProcessError: return False def build_authed_url(repo_url, token): if repo_url.startswith("https://github.com/"): return repo_url.replace("https://github.com/", f"https://x-access-token:{token}@github.com/") if repo_url.startswith("https://"): return "https://x-access-token:{}@{}".format(token, repo_url.split("https://",1)[1]) return repo_url def ensure_private_core(): REPO_URL = os.getenv("CORE_REPO_URL") BRANCH = os.getenv("CORE_BRANCH") TOKEN = os.getenv("GIT_TOKEN") def fresh_clone(url): if os.path.isdir(TARGET_DIR): shutil.rmtree(TARGET_DIR) run(["git", "clone", "--depth", "1", "-b", BRANCH, url, TARGET_DIR]) if git_probe(REPO_URL): fresh_clone(REPO_URL) else: if not TOKEN: raise RuntimeError("Repo requires auth but GIT_TOKEN is missing.") authed = build_authed_url(REPO_URL, TOKEN) if not git_probe(authed): raise RuntimeError("Git auth probe failed for the provided token/permissions.") fresh_clone(authed) return TARGET_DIR def import_callable_from_entry(entry, base_path): if base_path not in sys.path: sys.path.insert(0, base_path) module_name, func_name = entry.split(":", 1) mod = importlib.import_module(module_name) return getattr(mod, func_name) CORE_DIR = ensure_private_core() # returns TARGET_DIR ENTRY = os.getenv("CORE_ENTRY", "kinetics_core:simulate") simulate_fn = import_callable_from_entry(ENTRY, CORE_DIR) # -------- Gradio UI -------- with gr.Blocks(title="Kinetics Simulator — Solver") as demo: gr.Markdown("## Kinetics Simulator — Solver") # Centered reaction scheme at 30% width gr.HTML(""" """) gr.Image(value="./reaction.png", show_label=False, interactive=False, elem_id="rxn_img") rtol_state = gr.State(1e-8) atol_state = gr.State(1e-14) ceil_state = gr.State(1e30) with gr.Row(): with gr.Column(): S0 = gr.Number(value=5e-1, label="Initial S (M)") I10 = gr.Number(value=0.0, label="Initial I-1 (M)") I20 = gr.Number(value=0.0, label="Initial I-2 (M)") P0 = gr.Number(value=0.0, label="Initial P (M)") A = gr.Number(value=2e-13, label="A (M)") HD = gr.Number(value=1e-1, label="HD (M)") kHAA1 = gr.Number(value=1e9, label="kHAA1 (M^-1 s^-1)") kHAD1 = gr.Number(value=1e7, label="kHAD1 (M^-1 s^-1)") kHAA2 = gr.Number(value=4e9, label="kHAA2 (M^-1 s^-1)") kHAD2 = gr.Number(value=2e7, label="kHAD2 (M^-1 s^-1)") krearr1 = gr.Number(value=1e6, label="krearr1 (s^-1)") krearr2 = gr.Number(value=1e4, label="krearr2 (s^-1)") T = gr.Number(value=1e5, label="Duration (s)") Npts = gr.Number(value=200, label="Points (t_eval)") run_btn = gr.Button("Run Simulation", variant="primary") with gr.Column(): plot = gr.Plot(label="Concentration evolution") stats = gr.JSON(label="Summary") def run_sim(S0, I10, I20, P0, A, HD, kHAA1, kHAD1, kHAA2, kHAD2, krearr1, krearr2, T, Npts, rtol, atol, ceil): return simulate_fn( S0, I10, I20, P0, A, HD, kHAA1, kHAD1, kHAA2, kHAD2, krearr1, krearr2, T, int(Npts), rtol=float(rtol), atol=float(atol), ceil=float(ceil) ) run_btn.click( run_sim, inputs=[S0, I10, I20, P0, A, HD, kHAA1, kHAD1, kHAA2, kHAD2, krearr1, krearr2, T, Npts, rtol_state, atol_state, ceil_state], outputs=[plot, stats], api_name="simulate" ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))