| from __future__ import annotations |
|
|
| import json |
| import os |
| from pathlib import Path |
|
|
| import gradio as gr |
| from huggingface_hub.utils import HfHubHTTPError |
|
|
| try: |
| from .repo_ops import ( |
| DEFAULT_REPO_ID, |
| allocate_next_task_id, |
| create_dataset_pr, |
| get_repo_head_sha, |
| list_existing_task_ids, |
| load_hf_token, |
| ) |
| from .validator import ( |
| DOMAINS, |
| PreparedSubmission, |
| SubmissionMetadata, |
| ValidationError, |
| build_public_report, |
| cleanup_stale_managed_files, |
| cleanup_submission_state, |
| cleanup_uploaded_archive, |
| cleanup_work_dir, |
| normalize_domain_token, |
| persist_uploaded_archive, |
| stage_submission, |
| validate_and_prepare_submission, |
| ) |
| except ImportError: |
| from repo_ops import ( |
| DEFAULT_REPO_ID, |
| allocate_next_task_id, |
| create_dataset_pr, |
| get_repo_head_sha, |
| list_existing_task_ids, |
| load_hf_token, |
| ) |
| from validator import ( |
| DOMAINS, |
| PreparedSubmission, |
| SubmissionMetadata, |
| ValidationError, |
| build_public_report, |
| cleanup_stale_managed_files, |
| cleanup_submission_state, |
| cleanup_uploaded_archive, |
| cleanup_work_dir, |
| normalize_domain_token, |
| persist_uploaded_archive, |
| stage_submission, |
| validate_and_prepare_submission, |
| ) |
|
|
|
|
| SPACE_TITLE = 'ResearchClawBench Task Submission' |
| GITHUB_REPO_URL = 'https://github.com/InternScience/ResearchClawBench' |
| DATASET_URL = f'https://huggingface.co/datasets/{DEFAULT_REPO_ID}' |
| SPACE_URL = 'https://huggingface.co/spaces/InternScience/ResearchClawBench-Task-Submit' |
| STATE_TTL_SECONDS = int(os.environ.get('RCB_SPACE_STATE_TTL_SECONDS', '3600')) |
| STALE_WORK_DIR_TTL_SECONDS = int( |
| os.environ.get('RCB_SPACE_STALE_WORK_DIR_TTL_SECONDS', str(max(STATE_TTL_SECONDS * 2, 24 * 3600))) |
| ) |
|
|
| _removed_stale_managed_files = cleanup_stale_managed_files(STALE_WORK_DIR_TTL_SECONDS) |
| if _removed_stale_managed_files: |
| print( |
| f'[startup] Removed {_removed_stale_managed_files} stale managed submission file(s) ' |
| f'older than {STALE_WORK_DIR_TTL_SECONDS}s.', |
| flush=True, |
| ) |
|
|
| CSS = """ |
| @import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&display=swap'); |
| |
| :root { |
| --page-text: #0f172a; |
| --page-muted: #526075; |
| --page-line: rgba(15, 23, 42, 0.12); |
| --page-surface-strong: #ffffff; |
| } |
| |
| body { |
| background: |
| radial-gradient(circle at top left, rgba(54, 107, 245, 0.12), transparent 34%), |
| radial-gradient(circle at top right, rgba(15, 118, 110, 0.08), transparent 28%), |
| linear-gradient(180deg, #f8fafc 0%, #f3f6fb 55%, #f6f8fb 100%); |
| color: var(--page-text); |
| } |
| |
| body, |
| button, |
| input, |
| textarea { |
| font-family: 'Manrope', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif !important; |
| } |
| |
| .gradio-container { |
| max-width: 1280px !important; |
| margin: 0 auto !important; |
| padding: 40px 40px 64px !important; |
| --block-background-fill: transparent; |
| --block-border-width: 0px; |
| --block-border-color: transparent; |
| --block-label-background-fill: transparent; |
| --block-label-border-width: 0px; |
| --panel-background-fill: transparent; |
| --panel-border-width: 0px; |
| --panel-border-color: transparent; |
| --background-fill-secondary: transparent; |
| --body-background-fill: transparent; |
| } |
| |
| .page-shell { |
| margin-top: 26px; |
| background: #ffffff; |
| border: 1px solid rgba(15, 23, 42, 0.08); |
| border-radius: 22px; |
| box-shadow: 0 18px 48px rgba(15, 23, 42, 0.05); |
| overflow: hidden; |
| padding: 34px 0 40px; |
| } |
| |
| .page-shell-content { |
| gap: 0 !important; |
| } |
| |
| .shell-spacer { |
| min-width: 44px !important; |
| } |
| |
| .hero { |
| padding: 42px 48px 36px; |
| border-radius: 24px; |
| color: #f8fbff; |
| background: |
| radial-gradient(circle at 14% 18%, rgba(255, 255, 255, 0.16), transparent 18%), |
| linear-gradient(135deg, #0f274d 0%, #133c7c 46%, #124f75 100%); |
| box-shadow: 0 26px 60px rgba(15, 39, 77, 0.18); |
| } |
| |
| .hero h1 { |
| margin: 0; |
| font-size: 2.4rem; |
| line-height: 1.02; |
| letter-spacing: -0.04em; |
| color: #f8fbff !important; |
| text-shadow: 0 1px 12px rgba(0, 0, 0, 0.14); |
| } |
| |
| .hero-copy { |
| margin-top: 16px; |
| max-width: 860px; |
| font-size: 1.04rem; |
| line-height: 1.72; |
| color: rgba(248, 251, 255, 0.9) !important; |
| } |
| |
| .hero-links { |
| display: flex; |
| gap: 14px; |
| flex-wrap: wrap; |
| margin-top: 22px; |
| } |
| |
| .hero-links a { |
| color: #f8fbff !important; |
| text-decoration: none; |
| font-weight: 700; |
| letter-spacing: -0.01em; |
| } |
| |
| .hero-links a:hover { |
| text-decoration: underline; |
| } |
| |
| .hero-meta { |
| margin-top: 18px; |
| font-size: 0.93rem; |
| color: rgba(248, 251, 255, 0.72) !important; |
| } |
| |
| .section-row { |
| margin-top: 34px; |
| gap: 30px; |
| } |
| |
| .section-row, |
| .section-row > div, |
| .section-copy, |
| .section-copy > div, |
| .main-form, |
| .side-notes { |
| background: transparent !important; |
| border: 0 !important; |
| box-shadow: none !important; |
| } |
| |
| .section-copy h2 { |
| margin: 0 0 10px; |
| font-size: 1.2rem; |
| letter-spacing: -0.03em; |
| } |
| |
| .section-copy h3 { |
| margin: 24px 0 8px; |
| font-size: 1rem; |
| } |
| |
| .section-copy p, |
| .section-copy li { |
| color: #5a667a; |
| line-height: 1.72; |
| } |
| |
| .section-copy ul, |
| .section-copy ol { |
| margin: 10px 0 0; |
| padding-left: 1.2rem; |
| } |
| |
| .section-copy code { |
| font-size: 0.95em; |
| } |
| |
| .section-copy .prose { |
| max-width: 100%; |
| } |
| |
| .subtle-block { |
| padding-bottom: 22px; |
| border-bottom: 1px solid var(--page-line); |
| } |
| |
| .section-copy .prose, |
| .section-copy .prose *, |
| .section-copy .md, |
| .section-copy .md *, |
| .section-copy .markdown, |
| .section-copy .markdown * { |
| background: transparent !important; |
| } |
| |
| .main-form { |
| padding-right: 0; |
| } |
| |
| .side-notes { |
| padding-left: 0; |
| } |
| |
| .main-form > div { |
| padding-right: 18px !important; |
| } |
| |
| .side-notes > div { |
| padding-left: 18px !important; |
| } |
| |
| .caption { |
| margin-top: 8px; |
| color: var(--page-muted); |
| font-size: 0.93rem; |
| line-height: 1.6; |
| } |
| |
| .field-label { |
| margin: 18px 0 8px; |
| color: var(--page-text); |
| font-size: 0.95rem; |
| font-weight: 700; |
| letter-spacing: -0.01em; |
| } |
| |
| .results-shell { |
| margin-top: 0; |
| padding-top: 22px; |
| border-top: 1px solid var(--page-line); |
| } |
| |
| .results-shell > div { |
| margin-top: 26px; |
| } |
| |
| .action-row { |
| margin-top: 10px; |
| } |
| |
| .upload-row { |
| margin-top: 18px; |
| margin-bottom: 10px; |
| } |
| |
| .upload-button button { |
| border-radius: 12px !important; |
| min-height: 48px !important; |
| padding: 0 18px !important; |
| background: #ffffff !important; |
| color: var(--page-text) !important; |
| border: 1px solid rgba(19, 70, 162, 0.16) !important; |
| box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04) !important; |
| } |
| |
| .upload-status { |
| padding-top: 10px; |
| } |
| |
| .upload-status p { |
| margin: 0 !important; |
| color: var(--page-muted) !important; |
| } |
| |
| .primary-button button, |
| .secondary-button button { |
| border-radius: 12px !important; |
| min-height: 48px !important; |
| font-weight: 700 !important; |
| letter-spacing: -0.01em; |
| } |
| |
| .primary-button button { |
| background: linear-gradient(135deg, #1346a2 0%, #155eef 100%) !important; |
| box-shadow: 0 16px 32px rgba(21, 94, 239, 0.2) !important; |
| } |
| |
| .secondary-button button { |
| background: var(--page-surface-strong) !important; |
| color: var(--page-text) !important; |
| border: 1px solid rgba(15, 23, 42, 0.12) !important; |
| } |
| |
| .gradio-container .block, |
| .gradio-container .gr-box, |
| .gradio-container .gr-form, |
| .gradio-container .gr-group, |
| .gradio-container .form, |
| .gradio-container .input-container, |
| .gradio-container .wrap, |
| .gradio-container .row, |
| .gradio-container .column, |
| .gradio-container fieldset { |
| background: transparent !important; |
| box-shadow: none !important; |
| border-color: transparent !important; |
| } |
| |
| .gradio-container input:not([type="checkbox"]), |
| .gradio-container textarea, |
| .gradio-container button[aria-haspopup="listbox"], |
| .gradio-container button[role="listbox"], |
| .gradio-container .wrap:has(input:not([type="checkbox"])), |
| .gradio-container .wrap:has(textarea), |
| .gradio-container .wrap:has(button[aria-haspopup="listbox"]), |
| .gradio-container .wrap:has(button[role="listbox"]), |
| .gradio-container .wrap:has(select), |
| .gradio-container .input-container:has(input:not([type="checkbox"])), |
| .gradio-container .input-container:has(textarea), |
| .gradio-container .input-container:has(button[aria-haspopup="listbox"]), |
| .gradio-container .input-container:has(button[role="listbox"]), |
| .gradio-container input:not([type="checkbox"]), |
| .gradio-container textarea { |
| background: var(--page-surface-strong) !important; |
| border: 1px solid rgba(19, 70, 162, 0.16) !important; |
| border-radius: 10px !important; |
| box-shadow: 0 1px 0 rgba(15, 23, 42, 0.02), 0 8px 22px rgba(15, 23, 42, 0.04) !important; |
| } |
| |
| .gradio-container .block, |
| .gradio-container .wrap, |
| .gradio-container .gr-box, |
| .gradio-container .gr-form, |
| .gradio-container .gr-panel, |
| .gradio-container .gr-group, |
| .gradio-container .form, |
| .gradio-container .input-container, |
| .gradio-container .wrap-inner { |
| overflow: visible !important; |
| } |
| |
| .gradio-container label, |
| .gradio-container .label-wrap, |
| .gradio-container .caption-label { |
| color: var(--page-text) !important; |
| } |
| |
| @media (max-width: 900px) { |
| .gradio-container { |
| padding: 22px 18px 42px !important; |
| } |
| |
| .page-shell { |
| padding: 24px 0 30px; |
| } |
| |
| .shell-spacer { |
| min-width: 18px !important; |
| } |
| |
| .hero { |
| padding: 30px 24px 26px; |
| border-radius: 20px; |
| } |
| |
| .hero h1 { |
| font-size: 2rem; |
| } |
| |
| .main-form, |
| .side-notes { |
| padding-right: 0; |
| padding-left: 0; |
| } |
| } |
| """ |
|
|
|
|
| def build_hero_html() -> str: |
| return f""" |
| <section class="hero"> |
| <h1>{SPACE_TITLE}</h1> |
| <p class="hero-copy"> |
| Submit a new ResearchClawBench task as a single ZIP archive. This Space validates the full task |
| structure, checks JSON fields and referenced paths, allocates the next available task ID, and then |
| opens a PR against the official Hugging Face dataset for maintainer review. |
| </p> |
| <div class="hero-links"> |
| <a href="{GITHUB_REPO_URL}" target="_blank">GitHub Repository</a> |
| <a href="{DATASET_URL}" target="_blank">Hugging Face Dataset</a> |
| <a href="{SPACE_URL}" target="_blank">Space Repository</a> |
| </div> |
| <div class="hero-meta"> |
| ZIP upload only · full task-format validation · PR to dataset repo after passing checks |
| </div> |
| </section> |
| """ |
|
|
|
|
| def field_label_html(text: str) -> str: |
| return f'<div class="field-label">{text}</div>' |
|
|
|
|
| def submission_guide_markdown() -> str: |
| return """ |
| ## Before You Upload |
| |
| 1. Put exactly one task directory at the top level of the ZIP. |
| 2. Make sure the directory contains `task_info.json`, `data/`, `related_work/`, and `target_study/`. |
| 3. Keep every data reference inside `task_info.json` in the `./data/...` format. |
| 4. Make sure every checklist image path points to `target_study/images/...`. |
| 5. Ensure that uploaded files can be redistributed through Hugging Face before submitting. |
| |
| Example task in GitHub: |
| [tasks/Astronomy_000](https://github.com/InternScience/ResearchClawBench/tree/main/tasks/Astronomy_000) |
| |
| --- |
| |
| ## Expected ZIP Layout |
| |
| ```text |
| your_submission.zip |
| └── any_folder_name/ |
| ├── task_info.json |
| ├── data/ |
| ├── related_work/ |
| │ ├── paper_000.pdf |
| │ ├── paper_001.pdf |
| │ └── ... |
| └── target_study/ |
| ├── checklist.json |
| ├── paper.pdf |
| └── images/ |
| ``` |
| |
| --- |
| |
| ## What The Space Checks |
| |
| - top-level folder structure and missing or extra files |
| - `task_info.json` and `checklist.json` parseability and required keys |
| - file naming conventions such as `related_work/paper_000.pdf` |
| - whether declared data paths actually exist |
| - whether image references actually exist |
| - whether invalid source paths or stale `/tasks/...` references remain in descriptions |
| """ |
|
|
|
|
| def final_task_help_html() -> str: |
| return ( |
| '<div class="caption">' |
| 'The final task ID is assigned automatically after the Space scans existing <code>tasks/</code> folders. ' |
| 'You do not need to choose the numeric suffix yourself. The selected domain becomes the prefix, and if the ' |
| 'custom field is filled, it overrides the suggested domain.' |
| '</div>' |
| ) |
|
|
|
|
| def resolve_domain(selected_domain: str, custom_domain: str) -> str: |
| raw_value = (custom_domain or '').strip() or (selected_domain or '').strip() |
| normalized = normalize_domain_token(raw_value) |
| if not normalized: |
| raise ValidationError('Please select a suggested domain or provide a custom domain.') |
| return normalized |
|
|
|
|
| def handle_archive_upload(archive_path: str | None, current_archive_path: str | None): |
| if current_archive_path and current_archive_path != archive_path: |
| cleanup_uploaded_archive(current_archive_path) |
| if not archive_path: |
| return '', 'No ZIP file selected yet.' |
| managed_archive_path = persist_uploaded_archive(archive_path) |
| original_path = Path(archive_path) |
| managed_name = managed_archive_path.name |
| if managed_archive_path.resolve() != original_path.resolve(): |
| try: |
| original_path.unlink() |
| except OSError: |
| pass |
| return str(managed_archive_path), f'Selected ZIP: `{managed_name}`' |
|
|
|
|
| def archive_notice_text(archive_path: str | None) -> str: |
| if not archive_path: |
| return 'No ZIP file selected yet.' |
| return f'Selected ZIP: `{Path(archive_path).name}`' |
|
|
|
|
| def build_validation_markdown(prepared: PreparedSubmission) -> str: |
| metadata = prepared.metadata |
| return '\n'.join([ |
| '## Validation passed', |
| '', |
| f'- Final task ID: `{prepared.assigned_task_id}`', |
| '- This is the folder name that will be created under `tasks/` in the dataset repo.', |
| f'- Domain token used for allocation: `{metadata.domain}`', |
| f'- Submitter: `{metadata.submitter}`', |
| f'- Archive file count: `{prepared.archive_stats.file_count}`', |
| f'- Archive total bytes: `{prepared.archive_stats.total_bytes}`', |
| '', |
| 'You can now create a PR to the Hugging Face dataset repo.', |
| ]) |
|
|
|
|
| def build_failure_markdown(message: str) -> str: |
| items = [line.strip() for line in message.splitlines() if line.strip()] |
| bullets = '\n'.join(f'- {item}' for item in items) if items else '- Unknown validation error' |
| return f'## Validation failed\n\n{bullets}' |
|
|
|
|
| def refresh_prepared_submission_for_pr( |
| prepared: PreparedSubmission, |
| *, |
| repo_id: str, |
| token: str | None, |
| ) -> tuple[PreparedSubmission, bool, str]: |
| head_sha = get_repo_head_sha(repo_id=repo_id, token=token) |
| existing_ids = list_existing_task_ids(repo_id=repo_id, token=token) |
|
|
| reassigned = False |
| final_task_id = prepared.assigned_task_id |
| if final_task_id in existing_ids: |
| final_task_id = allocate_next_task_id(prepared.metadata.domain, existing_ids) |
| prepared.assigned_task_id = final_task_id |
| prepared.staged_task_dir = str( |
| stage_submission(prepared.uploaded_task_dir, final_task_id, prepared.work_dir) |
| ) |
| reassigned = True |
|
|
| return prepared, reassigned, head_sha |
|
|
|
|
| def is_retryable_pr_error(exc: Exception) -> bool: |
| if not isinstance(exc, HfHubHTTPError): |
| return False |
| status_code = getattr(getattr(exc, 'response', None), 'status_code', None) |
| message = str(exc).lower() |
| return status_code in {409, 412} or 'parent commit' in message or 'conflict' in message or 'stale' in message |
|
|
|
|
| def validate_submission( |
| archive_path: str, |
| suggested_domain: str, |
| custom_domain: str, |
| submitter: str, |
| email: str, |
| paper_title: str, |
| paper_url: str, |
| notes: str, |
| current_state: dict | None, |
| ): |
| if current_state: |
| cleanup_work_dir(current_state.get('work_dir')) |
|
|
| if not archive_path: |
| return ( |
| None, |
| '', |
| '', |
| '## Validation failed\n\n- Please upload a zip file.', |
| '{}', |
| gr.update(interactive=False), |
| '', |
| archive_notice_text(None), |
| ) |
|
|
| domain = resolve_domain(suggested_domain, custom_domain) |
| token = load_hf_token() |
| metadata = SubmissionMetadata( |
| domain=domain, |
| submitter=submitter, |
| email=email, |
| paper_title=paper_title, |
| paper_url=paper_url, |
| notes=notes or '', |
| ) |
|
|
| try: |
| existing_ids = list_existing_task_ids(repo_id=DEFAULT_REPO_ID, token=token) |
| assigned_task_id = allocate_next_task_id(domain, existing_ids) |
| prepared = validate_and_prepare_submission(archive_path, metadata, assigned_task_id) |
| pr_ready = bool(token) |
| return ( |
| prepared.to_state(), |
| archive_path, |
| prepared.assigned_task_id, |
| build_validation_markdown(prepared), |
| json.dumps(build_public_report(prepared), indent=2, ensure_ascii=False), |
| gr.update(interactive=pr_ready), |
| '' if pr_ready else 'Validation passed, but PR creation is disabled until a write token is configured.', |
| archive_notice_text(archive_path), |
| ) |
| except ValidationError as exc: |
| cleanup_uploaded_archive(archive_path) |
| return ( |
| None, |
| '', |
| '', |
| build_failure_markdown(str(exc)), |
| json.dumps({'status': 'error', 'errors': str(exc).splitlines()}, indent=2, ensure_ascii=False), |
| gr.update(interactive=False), |
| '', |
| archive_notice_text(None), |
| ) |
| except Exception as exc: |
| cleanup_uploaded_archive(archive_path) |
| return ( |
| None, |
| '', |
| '', |
| build_failure_markdown(str(exc)), |
| json.dumps({'status': 'error', 'errors': [str(exc)]}, indent=2, ensure_ascii=False), |
| gr.update(interactive=False), |
| '', |
| archive_notice_text(None), |
| ) |
|
|
|
|
| def create_pr(state: dict | None, archive_path: str | None): |
| if not state: |
| return ( |
| None, |
| '', |
| gr.update(interactive=False), |
| '## PR creation failed\n\n- Validate a submission first.', |
| 'No ZIP file selected yet.', |
| ) |
|
|
| prepared = PreparedSubmission.from_state(state) |
| token = load_hf_token() |
| reassigned = False |
|
|
| for attempt in range(2): |
| try: |
| prepared, was_reassigned, head_sha = refresh_prepared_submission_for_pr( |
| prepared, |
| repo_id=DEFAULT_REPO_ID, |
| token=token, |
| ) |
| reassigned = reassigned or was_reassigned |
| commit_info = create_dataset_pr( |
| prepared, |
| repo_id=DEFAULT_REPO_ID, |
| token=token, |
| parent_commit=head_sha, |
| ) |
| pr_url = commit_info.pr_url or commit_info.commit_url |
| lines = [ |
| '## PR created', |
| '', |
| f'- Task ID: `{prepared.assigned_task_id}`', |
| f'- PR: {pr_url}', |
| ] |
| if reassigned: |
| lines.insert(3, '- The task ID was reassigned at PR time because the previously validated ID is no longer available on the dataset main branch.') |
| message = '\n'.join(lines) |
| cleanup_work_dir(prepared.work_dir) |
| cleanup_uploaded_archive(archive_path) |
| return None, '', gr.update(interactive=False), message, archive_notice_text(None) |
| except Exception as exc: |
| if attempt == 0 and is_retryable_pr_error(exc): |
| continue |
|
|
| message = str(exc).strip() or 'Unknown PR creation error' |
| if is_retryable_pr_error(exc): |
| message += '\nPlease click "Create Dataset PR" again. The dataset main branch changed while your PR was being created.' |
| return ( |
| prepared.to_state(), |
| archive_path or '', |
| gr.update(interactive=bool(token)), |
| build_failure_markdown(message), |
| archive_notice_text(archive_path), |
| ) |
|
|
|
|
| with gr.Blocks(title=SPACE_TITLE, fill_width=True) as demo: |
| state = gr.State(None, time_to_live=STATE_TTL_SECONDS, delete_callback=cleanup_submission_state) |
| archive_state = gr.State('', time_to_live=STATE_TTL_SECONDS, delete_callback=cleanup_uploaded_archive) |
|
|
| gr.HTML(build_hero_html()) |
|
|
| with gr.Group(elem_classes=['page-shell']): |
| with gr.Row(): |
| with gr.Column(scale=1, min_width=0, elem_classes=['shell-spacer']): |
| gr.HTML('') |
| with gr.Column(scale=30, min_width=0, elem_classes=['page-shell-content']): |
| with gr.Row(elem_classes=['section-row']): |
| with gr.Column(scale=7, elem_classes=['section-copy', 'main-form']): |
| gr.HTML(field_label_html('Task ZIP archive')) |
| with gr.Row(elem_classes=['upload-row']): |
| archive = gr.UploadButton( |
| 'Select ZIP file', |
| file_types=['.zip'], |
| file_count='single', |
| type='filepath', |
| variant='secondary', |
| elem_classes=['upload-button'], |
| ) |
| archive_notice = gr.Markdown('No ZIP file selected yet.', elem_classes=['upload-status']) |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML(field_label_html('Suggested domain')) |
| suggested_domain = gr.Dropdown( |
| choices=list(DOMAINS), |
| value='Astronomy', |
| show_label=False, |
| container=False, |
| ) |
| with gr.Column(): |
| gr.HTML(field_label_html('Custom domain (optional)')) |
| custom_domain = gr.Textbox( |
| placeholder='e.g. Robotics or Robot-Learning', |
| show_label=False, |
| container=False, |
| ) |
| gr.Markdown( |
| '<div class="caption">Use the custom field if your task does not belong to the suggested list. ' |
| 'If the custom field is filled, it overrides the suggested domain and becomes the prefix of the final task ID.</div>' |
| ) |
| gr.HTML(field_label_html('Submitter name or HF username')) |
| submitter = gr.Textbox( |
| placeholder='e.g. your-hf-handle', |
| show_label=False, |
| container=False, |
| ) |
| gr.HTML(field_label_html('Contact email')) |
| email = gr.Textbox( |
| placeholder='name@example.com', |
| show_label=False, |
| container=False, |
| ) |
| gr.HTML(field_label_html('Target paper title')) |
| paper_title = gr.Textbox(show_label=False, container=False) |
| gr.HTML(field_label_html('Target paper URL or DOI')) |
| paper_url = gr.Textbox( |
| placeholder='https://... or DOI', |
| show_label=False, |
| container=False, |
| ) |
| gr.HTML(field_label_html('Optional notes for reviewers')) |
| notes = gr.Textbox( |
| lines=4, |
| placeholder='Anything maintainers should know about licensing, preprocessing, or provenance.', |
| show_label=False, |
| container=False, |
| ) |
| with gr.Column(scale=5, elem_classes=['section-copy', 'side-notes']): |
| gr.Markdown(submission_guide_markdown(), elem_classes=['subtle-block']) |
|
|
| with gr.Row(elem_classes=['action-row']): |
| validate_btn = gr.Button('Validate ZIP', variant='primary', elem_classes=['primary-button']) |
| create_pr_btn = gr.Button('Create Dataset PR', interactive=False, elem_classes=['secondary-button']) |
|
|
| with gr.Column(elem_classes=['section-copy', 'results-shell']): |
| gr.HTML(field_label_html('Final task ID (assigned automatically)')) |
| assigned_task_id = gr.Textbox( |
| interactive=False, |
| show_label=False, |
| container=False, |
| ) |
| gr.Markdown(final_task_help_html()) |
| validation_md = gr.Markdown() |
| gr.HTML(field_label_html('Validation report')) |
| validation_report = gr.Code(language='json', show_label=False, container=False) |
| pr_md = gr.Markdown() |
| with gr.Column(scale=1, min_width=0, elem_classes=['shell-spacer']): |
| gr.HTML('') |
|
|
| archive.upload( |
| fn=handle_archive_upload, |
| inputs=[archive, archive_state], |
| outputs=[archive_state, archive_notice], |
| ) |
|
|
| validate_btn.click( |
| fn=validate_submission, |
| inputs=[ |
| archive_state, |
| suggested_domain, |
| custom_domain, |
| submitter, |
| email, |
| paper_title, |
| paper_url, |
| notes, |
| state, |
| ], |
| outputs=[ |
| state, |
| archive_state, |
| assigned_task_id, |
| validation_md, |
| validation_report, |
| create_pr_btn, |
| pr_md, |
| archive_notice, |
| ], |
| ) |
| create_pr_btn.click( |
| fn=create_pr, |
| inputs=[ |
| state, |
| archive_state, |
| ], |
| outputs=[ |
| state, |
| archive_state, |
| create_pr_btn, |
| pr_md, |
| archive_notice, |
| ], |
| ) |
|
|
|
|
| if __name__ == '__main__': |
| demo.launch( |
| theme=gr.themes.Base(), |
| css=CSS, |
| ssr_mode=False, |
| server_name=os.environ.get('GRADIO_SERVER_NAME', '0.0.0.0'), |
| server_port=int(os.environ.get('GRADIO_SERVER_PORT', os.environ.get('PORT', '7860'))), |
| ) |
|
|