import gradio as gr import pandas as pd import plotly.graph_objects as go from ui.welcome_page import build_welcome_page from ui.multi_report_page import build_multi_report_page from ui.filters import ( sdg_images, gri_images, filter_sdg, filter_gri, toggle_filters, quality_task_1_gallery, quality_task_2_gallery, quality_task_3_gallery, filter_quality_1, filter_quality_2, filter_quality_3, toggle_quality_filters, climate_images, gri_climate_images, sdg13_images, gri_sdg13_images, filter_climate, filter_gri_climate, filter_sdg13, filter_gri_sdg13, toggle_climate ) from ui.gradio_functions import ( load_pdf, change_page, load_pdf_wrapper, highlight, upload_csv1, upload_csv2, run_sa_wrapper, set_task, run_task, run_selected_tasks ) def _empty_plot(): """ Returns a blank Plotly figure to use as the initial value for every gr.Plot(). Gradio (6.0.2+, including 6.14) has a confirmed bug where a gr.Plot left at its default value=None fails to render on the FIRST update that sets a real value -- it only shows up correctly starting from the second update onward. See: https://github.com/gradio-app/gradio/issues/12511 https://github.com/gradio-app/gradio/issues/10162 Giving the component a non-None placeholder value at construction time avoids that first-update failure. """ return go.Figure() def _empty_df(): """ Returns an empty DataFrame to use as the initial value for gr.Dataframe components that get populated later via .select()/.click() events. Same first-update rendering bug as gr.Plot affects gr.Dataframe when left at value=None (see issue links above) -- giving it a non-None placeholder avoids the "works on the second click, not the first" bug. """ return pd.DataFrame() def build_single_report_page(): """ Builds the original Single Report Analysis workflow (Steps 1-5), unchanged in content. This is placed inside a gr.Tab by build_interface(), rather than toggled via Column visibility -- this avoids a known Gradio rendering bug where gr.Plot / gr.Gallery / gr.Dataframe children can break (render as a blank page) after their parent Column's visibility is toggled off and back on. """ back_btn = gr.Button("⬅ Back to Home", size="sm") gr.Markdown("# UniTor ESG Insights: An Interactive Human-in-the-Loop System for Traceable Sustainability Report Analysis") gr.Image( value="assets/workflow.png", label="Human-in-the-Loop Interactive Workflow Overview", interactive=False, ) # ========================= # STAGE 1: PDF -> CSV # ========================= gr.Markdown("## Step 1: Upload PDF and Extract Data") file = gr.File(label="Upload PDF") load_btn = gr.Button("Load PDF", variant="primary") extracted_download = gr.File(label="Download Extracted CSV") upload_csv_1 = gr.File(label="(Optional) Upload Custom CSV") with gr.Row(): prev = gr.Button("⬅", variant="primary") next = gr.Button("➡", variant="primary") with gr.Row(): img = gr.Image() table = gr.Dataframe(value=_empty_df(), interactive=True) text = gr.Markdown() # ========================= # STAGE 2: SA # ========================= gr.Markdown("## Step 2: Sustainability Frameworks (GRI & SDG) Alignment (SFA) for Relevant/Irrelevant Classification Task") sa_btn = gr.Button("Run SFA", variant="primary") sa_download = gr.File(label="Download SFA Results") chart1 = gr.Plot(value=_empty_plot(), label="Distribution") upload_csv_2 = gr.File(label="(Optional) Upload Modified SFA CSV") # ========================= # CONNECTIONS # ========================= load_btn.click(load_pdf_wrapper, file, [img, table, extracted_download]) next.click(lambda: change_page(1), outputs=[img, table]) prev.click(lambda: change_page(-1), outputs=[img, table]) table.select(highlight, outputs=text) upload_csv_1.upload(upload_csv1, upload_csv_1, table) sa_btn.click( run_sa_wrapper, outputs=[sa_download, table, chart1] ) upload_csv_2.upload(upload_csv2, upload_csv_2, table) # ========================= # STAGE 3: SDG / GRI # ========================= gr.Markdown("## Step 3: SDG/GRI Classification (Topic Alignment)") task_selector_1 = gr.Dropdown( choices=[ "17 SDG Alignment", "GRI Topic Alignment" ], multiselect=True, label="Select Tasks" ) run_tasks_btn_1 = gr.Button("Run", variant="primary") chart_tasks_1 = gr.Plot(value=_empty_plot()) download_1 = gr.File(label="Download Results") with gr.Column(visible=True) as sdg_container: gr.Markdown("### SDG Filter") sdg_gallery = gr.Gallery(value=sdg_images, columns=6, allow_preview=False) sdg_result_table = gr.Dataframe(value=_empty_df(), interactive=True) sdg_gallery.select(fn=filter_sdg, outputs=[sdg_result_table, text]) with gr.Column(visible=True) as gri_container: gr.Markdown("### GRI Topic Filter") gri_gallery = gr.Gallery(value=gri_images, columns=4, allow_preview=False) gri_result_table = gr.Dataframe(value=_empty_df(), interactive=True) gri_gallery.select(fn=filter_gri, outputs=[gri_result_table, text]) task_selector_1.change( fn=toggle_filters, inputs=task_selector_1, outputs=[sdg_container, gri_container] ) run_tasks_btn_1.click( run_selected_tasks, inputs=task_selector_1, outputs=[download_1, table, chart_tasks_1] ) # ========================= # STAGE 4: DISCLOSURE QUALITY # ========================= gr.Markdown("## Step 4: Disclosure Quality Analysis") task_selector_2 = gr.Dropdown( choices=[ "Informative & Non-Informative/Vague Sustainability Text Identification", "Qualitative & Quantitative Sustainability Text Identification", "High Potential Greenwashing Detection" ], multiselect=True, label="Select Tasks" ) run_tasks_btn_2 = gr.Button("Run", variant="primary") chart_tasks_2 = gr.Plot(value=_empty_plot()) download_2 = gr.File(label="Download Results") with gr.Row(): with gr.Column(visible=True) as quality1_container: gr.Markdown("### Informative vs Non-Informative/Vague") quality1_gallery = gr.Gallery(value=quality_task_1_gallery, columns=2, allow_preview=False) with gr.Column(visible=True) as quality2_container: gr.Markdown("### Qualitative vs Quantitative") quality2_gallery = gr.Gallery(value=quality_task_2_gallery, columns=2, allow_preview=False) with gr.Column(visible=True) as quality3_container: gr.Markdown("### Greenwashing Potential") quality3_gallery = gr.Gallery(value=quality_task_3_gallery, columns=2, allow_preview=False) quality_result_table = gr.Dataframe(value=_empty_df(), interactive=True) quality1_gallery.select(fn=filter_quality_1, outputs=[quality_result_table, text]) quality2_gallery.select(fn=filter_quality_2, outputs=[quality_result_table, text]) quality3_gallery.select(fn=filter_quality_3, outputs=[quality_result_table, text]) task_selector_2.change( fn=toggle_quality_filters, inputs=task_selector_2, outputs=[quality1_container, quality2_container, quality3_container] ) run_tasks_btn_2.click( run_selected_tasks, inputs=task_selector_2, outputs=[download_2, table, chart_tasks_2] ) # ========================= # STAGE 5: CLIMATE ANALYSIS # ========================= gr.Markdown("## Step 5: More Specific Climate Analysis") task_selector_3 = gr.Dropdown( choices=[ "Climate Alignment", "GRI Climate Alignment", "GRI Climate Action (SDG13) Alignment", "Climate Action (SDG13) Alignment" ], multiselect=True, label="Select Tasks" ) run_tasks_btn_3 = gr.Button("Run", variant="primary") chart_tasks_3 = gr.Plot(value=_empty_plot()) download_3 = gr.File(label="Download Results") with gr.Row(): with gr.Column(visible=True) as climate1_container: gr.Markdown("### Climate Alignment") climate1_gallery = gr.Gallery(value=climate_images, columns=2, allow_preview=False) with gr.Column(visible=True) as climate2_container: gr.Markdown("### GRI Climate Alignment") climate2_gallery = gr.Gallery(value=gri_climate_images, columns=4, allow_preview=False) climate_table_1 = gr.Dataframe(value=_empty_df(), interactive=True) with gr.Row(): with gr.Column(visible=True) as climate3_container: gr.Markdown("### Climate Action (SDG13)") climate3_gallery = gr.Gallery(value=sdg13_images, columns=2, allow_preview=False) with gr.Column(visible=True) as climate4_container: gr.Markdown("### GRI Climate Action (SDG13)") climate4_gallery = gr.Gallery(value=gri_sdg13_images, columns=3, allow_preview=False) climate_table_2 = gr.Dataframe(value=_empty_df(), interactive=True) climate1_gallery.select(fn=filter_climate, outputs=[climate_table_1, text]) climate2_gallery.select(fn=filter_gri_climate, outputs=[climate_table_1, text]) climate3_gallery.select(fn=filter_sdg13, outputs=[climate_table_2, text]) climate4_gallery.select(fn=filter_gri_sdg13, outputs=[climate_table_2, text]) task_selector_3.change( fn=toggle_climate, inputs=task_selector_3, outputs=[climate1_container, climate2_container, climate3_container, climate4_container] ) run_tasks_btn_3.click( run_selected_tasks, inputs=task_selector_3, outputs=[download_3, table, chart_tasks_3] ) toggle_containers = [ sdg_container, gri_container, quality1_container, quality2_container, quality3_container, climate1_container, climate2_container, climate3_container, climate4_container ] return back_btn, toggle_containers def build_interface(): with gr.Blocks() as demo: with gr.Tabs(selected="welcome") as tabs: with gr.Tab("Home", id="welcome"): btn_single, btn_multi = build_welcome_page() with gr.Tab("Single Report Analysis", id="single"): single_back_btn, toggle_containers = build_single_report_page() with gr.Tab("Multi-Report Comparative Analysis", id="multi"): multi_back_btn = build_multi_report_page() # ========================= # ICON-FILTER VISIBILITY WORKAROUND # ========================= # The SDG / GRI / quality / climate icon-filter Columns are created # with visible=True (see build_single_report_page) instead of # visible=False, then immediately hidden here via demo.load(). # This works around a confirmed Gradio bug (6.0+) where a Column's # FIRST-EVER visibility change can fail to render correctly -- # manifesting as "select task N, see task N-1's icons" or needing # to reselect the last option to see it. By giving every toggle # Column one real visibility transition (True -> False) immediately # on page load, the dropdown's later True/False toggles are no # longer anyone's first transition, and update correctly every time. # See: https://github.com/gradio-app/gradio/issues/13521 # https://github.com/gradio-app/gradio/issues/12511 def _hide_all_toggle_containers(): return [gr.update(visible=False) for _ in toggle_containers] demo.load(_hide_all_toggle_containers, outputs=toggle_containers) # ========================= # PAGE NAVIGATION # ========================= # Using gr.Tabs(selected=...) to switch "pages" is the robust, # well-supported Gradio pattern for this. The earlier approach # (toggling gr.Column visibility for whole pages) triggered a # known Gradio bug where complex children -- gr.Plot, gr.Gallery, # gr.Dataframe -- can break and render a blank page once their # parent Column's visibility is toggled off and back on. Tabs # don't have this issue since each Tab's content stays mounted. btn_single.click(lambda: gr.Tabs(selected="single"), outputs=tabs) btn_multi.click(lambda: gr.Tabs(selected="multi"), outputs=tabs) single_back_btn.click(lambda: gr.Tabs(selected="welcome"), outputs=tabs) multi_back_btn.click(lambda: gr.Tabs(selected="welcome"), outputs=tabs) return demo