@danhachuel/thunderbolt 0.4.86 → 0.4.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app/main.py CHANGED
@@ -130,6 +130,16 @@ AI_STYLE_OPTIONS = [
130
130
  ]
131
131
 
132
132
  WIDE_STYLE_OPTIONS = ["Pexels/Pixabay", "full_ia", "Apenas Música"]
133
+ CHANNEL_ASPECT_RATIO_OPTIONS = ["Landscape 16:9", "Portrait 9:16", "Square 1:1"]
134
+ CHANNEL_FORMAT_OPTIONS = ["wide", "Shorts", "Music"]
135
+
136
+
137
+ def channel_video_source_value(value: Any) -> str:
138
+ return {"pexels": "Pexels/Pixabay", "full_ia": "full_ia", "music": "Apenas Música"}.get(str(value or "").strip(), str(value or "Pexels/Pixabay") if str(value or "").strip() in set(WIDE_STYLE_OPTIONS) else "Pexels/Pixabay")
139
+
140
+
141
+ def channel_video_source_storage(value: str) -> str:
142
+ return {"Pexels/Pixabay": "pexels", "full_ia": "full_ia", "Apenas Música": "music"}.get(value, value)
133
143
  MATERIAL_SOURCE_OPTIONS = ["Pexels", "Pixabay"]
134
144
 
135
145
  VIDEO_LANGUAGE_OPTIONS = [
@@ -1289,10 +1299,11 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
1289
1299
  edited_name = st.text_input("Nome do canal", value=str(channel.get("name") or ""))
1290
1300
  edited_url = st.text_input("URL", value=str(channel.get("url") or ""))
1291
1301
  edited_handle = st.text_input("Handle", value=str(channel.get("handle") or ""))
1292
- edited_language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=list(LANGUAGE_CODES).index(language_code(channel.get("language") or "pt")), format_func=language_label)
1293
- style_options = ["Pexels/Pixabay", "full_ia", "Apenas Música"]
1294
- style_value = {"pexels": "Pexels/Pixabay", "music": "Apenas Música"}.get(str(channel.get("style_wide") or "pexels"), str(channel.get("style_wide") or "Pexels/Pixabay"))
1295
- edited_style = st.selectbox("Estilo wide", style_options, index=style_options.index(style_value) if style_value in style_options else 0)
1302
+ language_options = VIDEO_LANGUAGE_SELECTION_OPTIONS
1303
+ edited_language = st.selectbox("Idioma do roteiro", language_options, index=language_options.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in language_options else 0, format_func=video_language_label)
1304
+ edited_style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
1305
+ edited_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Landscape 16:9")) if str(channel.get("video_aspect_ratio") or "Landscape 16:9") in CHANNEL_ASPECT_RATIO_OPTIONS else 0)
1306
+ edited_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "wide")) if str(channel.get("format") or "wide") in CHANNEL_FORMAT_OPTIONS else 0)
1296
1307
  edited_niche = st.text_input("Nicho", value=str(channel.get("niche") or channel_niche_label(channel) if channel_niche_label(channel) != "SEM NICHO CONFIGURADO" else "") )
1297
1308
  with edit_cols[1]:
1298
1309
  edited_blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"))
@@ -1314,7 +1325,7 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
1314
1325
  else:
