Auto-Sync: Premium Labels & Models (2026-03-01)
This commit is contained in:
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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)"
|
||||
}
|
||||
Reference in New Issue
Block a user