zach commited on
Commit
09be04f
·
1 Parent(s): f477f87

Move more business logic out of app.py, simplify state management for options

Browse files
Files changed (4) hide show
  1. src/app.py +31 -64
  2. src/constants.py +5 -3
  3. src/types.py +33 -5
  4. 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 Union, Tuple
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, VotingResults
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
- selected_button: str,
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
- option_a_selected = selected_button == constants.SELECT_OPTION_A
223
- selected_option, other_option = (
224
- (constants.OPTION_A, constants.OPTION_B)
225
- if option_a_selected
226
- else (constants.OPTION_B, constants.OPTION_A)
 
 
 
 
 
 
 
227
  )
228
- selected_provider = option_map.get(selected_option)
229
- other_provider = option_map.get(other_option)
230
 
231
- # Build button labels, displaying the provider and voice name, appending the trophy emoji to the selected option.
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 option_a_selected
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 option_a_selected
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, None, bool]:
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.OPTION_A, type="filepath", interactive=False
334
  )
335
  option_b_audio_player = gr.Audio(
336
- label=constants.OPTION_B, type="filepath", interactive=False
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 current_audio_path: gr.update(
541
- value=f"{current_audio_path}?t={int(time.time())}", autoplay=True
 
542
  ),
543
- inputs=[option_b_audio_state],
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
- OPTION_A: OptionKey = "Option A"
26
- OPTION_B: OptionKey = "Option B"
 
 
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
- OptionKey = Literal["Option A", "Option B"]
20
- """OptionKey is restricted to the literal values 'Option A' or 'Option B'."""
21
 
22
 
23
- OptionMap = Dict[OptionKey, TTSProviderName]
24
- """OptionMap defines the structure of the options mapping, where each key is an OptionKey and the value is a TTS provider."""
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 ComparisonType, Option, OptionMap, TTSProviderName
 
 
 
 
 
 
 
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
- ) -> Tuple[str, str, str, str, OptionMap]:
248
  """
249
  Create and shuffle TTS generation options.
250
 
251
  This function creates two Option instances from the provided TTS details, shuffles them,
252
- and then extracts the audio file paths and generation IDs from the shuffled options.
253
- It also returns a mapping from option constants to the corresponding TTS providers.
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
- Tuple[str, str, str, str, OptionMap]:
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
- constants.OPTION_A: option_a.provider,
293
- constants.OPTION_B: option_b.provider,
 
 
 
 
 
 
 
 
294
  }
295
 
296
- return (
297
- option_a_audio,
298
- option_b_audio,
299
- option_a_generation_id,
300
- option_b_generation_id,
301
- options_map,
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