Spaces:
Running
Running
zach
commited on
Commit
·
fcb34bb
1
Parent(s):
0dde48b
Update theme configuration and clean up app.py
Browse files- src/app.py +112 -39
- src/constants.py +2 -0
- src/integrations/hume_api.py +1 -1
- src/theme.py +76 -54
- src/utils.py +12 -10
src/app.py
CHANGED
@@ -12,6 +12,7 @@ Users can compare the outputs in an interactive UI.
|
|
12 |
from concurrent.futures import ThreadPoolExecutor
|
13 |
from functools import partial
|
14 |
import random
|
|
|
15 |
# Third-Party Library Imports
|
16 |
import gradio as gr
|
17 |
# Local Application Imports
|
@@ -19,11 +20,13 @@ from src.config import logger
|
|
19 |
from src.constants import (
|
20 |
OPTION_ONE,
|
21 |
OPTION_TWO,
|
22 |
-
VOTE_FOR_OPTION_ONE,
|
23 |
-
VOTE_FOR_OPTION_TWO,
|
24 |
PROMPT_MAX_LENGTH,
|
25 |
PROMPT_MIN_LENGTH,
|
26 |
-
SAMPLE_PROMPTS
|
|
|
|
|
|
|
|
|
27 |
)
|
28 |
from src.integrations import (
|
29 |
generate_text_with_claude,
|
@@ -34,19 +37,22 @@ from src.theme import CustomTheme
|
|
34 |
from src.utils import truncate_text, validate_prompt_length
|
35 |
|
36 |
|
37 |
-
def generate_text(prompt: str):
|
38 |
"""
|
39 |
-
Generates text
|
40 |
|
41 |
Args:
|
42 |
prompt (str): User-provided text prompt.
|
|
|
|
|
|
|
|
|
43 |
"""
|
44 |
logger.info(f'Generating text with prompt: {truncate_text(prompt, max_length=100)}')
|
45 |
try:
|
46 |
# Validate prompt length
|
47 |
validate_prompt_length(prompt, PROMPT_MAX_LENGTH, PROMPT_MIN_LENGTH)
|
48 |
|
49 |
-
|
50 |
# Generate text
|
51 |
generated_text = generate_text_with_claude(prompt)
|
52 |
logger.info(f'Generated text ({len(generated_text)} characters).')
|
@@ -57,15 +63,29 @@ def generate_text(prompt: str):
|
|
57 |
logger.warning(f'Validation error: {ve}')
|
58 |
return str(ve)
|
59 |
|
60 |
-
def text_to_speech(prompt: str, generated_text: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
try:
|
62 |
# Generate TTS output in parallel
|
63 |
with ThreadPoolExecutor(max_workers=2) as executor:
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
)
|
69 |
|
70 |
logger.info(
|
71 |
f'TTS generated: Hume={len(hume_audio)} bytes, ElevenLabs={len(elevenlabs_audio)} bytes'
|
@@ -95,45 +115,55 @@ def text_to_speech(prompt: str, generated_text: str):
|
|
95 |
return None, None, {}
|
96 |
|
97 |
|
98 |
-
def vote(
|
|
|
|
|
|
|
|
|
99 |
"""
|
100 |
-
|
101 |
|
102 |
Args:
|
103 |
-
|
104 |
-
|
|
|
105 |
|
106 |
Returns:
|
107 |
-
tuple[gr.update, gr.update]:
|
|
|
|
|
|
|
|
|
108 |
"""
|
109 |
-
if not option_mapping:
|
110 |
-
return gr.update(), gr.update() # No updates if mapping is missing
|
111 |
|
112 |
# Determine selected option
|
113 |
is_option_1 = selected_button == VOTE_FOR_OPTION_ONE
|
114 |
selected_option, other_option = (OPTION_ONE, OPTION_TWO) if is_option_1 else (OPTION_TWO, OPTION_ONE)
|
115 |
|
116 |
# Get provider names
|
117 |
-
selected_provider = option_mapping.get(selected_option,
|
118 |
-
other_provider = option_mapping.get(other_option,
|
119 |
|
120 |
# Return updated button states
|
121 |
return (
|
122 |
-
|
123 |
-
gr.update(value=
|
|
|
124 |
gr.update(interactive=True, variant='primary')
|
125 |
)
|
126 |
|
127 |
|
128 |
def build_gradio_interface() -> gr.Blocks:
|
129 |
"""
|
130 |
-
|
131 |
|
132 |
Returns:
|
133 |
-
gr.Blocks: The Gradio UI layout.
|
134 |
"""
|
135 |
custom_theme = CustomTheme()
|
136 |
-
with gr.Blocks(title='Expressive TTS Arena', theme=custom_theme) as demo:
|
137 |
# Title
|
138 |
gr.Markdown('# Expressive TTS Arena')
|
139 |
|
@@ -154,8 +184,8 @@ def build_gradio_interface() -> gr.Blocks:
|
|
154 |
|
155 |
# Prompt input
|
156 |
prompt_input = gr.Textbox(
|
157 |
-
|
158 |
-
placeholder='
|
159 |
lines=2,
|
160 |
max_lines=2,
|
161 |
show_copy_button=True,
|
@@ -177,8 +207,16 @@ def build_gradio_interface() -> gr.Blocks:
|
|
177 |
|
178 |
# Audio players
|
179 |
with gr.Row():
|
180 |
-
option1_audio_player = gr.Audio(
|
181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
# Vote buttons
|
184 |
with gr.Row():
|
@@ -186,9 +224,10 @@ def build_gradio_interface() -> gr.Blocks:
|
|
186 |
vote_button_2 = gr.Button(VOTE_FOR_OPTION_TWO, interactive=False)
|
187 |
|
188 |
# UI state components
|
189 |
-
option_mapping_state = gr.State()
|
190 |
-
option2_audio_state = gr.State()
|
191 |
-
generated_text_state = gr.State()
|
|
|
192 |
|
193 |
# Event handlers
|
194 |
sample_prompt_dropdown.change(
|
@@ -198,35 +237,69 @@ def build_gradio_interface() -> gr.Blocks:
|
|
198 |
)
|
199 |
|
200 |
generate_button.click(
|
|
|
201 |
fn=lambda _: (
|
202 |
gr.update(interactive=False),
|
203 |
gr.update(interactive=False, value=VOTE_FOR_OPTION_ONE, variant='secondary'),
|
204 |
gr.update(interactive=False, value=VOTE_FOR_OPTION_TWO, variant='secondary'),
|
205 |
None,
|
206 |
None,
|
|
|
207 |
),
|
208 |
inputs=[],
|
209 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
210 |
).then(
|
|
|
211 |
fn=generate_text,
|
212 |
inputs=[prompt_input],
|
213 |
outputs=[generated_text]
|
214 |
).then(
|
|
|
215 |
fn=text_to_speech,
|
216 |
inputs=[prompt_input, generated_text],
|
217 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
218 |
)
|
219 |
|
220 |
vote_button_1.click(
|
221 |
fn=vote,
|
222 |
-
inputs=[
|
223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
)
|
225 |
|
226 |
vote_button_2.click(
|
227 |
fn=vote,
|
228 |
-
inputs=[
|
229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
)
|
231 |
|
232 |
# Auto-play second audio after first finishes
|
|
|
12 |
from concurrent.futures import ThreadPoolExecutor
|
13 |
from functools import partial
|
14 |
import random
|
15 |
+
from typing import Union
|
16 |
# Third-Party Library Imports
|
17 |
import gradio as gr
|
18 |
# Local Application Imports
|
|
|
20 |
from src.constants import (
|
21 |
OPTION_ONE,
|
22 |
OPTION_TWO,
|
|
|
|
|
23 |
PROMPT_MAX_LENGTH,
|
24 |
PROMPT_MIN_LENGTH,
|
25 |
+
SAMPLE_PROMPTS,
|
26 |
+
TROPHY_EMOJI,
|
27 |
+
UNKNOWN_PROVIDER,
|
28 |
+
VOTE_FOR_OPTION_ONE,
|
29 |
+
VOTE_FOR_OPTION_TWO
|
30 |
)
|
31 |
from src.integrations import (
|
32 |
generate_text_with_claude,
|
|
|
37 |
from src.utils import truncate_text, validate_prompt_length
|
38 |
|
39 |
|
40 |
+
def generate_text(prompt: str) -> Union[str, gr.update]:
|
41 |
"""
|
42 |
+
Generates text using the Claude API.
|
43 |
|
44 |
Args:
|
45 |
prompt (str): User-provided text prompt.
|
46 |
+
|
47 |
+
Returns:
|
48 |
+
Union[str, gr.update]: The generated text wrapped in `gr.update` for Gradio UI,
|
49 |
+
or an error message as a string if validation fails.
|
50 |
"""
|
51 |
logger.info(f'Generating text with prompt: {truncate_text(prompt, max_length=100)}')
|
52 |
try:
|
53 |
# Validate prompt length
|
54 |
validate_prompt_length(prompt, PROMPT_MAX_LENGTH, PROMPT_MIN_LENGTH)
|
55 |
|
|
|
56 |
# Generate text
|
57 |
generated_text = generate_text_with_claude(prompt)
|
58 |
logger.info(f'Generated text ({len(generated_text)} characters).')
|
|
|
63 |
logger.warning(f'Validation error: {ve}')
|
64 |
return str(ve)
|
65 |
|
66 |
+
def text_to_speech(prompt: str, generated_text: str) -> tuple[gr.update, gr.update, dict, str | None]:
|
67 |
+
"""
|
68 |
+
Converts generated text to speech using Hume AI and ElevenLabs APIs.
|
69 |
+
|
70 |
+
Args:
|
71 |
+
prompt (str): The original user-provided prompt.
|
72 |
+
generated_text (str): The generated text that will be converted to speech.
|
73 |
+
|
74 |
+
Returns:
|
75 |
+
tuple[gr.update, gr.update, dict, Union[str, None]]:
|
76 |
+
- `gr.update(value=option_1_audio, autoplay=True)`: Updates the first audio player.
|
77 |
+
- `gr.update(value=option_2_audio)`: Updates the second audio player.
|
78 |
+
- `options_map`: A dictionary mapping OPTION_ONE and OPTION_TWO to their providers.
|
79 |
+
- `option_2_audio`: The second audio file path or `None` if an error occurs.
|
80 |
+
"""
|
81 |
try:
|
82 |
# Generate TTS output in parallel
|
83 |
with ThreadPoolExecutor(max_workers=2) as executor:
|
84 |
+
future_hume = executor.submit(text_to_speech_with_hume, prompt, generated_text)
|
85 |
+
future_elevenlabs = executor.submit(text_to_speech_with_elevenlabs, generated_text)
|
86 |
+
|
87 |
+
hume_audio = future_hume.result()
|
88 |
+
elevenlabs_audio = future_elevenlabs.result()
|
89 |
|
90 |
logger.info(
|
91 |
f'TTS generated: Hume={len(hume_audio)} bytes, ElevenLabs={len(elevenlabs_audio)} bytes'
|
|
|
115 |
return None, None, {}
|
116 |
|
117 |
|
118 |
+
def vote(
|
119 |
+
vote_submitted: bool,
|
120 |
+
option_mapping: dict,
|
121 |
+
selected_button: str
|
122 |
+
) -> tuple[bool, gr.update, gr.update, gr.update]:
|
123 |
"""
|
124 |
+
Handles user voting and updates the UI to reflect the selected choice.
|
125 |
|
126 |
Args:
|
127 |
+
vote_submitted (bool): Indicates if a vote has already been submitted.
|
128 |
+
option_mapping (dict): Maps "Option 1" and "Option 2" to their respective TTS providers.
|
129 |
+
selected_button (str): The button that was clicked by the user.
|
130 |
|
131 |
Returns:
|
132 |
+
tuple[bool, gr.update, gr.update, gr.update]:
|
133 |
+
- `True`: Indicates the vote has been submitted.
|
134 |
+
- `gr.update`: Updates the selected vote button.
|
135 |
+
- `gr.update`: Updates the unselected vote button.
|
136 |
+
- `gr.update(interactive=True)`: Enables the "Generate" button after voting.
|
137 |
"""
|
138 |
+
if not option_mapping or vote_submitted:
|
139 |
+
return True, gr.update(), gr.update(), gr.update() # No updates if mapping is missing
|
140 |
|
141 |
# Determine selected option
|
142 |
is_option_1 = selected_button == VOTE_FOR_OPTION_ONE
|
143 |
selected_option, other_option = (OPTION_ONE, OPTION_TWO) if is_option_1 else (OPTION_TWO, OPTION_ONE)
|
144 |
|
145 |
# Get provider names
|
146 |
+
selected_provider = option_mapping.get(selected_option, UNKNOWN_PROVIDER)
|
147 |
+
other_provider = option_mapping.get(other_option, UNKNOWN_PROVIDER)
|
148 |
|
149 |
# Return updated button states
|
150 |
return (
|
151 |
+
True,
|
152 |
+
gr.update(value=f'{selected_provider} {TROPHY_EMOJI}', variant='primary') if is_option_1 else gr.update(value=other_provider, variant='secondary'),
|
153 |
+
gr.update(value=other_provider, variant='secondary') if is_option_1 else gr.update(value=f'{selected_provider} {TROPHY_EMOJI}', variant='primary'),
|
154 |
gr.update(interactive=True, variant='primary')
|
155 |
)
|
156 |
|
157 |
|
158 |
def build_gradio_interface() -> gr.Blocks:
|
159 |
"""
|
160 |
+
Builds and configures the Gradio user interface.
|
161 |
|
162 |
Returns:
|
163 |
+
gr.Blocks: The fully constructed Gradio UI layout.
|
164 |
"""
|
165 |
custom_theme = CustomTheme()
|
166 |
+
with gr.Blocks(title='Expressive TTS Arena', theme=custom_theme, fill_width=True) as demo:
|
167 |
# Title
|
168 |
gr.Markdown('# Expressive TTS Arena')
|
169 |
|
|
|
184 |
|
185 |
# Prompt input
|
186 |
prompt_input = gr.Textbox(
|
187 |
+
show_label=False,
|
188 |
+
placeholder='Enter your prompt...',
|
189 |
lines=2,
|
190 |
max_lines=2,
|
191 |
show_copy_button=True,
|
|
|
207 |
|
208 |
# Audio players
|
209 |
with gr.Row():
|
210 |
+
option1_audio_player = gr.Audio(
|
211 |
+
label=OPTION_ONE,
|
212 |
+
type='filepath',
|
213 |
+
interactive=False
|
214 |
+
)
|
215 |
+
option2_audio_player = gr.Audio(
|
216 |
+
label=OPTION_TWO,
|
217 |
+
type='filepath',
|
218 |
+
interactive=False
|
219 |
+
)
|
220 |
|
221 |
# Vote buttons
|
222 |
with gr.Row():
|
|
|
224 |
vote_button_2 = gr.Button(VOTE_FOR_OPTION_TWO, interactive=False)
|
225 |
|
226 |
# UI state components
|
227 |
+
option_mapping_state = gr.State() # Track option map (option 1 and option 2 are randomized)
|
228 |
+
option2_audio_state = gr.State() # Track generated audio for option 2 for playing automatically after option 1 audio finishes
|
229 |
+
generated_text_state = gr.State() # Track the text which was generated from the user's prompt
|
230 |
+
vote_submitted_state = gr.State(False) # Track whether the user has voted on an option
|
231 |
|
232 |
# Event handlers
|
233 |
sample_prompt_dropdown.change(
|
|
|
237 |
)
|
238 |
|
239 |
generate_button.click(
|
240 |
+
# Reset UI
|
241 |
fn=lambda _: (
|
242 |
gr.update(interactive=False),
|
243 |
gr.update(interactive=False, value=VOTE_FOR_OPTION_ONE, variant='secondary'),
|
244 |
gr.update(interactive=False, value=VOTE_FOR_OPTION_TWO, variant='secondary'),
|
245 |
None,
|
246 |
None,
|
247 |
+
False
|
248 |
),
|
249 |
inputs=[],
|
250 |
+
outputs=[
|
251 |
+
generate_button,
|
252 |
+
vote_button_1,
|
253 |
+
vote_button_2,
|
254 |
+
option_mapping_state,
|
255 |
+
option2_audio_state,
|
256 |
+
vote_submitted_state
|
257 |
+
]
|
258 |
).then(
|
259 |
+
# Generate text from user prompt
|
260 |
fn=generate_text,
|
261 |
inputs=[prompt_input],
|
262 |
outputs=[generated_text]
|
263 |
).then(
|
264 |
+
# Synthesize text to speech and trigger playback of generated audio
|
265 |
fn=text_to_speech,
|
266 |
inputs=[prompt_input, generated_text],
|
267 |
+
outputs=[
|
268 |
+
option1_audio_player,
|
269 |
+
option2_audio_player,
|
270 |
+
option_mapping_state,
|
271 |
+
option2_audio_state
|
272 |
+
]
|
273 |
)
|
274 |
|
275 |
vote_button_1.click(
|
276 |
fn=vote,
|
277 |
+
inputs=[
|
278 |
+
vote_submitted_state,
|
279 |
+
option_mapping_state,
|
280 |
+
vote_button_1
|
281 |
+
],
|
282 |
+
outputs=[
|
283 |
+
vote_submitted_state,
|
284 |
+
vote_button_1,
|
285 |
+
vote_button_2,
|
286 |
+
generate_button
|
287 |
+
]
|
288 |
)
|
289 |
|
290 |
vote_button_2.click(
|
291 |
fn=vote,
|
292 |
+
inputs=[
|
293 |
+
vote_submitted_state,
|
294 |
+
option_mapping_state,
|
295 |
+
vote_button_2
|
296 |
+
],
|
297 |
+
outputs=[
|
298 |
+
vote_submitted_state,
|
299 |
+
vote_button_1,
|
300 |
+
vote_button_2,
|
301 |
+
generate_button
|
302 |
+
]
|
303 |
)
|
304 |
|
305 |
# Auto-play second audio after first finishes
|
src/constants.py
CHANGED
@@ -11,6 +11,8 @@ PROMPT_MAX_LENGTH: int = 300
|
|
11 |
# Vote button constants
|
12 |
OPTION_ONE: str = "Option 1"
|
13 |
OPTION_TWO: str = "Option 2"
|
|
|
|
|
14 |
VOTE_FOR_OPTION_ONE: str = "Vote for option 1"
|
15 |
VOTE_FOR_OPTION_TWO: str = "Vote for option 2"
|
16 |
|
|
|
11 |
# Vote button constants
|
12 |
OPTION_ONE: str = "Option 1"
|
13 |
OPTION_TWO: str = "Option 2"
|
14 |
+
TROPHY_EMOJI = "🏆"
|
15 |
+
UNKNOWN_PROVIDER = "Unknown"
|
16 |
VOTE_FOR_OPTION_ONE: str = "Vote for option 1"
|
17 |
VOTE_FOR_OPTION_TWO: str = "Vote for option 2"
|
18 |
|
src/integrations/hume_api.py
CHANGED
@@ -36,7 +36,7 @@ class HumeConfig:
|
|
36 |
"""Immutable configuration for interacting with the Hume TTS API."""
|
37 |
tts_endpoint_url: str = 'https://api.hume.ai/v0/tts'
|
38 |
api_key: str = validate_env_var('HUME_API_KEY')
|
39 |
-
voices: List[str] = ('ITO', 'KORA', '
|
40 |
audio_format: str = 'wav'
|
41 |
headers: dict = None # Headers for the API requests
|
42 |
|
|
|
36 |
"""Immutable configuration for interacting with the Hume TTS API."""
|
37 |
tts_endpoint_url: str = 'https://api.hume.ai/v0/tts'
|
38 |
api_key: str = validate_env_var('HUME_API_KEY')
|
39 |
+
voices: List[str] = ('ITO', 'KORA', 'STELLA') # List of available Hume voices
|
40 |
audio_format: str = 'wav'
|
41 |
headers: dict = None # Headers for the API requests
|
42 |
|
src/theme.py
CHANGED
@@ -1,32 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from __future__ import annotations
|
2 |
from collections.abc import Iterable
|
|
|
3 |
from gradio.themes.base import Base
|
4 |
from gradio.themes.utils import colors, fonts, sizes
|
5 |
|
6 |
-
# TODO: Update theme styling
|
7 |
-
|
8 |
-
# Custom theme
|
9 |
class CustomTheme(Base):
|
10 |
def __init__(
|
11 |
self,
|
12 |
*,
|
13 |
-
primary_hue: colors.Color | str = colors.
|
14 |
-
secondary_hue: colors.Color | str = colors.
|
15 |
-
neutral_hue: colors.Color | str = colors.
|
16 |
spacing_size: sizes.Size | str = sizes.spacing_md,
|
17 |
radius_size: sizes.Size | str = sizes.radius_md,
|
18 |
text_size: sizes.Size | str = sizes.text_md,
|
19 |
font: fonts.Font | str | Iterable[fonts.Font | str] = (
|
20 |
-
fonts.GoogleFont(
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
),
|
25 |
font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
|
26 |
-
fonts.
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
),
|
31 |
):
|
32 |
super().__init__(
|
@@ -39,53 +54,60 @@ class CustomTheme(Base):
|
|
39 |
font=font,
|
40 |
font_mono=font_mono,
|
41 |
)
|
42 |
-
self.name =
|
43 |
super().set(
|
44 |
# Colors
|
45 |
-
input_background_fill_dark=
|
46 |
-
error_background_fill=
|
47 |
-
error_background_fill_dark=
|
48 |
-
error_border_color=
|
49 |
-
error_border_color_dark=
|
50 |
-
error_icon_color=
|
51 |
-
error_icon_color_dark=
|
|
|
52 |
# Shadows
|
53 |
-
input_shadow_focus=
|
54 |
-
input_shadow_focus_dark=
|
|
|
55 |
# Button borders
|
56 |
-
button_border_width=
|
57 |
-
input_border_width=
|
58 |
-
input_background_fill=
|
|
|
59 |
# Gradients
|
60 |
-
stat_background_fill=
|
61 |
-
stat_background_fill_dark=
|
62 |
-
checkbox_label_background_fill=
|
63 |
-
checkbox_label_background_fill_dark=
|
64 |
-
checkbox_label_background_fill_hover=
|
65 |
-
checkbox_label_background_fill_hover_dark=
|
|
|
66 |
# Primary Button
|
67 |
-
button_primary_background_fill=
|
68 |
-
button_primary_background_fill_dark=
|
69 |
-
button_primary_background_fill_hover=
|
70 |
-
button_primary_background_fill_hover_dark=
|
71 |
-
button_primary_text_color=
|
72 |
-
button_primary_text_color_dark=
|
|
|
73 |
# Secondary Button
|
74 |
-
button_secondary_background_fill=
|
75 |
-
button_secondary_background_fill_dark=
|
76 |
-
button_secondary_background_fill_hover=
|
77 |
-
button_secondary_background_fill_hover_dark=
|
78 |
-
button_secondary_text_color=
|
79 |
-
button_secondary_text_color_dark=
|
|
|
80 |
# Cancel Button
|
81 |
-
button_cancel_background_fill=
|
82 |
-
button_cancel_background_fill_dark=
|
83 |
-
button_cancel_background_fill_hover=
|
84 |
-
button_cancel_background_fill_hover_dark=
|
85 |
-
button_cancel_text_color=
|
86 |
-
button_cancel_text_color_dark=
|
87 |
-
button_cancel_text_color_hover=
|
88 |
-
button_cancel_text_color_hover_dark=
|
|
|
89 |
# Other
|
90 |
-
border_color_accent_subdued=
|
91 |
)
|
|
|
1 |
+
"""
|
2 |
+
custom_theme.py
|
3 |
+
|
4 |
+
Defines a custom Gradio theme.
|
5 |
+
|
6 |
+
This module creates a `CustomTheme` class, inheriting from `gradio.themes.base.Base`,
|
7 |
+
and overrides default styling variables. It uses CSS variables for consistency
|
8 |
+
with the application's styling.
|
9 |
+
|
10 |
+
Key Features:
|
11 |
+
- Defines a color palette using CSS variables.
|
12 |
+
- Customizes styling for Gradio components (buttons, inputs, etc.).
|
13 |
+
- Adjusts shadows, borders, and gradients.
|
14 |
+
- Supports light and dark modes.
|
15 |
+
"""
|
16 |
+
|
17 |
+
# Standard Library Imports
|
18 |
from __future__ import annotations
|
19 |
from collections.abc import Iterable
|
20 |
+
# Third-Party Library Imports
|
21 |
from gradio.themes.base import Base
|
22 |
from gradio.themes.utils import colors, fonts, sizes
|
23 |
|
|
|
|
|
|
|
24 |
class CustomTheme(Base):
|
25 |
def __init__(
|
26 |
self,
|
27 |
*,
|
28 |
+
primary_hue: colors.Color | str = colors.purple,
|
29 |
+
secondary_hue: colors.Color | str = colors.stone,
|
30 |
+
neutral_hue: colors.Color | str = colors.neutral,
|
31 |
spacing_size: sizes.Size | str = sizes.spacing_md,
|
32 |
radius_size: sizes.Size | str = sizes.radius_md,
|
33 |
text_size: sizes.Size | str = sizes.text_md,
|
34 |
font: fonts.Font | str | Iterable[fonts.Font | str] = (
|
35 |
+
fonts.GoogleFont('Source Sans Pro'),
|
36 |
+
'ui-sans-serif',
|
37 |
+
'system-ui',
|
38 |
+
'sans-serif',
|
39 |
),
|
40 |
font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (
|
41 |
+
fonts.GoogleFont('IBM Plex Mono'),
|
42 |
+
'ui-monospace',
|
43 |
+
'Consolas',
|
44 |
+
'monospace',
|
45 |
),
|
46 |
):
|
47 |
super().__init__(
|
|
|
54 |
font=font,
|
55 |
font_mono=font_mono,
|
56 |
)
|
57 |
+
self.name = 'custom_theme'
|
58 |
super().set(
|
59 |
# Colors
|
60 |
+
input_background_fill_dark='#262626',
|
61 |
+
error_background_fill='#EF4444',
|
62 |
+
error_background_fill_dark='#171717',
|
63 |
+
error_border_color='#B91C1C',
|
64 |
+
error_border_color_dark='#EF4444',
|
65 |
+
error_icon_color='#B91C1C',
|
66 |
+
error_icon_color_dark='#EF4444',
|
67 |
+
|
68 |
# Shadows
|
69 |
+
input_shadow_focus='0 0 0 *shadow_spread #7C3AED80, *shadow_inset',
|
70 |
+
input_shadow_focus_dark='0 0 0 *shadow_spread #40404080, *shadow_inset',
|
71 |
+
|
72 |
# Button borders
|
73 |
+
button_border_width='0px',
|
74 |
+
input_border_width='1px',
|
75 |
+
input_background_fill='#F9FAFB',
|
76 |
+
|
77 |
# Gradients
|
78 |
+
stat_background_fill='linear-gradient(to right, #7C3AED, #D8B4FE)',
|
79 |
+
stat_background_fill_dark='linear-gradient(to right, #7C3AED, #5B21B6)',
|
80 |
+
checkbox_label_background_fill='#F3F4F6',
|
81 |
+
checkbox_label_background_fill_dark='#1F2937',
|
82 |
+
checkbox_label_background_fill_hover='#E5E7EB',
|
83 |
+
checkbox_label_background_fill_hover_dark='#374151',
|
84 |
+
|
85 |
# Primary Button
|
86 |
+
button_primary_background_fill='#111111',
|
87 |
+
button_primary_background_fill_dark='#171717',
|
88 |
+
button_primary_background_fill_hover='#3F3F3F',
|
89 |
+
button_primary_background_fill_hover_dark='#3F3F3F',
|
90 |
+
button_primary_text_color='#FFFFFF',
|
91 |
+
button_primary_text_color_dark='#FFFFFF',
|
92 |
+
|
93 |
# Secondary Button
|
94 |
+
button_secondary_background_fill='#E5E7EB',
|
95 |
+
button_secondary_background_fill_dark='#4B5563',
|
96 |
+
button_secondary_background_fill_hover='#D1D5DB',
|
97 |
+
button_secondary_background_fill_hover_dark='#374151',
|
98 |
+
button_secondary_text_color='#111827',
|
99 |
+
button_secondary_text_color_dark='#FFFFFF',
|
100 |
+
|
101 |
# Cancel Button
|
102 |
+
button_cancel_background_fill='#EF4444',
|
103 |
+
button_cancel_background_fill_dark='#B91C1C',
|
104 |
+
button_cancel_background_fill_hover='#DC2626',
|
105 |
+
button_cancel_background_fill_hover_dark='#991B1B',
|
106 |
+
button_cancel_text_color='#FFFFFF',
|
107 |
+
button_cancel_text_color_dark='#FFFFFF',
|
108 |
+
button_cancel_text_color_hover='#FFFFFF',
|
109 |
+
button_cancel_text_color_hover_dark='#FFFFFF',
|
110 |
+
|
111 |
# Other
|
112 |
+
border_color_accent_subdued='#A78BFA',
|
113 |
)
|
src/utils.py
CHANGED
@@ -96,24 +96,26 @@ def validate_prompt_length(prompt: str, max_length: int, min_length: int) -> Non
|
|
96 |
>>> validate_prompt_length("Hello world", max_length=500, min_length=5)
|
97 |
# Passes validation
|
98 |
|
99 |
-
>>> validate_prompt_length("", max_length=
|
100 |
-
# Raises ValueError: "Prompt must be at least
|
101 |
"""
|
102 |
-
logger.debug(f'Prompt length being validated: {len(prompt)} characters')
|
103 |
-
|
104 |
-
# Check if prompt is empty or too short
|
105 |
stripped_prompt = prompt.strip()
|
106 |
-
|
|
|
|
|
|
|
|
|
|
|
107 |
raise ValueError(
|
108 |
f'Prompt must be at least {min_length} character(s) long. '
|
109 |
-
f'Received only {
|
110 |
)
|
111 |
|
112 |
-
# Check if prompt
|
113 |
-
if
|
114 |
raise ValueError(
|
115 |
f'The prompt exceeds the maximum allowed length of {max_length} characters. '
|
116 |
-
f'Your prompt contains {
|
117 |
)
|
118 |
|
119 |
logger.debug(f'Prompt length validation passed for prompt: {truncate_text(stripped_prompt)}')
|
|
|
96 |
>>> validate_prompt_length("Hello world", max_length=500, min_length=5)
|
97 |
# Passes validation
|
98 |
|
99 |
+
>>> validate_prompt_length("", max_length=300, min_length=10)
|
100 |
+
# Raises ValueError: "Prompt must be at least 10 characters long."
|
101 |
"""
|
|
|
|
|
|
|
102 |
stripped_prompt = prompt.strip()
|
103 |
+
prompt_length = len(stripped_prompt)
|
104 |
+
|
105 |
+
logger.debug(f'Prompt length being validated: {prompt_length} characters')
|
106 |
+
|
107 |
+
# Check if prompt is too short
|
108 |
+
if prompt_length < min_length:
|
109 |
raise ValueError(
|
110 |
f'Prompt must be at least {min_length} character(s) long. '
|
111 |
+
f'Received only {prompt_length}.'
|
112 |
)
|
113 |
|
114 |
+
# Check if prompt exceeds max length
|
115 |
+
if prompt_length > max_length:
|
116 |
raise ValueError(
|
117 |
f'The prompt exceeds the maximum allowed length of {max_length} characters. '
|
118 |
+
f'Your prompt contains {prompt_length} characters.'
|
119 |
)
|
120 |
|
121 |
logger.debug(f'Prompt length validation passed for prompt: {truncate_text(stripped_prompt)}')
|