1315
1326
  update_channel(channel_id, {
1316
1327
  "name": edited_name.strip(), "url": edited_url.strip(), "handle": edited_handle.strip(), "language": edited_language,
1317
- "style_wide": {"Pexels/Pixabay": "pexels", "Apenas Música": "music"}.get(edited_style, edited_style),
1328
+ "style_wide": channel_video_source_storage(edited_style), "video_aspect_ratio": edited_aspect, "format": edited_format,
1318
1329
  "niche": edited_niche.strip(), "reference_channels": [item.strip() for item in re.split(r"[,|]", edited_niche) if item.strip()],
1319
1330
  "default_blueprint_id": edited_blueprint.strip(), "blueprint_id": edited_blueprint.strip(),
1320
1331
  "default_voice": edited_voice.strip(), "voice": edited_voice.strip(),
@@ -1982,7 +1993,10 @@ def render_tiktok_channels():
1982
1993
  name = st.text_input("Nome canal", value=str(imported.get("name") or imported.get("username") or ""))
1983
1994
  url = st.text_input("URL canal", value=str(imported.get("public_url") or imported.get("url") or ""))
1984
1995
  handle = st.text_input("Handle canal", value=str(imported.get("handle") or ""))
1985
- language = st.selectbox("Idioma", list(LANGUAGE_CODES), format_func=language_label, key="tiktok_import_language")
1996
+ language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, format_func=video_language_label, key="tiktok_import_language")
1997
+ source_value = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, key="tiktok_import_source")
1998
+ aspect_value = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=1, key="tiktok_import_aspect")
1999
+ format_value = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=1, key="tiktok_import_format")
1986
2000
  niche = st.text_input("Nicho", key="tiktok_import_niche")
1987
2001
  prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_import_prompt")
1988
2002
  voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_import_voice")
@@ -1993,7 +2007,7 @@ def render_tiktok_channels():
1993
2007
  if not valid_hhmm(automation_time):
1994
2008
  st.error("O horário diário deve estar no formato HH:MM.")
1995
2009
  st.stop()
1996
- channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "automation_on": automation_on, "automation_time": automation_time.strip(), "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
2010
+ channel = create_channel(name.strip(), url.strip(), {"platform": "tiktok", "handle": handle.strip(), "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": channel_video_source_storage(source_value), "video_aspect_ratio": aspect_value, "format": format_value, "automation_on": automation_on, "automation_time": automation_time.strip(), "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
1997
2011
  avatar_url = _tiktok_avatar_url(imported)
1998
2012
  update_channel(channel["id"], {"platform": "tiktok", "default_prompt_master": prompt, "prompt_master": prompt, "avatar_url": avatar_url, "thumbnail_url": avatar_url})
1999
2013
  st.session_state.pop("tiktok_channel_import", None)
@@ -2035,20 +2049,22 @@ def render_tiktok_channels():
2035
2049
  if st.button("Apagar card", key=f"tiktok_import_card_delete_{channel_id}"):
2036
2050
  st.session_state[f"tiktok_delete_{channel_id}"] = True
2037
2051
  st.rerun()
2038
- st.caption("Estilo wide: Portrait 9:16")
2039
- st.caption(f"Automação: {'ligada' if automation_on else 'desligada'} · {automation_time}")
2040
2052
  _, _, _, card_voice_options, card_current_voice = channel_default_options(channel)
2041
2053
  selected_voice = st.selectbox("Narrador/Voz Padrão", card_voice_options, index=card_voice_options.index(card_current_voice) if card_current_voice in card_voice_options else 0, format_func=lambda item: item or "Sem voz padrão", key=f"tiktok_import_card_voice_{channel_id}")
2054
+ selected_source = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0, key=f"tiktok_import_card_source_{channel_id}")
2055
+ selected_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Portrait 9:16")) if str(channel.get("video_aspect_ratio") or "Portrait 9:16") in CHANNEL_ASPECT_RATIO_OPTIONS else 1, key=f"tiktok_import_card_aspect_{channel_id}")
2056
+ selected_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1, key=f"tiktok_import_card_format_{channel_id}")
2057
+ selected_language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label, key=f"tiktok_import_card_language_{channel_id}")
2042
2058
  automation_on = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_import_card_automation_{channel_id}")
2043
2059
  automation_time = st.text_input("Horário diário (HH:MM)", value=str(channel.get("automation_time") or "00:00"), key=f"tiktok_import_card_time_{channel_id}")
2044
2060
  if st.button("Guardar", key=f"tiktok_import_card_save_{channel_id}", use_container_width=True, type="primary"):
2045
2061
  if not valid_hhmm(automation_time):
2046
2062
  st.error("O horário diário deve estar no formato HH:MM.")
