Auto-Sync: Premium Labels & Models (2026-03-01)

This commit is contained in:
Krissy
2026-03-01 03:30:27 +00:00
parent 8278a63db5
commit 9e8a035e1c
4 changed files with 114 additions and 111 deletions
+15 -15
View File
@@ -26,13 +26,13 @@ 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:**
* `kontext`
* `nanobanana`
* `nanobanana-pro`
* `seedream`
* `seedream-pro`
* `kontext 💎`
* `nanobanana 💎`
* `nanobanana-pro 💎`
* `seedream 💎`
* `seedream-pro 💎`
* `gptimage`
* `gptimage-large`
* `gptimage-large 💎`
* `flux`
* `zimage`
* `klein`
@@ -44,12 +44,12 @@ Generates high-fidelity images directly to a ComfyUI `IMAGE` tensor.
### 2. 🌸 Pollinations Video Gen (BYOP)
Generates high-quality AI video.
* **Supported Models:**
* `veo`
* `veo 💎`
* `seedance`
* `seedance-pro`
* `wan`
* `seedance-pro 💎`
* `wan 💎`
* `grok-video`
* `ltx-2`
* `ltx-2 💎`
* **Parameters:** `prompt`, `model`, `seed`, `api_key`
### 3. 🌸 Pollinations Text Gen (BYOP)
@@ -61,19 +61,19 @@ Leverage top-tier LLMs for prompt expansion, dynamic tagging, or scriptwriting i
* `qwen-coder`
* `mistral`
* `openai-audio`
* `gemini`
* `gemini 💎`
* `gemini-fast`
* `deepseek`
* `grok`
* `grok 💎`
* `gemini-search`
* `midijourney`
* `claude-fast`
* `claude`
* `claude-large`
* `claude 💎`
* `claude-large 💎`
* `perplexity-fast`
* `perplexity-reasoning`
* `kimi`
* `gemini-large`
* `gemini-large 💎`
* `nova-fast`
* `glm`
* `minimax`
+15 -15
View File
@@ -1,12 +1,12 @@
{
"image": [
"kontext",
"nanobanana",
"nanobanana-pro",
"seedream",
"seedream-pro",
"kontext \ud83d\udc8e",
"nanobanana \ud83d\udc8e",
"nanobanana-pro \ud83d\udc8e",
"seedream \ud83d\udc8e",
"seedream-pro \ud83d\udc8e",
"gptimage",
"gptimage-large",
"gptimage-large \ud83d\udc8e",
"flux",
"zimage",
"klein",
@@ -15,12 +15,12 @@
"grok-imagine"
],
"video": [
"veo",
"veo \ud83d\udc8e",
"seedance",
"seedance-pro",
"wan",
"seedance-pro \ud83d\udc8e",
"wan \ud83d\udc8e",
"grok-video",
"ltx-2"
"ltx-2 \ud83d\udc8e"
],
"text": [
"openai",
@@ -29,19 +29,19 @@
"qwen-coder",
"mistral",
"openai-audio",
"gemini",
"gemini \ud83d\udc8e",
"gemini-fast",
"deepseek",
"grok",
"grok \ud83d\udc8e",
"gemini-search",
"midijourney",
"claude-fast",
"claude",
"claude-large",
"claude \ud83d\udc8e",
"claude-large \ud83d\udc8e",
"perplexity-fast",
"perplexity-reasoning",
"kimi",
"gemini-large",
"gemini-large \ud83d\udc8e",
"nova-fast",
"glm",
"minimax",
+16 -21
View File
@@ -11,11 +11,15 @@ 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...")
print("🛰️ Scouting Pollinations API for new models (Premium Aware)...")
try:
# 1. Fetch Text Models
text_resp = requests.get("https://gen.pollinations.ai/text/models", timeout=10).json()
text_models = [m["name"] for m in text_resp if "name" in m]
# 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
]
# 2. Fetch Image & Video Models
img_vid_resp = requests.get("https://gen.pollinations.ai/image/models", timeout=10).json()
@@ -27,18 +31,17 @@ def fetch_api_models():
model_id = m.get("name")
if not model_id: continue
# FIXED LOGIC: Detect video models via output_modalities array
# 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(model_id)
video_models.append(display_name)
else:
image_models.append(model_id)
image_models.append(display_name)
# Safety defaults to ensure nodes don't break if API is empty
if "flux" not in image_models: image_models.insert(0, "flux")
if "wan" not in video_models: video_models.insert(0, "wan")
if "openai" not in text_models: text_models.insert(0, "openai")
# Safety defaults (Clean IDs - 💎 logic handled later in script if missing)
return {"image": image_models, "video": video_models, "text": text_models}
except Exception as e:
print(f"❌ API Fetch Failed: {e}")
@@ -58,9 +61,7 @@ def update_readme(models):
content = f.read()
def replace_md_list(section_id, new_list, text):
"""Surgically replaces the bulleted list in the README based on section headers."""
escaped_id = re.escape(section_id)
# Matches from header + Supported Models line until the Parameters line 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)
@@ -75,32 +76,26 @@ def update_readme(models):
def git_sync_everything():
print("🚀 Synchronizing local machine to GitHub and HuggingFace...")
try:
# 1. Add everything (Includes new folders: images/, workflows/)
subprocess.run(["git", "add", "."], cwd=REPO_DIR)
# 2. Check if there are actually changes to commit
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. No push needed.")
print(" ✅ Local state matches previous commit.")
return
# 3. Create the commit
date_str = datetime.now().strftime("%Y-%m-%d")
subprocess.run(["git", "commit", "-m", f"Auto-Sync: Models, Assets, and Workflows ({date_str})"], cwd=REPO_DIR)
subprocess.run(["git", "commit", "-m", f"Auto-Sync: Premium Labels & Models ({date_str})"], cwd=REPO_DIR)
# 4. Pull first to resolve any cloud-side changes (Force our version)
print(" Merging cloud changes...")
subprocess.run(["git", "pull", "origin", "main", "-X", "ours", "--no-edit"], cwd=REPO_DIR)
# 5. Push to GitHub
print(" Pushing to GitHub (origin)...")
subprocess.run(["git", "push", "origin", "main"], cwd=REPO_DIR)
# 6. Push to HuggingFace
print(" Pushing to HuggingFace...")
subprocess.run(["git", "push", "huggingface", "main", "--force"], cwd=REPO_DIR)
print("🎉 Full sync complete! All folders and models are now live.")
print("🎉 Full sync complete!")
except Exception as e:
print(f"❌ Git Sync Failed: {e}")
+68 -60
View File
@@ -5,20 +5,17 @@ import numpy as np
from PIL import Image
import io
import json
import time
import logging
import os
import logging
# SILENCE LOGGERS (Prevent WinError 233)
# SILENCE LOGGERS to prevent any console/pipe conflicts
logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
def load_model_list(category, default_list):
"""Safely loads model lists from models.json relative to this file."""
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)
@@ -30,17 +27,14 @@ def load_model_list(category, default_list):
class PollinationsImageGen:
@classmethod
def INPUT_TYPES(s):
# Default fallback if JSON fails
defaults = ["flux", "turbo", "klein"]
model_list = load_model_list("image", defaults)
model_list = load_model_list("image", ["flux", "zimage", "klein-large"])
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "A cinematic product shot..."}),
"prompt": ("STRING", {"multiline": True, "default": "a cat in space"}),
"model": (model_list, {"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": 0, "max": 0xffffffffffffffff}),
"seed": ("INT", {"default": 42, "min": -1, "max": 2147483647}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False}),
@@ -54,51 +48,60 @@ class PollinationsImageGen:
CATEGORY = "Pollinations"
def generate(self, prompt, model, width, height, seed, api_key, negative_prompt=""):
encoded_prompt = urllib.parse.quote(prompt)
url = f"https://image.pollinations.ai/prompt/{encoded_prompt}?model={model}&width={width}&height={height}&seed={seed}&nologo=true"
# Clean the model name by removing the diamond emoji and extra spaces
model = model.replace("💎", "").strip()
# 1. Official Path Construction
encoded_prompt = urllib.parse.quote(prompt.replace("\n", " ").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() != "":
encoded_neg = urllib.parse.quote(negative_prompt)
url += f"&nofeed={encoded_neg}"
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",
"Accept": "image/jpeg, image/png, image/*"
}
if api_key and api_key.strip() != "":
headers["Authorization"] = f"Bearer {api_key.strip()}"
try:
response = requests.get(url, headers=headers, timeout=120)
response = requests.get(base_url, params=params, headers=headers, timeout=120)
if response.status_code == 200:
image_bytes = io.BytesIO(response.content)
image = Image.open(image_bytes).convert("RGB")
image = Image.open(io.BytesIO(response.content)).convert("RGB")
image_np = np.array(image).astype(np.float32) / 255.0
image_tensor = torch.from_numpy(image_np)[None,]
return (image_tensor,)
return (torch.from_numpy(image_np)[None,],)
else:
error_img = torch.zeros((1, 512, 512, 3))
error_img[:, :, :, 0] = 1.0
return (error_img,)
# 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:
error_img = torch.zeros((1, 512, 512, 3))
error_img[:, :, :, 2] = 1.0
return (error_img,)
return (torch.zeros((1, 512, 512, 3)),)
class PollinationsTextGen:
@classmethod
def INPUT_TYPES(s):
defaults = ["openai", "claude", "mistral"]
model_list = load_model_list("text", defaults)
model_list = load_model_list("text", ["openai", "deepseek", "gemini"])
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "Write a viral TikTok script..."}),
"system_instruction": ("STRING", {"multiline": True, "default": "You are a creative assistant."}),
"prompt": ("STRING", {"multiline": True, "default": "Hello!"}),
"model": (model_list, {"default": "openai"}),
"temperature": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 2.0, "step": 0.1}),
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
"system_instruction": ("STRING", {"multiline": True, "default": "You are a helpful assistant."}),
"seed": ("INT", {"default": 42, "min": -1, "max": 9007199254740991}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False})
@@ -110,50 +113,39 @@ class PollinationsTextGen:
FUNCTION = "generate"
CATEGORY = "Pollinations"
def generate(self, prompt, system_instruction, model, temperature, seed, api_key):
url = "https://text.pollinations.ai/"
headers = {"Content-Type": "application/json", "User-Agent": "ComfyUI-Pollinations-Node"}
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}
],
"model": model,
"temperature": temperature,
"seed": seed
}
try:
response = requests.post(url, headers=headers, json=payload, timeout=60)
if response.status_code == 200:
if 'application/json' in response.headers.get('Content-Type', ''):
try:
result_text = response.json().get("choices", [{}])[0].get("message", {}).get("content", "")
return (result_text,)
except:
return (response.text,)
else:
return (response.text,)
else:
return (f"Error: {response.status_code}",)
return (response.json()["choices"][0]["message"]["content"],)
return (f"Error: {response.status_code}",)
except Exception as e:
return (f"Exception: {e}",)
return (str(e),)
class PollinationsVideoGen:
@classmethod
def INPUT_TYPES(s):
defaults = ["wan", "luma", "kling"]
model_list = load_model_list("video", defaults)
model_list = load_model_list("video", ["wan", "seedance", "veo"])
return {
"required": {
"prompt": ("STRING", {"multiline": True, "default": "A cinematic shot of..."}),
"prompt": ("STRING", {"multiline": True, "default": "a sunset timelapse"}),
"model": (model_list, {"default": "wan"}),
"seed": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),
},
"optional": {
"api_key": ("STRING", {"default": "", "multiline": False})
@@ -165,7 +157,23 @@ class PollinationsVideoGen:
FUNCTION = "generate"
CATEGORY = "Pollinations"
def generate(self, prompt, model, seed, api_key):
encoded_prompt = urllib.parse.quote(prompt)
url = f"https://video.pollinations.ai/prompt/{encoded_prompt}?model={model}&seed={seed}"
return (url,)
def generate(self, prompt, model, api_key):
# Clean the model name by removing the diamond emoji and extra spaces
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()}"
return (url,)
NODE_CLASS_MAPPINGS = {
"PollinationsImageGen": PollinationsImageGen,
"PollinationsTextGen": PollinationsTextGen,
"PollinationsVideoGen": PollinationsVideoGen
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PollinationsImageGen": "🌸🖼️ Pollinations Image Gen (BYOP)",
"PollinationsTextGen": "🌸🤖 Pollinations Text Gen (BYOP)",
"PollinationsVideoGen": "🌸🎞️ Pollinations Video Gen URL (BYOP)"
}