@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.
- package/app/main.py +127 -125
- 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
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
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
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
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
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
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
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
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