@danhachuel/thunderbolt 0.5.28 → 0.5.30

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
@@ -78,7 +78,7 @@ from hermes_ui.mcp import detect_local_service, install_skill_locally, load_inte
78
78
  from hermes_ui.mcp_server import server_status, start_server, stop_server
79
79
  from hermes_ui.material_sources import apply_material_source_cards_to_settings, ensure_material_source_cards, material_source_catalog, material_source_definition, new_material_card, normalize_material_card, selected_material_source
80
80
  from hermes_ui.llm_providers import LLM_CARDS_KEY, LLM_PROVIDER_CATALOG, apply_llm_cards_to_settings, ensure_llm_provider_cards, new_llm_card, normalize_llm_card, provider_definition, test_llm_provider_card, stamp_test_result
81
- from hermes_ui.media_providers import FULL_IA_VIDEO_PROVIDER_CODES, MEDIA_CARDS_KEY, MEDIA_IMAGE_ACTIVE_CARD_KEY, MEDIA_VIDEO_ACTIVE_CARD_KEY, apply_media_provider_cards_to_settings, ensure_media_provider_cards, media_cards_for_pool, media_provider_catalog, media_provider_definition, new_media_card, normalize_media_card
81
+ from hermes_ui.media_providers import FULL_IA_VIDEO_PROVIDER_CODES, KIE_MEDIA_MODEL_CATALOG, MEDIA_CARDS_KEY, MEDIA_IMAGE_ACTIVE_CARD_KEY, MEDIA_VIDEO_ACTIVE_CARD_KEY, apply_media_provider_cards_to_settings, ensure_media_provider_cards, media_cards_for_pool, media_provider_catalog, media_provider_definition, new_media_card, normalize_media_card
82
82
  from hermes_ui.music import create_music_task, list_music_files, list_music_tasks, materialize_suno_audio, request_suno_generation, run_music_task, store_music_file, store_voiceover_file, transition_music_task
83
83
  from hermes_ui.music_generation import MUSIC_GENRES, MUSIC_VOCAL_OPTIONS, generate_music_fields
84
84
  from hermes_ui.media_downloader import AUDIO_FORMATS, VIDEO_CONTAINERS, VIDEO_QUALITY_OPTIONS, MediaDownloadError, build_download_options, clear_media_download_history, dependency_status, download_media, list_media_downloads, media_download_file
