File size: 4,941 Bytes
5ed9749
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Standard Library Imports
from typing import Tuple, Union

# Third-Party Library Imports
import gradio as gr

# Local Application Imports
from src.common import Config, logger
from src.core import TTSService, VotingService
from src.database import AsyncDBSessionMaker

from .components import Arena, Leaderboard


class Frontend:
    """
    Main frontend class orchestrating the Gradio UI application.

    Initializes and manages the Arena and Leaderboard components, builds the overall UI structure (Tabs, HTML),
    and handles top-level events like tab selection.
    """
    def __init__(self, config: Config, db_session_maker: AsyncDBSessionMaker):
        """
        Initializes the Frontend application controller.

        Args:
            config: The application configuration object.
            db_session_maker: An asynchronous database session factory.
        """
        self.config = config

        # Instantiate services
        self.tts_service: TTSService = TTSService(config)
        self.voting_service: VotingService = VotingService(db_session_maker)
        logger.debug("Frontend initialized with TTSService and VotingService.")

        # Initialize components with dependencies
        self.arena = Arena(config, self.tts_service, self.voting_service)
        self.leaderboard = Leaderboard(self.voting_service)
        logger.debug("Frontend initialized with Arena and Leaderboard components.")

    async def _handle_tab_select(self, evt: gr.SelectData) -> Tuple[
        Union[dict, gr.skip],
        Union[dict, gr.skip],
        Union[dict, gr.skip],
    ]:
        """
        Handles tab selection events. Refreshes leaderboard if its tab is selected.

        Args:
            evt: Gradio SelectData event, containing the selected tab's value (label).

        Returns:
            A tuple of Gradio update dictionaries for the leaderboard tables if the Leaderboard tab was selected
            and data needed refreshing, otherwise a tuple of gr.skip() objects.
        """
        selected_tab = evt.value
        if selected_tab == "Leaderboard":
            # Refresh leaderboard, but don't force it (allow cache/throttle)
            return await self.leaderboard.refresh_leaderboard(force=False)
        # Return skip updates for other tabs
        return gr.skip(), gr.skip(), gr.skip()

    async def build_gradio_interface(self) -> gr.Blocks:
        """
        Builds and configures the complete Gradio Blocks UI.

        Pre-loads initial leaderboard data, defines layout (HTML, Tabs), integrates Arena and Leaderboard sections,
        and sets up tab selection handler.

        Returns:
            The fully constructed Gradio Blocks application instance.
        """
        logger.info("Building Gradio interface...")

        with gr.Blocks(title="Expressive TTS Arena", css_paths="static/css/styles.css") as demo:
            # --- Header HTML ---
            gr.HTML(
                value="""
                <div class="title-container">
                    <h1>Expressive TTS Arena</h1>
                    <div class="social-links">
                        <a
                            href="https://discord.com/invite/humeai"
                            target="_blank"
                            id="discord-link"
                            title="Join our Discord"
                            aria-label="Join our Discord server"
                        ></a>
                        <a
                            href="https://github.com/HumeAI/expressive-tts-arena"
                            target="_blank"
                            id="github-link"
                            title="View on GitHub"
                            aria-label="View project on GitHub"
                        ></a>
                    </div>
                </div>
                <div class="excerpt-container">
                    <p>
                        Join the community in evaluating text-to-speech models, and vote for the AI voice that best
                        captures the emotion, nuance, and expressiveness of human speech.
                    </p>
                </div>
                """
            )

            # --- Tabs ---
            with gr.Tabs() as tabs:
                with gr.TabItem("Arena"):
                    self.arena.build_arena_section()
                with gr.TabItem("Leaderboard"):
                    (
                        leaderboard_table,
                        battle_counts_table,
                        win_rates_table
                    ) = await self.leaderboard.build_leaderboard_section()

            # --- Top-level Event Handlers ---
            tabs.select(
                fn=self._handle_tab_select,
                inputs=[],
                outputs=[leaderboard_table, battle_counts_table, win_rates_table],
            )

        logger.debug("Gradio interface built successfully")
        return demo