@danhachuel/thunderbolt 0.6.97 → 0.6.98
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/influencers_ui.py +12 -12
- package/app/main.py +214 -214
- package/app/social_networks_ui.py +19 -19
- package/hermes_ui/growth_bilibili.py +1 -1
- package/hermes_ui/growth_facebook_pages.py +3 -3
- package/hermes_ui/growth_instagram.py +3 -3
- package/hermes_ui/growth_tiktok.py +3 -3
- package/package.json +1 -1
|
@@ -186,11 +186,11 @@ def _render_instagram_posts(profile: Mapping[str, Any]) -> None:
|
|
|
186
186
|
with st.expander("Últimos posts do Instagram", expanded=False):
|
|
187
187
|
action_cols = st.columns(3)
|
|
188
188
|
with action_cols[0]:
|
|
189
|
-
load_clicked = st.button("Carregar últimos 10", key=f"load_instagram_posts_{profile_id}",
|
|
189
|
+
load_clicked = st.button("Carregar últimos 10", key=f"load_instagram_posts_{profile_id}", width="stretch")
|
|
190
190
|
with action_cols[1]:
|
|
191
|
-
refresh_clicked = st.button("Actualizar tudo", key=f"refresh_instagram_posts_{profile_id}",
|
|
191
|
+
refresh_clicked = st.button("Actualizar tudo", key=f"refresh_instagram_posts_{profile_id}", width="stretch")
|
|
192
192
|
with action_cols[2]:
|
|
193
|
-
download_clicked = bool(posts) and st.download_button("Baixar todos", data=_instagram_posts_archive(posts), file_name=f"instagram-{profile_id}-posts.zip", mime="application/zip", key=f"download_instagram_posts_{profile_id}",
|
|
193
|
+
download_clicked = bool(posts) and st.download_button("Baixar todos", data=_instagram_posts_archive(posts), file_name=f"instagram-{profile_id}-posts.zip", mime="application/zip", key=f"download_instagram_posts_{profile_id}", width="stretch")
|
|
194
194
|
if load_clicked or refresh_clicked:
|
|
195
195
|
target = max(10, len(posts)) if refresh_clicked else 10
|
|
196
196
|
posts_ok, posts_message, loaded_posts = _load_instagram_posts(profile, limit=target)
|
|
@@ -212,13 +212,13 @@ def _render_instagram_posts(profile: Mapping[str, Any]) -> None:
|
|
|
212
212
|
post_cols = st.columns([1, 3])
|
|
213
213
|
with post_cols[0]:
|
|
214
214
|
if _clean(post.get("image_url")):
|
|
215
|
-
st.image(post["image_url"],
|
|
215
|
+
st.image(post["image_url"], width="stretch")
|
|
216
216
|
with post_cols[1]:
|
|
217
217
|
if _clean(post.get("url")):
|
|
218
218
|
st.link_button("Abrir post", post["url"])
|
|
219
219
|
if _clean(post.get("caption")):
|
|
220
220
|
st.caption(post["caption"])
|
|
221
|
-
if st.button("Mostrar + 10", key=f"more_instagram_posts_{profile_id}",
|
|
221
|
+
if st.button("Mostrar + 10", key=f"more_instagram_posts_{profile_id}", width="stretch"):
|
|
222
222
|
posts_ok, posts_message, loaded_posts = _load_instagram_posts(profile, limit=len(posts) + 10)
|
|
223
223
|
if posts_ok:
|
|
224
224
|
_persist_instagram_posts(profile, loaded_posts)
|
|
@@ -314,11 +314,11 @@ def render_meta_api_cards(settings: dict[str, Any], kind: str) -> None:
|
|
|
314
314
|
token = st.text_input("Access Token", value=_clean(card.get("access_token")), type="password", key=f"{kind}_api_{card_id}_token")
|
|
315
315
|
action_cols = st.columns(3)
|
|
316
316
|
with action_cols[0]:
|
|
317
|
-
test_clicked = st.form_submit_button("Teste chamada API",
|
|
317
|
+
test_clicked = st.form_submit_button("Teste chamada API", width="stretch")
|
|
318
318
|
with action_cols[1]:
|
|
319
|
-
save_clicked = st.form_submit_button("Guardar Card", type="primary",
|
|
319
|
+
save_clicked = st.form_submit_button("Guardar Card", type="primary", width="stretch")
|
|
320
320
|
with action_cols[2]:
|
|
321
|
-
delete_clicked = st.form_submit_button("Apagar Card",
|
|
321
|
+
delete_clicked = st.form_submit_button("Apagar Card", width="stretch")
|
|
322
322
|
edited = {
|
|
323
323
|
"id": card_id,
|
|
324
324
|
"label": label.strip(),
|
|
@@ -345,7 +345,7 @@ def render_meta_api_cards(settings: dict[str, Any], kind: str) -> None:
|
|
|
345
345
|
_persist_api_cards(settings, kind, cards)
|
|
346
346
|
st.success(f"{title} apagada.")
|
|
347
347
|
st.rerun()
|
|
348
|
-
if st.button("Adicionar nova API", type="primary",
|
|
348
|
+
if st.button("Adicionar nova API", type="primary", width="stretch", key=f"add_{kind}_api_card"):
|
|
349
349
|
identifier_key = "account_id" if is_instagram else "page_id"
|
|
350
350
|
cards.append({"id": f"{kind}-api-{uuid.uuid4().hex[:10]}", "label": f"Conta {len(cards) + 1}", identifier_key: "", "access_token": ""})
|
|
351
351
|
_persist_api_cards(settings, kind, cards)
|
|
@@ -498,29 +498,29 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
|
|
|
498
498
|
st.rerun()
|
|
499
499
|
st.warning(refreshed_message)
|
|
500
500
|
with edit_col:
|
|
501
|
-
if st.button("Editar", key=f"edit_instagram_button_{profile_id}",
|
|
501
|
+
if st.button("Editar", key=f"edit_instagram_button_{profile_id}", width="stretch"):
|
|
502
502
|
for field in ("name", "handle", "bio", "country", "language", "posts", "followers", "following"):
|
|
503
503
|
st.session_state.pop(f"instagram_edit_{field}_{profile_id}", None)
|
|
504
504
|
st.session_state[edit_key] = True
|
|
505
505
|
st.rerun()
|
|
506
|
-
if st.button("Apagar", key=f"delete_instagram_button_{profile_id}",
|
|
506
|
+
if st.button("Apagar", key=f"delete_instagram_button_{profile_id}", width="stretch"):
|
|
507
507
|
st.session_state[f"confirm_delete_instagram_{profile_id}"] = True
|
|
508
508
|
st.rerun()
|
|
509
509
|
url = _clean(profile.get("url"))
|
|
510
510
|
if url:
|
|
511
|
-
st.link_button("Abrir Instagram", url,
|
|
511
|
+
st.link_button("Abrir Instagram", url, width="stretch")
|
|
512
512
|
|
|
513
513
|
if st.session_state.get(f"confirm_delete_instagram_{profile_id}"):
|
|
514
514
|
st.warning("Apagar este card remove o cadastro da conta Instagram, mas não os ficheiros associados.")
|
|
515
515
|
confirm_cols = st.columns(2)
|
|
516
516
|
with confirm_cols[0]:
|
|
517
|
-
if st.button("Confirmar apagar", type="primary", key=f"confirm_delete_instagram_button_{profile_id}",
|
|
517
|
+
if st.button("Confirmar apagar", type="primary", key=f"confirm_delete_instagram_button_{profile_id}", width="stretch"):
|
|
518
518
|
delete_channel(profile_id)
|
|
519
519
|
st.session_state.pop(f"confirm_delete_instagram_{profile_id}", None)
|
|
520
520
|
st.success("Conta Instagram apagada.")
|
|
521
521
|
st.rerun()
|
|
522
522
|
with confirm_cols[1]:
|
|
523
|
-
if st.button("Cancelar", key=f"cancel_delete_instagram_button_{profile_id}",
|
|
523
|
+
if st.button("Cancelar", key=f"cancel_delete_instagram_button_{profile_id}", width="stretch"):
|
|
524
524
|
st.session_state.pop(f"confirm_delete_instagram_{profile_id}", None)
|
|
525
525
|
st.rerun()
|
|
526
526
|
|
|
@@ -537,7 +537,7 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
|
|
|
537
537
|
posts = st.number_input("posts", min_value=0, value=_metric_input(_profile_metric(profile, "post_count", "posts", "media_count")), placeholder="Não encontrado", key=f"instagram_edit_posts_{profile_id}")
|
|
538
538
|
followers = st.number_input("Seguidores", min_value=0, value=_metric_input(_profile_metric(profile, "subscriber_count", "followers_count", "follower_count", "followers", "edge_followed_by")), placeholder="Não encontrado", key=f"instagram_edit_followers_{profile_id}")
|
|
539
539
|
following = st.number_input("seguindo", min_value=0, value=_metric_input(_profile_metric(profile, "following_count", "following", "follows", "followingCount", "edge_follow")), placeholder="Não encontrado", key=f"instagram_edit_following_{profile_id}")
|
|
540
|
-
save_edit = st.form_submit_button("Guardar alterações", type="primary",
|
|
540
|
+
save_edit = st.form_submit_button("Guardar alterações", type="primary", width="stretch")
|
|
541
541
|
if save_edit:
|
|
542
542
|
update_channel(profile_id, {"name": name.strip(), "handle": handle.strip(), "bio": bio, "bio_raw": bio, "country": _normalise_country(country), "language": language, "post_count": _metric_input(posts), "subscriber_count": _metric_input(followers), "following_count": _metric_input(following)})
|
|
543
543
|
st.session_state.pop(edit_key, None)
|
|
@@ -558,7 +558,7 @@ def _render_instagram_card(profile: dict[str, Any], characters: list[dict[str, A
|
|
|
558
558
|
character_labels = {"": "Sem personagem associado"} | {_clean(item.get("id")): _clean(item.get("name")) or _clean(item.get("id")) for item in characters}
|
|
559
559
|
current_character = _clean(profile.get("character_id"))
|
|
560
560
|
selected_character = st.selectbox("Personagem", character_options, index=character_options.index(current_character) if current_character in character_options else 0, format_func=lambda value: character_labels.get(value, value), key=f"instagram_character_{profile_id}")
|
|
561
|
-
if st.button("Atrelar Personagem", key=f"attach_instagram_character_{profile_id}",
|
|
561
|
+
if st.button("Atrelar Personagem", key=f"attach_instagram_character_{profile_id}", width="stretch"):
|
|
562
562
|
update_channel(profile_id, {"character_id": selected_character})
|
|
563
563
|
st.success("Personagem associado à conta Instagram.")
|
|
564
564
|
st.rerun()
|
|
@@ -574,9 +574,9 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
574
574
|
source = st.text_input("URL pública ou @handler Instagram", placeholder="https://www.instagram.com/conta/ ou @conta", key="social_instagram_source")
|
|
575
575
|
search_cols = st.columns(2)
|
|
576
576
|
with search_cols[0]:
|
|
577
|
-
search_clicked = st.button("Pesquisar conta pública", type="primary",
|
|
577
|
+
search_clicked = st.button("Pesquisar conta pública", type="primary", width="stretch", key="social_instagram_search")
|
|
578
578
|
with search_cols[1]:
|
|
579
|
-
if st.button("Limpar pesquisa",
|
|
579
|
+
if st.button("Limpar pesquisa", width="stretch", key="social_instagram_clear"):
|
|
580
580
|
for key in ("social_instagram_result", "social_instagram_ok", "social_instagram_message"):
|
|
581
581
|
st.session_state.pop(key, None)
|
|
582
582
|
st.rerun()
|
|
@@ -622,7 +622,7 @@ def render_social_networks(settings: dict[str, Any]) -> None:
|
|
|
622
622
|
character_options = [""] + [_clean(item.get("id")) for item in characters if _clean(item.get("id"))]
|
|
623
623
|
character_labels = {"": "Sem personagem associado"} | {_clean(item.get("id")): _clean(item.get("name")) or _clean(item.get("id")) for item in characters}
|
|
624
624
|
selected_character = st.selectbox("Personagem", character_options, format_func=lambda value: character_labels.get(value, value), key=f"social_instagram_result_character_{result_widget_id}")
|
|
625
|
-
save_profile = st.form_submit_button("Cadastrar conta Instagram", type="primary",
|
|
625
|
+
save_profile = st.form_submit_button("Cadastrar conta Instagram", type="primary", width="stretch")
|
|
626
626
|
if save_profile:
|
|
627
627
|
profile_data = dict(data)
|
|
628
628
|
profile_data.update({"name": name, "bio": bio, "bio_raw": bio, "post_count": _metric_input(posts), "subscriber_count": _metric_input(followers), "following_count": _metric_input(following)})
|
|
@@ -142,7 +142,7 @@ def render_growth_bilibili() -> None:
|
|
|
142
142
|
)
|
|
143
143
|
with status_col:
|
|
144
144
|
st.write("")
|
|
145
|
-
st.button("ANALISAR CANAL", disabled=True,
|
|
145
|
+
st.button("ANALISAR CANAL", disabled=True, width="stretch", key="growth_bilibili_analyse_disabled")
|
|
146
146
|
st.caption("A ligação ao Studio será usada quando autorizada.")
|
|
147
147
|
|
|
148
148
|
st.subheader("Dashboard de Growth")
|
|
@@ -131,7 +131,7 @@ def render_growth_facebook_pages() -> None:
|
|
|
131
131
|
selected = st.selectbox("Página a analisar", pages, format_func=lambda item: str(item.get("name") or item.get("page_name") or "Página sem nome"), key="growth_facebook_page")
|
|
132
132
|
with action_col:
|
|
133
133
|
st.write("")
|
|
134
|
-
analyse_clicked = st.button("ANALISAR PÁGINA", type="primary",
|
|
134
|
+
analyse_clicked = st.button("ANALISAR PÁGINA", type="primary", width="stretch", key="growth_facebook_analyse")
|
|
135
135
|
st.markdown("**Estado das APIs de Growth**")
|
|
136
136
|
api_cols = st.columns(2, gap="small")
|
|
137
137
|
with api_cols[0]:
|
|
@@ -161,9 +161,9 @@ def render_growth_facebook_pages() -> None:
|
|
|
161
161
|
st.write("")
|
|
162
162
|
path = Path(str(selected_record.get("report_path") or "")) if selected_record else Path()
|
|
163
163
|
if path.is_file():
|
|
164
|
-
st.download_button("BAIXAR ANALISE COMPLETA", path.read_bytes(), file_name=path.name, mime="text/markdown",
|
|
164
|
+
st.download_button("BAIXAR ANALISE COMPLETA", path.read_bytes(), file_name=path.name, mime="text/markdown", width="stretch", key=f"facebook_download_{selected_record['code']}")
|
|
165
165
|
else:
|
|
166
|
-
st.button("BAIXAR ANALISE COMPLETA", disabled=True,
|
|
166
|
+
st.button("BAIXAR ANALISE COMPLETA", disabled=True, width="stretch", key="facebook_download_disabled")
|
|
167
167
|
st.subheader("Dashboard de Growth")
|
|
168
168
|
st.caption("3 pilares críticos em destaque e camadas operacionais abaixo. Vermelho: 0–30 · Amarelo: 31–69 · Verde: 70–100.")
|
|
169
169
|
def item(label: str, target: str, available: bool = False) -> tuple[str, str, str, str, str]:
|
|
@@ -188,7 +188,7 @@ def render_growth_instagram() -> None:
|
|
|
188
188
|
selected = st.selectbox("Conta a analisar", channels, format_func=lambda item: str(item.get("name") or item.get("handle") or "Conta sem nome"), key="growth_instagram_channel")
|
|
189
189
|
with action_col:
|
|
190
190
|
st.write("")
|
|
191
|
-
analyse_clicked = st.button("ANALISAR CONTA", type="primary",
|
|
191
|
+
analyse_clicked = st.button("ANALISAR CONTA", type="primary", width="stretch", key="growth_instagram_analyse")
|
|
192
192
|
st.markdown("**Estado das APIs de Growth**")
|
|
193
193
|
api_cols = st.columns(2, gap="small")
|
|
194
194
|
with api_cols[0]:
|
|
@@ -219,9 +219,9 @@ def render_growth_instagram() -> None:
|
|
|
219
219
|
st.write("")
|
|
220
220
|
report_path = Path(str(selected_record.get("report_path") or "")) if selected_record else Path()
|
|
221
221
|
if report_path.is_file():
|
|
222
|
-
st.download_button("BAIXAR ANALISE COMPLETA", report_path.read_bytes(), file_name=report_path.name, mime="text/markdown",
|
|
222
|
+
st.download_button("BAIXAR ANALISE COMPLETA", report_path.read_bytes(), file_name=report_path.name, mime="text/markdown", width="stretch", key=f"instagram_download_{selected_record['code']}")
|
|
223
223
|
else:
|
|
224
|
-
st.button("BAIXAR ANALISE COMPLETA", disabled=True,
|
|
224
|
+
st.button("BAIXAR ANALISE COMPLETA", disabled=True, width="stretch", key="instagram_download_disabled")
|
|
225
225
|
st.subheader("Dashboard de Growth")
|
|
226
226
|
st.caption("3 pilares críticos em destaque e camadas operacionais abaixo. Vermelho: 0–30 · Amarelo: 31–69 · Verde: 70–100.")
|
|
227
227
|
def item(label: str, target: str, available: bool = False) -> tuple[str, str, str, str, str]:
|
|
@@ -185,7 +185,7 @@ def render_growth_tiktok() -> None:
|
|
|
185
185
|
selected = st.selectbox("Conta a analisar", channels, format_func=lambda item: str(item.get("name") or item.get("handle") or "Conta sem nome"), key="growth_tiktok_channel")
|
|
186
186
|
with action_col:
|
|
187
187
|
st.write("")
|
|
188
|
-
analyse_clicked = st.button("ANALISAR CONTA", type="primary",
|
|
188
|
+
analyse_clicked = st.button("ANALISAR CONTA", type="primary", width="stretch", key="growth_tiktok_analyse")
|
|
189
189
|
st.markdown("**Estado das APIs de Growth**")
|
|
190
190
|
api_cols = st.columns(2, gap="small")
|
|
191
191
|
with api_cols[0]:
|
|
@@ -216,9 +216,9 @@ def render_growth_tiktok() -> None:
|
|
|
216
216
|
st.write("")
|
|
217
217
|
report_path = Path(str(selected_record.get("report_path") or "")) if selected_record else Path()
|
|
218
218
|
if report_path.is_file():
|
|
219
|
-
st.download_button("BAIXAR ANALISE COMPLETA", report_path.read_bytes(), file_name=report_path.name, mime="text/markdown",
|
|
219
|
+
st.download_button("BAIXAR ANALISE COMPLETA", report_path.read_bytes(), file_name=report_path.name, mime="text/markdown", width="stretch", key=f"tiktok_download_{selected_record['code']}")
|
|
220
220
|
else:
|
|
221
|
-
st.button("BAIXAR ANALISE COMPLETA", disabled=True,
|
|
221
|
+
st.button("BAIXAR ANALISE COMPLETA", disabled=True, width="stretch", key="tiktok_download_disabled")
|
|
222
222
|
st.subheader("Dashboard de Growth")
|
|
223
223
|
st.caption("3 pilares críticos em destaque e camadas operacionais abaixo. Vermelho: 0–30 · Amarelo: 31–69 · Verde: 70–100.")
|
|
224
224
|
def item(label: str, target: str, available: bool = False) -> tuple[str, str, str, str, str]:
|
package/package.json
CHANGED