@danhachuel/thunderbolt 0.7.4 → 0.7.6

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 CHANGED
@@ -3252,17 +3252,18 @@ def _saved_video_draft_records() -> list[dict[str, Any]]:
3252
3252
  return records
3253
3253
 
3254
3254
 
3255
- def _seed_resume_video_settings(record: dict[str, Any]) -> None:
3255
+ def _seed_resume_video_settings(record: dict[str, Any], prefix: str = "new_video") -> None:
3256
3256
  """Load one persisted record into the namespaced resume widgets."""
3257
3257
  resume_id = str(record.get("resume_id") or "")
3258
- if st.session_state.get("new_video_resume_loaded_id") == resume_id:
3258
+ state_prefix = f"{prefix}_resume"
3259
+ if st.session_state.get(f"{state_prefix}_loaded_id") == resume_id:
3259
3260
  return
3260
3261
  generation_settings = record.get("generation_settings") if isinstance(record.get("generation_settings"), dict) else {}
3261
- st.session_state["new_video_resume_sections"] = missing_setting_sections(generation_settings)
3262
+ st.session_state[f"{state_prefix}_sections"] = missing_setting_sections(generation_settings)
3262
3263
  for suffix in setting_widget_suffixes():
3263
3264
  if suffix in generation_settings:
3264
- st.session_state[f"new_video_resume_{suffix}"] = generation_settings[suffix]
3265
- st.session_state["new_video_resume_loaded_id"] = resume_id
3265
+ st.session_state[f"{state_prefix}_{suffix}"] = generation_settings[suffix]
3266
+ st.session_state[f"{state_prefix}_loaded_id"] = resume_id
3266
3267
 
3267
3268
 
3268
3269
  def _create_video_task_from_saved_script(record: dict[str, Any], channel: dict[str, Any], settings: dict[str, Any]) -> list[dict[str, Any]]:
@@ -3313,7 +3314,7 @@ def _create_video_task_from_saved_script(record: dict[str, Any], channel: dict[s
3313
3314
  return create_tasks_for_batch(batch)
3314
3315
 
3315
3316
 
3316
- def render_video_from_draft() -> None:
3317
+ def render_video_from_draft(prefix: str = "new_video") -> None:
3317
3318
  """Render the continuation flow for saved scripts and local pipeline drafts."""
3318
3319
  st.subheader("Roteiros guardados")
3319
3320
  st.caption("Seleccione um roteiro guardado para continuar a criação do vídeo sem perder o conteúdo já preparado.")
@@ -3327,15 +3328,15 @@ def render_video_from_draft() -> None:
3327
3328
  "Seleccione um roteiro",
3328
3329
  list(record_by_id),
3329
3330
  format_func=lambda identifier: f"{record_by_id[identifier].get('title') or 'Roteiro sem título'} · {record_by_id[identifier].get('source_label') or 'Rascunho'}",
3330
- key="new_video_resume_selected",
3331
+ key=f"{prefix}_resume_selected",
3331
3332
  )
3332
3333
  record = record_by_id[selected_id]
3333
- _seed_resume_video_settings(record)
3334
+ _seed_resume_video_settings(record, prefix)
3334
3335
  with st.container(border=True):
3335
3336
  st.markdown(f"**Video Subject:** {record.get('video_subject') or '—'}")
3336
3337
  st.caption(f"{record.get('source_label') or 'Rascunho'} · {record.get('channel_name') or 'Documento independente'} · Blueprint: {record.get('blueprint_name') or '—'}")
3337
3338
  if record.get("video_script"):
3338
- st.text_area("Video Script (Optional)", value=str(record["video_script"]), height=150, disabled=True, key=f"resume_preview_script_{selected_id}")
3339
+ st.text_area("Video Script (Optional)", value=str(record["video_script"]), height=150, disabled=True, key=f"{prefix}_resume_preview_script_{selected_id}")
3339
3340
  if record.get("video_keywords"):
3340
3341
  st.caption(f"**Video Keywords:** {record['video_keywords']}")
3341
3342
 
@@ -3352,7 +3353,7 @@ def render_video_from_draft() -> None:
3352
3353
  "Configurações a completar",
3353
3354
  list(DRAFT_SETTING_SECTIONS),
3354
3355
  default=missing_sections,
3355
- key="new_video_resume_sections",
3356
+ key=f"{prefix}_resume_sections",
3356
3357
  help="Seleccione uma ou mais áreas para completar antes de criar a tarefa.",
3357
3358
  )
