Spaces:
Running
Running
zach
commited on
Commit
·
09be04f
1
Parent(s):
f477f87
Move more business logic out of app.py, simplify state management for options
Browse files- src/app.py +31 -64
- src/constants.py +5 -3
- src/types.py +33 -5
- src/utils.py +88 -26
src/app.py
CHANGED
@@ -10,9 +10,8 @@ Users can compare the outputs and vote for their favorite in an interactive UI.
|
|
10 |
|
11 |
# Standard Library Imports
|
12 |
from concurrent.futures import ThreadPoolExecutor
|
13 |
-
import json
|
14 |
import time
|
15 |
-
from typing import
|
16 |
|
17 |
# Third-Party Library Imports
|
18 |
import gradio as gr
|
@@ -29,10 +28,12 @@ from src.integrations import (
|
|
29 |
text_to_speech_with_hume,
|
30 |
)
|
31 |
from src.theme import CustomTheme
|
32 |
-
from src.types import ComparisonType, OptionMap
|
33 |
from src.utils import (
|
34 |
choose_providers,
|
35 |
create_shuffled_tts_options,
|
|
|
|
|
36 |
validate_character_description_length,
|
37 |
)
|
38 |
|
@@ -148,24 +149,18 @@ def synthesize_speech(
|
|
148 |
generation_id_b, audio_b = future_audio_b.result()
|
149 |
|
150 |
# Shuffle options so that placement of options in the UI will always be random
|
151 |
-
(
|
152 |
-
option_a_audio,
|
153 |
-
option_b_audio,
|
154 |
-
option_a_generation_id,
|
155 |
-
option_b_generation_id,
|
156 |
-
options_map,
|
157 |
-
) = create_shuffled_tts_options(
|
158 |
provider_a, audio_a, generation_id_a, provider_b, audio_b, generation_id_b
|
159 |
)
|
160 |
|
|
|
|
|
|
|
161 |
return (
|
162 |
gr.update(value=option_a_audio, visible=True, autoplay=True),
|
163 |
gr.update(value=option_b_audio, visible=True),
|
164 |
options_map,
|
165 |
-
option_b_audio,
|
166 |
comparison_type,
|
167 |
-
option_a_generation_id,
|
168 |
-
option_b_generation_id,
|
169 |
text_modified,
|
170 |
text,
|
171 |
character_description,
|
@@ -188,10 +183,8 @@ def synthesize_speech(
|
|
188 |
def vote(
|
189 |
vote_submitted: bool,
|
190 |
option_map: OptionMap,
|
191 |
-
|
192 |
comparison_type: ComparisonType,
|
193 |
-
option_a_generation_id: str,
|
194 |
-
option_b_generation_id: str,
|
195 |
text_modified: bool,
|
196 |
character_description: str,
|
197 |
text: str,
|
@@ -219,52 +212,41 @@ def vote(
|
|
219 |
if not option_map or vote_submitted:
|
220 |
return gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
221 |
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
227 |
)
|
228 |
-
selected_provider = option_map.get(selected_option)
|
229 |
-
other_provider = option_map.get(other_option)
|
230 |
|
231 |
-
# Build button
|
232 |
selected_label = f"{selected_provider} {constants.TROPHY_EMOJI}"
|
233 |
other_label = f"{other_provider}"
|
234 |
|
235 |
-
# Report voting results to be persisted to results DB
|
236 |
-
voting_results: VotingResults = {
|
237 |
-
"comparison_type": comparison_type,
|
238 |
-
"winning_provider": selected_provider,
|
239 |
-
"winning_option": selected_option,
|
240 |
-
"option_a_provider": option_map.get(constants.OPTION_A),
|
241 |
-
"option_b_provider": option_map.get(constants.OPTION_B),
|
242 |
-
"option_a_generation_id": option_a_generation_id,
|
243 |
-
"option_b_generation_id": option_b_generation_id,
|
244 |
-
"character_description": character_description,
|
245 |
-
"text": text,
|
246 |
-
"is_custom_text": text_modified,
|
247 |
-
}
|
248 |
-
# TODO: Currently logging the results until we hook the API for writing results to DB
|
249 |
-
logger.info("Voting results:\n%s", json.dumps(voting_results, indent=4))
|
250 |
-
|
251 |
return (
|
252 |
True,
|
253 |
(
|
254 |
gr.update(value=selected_label, variant="primary", interactive=False)
|
255 |
-
if
|
256 |
else gr.update(value=other_label, variant="secondary", interactive=False)
|
257 |
),
|
258 |
(
|
259 |
gr.update(value=other_label, variant="secondary", interactive=False)
|
260 |
-
if
|
261 |
else gr.update(value=selected_label, variant="primary", interactive=False)
|
262 |
),
|
263 |
gr.update(interactive=True),
|
264 |
)
|
265 |
|
266 |
|
267 |
-
def reset_ui() -> Tuple[gr.update, gr.update, gr.update, gr.update, None,
|
268 |
"""
|
269 |
Resets UI state before generating new text.
|
270 |
|
@@ -275,7 +257,6 @@ def reset_ui() -> Tuple[gr.update, gr.update, gr.update, gr.update, None, None,
|
|
275 |
- vote_button_a (disable and reset button text)
|
276 |
- vote_button_a (disable and reset button text)
|
277 |
- option_map_state (reset option map state)
|
278 |
-
- option_b_audio_state (reset option B audio state)
|
279 |
- vote_submitted_state (reset submitted vote state)
|
280 |
"""
|
281 |
return (
|
@@ -284,7 +265,6 @@ def reset_ui() -> Tuple[gr.update, gr.update, gr.update, gr.update, None, None,
|
|
284 |
gr.update(value=constants.SELECT_OPTION_A, variant="secondary"),
|
285 |
gr.update(value=constants.SELECT_OPTION_B, variant="secondary"),
|
286 |
None,
|
287 |
-
None,
|
288 |
False,
|
289 |
)
|
290 |
|
@@ -330,10 +310,10 @@ def build_output_section() -> (
|
|
330 |
synthesize_speech_button = gr.Button("Synthesize Speech", variant="primary")
|
331 |
with gr.Row(equal_height=True):
|
332 |
option_a_audio_player = gr.Audio(
|
333 |
-
label=constants.
|
334 |
)
|
335 |
option_b_audio_player = gr.Audio(
|
336 |
-
label=constants.
|
337 |
)
|
338 |
with gr.Row(equal_height=True):
|
339 |
vote_button_a = gr.Button(constants.SELECT_OPTION_A, interactive=False)
|
@@ -402,12 +382,6 @@ def build_gradio_interface() -> gr.Blocks:
|
|
402 |
# Track whether text that was used was generated or modified/custom
|
403 |
text_modified_state = gr.State()
|
404 |
|
405 |
-
# Track generated audio for option B (for playing automatically after option 1 audio finishes)
|
406 |
-
option_b_audio_state = gr.State()
|
407 |
-
# Track generation ID for Option A
|
408 |
-
option_a_generation_id_state = gr.State()
|
409 |
-
# Track generation ID for Option B
|
410 |
-
option_b_generation_id_state = gr.State()
|
411 |
# Track comparison type (which set of providers are being compared)
|
412 |
comparison_type_state = gr.State()
|
413 |
# Track option map (option A and option B are randomized)
|
@@ -465,7 +439,6 @@ def build_gradio_interface() -> gr.Blocks:
|
|
465 |
vote_button_a,
|
466 |
vote_button_b,
|
467 |
option_map_state,
|
468 |
-
option_b_audio_state,
|
469 |
vote_submitted_state,
|
470 |
],
|
471 |
).then(
|
@@ -475,10 +448,7 @@ def build_gradio_interface() -> gr.Blocks:
|
|
475 |
option_a_audio_player,
|
476 |
option_b_audio_player,
|
477 |
option_map_state,
|
478 |
-
option_b_audio_state,
|
479 |
comparison_type_state,
|
480 |
-
option_a_generation_id_state,
|
481 |
-
option_b_generation_id_state,
|
482 |
text_modified_state,
|
483 |
text_state,
|
484 |
character_description_state,
|
@@ -501,8 +471,6 @@ def build_gradio_interface() -> gr.Blocks:
|
|
501 |
option_map_state,
|
502 |
vote_button_a,
|
503 |
comparison_type_state,
|
504 |
-
option_a_generation_id_state,
|
505 |
-
option_b_generation_id_state,
|
506 |
text_modified_state,
|
507 |
character_description_state,
|
508 |
text_state,
|
@@ -521,8 +489,6 @@ def build_gradio_interface() -> gr.Blocks:
|
|
521 |
option_map_state,
|
522 |
vote_button_b,
|
523 |
comparison_type_state,
|
524 |
-
option_a_generation_id_state,
|
525 |
-
option_b_generation_id_state,
|
526 |
text_modified_state,
|
527 |
character_description_state,
|
528 |
text_state,
|
@@ -537,10 +503,11 @@ def build_gradio_interface() -> gr.Blocks:
|
|
537 |
|
538 |
# Reload audio player B with audio and set autoplay to True (workaround to play audio back-to-back)
|
539 |
option_a_audio_player.stop(
|
540 |
-
fn=lambda
|
541 |
-
value=f"{
|
|
|
542 |
),
|
543 |
-
inputs=[
|
544 |
outputs=[option_b_audio_player],
|
545 |
)
|
546 |
|
|
|
10 |
|
11 |
# Standard Library Imports
|
12 |
from concurrent.futures import ThreadPoolExecutor
|
|
|
13 |
import time
|
14 |
+
from typing import Tuple, Union
|
15 |
|
16 |
# Third-Party Library Imports
|
17 |
import gradio as gr
|
|
|
28 |
text_to_speech_with_hume,
|
29 |
)
|
30 |
from src.theme import CustomTheme
|
31 |
+
from src.types import ComparisonType, OptionMap
|
32 |
from src.utils import (
|
33 |
choose_providers,
|
34 |
create_shuffled_tts_options,
|
35 |
+
determine_selected_option,
|
36 |
+
submit_voting_results,
|
37 |
validate_character_description_length,
|
38 |
)
|
39 |
|
|
|
149 |
generation_id_b, audio_b = future_audio_b.result()
|
150 |
|
151 |
# Shuffle options so that placement of options in the UI will always be random
|
152 |
+
options_map: OptionMap = create_shuffled_tts_options(
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
provider_a, audio_a, generation_id_a, provider_b, audio_b, generation_id_b
|
154 |
)
|
155 |
|
156 |
+
option_a_audio = options_map["option_a"]["audio_file_path"]
|
157 |
+
option_b_audio = options_map["option_b"]["audio_file_path"]
|
158 |
+
|
159 |
return (
|
160 |
gr.update(value=option_a_audio, visible=True, autoplay=True),
|
161 |
gr.update(value=option_b_audio, visible=True),
|
162 |
options_map,
|
|
|
163 |
comparison_type,
|
|
|
|
|
164 |
text_modified,
|
165 |
text,
|
166 |
character_description,
|
|
|
183 |
def vote(
|
184 |
vote_submitted: bool,
|
185 |
option_map: OptionMap,
|
186 |
+
clicked_option_button: str,
|
187 |
comparison_type: ComparisonType,
|
|
|
|
|
188 |
text_modified: bool,
|
189 |
character_description: str,
|
190 |
text: str,
|
|
|
212 |
if not option_map or vote_submitted:
|
213 |
return gr.skip(), gr.skip(), gr.skip(), gr.skip()
|
214 |
|
215 |
+
selected_option, other_option = determine_selected_option(clicked_option_button)
|
216 |
+
selected_provider = option_map[selected_option]["provider"]
|
217 |
+
other_provider = option_map[other_option]["provider"]
|
218 |
+
|
219 |
+
# Report voting results to be persisted to results DB
|
220 |
+
submit_voting_results(
|
221 |
+
option_map,
|
222 |
+
selected_option,
|
223 |
+
comparison_type,
|
224 |
+
text_modified,
|
225 |
+
character_description,
|
226 |
+
text,
|
227 |
)
|
|
|
|
|
228 |
|
229 |
+
# Build button text, displaying the provider and voice name, appending the trophy emoji to the selected option.
|
230 |
selected_label = f"{selected_provider} {constants.TROPHY_EMOJI}"
|
231 |
other_label = f"{other_provider}"
|
232 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
return (
|
234 |
True,
|
235 |
(
|
236 |
gr.update(value=selected_label, variant="primary", interactive=False)
|
237 |
+
if selected_option == constants.OPTION_A_KEY
|
238 |
else gr.update(value=other_label, variant="secondary", interactive=False)
|
239 |
),
|
240 |
(
|
241 |
gr.update(value=other_label, variant="secondary", interactive=False)
|
242 |
+
if selected_option == constants.OPTION_A_KEY
|
243 |
else gr.update(value=selected_label, variant="primary", interactive=False)
|
244 |
),
|
245 |
gr.update(interactive=True),
|
246 |
)
|
247 |
|
248 |
|
249 |
+
def reset_ui() -> Tuple[gr.update, gr.update, gr.update, gr.update, None, bool]:
|
250 |
"""
|
251 |
Resets UI state before generating new text.
|
252 |
|
|
|
257 |
- vote_button_a (disable and reset button text)
|
258 |
- vote_button_a (disable and reset button text)
|
259 |
- option_map_state (reset option map state)
|
|
|
260 |
- vote_submitted_state (reset submitted vote state)
|
261 |
"""
|
262 |
return (
|
|
|
265 |
gr.update(value=constants.SELECT_OPTION_A, variant="secondary"),
|
266 |
gr.update(value=constants.SELECT_OPTION_B, variant="secondary"),
|
267 |
None,
|
|
|
268 |
False,
|
269 |
)
|
270 |
|
|
|
310 |
synthesize_speech_button = gr.Button("Synthesize Speech", variant="primary")
|
311 |
with gr.Row(equal_height=True):
|
312 |
option_a_audio_player = gr.Audio(
|
313 |
+
label=constants.OPTION_A_LABEL, type="filepath", interactive=False
|
314 |
)
|
315 |
option_b_audio_player = gr.Audio(
|
316 |
+
label=constants.OPTION_B_LABEL, type="filepath", interactive=False
|
317 |
)
|
318 |
with gr.Row(equal_height=True):
|
319 |
vote_button_a = gr.Button(constants.SELECT_OPTION_A, interactive=False)
|
|
|
382 |
# Track whether text that was used was generated or modified/custom
|
383 |
text_modified_state = gr.State()
|
384 |
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
# Track comparison type (which set of providers are being compared)
|
386 |
comparison_type_state = gr.State()
|
387 |
# Track option map (option A and option B are randomized)
|
|
|
439 |
vote_button_a,
|
440 |
vote_button_b,
|
441 |
option_map_state,
|
|
|
442 |
vote_submitted_state,
|
443 |
],
|
444 |
).then(
|
|
|
448 |
option_a_audio_player,
|
449 |
option_b_audio_player,
|
450 |
option_map_state,
|
|
|
451 |
comparison_type_state,
|
|
|
|
|
452 |
text_modified_state,
|
453 |
text_state,
|
454 |
character_description_state,
|
|
|
471 |
option_map_state,
|
472 |
vote_button_a,
|
473 |
comparison_type_state,
|
|
|
|
|
474 |
text_modified_state,
|
475 |
character_description_state,
|
476 |
text_state,
|
|
|
489 |
option_map_state,
|
490 |
vote_button_b,
|
491 |
comparison_type_state,
|
|
|
|
|
492 |
text_modified_state,
|
493 |
character_description_state,
|
494 |
text_state,
|
|
|
503 |
|
504 |
# Reload audio player B with audio and set autoplay to True (workaround to play audio back-to-back)
|
505 |
option_a_audio_player.stop(
|
506 |
+
fn=lambda option_map: gr.update(
|
507 |
+
value=f"{option_map['option_b']['audio_file_path']}?t={int(time.time())}",
|
508 |
+
autoplay=True,
|
509 |
),
|
510 |
+
inputs=[option_map_state],
|
511 |
outputs=[option_b_audio_player],
|
512 |
)
|
513 |
|
src/constants.py
CHANGED
@@ -8,7 +8,7 @@ This module defines global constants used throughout the project.
|
|
8 |
from typing import List
|
9 |
|
10 |
# Third-Party Library Imports
|
11 |
-
from src.types import ComparisonType, OptionKey, TTSProviderName
|
12 |
|
13 |
|
14 |
# UI constants
|
@@ -22,8 +22,10 @@ HUME_TO_ELEVENLABS: ComparisonType = "Hume AI - ElevenLabs"
|
|
22 |
CHARACTER_DESCRIPTION_MIN_LENGTH: int = 20
|
23 |
CHARACTER_DESCRIPTION_MAX_LENGTH: int = 800
|
24 |
|
25 |
-
|
26 |
-
|
|
|
|
|
27 |
TROPHY_EMOJI: str = "🏆"
|
28 |
SELECT_OPTION_A: str = "Select Option A"
|
29 |
SELECT_OPTION_B: str = "Select Option B"
|
|
|
8 |
from typing import List
|
9 |
|
10 |
# Third-Party Library Imports
|
11 |
+
from src.types import ComparisonType, OptionKey, OptionLabel, TTSProviderName
|
12 |
|
13 |
|
14 |
# UI constants
|
|
|
22 |
CHARACTER_DESCRIPTION_MIN_LENGTH: int = 20
|
23 |
CHARACTER_DESCRIPTION_MAX_LENGTH: int = 800
|
24 |
|
25 |
+
OPTION_A_KEY: OptionKey = "option_a"
|
26 |
+
OPTION_B_KEY: OptionKey = "option_b"
|
27 |
+
OPTION_A_LABEL: OptionLabel = "Option A"
|
28 |
+
OPTION_B_LABEL: OptionLabel = "Option B"
|
29 |
TROPHY_EMOJI: str = "🏆"
|
30 |
SELECT_OPTION_A: str = "Select Option A"
|
31 |
SELECT_OPTION_B: str = "Select Option B"
|
src/types.py
CHANGED
@@ -5,7 +5,7 @@ This module defines custom types for the application.
|
|
5 |
"""
|
6 |
|
7 |
# Standard Library Imports
|
8 |
-
from typing import Dict, Literal, NamedTuple, TypedDict
|
9 |
|
10 |
|
11 |
TTSProviderName = Literal["Hume AI", "ElevenLabs"]
|
@@ -16,12 +16,12 @@ ComparisonType = Literal["Hume AI - Hume AI", "Hume AI - ElevenLabs"]
|
|
16 |
"""Comparison type denoting which providers are compared."""
|
17 |
|
18 |
|
19 |
-
|
20 |
-
"""
|
21 |
|
22 |
|
23 |
-
|
24 |
-
"""
|
25 |
|
26 |
|
27 |
class Option(NamedTuple):
|
@@ -56,3 +56,31 @@ class VotingResults(TypedDict):
|
|
56 |
voice_description: str
|
57 |
text: str
|
58 |
is_custom_text: bool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
"""
|
6 |
|
7 |
# Standard Library Imports
|
8 |
+
from typing import Dict, Literal, NamedTuple, Optional, TypedDict
|
9 |
|
10 |
|
11 |
TTSProviderName = Literal["Hume AI", "ElevenLabs"]
|
|
|
16 |
"""Comparison type denoting which providers are compared."""
|
17 |
|
18 |
|
19 |
+
OptionLabel = Literal["Option A", "Option B"]
|
20 |
+
"""OptionLabel is restricted to the literal values 'Option A' or 'Option B'."""
|
21 |
|
22 |
|
23 |
+
OptionKey = Literal["option_a", "option_b"]
|
24 |
+
"""OptionKey is restricted to the literal values 'option_a' or 'option_b'."""
|
25 |
|
26 |
|
27 |
class Option(NamedTuple):
|
|
|
56 |
voice_description: str
|
57 |
text: str
|
58 |
is_custom_text: bool
|
59 |
+
|
60 |
+
|
61 |
+
class OptionDetail(TypedDict):
|
62 |
+
"""
|
63 |
+
Details for a single TTS option.
|
64 |
+
|
65 |
+
Attributes:
|
66 |
+
provider (TTSProviderName): The TTS provider that generated the audio.
|
67 |
+
generation_id (Optional[str]): The unique identifier for this TTS generation, or None if not available.
|
68 |
+
audio_file_path (str): The relative file path to the generated audio file.
|
69 |
+
"""
|
70 |
+
|
71 |
+
provider: TTSProviderName
|
72 |
+
generation_id: Optional[str]
|
73 |
+
audio_file_path: str
|
74 |
+
|
75 |
+
|
76 |
+
class OptionMap(TypedDict):
|
77 |
+
"""
|
78 |
+
Mapping of TTS options.
|
79 |
+
|
80 |
+
Structure:
|
81 |
+
option_a: OptionDetail,
|
82 |
+
option_b: OptionDetail
|
83 |
+
"""
|
84 |
+
|
85 |
+
option_a: OptionDetail
|
86 |
+
option_b: OptionDetail
|
src/utils.py
CHANGED
@@ -7,6 +7,7 @@ These functions provide reusable logic to simplify code in other modules.
|
|
7 |
|
8 |
# Standard Library Imports
|
9 |
import base64
|
|
|
10 |
import os
|
11 |
import random
|
12 |
import time
|
@@ -15,7 +16,14 @@ from typing import Tuple
|
|
15 |
# Local Application Imports
|
16 |
from src import constants
|
17 |
from src.config import AUDIO_DIR, logger
|
18 |
-
from src.types import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
|
21 |
def truncate_text(text: str, max_length: int = 50) -> str:
|
@@ -244,13 +252,13 @@ def create_shuffled_tts_options(
|
|
244 |
provider_b: TTSProviderName,
|
245 |
audio_b: str,
|
246 |
generation_id_b: str,
|
247 |
-
) ->
|
248 |
"""
|
249 |
Create and shuffle TTS generation options.
|
250 |
|
251 |
This function creates two Option instances from the provided TTS details, shuffles them,
|
252 |
-
|
253 |
-
|
254 |
|
255 |
Args:
|
256 |
provider_a (TTSProviderName): The TTS provider for the first generation.
|
@@ -261,13 +269,7 @@ def create_shuffled_tts_options(
|
|
261 |
generation_id_b (str): The generation ID for the second generation.
|
262 |
|
263 |
Returns:
|
264 |
-
|
265 |
-
A tuple containing:
|
266 |
-
- option_a_audio (str): Audio file path for the first shuffled option.
|
267 |
-
- option_b_audio (str): Audio file path for the second shuffled option.
|
268 |
-
- option_a_generation_id (str): Generation ID for the first shuffled option.
|
269 |
-
- option_b_generation_id (str): Generation ID for the second shuffled option.
|
270 |
-
- options_map (OptionMap): Mapping from option constants to their TTS providers.
|
271 |
"""
|
272 |
# Create a list of Option instances for the available providers.
|
273 |
options = [
|
@@ -281,22 +283,82 @@ def create_shuffled_tts_options(
|
|
281 |
# Unpack the two options.
|
282 |
option_a, option_b = options
|
283 |
|
284 |
-
# Extract audio file paths and generation IDs.
|
285 |
-
option_a_audio = option_a.audio
|
286 |
-
option_b_audio = option_b.audio
|
287 |
-
option_a_generation_id = option_a.generation_id
|
288 |
-
option_b_generation_id = option_b.generation_id
|
289 |
-
|
290 |
# Build a mapping from option constants to the corresponding providers.
|
291 |
options_map: OptionMap = {
|
292 |
-
|
293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
}
|
295 |
|
296 |
-
return
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
# Standard Library Imports
|
9 |
import base64
|
10 |
+
import json
|
11 |
import os
|
12 |
import random
|
13 |
import time
|
|
|
16 |
# Local Application Imports
|
17 |
from src import constants
|
18 |
from src.config import AUDIO_DIR, logger
|
19 |
+
from src.types import (
|
20 |
+
ComparisonType,
|
21 |
+
Option,
|
22 |
+
OptionKey,
|
23 |
+
OptionMap,
|
24 |
+
TTSProviderName,
|
25 |
+
VotingResults,
|
26 |
+
)
|
27 |
|
28 |
|
29 |
def truncate_text(text: str, max_length: int = 50) -> str:
|
|
|
252 |
provider_b: TTSProviderName,
|
253 |
audio_b: str,
|
254 |
generation_id_b: str,
|
255 |
+
) -> OptionMap:
|
256 |
"""
|
257 |
Create and shuffle TTS generation options.
|
258 |
|
259 |
This function creates two Option instances from the provided TTS details, shuffles them,
|
260 |
+
then extracts the providers, audio file paths, and generation IDs from the shuffled options,
|
261 |
+
and finally maps the options to an OptionMap.
|
262 |
|
263 |
Args:
|
264 |
provider_a (TTSProviderName): The TTS provider for the first generation.
|
|
|
269 |
generation_id_b (str): The generation ID for the second generation.
|
270 |
|
271 |
Returns:
|
272 |
+
options_map (OptionMap): Mapping of TTS output options.
|
|
|
|
|
|
|
|
|
|
|
|
|
273 |
"""
|
274 |
# Create a list of Option instances for the available providers.
|
275 |
options = [
|
|
|
283 |
# Unpack the two options.
|
284 |
option_a, option_b = options
|
285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
# Build a mapping from option constants to the corresponding providers.
|
287 |
options_map: OptionMap = {
|
288 |
+
"option_a": {
|
289 |
+
"provider": option_a.provider,
|
290 |
+
"generation_id": option_a.generation_id,
|
291 |
+
"audio_file_path": option_a.audio,
|
292 |
+
},
|
293 |
+
"option_b": {
|
294 |
+
"provider": option_b.provider,
|
295 |
+
"generation_id": option_b.generation_id,
|
296 |
+
"audio_file_path": option_b.audio,
|
297 |
+
},
|
298 |
}
|
299 |
|
300 |
+
return options_map
|
301 |
+
|
302 |
+
|
303 |
+
def determine_selected_option(
|
304 |
+
selected_option_button: str,
|
305 |
+
) -> Tuple[OptionKey, OptionKey]:
|
306 |
+
"""
|
307 |
+
Determines the selected option and the alternative option based on the user's selection.
|
308 |
+
|
309 |
+
Args:
|
310 |
+
selected_option_button (str): The option selected by the user, expected to be either
|
311 |
+
constants.OPTION_A_KEY or constants.OPTION_B_KEY.
|
312 |
+
|
313 |
+
Returns:
|
314 |
+
tuple: A tuple (selected_option, other_option) where:
|
315 |
+
- selected_option is the same as the selected_option.
|
316 |
+
- other_option is the alternative option.
|
317 |
+
"""
|
318 |
+
if selected_option_button == constants.SELECT_OPTION_A:
|
319 |
+
selected_option, other_option = constants.OPTION_A_KEY, constants.OPTION_B_KEY
|
320 |
+
elif selected_option_button == constants.SELECT_OPTION_B:
|
321 |
+
selected_option, other_option = constants.OPTION_B_KEY, constants.OPTION_A_KEY
|
322 |
+
else:
|
323 |
+
raise ValueError(f"Invalid selected button: {selected_option_button}")
|
324 |
+
|
325 |
+
return selected_option, other_option
|
326 |
+
|
327 |
+
|
328 |
+
def submit_voting_results(
|
329 |
+
option_map: OptionMap,
|
330 |
+
selected_option: str,
|
331 |
+
comparison_type: ComparisonType,
|
332 |
+
text_modified: bool,
|
333 |
+
character_description: str,
|
334 |
+
text: str,
|
335 |
+
) -> VotingResults:
|
336 |
+
"""
|
337 |
+
Constructs the voting results dictionary from the provided inputs and logs it.
|
338 |
+
|
339 |
+
Args:
|
340 |
+
option_map (OptionMap): Mapping of comparison data and TTS options.
|
341 |
+
selected_option (str): The option selected by the user.
|
342 |
+
comparison_type (ComparisonType): The type of comparison between providers.
|
343 |
+
text_modified (bool): Indicates whether the text was modified.
|
344 |
+
character_description (str): Description of the voice/character.
|
345 |
+
text (str): The text associated with the TTS generation.
|
346 |
+
|
347 |
+
Returns:
|
348 |
+
VotingResults: The constructed voting results dictionary.
|
349 |
+
"""
|
350 |
+
voting_results: VotingResults = {
|
351 |
+
"comparison_type": comparison_type,
|
352 |
+
"winning_provider": option_map[selected_option]["provider"],
|
353 |
+
"winning_option": selected_option,
|
354 |
+
"option_a_provider": option_map[constants.OPTION_A_KEY]["provider"],
|
355 |
+
"option_b_provider": option_map[constants.OPTION_B_KEY]["provider"],
|
356 |
+
"option_a_generation_id": option_map[constants.OPTION_A_KEY]["generation_id"],
|
357 |
+
"option_b_generation_id": option_map[constants.OPTION_B_KEY]["generation_id"],
|
358 |
+
"voice_description": character_description,
|
359 |
+
"text": text,
|
360 |
+
"is_custom_text": text_modified,
|
361 |
+
}
|
362 |
+
# TODO: Currently logging the results until we hook the API for writing results to DB
|
363 |
+
logger.info("Voting results:\n%s", json.dumps(voting_results, indent=4))
|
364 |
+
return voting_results
|