UI user task assignment
This commit is contained in:
parent
b8fa6dfd13
commit
ebbaa972e9
|
|
@ -25,13 +25,14 @@ def hash_password(password):
|
||||||
"""Hash password using SHA-256"""
|
"""Hash password using SHA-256"""
|
||||||
return hashlib.sha256(password.encode()).hexdigest()
|
return hashlib.sha256(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def associate_user_with_manager(users, user_assignment_manager):
|
def associate_user_with_manager(users, user_assignment_manager):
|
||||||
user_list=users.keys()
|
user_list = users.keys()
|
||||||
print(f"registering--Associating users with manager: {list(user_list)}")
|
print(f"registering--Associating users with manager: {list(user_list)}")
|
||||||
user_assignment_manager.register_active_users(list(user_list))
|
user_assignment_manager.register_active_users(list(user_list))
|
||||||
|
|
||||||
|
|
||||||
def register_user(username, password, confirm_password,user_assignment_manager):
|
def register_user(username, password, confirm_password, user_assignment_manager):
|
||||||
"""Register a new user"""
|
"""Register a new user"""
|
||||||
if not username or not password:
|
if not username or not password:
|
||||||
return "", "Username and password cannot be empty!", None
|
return "", "Username and password cannot be empty!", None
|
||||||
|
|
@ -53,7 +54,7 @@ def register_user(username, password, confirm_password,user_assignment_manager):
|
||||||
try:
|
try:
|
||||||
associate_user_with_manager(users, user_assignment_manager)
|
associate_user_with_manager(users, user_assignment_manager)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error associating user with manager: {e}")
|
print(f"Error associating user with manager: {e}")
|
||||||
|
|
||||||
return "", f"✅ Registration successful! You can now login.", None
|
return "", f"✅ Registration successful! You can now login.", None
|
||||||
|
|
||||||
|
|
@ -125,3 +126,44 @@ def protected_content(state):
|
||||||
if state.get("logged_in"):
|
if state.get("logged_in"):
|
||||||
return f"You are logged as {state.get('username')}\n"
|
return f"You are logged as {state.get('username')}\n"
|
||||||
return "Please login to access this content."
|
return "Please login to access this content."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_assessments_done(connection_db, username):
|
||||||
|
"""
|
||||||
|
it returns:
|
||||||
|
{
|
||||||
|
"https://example.com/page1": [1, 3, 5],
|
||||||
|
"https://example.com/page2": [2, 4, 6],
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor = connection_db.cursor()
|
||||||
|
username = json.dumps({"username": username}, ensure_ascii=False)
|
||||||
|
cursor.execute(
|
||||||
|
"""
|
||||||
|
SELECT page_url, json_output_data
|
||||||
|
FROM wcag_user_assessments
|
||||||
|
WHERE user = ? AND insert_type = ?
|
||||||
|
ORDER BY page_url
|
||||||
|
""",
|
||||||
|
(username, "wcag_user_llm_alttext_assessments"),
|
||||||
|
)
|
||||||
|
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
assessment_done = {} # dict: {page_url: sorted list of image numbers}
|
||||||
|
for row in rows:
|
||||||
|
page_url = row[0]
|
||||||
|
data = json.loads(row[1])
|
||||||
|
image_numbers = {int(item["Image #"]) for item in data} # set to deduplicate
|
||||||
|
if page_url not in assessment_done:
|
||||||
|
assessment_done[page_url] = image_numbers
|
||||||
|
else:
|
||||||
|
assessment_done[page_url].update(
|
||||||
|
image_numbers
|
||||||
|
) # merge if url appears multiple times
|
||||||
|
|
||||||
|
# Convert sets to sorted lists
|
||||||
|
assessment_done = {url: sorted(imgs) for url, imgs in assessment_done.items()}
|
||||||
|
|
||||||
|
return assessment_done
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ class UserAssignmentManager:
|
||||||
assignments_xlsx_path: str = "alt_text_assignments_output_target_overlap.xlsx",
|
assignments_xlsx_path: str = "alt_text_assignments_output_target_overlap.xlsx",
|
||||||
target_overlap: int = 2,
|
target_overlap: int = 2,
|
||||||
seed: int = 42,
|
seed: int = 42,
|
||||||
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize the User Assignment Manager.
|
Initialize the User Assignment Manager.
|
||||||
|
|
@ -86,8 +87,8 @@ class UserAssignmentManager:
|
||||||
# Initialize database
|
# Initialize database
|
||||||
self._init_database()
|
self._init_database()
|
||||||
|
|
||||||
# Load existing assignments from JSON if available
|
# Load existing assignments to db from JSON if available
|
||||||
self._load_existing_assignments()
|
#self._load_existing_assignments()
|
||||||
|
|
||||||
def _load_sites_config(self) -> List[SiteConfig]:
|
def _load_sites_config(self) -> List[SiteConfig]:
|
||||||
"""Load site configuration from JSON."""
|
"""Load site configuration from JSON."""
|
||||||
|
|
@ -160,7 +161,7 @@ class UserAssignmentManager:
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def _load_existing_assignments(self, active_user_names: Optional[List[str]] = None):
|
def _load_existing_assignments(self, active_user_names: List[str] = []):
|
||||||
"""Load existing assignments from JSON file into database if not already there."""
|
"""Load existing assignments from JSON file into database if not already there."""
|
||||||
if not self.assignments_json_path.exists():
|
if not self.assignments_json_path.exists():
|
||||||
return
|
return
|
||||||
|
|
@ -172,17 +173,15 @@ class UserAssignmentManager:
|
||||||
conn = sqlite3.connect(self.db_path)
|
conn = sqlite3.connect(self.db_path)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# nb: every service restart and user registration will trigger this (ONCONFLICT ensures no duplicates)
|
||||||
for user_id, sites_dict in assignments.items():
|
for user_id, sites_dict in assignments.items():
|
||||||
for site_url, image_indices in sites_dict.items():
|
try:
|
||||||
# print(f"[DB] Loading assignment for user {user_id}, site {site_url}, "
|
for site_url, image_indices in sites_dict.items():
|
||||||
# f"{image_indices} images")
|
|
||||||
try:
|
print(
|
||||||
'''
|
f"[DB] Loading assignment for user {user_id}, site {site_url}, "
|
||||||
cursor.execute("""
|
f"{image_indices} images"
|
||||||
INSERT OR IGNORE INTO user_assignments
|
)
|
||||||
(user_id, site_url, image_indices)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
""", (user_id, site_url, json.dumps(image_indices)))'''
|
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -195,28 +194,26 @@ class UserAssignmentManager:
|
||||||
(user_id, site_url, json.dumps(image_indices)),
|
(user_id, site_url, json.dumps(image_indices)),
|
||||||
)
|
)
|
||||||
|
|
||||||
cursor.execute( # also update user_info table with user_name if active_user_names is provided and user_id starts with "user"
|
cursor.execute( # also update user_info table with user_name if active_user_names is provided and user_id starts with "user"
|
||||||
"""
|
"""
|
||||||
INSERT INTO user_info (user_id, user_name)
|
INSERT INTO user_info (user_id, user_name)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
ON CONFLICT(user_id) DO UPDATE SET
|
ON CONFLICT(user_id) DO UPDATE SET
|
||||||
user_name = excluded.user_name
|
user_name = excluded.user_name
|
||||||
""",
|
""",
|
||||||
|
(
|
||||||
|
user_id,
|
||||||
(
|
(
|
||||||
user_id,
|
active_user_names[int(user_id[4:]) - 1]
|
||||||
(
|
if active_user_names and user_id.startswith("user")
|
||||||
active_user_names[int(user_id[4:]) - 1]
|
else None
|
||||||
if active_user_names and user_id.startswith("user")
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
print(
|
print(f"[DB] Error. Skipping existing assignment for user {user_id}")
|
||||||
f"[DB] Error. Skipping existing assignment for user {user_id}, site {site_url}"
|
pass
|
||||||
)
|
|
||||||
pass
|
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
@ -243,7 +240,7 @@ class UserAssignmentManager:
|
||||||
|
|
||||||
if from_user_name:
|
if from_user_name:
|
||||||
print(f"[DB] Looking up user_id for user_name: {user_id}")
|
print(f"[DB] Looking up user_id for user_name: {user_id}")
|
||||||
|
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"""
|
"""
|
||||||
SELECT user_id
|
SELECT user_id
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ from dependences.utils import (
|
||||||
db_persistence_startup,
|
db_persistence_startup,
|
||||||
db_persistence_insert,
|
db_persistence_insert,
|
||||||
return_from_env_valid,
|
return_from_env_valid,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from dependences_ui.utils import *
|
from dependences_ui.utils import *
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
@ -31,18 +33,37 @@ import sqlite3
|
||||||
|
|
||||||
|
|
||||||
from user_task_assignment.user_assignment_manager import UserAssignmentManager
|
from user_task_assignment.user_assignment_manager import UserAssignmentManager
|
||||||
|
from dependences_ui.utils import load_users,get_user_assessments_done
|
||||||
|
users=load_users()
|
||||||
|
user_list=list(users.keys())
|
||||||
|
print(f"Loaded users from simple JSON: {len(user_list)}")
|
||||||
|
|
||||||
user_assignment_manager = UserAssignmentManager(
|
user_assignment_manager = UserAssignmentManager(
|
||||||
db_path="persistence/wcag_validator_ui.db",
|
db_path="persistence/wcag_validator_ui.db",
|
||||||
config_json_path="user_task_assignment/sites_config.json",
|
config_json_path="user_task_assignment/sites_config.json",
|
||||||
assignments_json_path="user_task_assignment/alt_text_assignments_output_target_overlap.json",
|
assignments_json_path="user_task_assignment/alt_text_assignments_output_target_overlap.json",
|
||||||
assignments_xlsx_path="user_task_assignment/alt_text_assignments_output_target_overlap.xlsx"
|
assignments_xlsx_path="user_task_assignment/alt_text_assignments_output_target_overlap.xlsx",
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get current managed users
|
# Get current managed users
|
||||||
managed_users = user_assignment_manager.get_all_user_ids()
|
managed_users_number = user_assignment_manager.get_managed_user_count()
|
||||||
print(f"Currently managed users from db: {managed_users}")
|
print(f"Currently managed users from db: {managed_users_number}")
|
||||||
print(f"Total managed users from db: {user_assignment_manager.get_managed_user_count()}\n")
|
if managed_users_number !=len(user_list):# rigenenerate files only if some user numbers disalignmnets. Avoid only updates on new user registration process
|
||||||
|
print(f"Warning: Number of users in db ({managed_users_number}) does not match number of users loaded from JSON ({len(user_list)}). Re-init user assignments files.")
|
||||||
|
user_assignment_manager.register_active_users(user_list)#on startup register users loaded from JSON into the manager (creating also assignments .json amd .xml files)
|
||||||
|
# Get current managed users after regsitration alignment
|
||||||
|
managed_users_number = user_assignment_manager.get_managed_user_count()
|
||||||
|
print(f"Currently managed users from db after alignment: {managed_users_number}")
|
||||||
|
|
||||||
|
# Get current managed users after regsitration alignment
|
||||||
|
|
||||||
|
print(f"Total managed users from db: {managed_users_number}\n")
|
||||||
|
if managed_users_number !=len(user_list):
|
||||||
|
print(f"Warning: Number of users in db ({managed_users_number}) does not match number of users loaded from JSON ({len(user_list)}). Check user assignment manager initialization.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
user_assignment_stats = user_assignment_manager.get_statistics()
|
user_assignment_stats = user_assignment_manager.get_statistics()
|
||||||
print(f"Current assignment stats:{user_assignment_stats} \n")
|
print(f"Current assignment stats:{user_assignment_stats} \n")
|
||||||
|
|
@ -52,9 +73,81 @@ print(f"Current assignment stats:{user_assignment_stats} \n")
|
||||||
WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")]
|
WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")]
|
||||||
|
|
||||||
|
|
||||||
def display_user_assignment(user_state):
|
def maybe_close_modal(process_dataframe_output_state):
|
||||||
|
print("Checking if modal can be closed based on:",type(process_dataframe_output_state), process_dataframe_output_state)
|
||||||
|
if not process_dataframe_output_state:
|
||||||
|
print("Modal cannot be closed.")
|
||||||
|
return Modal(visible=True) # keep it open
|
||||||
|
return Modal(visible=False) # close it
|
||||||
|
|
||||||
|
def maybe_open_modal(make_alttext_llm_assessment_api_call_output_state):
|
||||||
|
print("Checking if modal can be opened based on:",type(make_alttext_llm_assessment_api_call_output_state), make_alttext_llm_assessment_api_call_output_state)
|
||||||
|
if not make_alttext_llm_assessment_api_call_output_state:
|
||||||
|
print("Modal cannot be opened.")
|
||||||
|
return Modal(visible=False)
|
||||||
|
return Modal(visible=True)
|
||||||
|
|
||||||
|
def render_user_assessmnet_status_table(df):
|
||||||
|
if df is None or df.empty:
|
||||||
|
return "<p>No assignments found.</p>"
|
||||||
|
|
||||||
|
total_work_to_be_done=[]
|
||||||
|
rows = ""
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
url = row["Website URL"]
|
||||||
|
assigned = row["Assigned Image Number"]
|
||||||
|
work_done = row["Work Done on Image Number"]
|
||||||
|
work_to_be_done = [img for img in assigned if img not in work_done]
|
||||||
|
total_work_to_be_done+=work_to_be_done
|
||||||
|
|
||||||
|
rows += f"""
|
||||||
|
<tr>
|
||||||
|
<td style="padding:8px; border:1px solid #ddd; word-break:break-all;">
|
||||||
|
<a href="{url}" target="_blank">{url}</a>
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px; border:1px solid #ddd; text-align:center;">
|
||||||
|
{assigned}
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px; border:1px solid #ddd; text-align:center;">
|
||||||
|
{work_done}
|
||||||
|
</td>
|
||||||
|
<td style="padding:8px; border:1px solid #ddd; text-align:center;">
|
||||||
|
{work_to_be_done}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
"""
|
||||||
|
total_work_to_be_done_text =""
|
||||||
|
if len(total_work_to_be_done)==0:
|
||||||
|
total_work_to_be_done_text="All assigned work is done! Great job!"
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
<div style="overflow-x:auto;">
|
||||||
|
<table style="width:100%; border-collapse:collapse; font-size:14px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color:#f2f2f2;">
|
||||||
|
<th style="padding:10px; border:1px solid #ddd; text-align:left;">Website URL</th>
|
||||||
|
<th style="padding:10px; border:1px solid #ddd; text-align:left;">Assigned Image Number</th>
|
||||||
|
<th style="padding:10px; border:1px solid #ddd; text-align:left;">Work Done on Image Number</th>
|
||||||
|
<th style="padding:10px; border:1px solid #ddd; text-align:left;">Work Still to be Done</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<p style="color:green;font-size: large;font-weight: bold;">{total_work_to_be_done_text}</p>
|
||||||
|
"""
|
||||||
|
|
||||||
|
def display_user_assignment(db_path,user_state):
|
||||||
if user_state and "username" in user_state:
|
if user_state and "username" in user_state:
|
||||||
username = user_state["username"]
|
username = user_state["username"]
|
||||||
|
connection_db = sqlite3.connect(db_path)
|
||||||
|
|
||||||
|
user_assessment_work=get_user_assessments_done(connection_db ,username)
|
||||||
|
print(f"User {username} has done assessments for {user_assessment_work} images.")
|
||||||
|
|
||||||
|
|
||||||
print(f"Fetching assignment for user: {username}")
|
print(f"Fetching assignment for user: {username}")
|
||||||
assignments = user_assignment_manager.get_user_assignments(username, from_user_name=True)
|
assignments = user_assignment_manager.get_user_assignments(username, from_user_name=True)
|
||||||
|
|
||||||
|
|
@ -70,7 +163,8 @@ def display_user_assignment(user_state):
|
||||||
data_frame.append(
|
data_frame.append(
|
||||||
{
|
{
|
||||||
"Website URL": url,
|
"Website URL": url,
|
||||||
"Assigned Image Number List": assignments[url]
|
"Assigned Image Number": assignments[url],
|
||||||
|
"Work Done on Image Number":user_assessment_work[url] if url in user_assessment_work else [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -83,7 +177,7 @@ def display_user_assignment(user_state):
|
||||||
|
|
||||||
def process_dataframe(db_path, url, updated_df, user_state={},llm_response_output={}):
|
def process_dataframe(db_path, url, updated_df, user_state={},llm_response_output={}):
|
||||||
|
|
||||||
print("Processing dataframe to adjust columns...type:",type(updated_df))
|
print("Processing dataframe to adjust columns...type:",type(updated_df),updated_df)
|
||||||
# accept different input forms from UI (DataFrame, JSON string, or list of dicts)
|
# accept different input forms from UI (DataFrame, JSON string, or list of dicts)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
|
@ -95,23 +189,23 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu
|
||||||
elif isinstance(updated_df, list):
|
elif isinstance(updated_df, list):
|
||||||
updated_df = pd.DataFrame(updated_df)
|
updated_df = pd.DataFrame(updated_df)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error parsing updated data: {str(e)}"
|
return f"Error parsing updated data: {str(e)}" ,False
|
||||||
for column_rating_name in ["User Assessment for LLM Proposal 1", "User Assessment for LLM Proposal 2"]:
|
for column_rating_name in ["User Assessment for LLM Proposal 1", "User Assessment for LLM Proposal 2"]:
|
||||||
|
|
||||||
# Get the assessment column
|
# Get the assessment column
|
||||||
try:
|
try:
|
||||||
updated_df[column_rating_name] = updated_df[column_rating_name].astype(int)
|
updated_df[column_rating_name] = updated_df[column_rating_name].astype(int)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return "Error: User Assessment for LLM Proposal must be an integer"
|
return "Error: User Assessment for LLM Proposal must be an integer",False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return f"No data Saved because no image selected. Please select at least one image."
|
return f"No data Saved because some images are not correcly managed. Please retry." ,False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error processing User Assessment for LLM Proposal: {str(e)}"
|
return f"Error processing User Assessment for LLM Proposal: {str(e)}" ,False
|
||||||
|
|
||||||
if (updated_df[column_rating_name] < 1).any() or (
|
if (updated_df[column_rating_name] < 1).any() or (
|
||||||
updated_df[column_rating_name] > 5
|
updated_df[column_rating_name] > 5
|
||||||
).any():
|
).any():
|
||||||
return "Error: User Assessment for LLM Proposal must be between 1 and 5"
|
return "Error: User Assessment for LLM Proposal must be between 1 and 5",False
|
||||||
|
|
||||||
dataframe_json = updated_df.to_json(orient="records")
|
dataframe_json = updated_df.to_json(orient="records")
|
||||||
connection_db = sqlite3.connect(db_path)
|
connection_db = sqlite3.connect(db_path)
|
||||||
|
|
@ -126,7 +220,7 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu
|
||||||
page_url=url,
|
page_url=url,
|
||||||
user=json_user_str,
|
user=json_user_str,
|
||||||
llm_model="",
|
llm_model="",
|
||||||
json_in_str=llm_response_output_str,#dataframe_json, # to improve
|
json_in_str=llm_response_output_str,#dataframe_json,
|
||||||
json_out_str=dataframe_json,
|
json_out_str=dataframe_json,
|
||||||
table="wcag_user_assessments",
|
table="wcag_user_assessments",
|
||||||
)
|
)
|
||||||
|
|
@ -135,11 +229,27 @@ def process_dataframe(db_path, url, updated_df, user_state={},llm_response_outpu
|
||||||
finally:
|
finally:
|
||||||
if connection_db:
|
if connection_db:
|
||||||
connection_db.close()
|
connection_db.close()
|
||||||
return "User assessment saved successfully!"
|
print("User assessment saved to database successfully.returning:", True)
|
||||||
|
return "User assessment saved successfully!",True
|
||||||
|
|
||||||
|
|
||||||
def load_images_from_json(json_input):
|
def load_images_from_json(json_input,user_assignment_current_status_df):
|
||||||
"""Extract URLs and alt text from JSON and create HTML gallery"""
|
"""Extract URLs and alt text from JSON and create HTML gallery"""
|
||||||
|
|
||||||
|
if user_assignment_current_status_df is None or user_assignment_current_status_df.empty:
|
||||||
|
print("No user assignment status found. Displaying all images without assignment info.")
|
||||||
|
|
||||||
|
user_assignments={}
|
||||||
|
for _, row in user_assignment_current_status_df.iterrows():
|
||||||
|
url = row["Website URL"]
|
||||||
|
assigned = row["Assigned Image Number"]
|
||||||
|
work_done = row["Work Done on Image Number"]
|
||||||
|
user_assignments[url] = {
|
||||||
|
"assigned": assigned,
|
||||||
|
"work_done": work_done
|
||||||
|
}
|
||||||
|
#print(f"User assignments extracted for image loading: {user_assignments}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json_input
|
data = json_input
|
||||||
|
|
||||||
|
|
@ -250,6 +360,17 @@ def load_images_from_json(json_input):
|
||||||
url = img_data.get("url", "")
|
url = img_data.get("url", "")
|
||||||
alt_text = img_data.get("alt_text", "No description")
|
alt_text = img_data.get("alt_text", "No description")
|
||||||
|
|
||||||
|
page_url = img_data.get("page_url", "")
|
||||||
|
assigned=user_assignments.get(page_url,{}).get("assigned",[])
|
||||||
|
work_done=user_assignments.get(page_url,{}).get("work_done",[])
|
||||||
|
assigned_text=""
|
||||||
|
if idx+1 in assigned:
|
||||||
|
assigned_text="-(Assigned)"
|
||||||
|
if idx+1 in work_done:
|
||||||
|
assigned_text+="->(Already managed)"
|
||||||
|
if idx+1 in assigned and idx+1 in work_done:
|
||||||
|
assigned_text+="<span style='font-family: wingdings; font-size: large; font-weight: bold; color:green'>ü</span>"
|
||||||
|
|
||||||
html += f"""
|
html += f"""
|
||||||
<div class="image-card">
|
<div class="image-card">
|
||||||
<img src="{url}" alt="{alt_text}" loading="lazy" onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22200%22 height=%22200%22%3E%3Crect fill=%22%23ddd%22 width=%22200%22 height=%22200%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23999%22%3EImage not found%3C/text%3E%3C/svg%3E'">
|
<img src="{url}" alt="{alt_text}" loading="lazy" onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22200%22 height=%22200%22%3E%3Crect fill=%22%23ddd%22 width=%22200%22 height=%22200%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23999%22%3EImage not found%3C/text%3E%3C/svg%3E'">
|
||||||
|
|
@ -260,9 +381,9 @@ def load_images_from_json(json_input):
|
||||||
const panel = document.getElementById('panel-{idx}');
|
const panel = document.getElementById('panel-{idx}');
|
||||||
const checkedCount = document.querySelectorAll('.image-checkbox:checked').length;
|
const checkedCount = document.querySelectorAll('.image-checkbox:checked').length;
|
||||||
if (this.checked) {{
|
if (this.checked) {{
|
||||||
if (checkedCount > 3) {{
|
if (checkedCount > 6) {{
|
||||||
this.checked = false;
|
this.checked = false;
|
||||||
alert('Maximum 3 images can be selected!');
|
alert('Maximum 6 images can be selected!');
|
||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
panel.classList.add('visible');
|
panel.classList.add('visible');
|
||||||
|
|
@ -270,7 +391,7 @@ def load_images_from_json(json_input):
|
||||||
panel.classList.remove('visible');
|
panel.classList.remove('visible');
|
||||||
}}
|
}}
|
||||||
">
|
">
|
||||||
Select #{idx + 1}
|
Select #{idx + 1}<span>{assigned_text}</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="alt-text">Current alt_text: {alt_text}</div>
|
<div class="alt-text">Current alt_text: {alt_text}</div>
|
||||||
|
|
||||||
|
|
@ -287,7 +408,7 @@ def load_images_from_json(json_input):
|
||||||
<span class="radio-label">2</span>
|
<span class="radio-label">2</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="radio-option">
|
<label class="radio-option">
|
||||||
<input type="radio" name="assessment-{idx}" value="3" data-index="{idx}" checked>
|
<input type="radio" name="assessment-{idx}" value="3" data-index="{idx}">
|
||||||
<span class="radio-label">3</span>
|
<span class="radio-label">3</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="radio-option">
|
<label class="radio-option">
|
||||||
|
|
@ -443,8 +564,8 @@ def make_alttext_llm_assessment_api_call(
|
||||||
|
|
||||||
if not selected_images or len(selected_images) == 0:
|
if not selected_images or len(selected_images) == 0:
|
||||||
info_text = "No images selected"
|
info_text = "No images selected"
|
||||||
|
print("LLM assessment not started because no valid images were selected.")
|
||||||
return "LLM assessment not started", pd.DataFrame(), {}
|
return "LLM assessment not started", pd.DataFrame(), {},False
|
||||||
|
|
||||||
# prepare data for insertion
|
# prepare data for insertion
|
||||||
json_in_str = {}
|
json_in_str = {}
|
||||||
|
|
@ -465,8 +586,8 @@ def make_alttext_llm_assessment_api_call(
|
||||||
selected_image_id.append(
|
selected_image_id.append(
|
||||||
int(img["image_index"]) + 1
|
int(img["image_index"]) + 1
|
||||||
) # add the id selected (+1 for index alignment)
|
) # add the id selected (+1 for index alignment)
|
||||||
user_assessments_llm_proposal_1.append(3) # default value for now
|
user_assessments_llm_proposal_1.append(0) # default value for now
|
||||||
user_assessments_llm_proposal_2.append(3) # default value for now
|
user_assessments_llm_proposal_2.append(0) # default value for now
|
||||||
json_in_str["images_urls"] = selected_urls
|
json_in_str["images_urls"] = selected_urls
|
||||||
json_in_str["images_alt_text_original"] = selected_alt_text_original
|
json_in_str["images_alt_text_original"] = selected_alt_text_original
|
||||||
json_out_str["user_assessments"] = user_assessments
|
json_out_str["user_assessments"] = user_assessments
|
||||||
|
|
@ -531,13 +652,14 @@ def make_alttext_llm_assessment_api_call(
|
||||||
finally:
|
finally:
|
||||||
if connection_db:
|
if connection_db:
|
||||||
connection_db.close()
|
connection_db.close()
|
||||||
return "LLM assessment completed", info_dataframe, response
|
return "LLM assessment completed", info_dataframe, response, True
|
||||||
|
|
||||||
|
|
||||||
def make_image_extraction_api_call(
|
def make_image_extraction_api_call(
|
||||||
url,
|
url,
|
||||||
number_of_images=30,
|
number_of_images=30,
|
||||||
wcag_rest_server_url="http://localhost:8000",
|
wcag_rest_server_url="http://localhost:8000",
|
||||||
|
user_assignment_current_status={},
|
||||||
):
|
):
|
||||||
print(
|
print(
|
||||||
f"Making API call for image_extraction for {url} to {wcag_rest_server_url}/extract_images"
|
f"Making API call for image_extraction for {url} to {wcag_rest_server_url}/extract_images"
|
||||||
|
|
@ -553,7 +675,7 @@ def make_image_extraction_api_call(
|
||||||
headers=WCAG_VALIDATOR_RESTSERVER_HEADERS,
|
headers=WCAG_VALIDATOR_RESTSERVER_HEADERS,
|
||||||
)
|
)
|
||||||
# return response
|
# return response
|
||||||
info_text, gallery_images = load_images_from_json(response)
|
info_text, gallery_images = load_images_from_json(response,user_assignment_current_status)
|
||||||
|
|
||||||
return info_text, gallery_images
|
return info_text, gallery_images
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -561,97 +683,101 @@ def make_image_extraction_api_call(
|
||||||
|
|
||||||
|
|
||||||
def render_alttext_form(df):
|
def render_alttext_form(df):
|
||||||
"""Render a pandas DataFrame (or list/dict) into an editable HTML form."""
|
"""Render a pandas DataFrame (or list/dict) into an editable HTML form."""
|
||||||
try:
|
try:
|
||||||
if df is None:
|
if df is None or df.empty:
|
||||||
return ""
|
print("No data to render in form.")
|
||||||
if isinstance(df, str):
|
html=""
|
||||||
df = pd.read_json(df, orient="records")
|
return gr.update(value=html), html # return empty form
|
||||||
if isinstance(df, dict):
|
if isinstance(df, str):
|
||||||
df = pd.DataFrame(df)
|
df = pd.read_json(df, orient="records")
|
||||||
if isinstance(df, list):
|
if isinstance(df, dict):
|
||||||
df = pd.DataFrame(df)
|
df = pd.DataFrame(df)
|
||||||
|
if isinstance(df, list):
|
||||||
|
df = pd.DataFrame(df)
|
||||||
|
|
||||||
html = """
|
html = """
|
||||||
<style>
|
<style>
|
||||||
.alttext-table { width:100%; border-collapse: collapse; }
|
.alttext-table { width:100%; border-collapse: collapse; }
|
||||||
.alttext-table th, .alttext-table td { border:1px solid #ddd; padding:8px; }
|
.alttext-table th, .alttext-table td { border:1px solid #ddd; padding:8px; }
|
||||||
.alttext-table th { background:#f5f5f5; }
|
.alttext-table th { background:#f5f5f5; }
|
||||||
.alttext-row td { vertical-align: top; }
|
.alttext-row td { vertical-align: top; }
|
||||||
.llm-select { width:auto; }
|
.llm-select { width:auto; }
|
||||||
</style>
|
</style>
|
||||||
<table class="alttext-table">
|
<table class="alttext-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Image #</th>
|
<th>Image #</th>
|
||||||
<th>Original Alt Text</th>
|
<th>Original Alt Text</th>
|
||||||
<th>User Assessment</th>
|
<th>User Assessment</th>
|
||||||
<th>User Proposed Alt Text</th>
|
<th>User Proposed Alt Text</th>
|
||||||
<th>LLM Assessment 1</th>
|
<th>LLM Assessment 1</th>
|
||||||
<th>LLM Proposed Alt Text 1</th>
|
<th>LLM Proposed Alt Text 1</th>
|
||||||
<th>User Assessment for LLM Proposal 1</th>
|
<th>User Assessment for LLM Proposal 1</th>
|
||||||
<th>LLM Assessment 2</th>
|
<th>LLM Assessment 2</th>
|
||||||
<th>LLM Proposed Alt Text 2</th>
|
<th>LLM Proposed Alt Text 2</th>
|
||||||
<th>User Assessment for LLM Proposal 2</th>
|
<th>User Assessment for LLM Proposal 2</th>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
"""
|
||||||
|
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
imgnum = row.get("Image #", "")
|
||||||
|
orig = row.get("Original Alt Text", "")
|
||||||
|
user_ass = row.get("User Assessment", "")
|
||||||
|
user_prop = row.get("User Proposed Alt Text", "")
|
||||||
|
llm1_ass = row.get("LLM Assessment 1", "")
|
||||||
|
llm2_ass = row.get("LLM Assessment 2", "")
|
||||||
|
llm1_prop = row.get("LLM Proposed Alt Text 1", "")
|
||||||
|
llm2_prop = row.get("LLM Proposed Alt Text 2", "")
|
||||||
|
|
||||||
|
user_llm1_ass = row.get("User Assessment for LLM Proposal 1", 0)
|
||||||
|
user_llm2_ass = row.get("User Assessment for LLM Proposal 2", 0)
|
||||||
|
|
||||||
|
html += f"""
|
||||||
|
<tr class="alttext-row" data-index="{imgnum}">
|
||||||
|
<td class="img-num">{imgnum}</td>
|
||||||
|
<td class="orig-alt">{orig}</td>
|
||||||
|
<td class="user-assessment">{user_ass}</td>
|
||||||
|
<td class="user-proposed">{user_prop}</td>
|
||||||
|
<td >{llm1_ass}</td>
|
||||||
|
<td >{llm1_prop}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<select class="user_llm1_ass llm-select">
|
||||||
|
<option value="0" {'selected' if int(user_llm1_ass)==0 else ''}>-- none --</option>
|
||||||
|
<option value="1" {'selected' if int(user_llm1_ass)==1 else ''}>1</option>
|
||||||
|
<option value="2" {'selected' if int(user_llm1_ass)==2 else ''}>2</option>
|
||||||
|
<option value="3" {'selected' if int(user_llm1_ass)==3 else ''}>3</option>
|
||||||
|
<option value="4" {'selected' if int(user_llm1_ass)==4 else ''}>4</option>
|
||||||
|
<option value="5" {'selected' if int(user_llm1_ass)==5 else ''}>5</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td >{llm2_ass}</td>
|
||||||
|
<td >{llm2_prop}</td>
|
||||||
|
<td>
|
||||||
|
<select class="user_llm2_ass llm-select">
|
||||||
|
<option value="" {'selected' if int(user_llm1_ass)==0 else ''}>-- none --</option>
|
||||||
|
<option value="1" {'selected' if int(user_llm2_ass)==1 else ''}>1</option>
|
||||||
|
<option value="2" {'selected' if int(user_llm2_ass)==2 else ''}>2</option>
|
||||||
|
<option value="3" {'selected' if int(user_llm2_ass)==3 else ''}>3</option>
|
||||||
|
<option value="4" {'selected' if int(user_llm2_ass)==4 else ''}>4</option>
|
||||||
|
<option value="5" {'selected' if int(user_llm2_ass)==5 else ''}>5</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for _, row in df.iterrows():
|
html += """
|
||||||
imgnum = row.get("Image #", "")
|
</tbody>
|
||||||
orig = row.get("Original Alt Text", "")
|
</table>
|
||||||
user_ass = row.get("User Assessment", "")
|
"""
|
||||||
user_prop = row.get("User Proposed Alt Text", "")
|
|
||||||
llm1_ass = row.get("LLM Assessment 1", "")
|
|
||||||
llm2_ass = row.get("LLM Assessment 2", "")
|
|
||||||
llm1_prop = row.get("LLM Proposed Alt Text 1", "")
|
|
||||||
llm2_prop = row.get("LLM Proposed Alt Text 2", "")
|
|
||||||
|
|
||||||
user_llm1_ass = row.get("User Assessment for LLM Proposal 1", 3)
|
return gr.update(value=html), html
|
||||||
user_llm2_ass = row.get("User Assessment for LLM Proposal 2", 3)
|
except Exception as e:
|
||||||
|
return f"Error rendering form: {str(e)}"
|
||||||
html += f"""
|
|
||||||
<tr class="alttext-row" data-index="{imgnum}">
|
|
||||||
<td class="img-num">{imgnum}</td>
|
|
||||||
<td class="orig-alt">{orig}</td>
|
|
||||||
<td class="user-assessment">{user_ass}</td>
|
|
||||||
<td class="user-proposed">{user_prop}</td>
|
|
||||||
<td >{llm1_ass}</td>
|
|
||||||
<td >{llm1_prop}</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<select class="user_llm1_ass llm-select">
|
|
||||||
<option value="1" {'selected' if int(user_llm1_ass)==1 else ''}>1</option>
|
|
||||||
<option value="2" {'selected' if int(user_llm1_ass)==2 else ''}>2</option>
|
|
||||||
<option value="3" {'selected' if int(user_llm1_ass)==3 else ''}>3</option>
|
|
||||||
<option value="4" {'selected' if int(user_llm1_ass)==4 else ''}>4</option>
|
|
||||||
<option value="5" {'selected' if int(user_llm1_ass)==5 else ''}>5</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
<td >{llm2_ass}</td>
|
|
||||||
<td >{llm2_prop}</td>
|
|
||||||
<td>
|
|
||||||
<select class="user_llm2_ass llm-select">
|
|
||||||
<option value="1" {'selected' if int(user_llm2_ass)==1 else ''}>1</option>
|
|
||||||
<option value="2" {'selected' if int(user_llm2_ass)==2 else ''}>2</option>
|
|
||||||
<option value="3" {'selected' if int(user_llm2_ass)==3 else ''}>3</option>
|
|
||||||
<option value="4" {'selected' if int(user_llm2_ass)==4 else ''}>4</option>
|
|
||||||
<option value="5" {'selected' if int(user_llm2_ass)==5 else ''}>5</option>
|
|
||||||
</select>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
"""
|
|
||||||
|
|
||||||
html += """
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
"""
|
|
||||||
|
|
||||||
return gr.update(value=html), html
|
|
||||||
except Exception as e:
|
|
||||||
return f"Error rendering form: {str(e)}"
|
|
||||||
|
|
||||||
|
|
||||||
# ------- Gradio Interface -------#
|
# ------- Gradio Interface -------#
|
||||||
|
|
@ -682,6 +808,9 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
llm_response_output = gr.State()
|
llm_response_output = gr.State()
|
||||||
alttext_popup_html_state = gr.State("")
|
alttext_popup_html_state = gr.State("")
|
||||||
user_assignment_manager_state = gr.State(value=user_assignment_manager)
|
user_assignment_manager_state = gr.State(value=user_assignment_manager)
|
||||||
|
user_assignment_current_status = gr.State()
|
||||||
|
process_dataframe_output_state = gr.State()
|
||||||
|
make_alttext_llm_assessment_api_call_output_state = gr.State()
|
||||||
|
|
||||||
with Modal(visible=False, allow_user_close=False) as alttext_modal:
|
with Modal(visible=False, allow_user_close=False) as alttext_modal:
|
||||||
gr.Markdown("## Alt Text LLMs Assessment Results")
|
gr.Markdown("## Alt Text LLMs Assessment Results")
|
||||||
|
|
@ -754,22 +883,14 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
with gr.Column(visible=False) as protected_section:
|
with gr.Column(visible=False) as protected_section:
|
||||||
|
|
||||||
content_display = gr.Textbox(
|
content_display = gr.Textbox(
|
||||||
label="Your account", lines=5, interactive=False
|
label="Your account", lines=2, interactive=False
|
||||||
)
|
|
||||||
|
|
||||||
user_assignment_status = gr.DataFrame(
|
|
||||||
headers=[
|
|
||||||
"Website URL",
|
|
||||||
"Assigned Image Number List"
|
|
||||||
#"Assignment Status",
|
|
||||||
],
|
|
||||||
label="Your Current Assignment",
|
|
||||||
wrap=True, # Wrap text in cells
|
|
||||||
interactive=False,
|
|
||||||
scale=7,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logout_btn = gr.Button("Logout", variant="stop")
|
logout_btn = gr.Button("Logout", variant="stop")
|
||||||
|
gr.Markdown("### Your Assignment")
|
||||||
|
user_assignment_status =gr.HTML(label="Your Assignment")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# end login section
|
# end login section
|
||||||
|
|
||||||
|
|
@ -816,7 +937,8 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
# Store the DataFrame in state and render a clear HTML form for user edits
|
# Store the DataFrame in state and render a clear HTML form for user edits
|
||||||
alttext_info_state = gr.State()
|
alttext_info_state = gr.State()
|
||||||
alttext_form = gr.HTML(label="Assessment Form")
|
alttext_form = gr.HTML(label="Assessment Form")
|
||||||
alttext_form_data = gr.JSON(visible=False)
|
|
||||||
|
alttext_form_data = gr.JSON(visible=False) ##gr.JSON(visible=False) because gr.State() components are not meant to receive values from JS returns
|
||||||
|
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
|
|
@ -835,7 +957,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
],
|
],
|
||||||
).then(
|
).then(
|
||||||
make_image_extraction_api_call,
|
make_image_extraction_api_call,
|
||||||
inputs=[url_input, images_number, wcag_rest_server_url_state],
|
inputs=[url_input, images_number, wcag_rest_server_url_state,user_assignment_current_status],
|
||||||
outputs=[image_info_output, gallery_html],
|
outputs=[image_info_output, gallery_html],
|
||||||
).then(
|
).then(
|
||||||
fn=lambda: gr.Button(interactive=True),
|
fn=lambda: gr.Button(interactive=True),
|
||||||
|
|
@ -852,27 +974,40 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
wcag_rest_server_url_state,
|
wcag_rest_server_url_state,
|
||||||
user_state,
|
user_state,
|
||||||
],
|
],
|
||||||
outputs=[image_info_output, alttext_info_state, llm_response_output],
|
outputs=[image_info_output, alttext_info_state, llm_response_output,make_alttext_llm_assessment_api_call_output_state],
|
||||||
js="""
|
js="""
|
||||||
(url_input,gallery_html) => {
|
(url_input, gallery_html) => {
|
||||||
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
||||||
if (checkboxes.length === 0) {
|
if (checkboxes.length === 0) {
|
||||||
alert('Please select at least one image!');
|
alert('Please select at least one image!');
|
||||||
return [url_input,JSON.stringify([])];
|
return [url_input, JSON.stringify([])];
|
||||||
}
|
}
|
||||||
if (checkboxes.length > 3) {
|
if (checkboxes.length > 6) {
|
||||||
alert('Please select maximum 3 images!');
|
alert('Please select maximum 6 images!');
|
||||||
return [url_input,JSON.stringify([])];
|
return [url_input, JSON.stringify([])];
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedData = [];
|
const selectedData = [];
|
||||||
|
let hasError = false; // flag to handle missing assessment
|
||||||
|
|
||||||
checkboxes.forEach(checkbox => {
|
checkboxes.forEach(checkbox => {
|
||||||
|
if (hasError) return; // skip remaining iterations if error found
|
||||||
|
|
||||||
const index = checkbox.dataset.index;
|
const index = checkbox.dataset.index;
|
||||||
const imageUrl = checkbox.dataset.imgurl;
|
const imageUrl = checkbox.dataset.imgurl;
|
||||||
const originalAlt = document.querySelector('.original-alt[data-index="' + index + '"]').value;
|
const originalAlt = document.querySelector('.original-alt[data-index="' + index + '"]').value;
|
||||||
const assessment = document.querySelector('input[name="assessment-' + index + '"]:checked').value;
|
|
||||||
const newAltText = document.querySelector('.new-alt-text[data-index="' + index + '"]').value;
|
|
||||||
|
|
||||||
|
const assessmentInput = document.querySelector('input[name="assessment-' + index + '"]:checked');
|
||||||
|
const assessment = assessmentInput ? assessmentInput.value : null;
|
||||||
|
|
||||||
|
if (!assessment) {
|
||||||
|
alert('Please provide an assessment (1-5) for all selected images. Missing assessment for image index: ' + (parseInt(index) + 1));
|
||||||
|
hasError = true;
|
||||||
|
return; // exits forEach callback only
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAltText = document.querySelector('.new-alt-text[data-index="' + index + '"]').value;
|
||||||
|
|
||||||
selectedData.push({
|
selectedData.push({
|
||||||
image_index: index,
|
image_index: index,
|
||||||
image_url: imageUrl,
|
image_url: imageUrl,
|
||||||
|
|
@ -881,56 +1016,82 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
new_alt_text: newAltText
|
new_alt_text: newAltText
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return [url_input,JSON.stringify(selectedData)];
|
if (hasError) return [url_input, JSON.stringify([])]; // now actually exits outer function
|
||||||
|
|
||||||
|
return [url_input, JSON.stringify(selectedData)];
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
).then(
|
).then(
|
||||||
fn=render_alttext_form,
|
fn=render_alttext_form,
|
||||||
inputs=[alttext_info_state],
|
inputs=[alttext_info_state],
|
||||||
outputs=[alttext_form,alttext_popup_html_state],
|
outputs=[alttext_form,alttext_popup_html_state],
|
||||||
|
).then(fn=maybe_open_modal,#open modal
|
||||||
|
inputs=[make_alttext_llm_assessment_api_call_output_state], # gr.State that holds your condition
|
||||||
|
outputs=[alttext_modal]
|
||||||
).then(
|
).then(
|
||||||
fn=lambda html: (gr.update(value=html), Modal(visible=True)),
|
fn=lambda html: (gr.update(value=html)),
|
||||||
inputs=[alttext_popup_html_state],
|
inputs=[alttext_popup_html_state],
|
||||||
outputs=[alttext_modal_content, alttext_modal], # ← populate + open modal
|
outputs=[alttext_modal_content], # ← populate modal
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
close_modal_btn.click( #the close button now save
|
close_modal_btn.click( #the close button now save
|
||||||
fn=process_dataframe,
|
fn=process_dataframe,
|
||||||
inputs=[db_path_state, url_input, alttext_form_data, user_state,llm_response_output],
|
inputs=[db_path_state, url_input, alttext_form_data, user_state,llm_response_output],
|
||||||
outputs=[image_info_output],
|
outputs=[image_info_output,process_dataframe_output_state],
|
||||||
js="""
|
js="""
|
||||||
(db_path_state, url_input, alttext_form_html, user_state, llm_response_output) => {
|
(db_path_state, url_input, alttext_form_data, user_state, llm_response_output) => {
|
||||||
|
|
||||||
const rows = document.querySelectorAll('.alttext-row');
|
const rows = document.querySelectorAll('.alttext-row');
|
||||||
const selectedData = [];
|
const selectedData = [];
|
||||||
|
|
||||||
|
// Check all rows first — if any select is unset (0 or empty), return empty list
|
||||||
|
const hasUnset = Array.from(rows).some(row => {
|
||||||
|
const user_llm1_ass = parseInt(row.querySelector('.user_llm1_ass')?.value || '0');
|
||||||
|
const user_llm2_ass = parseInt(row.querySelector('.user_llm2_ass')?.value || '0');
|
||||||
|
return user_llm1_ass === 0 || user_llm2_ass === 0;
|
||||||
|
});
|
||||||
|
console.log("hasUnset:",hasUnset)
|
||||||
|
|
||||||
|
if (hasUnset)
|
||||||
|
{alert('Please provide an assessment (1-5) for all selected images for both models');
|
||||||
|
return [db_path_state, url_input, [], user_state, llm_response_output];}
|
||||||
|
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
const imgNum = row.querySelector('.img-num')?.innerText || '';
|
const imgNum = row.querySelector('.img-num')?.innerText || '';
|
||||||
const origAlt = row.querySelector('.orig-alt')?.innerText || '';
|
const origAlt = row.querySelector('.orig-alt')?.innerText || '';
|
||||||
const userAssessment = row.querySelector('.user-assessment')?.innerText || '3';
|
const userAssessment = row.querySelector('.user-assessment')?.innerText || '3';
|
||||||
const userProposed = row.querySelector('.user-proposed')?.innerText || '';
|
const userProposed = row.querySelector('.user-proposed')?.innerText || '';
|
||||||
const user_llm1_ass = row.querySelector('.user_llm1_ass')?.value || '3';
|
const user_llm1_ass = row.querySelector('.user_llm1_ass')?.value || '0';
|
||||||
const user_llm2_ass = row.querySelector('.user_llm2_ass')?.value || '3';
|
const user_llm2_ass = row.querySelector('.user_llm2_ass')?.value || '0';
|
||||||
|
|
||||||
selectedData.push({
|
selectedData.push({
|
||||||
"Image #": imgNum,
|
"Image #": imgNum,
|
||||||
"Original Alt Text": origAlt,
|
"Original Alt Text": origAlt,
|
||||||
"User Assessment": parseInt(userAssessment)||3,
|
"User Assessment": parseInt(userAssessment) || 3,
|
||||||
"User Proposed Alt Text": userProposed,
|
"User Proposed Alt Text": userProposed,
|
||||||
"User Assessment for LLM Proposal 1": parseInt(user_llm1_ass),
|
"User Assessment for LLM Proposal 1": parseInt(user_llm1_ass),
|
||||||
"User Assessment for LLM Proposal 2": parseInt(user_llm2_ass)
|
"User Assessment for LLM Proposal 2": parseInt(user_llm2_ass)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
console.log("selectedData:",selectedData);
|
||||||
return [db_path_state, url_input, selectedData, user_state, llm_response_output];
|
return [db_path_state, url_input, selectedData, user_state, llm_response_output];
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
).then( # Close button dismisses the modal
|
).then( # Close button dismisses the modal
|
||||||
fn=lambda: Modal(visible=False),
|
fn=maybe_close_modal,
|
||||||
inputs=[],
|
inputs=[process_dataframe_output_state], # gr.State that holds your condition
|
||||||
outputs=[alttext_modal],
|
outputs=[alttext_modal],
|
||||||
js="""
|
js="""
|
||||||
async () => {
|
async (is_valid) => {
|
||||||
|
console.log("is_valid animation:",is_valid) // da sistemare, animazione non gestita
|
||||||
|
if (!is_valid) {
|
||||||
|
console.log("skip animation");
|
||||||
|
return;} // Skip animation if not closing
|
||||||
|
|
||||||
const btn = document.querySelector('.close-modal-btn');
|
const btn = document.querySelector('.close-modal-btn');
|
||||||
|
|
||||||
// Change button text
|
// Change button text
|
||||||
|
|
@ -945,7 +1106,10 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
await new Promise(resolve => setTimeout(resolve, 400));
|
await new Promise(resolve => setTimeout(resolve, 400));
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
)
|
|
||||||
|
|
||||||
|
).then(# refresh the user assignment display after saving the assessment
|
||||||
|
fn=display_user_assignment, inputs=[db_path_state,user_state], outputs=[user_assignment_current_status]).then(fn=render_user_assessmnet_status_table, inputs=[user_assignment_current_status], outputs=[user_assignment_status]) # sync the user assignment display after saving the assessment
|
||||||
|
|
||||||
# placed here at the end to give full contents visibility to events
|
# placed here at the end to give full contents visibility to events
|
||||||
# Event handlers
|
# Event handlers
|
||||||
|
|
@ -961,7 +1125,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
alttext_assessment,
|
alttext_assessment,
|
||||||
register_and_login,
|
register_and_login,
|
||||||
],
|
],
|
||||||
).then(fn=protected_content, inputs=[user_state], outputs=[content_display]).then(fn=display_user_assignment, inputs=[user_state], outputs=[user_assignment_status])
|
).then(fn=protected_content, inputs=[user_state], outputs=[content_display]).then(fn=display_user_assignment, inputs=[db_path_state,user_state], outputs=[user_assignment_current_status]).then(fn=render_user_assessmnet_status_table, inputs=[user_assignment_current_status], outputs=[user_assignment_status]) # display the user assignment after login
|
||||||
|
|
||||||
reg_btn.click(
|
reg_btn.click(
|
||||||
fn=register_user,
|
fn=register_user,
|
||||||
|
|
@ -979,7 +1143,16 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||||
protected_section,
|
protected_section,
|
||||||
alttext_assessment,
|
alttext_assessment,
|
||||||
],
|
],
|
||||||
)
|
).then(
|
||||||
|
fn=lambda: ("", "", gr.update(visible=False), gr.Button(interactive=False)),
|
||||||
|
inputs=[],
|
||||||
|
outputs=[
|
||||||
|
image_info_output,
|
||||||
|
gallery_html,
|
||||||
|
alttext_results_row,
|
||||||
|
alttext_api_call_btn,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue