@danhachuel/thunderbolt 0.4.47 → 0.4.49

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 +98 -0
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -1883,6 +1883,104 @@ def render_tiktok_channels():
1883
1883
  prompt_ids, prompt_labels = _tiktok_prompt_options()
1884
1884
  with import_tab:
1885
1885
  st.caption("A pesquisa consulta exclusivamente a página pública do TikTok. Não existe método alternativo nem utilização de API.")
1886
+ st.subheader(f"Canais TikTok cadastrados ({len(channels)})")
1887
+ if channels:
1888
+ for channel in channels:
1889
+ channel_id = str(channel.get("id") or "")
1890
+ with st.container(border=True):
1891
+ card_cols = st.columns([0.7, 2.3, 1.0, 1.0, 1.35, 1.25])
1892
+ with card_cols[0]:
1893
+ avatar_url = _tiktok_avatar_url(channel)
1894
+ if avatar_url:
1895
+ st.image(avatar_url, width=58)
1896
+ else:
1897
+ st.markdown("### TT")
1898
+ with card_cols[1]:
1899
+ st.markdown(f"**{channel.get('name') or 'Perfil TikTok'}**")
1900
+ st.caption(f"{channel.get('handle') or 'sem handle'} · {channel.get('url') or 'sem URL'}")
1901
+ st.caption(channel.get("description") or "Perfil carregado da página pública do TikTok.")
1902
+ with card_cols[2]:
1903
+ st.metric("Seguidores", format_metric_number(channel.get("subscriber_count")))
1904
+ with card_cols[3]:
1905
+ st.metric("Curtidas", format_metric_number(channel.get("likes_count")))
1906
+ with card_cols[4]:
1907
+ current_prompt = str(channel.get("default_prompt_master") or channel.get("prompt_master") or "")
1908
+ selected_prompt = st.selectbox("Prompt Master", prompt_ids, index=prompt_ids.index(current_prompt) if current_prompt in prompt_ids else 0, format_func=lambda item: prompt_labels.get(item, item), key=f"tiktok_import_card_prompt_{channel_id}")
1909
+ with card_cols[5]:
1910
+ st.caption("Formato")
1911
+ st.write("Portrait 9:16")
1912
+ if st.button("Guardar", key=f"tiktok_import_card_save_{channel_id}", use_container_width=True, type="primary"):
1913
+ update_channel(channel_id, {"platform": "tiktok", "default_prompt_master": selected_prompt, "prompt_master": selected_prompt, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait"})
1914
+ st.success("Prompt Master do canal guardado.")
1915
+ st.rerun()
1916
+ actions = st.columns([1.0, 1.0, 1.0, 1.0])
1917
+ with actions[0]:
1918
+ active = st.toggle("Activo", value=bool(channel.get("active", True)), key=f"tiktok_import_card_active_{channel_id}")
1919
+ if active != bool(channel.get("active", True)):
1920
+ update_channel(channel_id, {"active": active})
1921
+ st.rerun()
1922
+ with actions[1]:
1923
+ if st.button("Editar", key=f"tiktok_import_card_edit_{channel_id}", use_container_width=True):
1924
+ st.session_state[f"tiktok_edit_{channel_id}"] = not st.session_state.get(f"tiktok_edit_{channel_id}", False)
1925
+ st.rerun()
1926
+ with actions[2]:
1927
+ if st.button("Editar nicho", key=f"tiktok_import_card_niche_{channel_id}", use_container_width=True):
1928
+ st.session_state[f"tiktok_niche_{channel_id}"] = not st.session_state.get(f"tiktok_niche_{channel_id}", False)
1929
+ st.rerun()
1930
+ with actions[3]:
1931
+ if st.button("Apagar card", key=f"tiktok_import_card_delete_{channel_id}", use_container_width=True):
1932
+ st.session_state[f"tiktok_delete_{channel_id}"] = True
1933
+ st.rerun()
1934
+ if st.session_state.get(f"tiktok_delete_{channel_id}"):
1935
+ st.warning("Apagar este canal TikTok e os dados locais associados?")
1936
+ confirm_cols = st.columns(2)
1937
+ with confirm_cols[0]:
1938
+ if st.button("Confirmar apagar", type="primary", key=f"tiktok_confirm_delete_{channel_id}"):
1939
+ delete_channel(channel_id)
1940
+ st.session_state.pop(f"tiktok_delete_{channel_id}", None)
1941
+ st.rerun()
1942
+ with confirm_cols[1]:
1943
+ if st.button("Cancelar", key=f"tiktok_cancel_delete_{channel_id}"):
1944
+ st.session_state.pop(f"tiktok_delete_{channel_id}", None)
1945
+ st.rerun()
1946
+ if st.session_state.get(f"tiktok_niche_{channel_id}"):
1947
+ with st.form(f"tiktok_niche_form_{channel_id}"):
1948
+ niche_value = st.text_input("Nicho do canal", value=str(channel.get("niche") or ""))
1949
+ if st.form_submit_button("Guardar nicho", type="primary"):
1950
+ update_channel(channel_id, {"niche": niche_value.strip(), "reference_channels": [niche_value.strip()] if niche_value.strip() else []})
1951
+ st.session_state.pop(f"tiktok_niche_{channel_id}", None)
1952
+ st.rerun()
1953
+ if st.session_state.get(f"tiktok_edit_{channel_id}"):
1954
+ with st.form(f"tiktok_edit_form_{channel_id}"):
1955
+ edit_name = st.text_input("Nome do canal", value=str(channel.get("name") or ""))
1956
+ edit_url = st.text_input("URL pública", value=str(channel.get("url") or ""))
1957
+ edit_handle = st.text_input("Handle", value=str(channel.get("handle") or ""))
1958
+ 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)
1959
+ edit_description = st.text_area("Descrição", value=str(channel.get("description") or ""))
1960
+ if st.form_submit_button("Guardar edição", type="primary"):
1961
+ 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"})
1962
+ st.session_state.pop(f"tiktok_edit_{channel_id}", None)
1963
+ st.rerun()
1964
+ with st.expander("Últimos 10 vídeos publicados", expanded=False):
1965
+ recent_videos = channel_videos_for(channel, limit=10)
1966
+ if not recent_videos:
1967
+ st.info("Ainda não existem vídeos públicos sincronizados para este canal TikTok.")
1968
+ for recent_video in recent_videos[:10]:
1969
+ video_cols = st.columns([0.7, 3.5, 1.2])
1970
+ with video_cols[0]:
1971
+ if recent_video.get("thumbnail_url"):
1972
+ st.image(recent_video["thumbnail_url"], width=64)
1973
+ else:
1974
+ st.markdown("### TT")
1975
+ with video_cols[1]:
1976
+ st.write(f"**{recent_video.get('title') or 'Vídeo sem título'}**")
1977
+ st.caption(f"{recent_video.get('published_at') or 'Sem data'} · {recent_video.get('url') or 'Sem URL'}")
1978
+ with video_cols[2]:
1979
+ st.caption(str(recent_video.get("status") or "publicado").title())
1980
+ else:
1981
+ st.info("Ainda não existem canais TikTok cadastrados. Use o campo abaixo para pesquisar e cadastrar um perfil público.")
1982
+ st.divider()
1983
+ st.subheader("Importar novo canal pela página pública")
1886
1984
  source = st.text_input("URL pública ou @handle", placeholder="https://www.tiktok.com/@conta", key="tiktok_channel_source")
1887
1985
  lookup_cols = st.columns([1, 1])
1888
1986
  with lookup_cols[0]:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.47",
3
+ "version": "0.4.49",
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",