@danhachuel/thunderbolt 0.4.84 → 0.4.85

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.
Files changed (2) hide show
  1. package/app/main.py +42 -11
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -1924,6 +1924,7 @@ def render_tiktok_channels():
1924
1924
  channels = _tiktok_channel_records()
1925
1925
  import_tab, spreadsheet_tab, manual_tab = render_localized_tabs(["Importar do Tiktok", "Canais em lote Planilha", "Cadastro manual"])
1926
1926
  prompt_ids, prompt_labels = _tiktok_prompt_options()
1927
+ _, _, _, voice_options, _ = channel_default_options({})
1927
1928
  with import_tab:
1928
1929
  st.caption("A pesquisa consulta exclusivamente a página pública do TikTok. Não existe método alternativo nem utilização de API.")
1929
1930
  st.divider()
@@ -1984,9 +1985,15 @@ def render_tiktok_channels():
1984
1985
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), format_func=language_label, key="tiktok_import_language")
1985
1986
  niche = st.text_input("Nicho", key="tiktok_import_niche")
1986
1987
  prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_import_prompt")
1988
+ voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_import_voice")
1989
+ automation_on = st.toggle("Automação ON", value=False, key="tiktok_import_automation")
1990
+ automation_time = st.text_input("Horário diário (HH:MM)", value="00:00", key="tiktok_import_automation_time")
1987
1991
  description = st.text_area("Descrição", value=str(imported.get("bio") or ""), key="tiktok_import_description")
1988
1992
  if st.form_submit_button("Cadastrar canal TikTok", type="primary"):
1989
- 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, "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "metrics_source": "tiktok_public_page", "subscriber_count": imported.get("subscriber_count"), "video_count": imported.get("video_count")})
1993
+ if not valid_hhmm(automation_time):
1994
+ st.error("O horário diário deve estar no formato HH:MM.")
1995
+ 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")})
1990
1997
  avatar_url = _tiktok_avatar_url(imported)
1991
1998
  update_channel(channel["id"], {"platform": "tiktok", "default_prompt_master": prompt, "prompt_master": prompt, "avatar_url": avatar_url, "thumbnail_url": avatar_url})
1992
1999
  st.session_state.pop("tiktok_channel_import", None)
@@ -2009,6 +2016,7 @@ def render_tiktok_channels():
2009
2016
  st.markdown(f"**{channel.get('name') or 'Perfil TikTok'}**")
2010
2017
  st.caption(f"{channel.get('handle') or 'sem handle'} · {channel.get('url') or 'sem URL'}")
2011
2018
  st.caption(channel.get("description") or "Perfil carregado da página pública do TikTok.")
2019
+ st.caption(f"Nicho: {channel.get('niche') or 'não configurado'}")
2012
2020
  with card_cols[2]:
2013
2021
  st.metric("Seguidores", format_metric_number(channel.get("subscriber_count")))
2014
2022
  with card_cols[3]:
@@ -2027,11 +2035,20 @@ def render_tiktok_channels():
2027
2035
  if st.button("Apagar card", key=f"tiktok_import_card_delete_{channel_id}"):
2028
2036
  st.session_state[f"tiktok_delete_{channel_id}"] = True
2029
2037
  st.rerun()
2030
- st.caption("Formato: Portrait 9:16")
2038
+ st.caption("Estilo wide: Portrait 9:16")
2039
+ st.caption(f"Automação: {'ligada' if automation_on else 'desligada'} · {automation_time}")
2040
+ _, _, _, card_voice_options, card_current_voice = channel_default_options(channel)
2041
+ 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}")
2042
+ automation_on = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_import_card_automation_{channel_id}")
2043
+ 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}")
2031
2044
  if st.button("Guardar", key=f"tiktok_import_card_save_{channel_id}", use_container_width=True, type="primary"):
2032
- update_channel(channel_id, {"platform": "tiktok", "default_prompt_master": selected_prompt, "prompt_master": selected_prompt, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait"})
2033
- st.success("Prompt Master do canal guardado.")
2034
- st.rerun()
2045
+ if not valid_hhmm(automation_time):
2046
+ st.error("O horário diário deve estar no formato HH:MM.")
2047
+ 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()})
2049
+ st.success("Dados do canal TikTok guardados.")
2050
+ st.rerun()
2051
+ st.caption("Estilo wide: Portrait 9:16")
2035
2052
  niche_action = st.columns([1.0, 4.0])
2036
2053
  with niche_action[0]:
2037
2054
  if st.button("Editar nicho", key=f"tiktok_import_card_niche_{channel_id}"):
@@ -2062,11 +2079,18 @@ def render_tiktok_channels():
2062
2079
  edit_url = st.text_input("URL pública", value=str(channel.get("url") or ""))
2063
2080
  edit_handle = st.text_input("Handle", value=str(channel.get("handle") or ""))
2064
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)
2082
+ 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
+ edit_automation = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"tiktok_edit_automation_{channel_id}")
2084
+ 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")
2065
2086
  edit_description = st.text_area("Descrição", value=str(channel.get("description") or ""))
2066
2087
  if st.form_submit_button("Guardar edição", type="primary"):