2047
2063
  else:
2048
- update_channel(channel_id, {"platform": "tiktok", "default_prompt_master": selected_prompt, "prompt_master": selected_prompt, "default_voice": selected_voice, "voice": selected_voice, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "automation_on": automation_on, "automation_time": automation_time.strip()})
2064
+ update_channel(channel_id, {"platform": "tiktok", "default_prompt_master": selected_prompt, "prompt_master": selected_prompt, "default_voice": selected_voice, "voice": selected_voice, "language": selected_language, "video_aspect_ratio": selected_aspect, "format": selected_format, "style_wide": channel_video_source_storage(selected_source), "automation_on": automation_on, "automation_time": automation_time.strip()})
2049
2065
  st.success("Dados do canal TikTok guardados.")
2050
2066
  st.rerun()
2051
- st.caption("Estilo wide: Portrait 9:16")
2067
+ st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))}")
2052
2068
  niche_action = st.columns([1.0, 4.0])
2053
2069
  with niche_action[0]:
2054
2070
  if st.button("Editar nicho", key=f"tiktok_import_card_niche_{channel_id}"):
@@ -2078,17 +2094,20 @@ def render_tiktok_channels():
2078
2094
  edit_name = st.text_input("Nome do canal", value=str(channel.get("name") or ""))
2079
2095
  edit_url = st.text_input("URL pública", value=str(channel.get("url") or ""))
2080
2096
  edit_handle = st.text_input("Handle", value=str(channel.get("handle") or ""))
2081
- edit_language = st.selectbox("Idioma do canal", list(LANGUAGE_CODES), index=list(LANGUAGE_CODES).index(language_code(channel.get("language") or "pt")), format_func=language_label)
2097
+ edit_language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(channel.get("language") or "pt")) if normalize_video_language(channel.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label)
2098
+ edit_source = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
2099
+ edit_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Portrait 9:16")) if str(channel.get("video_aspect_ratio") or "Portrait 9:16") in CHANNEL_ASPECT_RATIO_OPTIONS else 1)
2100
+ edit_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "Shorts")) if str(channel.get("format") or "Shorts") in CHANNEL_FORMAT_OPTIONS else 1)
2082
2101
  edit_voice = st.selectbox("Narrador/Voz Padrão", voice_options, index=voice_options.index(str(channel.get("default_voice") or channel.get("voice") or "")) if str(channel.get("default_voice") or channel.get("voice") or "") in voice_options else 0, format_func=lambda item: item or "Sem voz padrão")
2083
2102
  edit_automation = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_edit_automation_{channel_id}")
2084
2103
  edit_time = st.text_input("Horário diário (HH:MM)", value=str(channel.get("automation_time") or "00:00"))
2085
- st.caption("Estilo wide: Portrait 9:16")
2104
+ st.caption(f"Fonte do vídeo: {channel_video_source_value(channel.get('style_wide'))}")
2086
2105
  edit_description = st.text_area("Descrição", value=str(channel.get("description") or ""))
2087
2106
  if st.form_submit_button("Guardar edição", type="primary"):
2088
2107
  if not valid_hhmm(edit_time):
2089
2108
  st.error("O horário diário deve estar no formato HH:MM.")
2090
2109
  else:
2091
- update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2110
+ update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "format": edit_format, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2092
2111
  st.session_state.pop(f"tiktok_edit_{channel_id}", None)
2093
2112
  st.rerun()
2094
2113
  with st.expander("Últimos 10 vídeos publicados", expanded=False):
@@ -2142,7 +2161,10 @@ def render_tiktok_channels():
2142
2161
  url = st.text_input("URL canal", placeholder="https://www.tiktok.com/@conta", key="tiktok_manual_channel_url")
2143
2162
  name = st.text_input("Nome canal", key="tiktok_manual_channel_name")
2144
2163
  handle = st.text_input("Handle canal", key="tiktok_manual_channel_handle")
