|
import gradio as gr |
|
import json |
|
import time |
|
import traceback |
|
from validation import validate_json, validate_croissant, validate_records |
|
import requests |
|
|
|
def process_file(file): |
|
results = [] |
|
|
|
|
|
json_valid, json_message, json_data = validate_json(file.name) |
|
results.append(("JSON Format Validation", json_valid, json_message)) |
|
|
|
if not json_valid: |
|
return results |
|
|
|
|
|
croissant_valid, croissant_message = validate_croissant(json_data) |
|
results.append(("Croissant Schema Validation", croissant_valid, croissant_message)) |
|
|
|
if not croissant_valid: |
|
return results |
|
|
|
|
|
records_valid, records_message = validate_records(json_data) |
|
results.append(("Records Generation Test", records_valid, records_message)) |
|
|
|
return results |
|
|
|
def create_ui(): |
|
with gr.Blocks(theme=gr.themes.Soft()) as app: |
|
gr.Markdown("# Croissant JSON-LD Validator for NeurIPS") |
|
gr.Markdown(""" |
|
Upload your Croissant JSON-LD file or enter a URL to validate if it meets the requirements for NeurIPS submission. |
|
The validator will check: |
|
1. If the file is valid JSON |
|
2. If it passes Croissant schema validation |
|
3. If records can be generated within a reasonable time |
|
""") |
|
|
|
|
|
active_tab = gr.State("upload") |
|
|
|
|
|
with gr.Group(): |
|
|
|
with gr.Tabs() as tabs: |
|
with gr.TabItem("Upload File", id="upload_tab"): |
|
file_input = gr.File(label="Upload Croissant JSON-LD File", file_types=[".json", ".jsonld"]) |
|
validate_btn = gr.Button("Validate Uploaded File", variant="primary") |
|
|
|
with gr.TabItem("URL Input", id="url_tab"): |
|
url_input = gr.Textbox( |
|
label="Enter Croissant JSON-LD URL", |
|
placeholder="e.g. https://huggingface.co/api/datasets/facebook/natural_reasoning/croissant" |
|
) |
|
fetch_btn = gr.Button("Fetch and Validate", variant="primary") |
|
|
|
|
|
with gr.Group(): |
|
|
|
upload_progress = gr.HTML( |
|
"""<div class="progress-container"> |
|
<div class="progress-status">Ready for validation</div> |
|
</div>""", visible=True) |
|
|
|
|
|
validation_results = gr.HTML(visible=False) |
|
|
|
|
|
gr.HTML(""" |
|
<style> |
|
.gradio-container { |
|
max-width: 800px; |
|
margin: 0 auto; |
|
} |
|
/* Make group boxes background transparent/match page background */ |
|
.gr-group { |
|
background-color: transparent !important; |
|
border: none !important; |
|
box-shadow: none !important; |
|
} |
|
.validation-step { |
|
margin-bottom: 15px; |
|
border: 1px solid var(--border-color-primary, #e0e0e0); |
|
border-radius: 8px; |
|
overflow: hidden; |
|
} |
|
.step-header { |
|
padding: 10px 15px; |
|
background-color: var(--background-fill-secondary, #f5f5f5); |
|
display: flex; |
|
align-items: center; |
|
cursor: pointer; |
|
} |
|
.step-left { |
|
display: flex; |
|
align-items: center; |
|
flex-grow: 1; |
|
} |
|
.step-status { |
|
margin-right: 10px; |
|
width: 24px; |
|
height: 24px; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-weight: bold; |
|
color: white !important; |
|
font-size: 16px; |
|
text-shadow: 0px 0px 1px rgba(0,0,0,0.5); |
|
} |
|
/* Style for the progress status container and text - fixed height and italics */ |
|
.progress-container { |
|
min-height: 45px; |
|
padding: 12px; |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 10px; |
|
} |
|
.progress-status { |
|
font-style: italic; |
|
width: 100%; |
|
} |
|
.arrow-indicator { |
|
margin-left: 10px; |
|
font-size: 16px; |
|
transition: transform 0.3s ease; |
|
} |
|
.arrow-down { |
|
transform: rotate(90deg); |
|
} |
|
.status-success { |
|
background-color: #4caf50; |
|
} |
|
.status-error { |
|
background-color: #f44336; |
|
} |
|
.status-waiting { |
|
background-color: #9e9e9e; |
|
} |
|
/* Dark mode specific styles */ |
|
.dark .step-header { |
|
background-color: var(--background-fill-secondary, #2e2e2e); |
|
color: var(--body-text-color, #ffffff); |
|
} |
|
.dark .step-title { |
|
color: var(--body-text-color, #ffffff); |
|
} |
|
.dark .step-details { |
|
color: var(--body-text-color, #ffffff); |
|
background-color: var(--background-fill-primary, #1f1f1f); |
|
padding: 10px 15px; |
|
} |
|
/* Add this to ensure details are also styled for light mode */ |
|
.step-details { |
|
padding: 10px 15px; |
|
background-color: var(--background-fill-primary, #ffffff); |
|
} |
|
</style> |
|
""") |
|
|
|
|
|
def on_tab_change(evt: gr.SelectData): |
|
tab_id = evt.value |
|
if tab_id == "Upload File": |
|
return "upload", """<div class="progress-container"> |
|
<div class="progress-status">Ready for upload</div> |
|
</div>""", gr.update(visible=False) |
|
else: |
|
return "url", """<div class="progress-container"> |
|
<div class="progress-status">Enter a URL to fetch</div> |
|
</div>""", gr.update(visible=False) |
|
|
|
def on_file_upload(file): |
|
if file is None: |
|
return """<div class="progress-container"> |
|
<div class="progress-status">Ready for upload</div> |
|
</div>""", gr.update(visible=False) |
|
|
|
return """<div class="progress-container"> |
|
<div class="progress-status">β
File uploaded successfully</div> |
|
</div>""", gr.update(visible=False) |
|
|
|
def fetch_from_url(url): |
|
if not url: |
|
return """<div class="progress-container"> |
|
<div class="progress-status">Please enter a URL</div> |
|
</div>""", gr.update(visible=False) |
|
|
|
try: |
|
|
|
response = requests.get(url, timeout=10) |
|
response.raise_for_status() |
|
json_data = response.json() |
|
|
|
|
|
progress_html = """<div class="progress-container"> |
|
<div class="progress-status">β
JSON fetched successfully from URL</div> |
|
</div>""" |
|
|
|
|
|
results = [] |
|
results.append(("JSON Format Validation", True, "β
The URL returned valid JSON.")) |
|
|
|
croissant_valid, croissant_message = validate_croissant(json_data) |
|
results.append(("Croissant Schema Validation", croissant_valid, croissant_message)) |
|
|
|
if not croissant_valid: |
|
return progress_html, build_results_html(results) |
|
|
|
records_valid, records_message = validate_records(json_data) |
|
results.append(("Records Generation Test", records_valid, records_message)) |
|
|
|
return progress_html, build_results_html(results) |
|
|
|
except requests.exceptions.RequestException as e: |
|
error_message = f"β Error fetching URL: {str(e)}" |
|
return f"""<div class="progress-container"> |
|
<div class="progress-status">{error_message}</div> |
|
</div>""", gr.update(visible=False) |
|
except json.JSONDecodeError as e: |
|
error_message = f"β URL did not return valid JSON: {str(e)}" |
|
return f"""<div class="progress-container"> |
|
<div class="progress-status">{error_message}</div> |
|
</div>""", gr.update(visible=False) |
|
except Exception as e: |
|
error_message = f"β Unexpected error: {str(e)}" |
|
return f"""<div class="progress-container"> |
|
<div class="progress-status">{error_message}</div> |
|
</div>""", gr.update(visible=False) |
|
|
|
def build_results_html(results): |
|
|
|
html = '<div class="validation-results">' |
|
|
|
for i, (test_name, passed, message) in enumerate(results): |
|
status_class = "status-success" if passed else "status-error" |
|
status_icon = "β" if passed else "β" |
|
|
|
html += f''' |
|
<div class="validation-step" id="step-{i}"> |
|
<div class="step-header" onclick=" |
|
var details = document.getElementById('details-{i}'); |
|
var arrow = document.getElementById('arrow-{i}'); |
|
if(details.style.display === 'none') {{ |
|
details.style.display = 'block'; |
|
arrow.classList.add('arrow-down'); |
|
}} else {{ |
|
details.style.display = 'none'; |
|
arrow.classList.remove('arrow-down'); |
|
}}"> |
|
<div class="step-left"> |
|
<div class="step-status {status_class}">{status_icon}</div> |
|
<div class="step-title">{test_name}</div> |
|
<div class="arrow-indicator" id="arrow-{i}">βΆ</div> |
|
</div> |
|
</div> |
|
<div class="step-details" id="details-{i}" style="display: none;"> |
|
{message} |
|
</div> |
|
</div> |
|
''' |
|
|
|
html += '</div>' |
|
return gr.update(value=html, visible=True) |
|
|
|
def on_validate(file): |
|
if file is None: |
|
return gr.update(visible=False) |
|
|
|
|
|
results = process_file(file) |
|
return build_results_html(results) |
|
|
|
|
|
tabs.select(on_tab_change, None, [active_tab, upload_progress, validation_results]) |
|
file_input.change(on_file_upload, inputs=file_input, outputs=[upload_progress, validation_results]) |
|
validate_btn.click(on_validate, inputs=file_input, outputs=validation_results) |
|
fetch_btn.click(fetch_from_url, inputs=url_input, outputs=[upload_progress, validation_results]) |
|
|
|
|
|
gr.HTML(""" |
|
<div style="text-align: center; margin-top: 20px;"> |
|
<p>Learn more about <a href="https://github.com/mlcommons/croissant" target="_blank">Croissant format</a>.</p> |
|
</div> |
|
""") |
|
|
|
return app |
|
|
|
if __name__ == "__main__": |
|
app = create_ui() |
|
app.launch() |