From fdbc314ef42bc9837fc09b0c278a2b798ccb893f Mon Sep 17 00:00:00 2001 From: nicola leonardi Date: Sun, 30 Nov 2025 19:04:10 +0100 Subject: [PATCH] UI improvements and docker files --- .env | 16 +- README.md | 11 +- UI/.env | 4 + .../__pycache__/utils.cpython-310.pyc | Bin 0 -> 2615 bytes UI/dependences_ui/utils.py | 117 +++++ UI/requirements_UI.txt | 4 + UI/requirements_extra.txt | 1 - UI/{ui_alt_text.py => wcag_validator_ui.py} | 457 +++++++++--------- .../image_extractor.cpython-310.pyc | Bin 16294 -> 0 bytes .../mllm_management.cpython-310.pyc | Bin 10290 -> 0 bytes dependences/__pycache__/utils.cpython-310.pyc | Bin 4794 -> 0 bytes dependences/image_extractor.py | 4 +- dependences/utils.py | 14 +- docker/UI/Dockerfile | 21 + docker/UI/requirements_UI.txt | 4 + docker/restServer/Dockerfile | 23 + docker/restServer/requirements.txt | 6 + requirements.txt | 3 +- .../__pycache__/routes_health.cpython-310.pyc | Bin 1369 -> 0 bytes .../routes_local_db.cpython-310.pyc | Bin 2347 -> 0 bytes .../routes_wcag_alttext.cpython-310.pyc | Bin 4424 -> 0 bytes restserver/routers/routes_wcag_alttext.py | 7 +- 22 files changed, 451 insertions(+), 241 deletions(-) create mode 100644 UI/.env create mode 100644 UI/dependences_ui/__pycache__/utils.cpython-310.pyc create mode 100644 UI/dependences_ui/utils.py create mode 100644 UI/requirements_UI.txt delete mode 100644 UI/requirements_extra.txt rename UI/{ui_alt_text.py => wcag_validator_ui.py} (59%) delete mode 100644 dependences/__pycache__/image_extractor.cpython-310.pyc delete mode 100644 dependences/__pycache__/mllm_management.cpython-310.pyc delete mode 100644 dependences/__pycache__/utils.cpython-310.pyc create mode 100644 docker/UI/Dockerfile create mode 100644 docker/UI/requirements_UI.txt create mode 100644 docker/restServer/Dockerfile create mode 100644 docker/restServer/requirements.txt delete mode 100644 restserver/routers/__pycache__/routes_health.cpython-310.pyc delete mode 100644 restserver/routers/__pycache__/routes_local_db.cpython-310.pyc delete mode 100644 restserver/routers/__pycache__/routes_wcag_alttext.cpython-310.pyc diff --git a/.env b/.env index c1bb668..93614b6 100644 --- a/.env +++ b/.env @@ -1,10 +1,10 @@ -mllm_end_point_openai='https://hiis-accessibility-fonderia.cognitiveservices.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2025-01-01-preview' -mllm_api_key_openai= -mllm_model_id_openai='gpt-4o' +MLLM_END_POINT_OPENAI=https://hiis-accessibility-fonderia.cognitiveservices.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2025-01-01-preview +MLLM_API_KEY_OPENAI= +MLLM_MODEL_ID_OPENAI=gpt-4o -mllm_end_point_local='https://vgpu.hiis.cloud.isti.cnr.it/api/chat' -mllm_api_key_local= -#mllm_model_id_local='gemma3:12b' -mllm_model_id_local='gemma3:4b' +MLLM_END_POINT_LOCAL=https://vgpu.hiis.cloud.isti.cnr.it/api/chat +MLLM_API_KEY_LOCAL= +#MLLM_MODEL_ID_LOCAL=gemma3:12b +MLLM_MODEL_ID_LOCAL=gemma3:4b -use_openai_model='False' \ No newline at end of file +USE_OPENAI_MODEL=True \ No newline at end of file diff --git a/README.md b/README.md index 99319a7..8d0d842 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,15 @@ python wcag_validator.py python wcag_validator_RESTserver.py ## For UI use: -python ui_alt_text.py +python wcag_validator_ui.py + +## Docker +### Rest server +docker build -t wcag_resr_server . +docker run --env-file .env -p 8000:8000 --name wcag_rest_server -d wcag_rest_server +### UI +docker build -t wcag_ui . +docker run --env-file UI/.env -p 8001:8001 --name wcag_ui -d wcag_ui + ## The scripts folder contains some elaboration scripts. They require a dedicated requirements file \ No newline at end of file diff --git a/UI/.env b/UI/.env new file mode 100644 index 0000000..1b0e7f3 --- /dev/null +++ b/UI/.env @@ -0,0 +1,4 @@ +DB_PATH=persistence/wcag_validator_ui.db +WCAG_REST_SERVER_URL=http://localhost:8000 +URL_LIST_old=["http://www.amazon.it","https://web.archive.org/web/20230630235957/http://www.amazon.com/", "https://web.archive.org/web/20251130033532/https://www.ebay.com/"] +URL_LIST=["https://amazon.com","https://ebay.com","https://walmart.com","https://etsy.com","https://target.com","https://wayfair.com","https://bestbuy.com","https://macys.com","https://homedepot.com","https://costco.com","https://www.ansa.it","https://en.wikipedia.org/wiki/Main_Page","https://www.lanazione.it","https://www.ansa.it","https://www.bbc.com","https://www.cnn.com","https://www.nytimes.com","https://www.theguardian.com"] \ No newline at end of file diff --git a/UI/dependences_ui/__pycache__/utils.cpython-310.pyc b/UI/dependences_ui/__pycache__/utils.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30e3cb8267fe6fc253e6f4faabaa11a2afd38687 GIT binary patch literal 2615 zcmb7GO>YxN7@nD3+Z)FTNvH^vrX3?naTU^*svIg>wW1(`f{QQ|5m|NDI}>}!de@y< zC&Y53N@#;dF_R zzs%t1$1wN?MgN3K5J4ldI&A4GwPUu*Z0oGLg01F+BivufYF^|-9&JGsgooA>6QYQA zLQIMh+M<{e(`YBfDKUe#BxsFPW?$kKQtcOeS}MKtL?>}zW=0=H$3wY+qQ66xk|%VR zrOqBzJatn?&>i;s)HX?TyZHgxqmKw9g1N*cI}Rl^@)^ z&D;p0Dm0^O`$SDb#eaXf@nt>mRVt&%Zv;s!!i`Y-^}Bx14r93@eHDjstA2ZVxfc1& zx*r5m>#!L{VY*e1lTJOLeLP8HAV+d&U!g?5S^hf8dlUU;Qu(LH92G0a7=m@&IkvP%bKm+b)`yUw0L0PUZ@?dvx0 z`dV)$%0K}j;kDb}Ub*_kwThb++E|XlW|r4&A5(xq93()J~{)U+yT{j^FRi4z}6jAGJZ`l+M&W(C+Z#H zVoZ0)wuy|hLDYiCVTPF<1I%3~sWWhP0DLh4bBq1c_hkz(mWum4mYX&<6(=M8OAqn# zV!tD~AB(X+0zU>4c~f%P>84xdYQ<5;jQ;$+1D9zo5^nZ%{4{8nt1nGVM!ObAyLhLk ztr$P$k@V5O#)G!6`~bRW9wl*WEc@(Omq2WEoCWqWCz7#!05%d9J(%?j^o$qO8SKku@M_mW z)j5oyn!)smAgcqPT3{|ML;YTb?(dIcGov00D46zP$ti3Br5Mwwah1kUxkh zzHkn;80$W^XWT>P7+O)8?4SN&NN_{?7)G&w`JTZJuoxQzHHgMZ&j$JoBqObY^ig6Fj0qaFYmK#0)S~r(FkMp9%9ln%wfRxNJc?|=h5_o^+od}!6AZjIyH-Lf(^(S$h4UG9Q*YhR35`e87&_C+<)@LlXEl0 z*LM{sF?H;m`Uv7q@S$N`i?UDAG3XKwB1MEduo>0l?gNz3 zQV4=|;;pJGLt6hV*5Ej9*2j-A+A2R#A7H)0)I~JvLsa`zT{3->CXcZDBs_xC)+G{4 zdK#6nb>tBDKUKlh39}Aqo?{gh57Y3+U>dM9ef&uP4BZz_jTm;U@~ literal 0 HcmV?d00001 diff --git a/UI/dependences_ui/utils.py b/UI/dependences_ui/utils.py new file mode 100644 index 0000000..dd3bf70 --- /dev/null +++ b/UI/dependences_ui/utils.py @@ -0,0 +1,117 @@ +import hashlib +import json +import os +import gradio as gr + +# File to store user credentials +USERS_FILE = "users.json" + + +def load_users(): + """Load users from JSON file""" + if os.path.exists(USERS_FILE): + with open(USERS_FILE, "r") as f: + return json.load(f) + return {} + + +def save_users(users): + """Save users to JSON file""" + with open(USERS_FILE, "w") as f: + json.dump(users, f) + + +def hash_password(password): + """Hash password using SHA-256""" + return hashlib.sha256(password.encode()).hexdigest() + + +def register_user(username, password, confirm_password): + """Register a new user""" + if not username or not password: + return "", "Username and password cannot be empty!", None + + if password != confirm_password: + return "", "Passwords do not match!", None + + if len(password) < 6: + return "", "Password must be at least 6 characters long!", None + + users = load_users() + + if username in users: + return "", "Username already exists!", None + + users[username] = hash_password(password) + save_users(users) + + return "", f"✅ Registration successful! You can now login.", None + + +def login_user(username, password, state): + """Validate user login""" + if not username or not password: + return ( + "Please enter both username and password!", + "", + state, + gr.update(visible=True), + gr.update(visible=False), + gr.update(visible=False), + gr.update(open=True), + ) + + users = load_users() + + if username not in users: + return ( + "Invalid username or password!", + "", + state, + gr.update(visible=True), + gr.update(visible=False), + gr.update(visible=False), + gr.update(open=True), + ) + + if users[username] != hash_password(password): + return ( + "Invalid username or password!", + "", + state, + gr.update(visible=True), + gr.update(visible=False), + gr.update(visible=False), + gr.update(open=True), + ) + + # Login successful + state = {"logged_in": True, "username": username} + return ( + f"✅ Welcome back, {username}!", + "", + state, + gr.update(visible=False), + gr.update(visible=True), + gr.update(visible=True), + gr.update(open=False), + ) + + +def logout_user(state): + """Logout current user""" + state = {"logged_in": False, "username": None} + return ( + "Logged out successfully!", + state, + gr.update(visible=True), + gr.update(visible=False), + gr.update(visible=False), + ) + + +def protected_content(state): + """Content only accessible to logged-in users""" + if state.get("logged_in"): + return f"You are logged as {state.get('username')}\n" + return "Please login to access this content." diff --git a/UI/requirements_UI.txt b/UI/requirements_UI.txt new file mode 100644 index 0000000..8bb7be9 --- /dev/null +++ b/UI/requirements_UI.txt @@ -0,0 +1,4 @@ +gradio==5.49.1 +pandas==2.3.3 +python-dotenv==1.2.1 +requests==2.32.5 \ No newline at end of file diff --git a/UI/requirements_extra.txt b/UI/requirements_extra.txt deleted file mode 100644 index 4bf8a16..0000000 --- a/UI/requirements_extra.txt +++ /dev/null @@ -1 +0,0 @@ -gradio==5.49.1 \ No newline at end of file diff --git a/UI/ui_alt_text.py b/UI/wcag_validator_ui.py similarity index 59% rename from UI/ui_alt_text.py rename to UI/wcag_validator_ui.py index fe9ad6e..8cd3f46 100644 --- a/UI/ui_alt_text.py +++ b/UI/wcag_validator_ui.py @@ -1,11 +1,24 @@ #### To launch the script -# gradio ui_alt_text.py -# python ui_alt_text.py +# gradio wcag_validator_ui.py +# python wcag_validator_ui.py import gradio as gr import requests +from pathlib import Path +import sys +import pandas as pd -# from ..dependences.utils import call_API_urlibrequest +parent_dir = Path(__file__).parent.parent +sys.path.insert(0, str(parent_dir)) +from dotenv import load_dotenv, find_dotenv +from dependences.utils import ( + call_API_urlibrequest, + create_folder, + db_persistence_startup, + db_persistence_insert, + return_from_env_valid, +) +from dependences_ui.utils import * import logging import time import json @@ -17,196 +30,6 @@ import sqlite3 WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")] -url_list = [ - "https://amazon.com", - "https://web.archive.org/web/20251126051721/https://www.amazon.com/", - "https://web.archive.org/web/20230630235957/http://www.amazon.com/", - "https://ebay.com", - "https://walmart.com", - "https://etsy.com", - "https://target.com", - "https://wayfair.com", - "https://bestbuy.com", - "https://macys.com", - "https://homedepot.com", - "https://costco.com", - "https://www.ansa.it", - "https://en.wikipedia.org/wiki/Main_Page", - "https://www.lanazione.it", - "https://www.ansa.it", - "https://www.bbc.com", - "https://www.cnn.com", - "https://www.nytimes.com", - "https://www.theguardian.com", -] - -# ------ TODO use from utils instead of redefining here - - -def call_API_urlibrequest( - data={}, - verbose=False, - url="", - headers=[], - method="post", - base=2, # number of seconds to wait - max_tries=3, -): - - if verbose: - logging.info("input_data:%s", data) - - # Allow multiple attempts to call the API incase of downtime. - # Return provided response to user after 3 failed attempts. - wait_seconds = [base**i for i in range(max_tries)] - - for num_tries in range(max_tries): - try: - - if method == "get": - - # Encode the parameters and append them to the URL - query_string = urllib.parse.urlencode(data) - - url_with_params = f"{url}?{query_string}" - request = urllib.request.Request(url_with_params, method="GET") - for ele in headers: - - request.add_header(ele[0], ele[1]) - - elif method == "post": - # Convert the dictionary to a JSON formatted string and encode it to bytes - data_to_send = json.dumps(data).encode("utf-8") - - request = urllib.request.Request(url, data=data_to_send, method="POST") - for ele in headers: - - request.add_header(ele[0], ele[1]) - else: - return {"error_message": "method_not_allowed"} - - # Send the request and capture the response - - with urllib.request.urlopen(request) as response: - # Read and decode the response - - response_json = json.loads(response.read().decode("utf-8")) - logging.info("response_json:%s", response_json) - - logging.info("response.status_code:%s", response.getcode()) - return response_json - - except Exception as e: - - logging.error("error message:%s", e) - response_json = {"error": e} - - logging.info("num_tries:%s", num_tries) - logging.info( - "Waiting %s seconds before automatically trying again.", - str(wait_seconds[num_tries]), - ) - time.sleep(wait_seconds[num_tries]) - - logging.info( - "Tried %s times to make API call to get a valid response object", max_tries - ) - logging.info("Returning provided response") - return response_json - - -def create_folder(root_path, directory_separator, next_path): - output_dir = root_path + directory_separator + next_path - try: - if not os.path.exists(output_dir): - os.mkdir(output_dir) - - except Exception as e: - logging.error(exception_msg, e) - - exit(1) - return output_dir - - -def db_persistence_startup( - db_name_and_path="persistence/wcag_validator.db", - table="wcag_validator_results", -): - - try: - - _ = create_folder( - root_path=os.getcwd(), - directory_separator="/", - next_path="persistence", - ) - - except Exception as e: - logging.error("exception on db persistence startup:%s", e) - - exit(1) - try: - db_connection = sqlite3.connect(db_name_and_path) - cursor = db_connection.cursor() - # Create a table to store JSON data - cursor.execute( - """CREATE TABLE IF NOT EXISTS """ - + table - + """ ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - insertion_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - insert_type TEXT, - json_input_data TEXT, json_output_data TEXT - )""" - ) - - db_connection.commit() - logging.info("connection to the database established") - return db_connection - - except Exception as e: - - logging.error("db_management problem:%s", e) - exit(1) - - -def db_persistence_insert( - connection_db, - insert_type, - json_in_str, - json_out_str, - table="wcag_validator_results", -): - - try: - cursor = connection_db.cursor() - - # Insert JSON data into the table along with the current timestamp - cursor.execute( - "INSERT INTO " - + table - + " (insert_type,json_input_data,json_output_data) VALUES (?,?,?)", - (insert_type, json_in_str, json_out_str), - ) - connection_db.commit() - logging.info( - "Data correctly saved on local db table:%s, insertion type:%s", - table, - insert_type, - ) - except Exception as e: - logging.error("exception" + " %s", e) - - -# ------- End TODO use from utils instead of redefining here - - -# Method 1: Embed external website (works only for sites that allow iframes) -def create_iframe(url): - iframe_html = ( - f'' - ) - return iframe_html def load_images_from_json(json_input): @@ -361,7 +184,7 @@ def load_images_from_json(json_input): """ - info_text += f"✓ Image {idx+1} alt_text: {alt_text}\n" + # info_text += f"✓ Image {idx+1} alt_text: {alt_text}\n" html += "" return info_text, html @@ -380,13 +203,14 @@ def load_llm_assessment_from_json(json_input): if "mllm_validations" not in data or not data["mllm_validations"]: print("no mllm_validations found") - return "No mllm_validations found in JSON", [] + return pd.DataFrame() info_text = f"Assessment done on {len(data['mllm_validations']['mllm_alttext_assessments'])} image(s)\n\n" print( f"Assessment done on {len(data['mllm_validations']['mllm_alttext_assessments'])} image(s)" ) + data_frame = [] for idx, img_data in enumerate( data["mllm_validations"]["mllm_alttext_assessments"], 1 ): @@ -399,9 +223,17 @@ def load_llm_assessment_from_json(json_input): ) alt_text_original = img_data.get("alt_text", "No alt_text provided") - info_text += f"✓ alt_text original: {alt_text_original}. LLM assessment: {original_alt_text_assessment} => LLM proposed alt_text: {new_alt_text}\n" + data_frame.append( + { + + "Original Alt Text": alt_text_original, + "LLM Assessment": original_alt_text_assessment, + "Proposed Alt Text": new_alt_text, + } + ) - return info_text + df = pd.DataFrame(data_frame) + return df except json.JSONDecodeError as e: return f"Error: Invalid JSON format - {str(e)}", [] @@ -410,19 +242,51 @@ def load_llm_assessment_from_json(json_input): def make_alttext_llm_assessment_api_call( - url, selected_images_json=[], number_of_images=30 + url, + selected_images_json=[], + db_path=None, + wcga_rest_server_url="http://localhost:8000", + user_state={}, + number_of_images=30, ): - print(f"Making API call to {url}") + + print( + f"Making API call for llm assessment for {url} to {wcga_rest_server_url}/wcag_alttext_validation" + ) selected_images = json.loads(selected_images_json) if selected_images_json else [] - print("selected_images:", selected_images) + # print("selected_images:", selected_images) if not selected_images or len(selected_images) == 0: info_text = "No images selected" - return info_text + print(info_text) + return pd.DataFrame() + # prepare data for insertion + json_in_str = {} + json_out_str = {} selected_urls = [] + selected_alt_text_original = [] + user_assessments = [] + user_new_alt_texts = [] + selected_image_id = [] for img in selected_images: selected_urls.append(img["image_url"]) + selected_alt_text_original.append(img["original_alt_text"]) + user_assessments.append(img["assessment"]) + user_new_alt_texts.append(img["new_alt_text"]) + selected_image_id.append( + int(img["image_index"]) + 1 + ) # add the id selected (+1 for index alignment) + json_in_str["images_urls"] = selected_urls + json_in_str["images_alt_text_original"] = selected_alt_text_original + json_out_str["user_assessments"] = user_assessments + json_out_str["user_new_alt_texts"] = user_new_alt_texts + json_in_str = json.dumps(json_in_str, ensure_ascii=False) + json_out_str = json.dumps(json_out_str, ensure_ascii=False) + json_user_str = json.dumps({"username": user_state["username"]}, ensure_ascii=False) + connection_db = sqlite3.connect(db_path) + # --------- + try: response = call_API_urlibrequest( @@ -435,19 +299,46 @@ def make_alttext_llm_assessment_api_call( "save_elaboration": "True", "specific_images_urls": selected_urls, }, - url="http://localhost:8000/wcag_alttext_validation", + url=wcga_rest_server_url + "/wcag_alttext_validation", headers=WCAG_VALIDATOR_RESTSERVER_HEADERS, ) # return response - info_text = load_llm_assessment_from_json(response) + info_dataframe = load_llm_assessment_from_json(response) + info_dataframe.insert( + 0, 'Image #', selected_image_id + ) # add the UI ids from to api response - return info_text except Exception as e: return {"error": str(e)} + try: + # insert after everything to keep datetime aligned + db_persistence_insert( + connection_db=connection_db, + insert_type="wcag_user_alttext_assessments", + page_url=url, + user=json_user_str, + llm_model="", + json_in_str=json_in_str, + json_out_str=json_out_str, + table="wcag_user_assessments", + ) + except Exception as e: + print("Error inserting user assessment into database:", str(e)) + finally: + if connection_db: + connection_db.close() + return info_dataframe -def make_image_extraction_api_call(url, number_of_images=30): - print(f"Making API call to {url}") + +def make_image_extraction_api_call( + url, + number_of_images=30, + wcga_rest_server_url="http://localhost:8000", +): + print( + f"Making API call for image_extraction for {url} to {wcga_rest_server_url}/extract_images" + ) try: response = call_API_urlibrequest( @@ -455,7 +346,7 @@ def make_image_extraction_api_call(url, number_of_images=30): "page_url": url, "number_of_images": number_of_images, }, - url="http://localhost:8000/extract_images", + url=wcga_rest_server_url + "/extract_images", headers=WCAG_VALIDATOR_RESTSERVER_HEADERS, ) # return response @@ -468,18 +359,83 @@ def make_image_extraction_api_call(url, number_of_images=30): # ------- Gradio Interface -------# -# Global variable to hold database connection -connection_db = db_persistence_startup(table="wcag_user_assessments") # Create Gradio interface -with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: +with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo: - # Use the global connection_db reference - print("Database connection reference available globally") + env_path = find_dotenv(filename=".env") + if env_path == "": + print("env path not found: service starting with the default params values") + _ = load_dotenv(env_path) # read .env file + db_path = return_from_env_valid("DB_PATH", "persistence/wcag_validator_ui.db") + print("db_path:", db_path) + wcga_rest_server_url = return_from_env_valid( + "WCGA_REST_SERVER_URL", "http://localhost:8000" + ) + + default_urls = [ + "https://amazon.com", + "https://ebay.com", + ] + url_list_str=return_from_env_valid("URL_LIST",json.dumps(default_urls)) + url_list = json.loads(url_list_str) + + print("wcga_rest_server_url:", wcga_rest_server_url) + + connection_db = db_persistence_startup( + db_name_and_path=db_path, table="wcag_user_assessments" + ) + print("Database connection reference available:", connection_db) + connection_db.close() gr.Markdown("# WCAG AI Validator UI") - with gr.Tab("Alt Text Assessment"): + # login section + user_state = gr.State({"logged_in": False, "username": None}) + with gr.Accordion(label="Register & Login", open=True) as register_and_login: + with gr.Column(visible=True) as login_section: + gr.Markdown("## Login / Register") + + with gr.Tab("Login"): + login_username = gr.Textbox( + label="Username", placeholder="Enter your username" + ) + login_password = gr.Textbox( + label="Password", type="password", placeholder="Enter your password" + ) + login_btn = gr.Button("Login", variant="primary") + login_msg = gr.Textbox(label="Login Status", interactive=False) + + with gr.Tab("Register"): + reg_username = gr.Textbox( + label="Username", placeholder="Choose a username" + ) + reg_password = gr.Textbox( + label="Password", + type="password", + placeholder="Choose a password (min 6 characters)", + ) + reg_confirm = gr.Textbox( + label="Confirm Password", + type="password", + placeholder="Confirm your password", + ) + reg_btn = gr.Button("Register", variant="primary") + reg_msg = gr.Textbox(label="Registration Status", interactive=False) + + with gr.Column(visible=False) as protected_section: + + content_display = gr.Textbox( + label="Your account", lines=5, interactive=False + ) + logout_btn = gr.Button("Logout", variant="stop") + + # end login section + + with gr.Tab("Alt Text Assessment", visible=False) as alttext_assessment: + + db_path_state = gr.State(value=db_path) # Store path in State + wcga_rest_server_url_state = gr.State(value=wcga_rest_server_url) with gr.Row(): with gr.Column(): @@ -492,10 +448,17 @@ with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: label="Select an URL", info="Select an URL to load in iframe", ) + images_number = gr.Slider( + 5, + 100, + value=30, + step=5, + label="Max number of images to retrieve", + ) with gr.Column(): image_extraction_api_call_btn = gr.Button( - "Extract Images & Alt Text", variant="primary" + "Extract Images & Alt Texts", variant="primary" ) alttext_api_call_btn = gr.Button( "Alt Text LLM Assessment", @@ -505,15 +468,27 @@ with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: with gr.Row(): - image_info_output = gr.Textbox(label="Original alt-text", lines=5) - alttext_info_output = gr.Textbox(label="LLM Assessment", lines=5) + image_info_output = gr.Textbox(label="Managed Images", lines=5) + + # Use DataFrame for tabular output + alttext_info_output = gr.DataFrame( + headers=[ + "Image #", + "Original Alt Text", + "LLM Assessment", + "Proposed Alt Text", + ], + label="LLM Assessment Results", + wrap=True, # Wrap text in cells + interactive=False, + ) with gr.Row(): gallery_html = gr.HTML(label="Image Gallery") image_extraction_api_call_btn.click( - fn=lambda: ("", "", "", gr.Button(interactive=False)), + fn=lambda: ("", "", pd.DataFrame(), gr.Button(interactive=False)), inputs=[], outputs=[ image_info_output, @@ -523,7 +498,7 @@ with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: ], ).then( make_image_extraction_api_call, - inputs=[url_input], + inputs=[url_input, images_number, wcga_rest_server_url_state], outputs=[image_info_output, gallery_html], ).then( fn=lambda: gr.Button(interactive=True), @@ -535,7 +510,13 @@ with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: alttext_api_call_btn.click( fn=make_alttext_llm_assessment_api_call, - inputs=[url_input, gallery_html], + inputs=[ + url_input, + gallery_html, + db_path_state, + wcga_rest_server_url_state, + user_state, + ], outputs=[alttext_info_output], js=""" (url_input,gallery_html) => { @@ -558,6 +539,7 @@ with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: const newAltText = document.querySelector('.new-alt-text[data-index="' + index + '"]').value; selectedData.push({ + image_index: index, image_url: imageUrl, original_alt_text: originalAlt, assessment: parseInt(assessment), @@ -570,7 +552,40 @@ with gr.Blocks(theme="Insuz/SimpleIndigo", title="WCAG AI Validator") as demo: """, ) + # placed here at the end to give full contents visibility to events + # Event handlers + login_btn.click( + fn=login_user, + inputs=[login_username, login_password, user_state], + outputs=[ + login_msg, + reg_msg, + user_state, + login_section, + protected_section, + alttext_assessment, + register_and_login, + ], + ).then(fn=protected_content, inputs=[user_state], outputs=[content_display]) + + reg_btn.click( + fn=register_user, + inputs=[reg_username, reg_password, reg_confirm], + outputs=[login_msg, reg_msg, user_state], + ) + + logout_btn.click( + fn=logout_user, + inputs=[user_state], + outputs=[ + login_msg, + user_state, + login_section, + protected_section, + alttext_assessment, + ], + ) + if __name__ == "__main__": - # connection_db = db_persistence_startup(table="wcag_user_assessments") - demo.launch() + demo.launch(server_name="0.0.0.0", server_port=7860) diff --git a/dependences/__pycache__/image_extractor.cpython-310.pyc b/dependences/__pycache__/image_extractor.cpython-310.pyc deleted file mode 100644 index b22845bf9f6a786542e27dcca13abf5a54cb132f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16294 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 G^Zx-}18a)_ diff --git a/dependences/__pycache__/mllm_management.cpython-310.pyc b/dependences/__pycache__/mllm_management.cpython-310.pyc deleted file mode 100644 index c70abca5177df5dce9dd190a0c7ab194097d0267..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10290 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 diff --git a/dependences/image_extractor.py b/dependences/image_extractor.py index 600dfad..357e41f 100644 --- a/dependences/image_extractor.py +++ b/dependences/image_extractor.py @@ -347,7 +347,7 @@ class ImageExtractor: #await page.goto(self.url, wait_until="networkidle") # method 1: use if the page has unpredictable async content and there is the need to ensure everything loads # The "networkidle" approach is generally more robust but slower, while the fixed timeout is faster but less adaptive to actual page behavior. # ---alternative method2: use if there is total awareness of the page's loading pattern and want faster, more reliable execution - await page.goto(self.url, wait_until="load") + await page.goto(self.url, timeout=50000, wait_until="load")# deafult timeout=30000, 30sec # Wait for page to load completely await page.wait_for_timeout(2000) # Wait for dynamic content # ----- @@ -380,7 +380,7 @@ class ImageExtractor: try: img_element = await page.locator( f'img[src="{url}"]' - ).first.element_handle() # Use first() to get only the first match + ).first.element_handle(timeout=0) # Use first() to get only the first match; 0 timeout=No timeout if img_element: img_elements.append(img_element) except Exception as e: diff --git a/dependences/utils.py b/dependences/utils.py index a8a9232..3253ece 100644 --- a/dependences/utils.py +++ b/dependences/utils.py @@ -160,6 +160,9 @@ def db_persistence_startup( id INTEGER PRIMARY KEY AUTOINCREMENT, insertion_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, insert_type TEXT, + page_url TEXT, + user TEXT, + llm_model TEXT, json_input_data TEXT, json_output_data TEXT )""" ) @@ -177,8 +180,11 @@ def db_persistence_startup( def db_persistence_insert( connection_db, insert_type, - json_in_str, - json_out_str, + page_url, + user="", + llm_model="", + json_in_str="", + json_out_str="", table="wcag_validator_results", ): @@ -189,8 +195,8 @@ def db_persistence_insert( cursor.execute( "INSERT INTO " + table - + " (insert_type,json_input_data,json_output_data) VALUES (?,?,?)", - (insert_type, json_in_str, json_out_str), + + " (insert_type,page_url,user,llm_model,json_input_data,json_output_data) VALUES (?,?,?,?,?,?)", + (insert_type, page_url, user, llm_model, json_in_str, json_out_str), ) connection_db.commit() logging.info( diff --git a/docker/UI/Dockerfile b/docker/UI/Dockerfile new file mode 100644 index 0000000..3b64cc2 --- /dev/null +++ b/docker/UI/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.10-slim + +COPY /docker/UI/requirements_UI.txt /tmp/requirements_UI.txt + +RUN pip install --no-cache-dir -r /tmp/requirements_UI.txt + +RUN rm /tmp/requirements_UI.txt + +COPY dependences /dependences + +COPY /UI/persistence /UI/persistence +COPY /UI/dependences_ui /UI/dependences_ui +COPY /UI/wcag_validator_ui.py /UI/wcag_validator_ui.py + +EXPOSE 7860 + +WORKDIR /UI +CMD ["python","wcag_validator_ui.py"] + + + diff --git a/docker/UI/requirements_UI.txt b/docker/UI/requirements_UI.txt new file mode 100644 index 0000000..8bb7be9 --- /dev/null +++ b/docker/UI/requirements_UI.txt @@ -0,0 +1,4 @@ +gradio==5.49.1 +pandas==2.3.3 +python-dotenv==1.2.1 +requests==2.32.5 \ No newline at end of file diff --git a/docker/restServer/Dockerfile b/docker/restServer/Dockerfile new file mode 100644 index 0000000..110aa6e --- /dev/null +++ b/docker/restServer/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.10-slim + +COPY /docker/restServer/requirements.txt /tmp/requirements.txt + +RUN pip install --no-cache-dir -r /tmp/requirements.txt + +# Install Playwright browsers and dependencies +RUN playwright install --with-deps + +RUN rm /tmp/requirements.txt + +COPY persistence /persistence + +COPY dependences /dependences + +COPY restserver /restserver +COPY wcag_validator_RESTserver.py wcag_validator_RESTserver.py + +EXPOSE 8000 +CMD ["python","wcag_validator_RESTserver.py"] + + + diff --git a/docker/restServer/requirements.txt b/docker/restServer/requirements.txt new file mode 100644 index 0000000..2aebf1b --- /dev/null +++ b/docker/restServer/requirements.txt @@ -0,0 +1,6 @@ +pandas==2.3.3 +playwright==1.56.0 +python-dotenv==1.2.1 +requests==2.32.5 +uvicorn==0.38.0 +fastapi==0.121.2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 55375ef..2aebf1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ pandas==2.3.3 playwright==1.56.0 python-dotenv==1.2.1 requests==2.32.5 -uvicorn==0.38.0 \ No newline at end of file +uvicorn==0.38.0 +fastapi==0.121.2 \ No newline at end of file diff --git a/restserver/routers/__pycache__/routes_health.cpython-310.pyc b/restserver/routers/__pycache__/routes_health.cpython-310.pyc deleted file mode 100644 index 020415c00cb0f0b64ec801800a5f78e7cfb305af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1369 zcmZuxOK)5?6t;a|lXMc&LJJE(u7r?gL9$~JA=E0W(x7xe32Mb?BQ|1>bdMwam$9Eq>S-VN}W-E%OS1pW}P_|HFR*syLGa~32c}kz+&DbZB zjj2pf@ZK?z`OAV5m%hqm@hc6v1|R)5bi*gx556Ddt+U15w&z8h>r+>bt-m3`Rz}U7(v44>atgjHVFrzarcz)RpG{(3+=Mb( zUTI^Xa`h=!jdH52)dJ*tFEq9YBpoY75ko1x%q=QUfvL^_AN*q1?f|BR>1Qs7U_9}7d89S$e8hAQuXK0bP7CHF=Df68D z$o4P$V|q+_ML(c#Z8?U;<0o79xB26%Qt+K-1O_1oz8?$1mF36U9AcU8^1eRih@0dZ zOU0e$6X#}|8ymH*%6g)$+dMfr*^s)jum8X#@Zl8*nuEsq8`hO>ZV^NYJ_BR4QGer$ z=w&9nGr#oUq=Lvkvxal?aac^OGrh#z3Jam)on3uL$S{&I{p}g_vqNufw=2#It!>w>#!`@E4GZ5l7G0q6vI8 zF{BJfIf@OcgEYCbX2Ke}DMlsMVW#3MiFf&%mrLtN!uTVy?e(z=!R-Hh_|8{tkY9(N PiVc)pBVF2|w~~JWK|5~< diff --git a/restserver/routers/__pycache__/routes_local_db.cpython-310.pyc b/restserver/routers/__pycache__/routes_local_db.cpython-310.pyc deleted file mode 100644 index 3c2df64067eb283f050105840e31095e59efb117..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2347 zcmZ`*&uM@&cxZU-Ze98 zLRNM~1-FW-JyRrd?5+PpFZ~5i9h)Y>=B>oiTT?%8d*OVQacUtpf6PJQ(e8;T&*{R>Sd`C z_2QE|sd&^AQ7*W*BxNSAcv03*xR++RCsOX^o5JhKED}mZHd-E+GCc7J3sBWXXa?jZ z*(F1JV85gTx=Sg69@~Y3ohHN5pfq3uYmN*UxX9{~!WX`!%TZsd3>*}XLl?YMOUP`J|;!pm#N@E*4~08T^T)t`(j6yU}@?nWn&YqG%X^}jk!}6ef2z;v^ zA^3ZRK?k8~LRDv=>44d zGcXZBJyK8-*PyEN&Tt8@ACc>em4XU0qv2A}n4v@>CH5u~)E< z=P1mY5~0oh_(7Kgblo3jq(7F00bA-w#|p(HQ8giTO=975{5^`iEv z?5ApkE2IKzd1?l3S zg^Fa{GwQU{uq$+xiymYRkw&pl#W@p2-u8?P(5{pq(`3hs(>RYoFooc=zN2dgG6!`? z#6Xuv+R?R;^C0ZSff?({!D+OUWStHuR@-s9kpc5;h~7zkQ$#u_+D7B;*3|Y@A8Rf>@I}SCb_+{Od+{aQp{k8 zbB9+>LW13eyCS#vWbVY2S4Z!c$vp`E(Ei45j-T6foA|Z5p4pDV&OxFI#`-AUZ7!`W zEv$J9t9R;a7cTnVmyOk1o@kCs0kneF9Z~Bwb8JZNNVM|nDQAjJ zL(ior{&63(zl&2xNq0c-aai#rE_t^`3h<9%=?(A6M#&<~T*iaZCqO1vG+IgK}m zJj7%sF$v3)(0EW4=24fpY-ZZb`4#PKgn(!-Hu^KR^zrf3EKtS#V^AaW2D@3XFap`& eiZ^H}%v zisE7zIH0eDbx#6FKrXrFmUAxo1v&IJCtU&r$R$c5<*S}0MJaZ6F*ViI-PP6A)m7i5 zt(Ifp_s74T3ZE<*#$Ty%{AZ(a7EksE5N0qlGGfy+W6QHlYFkk$wmn<(rKs$cA+w`O zT=l9`SVC;vLc7g6Xm|E4?*uE|Fb3U6u-h0|s($XX z%h%Jf3Yu9-;N|TIVgP?vs$oSQi@d$J5kHeu<_LUH0EmdQI z1h_V$B_<!T zGd$~a9Jg2EHZL>lLxYvP3a^5sN!}?J(h2cpwsl-}%ovtJKPAPQ0}h7 z1davn_;JX?b;OhZT)(elDnPug!*3Fyc$b6KcP1;|nZGj(w)`NH&o<(F%VZ@yHY;x#ta5_fi|CQs8$?Bv7d1%5lNY_;67C; zqCm9*#{6Iu`ns*O$%oN!+#l5_-P)o&XotyWIw&bSZe#NYqHM3$_rmg>$N&m_}+s@?m?~a0%Lp`c0iX5+O96^8$a5)yqb5TOqjuOM4 zXwAQ1pT9Eio0Ao3JTPiX@_d#^$i-%1i8f`5di#%lHEO z*qi(1Tv9IHJLZn@{0ZJS#cAyQ1gvn__ZJNIg9EGBJ$82zvH@hL*_-UGgVHy>y?NO5 zQr^wy*%{ciasb$K3v1YhZS0p!BdNb*+&8o39e9-fU*M6a@)@?u-afEyA&bCTWBcX% z=I*6p1lcQJp%aj&*gNu-&}8q@*b3(A(TfA{B9__OC5F>hrdX9@Z?-%-MRLvS@WJH5bsha00U*J+FJu_k4YCY%BIb#7H-LYbn!>%Jd(btCQaH7yfeWFS9etm;B|c{_>5-<{4EfO6|b%W?4wt zC>6U%H9$&KcDyEl|)8fSfMj;&? z>f01)yyof>J23U@nriE`haBpUQdDNu)NRUjclhq4_k8c#ayIE|(V;&`vQ$V4khXH9 z3SFw1B0UEoVFe&dQ=CQ48Cc?ncuiME$<-h3@$@oIc*sG8RuNA<>s@^K_H_>5Ap*C>FdFLy^=HMTP({xBivs+ziilCN838Eiy+ z;&OtM?i8t#A7$!B=`e^~wz1}7=ku#LIKrx&O`*~XDT2Yy6aeG6C;e5urK6qdG0NPT zl*e-O%#Sd-h60u{tni4+4ls8mq@pIpc{D}8kX1GI!x1%r^W-=K4q{xDwUOa{(=a9_ z*cAQ)+~8GZlOd`cr2*<^r#LP)jz>Ppi(;hFAqvJQ3Wi+Nu!<@r>5d=>p-N*pR5kc! zlR&>{kReU#D35^jrD&7jZ6fc0ctPp>KeXIr%`Nel;H!9|wAaI9K}oh_fx# zA0K@m_}W(_9W01_grK&;aCk>XfW9L671O z+2elQMyvVMu^qE%0o|6`03;THlP6Buu`H_%3Bi;CqFAQ$uXc6OtbJuYohjQ`srl3@ zJJ4-eee>o2w98$~(es_BUAwEfnWq^2Wb*F+0XE-(Jg*IE;w1JbULtae2<=%A0@mnT z2Z{E@G5-+C5(K=&8z3&8YzCxe)GVuqZ9LVF)qbM;2Pai^Gr(ONg&GZN<*GLotsGtF zVkx=`_*&a#K_bJUpszLYDak6xHn`b>;J^N`(KoHQWW6K!%CnWR45LiYrF?eV^tJF# z9v}8%V9`ZX@A#w%2>pnsnF*0&*7hg9xO*~!v R7p-QcY1)=;c1ttPe*q&8*n9v0 diff --git a/restserver/routers/routes_wcag_alttext.py b/restserver/routers/routes_wcag_alttext.py index 4204cf7..15afa9e 100644 --- a/restserver/routers/routes_wcag_alttext.py +++ b/restserver/routers/routes_wcag_alttext.py @@ -24,7 +24,7 @@ class WCAGAltTextValuation(BaseModel): context_levels: int = 5 pixel_distance_threshold: int = 200 number_of_images: int = 10 - save_images: str = "True" + save_images: str = "True" save_elaboration: str = "True" specific_images_urls: List[str] = [] @@ -110,13 +110,12 @@ class WCAGAltTextValuationRoutes: images, openai_model=self.mllm_settings["openai_model"], ) - # Parse MLLM responses + # Parse MLLM responses for i, response in enumerate(mllm_responses): parsed_resp = parse_mllm_alt_text_response(response["mllm_response"]) mllm_responses[i]["mllm_response"] = parsed_resp mllm_responses_object = { - "mllm_model_id": mllm_model_id, "mllm_alttext_assessments": mllm_responses, } @@ -133,6 +132,8 @@ class WCAGAltTextValuationRoutes: db_persistence_insert( connection_db=self.connection_db, insert_type="wcag_alttext_validation", + page_url=json_content["page_url"], + llm_model=mllm_model_id, json_in_str=json_in_str, json_out_str=json_out_str, table="wcag_validator_results",