ema-dev / ui /layout.py
carlosh93's picture
fixing some issues including browse section disappear and whenever the image is replaced or cleared the vlm_usage_state because that usage belongs to the previous image and VLM caption. When the replacement image gets its new caption, vlm_usage_state will be populated with the new API call’s usage.
8aa9bea
Raw
History Blame Contribute Delete
108 kB
import datetime
import gradio as gr
import json
import time
from pathlib import Path
# from logic.supabase_client import auth_handler
from huggingface_hub import HfApi
from urllib.parse import urlencode, parse_qs
from logic.data_utils import CustomHFDatasetSaver, mark_sample_excluded
from data.lang2eng_map import lang2eng_mapping
from gradio_modal import Modal
from logic.handlers import *
from logic.image_transforms import rotate_image_90_left, rotate_image_90_right, reset_image_to_original
from config.settings import *
from functools import partial
from .selection_page import build_selection_page, prefill_country_language
from .main_page import build_main_page
from .main_page import sort_with_pyuca
from .user_page import build_user_page, render_comparative_stats, render_user_centric_stats, render_missing_concepts
from logic.user_stats import rebuild_user_profile, compute_comparative_stats, compute_user_centric_stats
# js_code = """
# function() {
# // Get the full URL with the fragment
# const url = window.location.href;
# const fragment = url.split('#')[1];
# if (!fragment) {
# return "";
# }
# // Parse the fragment into an object
# const params = new URLSearchParams(fragment);
# const access_token = params.get('access_token');
# const refresh_token = params.get('refresh_token');
# // Create a JSON string with the tokens
# const tokens = JSON.stringify({
# access_token: access_token,
# refresh_token: refresh_token
# });
# // Return the JSON string to the Gradio output component
# return tokens;
# }
# """
def load_examples_raw(json_path: Path):
"""
Load static examples from JSON as a list of dicts, preserving all fields
(image, image_url, caption, country, language, category, concept,
additional_concepts, id, notes).
"""
if not json_path.exists():
print(f"Examples file not found at {json_path}")
return []
try:
with open(json_path, "r", encoding="utf-8") as f:
raw_examples = json.load(f)
except Exception as e:
print(f"Failed to read examples file: {e}")
return []
if not isinstance(raw_examples, list):
return []
return raw_examples
def _html_escape(text):
if text is None:
return ""
text = str(text)
return (
text.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
)
def render_examples_modal_html(examples, metadata_dict, country, language):
"""Build an HTML string displaying example cards for the current locale."""
meta = {}
try:
meta = metadata_dict.get(country, {}).get(language, {}) or {}
except Exception:
meta = {}
# Labels
image_label = meta.get("Examples_Image_Label", "Image")
desc_label = meta.get("Examples_Description_Label", "Description")
country_label = meta.get("Country", "Country")
language_label = meta.get("Language", "Language")
category_label = meta.get("Examples_Category_Label", "Main category")
concept_label = meta.get("Examples_Concept_Label", "Main concept")
additional_label = meta.get("Examples_Additional_Label", "Additional concepts")
example_label = meta.get("Examples_Example_Label", "Example")
intro_text = meta.get("Examples_Intro", "Below are reference examples. Each field is labeled so you can see exactly what to put where.")
no_examples_text = meta.get("Examples_Empty_Msg", "No examples configured yet. Add entries to <code>data/examples.json</code>.")
intro = (
'<div class="examples-intro">'
f'<p>{_html_escape(intro_text)}</p>'
'</div>'
)
display_examples = [ex for ex in (examples or []) if isinstance(ex, dict)]
if not display_examples:
return intro + f'<p class="examples-empty"><em>{no_examples_text}</em></p>'
cards = []
for idx, ex in enumerate(display_examples, start=1):
concept = ex.get("concept", "") or ""
image_url = ex.get("image_url") or ""
caption = ex.get("caption", "") or ""
ex_country = ex.get("country", "") or ""
ex_language = ex.get("language", "") or ""
ex_category = ex.get("category", "") or ""
additional = ex.get("additional_concepts", []) or []
if isinstance(additional, list):
additional_chips = "".join(
f'<span class="chip">{_html_escape(str(c).strip())}</span>'
for c in additional if c
) or '<span class="chip chip-empty">—</span>'
else:
additional_chips = (
f'<span class="chip">{_html_escape(str(additional).strip())}</span>'
if str(additional).strip() else '<span class="chip chip-empty">—</span>'
)
notes = ex.get("notes", "") or ""
title = concept.strip() or f"{example_label} {idx}"
image_html = ""
if image_url:
src = image_url if image_url.startswith(("http://", "https://")) else f"/gradio_api/file={image_url}"
image_html = (
'<div class="example-image">'
f'<img src="{_html_escape(src)}" alt="{_html_escape(title)}" />'
'</div>'
)
meta_rows = (
'<dl class="example-fields">'
f'<dt>📍 {_html_escape(country_label)}</dt><dd>{_html_escape(ex_country) or "—"}</dd>'
f'<dt>💬 {_html_escape(language_label)}</dt><dd>{_html_escape(ex_language) or "—"}</dd>'
f'<dt>📂 {_html_escape(category_label)}</dt><dd>{_html_escape(ex_category) or "—"}</dd>'
f'<dt>🎯 {_html_escape(concept_label)}</dt><dd><strong>{_html_escape(concept) or "—"}</strong></dd>'
f'<dt>➕ {_html_escape(additional_label)}</dt><dd class="chip-row">{additional_chips}</dd>'
'</dl>'
)
description_html = (
'<div class="example-description">'
f'<span class="block-label">{_html_escape(desc_label)}</span>'
f'<p>{_html_escape(caption) or "<em>—</em>"}</p>'
'</div>'
)
notes_html = ""
if notes:
is_positive = ex.get("is_positive")
if is_positive is True:
bg_color = "rgba(34, 197, 94, 0.12)"
border_color = "rgba(34, 197, 94, 0.35)"
emoji = "✅"
elif is_positive is False:
bg_color = "rgba(239, 68, 68, 0.12)"
border_color = "rgba(239, 68, 68, 0.35)"
emoji = "❌"
else:
bg_color = "rgba(250, 204, 21, 0.12)"
border_color = "rgba(250, 204, 21, 0.35)"
emoji = "💡"
notes_html = (
f'<div class="example-notes" style="background: {bg_color}; border: 1px solid {border_color};">'
f'<span class="notes-icon" aria-hidden="true">{emoji}</span>'
f'<p>{_html_escape(notes)}</p>'
'</div>'
)
cards.append(
'<article class="example-card">'
'<header class="example-header">'
f'<span class="example-number">{_html_escape(example_label)} {idx}</span>'
f'<h3>{_html_escape(title)}</h3>'
'</header>'
'<div class="example-body">'
f'{image_html}'
'<div class="example-content">'
f'{meta_rows}'
f'{description_html}'
f'{notes_html}'
'</div>'
'</div>'
'</article>'
)
return intro + '<div class="examples-grid">' + "".join(cards) + '</div>'
_EXAMPLES_FALLBACK_PATH = Path(__file__).resolve().parent.parent / "data" / "guidelines" / "USA" / "English" / "examples.json"
def build_examples_modal_updates(local_storage, metadata_dict):
"""
Build (button_label, modal_title_md, modal_content_md) updates for the
current country/language stored in local_storage.
"""
country, language, _ = extract_country_language_username(local_storage)
meta = {}
try:
meta = metadata_dict.get(country, {}).get(language, {}) or {}
except Exception:
meta = {}
# Load locale-specific examples, fall back to USA/English
examples_path = Path(meta.get("Examples", "")) if meta.get("Examples") else None
if not examples_path or not examples_path.exists():
examples_path = _EXAMPLES_FALLBACK_PATH
examples = load_examples_raw(examples_path)
btn_label = meta.get("Examples_btn") or meta.get("Tab_Examples", "🖼️ See Examples")
if not btn_label.startswith(("🖼", "📘", "📗", "📚")):
btn_label = f"🖼️ {btn_label}"
modal_title = meta.get(
"Examples_Modal_Title", "## Examples — how to fill in each field"
)
close_label = meta.get("Examples_Modal_Close_btn") or meta.get("Modal_Cancel_btn", "Close")
content = render_examples_modal_html(examples, metadata_dict, country, language)
return (
gr.update(value=btn_label),
gr.update(value=modal_title),
gr.update(value=content),
gr.update(value=close_label),
)
scroll_to_top_js = """
function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
return "";
}
"""
# def login_user(email, password):
# result = auth_handler.login(email, password)
# if result['success']:
# session_data = result['data']
# persistent_data = {
# "refresh_token": session_data['refresh_token'],
# "user_email": session_data['user_email']
# }
# return session_data['client'], persistent_data, result['message']
# else:
# persistent_data = {
# "refresh_token": "",
# "user_email": ""
# }
# return None, persistent_data, result['message']
# def login_user_recovery(session_data: str):
# """
# This function receives session data (tokens as a JSON string) from the frontend,
# retrieves the session, and returns data in a format similar to login_user.
# """
# try:
# import json
# tokens = json.loads(session_data)
# access_token = tokens.get("access_token")
# refresh_token = tokens.get("refresh_token")
# if not access_token or not refresh_token:
# return None, gr.skip(), "Invalid session data provided."
# result = auth_handler.retrieve_session_from_tokens(access_token, refresh_token)
# if result['success']:
# session_data_result = result['data']
# persistent_data = {
# "refresh_token": session_data_result['refresh_token'],
# "user_email": session_data_result['user_email']
# }
# return session_data_result['client'], persistent_data, result['message']
# else:
# persistent_data = {
# "refresh_token": "",
# "user_email": ""
# }
# return None, persistent_data, result['message']
# except Exception as e:
# return None, gr.skip(), f"Failed to process recovery login: {e}"
# def sign_up(email, password):
# result = auth_handler.sign_up(email, password)
# return result['message']
# def reset_password(email):
# result = auth_handler.reset_password_for_email(email)
# return result['message']
# def log_out(supabase_user_client, persistent_session):
# """
# Logs out the user and clears the session. If error occurs, it returns an empty persistent session (logging out user).
# """
# persistent_session = {
# "refresh_token": "",
# "user_email": ""
# }
# if supabase_user_client:
# result = auth_handler.logout(supabase_user_client)
# if result['success']:
# print("User logged out successfully.")
# return persistent_session
# else:
# print(f"Error logging out: {result['message']}")
# return persistent_session
# else:
# print("No user client provided to log out.")
# return persistent_session
# def restore_user_session(session_data, login_status=None):
# print("Restoring user session with data:", session_data)
# # defualt values if the user is not logged in
# # or the session data is not valid
# login_status_update = gr.update(value= login_status if login_status else "")
# proceed_button_update = gr.update(value="Proceed as Anonymous User", interactive=True)
# login_button_update = gr.update(visible=True)
# sign_up_button_update = gr.update(visible=True)
# reset_password_button_update = gr.update(visible=True)
# logout_button_update = gr.update(visible=False)
# change_password_field_update = gr.update(visible=False)
# change_password_field_confirm_update = gr.update(visible=False)
# change_password_button_update = gr.update(visible=False)
# change_password_status_update = gr.update(value="")
# persistent_data = {
# "refresh_token": "",
# "user_email": ""
# }
# if not session_data or not session_data.get('refresh_token', ''):
# print("No session data found, proceeding as anonymous user.")
# return None, persistent_data, login_status_update, proceed_button_update, login_button_update, sign_up_button_update, reset_password_button_update, logout_button_update, change_password_field_update, change_password_field_confirm_update, change_password_button_update, change_password_status_update
# result = auth_handler.restore_session(session_data['refresh_token'])
# if result['success']:
# restored_session = result['data']
# new_persistent_data = {
# "refresh_token": restored_session['refresh_token'],
# "user_email": restored_session['user_email']
# }
# login_status_update = gr.update(value=result['message'])
# proceed_button_update = gr.update(value="Proceed", interactive=True)
# login_button_update = gr.update(visible=False)
# sign_up_button_update = gr.update(visible=False)
# reset_password_button_update = gr.update(visible=False)
# logout_button_update = gr.update(visible=True)
# change_password_field_update = gr.update(visible=True)
# change_password_field_confirm_update = gr.update(visible=True)
# change_password_button_update = gr.update(visible=True)
# return restored_session['client'], new_persistent_data, login_status_update, proceed_button_update, login_button_update, sign_up_button_update, reset_password_button_update, logout_button_update, change_password_field_update, change_password_field_confirm_update, change_password_button_update, change_password_status_update
# else:
# return None, persistent_data, login_status_update, proceed_button_update, login_button_update, sign_up_button_update, reset_password_button_update, logout_button_update, change_password_field_update, change_password_field_confirm_update, change_password_button_update, change_password_status_update
# def change_password(supabase_user_client, new_password, confirm_password):
# """
# Changes the user's password.
# """
# if new_password != confirm_password:
# return "Passwords do not match. Please try again."
# result = auth_handler.change_password(supabase_user_client, new_password)
# return result['message']
def get_key_by_value(dictionary, value):
for key, val in dictionary.items():
if val == value:
return key
return None
def _category_display_value_choices(categories_list, language):
translations = words_mapping.get(language) or {}
return [(translations.get(category, category), category) for category in categories_list]
def _canonical_category_value(category, language, categories):
if not category:
return None
if category in categories:
return category
reverse_translations = {
display: canonical
for canonical, display in (words_mapping.get(language) or {}).items()
}
canonical = reverse_translations.get(category)
if canonical in categories:
return canonical
return None
def handle_click_example_with_states(
user_examples,
vlm_captions,
vlm_models,
vlm_feedbacks,
vlm_usages,
additional_concepts_map,
concepts_dict,
):
"""Load an example while preserving its original VLM metadata in state."""
result = list(
handle_click_example(
user_examples,
vlm_captions,
vlm_models,
concepts_dict,
additional_concepts_map,
)
)
img, url, caption, example_id, cat, concept = result[:6]
ac1, ac2, ac3, ac4, ac5 = result[6:11]
vlm_cap = result[12]
saved_model = (vlm_models or {}).get(example_id) or result[13]
saved_feedback = (vlm_feedbacks or {}).get(example_id) or None
saved_usage = (vlm_usages or {}).get(example_id) if vlm_cap else None
needs_feedback = bool(vlm_cap and not saved_feedback)
feedback_update = (
gr.update(value=None, visible=True)
if needs_feedback
else gr.update(visible=False)
)
ts = datetime.datetime.now().timestamp()
vlm_sig = image_signature(img) if vlm_cap else None
return result + [
feedback_update,
img,
url,
caption,
cat,
concept,
ac1,
ac2,
ac3,
ac4,
ac5,
example_id,
vlm_cap,
saved_model,
saved_feedback,
saved_usage,
ts,
vlm_sig,
]
def extract_country_language_username(local_storage):
if not local_storage:
return None, None, None
email = (local_storage[2] or "").strip() if len(local_storage) > 2 else ""
return local_storage[0], local_storage[1], email
def _is_anonymous_email(email):
email = (email or "").strip().lower()
return (not email) or email == "anonymous"
def set_step(step, show_browse_data=True, email=""):
is_anonymous = _is_anonymous_email(email)
show_data_row = (step == 1) and (not is_anonymous)
show_data_table = show_data_row and bool(show_browse_data)
return (
gr.update(visible=step == 1),
gr.update(visible=step == 2),
gr.update(visible=step == 3),
gr.update(visible=show_data_row),
gr.update(visible=show_data_table),
)
def set_step_from_storage(step, show_browse_data, local_storage):
_, _, email = extract_country_language_username(local_storage)
return set_step(step, show_browse_data, email)
def sync_preview(image):
return image, image
def toggle_browse_data(show_browse, current_btn_value):
new_show = not show_browse
base_text = current_btn_value.lstrip("▼▲").strip()
return (
new_show,
gr.update(visible=new_show),
gr.update(value=f"▼ {base_text}" if new_show else f"▲ {base_text}"),
)
def load_concepts(category, concept_btn, local_storage, loading_example, concepts):
country, lang, _ = local_storage
if country is None or lang is None:
return gr.update(choices=[], value=None), False
eng_lang = lang2eng_mapping.get(lang, lang)
if category:
category = _canonical_category_value(category, lang, concepts[country][eng_lang])
if category is None:
return gr.update(choices=[], value=None), False
choices = concepts[country][eng_lang][category]
# Keep custom concept values when loading an existing sample.
if loading_example:
concept_val = concept_btn
else:
concept_val = concept_btn if concept_btn in choices else None
return gr.update(choices=sort_with_pyuca(choices), value=concept_val), False
else:
concepts_list = []
for cat in concepts[country][eng_lang]:
for concept in concepts[country][eng_lang][cat]:
concepts_list.append(concept)
return gr.update(choices=sort_with_pyuca(concepts_list), value=None), False
def update_category(category, concept_btn, local_storage, loading_example, concepts):
country, lang, _ = local_storage
if country is None or lang is None:
return gr.update(choices=[], value=None), False
eng_lang = lang2eng_mapping.get(lang, lang)
all_eng_cats = sort_with_pyuca(list(concepts[country][eng_lang].keys()))
category_choices = _category_display_value_choices(all_eng_cats, lang)
if not category and concept_btn:
found_eng_cat = None
for cat in concepts[country][eng_lang]:
for concept in concepts[country][eng_lang][cat]:
if concept == concept_btn:
found_eng_cat = cat
return gr.update(choices=category_choices, value=found_eng_cat), True
else:
category = _canonical_category_value(category, lang, concepts[country][eng_lang])
return gr.update(choices=category_choices, value=category), loading_example
def load_profile(profile: gr.OAuthProfile | None):
# if the user is not logged in, profile will be None
if profile is None:
proceed_msg = "Continue"
anno_msg = "Annotate"
username = ""
user_name = ""
hf_username = ""
hf_email = ""
flag = False
else:
user_name = profile.name
proceed_msg = f"Continue as {user_name}"
anno_msg = f"Annotate as {user_name}"
username = profile.username
hf_username = profile.username
# The HF "email" OAuth scope (see README.md) makes the email claim
# available in the userinfo. OAuthProfile is a dict, so access it via
# .get(). Falls back to "" if the claim is absent (e.g. local mock).
hf_email = (profile.get("email") or "").strip()
flag = True
return (
anno_msg,
username,
user_name,
gr.update(value=proceed_msg, visible=flag, interactive=False),
hf_username,
hf_email,
)
def get_username(request: gr.Request):
if request.user:
return request.user
return "anonymous"
def update_profile_proceed(profile: gr.OAuthProfile | None, current_email=""):
# Uses OAuth profile instead of request.user (no auth middleware).
current_email = (current_email or "").strip()
if profile is None:
# Not signed in with Hugging Face. If the user typed an email manually,
# proceed with that identity instead of forcing anonymous (and without
# clobbering the email they entered).
if current_email:
return "Annotate", "", "", ""
gr.Warning("User is not logged in. Proceeding as anonymous.", duration=3)
return "Annotate as Anonymous", "anonymous", "", ""
api = HfApi()
username = profile.username
hf_username = profile.username
hf_email = (profile.get("email") or "").strip()
user_obj = api.get_user_overview(username)
user_name = user_obj.fullname or username
return f"Annotate as {user_name}", username, hf_username, hf_email
def load_anonymous():
proceed_msg = "Annotate as Anonymous"
username = "anonymous" # TODO: Make random anonymous users
return proceed_msg, username, "", gr.update(value="")
def switch_ui(country, language, email, flag=False, metadata_dict=None):
# wait for 2 seconds
print(f"Language: {language}, Country: {country}")
email = (email or "").strip()
if not flag and (not country or not language):
gr.Warning(f"⚠️ Please select Country and Language first.", duration=3)
return (gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip())
# Hide the selection page and show the main UI.
meta = (metadata_dict or {}).get(country, {}).get(language, {}) if metadata_dict else {}
if flag:
gr.Info(meta.get("Info_Loading_Selection", "Loading the Language Selection UI"), duration=3)
time.sleep(2)
local_storage = [None, None, ""]
countries = sort_with_pyuca(list(metadata_dict.keys()))
return (local_storage, gr.update(visible=flag), gr.update(visible=not flag),
gr.update(visible=not flag),
gr.update(visible=not flag),
gr.update(choices=countries, label="Country", value=None),
gr.update(value=None, choices=[], label="Language", allow_custom_value=False, interactive=False),
gr.update(value="## Please select your profile"),
gr.update(value="## Please select your country"),
gr.update(value="Annotate"))
else:
gr.Info(meta.get("Info_Loading_Main", "Loading the Main UI"), duration=3)
time.sleep(2)
local_storage = [country, language, email]
return (local_storage, gr.update(visible=flag), gr.update(visible=not flag),
gr.update(visible=not flag), gr.update(visible=not flag),
gr.skip(), gr.skip(), gr.skip(), gr.skip(), gr.skip())
def build_ui(concepts_dict, metadata_dict, HF_API_TOKEN, HF_DATASET_NAME, prefs_store=None):
hf_writer = CustomHFDatasetSaver(HF_API_TOKEN, HF_DATASET_NAME, private=True)
custom_css = """
.compact-container {
max-width: 600px;
margin: auto;
padding: 20px;
}
.compact-btn {
min-width: 0 !important;
}
#user_page_btn,
#user_page_btn button {
max-width: 260px;
min-width: 0 !important;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.step-description-box {
border: 1px solid var(--border-color-primary, #e5e7eb) !important;
background: var(--block-background-fill, #f9fafb) !important;
border-radius: 8px !important;
padding: 0.85rem 1.15rem !important;
margin-bottom: 1.25rem !important;
box-shadow: var(--block-shadow, none);
}
.step-description-box .step-description-box {
border: none !important;
background: transparent !important;
padding: 0 !important;
margin: 0 !important;
box-shadow: none !important;
}
#image_instruction_accordion,
#description_instruction_accordion {
border: 2px solid rgba(37, 99, 235, 0.35) !important;
background: rgba(37, 99, 235, 0.06) !important;
border-radius: 8px !important;
margin: 0.75rem 0 1rem;
}
#image_instruction_accordion .label-wrap,
#description_instruction_accordion .label-wrap {
font-weight: 700 !important;
color: #1d4ed8 !important;
}
#image_inp img {
object-fit: contain; /* make sure the full image shows */
height: 460px; /* set a fixed height */
}
#vlm_output .input-container {
position: relative;
}
#vlm_output .input-container::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
z-index: 10; /* sits above the textarea */
background: transparent;
}
/* === Examples modal === */
#examples_modal_content { padding: 0; }
#examples_modal_content .examples-intro { margin-bottom: 1.25rem; }
#examples_modal_content .examples-intro > p {
font-size: 0.95rem;
color: var(--body-text-color);
margin: 0 0 0.75rem 0;
}
#examples_modal_content .examples-tips {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 0.6rem;
}
#examples_modal_content .tip-card {
display: flex;
gap: 0.6rem;
padding: 0.7rem 0.85rem;
border-radius: 10px;
background: var(--background-fill-secondary, #f6f7fb);
border: 1px solid var(--border-color-primary, #e3e6ee);
}
#examples_modal_content .tip-card .tip-icon { font-size: 1.25rem; line-height: 1.2; }
#examples_modal_content .tip-card strong { display: block; margin-bottom: 0.2rem; }
#examples_modal_content .tip-card p { margin: 0; font-size: 0.85rem; opacity: 0.9; }
#examples_modal_content .examples-grid {
display: flex;
flex-direction: column;
gap: 1.25rem;
margin-top: 0.5rem;
}
#examples_modal_content .example-card {
border: 1px solid var(--border-color-primary, #e3e6ee);
border-radius: 14px;
overflow: hidden;
background: var(--background-fill-primary, #fff);
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
#examples_modal_content .example-header {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.7rem 1rem;
background: linear-gradient(90deg, rgba(99, 102, 241, 0.08), rgba(168, 85, 247, 0.06));
border-bottom: 1px solid var(--border-color-primary, #e3e6ee);
}
#examples_modal_content .example-number {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
background: rgba(99, 102, 241, 0.15);
color: #4338ca;
padding: 0.2rem 0.55rem;
border-radius: 999px;
}
#examples_modal_content .example-header h3 {
margin: 0;
font-size: 1.05rem;
font-weight: 600;
}
#examples_modal_content .example-body {
display: grid;
grid-template-columns: minmax(200px, 280px) 1fr;
gap: 1rem;
padding: 1rem;
}
@media (max-width: 720px) {
#examples_modal_content .example-body { grid-template-columns: 1fr; }
}
#examples_modal_content .example-image img {
width: 100%;
height: auto;
max-height: 280px;
object-fit: cover;
border-radius: 10px;
display: block;
}
#examples_modal_content .example-content { display: flex; flex-direction: column; gap: 0.85rem; }
#examples_modal_content .example-fields {
display: grid;
grid-template-columns: max-content 1fr;
row-gap: 0.35rem;
column-gap: 0.75rem;
margin: 0;
font-size: 0.9rem;
}
#examples_modal_content .example-fields dt {
font-weight: 500;
opacity: 0.75;
}
#examples_modal_content .example-fields dd { margin: 0; }
#examples_modal_content .chip-row {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
}
#examples_modal_content .chip {
display: inline-block;
padding: 0.15rem 0.55rem;
border-radius: 999px;
background: rgba(99, 102, 241, 0.1);
color: #4338ca;
font-size: 0.8rem;
font-weight: 500;
}
#examples_modal_content .chip-empty {
background: transparent;
color: var(--body-text-color);
opacity: 0.6;
font-weight: 400;
}
#examples_modal_content .example-description {
background: var(--background-fill-secondary, #f6f7fb);
border-left: 3px solid #6366f1;
border-radius: 0 8px 8px 0;
padding: 0.6rem 0.85rem;
}
#examples_modal_content .block-label {
display: inline-block;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #4f46e5;
margin-bottom: 0.25rem;
}
#examples_modal_content .example-description p {
margin: 0;
font-size: 0.92rem;
line-height: 1.45;
}
#examples_modal_content .example-notes {
display: flex;
gap: 0.55rem;
padding: 0.55rem 0.75rem;
background: rgba(250, 204, 21, 0.12);
border: 1px solid rgba(250, 204, 21, 0.35);
border-radius: 10px;
font-size: 0.88rem;
}
#examples_modal_content .example-notes .notes-icon { font-size: 1.05rem; }
#examples_modal_content .example-notes p { margin: 0; line-height: 1.45; }
#examples_modal_content .examples-empty {
padding: 1rem;
text-align: center;
opacity: 0.7;
}
"""
custom_js = """
function setMissingConcept(concept) {
const element = document.querySelector('#hidden_concept_trigger input');
if (element) {
element.value = concept;
element.dispatchEvent(new Event('input'));
}
}
"""
############################################################################
with gr.Blocks(css=custom_css, js=custom_js) as ui:
supabase_user_client = gr.State(None)
# session_state = gr.State({})
persistent_session = gr.BrowserState(None)
local_storage = gr.State([None, None, ""])
country_state = gr.State(None)
language_state = gr.State(None)
username_state = gr.State(None)
category_state = gr.State(None)
concept_state = gr.State(None)
caption_state = gr.State(None)
image_url_state = gr.State(None)
quick_submit_state = gr.State(False)
category_1_concepts_state = gr.State(None)
category_2_concepts_state = gr.State(None)
category_3_concepts_state = gr.State(None)
category_4_concepts_state = gr.State(None)
category_5_concepts_state = gr.State(None)
timestamp_state = gr.State(None)
exampleid_state = gr.State(None)
vlm_caption_state = gr.State(None)
vlm_feedback_state = gr.State(None)
vlm_model_state = gr.State(None)
vlm_usage_state = gr.State(None)
vlm_image_signature_state = gr.State(None)
hf_username_state = gr.State("")
hf_email_state = gr.State("")
pref_country_state = gr.State(None)
pref_language_state = gr.State(None)
loading_example = gr.State(False) # to check if the values are loaded from a user click on an example in
current_step_state = gr.State(1)
# First page: selection
(
selection_page, country_choice, language_choice, proceed_btn, anon_btn, login_btn, prof_btn,
username, user_name, email_inp, intro_title, intro_markdown, signin_title, selection_title,
signin_section, selection_section,
) = build_selection_page(metadata_dict)
# Output order: proceed button, displayed username, display name, profile button, HF username state, email field.
# Clearing the email field is important so a previously HF-authenticated email is not
# reused when the user explicitly chooses to proceed anonymously.
anon_continue_event = anon_btn.click(fn=load_anonymous, outputs=[proceed_btn, username, hf_username_state, email_inp])
# Helper-text shown under the email field depending on the identity mode.
_EMAIL_INFO_EDITABLE = "Add your email to save your work and review or edit it later."
_EMAIL_INFO_LOCKED = "Using your Hugging Face email. Log out to use a different email."
def _email_update(value, locked):
"""Build an email_inp update that locks the field (read-only) when the
user is signed in with a verified Hugging Face email, and keeps it
editable otherwise."""
return gr.update(
value=value,
interactive=not locked,
info=_EMAIL_INFO_LOCKED if locked else _EMAIL_INFO_EDITABLE,
)
def _prefs_from_hf_or_session(hf_username, hf_email, current_email, session):
session = session or {}
session_email = (session.get("email") or "").strip()
session_country = session.get("country")
session_language = session.get("language")
prev_hf_username = (session.get("hf_username") or "").strip()
current_hf_username = (hf_username or "").strip()
hf_email = (hf_email or "").strip()
current_email = (current_email or "").strip()
# If the user was previously signed in with Hugging Face but is no
# longer signed in (e.g., they just clicked "Logout"), clear the email
# and make the field editable again.
if prev_hf_username and not current_hf_username:
return _email_update("", locked=False), session_country, session_language
# Signed in with Hugging Face: force the verified HF email and lock the
# field, so the contribution identity is always the verified account.
if current_hf_username:
country = session_country
language = session_language
if prefs_store:
by_hf = prefs_store.get_by_hf_username(current_hf_username)
if by_hf:
country = by_hf.get("last_country") or session_country
language = by_hf.get("last_language") or session_language
if hf_email:
# Verified email available -> lock to it.
return _email_update(hf_email, locked=True), country, language
# No verified email (e.g., missing scope / local mock): fall back to
# a known email but keep the field editable so identity isn't lost.
fallback_email = session_email or current_email
return _email_update(fallback_email, locked=False), country, language
# Not signed in with HF: keep the email editable. Respect a value that
# is already in the field (manually typed or kept from a prefill).
if current_email:
country = session_country
language = session_language
if prefs_store:
by_email = prefs_store.get_by_email(current_email)
if by_email:
country = by_email.get("last_country") or session_country
language = by_email.get("last_language") or session_language
return _email_update(current_email, locked=False), country, language
# Empty field, not logged in: restore a previously persisted email if any.
email = session_email
country = session_country
language = session_language
if prefs_store and session_email:
by_email = prefs_store.get_by_email(session_email)
if by_email:
country = by_email.get("last_country") or session_country
language = by_email.get("last_language") or session_language
return _email_update(email, locked=False), country, language
def _sync_session_after_logout(hf_username, session):
"""Persist the cleared email/hf_username when we detect a logout so
subsequent reloads don't re-populate the field from stale state."""
session = session or {}
prev_hf_username = (session.get("hf_username") or "").strip()
current_hf_username = (hf_username or "").strip()
if prev_hf_username and not current_hf_username:
return dict(session, email="", hf_username="")
return session
ui.load(
load_profile,
inputs=None,
outputs=[proceed_btn, username, user_name, prof_btn, hf_username_state, hf_email_state],
).then(
fn=_prefs_from_hf_or_session,
inputs=[hf_username_state, hf_email_state, email_inp, persistent_session],
outputs=[email_inp, pref_country_state, pref_language_state],
).then(
fn=_sync_session_after_logout,
inputs=[hf_username_state, persistent_session],
outputs=[persistent_session],
).then(
fn=partial(prefill_country_language, metadata=metadata_dict),
inputs=[pref_country_state, pref_language_state, country_choice, language_choice],
outputs=[country_choice, language_choice, intro_title, intro_markdown],
)
profile_continue_event = prof_btn.click(
fn=update_profile_proceed,
inputs=[email_inp],
outputs=[proceed_btn, username, hf_username_state, hf_email_state],
).then(
fn=_prefs_from_hf_or_session,
inputs=[hf_username_state, hf_email_state, email_inp, persistent_session],
outputs=[email_inp, pref_country_state, pref_language_state],
).then(
fn=partial(prefill_country_language, metadata=metadata_dict),
inputs=[pref_country_state, pref_language_state, country_choice, language_choice],
outputs=[country_choice, language_choice, intro_title, intro_markdown],
)
# def save_choices(d1, d2, state):
# state["d1"] = d1
# state["d2"] = d2
# return state
# def restore_choices(state):
# return state.get("d1"), state.get("d2")
# saved = gr.State({})
# d1 = gr.Dropdown(["A", "B", "C"], label="Dropdown 1")
# d2 = gr.Dropdown(["X", "Y", "Z"], label="Dropdown 2")
# with gr.Row():
# anon = gr.Button("Continue anonymously")
# login = gr.Button("Login with Hugging Face")
# login_real = gr.LoginButton(visible=False)
# save.click(
# save_state,
# [d1, d2],
# session_state
# ).then(
# lambda: gr.update(visible=True),
# None,
# login_btn
# )
# Second page
cmp_main_ui = build_main_page(concepts_dict, metadata_dict, local_storage)
main_ui_placeholder = cmp_main_ui["main_ui_placeholder"]
step1_section = cmp_main_ui["step1_section"]
step2_section = cmp_main_ui["step2_section"]
step3_section = cmp_main_ui["step3_section"]
step1_title = cmp_main_ui["step1_title"]
step2_title = cmp_main_ui["step2_title"]
step3_title = cmp_main_ui["step3_title"]
step1_next_btn = cmp_main_ui["step1_next_btn"]
step2_back_btn = cmp_main_ui["step2_back_btn"]
step2_next_btn = cmp_main_ui["step2_next_btn"]
step3_back_btn = cmp_main_ui["step3_back_btn"]
country_inp = cmp_main_ui["country_inp"]
language_inp = cmp_main_ui["language_inp"]
image_inp = cmp_main_ui["image_inp"]
image_preview = cmp_main_ui["image_preview"]
image_preview_step3 = cmp_main_ui["image_preview_step3"]
image_url_inp = cmp_main_ui["image_url_inp"]
load_image_url_btn = cmp_main_ui["load_image_url_btn"]
long_caption_inp = cmp_main_ui["long_caption_inp"]
num_words_inp = cmp_main_ui["num_words_inp"]
category_btn = cmp_main_ui["category_btn"]
concept_btn = cmp_main_ui["concept_btn"]
category_concept_dropdowns = cmp_main_ui["category_concept_dropdowns"]
back_btn = cmp_main_ui["back_btn"]
examples_btn = cmp_main_ui["examples_btn"]
examples_modal = cmp_main_ui["examples_modal"]
examples_modal_title_md = cmp_main_ui["examples_modal_title_md"]
examples_modal_content_md = cmp_main_ui["examples_modal_content_md"]
examples_modal_close_btn = cmp_main_ui["examples_modal_close_btn"]
clear_btn = cmp_main_ui["clear_btn"]
hide_faces_btn = cmp_main_ui["hide_faces_btn"]
hide_all_faces_btn = cmp_main_ui["hide_all_faces_btn"]
unhide_faces_btn = cmp_main_ui["unhide_faces_btn"]
submit_btn = cmp_main_ui["submit_btn"]
timestamp_btn = cmp_main_ui["timestamp_btn"]
exampleid_btn = cmp_main_ui["exampleid_btn"]
username_inp = cmp_main_ui["username_inp"]
# password_inp = cmp_main_ui["password_inp"]
modal_saving = cmp_main_ui["modal_saving"]
modal_data_saved = cmp_main_ui["modal_data_saved"]
modal = cmp_main_ui["modal"]
exit_btn = cmp_main_ui["exit_btn"]
intro_text_inp = cmp_main_ui["intro_text_inp"] # Note intro_text is actually the "Task" text from metadata
intro_text_inst_inp = cmp_main_ui["intro_text_inst_inp"] # This is the "Instructions" text from metadata
modal_saving_text = cmp_main_ui["modal_saving_text"]
modal_data_saved_text = cmp_main_ui["modal_data_saved_text"]
modal_exclude_confirm = cmp_main_ui["modal_exclude_confirm"]
cancel_exclude_btn = cmp_main_ui["cancel_exclude_btn"]
confirm_exclude_btn = cmp_main_ui["confirm_exclude_btn"]
vlm_output = cmp_main_ui["vlm_output"]
gen_button = cmp_main_ui["gen_button"]
vlm_feedback = cmp_main_ui["vlm_feedback"]
vlm_model_dropdown = cmp_main_ui["vlm_model_dropdown"]
user_page_btn = cmp_main_ui["user_page_btn"]
# Image rotation buttons
rotate_left_btn = cmp_main_ui["rotate_left_btn"]
rotate_right_btn = cmp_main_ui["rotate_right_btn"]
reset_image_btn = cmp_main_ui["reset_image_btn"]
# Multilingual text variables
step1_md_cmp = cmp_main_ui["step1_md_cmp"]
step2_md_cmp = cmp_main_ui["step2_md_cmp"]
step3_md_cmp = cmp_main_ui["step3_md_cmp"]
additional_concepts_title = cmp_main_ui["additional_concepts_title"]
additional_concepts_instructions = cmp_main_ui["additional_concepts_instructions"]
exclude_modal_title = cmp_main_ui["exclude_modal_title"]
exclude_modal_text = cmp_main_ui["exclude_modal_text"]
step1_title = cmp_main_ui["step1_title"]
step2_title = cmp_main_ui["step2_title"]
step3_title = cmp_main_ui["step3_title"]
vlm_description = cmp_main_ui["vlm_description"]
vlm_accordion = cmp_main_ui["vlm_accordion"]
quick_submit_checkbox = cmp_main_ui["quick_submit_checkbox"]
image_accordion = cmp_main_ui["image_accordion"]
image_accordion_content = cmp_main_ui["image_accordion_content"]
description_accordion = cmp_main_ui["description_accordion"]
description_accordion_content = cmp_main_ui["description_accordion_content"]
app_title = cmp_main_ui["app_title"]
# ── Third page: User Profile ──
cmp_user_page = build_user_page(concepts_dict)
user_page_placeholder = cmp_user_page["user_page_placeholder"]
user_page_back_btn = cmp_user_page["user_page_back_btn"]
user_page_title = cmp_user_page["user_page_title"]
comparative_stats_html = cmp_user_page["comparative_stats_html"]
user_centric_stats_html = cmp_user_page["user_centric_stats_html"]
missing_concepts_html = cmp_user_page["missing_concepts_html"]
up_country_filter = cmp_user_page["up_country_filter"]
up_language_filter = cmp_user_page["up_language_filter"]
up_category_filter = cmp_user_page["up_category_filter"]
up_concept_filter = cmp_user_page["up_concept_filter"]
up_clear_filters_btn = cmp_user_page["up_clear_filters_btn"]
up_data_table = cmp_user_page["up_data_table"]
up_delete_btn = cmp_user_page["up_delete_btn"]
up_vlm_feedback_radio = cmp_user_page["up_vlm_feedback_radio"]
up_modal_delete_confirm = cmp_user_page["up_modal_delete_confirm"]
up_cancel_delete_btn = cmp_user_page["up_cancel_delete_btn"]
up_confirm_delete_btn = cmp_user_page["up_confirm_delete_btn"]
up_hidden_concept_trigger = cmp_user_page["up_hidden_concept_trigger"]
up_comparative_stats_title = cmp_user_page["up_comparative_stats_title"]
up_comparative_stats_desc = cmp_user_page["up_comparative_stats_desc"]
up_contribution_metrics_title = cmp_user_page["up_contribution_metrics_title"]
up_missing_concepts_accordion = cmp_user_page["up_missing_concepts_accordion"]
up_contributed_data_title = cmp_user_page["up_contributed_data_title"]
up_delete_modal_title = cmp_user_page["up_delete_modal_title"]
up_delete_modal_text = cmp_user_page["up_delete_modal_text"]
# State for user page raw data (list of dicts)
up_all_samples_raw = gr.State([])
up_selected_example_id = gr.State(None)
up_selected_country = gr.State(None)
up_selected_language = gr.State(None)
# VLM metadata indexed by example ID for browse-table editing.
vlm_captions = gr.State(None)
vlm_models = gr.State(None)
vlm_feedbacks = gr.State(None)
vlm_usages = gr.State(None)
additional_concepts_map = gr.State(None)
### Category button
category_btn.change(
fn=partial(load_concepts, concepts=concepts_dict),
inputs=[category_btn, concept_btn, local_storage, loading_example],
outputs=[concept_btn, loading_example]
)
concept_btn.change(
fn=partial(update_category, concepts=concepts_dict),
inputs=[category_btn, concept_btn, local_storage, loading_example],
outputs=[category_btn, loading_example]
)
def _update_category_state(category):
return category
def _update_concept_state(concept):
return concept
gr.on(
triggers=[category_btn.change],
fn=_update_category_state,
inputs=[category_btn],
outputs=[category_state],
)
gr.on(
triggers=[concept_btn.change],
fn=_update_concept_state,
inputs=[concept_btn],
outputs=[concept_state],
)
def _passthru(x):
return x
for i, dd in enumerate(category_concept_dropdowns):
state_var = [category_1_concepts_state, category_2_concepts_state, category_3_concepts_state,
category_4_concepts_state, category_5_concepts_state][i]
gr.on(triggers=[dd.change], fn=_passthru, inputs=[dd], outputs=[state_var])
gr.on(triggers=[exampleid_btn.change], fn=lambda x: x, inputs=[exampleid_btn], outputs=[exampleid_state])
# .then(
# fn=partial(load_concepts, concepts=concepts_dict),
# inputs=[category_btn, concept_btn, local_storage, gr.State(True)],
# outputs=[concept_btn, loading_example]
# )
####
# is_blurred = gr.State(False) # Initialize as False
# Notification image is false
with Modal(visible=False) as modal_img_url:
gr.Markdown("The image URL is not valid. Please provide a valid image URL.")
# gr.Warning(f"⚠️ The image URL is not valid. Please provide a valid image URL.")
# Event listeners
gr.on(
triggers=[country_choice.change, language_choice.change],
fn=validate_metadata,
inputs=[country_choice, language_choice],
outputs=[proceed_btn],
)
ori_img = gr.State(None)
image_tool_buttons = [rotate_left_btn, rotate_right_btn, reset_image_btn]
def _copy_image_for_original_state(image):
if image is None:
return None
return image.copy() if hasattr(image, "copy") else image
def _image_tool_updates(image):
has_image = image is not None
return tuple(gr.update(interactive=has_image) for _ in image_tool_buttons)
def _disable_image_tools():
return tuple(gr.update(interactive=False) for _ in image_tool_buttons)
image_replacement_outputs = [
exampleid_btn,
exampleid_state,
vlm_output,
vlm_feedback,
gen_button,
vlm_model_dropdown,
vlm_caption_state,
vlm_feedback_state,
vlm_model_state,
vlm_image_signature_state,
vlm_usage_state,
]
def _reset_image_replacement_state():
return (
None,
None,
gr.update(value=None),
gr.update(value=None, visible=False),
gr.update(interactive=True, visible=False),
gr.update(value=None, interactive=False, visible=False),
None,
None,
None,
None,
None,
)
def _clear_image_url_state():
return gr.update(value=None), None
load_image_url_btn.click(
fn=_disable_image_tools,
inputs=None,
outputs=image_tool_buttons,
).then(
fn=update_image,
inputs=[image_url_inp],
outputs=[image_inp, modal_img_url],
).then(
fn=_copy_image_for_original_state,
inputs=[image_inp],
outputs=[ori_img],
).then(
fn=_reset_image_replacement_state,
outputs=image_replacement_outputs,
).then(
fn=sync_preview,
inputs=[image_inp],
outputs=[image_preview, image_preview_step3],
).then(
fn=_image_tool_updates,
inputs=[image_inp],
outputs=image_tool_buttons,
)
image_url_inp.submit(
fn=_disable_image_tools,
inputs=None,
outputs=image_tool_buttons,
).then(
fn=update_image,
inputs=[image_url_inp],
outputs=[image_inp, modal_img_url],
).then(
fn=_copy_image_for_original_state,
inputs=[image_inp],
outputs=[ori_img],
).then(
fn=_reset_image_replacement_state,
outputs=image_replacement_outputs,
).then(
fn=sync_preview,
inputs=[image_inp],
outputs=[image_preview, image_preview_step3],
).then(
fn=_image_tool_updates,
inputs=[image_inp],
outputs=image_tool_buttons,
)
image_inp.upload(
fn=_copy_image_for_original_state,
inputs=[image_inp],
outputs=[ori_img],
).then(
fn=_reset_image_replacement_state,
outputs=image_replacement_outputs,
).then(
fn=_clear_image_url_state,
outputs=[image_url_inp, image_url_state],
)
image_inp.clear(
fn=lambda: None,
outputs=[ori_img],
).then(
fn=_reset_image_replacement_state,
outputs=image_replacement_outputs,
).then(
fn=_clear_image_url_state,
outputs=[image_url_inp, image_url_state],
)
gr.on(
triggers=[image_url_inp.change],
fn=lambda x: x,
inputs=[image_url_inp],
outputs=[image_url_state],
)
gr.on(
triggers=[language_choice.change],
fn=partial(update_intro_language, metadata=metadata_dict),
inputs=[country_choice, language_choice],
outputs=[intro_title, intro_markdown]
)
gr.on(
triggers=[language_choice.change],
fn=partial(update_selection_language, metadata=metadata_dict),
inputs=[country_choice, language_choice, hf_username_state],
outputs=[signin_title, selection_title, anon_btn, country_choice, language_choice, email_inp, login_btn, proceed_btn],
)
def _update_timestamp_with_state():
ts = datetime.datetime.now().timestamp()
return gr.update(value=ts), ts
gr.on(
triggers=[image_inp.change, long_caption_inp.change],
fn=_update_timestamp_with_state,
outputs=[timestamp_btn, timestamp_state]
)
image_state = gr.State(None)
gr.on(
triggers=[
image_inp.change, category_btn.change, concept_btn.change,
long_caption_inp.change, quick_submit_checkbox.change
],
fn=validate_inputs,
inputs=[image_inp, ori_img, category_btn, concept_btn, long_caption_inp, quick_submit_checkbox], # is_blurred
outputs=[submit_btn, image_inp, ori_img], # is_blurred
)
gr.on(
triggers=[image_inp.change],
fn=lambda img: img,
inputs=[image_inp],
outputs=[image_state],
)
gr.on(
triggers=[image_inp.change],
fn=lambda img: gr.update(interactive=(img is not None)),
inputs=[image_inp],
outputs=[step1_next_btn],
)
gr.on(
triggers=[image_inp.change],
fn=_image_tool_updates,
inputs=[image_inp],
outputs=image_tool_buttons,
)
def validate_step2_next(category, concept):
category_empty = category is None or (isinstance(category, str) and category.strip() == "")
concept_empty = concept is None or (isinstance(concept, str) and concept.strip() == "")
return gr.update(interactive=not (category_empty or concept_empty))
gr.on(
triggers=[category_btn.change, concept_btn.change],
fn=validate_step2_next,
inputs=[category_btn, concept_btn],
outputs=[step2_next_btn],
)
gr.on(
triggers=[image_inp.change],
fn=sync_preview,
inputs=[image_inp],
outputs=[image_preview, image_preview_step3],
)
def _log_caption_and_count(caption, language):
return count_words(caption, language)
gr.on(
triggers=[long_caption_inp.change],
fn=_log_caption_and_count,
inputs=[long_caption_inp, language_choice],
outputs=[num_words_inp],
)
def _update_caption_state(caption):
return caption
gr.on(
triggers=[long_caption_inp.change],
fn=_update_caption_state,
inputs=[long_caption_inp],
outputs=[caption_state],
)
#============= Face Blurring ============= #
with Modal(visible=False) as modal_faces:
with gr.Column():
face_img = gr.Image(label="Image Faces", elem_id="image_faces", format="png", height=512, width=768)
with gr.Row():
faces_count = gr.Textbox(label="Face Counts", elem_id="face_counts", interactive=False)
blur_faces_ids = gr.Dropdown(
[], value=[], multiselect=True, label="Please select the faces IDs you want to blur.", elem_id="blur_faces_ids")
# blur_faces_ids = gr.Textbox(label="Specify faces ids to blur by comma", elem_id="blur_faces_ids", interactive=True)
submit_btn_face = gr.Button("Submit", variant="primary", interactive=True, elem_id="submit_btn_face")
faces_info = gr.State(None)
hide_faces_btn.click(
fn=select_faces_to_hide,
inputs=[image_inp, blur_faces_ids],
outputs=[image_inp, modal_faces, face_img, faces_count, faces_info, blur_faces_ids]
)
submit_btn_face.click(
fn=blur_selected_faces,
inputs=[image_inp, blur_faces_ids, faces_info, face_img, faces_count], # is_blurred
outputs=[image_inp, modal_faces, face_img, faces_count, blur_faces_ids] # is_blurred
)
hide_all_faces_btn.click(
fn=blur_all_faces,
inputs=[image_inp],
outputs=[image_inp]
)
unhide_faces_btn.click(
fn=unhide_faces,
inputs=[image_inp, ori_img], # is_blurred
outputs=[image_inp] # is_blurred
)
# ============= Image Rotation ============= #
rotate_left_btn.click(
fn=rotate_image_90_left,
inputs=[image_inp],
outputs=[image_inp]
)
rotate_right_btn.click(
fn=rotate_image_90_right,
inputs=[image_inp],
outputs=[image_inp]
)
reset_image_btn.click(
fn=reset_image_to_original,
inputs=[image_inp, ori_img],
outputs=[image_inp]
)
# ===============================
show_browse_data_state = gr.State(True)
with gr.Column(visible=False, elem_id="browse_toggle_row") as browse_toggle_row:
with gr.Row(equal_height=True):
clear_top_btn = gr.Button("Clear", variant="huggingface", elem_id="clear_top_btn")
exclude_btn = gr.Button("Delete", variant="stop", elem_id="exclude_btn", interactive=False)
with gr.Row(equal_height=True):
toggle_browse_btn = gr.Button("▼ Show/Hide Data", elem_id="toggle_browse_btn")
with gr.Column(visible=False, elem_id="browse_data") as browse_data_placeholder:
loading_msg = gr.Markdown("**Loading your data, please wait ...**")
with gr.Tab("Your data") as tab_your_data:
# Show user's past data points
user_examples = gr.Dataset(
samples=[],
show_label=False,
components=['image','textbox','textbox','textbox','textbox',
'textbox','textbox','textbox', 'textbox'],
headers=['Image', 'Image URL (Optional, if not uploading an image)', 'Description', 'Country', 'Language',
'Category', 'Concept', 'Additional Concepts', 'ID'],
)
# Handle clicking on an example
user_examples.click(
fn=partial(handle_click_example_with_states, concepts_dict=concepts_dict),
inputs=[
user_examples,
vlm_captions,
vlm_models,
vlm_feedbacks,
vlm_usages,
additional_concepts_map,
],
outputs=[
image_inp, image_url_inp, long_caption_inp, exampleid_btn,
category_btn, concept_btn,
category_concept_dropdowns[0], category_concept_dropdowns[1], category_concept_dropdowns[2],
category_concept_dropdowns[3], category_concept_dropdowns[4], loading_example, vlm_output, vlm_model_dropdown,
vlm_feedback,
image_state, image_url_state, caption_state, category_state, concept_state,
category_1_concepts_state, category_2_concepts_state, category_3_concepts_state,
category_4_concepts_state, category_5_concepts_state,
exampleid_state, vlm_caption_state, vlm_model_state, vlm_feedback_state, vlm_usage_state,
timestamp_state, vlm_image_signature_state,
],
).then(
fn=_copy_image_for_original_state,
inputs=[image_inp],
outputs=[ori_img],
).then(
fn=sync_preview,
inputs=[image_inp],
outputs=[image_preview, image_preview_step3],
).then(
fn=lambda ts: gr.update(value=ts, label="Timestamp", visible=False),
inputs=[timestamp_state],
outputs=[timestamp_btn],
).then(
fn=lambda: gr.update(interactive=True),
outputs=[exclude_btn],
)
toggle_browse_btn.click(
fn=toggle_browse_data,
inputs=[show_browse_data_state, toggle_browse_btn],
outputs=[show_browse_data_state, browse_data_placeholder, toggle_browse_btn],
)
# Clear Button (must be after exclude_btn is defined)
clear_outputs = [
image_inp, image_url_inp, long_caption_inp, vlm_output, vlm_feedback, gen_button, vlm_model_dropdown, exampleid_btn,
category_btn, concept_btn,
category_concept_dropdowns[0], category_concept_dropdowns[1], category_concept_dropdowns[2],
category_concept_dropdowns[3], category_concept_dropdowns[4],
category_state, concept_state, caption_state,
image_url_state, quick_submit_state,
category_1_concepts_state, category_2_concepts_state, category_3_concepts_state,
category_4_concepts_state, category_5_concepts_state,
timestamp_state, exampleid_state,
vlm_caption_state, vlm_feedback_state, vlm_model_state, vlm_usage_state,
exclude_btn, ori_img,
]
for clear_button in (clear_btn, clear_top_btn):
clear_button.click(
fn=partial(clear_data, metadata_dict=metadata_dict),
inputs=[local_storage],
outputs=clear_outputs,
).then(
fn=sync_preview,
inputs=[image_inp],
outputs=[image_preview, image_preview_step3],
)
# Step navigation
step1_next_btn.click(
fn=lambda show_browse, storage: (2, *set_step_from_storage(2, show_browse, storage), gr.update(interactive=True)),
inputs=[show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
scroll_to_output=False,
show_progress_on=step2_title,
js=scroll_to_top_js,
).then(
fn=sync_preview,
inputs=[image_inp],
outputs=[image_preview, image_preview_step3],
scroll_to_output=False,
).then(
fn=partial(auto_generate_vlm_caption, metadata_dict=metadata_dict),
inputs=[image_inp, vlm_caption_state, vlm_model_state, vlm_image_signature_state, exampleid_state, local_storage],
outputs=[vlm_output, vlm_feedback, gen_button, vlm_model_dropdown, vlm_caption_state, vlm_model_state, vlm_image_signature_state, vlm_usage_state],
concurrency_limit=3,
concurrency_id="vlm_queue",
scroll_to_output=False,
)
step2_back_btn.click(
fn=lambda show_browse, storage: (1, *set_step_from_storage(1, show_browse, storage), gr.update(interactive=False)),
inputs=[show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
scroll_to_output=False,
show_progress_on=step1_title,
js=scroll_to_top_js,
)
step2_next_btn.click(
fn=lambda show_browse, storage: (3, *set_step_from_storage(3, show_browse, storage), gr.update(interactive=True)),
inputs=[show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
scroll_to_output=False,
show_progress_on=step3_title,
js=scroll_to_top_js,
)
step3_back_btn.click(
fn=lambda show_browse, storage: (2, *set_step_from_storage(2, show_browse, storage), gr.update(interactive=True)),
inputs=[show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
scroll_to_output=False,
show_progress_on=step2_title,
js=scroll_to_top_js,
)
def handle_global_back(current_step, show_browse, storage):
if current_step == 2:
step_outputs = set_step_from_storage(1, show_browse, storage)
return (1, *step_outputs, gr.update(interactive=False))
elif current_step == 3:
step_outputs = set_step_from_storage(2, show_browse, storage)
return (2, *step_outputs, gr.update(interactive=True))
else:
step_outputs = set_step_from_storage(1, show_browse, storage)
return (1, *step_outputs, gr.update(interactive=False))
back_btn.click(
fn=handle_global_back,
inputs=[current_step_state, show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
scroll_to_output=False,
js=scroll_to_top_js,
)
# ============================================ #
# Submit Button Click events
# login_btn.click(
# fn=login_user,
# inputs=[username, password],
# outputs=[supabase_user_client, persistent_session, login_status],
# ).then(
# fn=restore_user_session,
# inputs=[persistent_session, login_status],
# outputs=[supabase_user_client, persistent_session, login_status, proceed_btn, login_btn, sign_up_btn, reset_password_btn, logout_btn, change_password_field, change_password_field_confirm, change_password_btn, change_password_status],
# )
# sign_up_btn.click(
# fn=sign_up,
# inputs=[username, password],
# outputs=[login_status],
# )
# logout_btn.click(
# fn=log_out,
# inputs=[supabase_user_client, persistent_session],
# outputs=[persistent_session]
# ).then(
# fn=restore_user_session,
# inputs=[persistent_session],
# outputs=[supabase_user_client, persistent_session, login_status, proceed_btn, login_btn, sign_up_btn, reset_password_btn, logout_btn, change_password_field, change_password_field_confirm, change_password_btn, change_password_status],
# )
# change_password_btn.click(
# fn=change_password,
# inputs=[supabase_user_client, change_password_field, change_password_field_confirm],
# outputs=[change_password_status]
# )
# reset_password_btn.click(
# fn=reset_password,
# inputs=[username],
# outputs=[login_status]
# )
def _short_email_label(email_val, max_len=30):
email_val = (email_val or "").strip()
if len(email_val) <= max_len:
return email_val
if "@" not in email_val:
return email_val[: max_len - 3] + "..."
local, domain = email_val.split("@", 1)
budget = max_len - len(domain) - 4
if budget < 4:
return email_val[: max_len - 3] + "..."
return f"{local[:budget]}...@{domain}"
def _update_user_page_btn(email_val, hf_username=""):
email_val = (email_val or "").strip()
hf_username = (hf_username or "").strip()
if email_val and email_val != "anonymous":
label = f"@{hf_username}" if hf_username else _short_email_label(email_val)
return gr.update(value=f"👤 {label}", interactive=True)
return gr.update(value="👤 Anonymous", interactive=False)
language_outputs = [
country_inp, language_inp, username_inp, category_btn, concept_btn, image_inp,
image_url_inp, long_caption_inp, intro_text_inp, intro_text_inst_inp, back_btn, clear_top_btn, clear_btn,
submit_btn, modal_saving_text, modal_data_saved_text, timestamp_btn, exit_btn,
loading_msg, hide_all_faces_btn, hide_faces_btn, unhide_faces_btn, exclude_btn,
category_concept_dropdowns[0], category_concept_dropdowns[1], category_concept_dropdowns[2],
category_concept_dropdowns[3], category_concept_dropdowns[4],
step2_md_cmp,
step1_md_cmp,
step3_md_cmp,
num_words_inp, additional_concepts_title, additional_concepts_instructions,
exclude_modal_title, exclude_modal_text, cancel_exclude_btn, confirm_exclude_btn,
gen_button, vlm_model_dropdown, vlm_feedback,
step1_title, step2_title,
step1_next_btn, step2_back_btn,
step2_next_btn, step3_back_btn, step3_title,
load_image_url_btn,
rotate_left_btn, rotate_right_btn, reset_image_btn,
quick_submit_checkbox,
vlm_description,
tab_your_data,
vlm_accordion, toggle_browse_btn,
vlm_output,
image_accordion, image_accordion_content,
description_accordion, description_accordion_content,
user_examples,
app_title,
]
examples_modal_outputs = [examples_btn, examples_modal_title_md, examples_modal_content_md, examples_modal_close_btn]
step_outputs = [step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder]
user_data_outputs = [
user_examples,
loading_msg,
vlm_captions,
vlm_models,
vlm_feedbacks,
vlm_usages,
additional_concepts_map,
]
user_page_language_outputs = [
user_page_back_btn, user_page_title,
up_comparative_stats_title, up_comparative_stats_desc,
up_contribution_metrics_title, up_missing_concepts_accordion,
up_contributed_data_title,
up_country_filter, up_language_filter, up_category_filter, up_concept_filter,
up_clear_filters_btn, up_delete_btn, up_vlm_feedback_radio,
up_delete_modal_title, up_delete_modal_text,
up_cancel_delete_btn, up_confirm_delete_btn,
up_data_table,
]
def _has_saved_locale(country, language):
return bool(country and language and metadata_dict.get(country, {}).get(language))
def _skip_outputs(outputs):
return tuple(gr.skip() for _ in outputs)
def _auto_extract_country_language_username(local_storage_value):
country, language, email = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return _skip_outputs([country_state, language_state, username_state])
return country, language, email
def _auto_update_language(local_storage_value):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return _skip_outputs(language_outputs)
return update_language(local_storage_value, metadata_dict, concepts_dict)
def _auto_update_user_page_language(local_storage_value):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return _skip_outputs(user_page_language_outputs)
return update_user_page_language(local_storage_value, metadata_dict)
def _auto_build_examples_modal(local_storage_value):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return _skip_outputs(examples_modal_outputs)
return build_examples_modal_updates(local_storage_value, metadata_dict)
def _maybe_show_examples_for_first_visit(local_storage_value, hf_username, email):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return gr.skip()
hf_username = (hf_username or "").strip()
email = (email or "").strip()
if not hf_username and _is_anonymous_email(email):
return gr.skip()
first_visit = prefs_store.mark_main_ui_visit(hf_username, email) if prefs_store else False
return gr.update(visible=True) if first_visit else gr.skip()
def _auto_set_step(local_storage_value, show_browse):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return _skip_outputs(step_outputs)
return set_step_from_storage(1, show_browse, local_storage_value)
def _auto_update_user_data(client, country, language, email):
if not _has_saved_locale(country, language):
return _skip_outputs(user_data_outputs)
return update_user_data(client, country, language, email, HF_DATASET_NAME, LOCAL_DS_DIRECTORY_PATH)
def _auto_update_user_page_btn(local_storage_value, email, hf_username):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return gr.skip()
return _update_user_page_btn(email, hf_username)
def _auto_reset_step_and_back(local_storage_value):
country, language, _ = extract_country_language_username(local_storage_value)
if not _has_saved_locale(country, language):
return gr.skip(), gr.skip()
return 1, gr.update(interactive=False)
def _persist_locale_session(email, country, language, hf_username, session):
if not _has_saved_locale(country, language):
return session
return dict(
session or {},
email=(email or "").strip(),
country=country,
language=language,
hf_username=(hf_username or "").strip(),
force_language_selection=False,
)
def _mark_language_selection_requested(session):
return dict(session or {}, force_language_selection=True)
def _persist_locale_prefs(hf_username, email, country, language):
if not _has_saved_locale(country, language):
return None
return prefs_store.upsert_prefs(hf_username, email, country, language) if prefs_store else None
proceed_btn.click(
fn=partial(switch_ui, flag=False, metadata_dict=metadata_dict),
inputs=[country_choice, language_choice, email_inp],
outputs=[local_storage, selection_page, main_ui_placeholder, browse_toggle_row, browse_data_placeholder, country_choice, language_choice, signin_title, selection_title, proceed_btn],
).then(
fn=extract_country_language_username,
inputs=[local_storage],
outputs=[country_state, language_state, username_state],
).then(
fn=partial(update_language, metadata_dict=metadata_dict, concepts_dict=concepts_dict),
inputs=[local_storage],
outputs=language_outputs,
).then(
fn=partial(update_user_page_language, metadata_dict=metadata_dict),
inputs=[local_storage],
outputs=user_page_language_outputs,
).then(
fn=partial(build_examples_modal_updates, metadata_dict=metadata_dict),
inputs=[local_storage],
outputs=examples_modal_outputs,
).then(
fn=_maybe_show_examples_for_first_visit,
inputs=[local_storage, hf_username_state, email_inp],
outputs=[examples_modal],
).then(
fn=lambda show_browse, storage: (1, *set_step_from_storage(1, show_browse, storage), gr.update(interactive=False)),
inputs=[show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
).then(
fn=partial(update_user_data, HF_DATASET_NAME=HF_DATASET_NAME, local_ds_directory_path = LOCAL_DS_DIRECTORY_PATH),
inputs=[supabase_user_client, country_choice, language_choice, email_inp],
outputs=user_data_outputs,
).then(
fn=_update_user_page_btn,
inputs=[email_inp, hf_username_state],
outputs=[user_page_btn],
).then(
fn=_persist_locale_session,
inputs=[email_inp, country_choice, language_choice, hf_username_state, persistent_session],
outputs=[persistent_session],
).then(
fn=lambda hf_username, email, country, language: prefs_store.upsert_prefs(
hf_username, email, country, language
) if prefs_store else None,
inputs=[hf_username_state, email_inp, country_choice, language_choice],
)
def _chain_saved_locale_entry(event):
return event.then(
fn=partial(switch_ui, flag=False, metadata_dict=metadata_dict),
inputs=[country_choice, language_choice, email_inp],
outputs=[local_storage, selection_page, main_ui_placeholder, browse_toggle_row, browse_data_placeholder, country_choice, language_choice, signin_title, selection_title, proceed_btn],
).then(
fn=_auto_extract_country_language_username,
inputs=[local_storage],
outputs=[country_state, language_state, username_state],
).then(
fn=_auto_update_language,
inputs=[local_storage],
outputs=language_outputs,
).then(
fn=_auto_update_user_page_language,
inputs=[local_storage],
outputs=user_page_language_outputs,
).then(
fn=_auto_build_examples_modal,
inputs=[local_storage],
outputs=examples_modal_outputs,
).then(
fn=_maybe_show_examples_for_first_visit,
inputs=[local_storage, hf_username_state, email_inp],
outputs=[examples_modal],
).then(
fn=_auto_set_step,
inputs=[local_storage, show_browse_data_state],
outputs=step_outputs,
).then(
fn=_auto_update_user_data,
inputs=[supabase_user_client, country_choice, language_choice, email_inp],
outputs=user_data_outputs,
).then(
fn=_auto_update_user_page_btn,
inputs=[local_storage, email_inp, hf_username_state],
outputs=[user_page_btn],
).then(
fn=_persist_locale_session,
inputs=[email_inp, country_choice, language_choice, hf_username_state, persistent_session],
outputs=[persistent_session],
).then(
fn=_persist_locale_prefs,
inputs=[hf_username_state, email_inp, country_choice, language_choice],
).then(
fn=_auto_reset_step_and_back,
inputs=[local_storage],
outputs=[current_step_state, back_btn],
)
_chain_saved_locale_entry(profile_continue_event)
_chain_saved_locale_entry(anon_continue_event)
# Exit Button
exit_btn.click(
fn=exit_fn,
outputs=[
image_inp, image_url_inp, long_caption_inp, vlm_output, vlm_feedback, gen_button, vlm_model_dropdown, user_examples, loading_msg,
username, local_storage, exampleid_btn, category_btn, concept_btn,
category_concept_dropdowns[0], category_concept_dropdowns[1], category_concept_dropdowns[2],
category_concept_dropdowns[3], category_concept_dropdowns[4], ori_img
],
).success(
fn=partial(switch_ui, flag=True, metadata_dict=metadata_dict),
inputs=[country_choice, language_choice, username_inp],
outputs=[local_storage, selection_page, main_ui_placeholder, browse_toggle_row, browse_data_placeholder, country_choice, language_choice, signin_title, selection_title, proceed_btn],
).success(
fn=lambda: gr.update(value="👤 Anonymous", interactive=False),
outputs=[user_page_btn],
).success(
fn=_mark_language_selection_requested,
inputs=[persistent_session],
outputs=[persistent_session],
)
# Disable button while saving
# Note: I think this is not longer needed as we clear all the inputs and disable the submit button if the data is saved correctly
# def disable_submit():
# return gr.update(interactive=False)
# def enable_submit():
# return gr.update(interactive=True)
# STEP 1: show modal
# submit_btn.click(lambda: Modal(visible=True), None, modal_saving)
# STEP 2: disable button
# submit_btn.click(disable_submit, None, [submit_btn], queue=False)
#STEP 3: perform save_data
gr.on(triggers=[quick_submit_checkbox.change], fn=lambda x: x if x is not None else False, inputs=[quick_submit_checkbox], outputs=[quick_submit_state])
data_outputs = {
"image": image_state,
"image_url": image_url_state,
"caption": caption_state,
"quick_submit_mode": quick_submit_state,
"country": country_state,
"language": language_state,
"category": category_state,
"concept": concept_state,
"category_1_concepts": category_1_concepts_state,
"category_2_concepts": category_2_concepts_state,
"category_3_concepts": category_3_concepts_state,
"category_4_concepts": category_4_concepts_state,
"category_5_concepts": category_5_concepts_state,
"timestamp": timestamp_state,
"username": username_state,
"hf_username": hf_username_state,
"id": exampleid_state,
"excluded": gr.State(value=False),
"concepts_dict": gr.State(value=concepts_dict),
"country_lang_map": gr.State(value=lang2eng_mapping),
"client": supabase_user_client,
"vlm_caption": vlm_caption_state,
"vlm_feedback": vlm_feedback_state,
"vlm_model": vlm_model_state,
"vlm_usage": vlm_usage_state,
}
# data_outputs = [image_inp, image_url_inp, long_caption_inp,
# country_inp, language_inp, category_btn, concept_btn,
# timestamp_btn, username_inp, password_inp, exampleid_btn]
hf_writer.setup(list(data_outputs.keys()), local_ds_folder=LOCAL_DS_DIRECTORY_PATH, metadata_dict=metadata_dict)
# STEP 4: Chain save_data, then update_user_data, then clear
def _log_and_save(*values):
return hf_writer.save(*values)
# ── Rebuild user profile helper (used by submit + delete flows) ──
def _rebuild_profile_after_save(email_val):
email_val = (email_val or "").strip()
if email_val:
rebuild_user_profile(email_val, LOCAL_DS_DIRECTORY_PATH)
def wire_submit_chain(button, js=None):
e = button.click(
_log_and_save,
inputs=list(data_outputs.values()),
outputs=None,
js=js,
).success(
fn=partial(clear_data, "submit", metadata_dict=metadata_dict),
inputs=[local_storage],
outputs=[
image_inp, image_url_inp, long_caption_inp, vlm_output, vlm_feedback, gen_button, vlm_model_dropdown, exampleid_btn,
category_btn, concept_btn,
category_concept_dropdowns[0], category_concept_dropdowns[1], category_concept_dropdowns[2],
category_concept_dropdowns[3], category_concept_dropdowns[4],
category_state, concept_state, caption_state,
image_url_state, quick_submit_state,
category_1_concepts_state, category_2_concepts_state, category_3_concepts_state,
category_4_concepts_state, category_5_concepts_state,
timestamp_state, exampleid_state,
vlm_caption_state, vlm_feedback_state, vlm_model_state, vlm_usage_state,
exclude_btn, ori_img,
],
).success(
# Navigate back to Step 1 immediately after save+clear succeed.
# Performed early so any transient error in the data-reload steps
# below can never leave the user stranded on Step 2.
fn=lambda show_browse, storage: (1, *set_step_from_storage(1, show_browse, storage), gr.update(interactive=False)),
inputs=[show_browse_data_state, local_storage],
outputs=[current_step_state, step1_section, step2_section, step3_section, browse_toggle_row, browse_data_placeholder, back_btn],
).then(
lambda: gr.update(value="**Loading your data, please wait ...**"),
None, loading_msg
).then(
fn=partial(update_user_data, HF_DATASET_NAME=HF_DATASET_NAME, local_ds_directory_path=LOCAL_DS_DIRECTORY_PATH, skip_snapshot=True),
inputs=[supabase_user_client, country_choice, language_choice, username_inp],
outputs=user_data_outputs
).then(
fn=_rebuild_profile_after_save,
inputs=[username_inp],
).then(
fn=lambda hf_username, email, country, language: prefs_store.upsert_prefs(
hf_username, email, country, language
) if prefs_store else None,
inputs=[hf_username_state, username_inp, country_choice, language_choice],
)
return e
wire_submit_chain(submit_btn, js=scroll_to_top_js)
# ============================================ #
# "See Examples" button — opens a read-only reference modal
# describing what/how to fill in each field. No click handlers on
# the displayed examples (those caused the old Examples-tab error).
examples_btn.click(lambda: gr.update(visible=True), None, examples_modal)
examples_modal_close_btn.click(lambda: gr.update(visible=False), None, examples_modal)
# ============================================ #
# # Load saved values from local storage (browser storage)
# @ui.load(inputs=[local_storage], outputs=[country_choice, language_choice, username, password])
# def load_from_local_storage(saved_values):
# print("loading from local storage", saved_values)
# return saved_values[0], saved_values[1], saved_values[2], saved_values[3]
# ============================================= #
# Exclude button
# ============================================= #
# Show confirmation modal when exclude button is clicked
exclude_btn.click(
fn=check_exclude_fn,
inputs=[exampleid_state],
outputs=[modal_exclude_confirm]
)
# Close modal when cancel button is clicked
cancel_exclude_btn.click(
fn=lambda: gr.update(visible=False),
outputs=[modal_exclude_confirm]
)
def _do_exclude(example_id, country, language, username):
mark_sample_excluded(example_id, country, language, username, LOCAL_DS_DIRECTORY_PATH)
# Keep profiles.json in sync
_rebuild_profile_after_save(username)
confirm_exclude_btn.click(
fn=lambda: gr.update(visible=False),
outputs=[modal_exclude_confirm]
).success(
fn=_do_exclude,
inputs=[exampleid_state, country_state, language_state, username_state],
outputs=None
).success(
fn=partial(clear_data, "remove", metadata_dict=metadata_dict),
inputs=[local_storage],
outputs=[
image_inp, image_url_inp, long_caption_inp, vlm_output, vlm_feedback, gen_button, vlm_model_dropdown, exampleid_btn,
category_btn, concept_btn,
category_concept_dropdowns[0], category_concept_dropdowns[1], category_concept_dropdowns[2],
category_concept_dropdowns[3], category_concept_dropdowns[4],
category_state, concept_state, caption_state,
image_url_state, quick_submit_state,
category_1_concepts_state, category_2_concepts_state, category_3_concepts_state,
category_4_concepts_state, category_5_concepts_state,
timestamp_state, exampleid_state,
vlm_caption_state, vlm_feedback_state, vlm_model_state, vlm_usage_state,
exclude_btn, ori_img,
]
).success(
fn=lambda: gr.update(value="**Refreshing your data, please wait...**"),
outputs=loading_msg
).success(
fn=partial(update_user_data, HF_DATASET_NAME=HF_DATASET_NAME, local_ds_directory_path=LOCAL_DS_DIRECTORY_PATH, skip_snapshot=True),
inputs=[supabase_user_client, country_choice, language_choice, username_inp],
outputs=user_data_outputs
)
# ============================================= #
# VLM Gen button
# ============================================= #
gen_button.click(
fn=partial(regenerate_vlm_caption, metadata_dict=metadata_dict),
inputs=[image_inp, local_storage],
outputs=[vlm_output, vlm_feedback, gen_button, vlm_model_dropdown, vlm_caption_state, vlm_model_state, vlm_image_signature_state, vlm_usage_state],
# OpenAI API replaces the on-device VLM, so the GPU bottleneck is
# gone. Keep a small queue (3) to stay polite to the API.
concurrency_limit=3,
concurrency_id="vlm_queue"
)
gr.on(triggers=[vlm_output.change], fn=_passthru, inputs=[vlm_output], outputs=[vlm_caption_state])
gr.on(triggers=[vlm_feedback.change], fn=_passthru, inputs=[vlm_feedback], outputs=[vlm_feedback_state])
# vlm_output.change(
# fn=lambda : gr.update(interactive=False) if vlm_output.value else gr.update(interactive=True),
# inputs=[],
# outputs=[gen_button]
# )
# ui.load(
# fn=login_user_recovery,
# inputs=gr.Textbox(visible=False, value=""), # hidden textbox to get the url tokens
# outputs=[supabase_user_client, persistent_session, login_status],
# js=js_code
# ).then(
# fn=restore_user_session,
# inputs=[persistent_session],
# outputs=[supabase_user_client, persistent_session, login_status, proceed_btn, login_btn, sign_up_btn, reset_password_btn, logout_btn, change_password_field, change_password_field_confirm, change_password_btn, change_password_status],
# )
# ============================================= #
# User Page wiring
# ============================================= #
def _open_user_page(email_val):
"""Navigate to user page — hide main UI, show user page."""
email_val = (email_val or "").strip()
if not email_val:
gr.Warning("⚠️ Please sign in to view your profile.")
return (
gr.skip(), # main_ui_placeholder
gr.skip(), # user_page_placeholder
gr.skip(), # browse_toggle_row
gr.skip(), # browse_data_placeholder
)
return (
gr.update(visible=False), # main_ui_placeholder
gr.update(visible=True), # user_page_placeholder
gr.update(visible=False), # browse_toggle_row
gr.update(visible=False), # browse_data_placeholder
)
def _load_user_page_data(email_val, local_storage_val):
"""Load all user data and stats for user page."""
email_val = (email_val or "").strip()
country, language, _ = extract_country_language_username(local_storage_val)
meta = (metadata_dict or {}).get(country, {}).get(language, {}) if country and language else {}
base_title = meta.get("UP_Title", "## 👤 Your Profile")
if not email_val:
return (
gr.Dataset(samples=[]),
gr.update(choices=[]),
gr.update(choices=[]),
gr.update(choices=[]),
gr.update(choices=[]),
[], # up_all_samples_raw
"<p>No data.</p>",
"<p>No data.</p>",
"<p>No data.</p>",
f"{base_title} — Anonymous",
)
table_rows, countries, languages, categories, concepts, raw_samples = \
load_all_user_data(email_val, HF_DATASET_NAME, LOCAL_DS_DIRECTORY_PATH, skip_snapshot=False)
# Compute stats
rebuild_user_profile(email_val, LOCAL_DS_DIRECTORY_PATH)
comp_stats = compute_comparative_stats(email_val, LOCAL_DS_DIRECTORY_PATH)
user_stats = compute_user_centric_stats(email_val, LOCAL_DS_DIRECTORY_PATH, concepts_dict)
return (
gr.Dataset(samples=table_rows),
gr.update(choices=[None] + countries, value=None),
gr.update(choices=[None] + languages, value=None),
gr.update(choices=[None] + categories, value=None),
gr.update(choices=[None] + concepts, value=None),
raw_samples,
render_comparative_stats(comp_stats, meta),
render_user_centric_stats(user_stats, meta),
render_missing_concepts(user_stats, meta),
f"{base_title}{email_val}",
)
user_page_btn.click(
fn=_open_user_page,
inputs=[username_inp],
outputs=[main_ui_placeholder, user_page_placeholder, browse_toggle_row, browse_data_placeholder],
).then(
fn=_load_user_page_data,
inputs=[username_inp, local_storage],
outputs=[
up_data_table,
up_country_filter, up_language_filter, up_category_filter, up_concept_filter,
up_all_samples_raw,
comparative_stats_html, user_centric_stats_html, missing_concepts_html,
user_page_title,
],
)
# Back button — return to annotation page
def _close_user_page():
return (
gr.update(visible=True), # main_ui_placeholder
gr.update(visible=False), # user_page_placeholder
gr.update(visible=True), # browse_toggle_row
gr.update(visible=True), # browse_data_placeholder
)
user_page_back_btn.click(
fn=_close_user_page,
outputs=[main_ui_placeholder, user_page_placeholder, browse_toggle_row, browse_data_placeholder],
)
# ── Filters ──
def _apply_filters(raw, country_f, lang_f, cat_f, concept_f):
return filter_user_page_data(raw, country_f, lang_f, cat_f, concept_f)
for _trigger in [up_country_filter, up_language_filter, up_category_filter, up_concept_filter]:
_trigger.change(
fn=_apply_filters,
inputs=[up_all_samples_raw, up_country_filter, up_language_filter, up_category_filter, up_concept_filter],
outputs=[up_data_table],
)
up_clear_filters_btn.click(
fn=lambda raw: (
gr.update(value=None), gr.update(value=None),
gr.update(value=None), gr.update(value=None),
gr.Dataset(samples=[[s["image"], s["caption"], s["country"], s["language"],
s["category"], s["concept"], s["vlm_caption"], s["id"]] for s in raw] if raw else []),
),
inputs=[up_all_samples_raw],
outputs=[up_country_filter, up_language_filter, up_category_filter, up_concept_filter, up_data_table],
)
# ── Row selection (clicking on a row in the Dataset) ──
def _on_user_page_row_click_load(clicked_row, all_samples_raw):
row = list(clicked_row)
img = row[0]
caption = row[1]
country = row[2]
language = row[3]
category = row[4]
concept = row[5]
categories = concepts_dict.get(country, {}).get(lang2eng_mapping.get(language, language), {})
category = _canonical_category_value(category, language, categories)
if category is None and concept:
matches = [cat for cat, values in categories.items() if concept in (values or [])]
category = matches[0] if len(matches) == 1 else None
vlm_cap = row[6]
example_id = row[7]
saved = next(
(sample for sample in (all_samples_raw or []) if sample.get("id") == example_id),
{},
)
saved_model = saved.get("vlm_model") or None
saved_feedback = saved.get("vlm_feedback") or None
saved_usage = saved.get("vlm_usage") if vlm_cap else None
needs_feedback = bool(vlm_cap and not saved_feedback)
ts = datetime.datetime.now().timestamp()
vlm_sig = image_signature(img) if vlm_cap else None
return (
gr.update(visible=True), # main_ui_placeholder
gr.update(visible=False), # user_page_placeholder
gr.update(visible=True), # browse_toggle_row
gr.update(visible=True), # browse_data_placeholder
img, # image_inp
caption, # long_caption_inp
category, # category_btn
concept, # concept_btn
example_id, # exampleid_btn
vlm_cap, # vlm_output
gr.update(interactive=True), # exclude_btn
(
gr.update(value=None, visible=True)
if needs_feedback
else gr.update(visible=False)
), # vlm_feedback
# States
img, # image_state
caption, # caption_state
category, # category_state
concept, # concept_state
example_id, # exampleid_state
vlm_cap, # vlm_caption_state
saved_model, # vlm_model_state
saved_feedback, # vlm_feedback_state
saved_usage, # vlm_usage_state
ts, # timestamp_state
vlm_sig, # vlm_image_signature_state
)
up_data_table.click(
fn=_on_user_page_row_click_load,
inputs=[up_data_table, up_all_samples_raw],
outputs=[
main_ui_placeholder, user_page_placeholder, browse_toggle_row, browse_data_placeholder,
image_inp, long_caption_inp, category_btn, concept_btn, exampleid_btn, vlm_output,
exclude_btn, vlm_feedback,
image_state, caption_state, category_state, concept_state, exampleid_state, vlm_caption_state,
vlm_model_state, vlm_feedback_state, vlm_usage_state, timestamp_state,
vlm_image_signature_state,
],
).then(
fn=_copy_image_for_original_state,
inputs=[image_inp],
outputs=[ori_img],
)
def _on_hidden_concept_trigger(concept):
return (
gr.update(visible=True), # main_ui_placeholder
gr.update(visible=False), # user_page_placeholder
gr.update(visible=True), # browse_toggle_row
gr.update(visible=True), # browse_data_placeholder
gr.update(value=concept), # concept_btn
)
up_hidden_concept_trigger.change(
fn=_on_hidden_concept_trigger,
inputs=[up_hidden_concept_trigger],
outputs=[main_ui_placeholder, user_page_placeholder, browse_toggle_row, browse_data_placeholder, concept_btn],
)
# ── Delete from user page ──
up_delete_btn.click(
fn=lambda eid: gr.update(visible=True) if eid else gr.update(visible=False),
inputs=[up_selected_example_id],
outputs=[up_modal_delete_confirm],
)
up_cancel_delete_btn.click(
fn=lambda: gr.update(visible=False),
outputs=[up_modal_delete_confirm],
)
def _do_user_page_delete(example_id, country, language, email_val):
email_val = (email_val or "").strip()
mark_sample_excluded(example_id, country, language, email_val, LOCAL_DS_DIRECTORY_PATH)
# Rebuild profile after deletion
rebuild_user_profile(email_val, LOCAL_DS_DIRECTORY_PATH)
up_confirm_delete_btn.click(
fn=lambda: gr.update(visible=False),
outputs=[up_modal_delete_confirm],
).success(
fn=_do_user_page_delete,
inputs=[up_selected_example_id, up_selected_country, up_selected_language, username_inp],
).success(
fn=_load_user_page_data,
inputs=[username_inp, local_storage],
outputs=[
up_data_table,
up_country_filter, up_language_filter, up_category_filter, up_concept_filter,
up_all_samples_raw,
comparative_stats_html, user_centric_stats_html, missing_concepts_html,
user_page_title,
],
).success(
fn=lambda: gr.update(interactive=False),
outputs=[up_delete_btn],
)
# ── VLM feedback from user page ──
def _save_vlm_feedback_from_user_page(example_id, feedback_val, email_val):
email_val = (email_val or "").strip()
if example_id and feedback_val and email_val:
update_vlm_feedback_in_json(example_id, feedback_val, email_val, LOCAL_DS_DIRECTORY_PATH)
rebuild_user_profile(email_val, LOCAL_DS_DIRECTORY_PATH)
gr.Info("✅ VLM feedback saved!", duration=3)
up_vlm_feedback_radio.change(
fn=_save_vlm_feedback_from_user_page,
inputs=[up_selected_example_id, up_vlm_feedback_radio, username_inp],
)
return ui