File size: 6,763 Bytes
baac5bb |
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 203 |
import type {RgthreeModelInfo} from "typings/rgthree.js";
export type ModelInfoType = "loras" | "checkpoints";
type ModelsOptions = {
type: ModelInfoType;
files?: string[];
};
type GetModelsOptions = ModelsOptions & {
type: ModelInfoType;
files?: string[];
format?: null | "details";
};
type GetModelsInfoOptions = GetModelsOptions & {
light?: boolean;
};
type GetModelsResponseDetails = {
file: string;
modified: number;
has_info: boolean;
image?: string;
};
class RgthreeApi {
private baseUrl!: string;
private comfyBaseUrl!: string;
getCheckpointsPromise: Promise<string[]> | null = null;
getSamplersPromise: Promise<string[]> | null = null;
getSchedulersPromise: Promise<string[]> | null = null;
getLorasPromise: Promise<GetModelsResponseDetails[]> | null = null;
getWorkflowsPromise: Promise<string[]> | null = null;
constructor(baseUrl?: string) {
this.setBaseUrl(baseUrl);
}
setBaseUrl(baseUrlArg?: string) {
let baseUrl = null;
if (baseUrlArg) {
baseUrl = baseUrlArg;
} else if (window.location.pathname.includes("/rgthree/")) {
// Try to find how many relatives paths we need to go back to hit ./rgthree/api
const parts = window.location.pathname.split("/rgthree/")[1]?.split("/");
if (parts && parts.length) {
baseUrl = parts.map(() => "../").join("") + "rgthree/api";
}
}
this.baseUrl = baseUrl || "./rgthree/api";
// Calculate the comfyUI api base path by checkin gif we're on an rgthree independant page (as
// we'll always use '/rgthree/' prefix) and, if so, assume the path before `/rgthree/` is the
// base path. If we're not, then just use the same pathname logic as the ComfyUI api.js uses.
const comfyBasePathname = location.pathname.includes("/rgthree/")
? location.pathname.split("rgthree/")[0]!
: location.pathname;
this.comfyBaseUrl = comfyBasePathname.split("/").slice(0, -1).join("/");
}
apiURL(route: string) {
return `${this.baseUrl}${route}`;
}
fetchApi(route: string, options?: RequestInit) {
return fetch(this.apiURL(route), options);
}
async fetchJson(route: string, options?: RequestInit) {
const r = await this.fetchApi(route, options);
return await r.json();
}
async postJson(route: string, json: any) {
const body = new FormData();
body.append("json", JSON.stringify(json));
return await rgthreeApi.fetchJson(route, {method: "POST", body});
}
getLoras(force = false) {
if (!this.getLorasPromise || force) {
this.getLorasPromise = this.fetchJson("/loras?format=details", {cache: "no-store"});
}
return this.getLorasPromise;
}
async fetchApiJsonOrNull<T>(route: string, options?: RequestInit) {
const response = await this.fetchJson(route, options);
if (response.status === 200 && response.data) {
return (response.data as T) || null;
}
return null;
}
/**
* Fetches the lora information.
*
* @param light Whether or not to generate a json file if there isn't one. This isn't necessary if
* we're just checking for values, but is more necessary when opening an info dialog.
*/
async getModelsInfo(options: GetModelsInfoOptions): Promise<RgthreeModelInfo[]> {
const params = new URLSearchParams();
if (options.files?.length) {
params.set("files", options.files.join(","));
}
if (options.light) {
params.set("light", "1");
}
if (options.format) {
params.set("format", options.format);
}
const path = `/${options.type}/info?` + params.toString();
return (await this.fetchApiJsonOrNull<RgthreeModelInfo[]>(path)) || [];
}
async getLorasInfo(options: Omit<GetModelsInfoOptions, "type"> = {}) {
return this.getModelsInfo({type: "loras", ...options});
}
async getCheckpointsInfo(options: Omit<GetModelsInfoOptions, "type"> = {}) {
return this.getModelsInfo({type: "checkpoints", ...options});
}
async refreshModelsInfo(options: ModelsOptions) {
const params = new URLSearchParams();
if (options.files?.length) {
params.set("files", options.files.join(","));
}
const path = `/${options.type}/info/refresh?` + params.toString();
const infos = await this.fetchApiJsonOrNull<RgthreeModelInfo[]>(path);
return infos;
}
async refreshLorasInfo(options: Omit<ModelsOptions, "type"> = {}) {
return this.refreshModelsInfo({type: "loras", ...options});
}
async refreshCheckpointsInfo(options: Omit<ModelsOptions, "type"> = {}) {
return this.refreshModelsInfo({type: "checkpoints", ...options});
}
async clearModelsInfo(options: ModelsOptions) {
const params = new URLSearchParams();
if (options.files?.length) {
// encodeURIComponent ?
params.set("files", options.files.join(","));
}
const path = `/${options.type}/info/clear?` + params.toString();
await this.fetchApiJsonOrNull<RgthreeModelInfo[]>(path);
return;
}
async clearLorasInfo(options: Omit<ModelsOptions, "type"> = {}) {
return this.clearModelsInfo({type: "loras", ...options});
}
async clearCheckpointsInfo(options: Omit<ModelsOptions, "type"> = {}) {
return this.clearModelsInfo({type: "checkpoints", ...options});
}
/**
* Saves partial data sending it to the backend..
*/
async saveModelInfo(
type: ModelInfoType,
file: string,
data: Partial<RgthreeModelInfo>,
): Promise<RgthreeModelInfo | null> {
const body = new FormData();
body.append("json", JSON.stringify(data));
return await this.fetchApiJsonOrNull<RgthreeModelInfo>(
`/${type}/info?file=${encodeURIComponent(file)}`,
{cache: "no-store", method: "POST", body},
);
}
async saveLoraInfo(
file: string,
data: Partial<RgthreeModelInfo>,
): Promise<RgthreeModelInfo | null> {
return this.saveModelInfo("loras", file, data);
}
async saveCheckpointsInfo(
file: string,
data: Partial<RgthreeModelInfo>,
): Promise<RgthreeModelInfo | null> {
return this.saveModelInfo("checkpoints", file, data);
}
/**
* [🤮] Fetches from the ComfyUI given a similar functionality to the real ComfyUI API
* implementation, but can be available on independant pages outside of the ComfyUI UI. This is
* because ComfyUI frontend stopped serving its modules independantly and opted for a giant bundle
* instead which no longer allows us to load its `api.js` file separately.
*/
fetchComfyApi(route: string, options?: any): Promise<any> {
const url = this.comfyBaseUrl + "/api" + route;
options = options || {};
options.headers = options.headers || {};
options.cache = options.cache || "no-cache";
return fetch(url, options);
}
}
export const rgthreeApi = new RgthreeApi();
|