zach commited on
Commit
3a906b3
·
1 Parent(s): b9a51b5

Update data fetching strategy for leaderboard data, prevent excessive data fetching

Browse files
Files changed (1) hide show
  1. src/frontend.py +72 -21
src/frontend.py CHANGED
@@ -10,6 +10,8 @@ Users can compare the outputs and vote for their favorite in an interactive UI.
10
 
11
  # Standard Library Imports
12
  import asyncio
 
 
13
  import time
14
  from typing import List, Tuple
15
 
@@ -43,18 +45,53 @@ from src.utils import (
43
  class Frontend:
44
  config: Config
45
  db_session_maker: AsyncDBSessionMaker
46
- _leaderboard_data: List[List[str]] = [[]]
47
 
48
  def __init__(self, config: Config, db_session_maker: AsyncDBSessionMaker):
49
  self.config = config
50
  self.db_session_maker = db_session_maker
51
 
52
- async def _update_leaderboard_data(self) -> None:
 
 
 
 
 
 
53
  """
54
- Fetches the latest leaderboard data
 
 
 
 
 
 
55
  """
 
 
 
 
 
 
 
 
 
56
  latest_leaderboard_data = await get_leaderboard_data(self.db_session_maker)
 
 
 
 
 
 
 
 
 
 
 
57
  self._leaderboard_data = latest_leaderboard_data
 
 
 
 
58
 
59
  async def _generate_text(self, character_description: str) -> Tuple[gr.Textbox, str]:
60
  """
@@ -152,7 +189,7 @@ class Frontend:
152
 
153
  # Await both tasks concurrently using asyncio.gather()
154
  (generation_id_a, audio_a), (generation_id_b, audio_b) = await asyncio.gather(task_a, task_b)
155
- logger.info(f"Synthesis Succeed for providers: {provider_a} and {provider_b}")
156
 
157
  option_a = Option(provider=provider_a, audio=audio_a, generation_id=generation_id_a)
158
  option_b = Option(provider=provider_b, audio=audio_b, generation_id=generation_id_b)
@@ -168,13 +205,13 @@ class Frontend:
168
  True,
169
  )
170
  except ElevenLabsError as ee:
171
- logger.error(f"Synthesis Failed with ElevenLabsError during TTS generation: {ee!s}")
172
  raise gr.Error(f'There was an issue communicating with the Elevenlabs API: "{ee.message}"')
173
  except HumeError as he:
174
- logger.error(f"Synthesis Failed with HumeError during TTS generation: {he!s}")
175
  raise gr.Error(f'There was an issue communicating with the Hume API: "{he.message}"')
176
  except Exception as e:
177
- logger.error(f"Synthesis Failed with an unexpected error during TTS generation: {e!s}")
178
  raise gr.Error("An unexpected error occurred. Please try again shortly.")
179
 
180
  async def _vote(
@@ -275,20 +312,26 @@ class Frontend:
275
  gr.update(value=character_description), # Update character description
276
  )
277
 
278
- async def _refresh_leaderboard(self) -> gr.DataFrame:
279
  """
280
  Asynchronously fetches and formats the latest leaderboard data.
281
 
 
 
 
282
  Returns:
283
- gr.DataFrame: A Gradio DataFrame update object containing the formatted leaderboard data,
284
- including rank, provider, model, win rate, and total votes.
285
- Raises:
286
- gr.Error: If the leaderboard data cannot be retrieved.
287
  """
288
- await self._update_leaderboard_data()
 
289
  if not self._leaderboard_data:
290
  raise gr.Error("Unable to retrieve leaderboard data. Please refresh the page or try again shortly.")
291
- return gr.update(value=self._leaderboard_data)
 
 
 
 
 
292
 
293
  async def _handle_tab_select(self, evt: gr.SelectData):
294
  """
@@ -296,13 +339,13 @@ class Frontend:
296
 
297
  Args:
298
  evt (gr.SelectData): Event data containing information about the selected tab
299
-
300
  Returns:
301
- gr.update: Update for the leaderboard table if the Leaderboard tab is selected
302
  """
303
  # Check if the selected tab is "Leaderboard" by name
304
  if evt.value == "Leaderboard":
305
- return await self._refresh_leaderboard()
306
  return gr.skip()
307
 
308
  def _disable_ui(self) -> Tuple[
@@ -840,20 +883,28 @@ class Frontend:
840
  elem_id="leaderboard-table"
841
  )
842
 
843
- # --- Register event handlers ---
 
 
 
 
 
 
 
 
844
  # Refresh button click event handler
845
  refresh_button.click(
846
  fn=lambda _=None: (gr.update(interactive=False)),
847
  inputs=[],
848
  outputs=[refresh_button],
849
  ).then(
850
- fn=self._refresh_leaderboard,
851
  inputs=[],
852
  outputs=[leaderboard_table]
853
  ).then(
854
- fn=lambda _=None: (gr.update(interactive=True)),
855
  inputs=[],
856
- outputs=[refresh_button],
857
  )
858
 
859
  return leaderboard_table
 
10
 
11
  # Standard Library Imports
12
  import asyncio
13
+ import hashlib
14
+ import json
15
  import time
16
  from typing import List, Tuple
17
 
 
45
  class Frontend:
46
  config: Config
47
  db_session_maker: AsyncDBSessionMaker
 
48
 
49
  def __init__(self, config: Config, db_session_maker: AsyncDBSessionMaker):
50
  self.config = config
51
  self.db_session_maker = db_session_maker
52
 
53
+ # leaderboard update state
54
+ self._leaderboard_data: List[List[str]] = [[]]
55
+ self._leaderboard_cache_hash = None
56
+ self._last_leaderboard_update_time = 0
57
+ self._min_refresh_interval = 30
58
+
59
+ async def _update_leaderboard_data(self, force: bool = False) -> bool:
60
  """
61
+ Fetches the latest leaderboard data only if needed based on cache and time constraints.
62
+
63
+ Args:
64
+ force (bool): If True, bypass the time-based throttling.
65
+
66
+ Returns:
67
+ bool: True if the leaderboard was updated, False otherwise.
68
  """
69
+ current_time = time.time()
70
+ time_since_last_update = current_time - self._last_leaderboard_update_time
71
+
72
+ # Skip update if it's been less than min_refresh_interval seconds and not forced
73
+ if not force and time_since_last_update < self._min_refresh_interval:
74
+ logger.debug(f"Skipping leaderboard update: last updated {time_since_last_update:.1f}s ago.")
75
+ return False
76
+
77
+ # Fetch the latest data
78
  latest_leaderboard_data = await get_leaderboard_data(self.db_session_maker)
79
+
80
+ # Generate a hash of the new data to check if it's changed
81
+ data_str = json.dumps(str(latest_leaderboard_data))
82
+ data_hash = hashlib.md5(data_str.encode()).hexdigest()
83
+
84
+ # Check if the data has changed
85
+ if data_hash == self._leaderboard_cache_hash and not force:
86
+ logger.debug("Leaderboard data unchanged since last fetch.")
87
+ return False
88
+
89
+ # Update the cache and timestamp
90
  self._leaderboard_data = latest_leaderboard_data
91
+ self._leaderboard_cache_hash = data_hash
92
+ self._last_leaderboard_update_time = current_time
93
+ logger.info("Leaderboard data updated successfully.")
94
+ return True
95
 
96
  async def _generate_text(self, character_description: str) -> Tuple[gr.Textbox, str]:
97
  """
 
189
 
190
  # Await both tasks concurrently using asyncio.gather()
191
  (generation_id_a, audio_a), (generation_id_b, audio_b) = await asyncio.gather(task_a, task_b)
192
+ logger.info(f"Synthesis succeeded for providers: {provider_a} and {provider_b}")
193
 
194
  option_a = Option(provider=provider_a, audio=audio_a, generation_id=generation_id_a)
195
  option_b = Option(provider=provider_b, audio=audio_b, generation_id=generation_id_b)
 
205
  True,
206
  )
