File size: 6,244 Bytes
2cd1b38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
"""Review UI component tree (Gradio Blocks layout)."""

from __future__ import annotations

from dataclasses import dataclass, field

import gradio as gr

from app.render import CANDIDATE_COUNT, OUTPUT_KEYS
from app.styles import (
    ACCEPTED_COUNT_INITIAL,
    APP_TITLE,
    BTN_ALL_DONE,
    BTN_CONTINUE,
    BTN_GO_BACK,
    BTN_PREVIOUS,
    BTN_SELECT,
    CAT_SUBTEXT,
    COMPLETE_HEADING,
    PRIMARY_INSTRUCTION,
)


def _candidate_image() -> gr.Image:
    return gr.Image(
        interactive=False,
        show_label=False,
        container=False,
        buttons=[],
        elem_classes=["candidate-photo"],
    )


def _build_card_row(
    slots: tuple[int, ...],
) -> tuple[list[gr.Image], list[gr.Column], list[gr.Button]]:
    images: list[gr.Image] = []
    cards: list[gr.Column] = []
    select_buttons: list[gr.Button] = []

    with gr.Row(elem_classes=["grid-row"]):
        for slot in slots:
            with gr.Column(elem_classes=["card-col"]) as card_col:
                cards.append(card_col)
                gr.Markdown(f"**Candidate {slot}**", elem_classes=["candidate-label"])
                images.append(_candidate_image())
                select_buttons.append(
                    gr.Button(BTN_SELECT, elem_classes=["select-btn", "select-btn-off"])
                )

    return images, cards, select_buttons


@dataclass
class ReviewUI:
    """Registry of all Gradio components for the review app."""

    ctx: gr.State
    task: gr.State
    selections: gr.State
    error_panel: gr.Markdown
    review_screen: gr.Column
    complete_screen: gr.Column
    cat_heading: gr.Markdown
    progress: gr.Markdown
    images: list[gr.Image] = field(default_factory=list)
    cards: list[gr.Column] = field(default_factory=list)
    select_buttons: list[gr.Button] = field(default_factory=list)
    accepted_count: gr.Markdown | None = None
    status_message: gr.Markdown | None = None
    primary_btn: gr.Button | None = None
    prev_btn: gr.Button | None = None
    all_done_nav_btn: gr.Button | None = None
    all_done_extra_btn: gr.Button | None = None
    go_back_btn: gr.Button | None = None
    _components: dict[str, gr.components.Component] = field(default_factory=dict, repr=False)

    @property
    def state_inputs(self) -> list[gr.State]:
        return [self.ctx, self.task, self.selections]

    @property
    def outputs(self) -> list[gr.components.Component]:
        return [self._components[key] for key in OUTPUT_KEYS]

    @classmethod
    def build(cls) -> ReviewUI:
        ctx_state = gr.State(None)
        task_state = gr.State(None)
        selections_state = gr.State([False] * CANDIDATE_COUNT)

        gr.Markdown(f"# {APP_TITLE}", elem_classes=["review-header"])
        gr.Markdown(PRIMARY_INSTRUCTION, elem_classes=["review-header"])

        error_panel = gr.Markdown("", elem_classes=["error-banner"], visible=False)

        with gr.Column(visible=False) as review_screen:
            with gr.Column(elem_classes=["review-body"]):
                with gr.Column(elem_classes=["cat-block"]):
                    cat_heading = gr.Markdown("")
                    gr.Markdown(CAT_SUBTEXT)
                progress = gr.Markdown("")

                row1_images, row1_cards, row1_buttons = _build_card_row((1, 2))
                row2_images, row2_cards, row2_buttons = _build_card_row((3, 4))
                images = row1_images + row2_images
                cards = row1_cards + row2_cards
                select_buttons = row1_buttons + row2_buttons

                accepted_count = gr.Markdown(
                    ACCEPTED_COUNT_INITIAL, elem_classes=["accepted-count"]
                )
                status_message = gr.Markdown("", elem_classes=["status-message"])

                with gr.Row(elem_classes=["actions-row"]):
                    prev_btn = gr.Button(
                        BTN_PREVIOUS, elem_classes=["secondary-btn"], visible=False
                    )
                    primary_btn = gr.Button(BTN_CONTINUE, elem_classes=["primary-btn"])
                    all_done_nav_btn = gr.Button(
                        BTN_ALL_DONE, elem_classes=["secondary-btn"], visible=False
                    )

                all_done_extra_btn = gr.Button(
                    BTN_ALL_DONE, elem_classes=["all-done-row"], visible=False
                )

        with gr.Column(visible=False) as complete_screen:
            gr.Markdown(COMPLETE_HEADING, elem_classes=["complete-box"])
            with gr.Row(elem_classes=["complete-actions"]):
                go_back_btn = gr.Button(BTN_GO_BACK, elem_classes=["secondary-btn"])

        components = {
            "ctx": ctx_state,
            "task": task_state,
            "selections": selections_state,
            "error_panel": error_panel,
            "review_screen": review_screen,
            "complete_screen": complete_screen,
            "cat_heading": cat_heading,
            "progress": progress,
            **{f"image_{index}": images[index] for index in range(CANDIDATE_COUNT)},
            **{f"card_{index}": cards[index] for index in range(CANDIDATE_COUNT)},
            **{f"select_{index}": select_buttons[index] for index in range(CANDIDATE_COUNT)},
            "accepted_count": accepted_count,
            "status_message": status_message,
            "primary_btn": primary_btn,
            "prev_btn": prev_btn,
            "all_done_nav_btn": all_done_nav_btn,
            "all_done_extra_btn": all_done_extra_btn,
        }

        return cls(
            ctx=ctx_state,
            task=task_state,
            selections=selections_state,
            error_panel=error_panel,
            review_screen=review_screen,
            complete_screen=complete_screen,
            cat_heading=cat_heading,
            progress=progress,
            images=images,
            cards=cards,
            select_buttons=select_buttons,
            accepted_count=accepted_count,
            status_message=status_message,
            primary_btn=primary_btn,
            prev_btn=prev_btn,
            all_done_nav_btn=all_done_nav_btn,
            all_done_extra_btn=all_done_extra_btn,
            go_back_btn=go_back_btn,
            _components=components,
        )