QuickStart / app.py
Tarek Masryo
chore: prepare space release
d00b7fc
"""Gradio UI for QuickStart."""
from __future__ import annotations
import inspect
from typing import Any
import gradio as gr
from quickstart_core import (
build_quickstart_zip,
cached_public,
esc,
fetch_repo_info,
filter_files,
generate_badge,
generate_cli_download,
generate_install,
generate_quickstart,
generate_snapshot_download,
get_effective_token,
hf_url,
human_bytes,
norm_id,
norm_type,
parse_hf_input,
to_files_table,
warnings_from_meta,
)
def status_card(meta_public: dict[str, Any], warnings: list[str], repo_type: str, repo_id: str) -> str:
url = hf_url(repo_type, repo_id)
last_modified = meta_public.get("Last Modified", "N/A")
last_modified = str(last_modified).split()[0] if last_modified and last_modified != "N/A" else "N/A"
pills: list[str] = []
if repo_type == "space":
sdk = meta_public.get("SDK", "N/A")
if sdk and sdk != "N/A":
pills.append(f"<span class='pill'>SDK: {esc(sdk)}</span>")
for key in ("License", "Pipeline", "Total Size"):
value = meta_public.get(key, "N/A")
if value and value != "N/A":
pills.append(f"<span class='pill'>{esc(value)}</span>")
if meta_public.get("Gated") == "Yes":
pills.append("<span class='pill warn'>Gated</span>")
warnings_html = ""
if warnings:
items = "".join(f"<li>{esc(item)}</li>" for item in warnings)
warnings_html = f"""
<div class="warnbox">
<div class="warn_title">Warnings</div>
<ul class="warn_list">{items}</ul>
</div>
"""
return f"""
<div class="card ok">
<div class="head">
<div class="title">{esc(meta_public.get("Repo ID", repo_id))}</div>
<a class="link" href="{esc(url)}" target="_blank">Open</a>
</div>
<div class="pills">{"".join(pills)}</div>
<div class="stats">
<div class="stat accent">
<div class="k">Likes</div>
<div class="v">{esc(meta_public.get("Likes", 0))}</div>
</div>
<div class="stat">
<div class="k">Downloads</div>
<div class="v">{esc(meta_public.get("Downloads", 0))}</div>
</div>
<div class="stat">
<div class="k">Last modified</div>
<div class="v">{esc(last_modified)}</div>
</div>
<div class="stat">
<div class="k">Author</div>
<div class="v">{esc(meta_public.get("Author", "N/A"))}</div>
</div>
</div>
{warnings_html}
</div>
"""
def status_error_card(message: str) -> str:
return f"""
<div class="card err">
<div class="title">Failed</div>
<div class="msg">{esc(message)}</div>
<div class="hint">
If this is a private/gated repo, enable a scoped server token locally or in Space settings.
</div>
</div>
"""
def render_risk_html(risk: dict[str, Any]) -> str:
suspicious = risk.get("suspicious_names") or []
suspicious_html = ""
if suspicious:
items = "".join(f"<li><code>{esc(item)}</code></li>" for item in suspicious[:20])
suspicious_html = f"""
<div class="riskbox">
<div class="risk_title">Potentially Sensitive Filenames</div>
<ul class="risk_list">{items}</ul>
<div class="risk_note">Filename-based only. File contents are not scanned.</div>
</div>
"""
artifacts = []
if risk.get("has_gguf"):
artifacts.append("GGUF")
if risk.get("has_onnx"):
artifacts.append("ONNX")
if risk.get("has_safetensors"):
artifacts.append("safetensors")
if risk.get("has_bin"):
artifacts.append(".bin")
artifacts_text = ", ".join(artifacts) if artifacts else "N/A"
size_text = human_bytes(risk.get("total_size_known")) if risk.get("total_size_known") else "N/A"
return f"""
<div class="card">
<div class="title">Files and risk</div>
<div class="mini_stats">
<span>Files <b>{esc(risk.get("files_count", 0))}</b></span>
<span>Size <b>{esc(size_text)}</b></span>
<span>Artifacts <b>{esc(artifacts_text)}</b></span>
</div>
{suspicious_html}
</div>
"""
def process(user_input: str, type_override: str):
auto_type, repo_id = parse_hf_input(user_input)
repo_type = auto_type if (type_override or "auto") == "auto" else norm_type(type_override)
repo_id = norm_id(repo_id)
token = get_effective_token(repo_id)
if token:
ok, meta, error = fetch_repo_info(repo_type, repo_id, token=token)
else:
ok, meta, error = cached_public(repo_type, repo_id)
if not ok or not meta:
return (
status_error_card(error or "Unknown error"),
"",
"",
"",
"",
"",
[],
"",
{},
{},
)
meta_public = {key: value for key, value in meta.items() if not str(key).startswith("_")}
install = generate_install(repo_type, meta)
quickstart = generate_quickstart(repo_type, repo_id, meta)
snapshot = generate_snapshot_download(repo_type, repo_id)
cli = generate_cli_download(repo_type, repo_id)
badge = generate_badge(repo_type, repo_id)
files = meta.get("_files", []) or []
risk = meta.get("_risk", {}) or {}
state = dict(meta)
state["_rid"] = repo_id
state["_rt"] = repo_type
warnings = warnings_from_meta(meta)
if token:
warnings.append(
"Server-token mode is being used for this repo. Do not expose this mode on a public Space unless intentional."
)
return (
status_card(meta_public, warnings, repo_type, repo_id),
install,
quickstart,
snapshot,
cli,
badge,
to_files_table(files, limit=250),
render_risk_html(risk),
meta_public,
state,
)
def do_filter_files(state: dict[str, Any], query: str):
files = (state or {}).get("_files", []) or []
return filter_files(files, query, limit=250)
def build_ui():
theme = gr.themes.Soft(
primary_hue="orange",
secondary_hue="slate",
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui"],
radius_size=gr.themes.sizes.radius_lg,
)
css = """
.gradio-container { max-width: 1120px !important; margin: auto; }
.hero{
padding: 18px 18px;
border-radius: 18px;
border: 1px solid rgba(148,163,184,.25);
background:
radial-gradient(1200px 300px at 30% 0%, rgba(249,115,22,.18), transparent 60%),
radial-gradient(1000px 260px at 70% 20%, rgba(99,102,241,.14), transparent 55%),
linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,0));
box-shadow: 0 12px 40px rgba(0,0,0,.10);
margin-bottom: 14px;
}
h1{
text-align:center;
margin: 0 0 6px 0;
color: var(--body-text-color);
font-weight: 850;
letter-spacing: -0.03em;
}
.sub{
text-align:center;
color: var(--body-text-color-subdued);
margin: 0;
line-height: 1.45;
}
.card{
padding: 14px 16px;
border-radius: 16px;
background: var(--block-background-fill);
border: 1px solid var(--block-border-color);
color: var(--body-text-color);
box-shadow: 0 10px 30px rgba(0,0,0,.06);
}
.ok{ border-left: 6px solid rgba(16,185,129,.95); }
.err{ border-left: 6px solid rgba(239,68,68,.95); }
.head{ display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; }
.title{ font-weight: 850; font-size: 1.02rem; }
.link{
color: #ffffff !important;
text-decoration: none !important;
font-weight: 900;
padding: 6px 12px;
border-radius: 10px;
border: none !important;
background: linear-gradient(135deg, rgba(249,115,22,1), rgba(245,158,11,1)) !important;
box-shadow: 0 10px 26px rgba(249,115,22,.18);
}
.link:hover{ filter: brightness(1.05); transform: translateY(-0.5px); }
.pills{ margin-top: 10px; display:flex; gap: 10px; flex-wrap:wrap; }
.pill{
display:inline-flex; align-items:center;
padding: 4px 10px; border-radius: 999px; font-size: .82rem;
border: 1px solid rgba(148,163,184,.28);
background: rgba(255,255,255,0.04);
color: var(--body-text-color);
}
.pill.warn{ border-color: rgba(245,158,11,.35); background: rgba(245,158,11,.10); }
.stats{
margin-top: 12px;
display: grid;
grid-template-columns: repeat(4, minmax(150px, 1fr));
gap: 10px;
}
.stat{
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(148,163,184,.22);
background:
radial-gradient(600px 140px at 0% 0%, rgba(255,255,255,.05), transparent 55%),
linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,0));
}
.stat .k{
font-size: .76rem;
color: var(--body-text-color-subdued);
text-transform: uppercase;
letter-spacing: .08em;
}
.stat .v{
margin-top: 6px;
font-weight: 900;
font-size: 1.06rem;
color: var(--body-text-color);
font-variant-numeric: tabular-nums;
}
.stat.accent{
border-color: rgba(249,115,22,.30);
background:
radial-gradient(700px 160px at 10% 0%, rgba(249,115,22,.20), transparent 60%),
linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,0));
}
@media (max-width: 820px){ .stats{ grid-template-columns: repeat(2, minmax(150px, 1fr)); } }
@media (max-width: 460px){ .stats{ grid-template-columns: 1fr; } }
.warnbox{
margin-top: 12px;
padding: 12px 12px;
border-radius: 14px;
border: 1px solid rgba(245, 158, 11, .30);
background: rgba(245, 158, 11, .08);
}
.warn_title{ font-weight: 850; margin-bottom: 6px; }
.warn_list{ margin: 0; padding-left: 18px; color: var(--body-text-color); }
.mini_stats{
display:flex;
gap: 14px;
flex-wrap:wrap;
margin-top: 10px;
color: var(--body-text-color-subdued);
font-size: .92rem;
}
.riskbox{
margin-top: 12px;
padding: 12px 12px;
border-radius: 14px;
border: 1px solid rgba(148,163,184,.20);
background: rgba(255,255,255,0.03);
}
.risk_title{ font-weight: 850; margin-bottom: 6px; }
.risk_list{ margin: 0; padding-left: 18px; color: var(--body-text-color); }
.risk_note{ margin-top: 6px; color: var(--body-text-color-subdued); font-size: .9rem; }
button.primary, .gr-button-primary, .primary > button {
border: none !important;
background: linear-gradient(135deg, rgba(249,115,22,1), rgba(245,158,11,1)) !important;
color: white !important;
font-weight: 850 !important;
box-shadow: 0 10px 26px rgba(249,115,22,.18);
}
button.primary:hover, .gr-button-primary:hover, .primary > button:hover {
filter: brightness(1.05);
transform: translateY(-0.5px);
}
"""
dataframe_signature = inspect.signature(gr.Dataframe)
dataframe_count_kw = (
{"column_count": (2, "fixed")}
if "column_count" in dataframe_signature.parameters
else {"col_count": (2, "fixed")}
)
with gr.Blocks(title="QuickStart") as demo:
gr.Markdown(
"<div class='hero'>"
"<h1>QuickStart</h1>"
"<p class='sub'>Paste a Hugging Face URL or Repo ID to generate run/download snippets and export a ready zip.</p>"
"</div>"
)
state = gr.State({})
with gr.Row(variant="panel"):
with gr.Column(scale=7):
user_input = gr.Textbox(
label="HF URL or Repo ID",
placeholder="google/gemma-2-9b-it or https://huggingface.co/datasets/squad",
autofocus=True,
)
with gr.Column(scale=2):
repo_type = gr.Dropdown(["auto", "model", "dataset", "space"], value="auto", label="Type")
with gr.Column(scale=2):
generate_button = gr.Button("Generate", variant="primary")
out_status = gr.HTML(label="Summary")
with gr.Tabs():
with gr.TabItem("QuickStart"):
out_py = gr.Code(language="python", label="Python QuickStart", interactive=False)
copy_py = gr.Button("Copy")
out_install = gr.Code(language="shell", label="Install", interactive=False)
copy_install = gr.Button("Copy")
with gr.TabItem("Download"):
out_snapshot = gr.Code(language="python", label="snapshot_download()", interactive=False)
copy_snapshot = gr.Button("Copy")
out_cli = gr.Code(language="shell", label="HF CLI download", interactive=False)
copy_cli = gr.Button("Copy")
with gr.TabItem("Files"):
file_query = gr.Textbox(label="Filter", placeholder="e.g. .gguf or config.json")
files_table = gr.Dataframe(
headers=["path", "size"],
datatype=["str", "str"],
label="Files (first 250)",
interactive=False,
row_count=10,
**dataframe_count_kw,
)
risk_html = gr.HTML(label="Risk")
with gr.TabItem("Export"):
gr.Markdown(
"Exports a zip: `run.py`, `download.py`, `requirements.txt`, `.env.example`, `README.md`."
)
zip_button = gr.Button("Build Zip", variant="primary")
zip_file = gr.File(label="Zip file")
zip_message = gr.Markdown()
with gr.TabItem("Badge"):
out_badge = gr.Code(language="markdown", label="Markdown", interactive=False)
copy_badge = gr.Button("Copy")
with gr.Accordion("Details", open=False):
out_meta = gr.JSON(label="Metadata")
outputs = [
out_status,
out_install,
out_py,
out_snapshot,
out_cli,
out_badge,
files_table,
risk_html,
out_meta,
state,
]
generate_button.click(process, inputs=[user_input, repo_type], outputs=outputs)
user_input.submit(process, inputs=[user_input, repo_type], outputs=outputs)
file_query.change(do_filter_files, inputs=[state, file_query], outputs=[files_table])
zip_button.click(build_quickstart_zip, inputs=[state], outputs=[zip_file, zip_message])
js_copy = "(t)=>{ if(!t){return [];} navigator.clipboard.writeText(String(t)); return []; }"
copy_install.click(None, inputs=[out_install], outputs=[], js=js_copy)
copy_py.click(None, inputs=[out_py], outputs=[], js=js_copy)
copy_snapshot.click(None, inputs=[out_snapshot], outputs=[], js=js_copy)
copy_cli.click(None, inputs=[out_cli], outputs=[], js=js_copy)
copy_badge.click(None, inputs=[out_badge], outputs=[], js=js_copy)
return demo, theme, css
if __name__ == "__main__":
app, app_theme, app_css = build_ui()
app.launch(theme=app_theme, css=app_css)