2145
- language = st.selectbox("Idioma", list(LANGUAGE_CODES), format_func=language_label, key="tiktok_manual_channel_language")
2164
+ language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, format_func=video_language_label, key="tiktok_manual_channel_language")
2165
+ source_value = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, key="tiktok_manual_channel_source")
2166
+ aspect_value = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=1, key="tiktok_manual_channel_aspect")
2167
+ format_value = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=1, key="tiktok_manual_channel_format")
2146
2168
  niche = st.text_input("Nicho", key="tiktok_manual_channel_niche")
2147
2169
  prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_manual_channel_prompt")
2148
2170
  voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_manual_channel_voice")
@@ -2155,7 +2177,7 @@ def render_tiktok_channels():
2155
2177
  if not valid_hhmm(automation_time):
2156
2178
  st.error("O horário diário deve estar no formato HH:MM.")
2157
2179
  st.stop()
2158
- channel = create_channel(name.strip() or reference["username"], reference["url"], {"platform": "tiktok", "handle": reference["handle"], "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "automation_on": automation_on, "automation_time": automation_time.strip()})
2180
+ channel = create_channel(name.strip() or reference["username"], reference["url"], {"platform": "tiktok", "handle": reference["handle"], "language": language, "niche": niche.strip(), "description": description.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "default_voice": voice, "voice": voice, "style_wide": channel_video_source_storage(source_value), "video_aspect_ratio": aspect_value, "format": format_value, "automation_on": automation_on, "automation_time": automation_time.strip()})
2159
2181
  update_channel(channel["id"], {"platform": "tiktok"})
2160
2182
  st.success("Canal TikTok cadastrado.")
2161
2183
  st.rerun()
@@ -2231,8 +2253,10 @@ def render_channels():
2231
2253
  name = st.text_input("Nome do canal", value=imported.get("name", ""), key="yt_import_name")
2232
2254
  url = st.text_input("URL", value=imported.get("url", source if source.startswith("http") else ""), key="yt_import_url")
2233
2255
  handle = st.text_input("Handle", value=imported.get("handle", ""), key="yt_import_handle")
2234
- language = st.selectbox("Idioma", list(LANGUAGE_CODES), index=list(LANGUAGE_CODES).index(language_code(imported.get("language") or "pt")), format_func=language_label, key="yt_import_language")
2235
- style = st.selectbox("Estilo wide", ["Pexels/Pixabay", "full_ia", "Apenas Música"], index=0, key="yt_import_style")
2256
+ language = st.selectbox("Idioma do roteiro", VIDEO_LANGUAGE_SELECTION_OPTIONS, index=VIDEO_LANGUAGE_SELECTION_OPTIONS.index(normalize_video_language(imported.get("language") or "pt")) if normalize_video_language(imported.get("language") or "pt") in VIDEO_LANGUAGE_SELECTION_OPTIONS else 0, format_func=video_language_label, key="yt_import_language")
2257
+ style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=0, key="yt_import_style")
2258
+ video_aspect_ratio = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, key="yt_import_aspect_ratio")
2259
+ video_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, key="yt_import_format")
2236
2260
  blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(imported_blueprint) if imported_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"), key="yt_import_blueprint")
2237
2261
  voice_options = voice_catalog(imported.get("default_voice") or imported.get("voice", ""))
2238
2262
  current_voice = imported.get("default_voice") or imported.get("voice", "")
@@ -2262,7 +2286,7 @@ def render_channels():
2262
2286
  "niche": niche.strip(),
2263
2287
  "reference_channels": [item.strip() for item in re.split(r"[,|]", niche) if item.strip()],
2264
2288
  "language": language,
2265
- "style_wide": {"Pexels/Pixabay": "pexels", "full_ia": "full_ia", "Apenas Música": "music"}.get(style, style),
2289
+ "style_wide": channel_video_source_storage(style), "video_aspect_ratio": video_aspect_ratio, "format": video_format,
2266
2290
  "blueprint_id": blueprint.strip(),
2267
2291
  "default_blueprint_id": blueprint.strip(),
