Download web/core.mjs from ceroomedia/DMAI-NODES: direct link, hf CLI and curl.
- Browser
- Download file 6.42 kB
-
https://huggingface.co/ceroomedia/DMAI-NODES/resolve/6e0df2b5334898f615431b75e477bab85c5ab136/web/core.mjs
- Command line
-
hf download hf://ceroomedia/DMAI-NODES@6e0df2b5334898f615431b75e477bab85c5ab136/web/core.mjs
-
curl -L -o core.mjs https://huggingface.co/ceroomedia/DMAI-NODES/resolve/6e0df2b5334898f615431b75e477bab85c5ab136/web/core.mjs
6.42 kB
| export const API = "/dmai-nodes/v1"; | |
| export const RATIOS = [ | |
| "1:1", | |
| "4:5", | |
| "5:4", | |
| "3:4", | |
| "4:3", | |
| "2:3", | |
| "3:2", | |
| "9:16", | |
| "16:9", | |
| "9:21", | |
| "21:9", | |
| "9:19.5", | |
| "19.5:9", | |
| "10:16", | |
| "16:10", | |
| "1:2", | |
| "2:1", | |
| "1:3", | |
| "3:1", | |
| ]; | |
| export const clone = (value) => structuredClone(value); | |
| export const uid = () => crypto.randomUUID(); | |
| export function number(value, min, max, label, integer = false) { | |
| if ( | |
| typeof value !== "number" || | |
| !Number.isFinite(value) || | |
| value < min || | |
| value > max || | |
| (integer && !Number.isInteger(value)) | |
| ) | |
| throw new Error( | |
| `${label} must be ${integer ? "a whole number" : "a number"} from ${min} to ${max}.`, | |
| ); | |
| return value; | |
| } | |
| export function parseObject(raw, label) { | |
| let value; | |
| try { | |
| value = JSON.parse(raw); | |
| } catch { | |
| throw new Error( | |
| `${label} contains invalid JSON. Your saved value has been preserved.`, | |
| ); | |
| } | |
| if ( | |
| !value || | |
| typeof value !== "object" || | |
| Array.isArray(value) || | |
| value.schema_version !== 1 | |
| ) | |
| throw new Error( | |
| `${label} must be a version 1 object. Your saved value has been preserved.`, | |
| ); | |
| return value; | |
| } | |
| export function readPrompt(raw) { | |
| const value = parseObject(raw, "Prompt configuration"); | |
| if ( | |
| typeof value.prompt !== "string" || | |
| typeof value.negative_prompt !== "string" | |
| ) | |
| throw new Error("Prompt and negative prompt must be text."); | |
| number(value.width, 64, 8192, "Width", true); | |
| number(value.height, 64, 8192, "Height", true); | |
| number(value.count, 1, 8, "Image count", true); | |
| if (value.width % 64 || value.height % 64) | |
| throw new Error("Dimensions must be multiples of 64."); | |
| return value; | |
| } | |
| export function dimensions(ratio, resolution) { | |
| if (!RATIOS.includes(ratio) || !["1K", "2K"].includes(resolution)) | |
| throw new Error("Choose a supported ratio and resolution."); | |
| const [w, h] = ratio.split(":").map(Number), | |
| long = resolution === "1K" ? 1024 : 2048; | |
| return w >= h | |
| ? [long, Math.max(64, Math.round((long * h) / w / 64) * 64)] | |
| : [Math.max(64, Math.round((long * w) / h / 64) * 64), long]; | |
| } | |
| export function customDimensions(width, height) { | |
| number(width, 64, 8192, "Width", true); | |
| number(height, 64, 8192, "Height", true); | |
| return [width, height].map((n) => Math.round(n / 64) * 64); | |
| } | |
| export function readManifest(raw) { | |
| const value = parseObject(raw, "LoRA configuration"); | |
| if (!Array.isArray(value.entries)) | |
| throw new Error("LoRA entries must be a list."); | |
| const ids = new Set(); | |
| for (const e of value.entries) { | |
| if (!e || typeof e.id !== "string" || !e.id || ids.has(e.id)) | |
| throw new Error("Each LoRA needs a unique entry ID."); | |
| ids.add(e.id); | |
| if (typeof e.name !== "string" || !e.name || typeof e.enabled !== "boolean") | |
| throw new Error( | |
| "Each LoRA needs a filename and a boolean enabled value.", | |
| ); | |
| number(e.strength_model, -20, 20, "Model strength"); | |
| number(e.strength_clip, -20, 20, "CLIP strength"); | |
| } | |
| return value; | |
| } | |
| export function moveEntry(entries, id, to) { | |
| const from = entries.findIndex((e) => e.id === id); | |
| if (from < 0 || !Number.isInteger(to) || to < 0 || to >= entries.length) | |
| return entries.slice(); | |
| const next = entries.slice(), | |
| [entry] = next.splice(from, 1); | |
| next.splice(to, 0, entry); | |
| return next; | |
| } | |
| export function readEngine(raw) { | |
| const value = parseObject(raw, "Engine configuration"); | |
| if ( | |
| typeof value.profile_id !== "string" || | |
| !["enhanced", "manual"].includes(value.mode) || | |
| typeof value.preset_id !== "string" | |
| ) | |
| throw new Error("Engine profile, mode or preset is invalid."); | |
| if (!value.settings || !value.models || !Array.isArray(value.presets)) | |
| throw new Error("Engine settings, models and presets are required."); | |
| if (typeof value.seed === "string") { | |
| if ( | |
| !/^\d{1,20}$/.test(value.seed) || | |
| BigInt(value.seed) > 18446744073709551615n | |
| ) | |
| throw new Error("Seed must be an unsigned 64-bit integer."); | |
| } else number(value.seed, 0, Number.MAX_SAFE_INTEGER, "Seed", true); | |
| validateSettings(value.settings); | |
| for (const key of ["diffusion_model", "text_encoder", "vae", "checkpoint"]) | |
| if (typeof value.models[key] !== "string") | |
| throw new Error(`Model filename ${key} must be text.`); | |
| return value; | |
| } | |
| export function validateSettings(s) { | |
| number(s.steps, 1, 150, "Steps", true); | |
| number(s.cfg, 0, 30, "CFG"); | |
| number(s.denoise, 0, 1, "Denoise"); | |
| if ( | |
| typeof s.sampler !== "string" || | |
| !s.sampler || | |
| typeof s.scheduler !== "string" || | |
| !s.scheduler || | |
| !["none", "krea2t"].includes(s.enhancer) | |
| ) | |
| throw new Error("Sampler, scheduler or enhancer is invalid."); | |
| return clone(s); | |
| } | |
| export function mergePresets(existing, incoming, builtins) { | |
| if (existing.length + incoming.length > 20) | |
| throw new Error( | |
| "An engine can store at most 20 imported presets. Remove an imported preset first.", | |
| ); | |
| const ids = new Set([...existing, ...builtins].map((p) => p.id)); | |
| for (const p of incoming) { | |
| if (ids.has(p.id)) | |
| throw new Error( | |
| `Preset ID ${p.id} already exists. Use a new ID before importing.`, | |
| ); | |
| ids.add(p.id); | |
| } | |
| return [...existing, ...incoming].map(clone); | |
| } | |
| export function selectionBody(selection, watermark) { | |
| if (selection.all) { | |
| if (!Number.isInteger(watermark) || watermark < 0) | |
| throw new Error("Refresh the gallery before selecting all images."); | |
| return { all: true, before: watermark }; | |
| } | |
| const ids = [...selection.ids]; | |
| if (!ids.length) throw new Error("Select at least one image."); | |
| return { ids }; | |
| } | |
| export async function envelope(api, path, options = {}) { | |
| const response = await api.fetchApi(`${API}${path}`, options); | |
| let data; | |
| try { | |
| data = await response.json(); | |
| } catch { | |
| throw new Error( | |
| `DMAI NODES returned an invalid response (${response.status}).`, | |
| ); | |
| } | |
| if (!response.ok || data?.ok !== true) | |
| throw new Error( | |
| data?.error?.message || `Request failed (${response.status}).`, | |
| ); | |
| return data.data; | |
| } | |
| export async function binary(api, path, options = {}) { | |
| const response = await api.fetchApi(`${API}${path}`, options); | |
| if (!response.ok) { | |
| let message; | |
| try { | |
| message = (await response.json())?.error?.message; | |
| } catch {} | |
| throw new Error(message || `Download failed (${response.status}).`); | |
| } | |
| return response.blob(); | |
| } | |