3358
3359
  )
@@ -3377,11 +3378,11 @@ def render_video_from_draft() -> None:
3377
3378
  selectable_channels,
3378
3379
  index=channel_index,
3379
3380
  format_func=lambda channel: str(channel.get("name") or "Canal sem nome"),
3380
- key="new_video_resume_channel",
3381
+ key=f"{prefix}_resume_channel",
3381
3382
  )
3382
3383
 
3383
3384
  settings_from_form = render_video_generation_settings(
3384
- "new_video_resume",
3385
+ f"{prefix}_resume",
3385
3386
  current_language=str(record.get("language") or "pt"),
3386
3387
  channel=selected_channel,
3387
3388
  sections=selected_sections,
@@ -3394,7 +3395,7 @@ def render_video_from_draft() -> None:
3394
3395
  st.caption(f"Faltam: {', '.join(still_missing)}")
3395
3396
  elif not missing_sections:
3396
3397
  st.caption("Este roteiro será usado directamente, sem regenerar o conteúdo editorial guardado.")
3397
- if st.button(action_label, type="primary", width="stretch", key="new_video_resume_submit", icon=":material/movie:"):
3398
+ if st.button(action_label, type="primary", width="stretch", key=f"{prefix}_resume_submit", icon=":material/movie:"):
3398
3399
  if still_missing:
3399
3400
  st.error(f"Complete as configurações seleccionadas: {', '.join(still_missing)}.")
3400
3401
  elif not selected_channel.get("id"):
@@ -3821,7 +3822,7 @@ def render_new_video(page_title: str = "Criação de Vídeos", prefix: str = "ne
3821
3822
  _render_pipeline_progress_panel()
3822
3823
  if draft_tab is not None:
3823
3824
  with draft_tab:
3824
- render_video_from_draft()
3825
+ render_video_from_draft(prefix)
3825
3826
 
3826
3827
 
3827
3828
  def render_music_creation():
@@ -117,7 +117,17 @@ def _connected_account_id(client: Any, user_id: str, toolkit: str, selector: str
117
117
  try:
118
118
  response = client.connected_accounts.list(user_ids=[_require_user_id(user_id)], statuses=["ACTIVE"])
119
119
  raw = _safe_value(response)
120
- items = raw.get("items", []) if isinstance(raw, dict) else raw
120
+ if isinstance(raw, dict):
121
+ # Composio SDK versions expose the list either directly or under
122
+ # ``data``; accept both shapes so aliases are never sent as IDs.
123
+ items = raw.get("items")
124
+ if items is None:
125
+ data = raw.get("data")
126
+ items = data.get("items", data) if isinstance(data, dict) else data
127
+ if items is None:
128
+ items = raw.get("connected_accounts", [])
129
+ else:
130
+ items = raw
121
131
  if not isinstance(items, list):
122
132
  items = []
123
133
  matching_items = []
@@ -135,8 +145,15 @@ def _connected_account_id(client: Any, user_id: str, toolkit: str, selector: str
135
145
  for item in matching_items:
136
146
  candidates = [item.get("id"), item.get("nanoid"), item.get("alias"), item.get("name")]
137
147
  if any(str(candidate or "").strip().casefold() == wanted for candidate in candidates):
138
- return str(item.get("id") or item.get("nanoid") or value).strip()
139
- return value
148
+ technical_id = str(item.get("id") or item.get("nanoid") or "").strip()
149
+ if technical_id:
150
+ return technical_id
151
+ available = [str(item.get("alias") or item.get("name") or item.get("id") or "").strip() for item in matching_items]
152
+ available = [item for item in available if item]
153
+ suffix = f" Contas activas: {', '.join(available)}." if available else ""
154
+ raise ComposioUploadError(
155
+ f"A connected account `{value}` não foi encontrada para o toolkit {toolkit or 'seleccionado'}.{suffix}"
156
+ )
140
157
  if len(matching_items) == 1:
141
158
  item = matching_items[0]
142
159
  return str(item.get("id") or item.get("nanoid") or "").strip()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.7.04",
3
+ "version": "0.7.06",
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",