EmmaScharfmann's picture
EmmaScharfmann HF Staff
Update app.py
7d9bd0c verified
Raw
History Blame Contribute Delete
11.3 kB
"""Hugging Science requests review — Gradio admin UI.
Ingestion happens in the separate feedback-api Space (/submit), which writes
submissions into requests.jsonl or feedback.jsonl (see submissions.py) in the
hugging-science/feedback dataset. This Space reads both files, lets the
reviewer filter the queue by type, and approve (-> opens a GitHub PR),
acknowledge, or reject each pending item.
"""
import gradio as gr
import formatters
import github_pr
import submissions
from about import ADMIN_USERS
# Types that map onto a src/data/*.js file and can go through the PR flow.
PR_TYPES = list(formatters.TARGETS.keys()) # organization, model, dataset, blog
# Everything else (e.g. "challenge") is acknowledge/reject only.
# Every type the feedback-api Space can write, for the type filter dropdowns.
FILTER_TYPES = ["All", "feedback", "collaboration", *PR_TYPES, "challenge"]
FIELD_VISIBILITY = {
"organization": {"id", "name", "link", "description", "tags"},
"model": {"id", "slug", "name", "orgId", "entry_type", "description", "tags"},
"dataset": {"id", "slug", "orgId", "entry_type", "description", "tags"},
"blog": {"id", "title", "slug", "orgId", "date", "excerpt", "link", "tags", "featured", "upvotes"},
}
ALL_FIELD_KEYS = [
"id", "name", "title", "slug", "orgId", "entry_type",
"description", "excerpt", "link", "date", "featured", "upvotes", "tags",
]
def is_admin(profile: gr.OAuthProfile | None) -> bool:
return profile is not None and profile.username in ADMIN_USERS
def _guess_slug(title: str | None) -> str:
if title and "/" in title:
return title.strip()
return ""
def _type_filter(type_filter: str | None) -> str | None:
return None if not type_filter or type_filter == "All" else type_filter
def pending_choices(type_filter: str | None = None):
rows = submissions.list_submissions(status="pending", type_=_type_filter(type_filter))
return [(f"[{r['type']}] {r['title'] or r['description'][:40]}", r["id"]) for r in rows]
def history_rows(type_filter: str | None = None):
rows = [
r for r in submissions.list_submissions(type_=_type_filter(type_filter))
if r["status"] != "pending"
]
return [
[r["type"], r["title"], r["description"], r["status"], r.get("reviewed_at"), r.get("pr_url") or r.get("reject_reason")]
for r in rows
]
def load_submission(submission_id: str):
"""Populate the detail panel + editable fields for a selected submission."""
if not submission_id:
return (
gr.update(value=""),
*[gr.update(visible=False) for _ in ALL_FIELD_KEYS],
gr.update(visible=False),
gr.update(visible=False),
)
row = submissions.get_submission(submission_id)
if row is None:
return (
gr.update(value="Not found."),
*[gr.update(visible=False) for _ in ALL_FIELD_KEYS],
gr.update(visible=False),
gr.update(visible=False),
)
detail_md = (
f"**Type:** {row['type']} \n"
f"**Title/link:** {row['title'] or '_none given_'} \n"
f"**Submitted:** {row['submitted_at']} via {row['source']} \n"
)
if row["type"] == "collaboration":
detail_md += (
f"**Email:** {row.get('email') or '_none given_'} \n"
f"**Institution:** {row.get('institution') or '_none given_'} \n"
)
if row["type"] in PR_TYPES:
detail_md += (
"**Ready for PR** — fields below come straight from the "
"submission; edit anything that looks off, then approve. \n"
)
detail_md += f"\n{row['description']}"
# Submissions from the current website form carry these structured
# fields directly (see the request-api Space); older/legacy pending
# rows won't have them, so fall back to guessing from title/link.
visible = FIELD_VISIBILITY.get(row["type"], set())
guessed_slug = _guess_slug(row["title"])
defaults = {
"id": row.get("entry_id") or guessed_slug.replace("/", "-").lower(),
"name": row.get("name") or row["title"] or "",
"title": row["title"] or "",
"slug": row.get("slug") or guessed_slug,
"orgId": row.get("org_id") or "",
"entry_type": row.get("entry_type") or "",
"description": row["description"],
"excerpt": row["description"],
"link": row.get("link")
or (row["title"] if row["title"] and row["title"].startswith("http") else ""),
"date": row.get("date") or "",
"tags": row.get("tags") or [],
}
field_updates = []
for key in ALL_FIELD_KEYS:
if key in visible:
field_updates.append(gr.update(visible=True, value=defaults.get(key, "" if key != "tags" else [])))
else:
field_updates.append(gr.update(visible=False))
show_pr_button = row["type"] in PR_TYPES
show_ack_button = row["type"] not in PR_TYPES
return (
gr.update(value=detail_md),
*field_updates,
gr.update(visible=show_pr_button),
gr.update(visible=show_ack_button),
)
with gr.Blocks(title="Hugging Science — requests review") as demo:
gr.Markdown("## Hugging Science — requests review")
login_btn = gr.LoginButton()
with gr.Column(visible=False) as admin_panel:
with gr.Tab("Pending"):
with gr.Row():
type_dd = gr.Dropdown(label="Filter by type", choices=FILTER_TYPES, value="All", scale=1)
refresh_btn = gr.Button("Refresh", scale=0)
pending_dd = gr.Dropdown(label="Pending requests", choices=[], interactive=True)
detail = gr.Markdown()
with gr.Group():
f_id = gr.Textbox(label="id", visible=False)
f_name = gr.Textbox(label="name", visible=False)
f_title = gr.Textbox(label="title", visible=False)
f_slug = gr.Textbox(label="slug (org/repo)", visible=False)
f_orgId = gr.Textbox(label="orgId", visible=False)
f_entry_type = gr.Textbox(label="type (e.g. Genomics, Foundation Model)", visible=False)
f_description = gr.Textbox(label="description", lines=3, visible=False)
f_excerpt = gr.Textbox(label="excerpt", lines=3, visible=False)
f_link = gr.Textbox(label="link", visible=False)
f_date = gr.Textbox(label="date (YYYY-MM-DD)", visible=False)
f_featured = gr.Checkbox(label="featured", visible=False)
f_upvotes = gr.Number(label="upvotes", visible=False)
f_tags = gr.CheckboxGroup(label="tags", choices=formatters.VALID_TAGS, visible=False)
all_fields = [f_id, f_name, f_title, f_slug, f_orgId, f_entry_type,
f_description, f_excerpt, f_link, f_date, f_featured, f_upvotes, f_tags]
with gr.Row():
approve_btn = gr.Button("Approve → open PR", variant="primary", visible=False)
ack_btn = gr.Button("Mark as reviewed", variant="primary", visible=False)
reject_reason = gr.Textbox(label="Reject reason (optional)", scale=2)
reject_btn = gr.Button("Reject", variant="stop")
result_md = gr.Markdown()
def do_refresh(type_filter):
return gr.update(choices=pending_choices(type_filter), value=None)
refresh_btn.click(do_refresh, inputs=[type_dd], outputs=pending_dd)
type_dd.change(do_refresh, inputs=[type_dd], outputs=pending_dd)
demo.load(do_refresh, inputs=[type_dd], outputs=pending_dd)
pending_dd.change(load_submission, inputs=pending_dd, outputs=[detail, *all_fields, approve_btn, ack_btn])
def do_approve(submission_id, type_filter, id_, name, title, slug, org_id, entry_type,
description, excerpt, link, date, featured, upvotes, tags):
row = submissions.get_submission(submission_id)
if row is None:
return "Submission not found.", gr.update(choices=pending_choices(type_filter))
fields = {
"id": id_, "name": name, "title": title, "slug": slug, "orgId": org_id or None,
"type": entry_type, "description": description, "excerpt": excerpt,
"link": link, "date": date, "featured": featured, "upvotes": upvotes,
"tags": tags or [],
}
missing = formatters.missing_fields(row["type"], fields)
if missing:
return f"Missing required fields: {', '.join(missing)}", gr.update()
try:
pr_url = github_pr.open_pr_for_submission(row, fields)
except Exception as exc: # surfaced to the reviewer, not swallowed
return f"Failed to open PR: {exc}", gr.update()
submissions.mark_approved(submission_id, pr_url)
return f"Approved — [PR opened]({pr_url})", gr.update(choices=pending_choices(type_filter), value=None)
approve_btn.click(
do_approve,
inputs=[pending_dd, type_dd, *all_fields],
outputs=[result_md, pending_dd],
)
def do_ack(submission_id, type_filter):
if not submission_id:
return "Nothing selected.", gr.update()
submissions.mark_acknowledged(submission_id)
return "Marked as reviewed.", gr.update(choices=pending_choices(type_filter), value=None)
ack_btn.click(do_ack, inputs=[pending_dd, type_dd], outputs=[result_md, pending_dd])
def do_reject(submission_id, reason, type_filter):
if not submission_id:
return "Nothing selected.", gr.update()
submissions.mark_rejected(submission_id, reason or None)
return "Rejected.", gr.update(choices=pending_choices(type_filter), value=None)
reject_btn.click(
do_reject,
inputs=[pending_dd, reject_reason, type_dd],
outputs=[result_md, pending_dd],
)
with gr.Tab("History"):
with gr.Row():
history_type_dd = gr.Dropdown(label="Filter by type", choices=FILTER_TYPES, value="All", scale=1)
history_refresh = gr.Button("Refresh", scale=0)
history_df = gr.Dataframe(
headers=["type", "title", "description", "status", "reviewed_at", "pr_url / reason"],
interactive=False,
)
history_refresh.click(history_rows, inputs=[history_type_dd], outputs=history_df)
history_type_dd.change(history_rows, inputs=[history_type_dd], outputs=history_df)
demo.load(history_rows, inputs=[history_type_dd], outputs=history_df)
unauthorized = gr.Markdown("Log in with an allow-listed Hugging Face account to review requests.")
def toggle_panel(profile: gr.OAuthProfile | None):
admin = is_admin(profile)
return gr.update(visible=admin), gr.update(visible=not admin)
demo.load(toggle_panel, outputs=[admin_panel, unauthorized])
if __name__ == "__main__":
demo.launch()