@@ -7087,6 +7087,8 @@ def _persist_media_cards(settings: dict[str, Any], cards: list[dict[str, Any]],
7087
7087
  def _fetch_media_models(card: dict[str, Any]) -> list[str]:
7088
7088
  """Consultar modelos do provider, incluindo o catálogo nativo Gemini."""
7089
7089
  provider = str(card.get("provider") or "").strip().lower()
7090
+ if provider == "kie_ai":
7091
+ return list(KIE_MEDIA_MODEL_CATALOG)
7090
7092
  if provider == "openrouter":
7091
7093
  base_url = str(card.get("base_url") or "https://openrouter.ai/api/v1").rstrip("/")
7092
7094
  headers = {"Accept": "application/json", "Authorization": f"Bearer {str(card.get('api_key') or '').strip()}"}
@@ -175,6 +175,8 @@ def test_media_provider_card(card: Mapping[str, Any]) -> dict[str, Any]:
175
175
  return _missing("Introduza a API key/token antes de testar este provider.")
176
176
  if provider == "heygen":
177
177
  return _get(f"{base_url}/v3/users/me", headers={"X-Api-Key": api_key})
178
+ if provider == "kie_ai":
179
+ return _get(f"{base_url}/chat/credit", headers={"Authorization": f"Bearer {api_key}"})
178
180
  if not model and provider not in {"inferenceport", "cloudflare_workers_ai", "heygen"}:
179
181
  return _result("missing", "Complete o modelo antes de testar.")
180
182
  if provider == "openrouter":
@@ -190,7 +192,7 @@ def test_media_provider_card(card: Mapping[str, Any]) -> dict[str, Any]:
190
192
  return _result("missing", "Complete o Account ID do Cloudflare antes de testar.")
191
193
  endpoint = f"{base_url}/accounts/{quote(account_id, safe='')}/ai/models/search"
192
194
  return _get(endpoint, headers=headers, params={"search": model or "stable-diffusion"})
193
- if api_style in {"openai_compatible", "huggingface", "agnes", "kie"} or provider in {"pollinations", "huggingface", "agnes", "kie_ai", "inferenceport"}:
195
+ if api_style in {"openai_compatible", "huggingface", "agnes", "kie"} or provider in {"pollinations", "huggingface", "agnes", "inferenceport"}:
194
196
  endpoint = _models_endpoint(base_url)
195
197
  elif provider == "fal_ai":
196
198
  endpoint = f"{base_url}/models" if base_url.endswith("/v1") else base_url
@@ -191,6 +191,11 @@ def _image_value(payload: Any) -> tuple[bytes | None, str]:
191
191
  def _image_request_id(payload: Any) -> str:
192
192
  if not isinstance(payload, Mapping):
193
193
  return ""
194
+ nested = payload.get("data")
195
+ if isinstance(nested, Mapping):
196
+ task_id = str(nested.get("taskId") or "").strip()
197
+ if task_id:
198
+ return task_id
194
199
  for key in ("id", "prediction_id", "request_id", "task_id", "job_id"):
195
200
  value = str(payload.get(key) or "").strip()
196
201
  if value:
@@ -254,7 +259,9 @@ def _image_endpoint(card: Mapping[str, Any]) -> str:
254
259
  return explicit
255
260
  if style == "openrouter":
256
261
  return f"{base}/images"
257
- if style in {"openai_compatible", "huggingface", "agnes", "kie"}:
262
+ if style == "kie":
263
+ return f"{base}/jobs/createTask"
264
+ if style in {"openai_compatible", "huggingface", "agnes"}:
258
265
  return f"{base}/images/generations"
259
266
  if style == "replicate":
260
267
  return f"{base}/predictions"
@@ -301,6 +308,12 @@ def _image_request(card: dict[str, Any], prompt: str, *, topic: str = "", letter
301
308
  if style == "replicate":
302
309
  body = {"version": _model(card), "input": {"prompt": constrained_prompt}}
303
310
  return requests.post(endpoint, headers=_headers(card), json=body, timeout=180)
311
+ if style == "kie":
312
+ body = {
313
+ "model": _model(card),
314
+ "input": {"prompt": constrained_prompt, "aspect_ratio": "16:9", "resolution": "1K"},
315
+ }
316
+ return requests.post(endpoint, headers=_headers(card), json=body, timeout=180)
304
317
  if provider == "agnes":
305
318
  body = {
306
319
  "model": _model(card) or AGNES_IMAGE_MODEL,
@@ -425,6 +438,9 @@ def generate_image_for_card(
425
438
  style = str(routed.card.get("api_style") or media_provider_definition(routed.card.get("provider")).api_style)
426
439
  if request_id and style == "replicate":
427
440
  image_bytes, url = _poll_image(routed.card, request_id)
441
+ elif request_id and style == "kie":
442
+ urls = _poll_kie_task(routed.card, request_id, endpoint="/jobs/recordInfo", veo=False)
443
+ url = urls[0] if urls else ""
428
444
  return _download_or_write(image_bytes, url, destination, routed.card)
429
445
 
430
446
 
@@ -491,7 +507,9 @@ def _video_endpoint(card: Mapping[str, Any]) -> str:
491
507
  return f"{base}/{_model(card).lstrip('/')}"
492
508
  if style == "openrouter":
493
509
  return f"{base}/videos"
494
- if style in {"openai_compatible", "agnes", "kie"}:
510
+ if style == "kie":
511
+ return f"{base}/jobs/createTask"
512
+ if style in {"openai_compatible", "agnes"}:
495
513
  return f"{base}/videos/generations"
496
514
  if style == "replicate":
497
515
  return f"{base}/predictions"
@@ -518,6 +536,15 @@ def _video_request(card: dict[str, Any], prompt: str, image_url: str = "") -> An
518
536
  body["aspect_ratio"] = str(card.get("aspect_ratio") or INTERNAL_VIDEO_ASPECT_RATIO)
519
537
  body["resolution"] = str(card.get("video_size") or INTERNAL_VIDEO_SIZE)
520
538
  return requests.post(endpoint, headers=_headers(card), json=body, timeout=180)
539
+ if style == "kie":
540
+ input_payload: dict[str, Any] = {
541
+ "prompt": body["prompt"],
542
+ "aspect_ratio": str(card.get("aspect_ratio") or INTERNAL_VIDEO_ASPECT_RATIO),
543
+ "resolution": str(card.get("video_size") or INTERNAL_VIDEO_SIZE),
544
+ }
545
+ if image_url:
546
+ input_payload["imageUrls"] = [image_url]
547
+ return requests.post(endpoint, headers=_headers(card), json={"model": _model(card), "input": input_payload}, timeout=180)
521
548
  if style == "heygen":
522
549
  avatar_id = str(card.get("avatar_id") or "").strip()
523
550
  if not avatar_id:
@@ -570,7 +597,7 @@ def _video_result(payload: Mapping[str, Any]) -> tuple[str, str]:
570
597
  direct = str(data.get("video_url") or data.get("url") or "").strip()
571
598
  if direct.startswith(("http://", "https://")):
572
599
  return direct, ""
573
- for key in ("video_id", "id", "request_id", "task_id", "job_id"):
600
+ for key in ("video_id", "id", "request_id", "task_id", "taskId", "job_id"):
574
601
  value = str(data.get(key) or "").strip()
575
602
  if value:
576
603
  return "", value
@@ -648,7 +675,11 @@ def generate_video_for_card(
648
675
  url, request_id = _video_result(routed.payload)
649
676
  if not url and request_id:
650
677
  try:
651
- url = _poll_video(routed.card, request_id)
678
+ if str(routed.card.get("api_style") or media_provider_definition(routed.card.get("provider")).api_style) == "kie":
679
+ urls = _poll_kie_task(routed.card, request_id, endpoint="/jobs/recordInfo", veo=False)
680
+ url = urls[0] if urls else ""
681
+ else:
682
+ url = _poll_video(routed.card, request_id)
652
683
  except ProviderCallError as exc:
653
684
  raise MediaGenerationError(str(exc)) from exc
654
685
  if not url:
@@ -25,6 +25,19 @@ FULL_IA_VIDEO_PROVIDER_CODES = (
25
25
  "openrouter",
26
26
  )
27
27
 
28
+ # A KIE disponibiliza o Market por páginas de modelo, sem um endpoint global
29
+ # documentado para listar modelos. Estes slugs são os modelos multimédia
30
+ # suportados pelo contrato unificado jobs/createTask.
31
+ KIE_MEDIA_MODEL_CATALOG = (
32
+ "bytedance/seedream-v4-text-to-image",
33
+ "seedream/4.5-text-to-image",
34
+ "grok-imagine/text-to-image",
35
+ "kling-2.6/text-to-video",
36
+ "grok-imagine/image-to-video",
37
+ "veo3_fast",
38
+ "veo3",
39
+ )
40
+
28
41
  # Mantidos internamente para preservar compatibilidade e controlar o prompt dos providers.
29
42
  INTERNAL_IMAGE_ASPECT_RATIO = "16:9"
30
43
  INTERNAL_IMAGE_SIZE = "1K"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.5.28",
3
+ "version": "0.5.30",
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",