Spaces:
Sleeping
Sleeping
Download app.py from EmmaScharfmann/request-moderator: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/spaces/EmmaScharfmann/request-moderator/resolve/main/app.py
- Command line
-
hf download hf://spaces/EmmaScharfmann/request-moderator/app.py
-
curl -L -o app.py https://huggingface.co/spaces/EmmaScharfmann/request-moderator/resolve/main/app.py
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() | |