File size: 6,204 Bytes
4c1ac2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1796779
 
 
4c1ac2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fe7d068
 
 
 
4c1ac2d
fe7d068
4c1ac2d
fe7d068
 
 
 
 
4c1ac2d
fe7d068
 
 
 
 
 
 
 
 
 
4c1ac2d
 
fe7d068
4c1ac2d
 
 
 
 
 
 
 
 
 
fe7d068
4c1ac2d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""Main Gradio application for Loci Similes Demo."""

from __future__ import annotations

import sys
from typing import Any

try:  # gradio is an optional dependency
    import gradio as gr
except ImportError as exc:  # pragma: no cover - import guard
    missing = getattr(exc, "name", None)
    base_msg = (
        "Optional GUI dependencies are missing. Install them via "
        "'pip install locisimiles[gui]' (Python 3.13+ also requires the "
        "audioop-lts backport) to use the Gradio interface."
    )
    if missing and missing != "gradio":
        raise ImportError(f"{base_msg} (missing package: {missing})") from exc
    raise ImportError(base_msg) from exc

from upload_stage import build_upload_stage, setup_upload_handlers
from config_stage import build_config_stage, setup_config_handlers
from results_stage import build_results_stage, setup_results_handlers, update_results_display, _export_results_to_csv


def build_interface() -> gr.Blocks:
    """Create the main Gradio Blocks interface."""
    # Custom theme matching the presentation color scheme
    # Colors extracted from the slide: warm beige background, blue accents, brown text
    theme = gr.themes.Soft(
        primary_hue="blue",      # Blue from the numbered circles (#6B9BD1 area)
        secondary_hue="orange",  # Warm accent color
        neutral_hue="stone",     # Warm neutral matching the beige/cream background
    ).set(
        # Primary buttons - blue accent color
        button_primary_background_fill="#6B9BD1",
        button_primary_background_fill_hover="#5A8BC0",
        button_primary_text_color="white",
        # Body styling - warm cream/beige background
        body_background_fill="#F5F3EF",
        body_text_color="#5B4636",
        # Blocks/panels - slightly lighter cream
        block_background_fill="white",
        block_border_color="#E5E3DF",
        # Input elements
        input_background_fill="white",
        input_border_color="#D4D2CE",
    )
    
    with gr.Blocks(title="Loci Similes Demo", theme=theme) as demo:
        # State to store pipeline results and files
        results_state = gr.State(value=None)
        query_doc_state = gr.State(value=None)
        query_file_state = gr.State(value=None)
        source_file_state = gr.State(value=None)
        
        gr.Markdown("# Loci Similes - Intertextuality Detection")
        gr.Markdown(
            "Find intertextual references in Latin documents using a two-stage pipeline with pre-trained language models. "
            "The first stage uses embedding similarity to quickly retrieve candidate passages from thousands of text segments. "
            "The second stage applies a classification model to accurately identify true intertextual references among the candidates. "
            "This approach balances computational efficiency with high-quality results. "
            "*Built with the [LociSimiles Python package](https://pypi.org/project/locisimiles/).*"
        )
        
        with gr.Walkthrough(selected=0) as walkthrough:
            # ========== Build All Stages ==========
            upload_step, upload_components = build_upload_stage()
            config_step, config_components = build_config_stage()
            results_step, results_components = build_results_stage()
        
        # ========== Setup Event Handlers ==========
        
        # File states for passing between stages
        file_states = {
            "query_file_state": query_file_state,
            "source_file_state": source_file_state,
        }
        
        # Pipeline states for passing between stages
        pipeline_states = {
            "results_state": results_state,
            "query_doc_state": query_doc_state,
        }
        
        # Setup handlers for each stage
        setup_upload_handlers(upload_components, file_states)
        setup_config_handlers(config_components, file_states, pipeline_states, walkthrough, results_components)
        setup_results_handlers(results_components, walkthrough)
        
        # Navigation: Step 1 β†’ Step 2
        upload_components["next_btn"].click(
            fn=lambda: gr.Walkthrough(selected=1),
            outputs=walkthrough,
        )
        
        # Download results
        results_components["download_btn"].click(
            fn=lambda qs, md, t: _export_results_to_csv(qs, md, t) if qs and md else None,
            inputs=[
                results_components["query_segments_state"],
                results_components["matches_dict_state"],
                config_components["threshold"],
            ],
            outputs=results_components["download_btn"],
        )
        
    return demo


def launch(**kwargs: Any) -> None:
    """Launch the Gradio app."""
    # Print startup banner
    print("\n" + "="*60)
    print("πŸš€ Starting Loci Similes Web Interface...")
    print("="*60)
    print("\nπŸ“¦ Building interface components...")
    
    demo = build_interface()
    
    print("βœ… Interface built successfully!")
    print("\n🌐 Starting web server...")
    print("-"*60)
    
    kwargs.setdefault("show_api", False)
    kwargs.setdefault("inbrowser", False)
    kwargs.setdefault("quiet", False)  # Changed to False to show URL
    
    try:
        demo.launch(share=False, **kwargs)
    except ValueError as exc:
        msg = str(exc)
        if "shareable link must be created" in msg:
            print(
                "\n⚠️  Unable to open the Gradio UI because localhost is blocked "
                "in this environment. Exiting without starting the server.",
                file=sys.stderr,
            )
            return
        raise


def main() -> None:
    """Entry point for the ``locisimiles-gui`` console script."""
    print("\n" + "="*60)
    print("  LOCI SIMILES - Intertextuality Detection Web Interface")
    print("="*60)
    print("\nπŸ“š A tool for finding intertextual links in Latin literature")
    print("   using pre-trained language models.\n")
    
    launch()
    
    print("\n" + "="*60)
    print("πŸ‘‹ Server stopped. Thank you for using Loci Similes!")
    print("="*60 + "\n")


if __name__ == "__main__":  # pragma: no cover
    main()