'
f''
f'{"✅" if completed else spinner_div}'
f''
f'{step}'
f'
'
for step, completed in steps
]
)
# status_description = '
Processing steps below.
'
status_description = ''
if error_text:
error_html = f'
{error_text}
'
else:
error_html = ''
return f'''
Status: {status}
{status_description}
{error_html}
{steps_html}
'''
def create_effect_span_prefix_postfix(effect_description: str):
"""Create an HTML span with effect tooltip."""
# NOTE: it's important not to use multiline python string in order not to add whitespaces
prefix = (
''
''
''
)
postfix = (
''
f'Effect: {effect_description}'
''
''
)
return prefix, postfix
def create_effect_span(text: str, effect_description: str) -> str:
prefix, postfix = create_effect_span_prefix_postfix(effect_description=effect_description)
res = f"{prefix}{text}{postfix}"
return res
def create_regular_span(text: str, bg_color: str) -> str:
"""Create a regular HTML span with background color."""
return f'{text}'
def _generate_legend_for_text_split_html(
character_phrases: list[CharacterPhrase], add_effect_legend: bool = False
) -> str:
html = (
"
"
"
Legend:
"
)
unique_characters = set(phrase.character or 'Unassigned' for phrase in character_phrases)
characters_sorted = sorted(unique_characters, key=lambda c: c.lower())
for character in characters_sorted:
color = get_character_color(character)
html += f"
{character}
"
if add_effect_legend:
html += (
'
'
'🎵 #1'
' - sound effect start position (hover to see the prompt)'
'
"]
index_mapping = {} # Mapping from original index to HTML index
orig_index = 0 # Index in the original text
html_index = len(html_items[0]) # Index in the HTML output
for phrase in character_phrases:
character = phrase.character or 'Unassigned'
text = phrase.text
color = get_character_color(character)
rgba_color = f"rgba({hex_to_rgb(color)}, 0.5)"
prefix = f""
suffix = ''
# Append the HTML for this phrase
html_items.append(f"{prefix}{text}{suffix}")
# Map each character index from the original text to the HTML text
html_index += len(prefix)
for i in range(len(text)):
index_mapping[orig_index + i] = html_index + i
# Update indices
orig_index += len(text)
html_index += len(text) + len(suffix)
html_items.append("
")
html = ''.join(html_items)
return html, index_mapping
def generate_text_split_inner_html_no_effect(character_phrases: list[CharacterPhrase]) -> str:
legend_html = _generate_legend_for_text_split_html(
character_phrases=character_phrases, add_effect_legend=False
)
text_split_html, char_ix_orig_2_html = _generate_text_split_html(
character_phrases=character_phrases
)
return legend_html + text_split_html
def generate_text_split_inner_html_with_effects(
character_phrases: list[CharacterPhrase],
sound_effects_descriptions: list[SoundEffectDescription],
) -> str:
legend_html = _generate_legend_for_text_split_html(
character_phrases=character_phrases, add_effect_legend=True
)
text_split_html, char_ix_orig_2_html = _generate_text_split_html(
character_phrases=character_phrases
)
if not sound_effects_descriptions:
return legend_html + text_split_html
prev_end = 0
content_html_parts = []
for ix, sed in enumerate(sound_effects_descriptions, start=1):
# NOTE: 'sed' contains approximate indices from the original text.
# that's why we use safe conversion before accessing char mapping
ix_start = get_collection_safe_index(
ix=sed.ix_start_orig_text, collection=char_ix_orig_2_html
)
# ix_end = get_collection_safe_index(ix=sed.ix_end_orig_text, collection=char_ix_orig_2_html)
html_start_ix = char_ix_orig_2_html[ix_start]
# html_end_ix = char_ix_orig_2_html[ix_end] # NOTE: this is incorrect
# BUG: here we take exact same number of characters as in text between sound effect tags.
# This introduces the bug: HTML text could be included in 'text_under_effect',
# due to inaccuracies in 'sed' indices.
# html_end_ix = html_start_ix + ix_end - ix_start # NOTE: this is correct
# NOTE: reason is that html may exist between original text characters
prefix = text_split_html[prev_end:html_start_ix]
if prefix:
content_html_parts.append(prefix)
# text_under_effect = text_split_html[html_start_ix:html_end_ix]
text_under_effect = f'🎵 #{ix}'
if text_under_effect:
effect_prefix, effect_postfix = create_effect_span_prefix_postfix(
effect_description=sed.prompt
)
text_under_effect_wrapped = f'{effect_prefix}{text_under_effect}{effect_postfix}'
content_html_parts.append(text_under_effect_wrapped)
# prev_end = html_end_ix
prev_end = html_start_ix
last = text_split_html[prev_end:]
if last:
content_html_parts.append(last)
content_html = ''.join(content_html_parts)
content_html = f'{EFFECT_CSS}
{content_html}
'
html = legend_html + content_html
return html
def generate_voice_mapping_inner_html(select_voice_chain_out):
character2props = {}
html = AUDIO_PLAYER_CSS
for key in set(select_voice_chain_out.character2props) | set(
select_voice_chain_out.character2voice
):
character_props = select_voice_chain_out.character2props.get(key, []).model_dump()
character_props["voice_id"] = select_voice_chain_out.character2voice.get(key, [])
character_props["sample_audio_url"] = get_audio_from_voice_id(character_props["voice_id"])
character2props[prettify_unknown_character_label(key)] = character_props
for character, voice_properties in sorted(character2props.items(), key=lambda x: x[0].lower()):
color = get_character_color(character)
audio_url = voice_properties.get('sample_audio_url', '')
html += f'''