Auto-Sync: Quad-Modal Update (2026-03-01)

This commit is contained in:
Krissy
2026-03-01 05:54:33 +00:00
parent 9e8a035e1c
commit c6ce82df36
5 changed files with 205 additions and 217 deletions
+29 -29
View File
@@ -26,61 +26,61 @@ Whether you are running on an 8GB laptop or a cloud server, this node suite offl
### 1. 🌸 Pollinations Image Gen (BYOP)
Generates high-fidelity images directly to a ComfyUI `IMAGE` tensor.
* **Supported Models:**
* `flux`
* `gptimage`
* `gptimage-large 💎`
* `grok-imagine`
* `imagen-4`
* `klein`
* `klein-large`
* `kontext 💎`
* `nanobanana 💎`
* `nanobanana-pro 💎`
* `seedream 💎`
* `seedream-pro 💎`
* `gptimage`
* `gptimage-large 💎`
* `flux`
* `zimage`
* `klein`
* `klein-large`
* `imagen-4`
* `grok-imagine`
* **Parameters:** `prompt`, `model`, `width`, `height`, `seed`, `api_key`, `negative_prompt`
### 2. 🌸 Pollinations Video Gen (BYOP)
Generates high-quality AI video.
* **Supported Models:**
* `veo 💎`
* `seedance`
* `seedance-pro 💎`
* `wan 💎`
* `grok-video`
* `ltx-2 💎`
* `seedance`
* `seedance-pro 💎`
* `veo 💎`
* `wan 💎`
* **Parameters:** `prompt`, `model`, `seed`, `api_key`
### 3. 🌸 Pollinations Text Gen (BYOP)
Leverage top-tier LLMs for prompt expansion, dynamic tagging, or scriptwriting inside your workflow.
* **Supported Models:**
* `openai`
* `openai-fast`
* `openai-large`
* `qwen-coder`
* `mistral`
* `openai-audio`
* `claude 💎`
* `claude-fast`
* `claude-large 💎`
* `deepseek`
* `gemini 💎`
* `gemini-fast`
* `deepseek`
* `grok 💎`
* `gemini-large 💎`
* `gemini-search`
* `glm`
* `grok 💎`
* `kimi`
* `midijourney`
* `claude-fast`
* `claude 💎`
* `claude-large 💎`
* `minimax`
* `mistral`
* `nomnom`
* `nova-fast`
* `openai`
* `openai-audio`
* `openai-fast`
* `openai-large`
* `perplexity-fast`
* `perplexity-reasoning`
* `kimi`
* `gemini-large 💎`
* `nova-fast`
* `glm`
* `minimax`
* `nomnom`
* `polly`
* `qwen-safety`
* `qwen-character`
* `qwen-coder`
* `qwen-safety`
* **Parameters:** `prompt`, `system_instruction`, `model`, `temperature`, `seed`, `api_key`
---
+4 -2
View File
@@ -3,13 +3,15 @@ from .pollinations_nodes import PollinationsImageGen, PollinationsTextGen, Polli
NODE_CLASS_MAPPINGS = {
"PollinationsImageGen": PollinationsImageGen,
"PollinationsTextGen": PollinationsTextGen,
"PollinationsVideoGen": PollinationsVideoGen
"PollinationsVideoGen": PollinationsVideoGen,
"PollinationsAudioGen": PollinationsAudioGen
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PollinationsImageGen": "🌸🖼️ Pollinations Image Gen (BYOP)",
"PollinationsTextGen": "🌸🤖 Pollinations Text Gen (BYOP)",
"PollinationsVideoGen": "🌸🎞️ Pollinations Video Gen URL (BYOP)"
"PollinationsVideoGen": "🌸🎞️ Pollinations Video Gen URL (BYOP)",
"PollinationsAudioGen": "🌸🔊 Pollinations Audio Gen (BYOP)"
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
+37 -31
View File
@@ -1,53 +1,59 @@
{
"image": [
"flux",
"gptimage",
"gptimage-large \ud83d\udc8e",
"grok-imagine",
"imagen-4",
"klein",
"klein-large",
"kontext \ud83d\udc8e",
"nanobanana \ud83d\udc8e",
"nanobanana-pro \ud83d\udc8e",
"seedream \ud83d\udc8e",
"seedream-pro \ud83d\udc8e",
"gptimage",
"gptimage-large \ud83d\udc8e",
"flux",
"zimage",
"klein",
"klein-large",
"imagen-4",
"grok-imagine"
"zimage"
],
"video": [
"veo \ud83d\udc8e",
"grok-video",
"ltx-2 \ud83d\udc8e",
"seedance",
"seedance-pro \ud83d\udc8e",
"wan \ud83d\udc8e",
"grok-video",
"ltx-2 \ud83d\udc8e"
"veo \ud83d\udc8e",
"wan \ud83d\udc8e"
],
"text": [
"openai",
"openai-fast",
"openai-large",
"qwen-coder",
"mistral",
"openai-audio",
"claude \ud83d\udc8e",
"claude-fast",
"claude-large \ud83d\udc8e",
"deepseek",
"gemini \ud83d\udc8e",
"gemini-fast",
"deepseek",
"grok \ud83d\udc8e",
"gemini-large \ud83d\udc8e",
"gemini-search",
"glm",
"grok \ud83d\udc8e",
"kimi",
"midijourney",
"claude-fast",
"claude \ud83d\udc8e",
"claude-large \ud83d\udc8e",
"minimax",
"mistral",
"nomnom",
"nova-fast",
"openai",
"openai-audio",
"openai-fast",
"openai-large",
"perplexity-fast",
"perplexity-reasoning",
"kimi",
"gemini-large \ud83d\udc8e",
"nova-fast",
"glm",
"minimax",
"nomnom",
"polly",
"qwen-safety",
"qwen-character"
"qwen-character",
"qwen-coder",
"qwen-safety"
],
"audio": [
"elevenlabs",
"elevenmusic",
"scribe",
"whisper"
]
}
+35 -48
View File
@@ -11,57 +11,53 @@ JSON_FILE = os.path.join(REPO_DIR, "models.json")
README_FILE = os.path.join(REPO_DIR, "README.md")
def fetch_api_models():
print("🛰️ Scouting Pollinations API for new models (Premium Aware)...")
print("🛰️ Scouting Pollinations Official API (Quad-Modal Pass)...")
try:
# 1. Fetch Text Models
# 1. Text Models
text_resp = requests.get("https://gen.pollinations.ai/text/models", timeout=10).json()
# We append 💎 if paid_only is True
text_models = [
f"{m['name']} 💎" if m.get("paid_only") else m["name"]
for m in text_resp if "name" in m
]
text_models = [f"{m['name']} 💎" if m.get("paid_only") else m["name"] for m in text_resp if "name" in m]
# 2. Fetch Image & Video Models
# 2. Image & Video Models
img_vid_resp = requests.get("https://gen.pollinations.ai/image/models", timeout=10).json()
image_models = []
video_models = []
image_models, video_models = [], []
for m in img_vid_resp:
model_id = m.get("name")
if not model_id: continue
# Append diamond for paid models
display_name = f"{model_id} 💎" if m.get("paid_only") else model_id
# Detect video models via output_modalities array
out_modalities = m.get("output_modalities", [])
if "video" in out_modalities:
video_models.append(display_name)
name = m.get("name")
if not name: continue
display = f"{name} 💎" if m.get("paid_only") else name
if "video" in m.get("output_modalities", []):
video_models.append(display)
else:
image_models.append(display_name)
# Safety defaults (Clean IDs - 💎 logic handled later in script if missing)
return {"image": image_models, "video": video_models, "text": text_models}
image_models.append(display)
# 3. Audio Models (NEW)
audio_resp = requests.get("https://gen.pollinations.ai/audio/models", timeout=10).json()
audio_models = [f"{m['name']} 💎" if m.get("paid_only") else m["name"] for m in audio_resp if "name" in m]
# Safety Fallbacks
if not text_models: text_models = ["openai"]
if not image_models: image_models = ["flux"]
if not video_models: video_models = ["wan"]
if not audio_models: audio_models = ["elevenlabs"]
return {
"image": sorted(image_models),
"video": sorted(video_models),
"text": sorted(text_models),
"audio": sorted(audio_models)
}
except Exception as e:
print(f"❌ API Fetch Failed: {e}")
return None
def update_json_file(models):
print("💾 Updating models.json...")
with open(JSON_FILE, 'w', encoding='utf-8') as f:
json.dump(models, f, indent=4)
def update_readme(models):
print("📝 Updating README.md...")
if not os.path.exists(README_FILE):
return
if not os.path.exists(README_FILE): return
with open(README_FILE, 'r', encoding='utf-8') as f:
content = f.read()
def replace_md_list(section_id, new_list, text):
escaped_id = re.escape(section_id)
# Regex looks for the section header down to the next parameter or section break
pattern = rf"(###.*?{escaped_id}.*?\n\* \*\*Supported Models:\*\*.*?\n)(.*?)(\n\* \*\*Parameters|\n---|\n##)"
formatted_list = "\n".join([f" * `{m}`" for m in new_list])
return re.sub(pattern, f"\\1{formatted_list}\\3", text, flags=re.DOTALL | re.IGNORECASE)
@@ -69,42 +65,33 @@ def update_readme(models):
content = replace_md_list("Image Gen", models["image"], content)
content = replace_md_list("Video Gen", models["video"], content)
content = replace_md_list("Text Gen", models["text"], content)
content = replace_md_list("Audio Gen", models["audio"], content)
with open(README_FILE, 'w', encoding='utf-8') as f:
f.write(content)
def git_sync_everything():
print("🚀 Synchronizing local machine to GitHub and HuggingFace...")
print("🚀 Synchronizing local machine to Cloud...")
try:
subprocess.run(["git", "add", "."], cwd=REPO_DIR)
status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, cwd=REPO_DIR)
if not status.stdout.strip():
print(" ✅ Local state matches previous commit.")
return
date_str = datetime.now().strftime("%Y-%m-%d")
subprocess.run(["git", "commit", "-m", f"Auto-Sync: Premium Labels & Models ({date_str})"], cwd=REPO_DIR)
print(" Merging cloud changes...")
subprocess.run(["git", "commit", "-m", f"Auto-Sync: Quad-Modal Update ({date_str})"], cwd=REPO_DIR)
subprocess.run(["git", "pull", "origin", "main", "-X", "ours", "--no-edit"], cwd=REPO_DIR)
print(" Pushing to GitHub (origin)...")
subprocess.run(["git", "push", "origin", "main"], cwd=REPO_DIR)
print(" Pushing to HuggingFace...")
subprocess.run(["git", "push", "huggingface", "main", "--force"], cwd=REPO_DIR)
print("🎉 Full sync complete!")
except Exception as e:
print(f"❌ Git Sync Failed: {e}")
if __name__ == "__main__":
print(f"=== Pollinations Engine Updater: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===")
os.chdir(REPO_DIR)
live_models = fetch_api_models()
if live_models:
update_json_file(live_models)
with open(JSON_FILE, 'w', encoding='utf-8') as f:
json.dump(live_models, f, indent=4)
update_readme(live_models)
git_sync_everything()
+100 -107
View File
@@ -8,172 +8,165 @@ import json
import os
import logging
# SILENCE LOGGERS to prevent any console/pipe conflicts
# SILENCE LOGGERS
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
def load_model_list(category, default_list):
def get_models():
current_dir = os.path.dirname(os.path.abspath(__file__))
json_path = os.path.join(current_dir, "models.json")
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
json_path = os.path.join(current_dir, "models.json")
if os.path.exists(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get(category, default_list)
except Exception:
pass
return default_list
return json.load(f)
except: pass
return {"image": ["flux"], "video": ["wan"], "text": ["openai"], "audio": ["elevenlabs"]}
def upload_to_pollinations(image_tensor):
try:
i = 255. * image_tensor.cpu().numpy().squeeze()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
buffered = io.BytesIO()
img.save(buffered, format="PNG")
resp = requests.post("https://media.pollinations.ai/upload", files={"file": ("image.png", buffered.getvalue(), "image/png")})
if resp.status_code == 200: return resp.json().get("url")
except: pass
return None
class PollinationsImageGen:
@classmethod
def INPUT_TYPES(s):
model_list = load_model_list("image", ["flux", "zimage", "klein-large"])
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "a cat in space"}),
"model": (model_list, {"default": "flux"}),
"model": (model_data["image"], {"default": "flux"}),
"width": ("INT", {"default": 1024, "min": 256, "max": 4096, "step": 8}),
"height": ("INT", {"default": 1024, "min": 256, "max": 4096, "step": 8}),
"seed": ("INT", {"default": 42, "min": -1, "max": 2147483647}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False}),
"image_input": ("IMAGE",),
"negative_prompt": ("STRING", {"multiline": True, "default": ""})
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("image", "url")
FUNCTION = "generate"
CATEGORY = "Pollinations"
CATEGORY = "Pollinations/Image"
def generate(self, prompt, model, width, height, seed, api_key, negative_prompt=""):
# Clean the model name by removing the diamond emoji and extra spaces
model = model.replace("💎", "").strip()
# 1. Official Path Construction
def generate(self, prompt, model, width, height, seed, api_key="", image_input=None, negative_prompt=""):
clean_model = model.replace("💎", "").strip()
encoded_prompt = urllib.parse.quote(prompt.replace("\n", " ").strip())
url = f"https://gen.pollinations.ai/image/{encoded_prompt}?model={clean_model}&width={width}&height={height}&seed={seed}&nologo=true"
if image_input is not None:
img_url = upload_to_pollinations(image_input)
if img_url: url += f"&image={urllib.parse.quote(img_url)}"
if negative_prompt.strip(): url += f"&negative_prompt={urllib.parse.quote(negative_prompt.strip())}"
# Base URL from official api.json
base_url = f"https://gen.pollinations.ai/image/{encoded_prompt}"
# 2. Query Parameters (Official Spec)
params = {
"model": model,
"width": width,
"height": height,
"seed": seed,
"nologo": "true"
}
if negative_prompt and negative_prompt.strip() != "":
params["negative_prompt"] = negative_prompt.strip()
# 3. Headers (Crucial for WAF bypass and BYOP)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
}
if api_key and api_key.strip() != "":
headers["Authorization"] = f"Bearer {api_key.strip()}"
headers = {"User-Agent": "Mozilla/5.0"}
if api_key.strip(): headers["Authorization"] = f"Bearer {api_key.strip()}"
try:
response = requests.get(base_url, params=params, headers=headers, timeout=120)
if response.status_code == 200:
image = Image.open(io.BytesIO(response.content)).convert("RGB")
image_np = np.array(image).astype(np.float32) / 255.0
return (torch.from_numpy(image_np)[None,],)
else:
# Return Black square with Red pixel in corner on error
err_img = np.zeros((width, height, 3), dtype=np.float32)
err_img[0,0] = [1.0, 0, 0]
return (torch.from_numpy(err_img)[None,],)
except Exception:
return (torch.zeros((1, 512, 512, 3)),)
r = requests.get(url, headers=headers, timeout=120)
if r.status_code == 200:
img = Image.open(io.BytesIO(r.content)).convert("RGB")
img_np = np.array(img).astype(np.float32) / 255.0
return (torch.from_numpy(img_np)[None,], url)
except: pass
return (torch.zeros((1, 512, 512, 3)), url)
class PollinationsTextGen:
class PollinationsAudioGen:
@classmethod
def INPUT_TYPES(s):
model_list = load_model_list("text", ["openai", "deepseek", "gemini"])
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "Hello!"}),
"model": (model_list, {"default": "openai"}),
"system_instruction": ("STRING", {"multiline": True, "default": "You are a helpful assistant."}),
"seed": ("INT", {"default": 42, "min": -1, "max": 9007199254740991}),
"text": ("STRING", {"multiline": True, "default": "Hello world"}),
"model": (model_data["audio"], {"default": "elevenlabs"}),
"voice": (["alloy", "echo", "fable", "onyx", "nova", "shimmer", "sarah", "rachel", "charlie"], {"default": "sarah"}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False})
}
"optional": {"api_key": ("STRING", {"default": ""})}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
RETURN_NAMES = ("audio_url",)
FUNCTION = "generate"
CATEGORY = "Pollinations"
CATEGORY = "Pollinations/Audio"
def generate(self, prompt, model, system_instruction, seed, api_key):
# Clean the model name by removing the diamond emoji and extra spaces
model = model.replace("💎", "").strip()
url = "https://gen.pollinations.ai/v1/chat/completions"
headers = {"Content-Type": "application/json"}
if api_key and api_key.strip() != "":
headers["Authorization"] = f"Bearer {api_key.strip()}"
payload = {
"model": model,
"messages": [
{"role": "system", "content": system_instruction},
{"role": "user", "content": prompt}
],
"seed": seed
}
try:
response = requests.post(url, headers=headers, json=payload, timeout=60)
if response.status_code == 200:
return (response.json()["choices"][0]["message"]["content"],)
return (f"Error: {response.status_code}",)
except Exception as e:
return (str(e),)
def generate(self, text, model, voice, api_key=""):
clean_model = model.replace("💎", "").strip()
encoded_text = urllib.parse.quote(text.strip())
url = f"https://gen.pollinations.ai/audio/{encoded_text}?model={clean_model}&voice={voice}"
if api_key.strip(): url += f"&key={api_key.strip()}"
return (url,)
class PollinationsVideoGen:
@classmethod
def INPUT_TYPES(s):
model_list = load_model_list("video", ["wan", "seedance", "veo"])
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "a sunset timelapse"}),
"model": (model_list, {"default": "wan"}),
"prompt": ("STRING", {"multiline": True, "default": "cinematic sunset"}),
"model": (model_data["video"], {"default": "wan"}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False})
"api_key": ("STRING", {"default": ""}),
"image_input": ("IMAGE",),
"duration": ("INT", {"default": 5, "min": 2, "max": 15}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_url",)
FUNCTION = "generate"
CATEGORY = "Pollinations"
CATEGORY = "Pollinations/Video"
def generate(self, prompt, model, api_key):
# Clean the model name by removing the diamond emoji and extra spaces
model = model.replace("💎", "").strip()
def generate(self, prompt, model, api_key="", image_input=None, duration=5):
clean_model = model.replace("💎", "").strip()
encoded_prompt = urllib.parse.quote(prompt.strip())
url = f"https://gen.pollinations.ai/video/{encoded_prompt}?model={model}"
if api_key and api_key.strip() != "":
url += f"&key={api_key.strip()}"
url = f"https://gen.pollinations.ai/video/{encoded_prompt}?model={clean_model}&duration={duration}"
if image_input is not None:
img_url = upload_to_pollinations(image_input)
if img_url: url += f"&image={urllib.parse.quote(img_url)}"
if api_key.strip(): url += f"&key={api_key.strip()}"
return (url,)
class PollinationsTextGen:
@classmethod
def INPUT_TYPES(s):
model_data = get_models()
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "Hi!"}),
"model": (model_data["text"], {"default": "openai"}),
"system_instruction": ("STRING", {"multiline": True, "default": "You are helpful assistant."}),
},
"optional": {"api_key": ("STRING", {"default": ""})}
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate"
CATEGORY = "Pollinations/Text"
def generate(self, prompt, model, system_instruction, api_key=""):
clean_model = model.replace("💎", "").strip()
url = "https://gen.pollinations.ai/v1/chat/completions"
headers = {"Content-Type": "application/json"}
if api_key.strip(): headers["Authorization"] = f"Bearer {api_key.strip()}"
payload = {"model": clean_model, "messages": [{"role": "system", "content": system_instruction}, {"role": "user", "content": prompt}]}
try:
r = requests.post(url, json=payload, headers=headers)
return (r.json()["choices"][0]["message"]["content"],)
except: return ("Error connecting to Text API",)
NODE_CLASS_MAPPINGS = {
"PollinationsImageGen": PollinationsImageGen,
"PollinationsTextGen": PollinationsTextGen,
"PollinationsVideoGen": PollinationsVideoGen
"PollinationsVideoGen": PollinationsVideoGen,
"PollinationsAudioGen": PollinationsAudioGen
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PollinationsImageGen": "🌸🖼️ Pollinations Image Gen (BYOP)",
"PollinationsTextGen": "🌸🤖 Pollinations Text Gen (BYOP)",
"PollinationsVideoGen": "🌸🎞️ Pollinations Video Gen URL (BYOP)"
"PollinationsVideoGen": "🌸🎞️ Pollinations Video Gen URL (BYOP)",
"PollinationsAudioGen": "🌸🔊 Pollinations Audio Gen (BYOP)"
}