CoolFace
Apppublic

hugging-science/request-moderator

sourceHugging Faceupdated 16d agoView on Hugging Face
0likes
app.py256 linesDownload Raw Back to root
1"""Hugging Science requests review — Gradio admin UI.2 3Ingestion happens in the separate feedback-api Space (/submit), which writes4submissions into requests.jsonl or feedback.jsonl (see submissions.py) in the5hugging-science/feedback dataset. This Space reads both files, lets the6reviewer filter the queue by type, and approve (-> opens a GitHub PR),7acknowledge, or reject each pending item.8"""9 10import gradio as gr11 12import formatters13import github_pr14import submissions15from about import ADMIN_USERS16 17# Types that map onto a src/data/*.js file and can go through the PR flow.18PR_TYPES = list(formatters.TARGETS.keys())  # organization, model, dataset, blog19# Everything else (e.g. "challenge") is acknowledge/reject only.20 21# Every type the feedback-api Space can write, for the type filter dropdowns.22FILTER_TYPES = ["All", "feedback", "collaboration", *PR_TYPES, "challenge"]23 24FIELD_VISIBILITY = {25    "organization": {"id", "name", "link", "description", "tags"},26    "model": {"id", "slug", "name", "orgId", "entry_type", "description", "tags"},27    "dataset": {"id", "slug", "orgId", "entry_type", "description", "tags"},28    "blog": {"id", "title", "slug", "orgId", "date", "excerpt", "link", "tags", "featured", "upvotes"},29}30 31ALL_FIELD_KEYS = [32    "id", "name", "title", "slug", "orgId", "entry_type",33    "description", "excerpt", "link", "date", "featured", "upvotes", "tags",34]35 36 37def is_admin(profile: gr.OAuthProfile | None) -> bool:38    return profile is not None and profile.username in ADMIN_USERS39 40 41def _guess_slug(title: str | None) -> str:42    if title and "/" in title:43        return title.strip()44    return ""45 46 47def _type_filter(type_filter: str | None) -> str | None:48    return None if not type_filter or type_filter == "All" else type_filter49 50 51def pending_choices(type_filter: str | None = None):52    rows = submissions.list_submissions(status="pending", type_=_type_filter(type_filter))53    return [(f"[{r['type']}] {r['title'] or r['description'][:40]}", r["id"]) for r in rows]54 55 56def history_rows(type_filter: str | None = None):57    rows = [58        r for r in submissions.list_submissions(type_=_type_filter(type_filter))59        if r["status"] != "pending"60    ]61    return [62        [r["type"], r["title"], r["description"], r["status"], r.get("reviewed_at"), r.get("pr_url") or r.get("reject_reason")]63        for r in rows64    ]65 66 67def load_submission(submission_id: str):68    """Populate the detail panel + editable fields for a selected submission."""69    if not submission_id:70        return (71            gr.update(value=""),72            *[gr.update(visible=False) for _ in ALL_FIELD_KEYS],73            gr.update(visible=False),74            gr.update(visible=False),75        )76 77    row = submissions.get_submission(submission_id)78    if row is None:79        return (80            gr.update(value="Not found."),81            *[gr.update(visible=False) for _ in ALL_FIELD_KEYS],82            gr.update(visible=False),83            gr.update(visible=False),84        )85 86    detail_md = (87        f"**Type:** {row['type']}  \n"88        f"**Title/link:** {row['title'] or '_none given_'}  \n"89        f"**Submitted:** {row['submitted_at']} via {row['source']}  \n"90    )91    if row["type"] == "collaboration":92        detail_md += (93            f"**Email:** {row.get('email') or '_none given_'}  \n"94            f"**Institution:** {row.get('institution') or '_none given_'}  \n"95        )96    if row["type"] in PR_TYPES:97        detail_md += (98            "**Ready for PR** — fields below come straight from the "99            "submission; edit anything that looks off, then approve.  \n"100        )101    detail_md += f"\n{row['description']}"102 103    # Submissions from the current website form carry these structured104    # fields directly (see the request-api Space); older/legacy pending105    # rows won't have them, so fall back to guessing from title/link.106    visible = FIELD_VISIBILITY.get(row["type"], set())107    guessed_slug = _guess_slug(row["title"])108    defaults = {109        "id": row.get("entry_id") or guessed_slug.replace("/", "-").lower(),110        "name": row.get("name") or row["title"] or "",111        "title": row["title"] or "",112        "slug": row.get("slug") or guessed_slug,113        "orgId": row.get("org_id") or "",114        "entry_type": row.get("entry_type") or "",115        "description": row["description"],116        "excerpt": row["description"],117        "link": row.get("link")118        or (row["title"] if row["title"] and row["title"].startswith("http") else ""),119        "date": row.get("date") or "",120        "tags": row.get("tags") or [],121    }122    field_updates = []123    for key in ALL_FIELD_KEYS:124        if key in visible:125            field_updates.append(gr.update(visible=True, value=defaults.get(key, "" if key != "tags" else [])))126        else:127            field_updates.append(gr.update(visible=False))128 129    show_pr_button = row["type"] in PR_TYPES130    show_ack_button = row["type"] not in PR_TYPES131    return (132        gr.update(value=detail_md),133        *field_updates,134        gr.update(visible=show_pr_button),135        gr.update(visible=show_ack_button),136    )137 138 139with gr.Blocks(title="Hugging Science — requests review") as demo:140    gr.Markdown("## Hugging Science — requests review")141    login_btn = gr.LoginButton()142 143    with gr.Column(visible=False) as admin_panel:144        with gr.Tab("Pending"):145            with gr.Row():146                type_dd = gr.Dropdown(label="Filter by type", choices=FILTER_TYPES, value="All", scale=1)147                refresh_btn = gr.Button("Refresh", scale=0)148            pending_dd = gr.Dropdown(label="Pending requests", choices=[], interactive=True)149            detail = gr.Markdown()150 151            with gr.Group():152                f_id = gr.Textbox(label="id", visible=False)153                f_name = gr.Textbox(label="name", visible=False)154                f_title = gr.Textbox(label="title", visible=False)155                f_slug = gr.Textbox(label="slug (org/repo)", visible=False)156                f_orgId = gr.Textbox(label="orgId", visible=False)157                f_entry_type = gr.Textbox(label="type (e.g. Genomics, Foundation Model)", visible=False)158                f_description = gr.Textbox(label="description", lines=3, visible=False)159                f_excerpt = gr.Textbox(label="excerpt", lines=3, visible=False)160                f_link = gr.Textbox(label="link", visible=False)161                f_date = gr.Textbox(label="date (YYYY-MM-DD)", visible=False)162                f_featured = gr.Checkbox(label="featured", visible=False)163                f_upvotes = gr.Number(label="upvotes", visible=False)164                f_tags = gr.CheckboxGroup(label="tags", choices=formatters.VALID_TAGS, visible=False)165 166            all_fields = [f_id, f_name, f_title, f_slug, f_orgId, f_entry_type,167                          f_description, f_excerpt, f_link, f_date, f_featured, f_upvotes, f_tags]168 169            with gr.Row():170                approve_btn = gr.Button("Approve → open PR", variant="primary", visible=False)171                ack_btn = gr.Button("Mark as reviewed", variant="primary", visible=False)172                reject_reason = gr.Textbox(label="Reject reason (optional)", scale=2)173                reject_btn = gr.Button("Reject", variant="stop")174 175            result_md = gr.Markdown()176 177            def do_refresh(type_filter):178                return gr.update(choices=pending_choices(type_filter), value=None)179 180            refresh_btn.click(do_refresh, inputs=[type_dd], outputs=pending_dd)181            type_dd.change(do_refresh, inputs=[type_dd], outputs=pending_dd)182            demo.load(do_refresh, inputs=[type_dd], outputs=pending_dd)183 184            pending_dd.change(load_submission, inputs=pending_dd, outputs=[detail, *all_fields, approve_btn, ack_btn])185 186            def do_approve(submission_id, type_filter, id_, name, title, slug, org_id, entry_type,187                            description, excerpt, link, date, featured, upvotes, tags):188                row = submissions.get_submission(submission_id)189                if row is None:190                    return "Submission not found.", gr.update(choices=pending_choices(type_filter))191                fields = {192                    "id": id_, "name": name, "title": title, "slug": slug, "orgId": org_id or None,193                    "type": entry_type, "description": description, "excerpt": excerpt,194                    "link": link, "date": date, "featured": featured, "upvotes": upvotes,195                    "tags": tags or [],196                }197                missing = formatters.missing_fields(row["type"], fields)198                if missing:199                    return f"Missing required fields: {', '.join(missing)}", gr.update()200                try:201                    pr_url = github_pr.open_pr_for_submission(row, fields)202                except Exception as exc:  # surfaced to the reviewer, not swallowed203                    return f"Failed to open PR: {exc}", gr.update()204                submissions.mark_approved(submission_id, pr_url)205                return f"Approved — [PR opened]({pr_url})", gr.update(choices=pending_choices(type_filter), value=None)206 207            approve_btn.click(208                do_approve,209                inputs=[pending_dd, type_dd, *all_fields],210                outputs=[result_md, pending_dd],211            )212 213            def do_ack(submission_id, type_filter):214                if not submission_id:215                    return "Nothing selected.", gr.update()216                submissions.mark_acknowledged(submission_id)217                return "Marked as reviewed.", gr.update(choices=pending_choices(type_filter), value=None)218 219            ack_btn.click(do_ack, inputs=[pending_dd, type_dd], outputs=[result_md, pending_dd])220 221            def do_reject(submission_id, reason, type_filter):222                if not submission_id:223                    return "Nothing selected.", gr.update()224                submissions.mark_rejected(submission_id, reason or None)225                return "Rejected.", gr.update(choices=pending_choices(type_filter), value=None)226 227            reject_btn.click(228                do_reject,229                inputs=[pending_dd, reject_reason, type_dd],230                outputs=[result_md, pending_dd],231            )232 233        with gr.Tab("History"):234            with gr.Row():235                history_type_dd = gr.Dropdown(label="Filter by type", choices=FILTER_TYPES, value="All", scale=1)236                history_refresh = gr.Button("Refresh", scale=0)237            history_df = gr.Dataframe(238                headers=["type", "title", "description", "status", "reviewed_at", "pr_url / reason"],239                interactive=False,240            )241            history_refresh.click(history_rows, inputs=[history_type_dd], outputs=history_df)242            history_type_dd.change(history_rows, inputs=[history_type_dd], outputs=history_df)243            demo.load(history_rows, inputs=[history_type_dd], outputs=history_df)244 245    unauthorized = gr.Markdown("Log in with an allow-listed Hugging Face account to review requests.")246 247    def toggle_panel(profile: gr.OAuthProfile | None):248        admin = is_admin(profile)249        return gr.update(visible=admin), gr.update(visible=not admin)250 251    demo.load(toggle_panel, outputs=[admin_panel, unauthorized])252 253 254if __name__ == "__main__":255    demo.launch()256