Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
|
||||
<template>
|
||||
<div class="rgthree-model-info-card">
|
||||
<figure class="rgthree-model-info-card-media-container">
|
||||
<video if="images.0 && images.0.type == 'video'" bind="images.0.url:src" loop autoplay></video>
|
||||
<img if="images.0 && images.0.type != 'video'" bind="images.0.url:src" loading="lazy" />
|
||||
</figure>
|
||||
<div class="rgthree-model-info-card-data-container">
|
||||
<div bind="name:text"></div>
|
||||
<div bind="file:text"></div>
|
||||
<div bind="getModified(modified):text"></div>
|
||||
<div bind="sha256:text"></div>
|
||||
<div bind="hasInfoFile"></div>
|
||||
<a if="getCivitaiLink(links)" bind="getCivitaiLink(links):href">Civitai</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
$space: 8px;
|
||||
|
||||
.rgthree-model-info-card {
|
||||
display: block;
|
||||
padding: $space;
|
||||
}
|
||||
|
||||
.-is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.rgthree-model-info-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>.rgthree-model-info-card-media-container {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 $space 0 0;
|
||||
padding: 0;
|
||||
flex: 0 0 auto;
|
||||
|
||||
>img,
|
||||
>video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
>.rgthree-model-info-card-data-container {
|
||||
|
||||
[bind*="name:"] {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: calc($space / 2);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import {RgthreeCustomElement} from "rgthree/common/components/base_custom_element";
|
||||
import {RgthreeModelInfo} from "typings/rgthree";
|
||||
|
||||
export class RgthreeModelInfoCard extends RgthreeCustomElement {
|
||||
static override readonly NAME = "rgthree-model-info-card";
|
||||
static override readonly TEMPLATES = "components/model-info-card.html";
|
||||
static override readonly CSS = "components/model-info-card.css";
|
||||
static override readonly USE_SHADOW = false;
|
||||
|
||||
private data: RgthreeModelInfo = {};
|
||||
|
||||
getModified(
|
||||
value: number,
|
||||
data: any,
|
||||
currentElement: HTMLElement,
|
||||
contextElement: RgthreeModelInfoCard,
|
||||
) {
|
||||
const date = new Date(value);
|
||||
return String(`${date.toLocaleDateString()} ${date.toLocaleTimeString()}`);
|
||||
}
|
||||
|
||||
getCivitaiLink(links: string[] | undefined) {
|
||||
return links?.find((i) => i.includes("civitai.com/models")) || null;
|
||||
}
|
||||
|
||||
setModelData(data: RgthreeModelInfo) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
hasBaseModel(baseModel: string) {
|
||||
return this.data.baseModel === baseModel;
|
||||
}
|
||||
|
||||
hasData(field: string) {
|
||||
// return !!this.data.hasInfoFile;
|
||||
if (field === "civitai") {
|
||||
return !!this.getCivitaiLink(this.data.links)?.length;
|
||||
}
|
||||
return !!(this.data as any)[field];
|
||||
}
|
||||
|
||||
matchesQueryText(query: string) {
|
||||
return (this.data.name || this.data.file)?.includes(query);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.classList.add("-is-hidden");
|
||||
}
|
||||
|
||||
show() {
|
||||
this.classList.remove("-is-hidden");
|
||||
}
|
||||
}
|
||||
18
custom_nodes/rgthree-comfy/src_web/models/index.html
Normal file
18
custom_nodes/rgthree-comfy/src_web/models/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>rgthree-comfy: Models Manager</title>
|
||||
<link href="./models.css" rel="stylesheet">
|
||||
<!-- <link rel="icon" href="./favicon.webp"> -->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<input type="text" placeholder="Search" id="searchbox" />
|
||||
</header>
|
||||
<ul id="models-list"></ul>
|
||||
<script type="module">
|
||||
import {ModelsInfoPage} from './models_info_page.js';
|
||||
window.page = new ModelsInfoPage();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
40
custom_nodes/rgthree-comfy/src_web/models/models.scss
Normal file
40
custom_nodes/rgthree-comfy/src_web/models/models.scss
Normal file
@@ -0,0 +1,40 @@
|
||||
$space: 8px;
|
||||
|
||||
:root {
|
||||
--rgthree-bg-color: rgba(23, 23, 23, 0.9);
|
||||
--rgthree-on-bg-color: rgba(48, 48, 48, 0.9);
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
box-sizing: border-body;
|
||||
background: var(--rgthree-bg-color);
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
[if-is="false"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.models-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.model-item {
|
||||
display: block;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
rgthree-model-info-card {
|
||||
background: var(--rgthree-on-bg-color);
|
||||
margin: $space * 2;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
}
|
||||
136
custom_nodes/rgthree-comfy/src_web/models/models_info_page.ts
Normal file
136
custom_nodes/rgthree-comfy/src_web/models/models_info_page.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import {createElement, getActionEls, query, queryAll} from "../common/utils_dom.js";
|
||||
|
||||
import {rgthreeApi} from "rgthree/common/rgthree_api.js";
|
||||
import {RgthreeModelInfoCard} from "./components/model-info-card.js";
|
||||
|
||||
function parseQuery(query: string) {
|
||||
// Split on spaces
|
||||
const matches = query.match(/"[^\"]+"/g) || [];
|
||||
for (const match of matches) {
|
||||
let cleaned = match.substring(1, match.length - 1);
|
||||
cleaned = cleaned.replace(/\s+/g, " ").trim().replace(/\s/g, "__SPACE__");
|
||||
query = query.replace(match, ` ${cleaned} `);
|
||||
}
|
||||
const queryParts = query
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.split(" ")
|
||||
.map((p) => p.replace(/__SPACE__/g, " "));
|
||||
return queryParts;
|
||||
}
|
||||
|
||||
export class ModelsInfoPage {
|
||||
private readonly selectBaseModel: HTMLSelectElement = createElement(
|
||||
'select[name="baseModel"][on="change:filter"]',
|
||||
);
|
||||
private readonly searchbox: HTMLInputElement = query<HTMLInputElement>("#searchbox")!;
|
||||
private readonly modelsList: HTMLUListElement = query("#models-list")!;
|
||||
private queryLast = "";
|
||||
private doSearchDebounce: number = 0;
|
||||
|
||||
constructor() {
|
||||
console.log("hello model page");
|
||||
// rgthreeApi.setBaseUrl('../api');
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.searchbox.addEventListener("input", (e) => {
|
||||
if (this.doSearchDebounce) {
|
||||
return;
|
||||
}
|
||||
this.doSearchDebounce = setTimeout(() => {
|
||||
this.doSearch();
|
||||
this.doSearchDebounce = 0;
|
||||
}, 250);
|
||||
});
|
||||
|
||||
const loras = await rgthreeApi.getLorasInfo({light: true});
|
||||
console.log(loras);
|
||||
|
||||
const baseModels = new Set<string>();
|
||||
|
||||
for (const lora of loras) {
|
||||
const el = RgthreeModelInfoCard.create<RgthreeModelInfoCard>();
|
||||
el.setModelData(lora);
|
||||
el.bindWhenConnected(lora);
|
||||
console.log(el);
|
||||
lora.baseModel && baseModels.add(lora.baseModel);
|
||||
this.modelsList.appendChild(createElement("li.model-item", {child: el}));
|
||||
}
|
||||
|
||||
if (baseModels.size > 1) {
|
||||
createElement(`option[value="ALL"][text="Choose base model."]`, {
|
||||
parent: this.selectBaseModel,
|
||||
});
|
||||
for (const baseModel of baseModels.values()) {
|
||||
createElement(`option[value="${baseModel}"][text="${baseModel}"]`, {
|
||||
parent: this.selectBaseModel,
|
||||
});
|
||||
}
|
||||
this.searchbox.insertAdjacentElement("afterend", this.selectBaseModel);
|
||||
}
|
||||
|
||||
const data = getActionEls(document.body);
|
||||
for (const dataItem of Object.values(data)) {
|
||||
for (const [event, action] of Object.entries(dataItem.actions)) {
|
||||
dataItem.el.addEventListener(event as keyof ElementEventMap, (e) => {
|
||||
if (typeof (this as any)[action] != "function") {
|
||||
throw new Error(`"${action}" does not exist on instance.`);
|
||||
}
|
||||
(this as any)[action](e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filter() {
|
||||
const parts = parseQuery(this.queryLast);
|
||||
const baseModel = this.selectBaseModel.value;
|
||||
const els = queryAll<RgthreeModelInfoCard>(RgthreeModelInfoCard.NAME);
|
||||
const shouldHide = (el: RgthreeModelInfoCard) => {
|
||||
let hide = baseModel !== "ALL" && !el.hasBaseModel(baseModel);
|
||||
if (!hide) {
|
||||
for (let part of parts) {
|
||||
let negate = false;
|
||||
if (part.startsWith("-")) {
|
||||
negate = true;
|
||||
part = part.substring(1);
|
||||
}
|
||||
if (!part) continue;
|
||||
|
||||
if (part.startsWith("has:")) {
|
||||
if (part === "has:civitai") {
|
||||
hide = !el.hasData(part.replace("has:", ""));
|
||||
}
|
||||
} else {
|
||||
hide = !el.matchesQueryText(part);
|
||||
}
|
||||
hide = negate ? !hide : hide;
|
||||
if (hide) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hide;
|
||||
};
|
||||
for (const el of els) {
|
||||
const hide = shouldHide(el);
|
||||
if (hide) {
|
||||
el.hide();
|
||||
} else {
|
||||
el.show();
|
||||
}
|
||||
}
|
||||
|
||||
console.log("filter");
|
||||
}
|
||||
|
||||
doSearch() {
|
||||
const query = this.searchbox.value.trim();
|
||||
if (this.queryLast != query) {
|
||||
this.queryLast = query;
|
||||
this.filter();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user