2268
2292
  "default_voice": voice.strip(),
@@ -4968,14 +4992,18 @@ def render_tiktok_automation():
4968
4992
  schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"tiktok_automation_time_{channel_id}")
4969
4993
  default_cols = st.columns([1.0, 1.0, 1.35, 1.45, 1.55, 1.0], gap="small")
4970
4994
  with default_cols[0]:
4971
- st.markdown("**Idioma Padrão**")
4972
- st.caption(language_label(channel.get("language") or "pt"))
4995
+ st.markdown("**Idioma do roteiro**")
4996
+ st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
4973
4997
  with default_cols[1]:
4974
4998
  st.markdown("**Nicho Padrão**")
4975
4999
  st.caption(channel_niche_label(channel))
4976
5000
  with default_cols[2]:
4977
- st.markdown("**Formato da fila**")
4978
- st.caption("Portrait 9:16")
5001
+ st.markdown("**Fonte do vídeo**")
5002
+ st.caption(channel_video_source_value(channel.get("style_wide")))
5003
+ st.markdown("**Proporção do vídeo**")
5004
+ st.caption(str(channel.get("video_aspect_ratio") or "Portrait 9:16"))
5005
+ st.markdown("**Formato**")
5006
+ st.caption(str(channel.get("format") or "Shorts"))
4979
5007
  with default_cols[3]:
4980
5008
  prompt = st.selectbox("Prompt Master Padrão", prompt_ids, index=prompt_ids.index(channel.get("default_prompt_master", "")) if channel.get("default_prompt_master", "") in prompt_ids else 0, format_func=lambda item: prompt_labels.get(item, item), key=f"tiktok_automation_prompt_{channel_id}")
4981
5009
  with default_cols[4]:
@@ -5058,8 +5086,8 @@ def render_automation():
5058
5086
  blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
5059
5087
  default_cols = st.columns([1.0, 1.0, 1.35, 1.45, 1.55, 1.0], gap="small")
5060
5088
  with default_cols[0]:
5061
- st.markdown("**Idioma Padrão**")
5062
- st.caption(language_label(channel.get("language") or "pt"))
5089
+ st.markdown("**Idioma do roteiro**")
5090
+ st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
5063
5091
  with default_cols[1]:
5064
5092
  st.markdown("**Nicho Padrão**")
5065
5093
  st.caption(channel_niche_label(channel))
@@ -5073,6 +5101,12 @@ def render_automation():
5073
5101
  )
5074
5102
  with default_cols[2]:
5075
5103
  paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5104
+ st.markdown("**Fonte do vídeo**")
5105
+ st.caption(channel_video_source_value(channel.get("style_wide")))
5106
+ st.markdown("**Proporção do vídeo**")
5107
+ st.caption(str(channel.get("video_aspect_ratio") or "Landscape 16:9"))
5108
+ st.markdown("**Formato**")
5109
+ st.caption(str(channel.get("format") or "wide"))
5076
5110
  st.markdown("**Thumbnail Blueprint**")
5077
5111
  st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
5078
5112
  with default_cols[4]:
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import base64
6
+ import re
6
7
  from functools import lru_cache
7
8
  from typing import Any
8
9
 
@@ -1130,7 +1131,11 @@ def translate_ui_content(value: Any, language: Any = "pt") -> Any:
1130
1131
  translated = value
1131
1132
  for source, target in _sorted_translations(code):
1132
1133
  if source != target and source in translated:
1133
- translated = translated.replace(source, target)
1134
+ # Only replace complete tokens. This prevents the English token
1135
+ # ``Font`` from matching the Portuguese word ``Fonte`` and
1136
+ # producing malformed labels such as ``Fontee...``.
1137
+ pattern = rf"(?<!\w){re.escape(source)}(?!\w)"
1138
+ translated = re.sub(pattern, target, translated)
1134
1139
  return translated
1135
1140
 
1136
1141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.86",
3
+ "version": "0.4.88",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",