298 lines
11 KiB
TypeScript
298 lines
11 KiB
TypeScript
import type { LGraphNode } from "@comfyorg/litegraph";
|
|
import type {
|
|
IBaseWidget,
|
|
IWidgetOptions
|
|
} from "@comfyorg/litegraph/dist/types/widgets";
|
|
import { findPropertiesByNode, findPropertyJson } from "./configProperties.js";
|
|
import { DTSampler } from "./types";
|
|
|
|
export function importConfig(sampler: DTSampler) {
|
|
{
|
|
navigator.clipboard.readText().then(async (text) => {
|
|
try {
|
|
const config = JSON.parse(text);
|
|
|
|
const requiredNodes: string[] = [];
|
|
|
|
for (const [k, v] of Object.entries(config)) {
|
|
const prop = findPropertyJson(k);
|
|
if (!prop) {
|
|
continue;
|
|
}
|
|
if (prop.node !== sampler.type) {
|
|
requiredNodes.push(...prop.getRequiredNodes(v, config));
|
|
continue;
|
|
}
|
|
const widget = sampler.widgets?.find(
|
|
(w) => w.name === prop.python
|
|
);
|
|
if (!widget) {
|
|
continue;
|
|
}
|
|
await prop.import(k, v, widget as any, sampler as any, config);
|
|
}
|
|
sampler.coerceWidgetValues();
|
|
sampler.updateDynamicWidgets?.();
|
|
|
|
const availableNodes = sampler.getConfigInputNodes();
|
|
const missingNodes: string[] = [];
|
|
|
|
if (requiredNodes.includes("DrawThingsUpscaler")) {
|
|
if (!availableNodes.DrawThingsUpscaler.length)
|
|
missingNodes.push("DrawThingsUpscaler");
|
|
else
|
|
applyConfig(
|
|
availableNodes.DrawThingsUpscaler[0],
|
|
config
|
|
);
|
|
}
|
|
|
|
if (requiredNodes.includes("DrawThingsRefiner")) {
|
|
if (!availableNodes.DrawThingsRefiner.length)
|
|
missingNodes.push("DrawThingsRefiner");
|
|
else
|
|
applyConfig(
|
|
availableNodes.DrawThingsRefiner[0],
|
|
config
|
|
);
|
|
}
|
|
|
|
if (requiredNodes.includes("DrawThingsControlNet")) {
|
|
const cnetNeeded = requiredNodes.filter(
|
|
(n) => n === "DrawThingsControlNet"
|
|
).length;
|
|
if (cnetNeeded > availableNodes.DrawThingsControlNet.length)
|
|
missingNodes.push(
|
|
`${
|
|
cnetNeeded -
|
|
availableNodes.DrawThingsControlNet.length
|
|
} x DrawThingsControlNet`
|
|
);
|
|
let controlIndex = 0;
|
|
for (const cnetNode of availableNodes.DrawThingsControlNet) {
|
|
applyCnetConfig(
|
|
cnetNode,
|
|
config.controls[controlIndex++]
|
|
);
|
|
}
|
|
}
|
|
|
|
if (requiredNodes.includes("DrawThingsLoRA")) {
|
|
const loraNeeded = requiredNodes.filter(
|
|
(n) => n === "DrawThingsLoRA"
|
|
).length;
|
|
if (loraNeeded > availableNodes.DrawThingsLoRA.length)
|
|
missingNodes.push(
|
|
`${
|
|
loraNeeded -
|
|
availableNodes.DrawThingsLoRA.length
|
|
} x DrawThingsLoRA`
|
|
);
|
|
|
|
applyLoraConfig(
|
|
availableNodes.DrawThingsLoRA as (LGraphNode & {
|
|
loraCount: number;
|
|
})[],
|
|
config.loras
|
|
);
|
|
}
|
|
|
|
if (missingNodes.length) {
|
|
window.app?.extensionManager.toast.add({
|
|
severity: "warn",
|
|
summary: "Draw Things gRPC",
|
|
detail: [
|
|
"The Draw Things config has been partially loaded. To load the full config, add the following nodes:\n",
|
|
...missingNodes.map((n) => `• ${n}`),
|
|
].join("\n"),
|
|
life: 8000,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
alert(
|
|
"Failed to parse Draw Things config from clipboard\n\n" + e
|
|
);
|
|
console.warn(e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function applyConfig(node: LGraphNode, config: Record<string, unknown>) {
|
|
const nodeProps = findPropertiesByNode(node.type);
|
|
for (const nodeProp of nodeProps) {
|
|
const widget = node.widgets?.find((w) => w.name === nodeProp.python);
|
|
if (!widget || !nodeProp.json) continue;
|
|
nodeProp.import(
|
|
nodeProp.json,
|
|
config[nodeProp.json],
|
|
widget as any,
|
|
node,
|
|
config
|
|
);
|
|
}
|
|
}
|
|
|
|
function applyCnetConfig(
|
|
node: LGraphNode,
|
|
controlConfig: Record<string, unknown>
|
|
) {
|
|
const widgets =
|
|
node.widgets?.reduce((acc, w) => {
|
|
acc[w.name] = w;
|
|
return acc;
|
|
}, {} as Record<string, IBaseWidget<string | number | boolean | object | undefined, string, IWidgetOptions<unknown>>>) ??
|
|
{};
|
|
|
|
if ("file" in controlConfig) {
|
|
const options = widgets.control_name?.options as IWidgetOptions<
|
|
{ value: { file: string } }[]
|
|
>;
|
|
const matchingOption = options.values?.find(
|
|
(wv) => wv.value?.file === controlConfig.file
|
|
);
|
|
if (matchingOption) widgets.control_name.value = matchingOption;
|
|
}
|
|
|
|
if ("globalAveragePooling" in controlConfig)
|
|
widgets.global_average_pooling.value =
|
|
!!controlConfig.globalAveragePooling;
|
|
|
|
if ("weight" in controlConfig && typeof controlConfig.weight === "number")
|
|
widgets.control_weight.value = Math.min(
|
|
Math.max(controlConfig.weight, 0),
|
|
1.5
|
|
);
|
|
else widgets.control_weight.value = 1.0;
|
|
|
|
if (
|
|
"guidanceStart" in controlConfig &&
|
|
typeof controlConfig.guidanceStart === "number"
|
|
)
|
|
widgets.control_start.value = Math.min(
|
|
Math.max(controlConfig.guidanceStart, 0),
|
|
1
|
|
);
|
|
else widgets.control_start.value = 0;
|
|
|
|
if (
|
|
"guidanceEnd" in controlConfig &&
|
|
typeof controlConfig.guidanceEnd === "number"
|
|
)
|
|
widgets.control_end.value = Math.min(
|
|
Math.max(controlConfig.guidanceEnd, 0),
|
|
1
|
|
);
|
|
else widgets.control_end.value = 1;
|
|
|
|
if (
|
|
"downSamplingRate" in controlConfig &&
|
|
typeof controlConfig.downSamplingRate === "number"
|
|
)
|
|
widgets.down_sampling_rate.value = controlConfig.downSamplingRate;
|
|
else widgets.down_sampling_rate.value = 1;
|
|
|
|
if (
|
|
"inputOverride" in controlConfig &&
|
|
typeof controlConfig.inputOverride === "string"
|
|
) {
|
|
const capitalized =
|
|
controlConfig.inputOverride.charAt(0).toUpperCase() +
|
|
controlConfig.inputOverride.slice(1);
|
|
const options = widgets.control_input_type?.options as {
|
|
values: string[];
|
|
};
|
|
if (options?.values?.includes(capitalized))
|
|
widgets.control_input_type.value = capitalized;
|
|
else widgets.control_input_type.value = "Unspecified";
|
|
} else widgets.control_input_type.value = "Unspecified";
|
|
|
|
if (
|
|
"controlImportance" in controlConfig &&
|
|
typeof controlConfig.controlImportance === "string"
|
|
) {
|
|
const capitalized =
|
|
controlConfig.controlImportance.charAt(0).toUpperCase() +
|
|
controlConfig.controlImportance.slice(1);
|
|
const options = widgets.control_mode?.options as {
|
|
values: string[];
|
|
};
|
|
if (options?.values?.includes(capitalized))
|
|
widgets.control_mode.value = capitalized;
|
|
else widgets.control_mode.value = "Balanced";
|
|
} else widgets.control_mode.value = "Balanced";
|
|
|
|
if (
|
|
"targetBlocks" in controlConfig &&
|
|
Array.isArray(controlConfig.targetBlocks)
|
|
) {
|
|
if (controlConfig.targetBlocks.length === 0)
|
|
widgets.target_blocks.value = "All";
|
|
if (controlConfig.targetBlocks.length === 1)
|
|
widgets.target_blocks.value = "Style";
|
|
if (controlConfig.targetBlocks.length > 1)
|
|
widgets.target_blocks.value = "Style and Layout";
|
|
} else widgets.target_blocks.value = "All";
|
|
}
|
|
|
|
async function applyLoraConfig(
|
|
nodes: (LGraphNode & { loraCount: number })[],
|
|
loraConfig: Record<string, unknown>[]
|
|
) {
|
|
// any exissting loras will be cleared when loading a config
|
|
// lora count will be set to the number of loras in config, unless the current count is higher
|
|
// (to preserve layouts)
|
|
for (let iNode = 0; iNode < nodes.length; iNode++) {
|
|
const node = nodes[iNode];
|
|
|
|
const lastIndex = iNode * 8 + 8;
|
|
if (loraConfig.length > lastIndex) node.loraCount = 8;
|
|
// 13 loras
|
|
// 8 - (16 - 13) = 5
|
|
else node.loraCount = 8 - (lastIndex - loraConfig.length);
|
|
|
|
for (let iSlot = 0; iSlot < 8; iSlot++) {
|
|
const lc = loraConfig[iSlot + iNode * 8];
|
|
const { file, weight, mode } = getLoraSlotWidgets(node, iSlot);
|
|
|
|
if (!lc || !lc.file) {
|
|
file.value = "(None selected)";
|
|
weight.value = 1.0;
|
|
mode.value = "All";
|
|
continue;
|
|
}
|
|
|
|
const loraOptions = file?.options as {
|
|
values: { value: { file: string } }[];
|
|
};
|
|
const matchingOption = loraOptions?.values?.find(
|
|
(wv: { value: { file: string } }) => wv.value?.file === lc.file
|
|
);
|
|
if (matchingOption) file.value = matchingOption;
|
|
|
|
if ("weight" in lc && typeof lc.weight === "number")
|
|
weight.value = Math.min(Math.max(lc.weight, -5.0), 5.0);
|
|
else weight.value = 1.0;
|
|
|
|
if ("mode" in lc && typeof lc.mode === "string") {
|
|
const capitalized =
|
|
lc.mode.charAt(0).toUpperCase() + lc.mode.slice(1);
|
|
const options = mode?.options as { values: string[] };
|
|
if (options?.values?.includes(capitalized))
|
|
mode.value = capitalized;
|
|
else mode.value = "All";
|
|
} else mode.value = "All";
|
|
}
|
|
}
|
|
}
|
|
|
|
function getLoraSlotWidgets(node: LGraphNode, loraIndex: number) {
|
|
const widgets =
|
|
node.widgets?.slice(loraIndex * 3 + 1, loraIndex * 3 + 4) ?? [];
|
|
return {
|
|
file: widgets[0],
|
|
weight: widgets[1],
|
|
mode: widgets[2],
|
|
};
|
|
}
|