new UI with multiple models
This commit is contained in:
parent
3d0936448b
commit
eca78a5ae2
|
|
@ -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
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
gradio==5.49.1
|
||||
pandas==2.3.3
|
||||
python-dotenv==1.2.1
|
||||
requests==2.32.5
|
||||
requests==2.32.5
|
||||
gradio-modal==0.0.4
|
||||
|
|
@ -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 = """
|
||||
<style>
|
||||
.alttext-table { width:100%; border-collapse: collapse; }
|
||||
.alttext-table th, .alttext-table td { border:1px solid #ddd; padding:8px; }
|
||||
.alttext-table th { background:#f5f5f5; }
|
||||
.alttext-row td { vertical-align: top; }
|
||||
.llm-select { width:auto; }
|
||||
</style>
|
||||
<table class="alttext-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Image #</th>
|
||||
<th>Original Alt Text</th>
|
||||
<th>User Assessment</th>
|
||||
<th>User Proposed Alt Text</th>
|
||||
<th>LLM Assessment 1</th>
|
||||
<th>LLM Proposed Alt Text 1</th>
|
||||
<th>User Assessment for LLM Proposal 1</th>
|
||||
<th>LLM Assessment 2</th>
|
||||
<th>LLM Proposed Alt Text 2</th>
|
||||
<th>User Assessment for LLM Proposal 2</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<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", 3)
|
||||
user_llm2_ass = row.get("User Assessment for LLM Proposal 2", 3)
|
||||
|
||||
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 -------#
|
||||
|
||||
|
||||
# Create Gradio interface
|
||||
with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||
|
||||
|
||||
|
||||
gr.HTML("""
|
||||
<style>
|
||||
input[type=radio]:checked {
|
||||
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e") !important;
|
||||
border-color: black !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
""")
|
||||
|
||||
|
||||
llm_response_output = gr.State()
|
||||
alttext_popup_html_state = gr.State("")
|
||||
|
||||
with Modal(visible=False, allow_user_close=False) as alttext_modal:
|
||||
gr.Markdown("## Alt Text LLMs Assessment Results")
|
||||
gr.Markdown("To assess the LLMs outputs, use the dropdowns to indicate how much you agree with the LLM proposed alt text.")
|
||||
alttext_modal_content = gr.HTML("")
|
||||
close_modal_btn = gr.Button("Save Your Assessment", variant="secondary",elem_classes=["close-modal-btn"])
|
||||
|
||||
env_path = find_dotenv(filename=".env")
|
||||
if env_path == "":
|
||||
print("env path not found: service starting with the default params values")
|
||||
|
|
@ -539,6 +679,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
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)
|
||||
|
||||
|
|
@ -556,8 +697,9 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
type="password",
|
||||
placeholder="Confirm your password",
|
||||
)
|
||||
|
||||
reg_btn = gr.Button("Register", variant="primary")
|
||||
reg_msg = gr.Textbox(label="Registration Status", interactive=False)
|
||||
reg_msg = gr.Textbox(label="Registration Status", interactive=True)
|
||||
|
||||
with gr.Column(visible=False) as protected_section:
|
||||
|
||||
|
|
@ -598,7 +740,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
"Extract Images & Alt Texts", variant="primary"
|
||||
)
|
||||
alttext_api_call_btn = gr.Button(
|
||||
"Start LLM Assessment",
|
||||
"Start LLMs Assessment",
|
||||
variant="secondary",
|
||||
interactive=False,
|
||||
)
|
||||
|
|
@ -608,41 +750,16 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
|
||||
with gr.Row(visible=False) as alttext_results_row:
|
||||
|
||||
# Use DataFrame for tabular output
|
||||
alttext_info_output = gr.DataFrame(
|
||||
headers=[
|
||||
"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",
|
||||
],
|
||||
label="LLM Assessment Results",
|
||||
wrap=True, # Wrap text in cells
|
||||
interactive=True,
|
||||
scale=7,
|
||||
)
|
||||
with gr.Column():
|
||||
save_user_assessment_btn = gr.Button(
|
||||
"Save Your Assessment",
|
||||
variant="secondary",
|
||||
interactive=True,
|
||||
scale=1,
|
||||
)
|
||||
gr.Markdown(
|
||||
"ℹ Info: to assess the LLM output, only the values for the 'User Assessment for LLM Proposal' column need to be changed."
|
||||
)
|
||||
|
||||
llm_response_output=gr.JSON()
|
||||
# Store the DataFrame in state and render a clear HTML form for user edits
|
||||
alttext_info_state = gr.State()
|
||||
alttext_form = gr.HTML(label="Assessment Form")
|
||||
alttext_form_data = gr.JSON(visible=False)
|
||||
|
||||
|
||||
with gr.Row():
|
||||
|
||||
gallery_html = gr.HTML(label="Image Gallery")
|
||||
|
||||
|
||||
|
||||
image_extraction_api_call_btn.click(
|
||||
fn=lambda: ("", "", gr.update(visible=False), gr.Button(interactive=False)),
|
||||
|
|
@ -663,8 +780,6 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
outputs=[alttext_api_call_btn],
|
||||
)
|
||||
|
||||
# Process selected images with JavaScript
|
||||
|
||||
alttext_api_call_btn.click(
|
||||
fn=make_alttext_llm_assessment_api_call,
|
||||
inputs=[
|
||||
|
|
@ -674,7 +789,7 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
wcag_rest_server_url_state,
|
||||
user_state,
|
||||
],
|
||||
outputs=[image_info_output, alttext_info_output,llm_response_output],
|
||||
outputs=[image_info_output, alttext_info_state, llm_response_output],
|
||||
js="""
|
||||
(url_input,gallery_html) => {
|
||||
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
||||
|
|
@ -693,7 +808,6 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
const imageUrl = checkbox.dataset.imgurl;
|
||||
const originalAlt = document.querySelector('.original-alt[data-index="' + index + '"]').value;
|
||||
const assessment = document.querySelector('input[name="assessment-' + index + '"]:checked').value;
|
||||
console.log("assessment:",assessment)
|
||||
const newAltText = document.querySelector('.new-alt-text[data-index="' + index + '"]').value;
|
||||
|
||||
selectedData.push({
|
||||
|
|
@ -709,15 +823,65 @@ with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
|||
}
|
||||
""",
|
||||
).then(
|
||||
fn=lambda: gr.update(visible=True),
|
||||
inputs=[],
|
||||
outputs=[alttext_results_row],
|
||||
fn=render_alttext_form,
|
||||
inputs=[alttext_info_state],
|
||||
outputs=[alttext_form,alttext_popup_html_state],
|
||||
).then(
|
||||
fn=lambda html: (gr.update(value=html), Modal(visible=True)),
|
||||
inputs=[alttext_popup_html_state],
|
||||
outputs=[alttext_modal_content, alttext_modal], # ← populate + open modal
|
||||
)
|
||||
|
||||
|
||||
save_user_assessment_btn.click(
|
||||
|
||||
close_modal_btn.click( #the close button now save
|
||||
fn=process_dataframe,
|
||||
inputs=[db_path_state, url_input, alttext_info_output, user_state,llm_response_output],
|
||||
inputs=[db_path_state, url_input, alttext_form_data, user_state,llm_response_output],
|
||||
outputs=[image_info_output],
|
||||
js="""
|
||||
(db_path_state, url_input, alttext_form_html, user_state, llm_response_output) => {
|
||||
const rows = document.querySelectorAll('.alttext-row');
|
||||
const selectedData = [];
|
||||
rows.forEach(row => {
|
||||
const imgNum = row.querySelector('.img-num')?.innerText || '';
|
||||
const origAlt = row.querySelector('.orig-alt')?.innerText || '';
|
||||
const userAssessment = row.querySelector('.user-assessment')?.innerText || '3';
|
||||
const userProposed = row.querySelector('.user-proposed')?.innerText || '';
|
||||
const user_llm1_ass = row.querySelector('.user_llm1_ass')?.value || '3';
|
||||
const user_llm2_ass = row.querySelector('.user_llm2_ass')?.value || '3';
|
||||
|
||||
selectedData.push({
|
||||
"Image #": imgNum,
|
||||
"Original Alt Text": origAlt,
|
||||
"User Assessment": parseInt(userAssessment)||3,
|
||||
"User Proposed Alt Text": userProposed,
|
||||
"User Assessment for LLM Proposal 1": parseInt(user_llm1_ass),
|
||||
"User Assessment for LLM Proposal 2": parseInt(user_llm2_ass)
|
||||
});
|
||||
});
|
||||
return [db_path_state, url_input, selectedData, user_state, llm_response_output];
|
||||
}
|
||||
""",
|
||||
).then( # Close button dismisses the modal
|
||||
fn=lambda: Modal(visible=False),
|
||||
inputs=[],
|
||||
outputs=[alttext_modal],
|
||||
js="""
|
||||
async () => {
|
||||
const btn = document.querySelector('.close-modal-btn');
|
||||
|
||||
// Change button text
|
||||
btn.textContent = 'Saving...';
|
||||
|
||||
// Fade out
|
||||
const modal = document.querySelector('.modal-container');
|
||||
modal.style.transition = 'opacity 0.4s ease';
|
||||
modal.style.opacity = '0';
|
||||
|
||||
// Wait for fade
|
||||
await new Promise(resolve => setTimeout(resolve, 400));
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
# placed here at the end to give full contents visibility to events
|
||||
|
|
|
|||
|
|
@ -0,0 +1,686 @@
|
|||
#### To launch the script
|
||||
# gradio wcag_validator_ui.py
|
||||
# python wcag_validator_ui.py
|
||||
# this is the UI version for the 12_2025 student test. The rest Server should work on single model
|
||||
|
||||
import gradio as gr
|
||||
import requests
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import pandas as pd
|
||||
|
||||
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
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
|
||||
WCAG_VALIDATOR_RESTSERVER_HEADERS = [("Content-Type", "application/json")]
|
||||
|
||||
|
||||
def process_dataframe(db_path, url, updated_df, user_state={}):
|
||||
|
||||
print("Processing dataframe to adjust columns...")
|
||||
column_rating_name = "User Assessment for LLM Proposal"
|
||||
|
||||
# Get the assessment column
|
||||
try:
|
||||
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"
|
||||
|
||||
if (updated_df[column_rating_name] < 1).any() or (
|
||||
updated_df[column_rating_name] > 5
|
||||
).any():
|
||||
return "Error: User Assessment for LLM Proposal must be between 1 and 5"
|
||||
|
||||
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)
|
||||
try:
|
||||
# insert after everything to keep datetime aligned
|
||||
db_persistence_insert(
|
||||
connection_db=connection_db,
|
||||
insert_type="wcag_user_llm_alttext_assessments",
|
||||
page_url=url,
|
||||
user=json_user_str,
|
||||
llm_model="",
|
||||
json_in_str=dataframe_json, # to improve
|
||||
json_out_str="done via UI",
|
||||
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 "User assessment saved successfully!"
|
||||
|
||||
|
||||
def load_images_from_json(json_input):
|
||||
"""Extract URLs and alt text from JSON and create HTML gallery"""
|
||||
try:
|
||||
data = json_input
|
||||
|
||||
if "images" not in data or not data["images"]:
|
||||
return "No images found in JSON", ""
|
||||
|
||||
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 = """
|
||||
<style>
|
||||
.image-gallery {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
.image-card {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
}
|
||||
.image-card:has(input[type="checkbox"]:checked) {
|
||||
border-color: #2196F3;
|
||||
background: #a7c1c1;
|
||||
}
|
||||
.image-card img {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
object-fit: scale-down;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.image-info {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
.image-checkbox {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: #2196F3;
|
||||
}
|
||||
.alt-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.assessment-panel {
|
||||
display: none;
|
||||
margin-top: 15px;
|
||||
padding: 10px;
|
||||
background: #7896b9;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #2196F3;
|
||||
}
|
||||
.assessment-panel.visible {
|
||||
display: block;
|
||||
}
|
||||
.form-group {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.radio-container {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.radio-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.radio-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
}
|
||||
</style>
|
||||
<div class="image-gallery">
|
||||
"""
|
||||
|
||||
for idx, img_data in enumerate(images):
|
||||
url = img_data.get("url", "")
|
||||
alt_text = img_data.get("alt_text", "No description")
|
||||
|
||||
html += f"""
|
||||
<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'">
|
||||
<div class="image-info">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" class="image-checkbox" data-imgurl="{url}" data-index="{idx}"
|
||||
onchange="
|
||||
const panel = document.getElementById('panel-{idx}');
|
||||
const checkedCount = document.querySelectorAll('.image-checkbox:checked').length;
|
||||
if (this.checked) {{
|
||||
if (checkedCount > 3) {{
|
||||
this.checked = false;
|
||||
alert('Maximum 3 images can be selected!');
|
||||
return;
|
||||
}}
|
||||
panel.classList.add('visible');
|
||||
}} else {{
|
||||
panel.classList.remove('visible');
|
||||
}}
|
||||
">
|
||||
Select #{idx + 1}
|
||||
</label>
|
||||
<div class="alt-text">Current alt_text: {alt_text}</div>
|
||||
|
||||
<div id="panel-{idx}" class="assessment-panel">
|
||||
<div class="form-group">
|
||||
<label>Rate current alt-text:</label>
|
||||
<div class="radio-container">
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="assessment-{idx}" value="1" data-index="{idx}">
|
||||
<span class="radio-label">1</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="assessment-{idx}" value="2" data-index="{idx}">
|
||||
<span class="radio-label">2</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="assessment-{idx}" value="3" data-index="{idx}" checked>
|
||||
<span class="radio-label">3</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="assessment-{idx}" value="4" data-index="{idx}">
|
||||
<span class="radio-label">4</span>
|
||||
</label>
|
||||
<label class="radio-option">
|
||||
<input type="radio" name="assessment-{idx}" value="5" data-index="{idx}">
|
||||
<span class="radio-label">5</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>New alt-text:</label>
|
||||
<textarea class="new-alt-text" data-index="{idx}" rows="3" placeholder="Enter improved alt-text...">{alt_text}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" class="original-alt" data-index="{idx}" value="{alt_text}" />
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
# info_text += f"✓ Image {idx+1} alt_text: {alt_text}\n"
|
||||
html += "</div>"
|
||||
|
||||
return info_text, html
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Error: Invalid JSON format - {str(e)}", ""
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}", ""
|
||||
|
||||
|
||||
def load_llm_assessment_from_json(json_input):
|
||||
|
||||
try:
|
||||
# Parse JSON input
|
||||
data = json_input
|
||||
|
||||
if "mllm_validations" not in data or not data["mllm_validations"]:
|
||||
print("no mllm_validations found")
|
||||
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
|
||||
):
|
||||
|
||||
original_alt_text_assessment = img_data["mllm_response"].get(
|
||||
"original_alt_text_assessment", "No description"
|
||||
)
|
||||
new_alt_text = img_data["mllm_response"].get(
|
||||
"new_alt_text", "No description"
|
||||
)
|
||||
alt_text_original = img_data.get("alt_text", "No alt_text provided")
|
||||
|
||||
data_frame.append(
|
||||
{
|
||||
"Original Alt Text": alt_text_original,
|
||||
"LLM Assessment": original_alt_text_assessment,
|
||||
"LLM Proposed Alt Text": new_alt_text,
|
||||
}
|
||||
)
|
||||
|
||||
df = pd.DataFrame(data_frame)
|
||||
return df
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return f"Error: Invalid JSON format - {str(e)}", []
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}", []
|
||||
|
||||
|
||||
def make_alttext_llm_assessment_api_call(
|
||||
url,
|
||||
selected_images_json=[],
|
||||
db_path=None,
|
||||
wcag_rest_server_url="http://localhost:8000",
|
||||
user_state={},
|
||||
number_of_images=30,
|
||||
):
|
||||
|
||||
print(
|
||||
f"Making API call for llm assessment for {url} to {wcag_rest_server_url}/wcag_alttext_validation"
|
||||
)
|
||||
selected_images = json.loads(selected_images_json) if selected_images_json else []
|
||||
# print("selected_images:", selected_images)
|
||||
|
||||
if not selected_images or len(selected_images) == 0:
|
||||
info_text = "No images selected"
|
||||
print(info_text)
|
||||
return "LLM assessment not started", 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 = []
|
||||
user_assessments_llm_proposal = []
|
||||
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)
|
||||
user_assessments_llm_proposal.append(3) # default value for now
|
||||
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(
|
||||
data={
|
||||
"page_url": url,
|
||||
"number_of_images": number_of_images,
|
||||
"context_levels": 5,
|
||||
"pixel_distance_threshold": 200,
|
||||
"save_images": "True",
|
||||
"save_elaboration": "True",
|
||||
"specific_images_urls": selected_urls,
|
||||
},
|
||||
url=wcag_rest_server_url + "/wcag_alttext_validation",
|
||||
headers=WCAG_VALIDATOR_RESTSERVER_HEADERS,
|
||||
)
|
||||
# return response
|
||||
info_dataframe = load_llm_assessment_from_json(response)
|
||||
|
||||
# add the UI ids and other fields to to api response
|
||||
info_dataframe.insert(
|
||||
0, "Image #", selected_image_id
|
||||
) # add the UI ids from to api response
|
||||
info_dataframe.insert(2, "User Assessment", user_assessments)
|
||||
|
||||
info_dataframe.insert(3, "User Proposed Alt Text", user_new_alt_texts)
|
||||
info_dataframe["User Assessment for LLM Proposal"] = (
|
||||
user_assessments_llm_proposal
|
||||
)
|
||||
|
||||
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 "LLM assessment completed", info_dataframe
|
||||
|
||||
|
||||
def make_image_extraction_api_call(
|
||||
url,
|
||||
number_of_images=30,
|
||||
wcag_rest_server_url="http://localhost:8000",
|
||||
):
|
||||
print(
|
||||
f"Making API call for image_extraction for {url} to {wcag_rest_server_url}/extract_images"
|
||||
)
|
||||
try:
|
||||
|
||||
response = call_API_urlibrequest(
|
||||
data={
|
||||
"page_url": url,
|
||||
"number_of_images": number_of_images,
|
||||
},
|
||||
url=wcag_rest_server_url + "/extract_images",
|
||||
headers=WCAG_VALIDATOR_RESTSERVER_HEADERS,
|
||||
)
|
||||
# return response
|
||||
info_text, gallery_images = load_images_from_json(response)
|
||||
|
||||
return info_text, gallery_images
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
# ------- Gradio Interface -------#
|
||||
|
||||
|
||||
# Create Gradio interface
|
||||
with gr.Blocks(theme=gr.themes.Glass(), title="WCAG AI Validator") as demo:
|
||||
|
||||
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)
|
||||
wcag_rest_server_url = return_from_env_valid(
|
||||
"WCAG_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("wcag_rest_server_url:", wcag_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")
|
||||
|
||||
# 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\
|
||||
wcag_rest_server_url_state = gr.State(value=wcag_rest_server_url)
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column():
|
||||
url_input = gr.Dropdown(
|
||||
url_list,
|
||||
value=url_list[0],
|
||||
multiselect=False,
|
||||
label="Select an URL",
|
||||
info="Select an URL to load in iframe",
|
||||
)
|
||||
images_number = gr.Slider(
|
||||
5,
|
||||
100,
|
||||
value=50,
|
||||
step=5,
|
||||
label="Max number of images to retrieve",
|
||||
visible=False,
|
||||
)
|
||||
with gr.Column():
|
||||
|
||||
image_extraction_api_call_btn = gr.Button(
|
||||
"Extract Images & Alt Texts", variant="primary"
|
||||
)
|
||||
alttext_api_call_btn = gr.Button(
|
||||
"Start LLM Assessment",
|
||||
variant="secondary",
|
||||
interactive=False,
|
||||
)
|
||||
image_info_output = gr.Textbox(
|
||||
label="Activity tracking", lines=1
|
||||
)
|
||||
|
||||
with gr.Row(visible=False) as alttext_results_row:
|
||||
|
||||
# Use DataFrame for tabular output
|
||||
alttext_info_output = gr.DataFrame(
|
||||
headers=[
|
||||
"Image #",
|
||||
"Original Alt Text",
|
||||
"User Assessment",
|
||||
"User Proposed Alt Text",
|
||||
"LLM Assessment",
|
||||
"LLM Proposed Alt Text",
|
||||
"User Assessment for LLM Proposal",
|
||||
],
|
||||
label="LLM Assessment Results",
|
||||
wrap=True, # Wrap text in cells
|
||||
interactive=True,
|
||||
scale=7,
|
||||
)
|
||||
with gr.Column():
|
||||
save_user_assessment_btn = gr.Button(
|
||||
"Save Your Assessment",
|
||||
variant="secondary",
|
||||
interactive=True,
|
||||
scale=1,
|
||||
)
|
||||
gr.Markdown(
|
||||
"ℹ Info: to assess the LLM output, only the values for the 'User Assessment for LLM Proposal' column need to be changed."
|
||||
)
|
||||
|
||||
with gr.Row():
|
||||
|
||||
gallery_html = gr.HTML(label="Image Gallery")
|
||||
|
||||
image_extraction_api_call_btn.click(
|
||||
fn=lambda: ("", "", gr.update(visible=False), gr.Button(interactive=False)),
|
||||
inputs=[],
|
||||
outputs=[
|
||||
image_info_output,
|
||||
gallery_html,
|
||||
alttext_results_row,
|
||||
alttext_api_call_btn,
|
||||
],
|
||||
).then(
|
||||
make_image_extraction_api_call,
|
||||
inputs=[url_input, images_number, wcag_rest_server_url_state],
|
||||
outputs=[image_info_output, gallery_html],
|
||||
).then(
|
||||
fn=lambda: gr.Button(interactive=True),
|
||||
inputs=[],
|
||||
outputs=[alttext_api_call_btn],
|
||||
)
|
||||
|
||||
# Process selected images with JavaScript
|
||||
|
||||
alttext_api_call_btn.click(
|
||||
fn=make_alttext_llm_assessment_api_call,
|
||||
inputs=[
|
||||
url_input,
|
||||
gallery_html,
|
||||
db_path_state,
|
||||
wcag_rest_server_url_state,
|
||||
user_state,
|
||||
],
|
||||
outputs=[image_info_output, alttext_info_output],
|
||||
js="""
|
||||
(url_input,gallery_html) => {
|
||||
const checkboxes = document.querySelectorAll('.image-checkbox:checked');
|
||||
if (checkboxes.length === 0) {
|
||||
alert('Please select at least one image!');
|
||||
return [url_input,JSON.stringify([])];
|
||||
}
|
||||
if (checkboxes.length > 3) {
|
||||
alert('Please select maximum 3 images!');
|
||||
return [url_input,JSON.stringify([])];
|
||||
}
|
||||
const selectedData = [];
|
||||
|
||||
checkboxes.forEach(checkbox => {
|
||||
const index = checkbox.dataset.index;
|
||||
const imageUrl = checkbox.dataset.imgurl;
|
||||
const originalAlt = document.querySelector('.original-alt[data-index="' + index + '"]').value;
|
||||
const assessment = document.querySelector('input[name="assessment-' + index + '"]:checked').value;
|
||||
console.log("assessment:",assessment)
|
||||
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),
|
||||
new_alt_text: newAltText
|
||||
});
|
||||
});
|
||||
|
||||
return [url_input,JSON.stringify(selectedData)];
|
||||
}
|
||||
""",
|
||||
).then(
|
||||
fn=lambda: gr.update(visible=True),
|
||||
inputs=[],
|
||||
outputs=[alttext_results_row],
|
||||
)
|
||||
|
||||
save_user_assessment_btn.click(
|
||||
fn=process_dataframe,
|
||||
inputs=[db_path_state, url_input, alttext_info_output, user_state],
|
||||
outputs=[image_info_output],
|
||||
)
|
||||
|
||||
# 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__":
|
||||
demo.launch(server_name="0.0.0.0", server_port=7860)
|
||||
|
|
@ -445,7 +445,7 @@ class MLLMManager:
|
|||
|
||||
|
||||
### Other utility functions
|
||||
def parse_mllm_alt_text_response(mllm_response):
|
||||
def parse_mllm_alt_text_response(mllm_response, model_id=""):
|
||||
"""
|
||||
Parse an MLLM response string and extract key attributes into a JSON object.
|
||||
|
||||
|
|
@ -467,6 +467,7 @@ def parse_mllm_alt_text_response(mllm_response):
|
|||
"assessment": None,
|
||||
"evaluation_result": None,
|
||||
"new_alt_text": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
|
||||
# Extract JSON content between ```json and ``` markers
|
||||
|
|
@ -482,6 +483,7 @@ def parse_mllm_alt_text_response(mllm_response):
|
|||
"assessment": None,
|
||||
"evaluation_result": None,
|
||||
"new_alt_text": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
|
||||
json_str = (
|
||||
|
|
@ -499,6 +501,7 @@ def parse_mllm_alt_text_response(mllm_response):
|
|||
"assessment": parsed_data.get("Assessment", ""),
|
||||
"evaluation_result": parsed_data.get("EvaluationResult", ""),
|
||||
"new_alt_text": parsed_data.get("New alt-text", ""),
|
||||
"mllm_model": model_id
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
@ -510,6 +513,7 @@ def parse_mllm_alt_text_response(mllm_response):
|
|||
"assessment": None,
|
||||
"evaluation_result": None,
|
||||
"new_alt_text": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error parsing MLLM response: {e}")
|
||||
|
|
@ -518,10 +522,11 @@ def parse_mllm_alt_text_response(mllm_response):
|
|||
"assessment": None,
|
||||
"evaluation_result": None,
|
||||
"new_alt_text": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
|
||||
|
||||
def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
||||
def parse_mllm_standard_response(mllm_response, extra_fields=[],model_id=""):
|
||||
|
||||
try:
|
||||
# Handle NaN or None values
|
||||
|
|
@ -530,6 +535,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": None,
|
||||
"judgment": None,
|
||||
"evaluation_result": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
# Extract JSON content between ```json and ``` markers
|
||||
# json_match = re.search(r"```json\s*(.*?)\s*```", mllm_response, re.DOTALL)
|
||||
|
|
@ -560,6 +566,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": None,
|
||||
"judgment": None,
|
||||
"evaluation_result": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
|
||||
json_str = (
|
||||
|
|
@ -580,6 +587,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": parsed_data.get("Assessment", ""),
|
||||
"judgment": parsed_data.get("Judgment", ""),
|
||||
"evaluation_result": parsed_data.get("EvaluationResult", ""),
|
||||
"mllm_model": model_id
|
||||
}
|
||||
if extra_fields:
|
||||
for field in extra_fields:
|
||||
|
|
@ -590,6 +598,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": None,
|
||||
"judgment": None,
|
||||
"evaluation_result": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
elif isinstance(
|
||||
parsed_data, list
|
||||
|
|
@ -601,6 +610,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": item.get("Assessment", ""),
|
||||
"judgment": item.get("Judgment", ""),
|
||||
"evaluation_result": item.get("EvaluationResult", ""),
|
||||
"mllm_model": model_id
|
||||
}
|
||||
if extra_fields:
|
||||
for field in extra_fields:
|
||||
|
|
@ -611,6 +621,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": None,
|
||||
"judgment": None,
|
||||
"evaluation_result": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
result.append(item_result)
|
||||
return result
|
||||
|
|
@ -621,6 +632,7 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": None,
|
||||
"judgment": None,
|
||||
"evaluation_result": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error parsing MLLM response: {e}")
|
||||
|
|
@ -628,4 +640,5 @@ def parse_mllm_standard_response(mllm_response, extra_fields=[]):
|
|||
"assessment": None,
|
||||
"judgment": None,
|
||||
"evaluation_result": None,
|
||||
"mllm_model": model_id
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,21 +103,29 @@ class WCAGAltTextValuationRoutes:
|
|||
extract_context=True,
|
||||
)
|
||||
|
||||
|
||||
if self.mllm_settings["openai_model"] == "Both":
|
||||
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
def run_model_evaluation(endpoint, api_key, model_id, openai_model, label):
|
||||
def run_model_evaluation(
|
||||
endpoint, api_key, model_id, openai_model, label
|
||||
):
|
||||
manager = MLLMManager(endpoint, api_key, model_id)
|
||||
print(f"Using {label} model for alt text evaluation.", manager.end_point)
|
||||
print(
|
||||
f"Using {label} model for alt text evaluation.",
|
||||
manager.end_point,
|
||||
)
|
||||
logging.info("mllm_end_point:%s", endpoint)
|
||||
logging.info("mllm_model_id:%s", model_id)
|
||||
|
||||
responses = manager.make_alt_text_evaluation(images, openai_model=openai_model)
|
||||
responses = manager.make_alt_text_evaluation(
|
||||
images, openai_model=openai_model
|
||||
)
|
||||
|
||||
for i, response in enumerate(responses):
|
||||
responses[i]["mllm_response"] = parse_mllm_alt_text_response(response["mllm_response"])
|
||||
responses[i]["mllm_response"] = parse_mllm_alt_text_response(
|
||||
response["mllm_response"], model_id=model_id
|
||||
)
|
||||
|
||||
return responses
|
||||
|
||||
|
|
@ -127,25 +135,27 @@ class WCAGAltTextValuationRoutes:
|
|||
self.mllm_settings["mllm_end_point"]["model_end_point_remote"],
|
||||
self.mllm_settings["mllm_api_key"]["api_key_remote"],
|
||||
self.mllm_settings["mllm_model_id"]["model_id_remote"],
|
||||
True, "first remote"
|
||||
True,
|
||||
"first remote",
|
||||
)
|
||||
future_local = executor.submit(
|
||||
run_model_evaluation,
|
||||
self.mllm_settings["mllm_end_point"]["model_end_point_local"],
|
||||
self.mllm_settings["mllm_api_key"]["api_key_local"],
|
||||
self.mllm_settings["mllm_model_id"]["model_id_local"],
|
||||
False, "second local"
|
||||
False,
|
||||
"second local",
|
||||
)
|
||||
|
||||
mllm_responses_openai = future_openai.result()
|
||||
mllm_responses_local = future_local.result()
|
||||
mllm_responses_local = future_local.result()
|
||||
|
||||
mllm_responses_object = {
|
||||
"mllm_alttext_assessments": {
|
||||
"mllm_alttext_assessments_openai": mllm_responses_openai,
|
||||
"mllm_alttext_assessments_local": mllm_responses_local,
|
||||
}
|
||||
}
|
||||
}
|
||||
else:
|
||||
|
||||
# MLLM settings
|
||||
|
|
@ -170,7 +180,7 @@ class WCAGAltTextValuationRoutes:
|
|||
# Parse MLLM responses
|
||||
for i, response in enumerate(mllm_responses):
|
||||
parsed_resp = parse_mllm_alt_text_response(
|
||||
response["mllm_response"]
|
||||
response["mllm_response"], model_id=mllm_model_id
|
||||
)
|
||||
mllm_responses[i]["mllm_response"] = parsed_resp
|
||||
|
||||
|
|
|
|||
|
|
@ -103,8 +103,9 @@ class WCAG_g88ValuationRoutes:
|
|||
)
|
||||
|
||||
parsed_mllm_responses = parse_mllm_standard_response(
|
||||
responses["mllm_response"]
|
||||
responses["mllm_response"], model_id=model_id
|
||||
)
|
||||
|
||||
return parsed_mllm_responses
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as executor:
|
||||
|
|
@ -156,8 +157,9 @@ class WCAG_g88ValuationRoutes:
|
|||
openai_model=self.mllm_settings["openai_model"],
|
||||
)
|
||||
parsed_mllm_responses = parse_mllm_standard_response(
|
||||
mllm_responses["mllm_response"]
|
||||
mllm_responses["mllm_response"], model_id=mllm_model_id
|
||||
)
|
||||
|
||||
mllm_responses_object = {"mllm_g88_assessments": parsed_mllm_responses}
|
||||
|
||||
# common: prepare the object to return in the response
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ class WCAG_h58ValuationRoutes:
|
|||
"html",
|
||||
"detected_lang",
|
||||
"declared_lang",
|
||||
],
|
||||
],model_id=model_id
|
||||
)
|
||||
|
||||
|
||||
mllm_respones_flattened.extend(parsed_resp)
|
||||
|
||||
return mllm_respones_flattened
|
||||
|
|
@ -186,7 +186,9 @@ class WCAG_h58ValuationRoutes:
|
|||
parsed_resp = parse_mllm_standard_response(
|
||||
response["mllm_response"],
|
||||
extra_fields=["tag", "html", "detected_lang", "declared_lang"],
|
||||
model_id=mllm_model_id
|
||||
)
|
||||
|
||||
|
||||
mllm_respones_flattened.extend(parsed_resp)
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue