@danhachuel/thunderbolt 0.6.78 → 0.6.79

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 +127 -125
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -5633,57 +5633,58 @@ def render_tiktok_automation():
5633
5633
  if not channels:
5634
5634
  st.info("Cadastre primeiro um canal TikTok.")
5635
5635
  return
5636
- for channel in channels:
5637
- channel_id = str(channel["id"])
5638
- with st.container(border=True):
5639
- header_cols = st.columns([0.55, 2.35, 1.25, 0.82, 0.92, 1.2])
5640
- with header_cols[0]:
5641
- profile_image = _tiktok_avatar_url(channel)
5642
- if profile_image:
5643
- st.image(profile_image, width=48)
5644
- else:
5645
- st.markdown("### TT")
5646
- with header_cols[1]:
5647
- st.write(f"**{channel.get('name', 'Sem nome')}**")
5648
- st.caption(channel.get("handle") or channel.get("url") or "sem URL")
5649
- with header_cols[2]:
5650
- enabled = st.toggle("Automação ligada", value=bool(channel.get("automation_on", False)), key=f"tiktok_automation_on_{channel_id}")
5651
- with header_cols[3]:
5652
- schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"tiktok_automation_time_{channel_id}")
5653
- with header_cols[4]:
5654
- prompt_file = TIKTOK_PROMPT_MASTERS / str(channel.get("default_prompt_master") or channel.get("prompt_master") or "")
5655
- prompt_content = load_prompt_master_file(prompt_file) if prompt_file.is_file() else ""
5656
- calculated_time, calculated_source, calculated_words = _channel_average_video_time(channel, prompt_master=prompt_content)
5657
- average_video_time = st.text_input("Tempo Medio de Video", value=channel_video_time_value(channel), key=f"tiktok_average_video_time_{channel_id}", help=f"Referência média: {calculated_words or 0} palavras · fonte: {calculated_source}.")
5658
- default_cols = st.columns([1.0, 1.0, 1.35, 1.45, 1.55, 1.0], gap="small")
5659
- with default_cols[0]:
5660
- st.markdown("**Idioma do roteiro**")
5661
- st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
5662
- with default_cols[1]:
5663
- st.markdown("**Nicho Padrão**")
5664
- st.caption(channel_niche_label(channel))
5665
- with default_cols[2]:
5666
- st.markdown("**Fonte do vídeo**")
5667
- st.caption(channel_video_source_value(channel.get("style_wide")))
5668
- st.markdown("**Proporção do vídeo**")
5669
- st.caption(str(channel.get("video_aspect_ratio") or "Portrait 9:16"))
5670
- st.markdown("**Formato**")
5671
- st.caption(str(channel.get("format") or "Shorts"))
5672
- with default_cols[3]:
5673
- 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}")
5674
- with default_cols[4]:
5675
- automation_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_automation_format_{channel_id}")
5676
- with default_cols[5]:
5677
- if st.button("Guardar", key=f"tiktok_automation_save_{channel_id}", use_container_width=True, type="primary"):
5678
- if not valid_hhmm(schedule_time):
5679
- st.error("Use o formato HH:MM, por exemplo 08:30.")
5680
- elif not valid_hhmm(average_video_time):
5681
- st.error("O Tempo Medio de Video deve estar no formato MM:SS, por exemplo 12:00.")
5636
+ with st.expander("Canais cadastrados", expanded=False):
5637
+ for channel in channels:
5638
+ channel_id = str(channel["id"])
5639
+ with st.container(border=True):
5640
+ header_cols = st.columns([0.55, 2.35, 1.25, 0.82, 0.92, 1.2])
5641
+ with header_cols[0]:
5642
+ profile_image = _tiktok_avatar_url(channel)
5643
+ if profile_image:
5644
+ st.image(profile_image, width=48)
5682
5645
  else:
5683
- avatar_url = _tiktok_avatar_url(channel)
5684
- update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "average_video_time": average_video_time.strip() or DEFAULT_AVERAGE_VIDEO_TIME, "average_video_word_count": words_from_channel_time(average_video_time), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "format": automation_format, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
5685
- st.success("Automação TikTok guardada.")
5686
- st.rerun()
5646
+ st.markdown("### TT")
5647
+ with header_cols[1]:
5648
+ st.write(f"**{channel.get('name', 'Sem nome')}**")
5649
+ st.caption(channel.get("handle") or channel.get("url") or "sem URL")
5650
+ with header_cols[2]:
5651
+ enabled = st.toggle("Automação ligada", value=bool(channel.get("automation_on", False)), key=f"tiktok_automation_on_{channel_id}")
5652
+ with header_cols[3]:
5653
+ schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"tiktok_automation_time_{channel_id}")
5654
+ with header_cols[4]:
5655
+ prompt_file = TIKTOK_PROMPT_MASTERS / str(channel.get("default_prompt_master") or channel.get("prompt_master") or "")
5656
+ prompt_content = load_prompt_master_file(prompt_file) if prompt_file.is_file() else ""
5657
+ calculated_time, calculated_source, calculated_words = _channel_average_video_time(channel, prompt_master=prompt_content)
5658
+ average_video_time = st.text_input("Tempo Medio de Video", value=channel_video_time_value(channel), key=f"tiktok_average_video_time_{channel_id}", help=f"Referência média: {calculated_words or 0} palavras · fonte: {calculated_source}.")
5659
+ default_cols = st.columns([1.0, 1.0, 1.35, 1.45, 1.55, 1.0], gap="small")
5660
+ with default_cols[0]:
5661
+ st.markdown("**Idioma do roteiro**")
5662
+ st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
5663
+ with default_cols[1]:
5664
+ st.markdown("**Nicho Padrão**")
5665
+ st.caption(channel_niche_label(channel))
5666
+ with default_cols[2]:
5667
+ st.markdown("**Fonte do vídeo**")
5668
+ st.caption(channel_video_source_value(channel.get("style_wide")))
5669
+ st.markdown("**Proporção do vídeo**")
5670
+ st.caption(str(channel.get("video_aspect_ratio") or "Portrait 9:16"))
5671
+ st.markdown("**Formato**")
5672
+ st.caption(str(channel.get("format") or "Shorts"))
5673
+ with default_cols[3]:
5674
+ 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}")
5675
+ with default_cols[4]:
5676
+ automation_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_automation_format_{channel_id}")
5677
+ with default_cols[5]:
5678
+ if st.button("Guardar", key=f"tiktok_automation_save_{channel_id}", use_container_width=True, type="primary"):
5679
+ if not valid_hhmm(schedule_time):
5680
+ st.error("Use o formato HH:MM, por exemplo 08:30.")
5681
+ elif not valid_hhmm(average_video_time):
5682
+ st.error("O Tempo Medio de Video deve estar no formato MM:SS, por exemplo 12:00.")
5683
+ else:
5684
+ avatar_url = _tiktok_avatar_url(channel)
5685
+ update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "average_video_time": average_video_time.strip() or DEFAULT_AVERAGE_VIDEO_TIME, "average_video_word_count": words_from_channel_time(average_video_time), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "format": automation_format, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
5686
+ st.success("Automação TikTok guardada.")
5687
+ st.rerun()
5687
5688
  _render_tiktok_automation_cards()
5688
5689
 
5689
5690
  @st.fragment(run_every=5.0)
@@ -5807,82 +5808,83 @@ def render_automation():
5807
5808
  channels = [channel for channel in read_json("channels.json", []) if is_youtube_channel_record(channel)]
5808
5809
  if not channels:
5809
5810
  st.info("Nenhum canal cadastrado para configurar.")
5810
- for channel in channels:
5811
- channel_id = channel["id"]
5812
- with st.container(border=True):
5813
- blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
5814
- automation_blueprint = current_blueprint
5815
- automation_voice = current_voice
5816
- paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5817
- automation_format = str(channel.get("format") or "wide")
5818
- header_cols = st.columns([0.58, 2.15, 1.4, 1.15, 1.15, 1.25, 0.82, 0.92], gap="small")
5819
- with header_cols[0]:
5820
- if channel.get("thumbnail_url"):
5821
- st.image(channel["thumbnail_url"], width=48)
5822
- else:
5823
- st.markdown("### YT")
5824
- with header_cols[1]:
5825
- st.write(f"**{channel.get('name', 'Sem nome')}**")
5826
- st.caption(channel.get("handle") or channel.get("url") or "sem URL")
5827
- with header_cols[2]:
5828
- st.markdown("**Thumbnail Blueprint**")
5829
- st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
5830
- with header_cols[3]:
5831
- st.markdown("**Idioma do roteiro**")
5832
- st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
5833
- with header_cols[4]:
5834
- st.markdown("**Nicho Padrão**")
5835
- st.caption(channel_niche_label(channel))
5836
- with header_cols[5]:
5837
- st.markdown("**Fonte do vídeo**")
5838
- st.caption(channel_video_source_value(channel.get("style_wide")))
5839
- st.markdown("**Proporção do vídeo**")
5840
- st.caption(str(channel.get("video_aspect_ratio") or "Landscape 16:9"))
5841
- with header_cols[6]:
5842
- schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"automation_time_{channel_id}")
5843
- with header_cols[7]:
5844
- calculated_time, calculated_source, calculated_words = _channel_average_video_time(channel)
5845
- average_video_time = st.text_input("Tempo Medio de Video", value=channel_video_time_value(channel), key=f"average_video_time_{channel_id}", help=f"Referência média: {calculated_words or 0} palavras · fonte: {calculated_source}.")
5846
-
5847
- control_cols = st.columns([1.8, 1.8, 1.35, 1.2], gap="small")
5848
- with control_cols[0]:
5849
- automation_blueprint = st.selectbox(
5850
- "Blueprint Padrão",
5851
- blueprint_ids,
5852
- index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0,
5853
- format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"),
5854
- key=f"automation_blueprint_{channel_id}",
5855
- )
5856
- paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5857
- with control_cols[1]:
5858
- automation_voice = st.selectbox(
5859
- "Narrador/Voz Padrão",
5860
- voice_options,
5861
- index=voice_options.index(current_voice) if current_voice in voice_options else 0,
5862
- format_func=lambda item: item or "Sem voz padrão",
5863
- key=f"automation_voice_{channel_id}",
5864
- )
5865
- with control_cols[2]:
5866
- enabled = st.toggle("Automação ligada", value=bool(channel.get("automation_on", False)), key=f"automation_on_{channel_id}")
5867
- with control_cols[3]:
5868
- if st.button("Guardar", key=f"automation_save_{channel_id}", use_container_width=True, type="primary"):
5869
- if not valid_hhmm(schedule_time):
5870
- st.error("Use o formato HH:MM, por exemplo 08:30.")
5871
- elif not valid_hhmm(average_video_time):
5872
- st.error("O Tempo Medio de Video deve estar no formato MM:SS, por exemplo 12:00.")
5811
+ with st.expander("Canais cadastrados", expanded=False):
5812
+ for channel in channels:
5813
+ channel_id = channel["id"]
5814
+ with st.container(border=True):
5815
+ blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
5816
+ automation_blueprint = current_blueprint
5817
+ automation_voice = current_voice
5818
+ paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5819
+ automation_format = str(channel.get("format") or "wide")
5820
+ header_cols = st.columns([0.58, 2.15, 1.4, 1.15, 1.15, 1.25, 0.82, 0.92], gap="small")
5821
+ with header_cols[0]:
5822
+ if channel.get("thumbnail_url"):
5823
+ st.image(channel["thumbnail_url"], width=48)
5873
5824
  else:
