@danhachuel/thunderbolt 0.5.28 → 0.5.29
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 -1
- package/hermes_ui/media_generation.py +35 -4
- package/hermes_ui/media_providers.py +13 -0
- package/package.json +1 -1
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()}"}
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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