File size: 6,424 Bytes
1758ac6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | 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();
}
|