Spaces:
Running
Running
zach
commited on
Commit
·
3a906b3
1
Parent(s):
b9a51b5
Update data fetching strategy for leaderboard data, prevent excessive data fetching
Browse files- 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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
|
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
|
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
|
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:
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
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
|
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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=
|
851 |
inputs=[],
|
852 |
outputs=[leaderboard_table]
|
853 |
).then(
|
854 |
-
fn=
|
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
|