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("<", "<") .replace(">", ">") .replace('"', """) ) 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 data/examples.json.") intro = ( '
' f'

{_html_escape(intro_text)}

' '
' ) display_examples = [ex for ex in (examples or []) if isinstance(ex, dict)] if not display_examples: return intro + f'

{no_examples_text}

' 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'{_html_escape(str(c).strip())}' for c in additional if c ) or '' else: additional_chips = ( f'{_html_escape(str(additional).strip())}' if str(additional).strip() else '' ) 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 = ( '
' f'{_html_escape(title)}' '
' ) meta_rows = ( '
' f'
📍 {_html_escape(country_label)}
{_html_escape(ex_country) or "—"}
' f'
💬 {_html_escape(language_label)}
{_html_escape(ex_language) or "—"}
' f'
📂 {_html_escape(category_label)}
{_html_escape(ex_category) or "—"}
' f'
🎯 {_html_escape(concept_label)}
{_html_escape(concept) or "—"}
' f'
➕ {_html_escape(additional_label)}
{additional_chips}
' '
' ) description_html = ( '
' f'{_html_escape(desc_label)}' f'

{_html_escape(caption) or ""}

' '
' ) 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'
' f'' f'

{_html_escape(notes)}

' '
' ) cards.append( '
' '
' f'{_html_escape(example_label)} {idx}' f'

{_html_escape(title)}

' '
' '
' f'{image_html}' '
' f'{meta_rows}' f'{description_html}' f'{notes_html}' '
' '
' '
' ) return intro + '
' + "".join(cards) + '
' _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 "

No data.

", "

No data.

", "

No data.

", 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