@danhachuel/thunderbolt 0.5.81 → 0.5.83

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 +41 -23
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -1494,6 +1494,23 @@ def _growth_channel_metrics(channel: dict[str, Any]) -> tuple[list[dict[str, Any
1494
1494
  def render_growth_youtube():
1495
1495
  st.title("Analista Growth Youtube")
1496
1496
  st.caption("Auditoria pública baseada nos 3 pilares críticos e nas métricas secundárias do agente Growth.")
1497
+ growth_settings = read_json("settings.json", {})
1498
+ youtube_data_api_ready = bool(str(growth_settings.get("youtube_api_key") or os.getenv("YOUTUBE_API_KEY") or "").strip())
1499
+ growth_accounts = growth_settings.get("youtube_batch_accounts", [])
1500
+ analytics_accounts_ready = False
1501
+ if isinstance(growth_accounts, list):
1502
+ for growth_account in growth_accounts:
1503
+ if isinstance(growth_account, dict) and youtube_batch_account_status(growth_account, STORAGE).ok:
1504
+ analytics_accounts_ready = True
1505
+ break
1506
+ st.markdown("**Estado das APIs de Growth**")
1507
+ api_status_cols = st.columns(2, gap="small")
1508
+ with api_status_cols[0]:
1509
+ st.caption("YouTube Data API v3: Metadados públicos")
1510
+ _api_status_badge("Configured" if youtube_data_api_ready else "Missing configuration", "ready" if youtube_data_api_ready else "missing")
1511
+ with api_status_cols[1]:
1512
+ st.caption("YouTube Analytics API: Métricas internas")
1513
+ _api_status_badge("Configured" if analytics_accounts_ready else "Missing configuration", "ready" if analytics_accounts_ready else "missing")
1497
1514
  channels = [item for item in read_json("channels.json", []) if isinstance(item, dict) and item.get("active", True) and is_youtube_channel_record(item)]
1498
1515
  if not channels:
1499
1516
  st.info("Cadastre pelo menos um canal YouTube em Canais YouTube para iniciar a análise.")
@@ -5441,7 +5458,11 @@ def render_automation():
5441
5458
  for channel in channels:
5442
5459
  channel_id = channel["id"]
5443
5460
  with st.container(border=True):
5444
- header_cols = st.columns([0.55, 2.35, 1.35, 1.5, 1.35])
5461
+ blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
5462
+ automation_blueprint = current_blueprint
5463
+ automation_voice = current_voice
5464
+ automation_format = str(channel.get("format") or "wide")
5465
+ header_cols = st.columns([0.62, 2.15, 1.55, 1.25, 1.25, 1.45, 1.3], gap="small")
5445
5466
  with header_cols[0]:
5446
5467
  if channel.get("thumbnail_url"):
5447
5468
  st.image(channel["thumbnail_url"], width=48)
@@ -5451,18 +5472,24 @@ def render_automation():
5451
5472
  st.write(f"**{channel.get('name', 'Sem nome')}**")
5452
5473
  st.caption(channel.get("handle") or channel.get("url") or "sem URL")
5453
5474
  with header_cols[2]:
5454
- enabled = st.toggle("Automação ligada", value=bool(channel.get("automation_on", False)), key=f"automation_on_{channel_id}")
5475
+ st.markdown("**Thumbnail Blueprint**")
5476
+ st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
5455
5477
  with header_cols[3]:
5456
- schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"automation_time_{channel_id}")
5457
- blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
5458
- default_cols = st.columns([1.0, 1.0, 1.35, 1.45, 1.55, 1.0], gap="small")
5459
- with default_cols[0]:
5460
5478
  st.markdown("**Idioma do roteiro**")
5461
5479
  st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
5462
- with default_cols[1]:
5480
+ with header_cols[4]:
5463
5481
  st.markdown("**Nicho Padrão**")
5464
5482
  st.caption(channel_niche_label(channel))
5465
- with default_cols[3]:
5483
+ with header_cols[5]:
5484
+ st.markdown("**Fonte do vídeo**")
5485
+ st.caption(channel_video_source_value(channel.get("style_wide")))
5486
+ st.markdown("**Proporção do vídeo**")
5487
+ st.caption(str(channel.get("video_aspect_ratio") or "Landscape 16:9"))
5488
+ with header_cols[6]:
5489
+ schedule_time = st.text_input("Horário (HH:MM)", value=channel.get("automation_time", "00:00"), key=f"automation_time_{channel_id}")
5490
+
5491
+ control_cols = st.columns([1.8, 1.8, 1.35, 1.2], gap="small")
5492
+ with control_cols[0]:
5466
5493
  automation_blueprint = st.selectbox(
5467
5494
  "Blueprint Padrão",
5468
5495
  blueprint_ids,
@@ -5470,19 +5497,8 @@ def render_automation():
5470
5497
  format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"),
5471
5498
  key=f"automation_blueprint_{channel_id}",
5472
5499
  )
5473
- with default_cols[2]:
5474
- paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5475
- st.markdown("**Fonte do vídeo**")
5476
- st.caption(channel_video_source_value(channel.get("style_wide")))
5477
- st.markdown("**Proporção do vídeo**")
5478
- st.caption(str(channel.get("video_aspect_ratio") or "Landscape 16:9"))
5479
- st.markdown("**Formato**")
5480
- st.caption(str(channel.get("format") or "wide"))
5481
- st.markdown("**Thumbnail Blueprint**")
5482
- st.caption(str(paired_thumbnail.get("name") or "Generic_Thumbnail_Blueprint"))
5483
- with default_cols[4]:
5484
- automation_format = st.selectbox("Formato", CHANNEL_FORMAT_OPTIONS, index=CHANNEL_FORMAT_OPTIONS.index(str(channel.get("format") or "wide")) if str(channel.get("format") or "wide") in CHANNEL_FORMAT_OPTIONS else 0, key=f"automation_format_{channel_id}")
5485
- with default_cols[5]:
5500
+ paired_thumbnail = thumbnail_blueprint_for_blueprint(automation_blueprint)
5501
+ with control_cols[1]:
5486
5502
  automation_voice = st.selectbox(
5487
5503
  "Narrador/Voz Padrão",
5488
5504
  voice_options,
@@ -5490,8 +5506,10 @@ def render_automation():
5490
5506
  format_func=lambda item: item or "Sem voz padrão",
5491
5507
  key=f"automation_voice_{channel_id}",
5492
5508
  )
5493
- with default_cols[5]:
5494
- if st.button("Guardar", key=f"automation_save_{channel_id}", use_container_width=True):
5509
+ with control_cols[2]:
5510
+ enabled = st.toggle("Automação ligada", value=bool(channel.get("automation_on", False)), key=f"automation_on_{channel_id}")
5511
+ with control_cols[3]:
5512
+ if st.button("Guardar", key=f"automation_save_{channel_id}", use_container_width=True, type="primary"):
5495
5513
  if not valid_hhmm(schedule_time):
5496
5514
  st.error("Use o formato HH:MM, por exemplo 08:30.")
5497
5515
  else:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.5.81",
3
+ "version": "0.5.83",
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",