@danhachuel/thunderbolt 0.4.58 → 0.4.60
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/MANUAL-INSTALACAO.md +2 -0
- package/app/main.py +15 -1
- package/hermes_ui/storage.py +1 -0
- package/integrations/upload_routing.py +18 -2
- package/package.json +1 -1
package/MANUAL-INSTALACAO.md
CHANGED
|
@@ -837,3 +837,5 @@ Quando o provider ainda não estiver conectado, use **Autorizar toolkit no Compo
|
|
|
837
837
|
Antes de clicar em **Enviar vídeo via Composio**, confirme o vídeo, o toolkit, o slug da ferramenta, o campo do ficheiro e os argumentos JSON. O resultado guarda apenas um registo local mínimo com o estado e, quando devolvido, o `log_id` do Composio. URLs presigned, tokens, headers e a API key não são guardados no histórico.
|
|
838
838
|
|
|
839
839
|
Na **Automação Youtube**, Composio é a rota padrão quando a integração está activa e tem API key, slug da ferramenta e campo do ficheiro configurados. A ordem automática passa a ser **Composio → API Oficial → Upload directo → Postiz**. Se Composio falhar ou não estiver configurado, o worker tenta os fallbacks disponíveis e mantém o comportamento de publicação existente.
|
|
840
|
+
|
|
841
|
+
Para garantir o canal correcto, configure também o **Campo do canal na ferramenta**. O worker lê o `youtube_channel_id` do canal associado à tarefa e substitui explicitamente esse campo nos argumentos enviados ao Composio. Se a tarefa não tiver um YouTube channel ID sincronizado, ou se os argumentos configurarem outro ID, o envio Composio é bloqueado e os fallbacks podem actuar; a publicação nunca é enviada silenciosamente para um canal diferente.
|
package/app/main.py
CHANGED
|
@@ -6666,6 +6666,19 @@ def render_llm_provider_cards(settings: dict[str, Any], *, embedded: bool = Fals
|
|
|
6666
6666
|
llm_rpm_limit = st.number_input("Pedidos por janela", min_value=1, max_value=1000, value=int(settings.get("llm_rpm_limit", 40)), step=1, key="settings_llm_rpm_limit")
|
|
6667
6667
|
with rpm_cols[2]:
|
|
6668
6668
|
llm_rpm_window_seconds = st.number_input("Janela (segundos)", min_value=1, max_value=3600, value=int(settings.get("llm_rpm_window_seconds", 60)), step=1, key="settings_llm_rpm_window_seconds")
|
|
6669
|
+
save_llm_rpm_clicked = (
|
|
6670
|
+
st.form_submit_button("Guardar limite LLM NVIDIA NIM", type="primary", use_container_width=True, key="save_llm_rpm_limit")
|
|
6671
|
+
if embedded
|
|
6672
|
+
else st.button("Guardar limite LLM NVIDIA NIM", type="primary", use_container_width=True, key="save_llm_rpm_limit")
|
|
6673
|
+
)
|
|
6674
|
+
if save_llm_rpm_clicked:
|
|
6675
|
+
settings.update({
|
|
6676
|
+
"llm_rpm_limit_enabled": bool(llm_rpm_limit_enabled),
|
|
6677
|
+
"llm_rpm_limit": int(llm_rpm_limit),
|
|
6678
|
+
"llm_rpm_window_seconds": int(llm_rpm_window_seconds),
|
|
6679
|
+
})
|
|
6680
|
+
write_json("settings.json", settings)
|
|
6681
|
+
st.success("Limite LLM NVIDIA NIM guardado.")
|
|
6669
6682
|
for index in range(len(cards)):
|
|
6670
6683
|
_render_llm_card(settings, cards, index, embedded=embedded)
|
|
6671
6684
|
st.divider()
|
|
@@ -7173,6 +7186,7 @@ def render_settings():
|
|
|
7173
7186
|
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.")
|
|
7174
7187
|
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.")
|
|
7175
7188
|
composio_file_field = st.text_input("Campo do ficheiro na ferramenta", value=str(settings.get("composio_file_field") or "file"), key="upload_composio_file_field")
|
|
7189
|
+
composio_channel_field = st.text_input("Campo do canal na ferramenta", value=str(settings.get("composio_channel_field") or "channel_id"), key="upload_composio_channel_field", help="O worker substitui este campo pelo YouTube channel ID do canal da tarefa, impedindo o envio para outro canal.")
|
|
7176
7190
|
composio_arguments_json = st.text_area("Argumentos JSON da Automação Youtube", value=str(settings.get("composio_arguments_json") or "{}"), key="upload_composio_arguments_json", height=120)
|
|
7177
7191
|
_render_credential_status(composio_api_key)
|
|
7178
7192
|
composio_action_cols = st.columns(2)
|
|
@@ -7181,7 +7195,7 @@ def render_settings():
|
|
|
7181
7195
|
with composio_action_cols[1]:
|
|
7182
7196
|
test_composio = st.button("Testar configuração", use_container_width=True, key="upload_composio_test")
|
|
7183
7197
|
if save_composio:
|
|
7184
|
-
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 "file", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
|
|
7198
|
+
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 "file", "composio_channel_field": composio_channel_field.strip() or "channel_id", "composio_arguments_json": composio_arguments_json.strip() or "{}"})
|
|
7185
7199
|
write_json("settings.json", settings)
|
|
7186
7200
|
st.success("Configuração Composio guardada.")
|
|
7187
7201
|
st.rerun()
|
package/hermes_ui/storage.py
CHANGED
|
@@ -294,6 +294,7 @@ DEFAULTS: dict[str, Any] = {
|
|
|
294
294
|
"composio_toolkit": "",
|
|
295
295
|
"composio_tool_slug": "",
|
|
296
296
|
"composio_file_field": "file",
|
|
297
|
+
"composio_channel_field": "channel_id",
|
|
297
298
|
"composio_arguments_json": "{}",
|
|
298
299
|
"postiz_enabled": False,
|
|
299
300
|
"postiz_api_key": "",
|
|
@@ -106,6 +106,7 @@ def upload_with_default_route(
|
|
|
106
106
|
try:
|
|
107
107
|
composio_result = composio_publisher(
|
|
108
108
|
settings,
|
|
109
|
+
channel=channel,
|
|
109
110
|
video_path=video_path,
|
|
110
111
|
title=title,
|
|
111
112
|
description=description,
|
|
@@ -212,15 +213,30 @@ def upload_with_default_route(
|
|
|
212
213
|
return IntegrationResult(False, "Nenhum método de envio conseguiu publicar o vídeo.", {"attempts": attempts, "route": "none"})
|
|
213
214
|
|
|
214
215
|
|
|
215
|
-
def _composio_upload(settings: dict[str, Any], **kwargs: Any) -> IntegrationResult:
|
|
216
|
+
def _composio_upload(settings: dict[str, Any], *, channel: dict[str, Any], **kwargs: Any) -> IntegrationResult:
|
|
217
|
+
channel_id = str(channel.get("youtube_channel_id") or "").strip()
|
|
218
|
+
if not channel_id:
|
|
219
|
+
return IntegrationResult(False, "Composio não foi executado: o canal da tarefa não tem um YouTube channel ID sincronizado.", {"missing": ["youtube_channel_id"]})
|
|
220
|
+
channel_field = str(settings.get("composio_channel_field") or "channel_id").strip()
|
|
221
|
+
if not channel_field:
|
|
222
|
+
return IntegrationResult(False, "Composio não foi executado: configure o campo de canal da ferramenta.", {"missing": ["composio_channel_field"]})
|
|
216
223
|
try:
|
|
224
|
+
arguments = str(settings.get("composio_arguments_json") or "{}").strip() or "{}"
|
|
225
|
+
import json
|
|
226
|
+
parsed_arguments = json.loads(arguments)
|
|
227
|
+
if not isinstance(parsed_arguments, dict):
|
|
228
|
+
return IntegrationResult(False, "Composio não foi executado: os argumentos JSON devem ser um objecto.", {})
|
|
229
|
+
configured_channel = parsed_arguments.get(channel_field)
|
|
230
|
+
if configured_channel not in (None, "", channel_id):
|
|
231
|
+
return IntegrationResult(False, f"Composio bloqueado: o campo `{channel_field}` aponta para outro canal.", {"expected_channel_id": channel_id})
|
|
232
|
+
parsed_arguments[channel_field] = channel_id
|
|
217
233
|
result = execute_upload(
|
|
218
234
|
str(settings.get("composio_api_key") or ""),
|
|
219
235
|
str(settings.get("composio_user_id") or ""),
|
|
220
236
|
str(settings.get("composio_tool_slug") or ""),
|
|
221
237
|
str(kwargs.get("video_path") or ""),
|
|
222
238
|
str(settings.get("composio_file_field") or "file"),
|
|
223
|
-
|
|
239
|
+
json.dumps(parsed_arguments, ensure_ascii=False),
|
|
224
240
|
)
|
|
225
241
|
except ComposioUploadError as exc:
|
|
226
242
|
return IntegrationResult(False, str(exc), {})
|
package/package.json
CHANGED