5874
- update_channel(channel_id, {
5875
- "automation_on": bool(enabled),
5876
- "automation_time": schedule_time.strip(),
5877
- "average_video_time": average_video_time.strip() or DEFAULT_AVERAGE_VIDEO_TIME,
5878
- "average_video_word_count": words_from_channel_time(average_video_time),
5879
- "format": automation_format,
5880
- })
5881
- set_channel_defaults(channel_id, automation_blueprint, automation_voice)
5882
- paired_id = str(paired_thumbnail.get("id") or "Generic_Thumbnail_Blueprint")
5883
- update_channel(channel_id, {"thumbnail_blueprint_id": paired_id, "default_thumbnail_blueprint_id": paired_id})
5884
- st.success("Agendamento guardado.")
5885
- st.rerun()
5825
+ st.markdown("### YT")
5826
+ with header_cols[1]:
5827
+ st.write(f"**{channel.get('name', 'Sem nome')}**")
5828
+ st.caption(channel.get("handle") or channel.get("url") or "sem URL")
5829
+ with header_cols[2]:
5830
+ st.markdown("**Thumbnail Blueprint**")
5831
+ st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
5832
+ with header_cols[3]:
5833
+ st.markdown("**Idioma do roteiro**")
5834
+ st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
5835
+ with header_cols[4]:
5836
+ st.markdown("**Nicho Padrão**")
5837
+ st.caption(channel_niche_label(channel))
5838
+ with header_cols[5]:
5839
+ st.markdown("**Fonte do vídeo**")
5840
+ st.caption(channel_video_source_value(channel.get("style_wide")))
5841
+ st.markdown("**Proporção do vídeo**")
5842
+ st.caption(str(channel.get("video_aspect_ratio") or "Landscape 16:9"))
5843
+ with header_cols[6]:
5844
+ schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"automation_time_{channel_id}")
5845
+ with header_cols[7]:
5846
+ calculated_time, calculated_source, calculated_words = _channel_average_video_time(channel)
5847
+ average_video_time = st.text_input("Tempo Medio de Video", value=channel_video_time_value(channel), key=f"average_video_time_{channel_id}", help=f"Referência média: {calculated_words or 0} palavras · fonte: {calculated_source}.")
5848
+
5849
+ control_cols = st.columns([1.8, 1.8, 1.35, 1.2], gap="small")
5850
+ with control_cols[0]:
5851
+ automation_blueprint = st.selectbox(
5852
+ "Blueprint Padrão",
5853
+ blueprint_ids,
5854
+ index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0,
5855
+ format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"),
5856
+ key=f"automation_blueprint_{channel_id}",
5857
+ )
5858
+ paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5859
+ with control_cols[1]:
5860
+ automation_voice = st.selectbox(
5861
+ "Narrador/Voz Padrão",
5862
+ voice_options,
5863
+ index=voice_options.index(current_voice) if current_voice in voice_options else 0,
5864
+ format_func=lambda item: item or "Sem voz padrão",
5865
+ key=f"automation_voice_{channel_id}",
5866
+ )
5867
+ with control_cols[2]:
5868
+ enabled = st.toggle("Automação ligada", value=bool(channel.get("automation_on", False)), key=f"automation_on_{channel_id}")
5869
+ with control_cols[3]:
5870
+ if st.button("Guardar", key=f"automation_save_{channel_id}", use_container_width=True, type="primary"):
5871
+ if not valid_hhmm(schedule_time):
5872
+ st.error("Use o formato HH:MM, por exemplo 08:30.")
5873
+ elif not valid_hhmm(average_video_time):
5874
+ st.error("O Tempo Medio de Video deve estar no formato MM:SS, por exemplo 12:00.")
5875
+ else:
5876
+ update_channel(channel_id, {
5877
+ "automation_on": bool(enabled),
5878
+ "automation_time": schedule_time.strip(),
5879
+ "average_video_time": average_video_time.strip() or DEFAULT_AVERAGE_VIDEO_TIME,
5880
+ "average_video_word_count": words_from_channel_time(average_video_time),
5881
+ "format": automation_format,
5882
+ })
5883
+ set_channel_defaults(channel_id, automation_blueprint, automation_voice)
5884
+ paired_id = str(paired_thumbnail.get("id") or "Generic_Thumbnail_Blueprint")
5885
+ update_channel(channel_id, {"thumbnail_blueprint_id": paired_id, "default_thumbnail_blueprint_id": paired_id})
5886
+ st.success("Agendamento guardado.")
5887
+ st.rerun()
5886
5888
 
5887
5889
  _render_youtube_automation_cards()
5888
5890
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.78",
3
+ "version": "0.6.79",
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",