diff --git a/UI/README.md b/UI/README.md new file mode 100644 index 0000000..908e8a2 --- /dev/null +++ b/UI/README.md @@ -0,0 +1,5 @@ +# Versions + +- wcag_validator_ui_pre_multimodel.py : the version used for the 12_2025 user test. Has to work with restServer with only one model (not two) +- wcag_validator_ui.py : the official version + diff --git a/UI/requirements_UI.txt b/UI/requirements_UI.txt index 8bb7be9..87efef4 100644 --- a/UI/requirements_UI.txt +++ b/UI/requirements_UI.txt @@ -1,4 +1,5 @@ gradio==5.49.1 pandas==2.3.3 python-dotenv==1.2.1 -requests==2.32.5 \ No newline at end of file +requests==2.32.5 +gradio-modal==0.0.4 \ No newline at end of file diff --git a/UI/wcag_validator_ui.py b/UI/wcag_validator_ui.py index 9d3ec2e..877db24 100644 --- a/UI/wcag_validator_ui.py +++ b/UI/wcag_validator_ui.py @@ -3,6 +3,7 @@ # python wcag_validator_ui.py import gradio as gr +from gradio_modal import Modal import requests from pathlib import Path import sys @@ -33,7 +34,19 @@ WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")] def process_dataframe(db_path, url, updated_df, user_state={},llm_response_output={}): - print("Processing dataframe to adjust columns...") + print("Processing dataframe to adjust columns...type:",type(updated_df)) + # accept different input forms from UI (DataFrame, JSON string, or list of dicts) + try: + + if isinstance(updated_df, str): + try: + updated_df = pd.read_json(updated_df, orient="records") + except Exception: + updated_df = pd.read_json(updated_df) + elif isinstance(updated_df, list): + updated_df = pd.DataFrame(updated_df) + except Exception as e: + return f"Error parsing updated data: {str(e)}" for column_rating_name in ["User Assessment for LLM Proposal 1", "User Assessment for LLM Proposal 2"]: # Get the assessment column @@ -41,6 +54,10 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu updated_df[column_rating_name] = updated_df[column_rating_name].astype(int) except ValueError: return "Error: User Assessment for LLM Proposal must be an integer" + except KeyError: + return f"No data Saved because no image selected. Please select at least one image." + except Exception as e: + return f"Error processing User Assessment for LLM Proposal: {str(e)}" if (updated_df[column_rating_name] < 1).any() or ( updated_df[column_rating_name] > 5 @@ -50,7 +67,8 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu dataframe_json = updated_df.to_json(orient="records") connection_db = sqlite3.connect(db_path) json_user_str = json.dumps({"username": user_state["username"]}, ensure_ascii=False) - lm_response_output_str = json.dumps(llm_response_output, ensure_ascii=False) #recuperato dalla chiamata all'llm, ho tutte le info anche sulle immagini + + llm_response_output_str = json.dumps(llm_response_output, ensure_ascii=False) #recuperato dalla chiamata all'llm, ho tutte le info anche sulle immagini try: # insert after everything to keep datetime aligned db_persistence_insert( @@ -59,7 +77,7 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu page_url=url, user=json_user_str, llm_model="", - json_in_str=lm_response_output_str,#dataframe_json, # to improve + json_in_str=llm_response_output_str,#dataframe_json, # to improve json_out_str=dataframe_json, table="wcag_user_assessments", ) @@ -81,7 +99,7 @@ def load_images_from_json(json_input): images = data["images"] info_text = f"Found {len(images)} image(s)" - print(f"Found {len(data['images'])} image(s)") + # Create HTML gallery with checkboxes and assessment forms html = """ @@ -133,7 +151,7 @@ def load_images_from_json(json_input): display: none; margin-top: 15px; padding: 10px; - background: #7896b9; + background: aliceblue; border-radius: 4px; border: 1px solid #2196F3; } @@ -376,8 +394,8 @@ def make_alttext_llm_assessment_api_call( if not selected_images or len(selected_images) == 0: info_text = "No images selected" - print(info_text) - return "LLM assessment not started", pd.DataFrame() + + return "LLM assessment not started", pd.DataFrame(), {} # prepare data for insertion json_in_str = {} @@ -442,7 +460,7 @@ def make_alttext_llm_assessment_api_call( info_dataframe["User Assessment for LLM Proposal 2"] = ( user_assessments_llm_proposal_2 ) - print("info_dataframe after adding user assessments:", info_dataframe) + #print("info_dataframe after adding user assessments:", info_dataframe) except Exception as e: return {"error": str(e)} @@ -493,12 +511,134 @@ def make_image_extraction_api_call( return {"error": str(e)} +def render_alttext_form(df): + """Render a pandas DataFrame (or list/dict) into an editable HTML form.""" + try: + if df is None: + return "" + if isinstance(df, str): + df = pd.read_json(df, orient="records") + if isinstance(df, dict): + df = pd.DataFrame(df) + if isinstance(df, list): + df = pd.DataFrame(df) + + html = """ + +
| Image # | +Original Alt Text | +User Assessment | +User Proposed Alt Text | +LLM Assessment 1 | +LLM Proposed Alt Text 1 | +User Assessment for LLM Proposal 1 | +LLM Assessment 2 | +LLM Proposed Alt Text 2 | +User Assessment for LLM Proposal 2 | + +
|---|---|---|---|---|---|---|---|---|---|
| {imgnum} | +{orig} | +{user_ass} | +{user_prop} | +{llm1_ass} | +{llm1_prop} | + ++ + | +{llm2_ass} | +{llm2_prop} | ++ + | +
| \n", + " | id | \n", + "insertion_time | \n", + "insert_type | \n", + "page_url | \n", + "user | \n", + "llm_model | \n", + "json_input_data | \n", + "json_output_data | \n", + "
|---|---|---|---|---|---|---|---|---|
| 0 | \n", + "1 | \n", + "2026-03-03 09:51:39 | \n", + "wcag_user_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images_urls\": [\"https://giove.isti.cnr.it/us... | \n", + "{\"user_assessments\": [3, 3], \"user_new_alt_tex... | \n", + "
| 1 | \n", + "2 | \n", + "2026-03-03 09:51:55 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images\": [{\"url\": \"https://giove.isti.cnr.it... | \n", + "[{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl... | \n", + "
| 2 | \n", + "3 | \n", + "2026-03-03 09:53:55 | \n", + "wcag_user_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images_urls\": [\"https://giove.isti.cnr.it/us... | \n", + "{\"user_assessments\": [3, 3, 3], \"user_new_alt_... | \n", + "
| 3 | \n", + "4 | \n", + "2026-03-03 09:54:06 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images\": [{\"url\": \"https://giove.isti.cnr.it... | \n", + "[{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z... | \n", + "
| \n", + " | id | \n", + "insertion_time | \n", + "insert_type | \n", + "page_url | \n", + "user | \n", + "llm_model | \n", + "json_input_data | \n", + "json_output_data | \n", + "
|---|---|---|---|---|---|---|---|---|
| 1 | \n", + "2 | \n", + "2026-03-03 09:51:55 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images\": [{\"url\": \"https://giove.isti.cnr.it... | \n", + "[{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl... | \n", + "
| 3 | \n", + "4 | \n", + "2026-03-03 09:54:06 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images\": [{\"url\": \"https://giove.isti.cnr.it... | \n", + "[{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z... | \n", + "
| \n", + " | id | \n", + "insertion_time | \n", + "insert_type | \n", + "page_url | \n", + "user | \n", + "llm_model | \n", + "json_input_data | \n", + "json_output_data | \n", + "
|---|---|---|---|---|---|---|---|---|
| 1 | \n", + "2 | \n", + "2026-03-03 09:51:55 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images\": [{\"url\": \"https://giove.isti.cnr.it... | \n", + "[{\"Image #\":21,\"Original Alt Text\":\"{*slate-bl... | \n", + "
| 3 | \n", + "4 | \n", + "2026-03-03 09:54:06 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "NaN | \n", + "{\"images\": [{\"url\": \"https://giove.isti.cnr.it... | \n", + "[{\"Image #\":7,\"Original Alt Text\":\"Nike Club Z... | \n", + "
| \n", + " | id | \n", + "insertion_time | \n", + "insert_type | \n", + "page_url | \n", + "user | \n", + "llm_model | \n", + "json_input_data | \n", + "json_output_data | \n", + "
|---|
| \n", + " | id | \n", + "insertion_time | \n", + "insert_type | \n", + "page_url | \n", + "user | \n", + "image_url | \n", + "original_alt_text | \n", + "html_context | \n", + "immediate_context | \n", + "nearby_context | \n", + "... | \n", + "page_keywords | \n", + "page_headings | \n", + "llm_assessment | \n", + "llm_judgment | \n", + "llm_evaluation_result | \n", + "llm_alt_text | \n", + "llm_model | \n", + "user_assessment | \n", + "user_alt_text | \n", + "user_llm_assessment | \n", + "
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | \n", + "2 | \n", + "2026-03-03 09:51:55 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{\"username\": \"nicola\"} | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{*slate-blue-8493310*} | \n", + "<span>: Save 22% <span>: Vendor: <h3>: Simond ... | \n", + "No immediate context found | \n", + "<span> [154px]: Save 22% | \n", + "... | \n", + "None | \n", + "[{\"level\": 2, \"text\": \"Your cart is empty\"}, {... | \n", + "1 | \n", + "failure | \n", + "The alt-text 'slate-blue-8493310' is insuffici... | \n", + "Simond Men's Travel 500 Merino Wool T-Shirt in... | \n", + "gpt-4o | \n", + "3 | \n", + "{*slate-blue-8493310*} | \n", + "3 | \n", + "
| 1 | \n", + "2 | \n", + "2026-03-03 09:51:55 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "{\"username\": \"nicola\"} | \n", + "https://giove.isti.cnr.it/users/leonardi/decat... | \n", + "Red and gray backpack on a white background | \n", + "<span>: Save 30% <span>: Vendor: <h3>: Simond ... | \n", + "No immediate context found | \n", + "<span> [154px]: Save 30% | \n", + "... | \n", + "None | \n", + "[{\"level\": 2, \"text\": \"Your cart is empty\"}, {... | \n", + "3 | \n", + "warning | \n", + "The current alt-text describes the image's app... | \n", + "Simond Men's MT100 Easyfit 70L backpacking pac... | \n", + "gpt-4o | \n", + "3 | \n", + "Red and gray backpack on a white background | \n", + "3 | \n", + "
| 2 | \n", + "4 | \n", + "2026-03-03 09:54:06 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "https://giove.isti.cnr.it/users/leonardi/nike/... | \n", + "Nike Club Zip a metà lunghezza per l'inverno –... | \n", + "<a>: Nike Club | \n", + "No immediate context found | \n", + "<a> [93px]: Nike Club | \n", + "... | \n", + "Acquista Abbigliamento da Uomo | \n", + "[{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418... | \n", + "5 | \n", + "success | \n", + "The alt-text describes the product accurately,... | \n", + "Nike Club maglia a scacchi con zip a metà lung... | \n", + "gpt-4o | \n", + "3 | \n", + "Nike Club Zip a metà lunghezza per l'inverno –... | \n", + "3 | \n", + "
| 3 | \n", + "4 | \n", + "2026-03-03 09:54:06 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "https://giove.isti.cnr.it/users/leonardi/nike/... | \n", + "Nike Icon Pantaloni in tessuto da basket - Uomo | \n", + "<a>: Nike Icon | \n", + "No immediate context found | \n", + "<a> [93px]: Nike Icon | \n", + "... | \n", + "Acquista Abbigliamento da Uomo | \n", + "[{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418... | \n", + "5 | \n", + "success | \n", + "The alt-text clearly describes the product, al... | \n", + "Nike Icon Pantaloni in tessuto da basket - Uomo | \n", + "gpt-4o | \n", + "3 | \n", + "Nike Icon Pantaloni in tessuto da basket - Uomo | \n", + "3 | \n", + "
| 4 | \n", + "4 | \n", + "2026-03-03 09:54:06 | \n", + "wcag_user_llm_alttext_assessments | \n", + "https://giove.isti.cnr.it/users/leonardi/nike.... | \n", + "{\"username\": \"nicola\"} | \n", + "https://giove.isti.cnr.it/users/leonardi/nike/... | \n", + "Kobe Pantaloni da basket Therma-FIT | \n", + "<a>: Kobe | \n", + "No immediate context found | \n", + "<a> [93px]: Kobe | \n", + "... | \n", + "Acquista Abbigliamento da Uomo | \n", + "[{\"level\": 1, \"text\": \"Abbigliamento Uomo(1418... | \n", + "3 | \n", + "warning | \n", + "The alt-text partially describes the product b... | \n", + "Pantaloni da basket Nike Therma-FIT in vendita... | \n", + "gpt-4o | \n", + "3 | \n", + "Kobe Pantaloni da basket Therma-FIT | \n", + "3 | \n", + "
5 rows × 22 columns
\n", + "