@danhachuel/thunderbolt 0.5.95 → 0.5.97
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 +3 -3
- package/hermes_ui/pipeline_worker.py +11 -1
- package/hermes_ui/storage.py +2 -2
- package/integrations/composio_upload.py +15 -4
- package/integrations/upload_routing.py +11 -6
- package/package.json +1 -1
- package/requirements.txt +1 -0
- package/seed/skills/mpt_agent.py +20 -0
package/app/main.py
CHANGED
|
@@ -7982,8 +7982,8 @@ def render_settings():
|
|
|
7982
7982
|
composio_user_id = st.text_input("Composio user ID", value=str(settings.get("composio_user_id") or "thunderbolt-local"), key="upload_composio_user_id", help="Identidade estável usada para associar as contas conectadas no Composio.")
|
|
7983
7983
|
composio_toolkit = st.text_input("Toolkit preferido (opcional)", value=str(settings.get("composio_toolkit") or ""), key="upload_composio_toolkit", help="Deixe vazio para descobrir ferramentas em todos os toolkits.")
|
|
7984
7984
|
composio_tool_slug = st.text_input("Slug da ferramenta para Automação Youtube", value=str(settings.get("composio_tool_slug") or ""), key="upload_composio_tool_slug", help="Depois de descobrir uma ferramenta em Upload via Composio, copie aqui o slug para a Automação Youtube.")
|
|
7985
|
-
composio_file_field = st.text_input("Campo do ficheiro na ferramenta", value=str(settings.get("composio_file_field") or "
|
|
7986
|
-
composio_channel_field = st.text_input("Campo do canal na ferramenta", value=str(settings.get("composio_channel_field") or "
|
|
7985
|
+
composio_file_field = st.text_input("Campo do ficheiro na ferramenta", value=str(settings.get("composio_file_field") or "videoFilePath"), key="upload_composio_file_field", help="Para YOUTUBE_UPLOAD_VIDEO use videoFilePath; o Composio envia para o canal da conta YouTube ligada.")
|
|
7986
|
+
composio_channel_field = st.text_input("Campo do canal na ferramenta (opcional)", value=str(settings.get("composio_channel_field") or ""), key="upload_composio_channel_field", help="As ferramentas YouTube actuais usam a conta ligada e não aceitam channel_id. Preencha apenas se a ferramenta descoberta exigir explicitamente esse campo.")
|
|
7987
7987
|
composio_privacy_field = st.text_input("Campo de privacidade", value=str(settings.get("composio_privacy_field") or "privacy_status"), key="upload_composio_privacy_field", help="Recebe sempre `unlisted` (Não listado), como no upload oficial.")
|
|
7988
7988
|
composio_category_field = st.text_input("Campo de categoria", value=str(settings.get("composio_category_field") or "category_id"), key="upload_composio_category_field", help="Recebe sempre `22`, categoria Pessoas e blogs, como no upload oficial.")
|
|
7989
7989
|
composio_language_field = st.text_input("Campo de idioma", value=str(settings.get("composio_language_field") or "language"), key="upload_composio_language_field", help="Recebe o locale do idioma do vídeo/canal, por exemplo `pt-BR`.")
|
|
@@ -7995,7 +7995,7 @@ def render_settings():
|
|
|
7995
7995
|
with composio_action_cols[1]:
|
|
7996
7996
|
test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
|
|
7997
7997
|
if save_composio:
|
|
7998
|
-
settings.update({"composio_enabled": bool(composio_enabled), "composio_auto_upload": bool(composio_auto_upload), "composio_api_key": composio_api_key.strip(), "composio_user_id": composio_user_id.strip() or "thunderbolt-local", "composio_toolkit": composio_toolkit.strip(), "composio_tool_slug": composio_tool_slug.strip(), "composio_file_field": composio_file_field.strip() or "
|
|
7998
|
+
settings.update({"composio_enabled": bool(composio_enabled), "composio_auto_upload": bool(composio_auto_upload), "composio_api_key": composio_api_key.strip(), "composio_user_id": composio_user_id.strip() or "thunderbolt-local", "composio_toolkit": composio_toolkit.strip(), "composio_tool_slug": composio_tool_slug.strip(), "composio_file_field": composio_file_field.strip() or "videoFilePath", "composio_channel_field": composio_channel_field.strip(), "composio_privacy_field": composio_privacy_field.strip() or "privacy_status", "composio_category_field": composio_category_field.strip() or "category_id", "composio_language_field": composio_language_field.strip() or "language", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
|
|
7999
7999
|
write_json("settings.json", settings)
|
|
8000
8000
|
st.success("Configuração Composio guardada.")
|
|
8001
8001
|
st.rerun()
|
|
@@ -1577,7 +1577,17 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
|
|
|
1577
1577
|
captions_path=str(artifacts.get("captions") or ""),
|
|
1578
1578
|
)
|
|
1579
1579
|
if not result.ok:
|
|
1580
|
-
|
|
1580
|
+
attempts = (result.data or {}).get("attempts") if isinstance(result.data, dict) else None
|
|
1581
|
+
detail = ""
|
|
1582
|
+
if isinstance(attempts, list):
|
|
1583
|
+
failed = [
|
|
1584
|
+
f"{item.get('route')}: {item.get('message')}"
|
|
1585
|
+
for item in attempts
|
|
1586
|
+
if isinstance(item, dict) and item.get("status") == "failed" and item.get("message")
|
|
1587
|
+
]
|
|
1588
|
+
if failed:
|
|
1589
|
+
detail = " Detalhes: " + " | ".join(failed[-4:])
|
|
1590
|
+
raise PipelineError(f"{result.message}{detail}")
|
|
1581
1591
|
artifacts["upload"] = result.data
|
|
1582
1592
|
return _update(task_id, stage="upload", state="done", progress=100, artifacts=artifacts, error=None)
|
|
1583
1593
|
|
package/hermes_ui/storage.py
CHANGED
|
@@ -293,8 +293,8 @@ DEFAULTS: dict[str, Any] = {
|
|
|
293
293
|
"composio_user_id": "thunderbolt-local",
|
|
294
294
|
"composio_toolkit": "",
|
|
295
295
|
"composio_tool_slug": "",
|
|
296
|
-
"composio_file_field": "
|
|
297
|
-
"composio_channel_field": "
|
|
296
|
+
"composio_file_field": "videoFilePath",
|
|
297
|
+
"composio_channel_field": "",
|
|
298
298
|
"composio_privacy_field": "privacy_status",
|
|
299
299
|
"composio_category_field": "category_id",
|
|
300
300
|
"composio_language_field": "language",
|
|
@@ -59,10 +59,17 @@ def _response(result: Any) -> dict[str, Any]:
|
|
|
59
59
|
raw = _safe_value(result)
|
|
60
60
|
if not isinstance(raw, dict):
|
|
61
61
|
raw = {"data": raw}
|
|
62
|
-
|
|
62
|
+
data = raw.get("data")
|
|
63
|
+
if not isinstance(data, dict):
|
|
64
|
+
data = {"value": data} if data is not None else {}
|
|
65
|
+
error = raw.get("error") or raw.get("message") or data.get("error") or data.get("message")
|
|
66
|
+
explicit_success = raw.get("successful")
|
|
67
|
+
if explicit_success is None:
|
|
68
|
+
explicit_success = raw.get("success")
|
|
69
|
+
successful = bool(explicit_success) if explicit_success is not None else not bool(error)
|
|
63
70
|
return {
|
|
64
|
-
"successful": not bool(error),
|
|
65
|
-
"data":
|
|
71
|
+
"successful": successful and not bool(error),
|
|
72
|
+
"data": data,
|
|
66
73
|
"error": str(error) if error else "",
|
|
67
74
|
"log_id": str(raw.get("log_id") or raw.get("request_id") or ""),
|
|
68
75
|
}
|
|
@@ -161,7 +168,11 @@ def execute_upload(api_key: str, user_id: str, slug: str, video_path: str, file_
|
|
|
161
168
|
version="latest",
|
|
162
169
|
dangerously_skip_version_check=True,
|
|
163
170
|
)
|
|
164
|
-
|
|
171
|
+
response = _response(result)
|
|
172
|
+
if not response["successful"] and not response["error"]:
|
|
173
|
+
response["error"] = f"A ferramenta `{slug}` devolveu uma resposta sem sucesso."
|
|
174
|
+
response["tool_slug"] = slug
|
|
175
|
+
return response
|
|
165
176
|
except ComposioUploadError:
|
|
166
177
|
raise
|
|
167
178
|
except Exception as exc:
|
|
@@ -229,12 +229,17 @@ def upload_with_default_route(
|
|
|
229
229
|
|
|
230
230
|
|
|
231
231
|
def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwargs: Any) -> IntegrationResult:
|
|
232
|
+
slug = str(settings.get("composio_tool_slug") or "").strip()
|
|
233
|
+
youtube_upload_tool = slug.upper() in {"YOUTUBE_UPLOAD_VIDEO", "YOUTUBE_MULTIPART_UPLOAD_VIDEO"}
|
|
232
234
|
channel_id = str(channel.get("youtube_channel_id") or "").strip()
|
|
233
|
-
if not channel_id:
|
|
234
|
-
return IntegrationResult(False, "Composio não foi executado: o canal da tarefa não tem um YouTube channel ID sincronizado.", {"missing": ["youtube_channel_id"]})
|
|
235
235
|
channel_field = str(settings.get("composio_channel_field") or "channel_id").strip()
|
|
236
|
-
if not
|
|
236
|
+
if not youtube_upload_tool and not channel_id:
|
|
237
|
+
return IntegrationResult(False, "Composio não foi executado: o canal da tarefa não tem um YouTube channel ID sincronizado.", {"missing": ["youtube_channel_id"]})
|
|
238
|
+
if not youtube_upload_tool and not channel_field:
|
|
237
239
|
return IntegrationResult(False, "Composio não foi executado: configure o campo de canal da ferramenta.", {"missing": ["composio_channel_field"]})
|
|
240
|
+
file_field = str(settings.get("composio_file_field") or "").strip()
|
|
241
|
+
if not file_field or (youtube_upload_tool and file_field == "file"):
|
|
242
|
+
file_field = "videoFilePath"
|
|
238
243
|
try:
|
|
239
244
|
arguments = str(settings.get("composio_arguments_json") or "{}").strip() or "{}"
|
|
240
245
|
import json
|
|
@@ -242,7 +247,7 @@ def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwa
|
|
|
242
247
|
if not isinstance(parsed_arguments, dict):
|
|
243
248
|
return IntegrationResult(False, "Composio não foi executado: os argumentos JSON devem ser um objecto.", {})
|
|
244
249
|
locked_values = {
|
|
245
|
-
channel_field: channel_id,
|
|
250
|
+
**({channel_field: channel_id} if channel_field and not youtube_upload_tool else {}),
|
|
246
251
|
str(settings.get("composio_privacy_field") or "privacy_status").strip(): str(kwargs.get("privacy_status") or "unlisted"),
|
|
247
252
|
str(settings.get("composio_category_field") or "category_id").strip(): str(kwargs.get("category_id") or "22"),
|
|
248
253
|
str(settings.get("composio_language_field") or "language").strip(): language_locale(kwargs.get("language") or channel.get("language") or "pt"),
|
|
@@ -261,7 +266,7 @@ def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwa
|
|
|
261
266
|
str(settings.get("composio_user_id") or ""),
|
|
262
267
|
str(settings.get("composio_tool_slug") or ""),
|
|
263
268
|
str(kwargs.get("video_path") or ""),
|
|
264
|
-
|
|
269
|
+
file_field,
|
|
265
270
|
json.dumps(parsed_arguments, ensure_ascii=False),
|
|
266
271
|
)
|
|
267
272
|
except ComposioUploadError as exc:
|
|
@@ -269,5 +274,5 @@ def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwa
|
|
|
269
274
|
return IntegrationResult(
|
|
270
275
|
bool(result.get("successful")),
|
|
271
276
|
str(result.get("error") or "Upload via Composio concluído."),
|
|
272
|
-
{"composio_data": result.get("data") or {}, "log_id": result.get("log_id") or ""},
|
|
277
|
+
{"composio_data": result.get("data") or {}, "log_id": result.get("log_id") or "", "tool_slug": result.get("tool_slug") or slug},
|
|
273
278
|
)
|
package/package.json
CHANGED
package/requirements.txt
CHANGED
package/seed/skills/mpt_agent.py
CHANGED
|
@@ -24,6 +24,7 @@ PROJECT_ARCHIVE_URL = (
|
|
|
24
24
|
"https://github.com/harry0703/MoneyPrinterTurbo/archive/refs/heads/main.zip"
|
|
25
25
|
)
|
|
26
26
|
DEFAULT_ROOT = Path.home() / "MoneyPrinterTurbo"
|
|
27
|
+
MOVIEPY_REQUIREMENT = "moviepy==2.2.1"
|
|
27
28
|
CHUNKED_SYNTHESIS_TIMEOUT_SECONDS = 15 * 60
|
|
28
29
|
DEPENDENCY_SYNC_TIMEOUT_SECONDS = 15 * 60
|
|
29
30
|
DEFAULT_VOICE_NAME = "zh-CN-XiaoxiaoNeural-Female"
|
|
@@ -686,6 +687,22 @@ def run_checked(command: list[str], *, cwd: Path) -> None:
|
|
|
686
687
|
log("project dependencies verified with uv")
|
|
687
688
|
|
|
688
689
|
|
|
690
|
+
def ensure_moviepy(root: Path, uv: str) -> None:
|
|
691
|
+
"""Ensure the render engine matches MoneyPrinterTurbo's MoviePy release."""
|
|
692
|
+
run_checked(
|
|
693
|
+
[
|
|
694
|
+
uv,
|
|
695
|
+
"run",
|
|
696
|
+
"--with",
|
|
697
|
+
MOVIEPY_REQUIREMENT,
|
|
698
|
+
"python",
|
|
699
|
+
"-c",
|
|
700
|
+
"import moviepy; assert moviepy.__version__ == '2.2.1'",
|
|
701
|
+
],
|
|
702
|
+
cwd=root,
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
|
|
689
706
|
def generate_video(
|
|
690
707
|
root: Path,
|
|
691
708
|
subject: str,
|
|
@@ -696,6 +713,7 @@ def generate_video(
|
|
|
696
713
|
if not uv:
|
|
697
714
|
raise SkillError("uv was not found; reopen the terminal or add uv to PATH")
|
|
698
715
|
run_checked([uv, "sync", "--frozen"], cwd=root)
|
|
716
|
+
ensure_moviepy(root, uv)
|
|
699
717
|
config_path = ensure_config(root)
|
|
700
718
|
|
|
701
719
|
task_id = str(uuid.uuid4())
|
|
@@ -748,6 +766,8 @@ def generate_video(
|
|
|
748
766
|
command = [
|
|
749
767
|
uv,
|
|
750
768
|
"run",
|
|
769
|
+
"--with",
|
|
770
|
+
MOVIEPY_REQUIREMENT,
|
|
751
771
|
"python",
|
|
752
772
|
"cli.py",
|
|
753
773
|
*forwarded_args,
|