Files
fulletLab-comfyui-anima-sty…/js/utils.js
T
fulletLab 52f4992ff4 upd, feat browse
Update for favorites branch and direct connection with fullet.lat
2026-03-11 15:51:26 -04:00

319 lines
10 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { CDN_BASE } from "./config.js";
export function thumbUrl(artist, useCustom = false) {
if (!artist) return "";
const id = artist.id ?? "";
if (!id) return "";
if (useCustom) {
return `/anima/images/custom/${id}.webp`;
}
const page = artist.p ?? 1;
const preferLocal = !!(artist?._preferLocalThumb || artist?.localPreviewCached);
if (preferLocal) {
return `/anima/images/${page}/${id}.webp`;
}
const isOnline = localStorage.getItem("anima_online") === "true";
if (!isOnline) {
return `/anima/images/${page}/${id}.webp`;
}
return `${CDN_BASE}/images/${page}/${id}.webp`;
}
export function getPromptWidget(node) {
return node.widgets?.find(w =>
w.name === "text" ||
w.name === "prompt" ||
w.type === "customtext" ||
(w.type === "STRING" && w.inputEl)
) ?? null;
}
function setPromptValue(node, w, value, tag = "") {
w.value = value;
if (w.inputEl) {
w.inputEl.value = value;
w.inputEl.dispatchEvent(new Event("input", { bubbles: true }));
w.inputEl.dispatchEvent(new Event("change", { bubbles: true }));
}
if (w.callback) w.callback(value);
if (tag) {
node._currentTag = tag;
}
const badge = document.getElementById("anima-badge");
if (badge && tag) {
const displayTag = tag.replace(/_/g, " ");
badge.textContent = `@${displayTag}`;
badge.style.display = "block";
}
node.setDirtyCanvas?.(true, true);
app.graph.setDirtyCanvas?.(true, true);
}
function normalizeArtist(value = "") {
const display = String(value || "")
.replace(/^@+/, "")
.replace(/_/g, " ")
.replace(/\s+/g, " ")
.trim();
return {
display,
tag: display ? display.replace(/\s+/g, "_") : "",
token: display ? `@${display}` : "",
};
}
function getFirstArtistToken(text = "") {
const match = String(text || "").match(/@[^,\n]+/);
return match ? match[0].trim() : "";
}
function artistTokenToTag(token = "") {
return String(token || "")
.replace(/^@+/, "")
.replace(/\s+/g, "_")
.trim();
}
function composeArtistAndPrompt(artistToken = "", promptText = "") {
const a = String(artistToken || "").trim().replace(/[\s,]+$/g, "");
const p = String(promptText || "").trim().replace(/^[,\s]+/g, "");
if (a && p) return `${a}, ${p}`;
return a || p;
}
function resolvePreservedArtist(current = "", fallbackTag = "") {
const existingArtistToken = getFirstArtistToken(current);
if (existingArtistToken) {
return {
token: existingArtistToken,
tag: artistTokenToTag(existingArtistToken),
};
}
const fallback = normalizeArtist(fallbackTag);
return {
token: fallback.token,
tag: fallback.tag,
};
}
const ARTIST_LINE_RE = /(^|\n)(\s*(?:artist|artista|author|autor)\s*:\s*)([^\n]*)/i;
const PROMPT_LINE_RE = /(^|\n)(\s*(?:prompt|positive\s*prompt|prompt\s*positivo)\s*:\s*)([^\n]*)/i;
function replaceLabeledLine(text, lineRegex, value) {
let changed = false;
const next = String(text || "").replace(lineRegex, (_m, prefix, label) => {
changed = true;
return `${prefix}${label}${value}`;
});
return { text: next, changed };
}
function applyStructuredFields(current, { artistToken = "", promptText = "", updateArtist = false, updatePrompt = false } = {}) {
let next = String(current || "");
let changed = false;
if (updateArtist) {
const result = replaceLabeledLine(next, ARTIST_LINE_RE, artistToken);
next = result.text;
changed = changed || result.changed;
}
if (updatePrompt) {
const result = replaceLabeledLine(next, PROMPT_LINE_RE, promptText);
next = result.text;
changed = changed || result.changed;
}
return changed ? next : null;
}
function looksStructuredPrompt(text = "") {
const raw = String(text || "");
if (!raw) return false;
if (/\n/.test(raw)) return true;
return /(^|\s)[a-zA-Z][a-zA-Z0-9 _-]{1,28}:\s/.test(raw);
}
export function injectTag(current, tag) {
const text = String(current || "");
const spaceTag = tag ? tag.replace(/_/g, " ") : "";
if (!spaceTag) {
return text.replace(/(^|,\s*)@[^,\n]+(,\s*|$)/g, (_match, p1, p2) => p1 && p2 ? ", " : "").trim();
}
if (/@[^,\n]+/.test(text)) {
return text.replace(/@[^,\n]+(,\s*)?/, `@${spaceTag}, `);
}
const cleaned = text.trim().replace(/[\s,]+$/g, "");
return cleaned ? `${cleaned}, @${spaceTag}, ` : `@${spaceTag}, `;
}
export function applyStyle(node, artist) {
const w = getPromptWidget(node);
if (!w) return;
const tag = artist.tag;
const newVal = injectTag(String(w.value || ""), tag);
setPromptValue(node, w, newVal, tag);
}
export function stripLeadingArtist(prompt = "") {
return String(prompt || "").replace(/^\s*@[^,\n]+\s*,?\s*/i, "").trim();
}
export function buildFulletCopyText(post, mode = "both") {
const artist = normalizeArtist(post?.artist || "");
const promptOnly = stripLeadingArtist(post?.prompt || "");
if (mode === "prompt") return promptOnly;
if (mode === "artist") return artist.token;
return composeArtistAndPrompt(artist.token, promptOnly);
}
export function applyTemplateStyle(node, post) {
const w = getPromptWidget(node);
if (!w) {
return { ok: false, error: "Prompt widget not found in node." };
}
const template = String(w.value || "");
const hasPromptToken = /\{\{\s*prompt\s*\}\}/i.test(template);
const hasArtistToken = /\{\{\s*artist\s*\}\}/i.test(template);
if (!hasPromptToken || !hasArtistToken) {
return {
ok: false,
error: "Template must include both {{prompt}} and {{artist}}.",
};
}
const artist = normalizeArtist(post?.artist || "");
const rawPrompt = String(post?.prompt || "").trim();
if (!artist.display || !rawPrompt) {
return { ok: false, error: "Selected post is missing prompt or artist." };
}
const cleanedPrompt = stripLeadingArtist(rawPrompt);
const rendered = template
.replace(/\{\{\s*prompt\s*\}\}/gi, cleanedPrompt)
.replace(/\{\{\s*artist\s*\}\}/gi, artist.token);
setPromptValue(node, w, rendered, artist.tag);
return {
ok: true,
prompt: rendered,
artist: artist.display,
};
}
export function applyFulletSelection(node, post, mode = "both") {
const w = getPromptWidget(node);
if (!w) {
return { ok: false, error: "Prompt widget not found in node." };
}
const artist = normalizeArtist(post?.artist || "");
const rawPrompt = String(post?.prompt || "").trim();
if (!artist.display || !rawPrompt) {
return { ok: false, error: "Selected post is missing prompt or artist." };
}
const promptOnly = stripLeadingArtist(rawPrompt);
const current = String(w.value || "");
if (mode === "artist") {
const structured = applyStructuredFields(current, {
artistToken: artist.token,
updateArtist: true,
updatePrompt: false,
});
if (structured !== null) {
setPromptValue(node, w, structured, artist.tag);
return { ok: true, mode, prompt: structured, artist: artist.display };
}
const next = injectTag(current, artist.tag);
setPromptValue(node, w, next, artist.tag);
return { ok: true, mode, prompt: next, artist: artist.display };
}
if (mode === "prompt") {
const hasPromptToken = /\{\{\s*prompt\s*\}\}/i.test(current);
if (hasPromptToken) {
const rendered = current.replace(/\{\{\s*prompt\s*\}\}/gi, promptOnly);
const keepTag = String(node?._currentTag || "");
setPromptValue(node, w, rendered, keepTag);
return { ok: true, mode, prompt: rendered, artist: keepTag ? keepTag.replace(/_/g, " ") : "" };
}
const structured = applyStructuredFields(current, {
promptText: promptOnly,
updateArtist: false,
updatePrompt: true,
});
if (structured !== null) {
const keepTag = String(node?._currentTag || "");
setPromptValue(node, w, structured, keepTag);
return { ok: true, mode, prompt: structured, artist: keepTag ? keepTag.replace(/_/g, " ") : "" };
}
const preservedArtist = resolvePreservedArtist(current, String(node?._currentTag || ""));
const next = composeArtistAndPrompt(preservedArtist.token, promptOnly);
setPromptValue(node, w, next, preservedArtist.tag);
return {
ok: true,
mode,
prompt: next,
artist: preservedArtist.tag ? preservedArtist.tag.replace(/_/g, " ") : "",
};
}
const hasPromptToken = /\{\{\s*prompt\s*\}\}/i.test(current);
const hasArtistToken = /\{\{\s*artist\s*\}\}/i.test(current);
if (hasPromptToken && hasArtistToken) {
return applyTemplateStyle(node, post);
}
if (hasPromptToken || hasArtistToken) {
const rendered = current
.replace(/\{\{\s*prompt\s*\}\}/gi, promptOnly)
.replace(/\{\{\s*artist\s*\}\}/gi, artist.token);
const next = hasArtistToken ? rendered : injectTag(rendered, artist.tag);
setPromptValue(node, w, next, artist.tag);
return { ok: true, mode: "both", prompt: next, artist: artist.display };
}
const structured = applyStructuredFields(current, {
artistToken: artist.token,
promptText: promptOnly,
updateArtist: true,
updatePrompt: true,
});
if (structured !== null) {
setPromptValue(node, w, structured, artist.tag);
return { ok: true, mode: "both", prompt: structured, artist: artist.display };
}
const next = composeArtistAndPrompt(artist.token, promptOnly);
setPromptValue(node, w, next, artist.tag);
return { ok: true, mode: "both", prompt: next, artist: artist.display };
}