2067
- update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "description": edit_description.strip(), "platform": "tiktok"})
2068
- st.session_state.pop(f"tiktok_edit_{channel_id}", None)
2069
- st.rerun()
2088
+ if not valid_hhmm(edit_time):
2089
+ st.error("O horário diário deve estar no formato HH:MM.")
2090
+ 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"})
2092
+ st.session_state.pop(f"tiktok_edit_{channel_id}", None)
2093
+ st.rerun()
2070
2094
  with st.expander("Últimos 10 vídeos publicados", expanded=False):
2071
2095
  recent_videos = channel_videos_for(channel, limit=10)
2072
2096
  if not recent_videos:
@@ -2096,7 +2120,7 @@ def render_tiktok_channels():
2096
2120
  candidate = {"name": row.get("name", ""), "handle": row.get("handle", ""), "url": row.get("url", "")}
2097
2121
  if find_duplicate_channel(candidate, channels):
2098
2122
  continue
2099
- created_channel = create_channel(str(row.get("name") or row.get("handle") or "TikTok"), str(row.get("url") or ""), {"platform": "tiktok", "handle": row.get("handle", ""), "language": language_code(row.get("language") or "pt"), "niche": row.get("niche", ""), "description": row.get("description", ""), "default_prompt_master": row.get("prompt_master", ""), "prompt_master": row.get("prompt_master", ""), "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "automation_time": row.get("automation_time") or "00:00"})
2123
+ created_channel = create_channel(str(row.get("name") or row.get("handle") or "TikTok"), str(row.get("url") or ""), {"platform": "tiktok", "handle": row.get("handle", ""), "language": language_code(row.get("language") or "pt"), "niche": row.get("niche", ""), "description": row.get("description", ""), "default_prompt_master": row.get("prompt_master", ""), "prompt_master": row.get("prompt_master", ""), "default_voice": row.get("voice", ""), "voice": row.get("voice", ""), "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16", "automation_on": bool(row.get("automation_on", False)), "automation_time": row.get("automation_time") or "00:00"})
2100
2124
  update_channel(created_channel["id"], {"platform": "tiktok"})
2101
2125
  created += 1
2102
2126
  st.success(f"{created} canal(is) TikTok cadastrado(s).")
@@ -2106,7 +2130,7 @@ def render_tiktok_channels():
2106
2130
  st.subheader(f"Canais TikTok cadastrados ({len(channels)})")
2107
2131
  if channels:
2108
2132
  st.dataframe(
2109
- [{k: c.get(v, "") for k, v in {"URL canal": "url", "Nome canal": "name", "Handle canal": "handle", "Idioma": "language", "Nicho": "niche", "Prompt Master": "default_prompt_master", "Proporção": "video_aspect_ratio", "Activo": "active", "Descrição": "description"}.items()} for c in channels],
2133
+ [{k: c.get(v, "") for k, v in {"URL canal": "url", "Nome canal": "name", "Handle canal": "handle", "Idioma": "language", "Nicho": "niche", "Prompt Master": "default_prompt_master", "Narrador/Voz Padrão": "default_voice", "Estilo wide": "style_wide", "Automação ligada": "automation_on", "Horário diário (HH:MM)": "automation_time", "Proporção": "video_aspect_ratio", "Activo": "active", "Descrição": "description"}.items()} for c in channels],
2110
2134
  use_container_width=True,
2111
2135
  hide_index=True,
2112
2136
  height=360,
@@ -2121,10 +2145,17 @@ def render_tiktok_channels():
2121
2145
  language = st.selectbox("Idioma", list(LANGUAGE_CODES), format_func=language_label, key="tiktok_manual_channel_language")
2122
2146
  niche = st.text_input("Nicho", key="tiktok_manual_channel_niche")
2123
2147
  prompt = st.selectbox("Prompt Master padrão", prompt_ids, format_func=lambda item: prompt_labels.get(item, item), key="tiktok_manual_channel_prompt")
2148
+ voice = st.selectbox("Narrador/Voz Padrão", voice_options, format_func=lambda item: item or "Sem voz padrão", key="tiktok_manual_channel_voice")
2149
+ automation_on = st.toggle("Automação ON", value=False, key="tiktok_manual_channel_automation")
2150
+ automation_time = st.text_input("Horário diário (HH:MM)", value="00:00", key="tiktok_manual_channel_automation_time")
2151
+ st.caption("Estilo wide: Portrait 9:16")
2124
2152
  description = st.text_area("Descrição", key="tiktok_manual_channel_description")
2125
2153
  if st.form_submit_button("Guardar canal manual", type="primary"):
2126
2154
  reference = normalize_tiktok_reference(url or handle)
2127
- 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, "style_wide": "portrait", "video_aspect_ratio": "Portrait 9:16"})
2155
+ if not valid_hhmm(automation_time):
2156
+ st.error("O horário diário deve estar no formato HH:MM.")
2157
+ 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()})
2128
2159
  update_channel(channel["id"], {"platform": "tiktok"})
2129
2160
  st.success("Canal TikTok cadastrado.")
2130
2161
  st.rerun()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.84",
3
+ "version": "0.4.85",
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",