207
  except ElevenLabsError as ee:
208
+ logger.error(f"Synthesis failed with ElevenLabsError during TTS generation: {ee!s}")
209
  raise gr.Error(f'There was an issue communicating with the Elevenlabs API: "{ee.message}"')
210
  except HumeError as he:
211
+ logger.error(f"Synthesis failed with HumeError during TTS generation: {he!s}")
212
  raise gr.Error(f'There was an issue communicating with the Hume API: "{he.message}"')
213
  except Exception as e:
214
+ logger.error(f"Synthesis failed with an unexpected error during TTS generation: {e!s}")
215
  raise gr.Error("An unexpected error occurred. Please try again shortly.")
216
 
217
  async def _vote(
 
312
  gr.update(value=character_description), # Update character description
313
  )
314
 
315
+ async def _refresh_leaderboard(self, force: bool = False) -> gr.DataFrame:
316
  """
317
  Asynchronously fetches and formats the latest leaderboard data.
318
 
319
+ Args:
320
+ force (bool): If True, bypass time-based throttling.
321
+
322
  Returns:
323
+ gr.DataFrame: Updated DataFrame or gr.skip() if no update needed
 
 
 
324
  """
325
+ data_updated = await self._update_leaderboard_data(force=force)
326
+
327
  if not self._leaderboard_data:
328
  raise gr.Error("Unable to retrieve leaderboard data. Please refresh the page or try again shortly.")
329
+
330
+ # Only return an update if the data changed or force=True
331
+ if data_updated:
332
+ return gr.update(value=self._leaderboard_data)
333
+ else:
334
+ return gr.skip()
335
 
336
  async def _handle_tab_select(self, evt: gr.SelectData):
337
  """
 
339
 
340
  Args:
341
  evt (gr.SelectData): Event data containing information about the selected tab
342
+
343
  Returns:
344
+ gr.update or gr.skip: Update for the leaderboard table if data changed, otherwise skip
345
  """
346
  # Check if the selected tab is "Leaderboard" by name
347
  if evt.value == "Leaderboard":
348
+ return await self._refresh_leaderboard(force=False)
349
  return gr.skip()
350
 
351
  def _disable_ui(self) -> Tuple[
 
883
  elem_id="leaderboard-table"
884
  )
885
 
886
+ # Wrapper for the async refresh function
887
+ async def async_refresh_handler():
888
+ return await self._refresh_leaderboard(force=True)
889
+
890
+ # Handler to re-enable the button after a refresh
891
+ def reenable_button():
892
+ time.sleep(3) # wait 3 seconds before enabling to prevent excessive data fetching
893
+ return gr.update(interactive=True)
894
+
895
  # Refresh button click event handler
896
  refresh_button.click(
897
  fn=lambda _=None: (gr.update(interactive=False)),
898
  inputs=[],
899
  outputs=[refresh_button],
900
  ).then(
901
+ fn=async_refresh_handler,
902
  inputs=[],
903
  outputs=[leaderboard_table]
904
  ).then(
905
+ fn=reenable_button,
906
  inputs=[],
907
+ outputs=[refresh_button]
908
  )
909
 
910
  return leaderboard_table