Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from .gemma_api_conditioning import GemmaAPITextEncode
from .gemma_encoder import LTXVGemmaCLIPModelLoader, LTXVGemmaEnhancePrompt
from .minimax_prompt_enhancer import MiniMaxPromptEnhancer
from .guide import LTXVAddGuideAdvanced, LTXVAddGuideAdvancedAttention
from .guiders import GuiderParametersNode, MultimodalGuiderNode
from .iclora import (
Expand Down Expand Up @@ -99,6 +100,7 @@
"LTXVGemmaCLIPModelLoader": LTXVGemmaCLIPModelLoader,
"LTXVGemmaEnhancePrompt": LTXVGemmaEnhancePrompt,
"GemmaAPITextEncode": GemmaAPITextEncode,
"MiniMaxPromptEnhancer": MiniMaxPromptEnhancer,
"DynamicConditioning": DynamicConditioning,
"LowVRAMCheckpointLoader": LowVRAMCheckpointLoader,
"LowVRAMAudioVAELoader": LowVRAMAudioVAELoader,
Expand Down
182 changes: 182 additions & 0 deletions minimax_prompt_enhancer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""
API-based prompt enhancement using MiniMax.

Provides an API-based alternative to the local LLM prompt enhancer, allowing
users to enhance their video generation prompts via MiniMax without requiring
local GPU resources.
"""

import logging
import re

import requests

from .nodes_registry import comfy_node
from .prompt_enhancer_utils import I2V_CINEMATIC_PROMPT, T2V_CINEMATIC_PROMPT

logger = logging.getLogger(__name__)

MINIMAX_API_BASE_URL = "https://api.minimax.io/v1"
MINIMAX_MODELS = ["MiniMax-M2.7", "MiniMax-M2.7-highspeed"]

_THINK_TAG_RE = re.compile(r"<think>.*?</think>", re.DOTALL)


def enhance_prompt_via_minimax(
prompt: str,
system_prompt: str,
api_key: str,
model: str = "MiniMax-M2.7",
base_url: str = MINIMAX_API_BASE_URL,
max_tokens: int = 512,
) -> str:
"""Call MiniMax chat completions API to enhance a video generation prompt."""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
payload = {
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt},
],
"max_tokens": max_tokens,
"temperature": 1.0,
}
response = requests.post(
f"{base_url}/chat/completions",
json=payload,
headers=headers,
timeout=60,
)
if response.status_code == 401:
raise RuntimeError(
"Invalid API key. Please provide a valid MINIMAX_API_KEY."
)
if response.status_code != 200:
raise RuntimeError(
f"MiniMax API request failed with status {response.status_code}: {response.text}"
)
data = response.json()
content = data["choices"][0]["message"]["content"]
# Strip <think>…</think> reasoning tokens emitted by MiniMax-M2.7
content = _THINK_TAG_RE.sub("", content).strip()
return content


@comfy_node(name="MiniMaxPromptEnhancer")
class MiniMaxPromptEnhancer:
"""
Enhances video generation prompts using the MiniMax API.

An API-based alternative to the local LLM prompt enhancer. No local GPU
resources are required — prompts are sent to the MiniMax chat completions
endpoint and returned as cinematic descriptions suitable for LTX-Video.

Inputs:
- api_key: MiniMax API key (MINIMAX_API_KEY). Get one at https://platform.minimax.io/
- prompt: Text prompt to enhance
- model: MiniMax model to use for enhancement
- mode: Enhancement mode — T2V (text-to-video) or I2V (image-to-video)
- max_tokens: Maximum number of tokens in the enhanced prompt

Returns:
- enhanced_prompt: Cinematically enhanced prompt string
"""

@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_key": (
"STRING",
{
"default": "",
"placeholder": "MINIMAX_API_KEY",
"multiline": False,
"tooltip": "MiniMax API key. Get one at https://platform.minimax.io/",
},
),
"prompt": (
"STRING",
{
"multiline": True,
"default": "",
"tooltip": "Text prompt to enhance for video generation",
},
),
"model": (
MINIMAX_MODELS,
{
"default": "MiniMax-M2.7",
"tooltip": "MiniMax model to use for prompt enhancement",
},
),
"mode": (
["T2V", "I2V"],
{
"default": "T2V",
"tooltip": (
"Enhancement mode: T2V for text-to-video, "
"I2V for image-to-video"
),
},
),
"max_tokens": (
"INT",
{
"default": 512,
"min": 64,
"max": 1024,
"step": 64,
"tooltip": "Maximum number of tokens in the enhanced prompt",
},
),
},
}

RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("enhanced_prompt",)
FUNCTION = "enhance"
CATEGORY = "api node/text/Lightricks"
TITLE = "MiniMax Prompt Enhancer"
OUTPUT_NODE = True
DESCRIPTION = (
"Enhances video generation prompts using the MiniMax API. "
"An API-based alternative to the local LLM enhancer — no local GPU required."
)

def enhance(
self,
api_key: str,
prompt: str,
model: str = "MiniMax-M2.7",
mode: str = "T2V",
max_tokens: int = 512,
) -> tuple:
if not api_key.strip():
raise ValueError(
"MiniMax API key is required. "
"Get one at https://platform.minimax.io/"
)
if not prompt.strip():
raise ValueError("Prompt cannot be empty")

system_prompt = T2V_CINEMATIC_PROMPT if mode == "T2V" else I2V_CINEMATIC_PROMPT

logger.info(
"Enhancing prompt via MiniMax API (model=%s, mode=%s): %s...",
model,
mode,
prompt[:60],
)
enhanced = enhance_prompt_via_minimax(
prompt=prompt,
system_prompt=system_prompt,
api_key=api_key,
model=model,
max_tokens=max_tokens,
)
logger.info("Enhanced prompt: %s...", enhanced[:60])
return (enhanced,)
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = ["-p", "no:flaky", "--import-mode=importlib"]
93 changes: 93 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Pytest configuration: sets up ComfyUI stubs so nodes can be imported
without a running ComfyUI environment, and handles the hyphenated package
name by creating a proper package alias.
"""

import importlib.util
import os
import sys
import types

REPO_ROOT = os.path.dirname(os.path.dirname(__file__))
PKG_NAME = "ComfyUI_LTXVideo" # Python-safe alias for the repo folder

# ---------------------------------------------------------------------------
# Stub heavy ComfyUI / ML dependencies
# ---------------------------------------------------------------------------

comfy_pkg = types.ModuleType("comfy")
comfy_mm = types.ModuleType("comfy.model_management")
comfy_mp = types.ModuleType("comfy.model_patcher")
comfy_pkg.model_management = comfy_mm
comfy_pkg.model_patcher = comfy_mp
sys.modules.setdefault("comfy", comfy_pkg)
sys.modules.setdefault("comfy.model_management", comfy_mm)
sys.modules.setdefault("comfy.model_patcher", comfy_mp)

fp = types.ModuleType("folder_paths")
fp.models_dir = "/tmp/models"
fp.get_filename_list = lambda _: []
fp.get_full_path_or_raise = lambda *a: "/tmp/fake"
sys.modules.setdefault("folder_paths", fp)

torch_stub = types.ModuleType("torch")
torch_stub.Tensor = object # minimal stub
sys.modules.setdefault("torch", torch_stub)

PIL_stub = types.ModuleType("PIL")
PIL_Image_stub = types.ModuleType("PIL.Image")
PIL_Image_stub.Image = object # minimal stub
PIL_stub.Image = PIL_Image_stub
sys.modules.setdefault("PIL", PIL_stub)
sys.modules.setdefault("PIL.Image", PIL_Image_stub)

for _mod in [
"transformers",
"safetensors",
"safetensors.torch",
"einops",
"kornia",
"kornia.geometry",
"huggingface_hub",
]:
sys.modules.setdefault(_mod, types.ModuleType(_mod))

# ---------------------------------------------------------------------------
# Register the repo root as a Python package so relative imports work
# ---------------------------------------------------------------------------

pkg = types.ModuleType(PKG_NAME)
# Explicitly do NOT set __path__ or __file__ to prevent pytest from
# traversing the repo root and trying to import its __init__.py
pkg.__package__ = PKG_NAME
pkg.__spec__ = None
sys.modules[PKG_NAME] = pkg

# With --import-mode=importlib, pytest falls back to module_name_from_path()
# which produces "__init__" as the module name for the repo's __init__.py.
# Pre-register it so pytest finds it in sys.modules and skips exec'ing it.
_init_stub = types.ModuleType("__init__")
_init_stub.__package__ = PKG_NAME
sys.modules["__init__"] = _init_stub


def _load_submodule(name: str) -> types.ModuleType:
"""Load a .py file from REPO_ROOT as PKG_NAME.name."""
full_name = f"{PKG_NAME}.{name}"
if full_name in sys.modules:
return sys.modules[full_name]
path = os.path.join(REPO_ROOT, f"{name}.py")
spec = importlib.util.spec_from_file_location(full_name, path)
mod = importlib.util.module_from_spec(spec)
mod.__package__ = PKG_NAME
sys.modules[full_name] = mod
spec.loader.exec_module(mod)
setattr(pkg, name, mod)
return mod


# Pre-load the modules that tests depend on
_load_submodule("nodes_registry")
_load_submodule("prompt_enhancer_utils")
_load_submodule("minimax_prompt_enhancer")
Loading