mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-15 10:13:22 -03:00
Compare commits
7 Commits
36ef840a22
..
v1.1.6
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e303ab316 | |||
| e9e8c31ad1 | |||
| 703a6a4ea0 | |||
| 283730cf38 | |||
| 20417797e8 | |||
| 004c69b9ef | |||
| 47fe2d3783 |
+94
-89
@@ -11,8 +11,8 @@
|
|||||||
"Insomnia Art Designs",
|
"Insomnia Art Designs",
|
||||||
"2018cfh",
|
"2018cfh",
|
||||||
"Arlecchino Shion",
|
"Arlecchino Shion",
|
||||||
"Rob Williams",
|
|
||||||
"Charles Blakemore",
|
"Charles Blakemore",
|
||||||
|
"Rob Williams",
|
||||||
"W+K+White",
|
"W+K+White",
|
||||||
"$MetaSamsara",
|
"$MetaSamsara",
|
||||||
"wackop",
|
"wackop",
|
||||||
@@ -21,6 +21,8 @@
|
|||||||
"stone9k",
|
"stone9k",
|
||||||
"Rosenthal",
|
"Rosenthal",
|
||||||
"Francisco Tatis",
|
"Francisco Tatis",
|
||||||
|
"JongWon Han",
|
||||||
|
"FreelancerZ",
|
||||||
"Polymorphic Indeterminate",
|
"Polymorphic Indeterminate",
|
||||||
"Skalabananen",
|
"Skalabananen",
|
||||||
"Marc Whiffen",
|
"Marc Whiffen",
|
||||||
@@ -38,24 +40,32 @@
|
|||||||
"Estragon",
|
"Estragon",
|
||||||
"J\\B/ 8r0wns0n",
|
"J\\B/ 8r0wns0n",
|
||||||
"ClockDaemon",
|
"ClockDaemon",
|
||||||
|
"KD",
|
||||||
|
"Omnidex",
|
||||||
|
"Tyler Trebuchon",
|
||||||
|
"Release Cabrakan",
|
||||||
"Tobi_Swagg",
|
"Tobi_Swagg",
|
||||||
"SG",
|
"SG",
|
||||||
|
"James Dooley",
|
||||||
"zenbound",
|
"zenbound",
|
||||||
|
"Buzzard",
|
||||||
"jmack",
|
"jmack",
|
||||||
"Andrew Wilson",
|
"Andrew Wilson",
|
||||||
"Greybush",
|
"Greybush",
|
||||||
|
"Mark Corneglio",
|
||||||
"Ricky Carter",
|
"Ricky Carter",
|
||||||
"James Todd",
|
"James Todd",
|
||||||
"JongWon Han",
|
"Steven Pfeiffer",
|
||||||
"VantAI",
|
"VantAI",
|
||||||
"レプサイ",
|
"レプサイ",
|
||||||
|
"Lisster",
|
||||||
"Michael Wong",
|
"Michael Wong",
|
||||||
"runte3221",
|
"runte3221",
|
||||||
"Illrigger",
|
"Illrigger",
|
||||||
"Tom Corrigan",
|
"Tom Corrigan",
|
||||||
"JackieWang",
|
"JackieWang",
|
||||||
"FreelancerZ",
|
|
||||||
"fnkylove",
|
"fnkylove",
|
||||||
|
"Yushio",
|
||||||
"Vik71it",
|
"Vik71it",
|
||||||
"Echo",
|
"Echo",
|
||||||
"Lilleman",
|
"Lilleman",
|
||||||
@@ -65,50 +75,48 @@
|
|||||||
"Fraser Cross",
|
"Fraser Cross",
|
||||||
"Liam MacDougal",
|
"Liam MacDougal",
|
||||||
"Sterilized",
|
"Sterilized",
|
||||||
|
"BadassArabianMofo",
|
||||||
"JORGE+LUIZ+HUSSNI+MESSIAS",
|
"JORGE+LUIZ+HUSSNI+MESSIAS",
|
||||||
"quarz",
|
"quarz",
|
||||||
"Greg",
|
"Greg",
|
||||||
|
"jean jahren",
|
||||||
"JSST",
|
"JSST",
|
||||||
"Snaggwort",
|
"Snaggwort",
|
||||||
|
"lmsupporter",
|
||||||
"Takkan",
|
"Takkan",
|
||||||
"wfpearl",
|
"wfpearl",
|
||||||
"Matt+J",
|
"Matt+J",
|
||||||
"Baekdoosixt",
|
"Baekdoosixt",
|
||||||
"Jonathan Ross",
|
"Jonathan Ross",
|
||||||
"KD",
|
"Jack B Nimble",
|
||||||
"Omnidex",
|
|
||||||
"Nazono_hito",
|
"Nazono_hito",
|
||||||
"Melville Parrish",
|
"Melville Parrish",
|
||||||
"daniel dove",
|
"daniel dove",
|
||||||
"Lustre",
|
"Lustre",
|
||||||
"Tyler Trebuchon",
|
|
||||||
"Release Cabrakan",
|
|
||||||
"JW Sin",
|
"JW Sin",
|
||||||
"Alex",
|
"Alex",
|
||||||
"bh",
|
"bh",
|
||||||
"carozzz",
|
"carozzz",
|
||||||
"Marlon Daniels",
|
"Marlon Daniels",
|
||||||
"James Dooley",
|
"Starkselle",
|
||||||
"Buzzard",
|
|
||||||
"Aaron Bleuer",
|
"Aaron Bleuer",
|
||||||
"LacesOut!",
|
"LacesOut!",
|
||||||
|
"greebles",
|
||||||
"Adam Shaw",
|
"Adam Shaw",
|
||||||
"Mark Corneglio",
|
|
||||||
"SarcasticHashtag",
|
"SarcasticHashtag",
|
||||||
"Anthony Rizzo",
|
"Anthony Rizzo",
|
||||||
"iamresist",
|
"iamresist",
|
||||||
|
"M Postkasse",
|
||||||
"RedrockVP",
|
"RedrockVP",
|
||||||
"Wolffen",
|
"Wolffen",
|
||||||
"Wicked Choices by ASLPro3D",
|
"Wicked Choices by ASLPro3D",
|
||||||
"Jacob Hoehler",
|
"Jacob Hoehler",
|
||||||
"FinalyFree",
|
"FinalyFree",
|
||||||
"Weasyl",
|
"Weasyl",
|
||||||
"Steven Pfeiffer",
|
|
||||||
"Timmy",
|
"Timmy",
|
||||||
"Johnny",
|
"Johnny",
|
||||||
"Cory Paza",
|
"Cory Paza",
|
||||||
"Tak",
|
"Tak",
|
||||||
"Lisster",
|
|
||||||
"Big Red",
|
"Big Red",
|
||||||
"whudunit",
|
"whudunit",
|
||||||
"Luc Job",
|
"Luc Job",
|
||||||
@@ -116,37 +124,36 @@
|
|||||||
"Philip Hempel",
|
"Philip Hempel",
|
||||||
"corde",
|
"corde",
|
||||||
"nwalker94",
|
"nwalker94",
|
||||||
"Yushio",
|
|
||||||
"Bishoujoker",
|
"Bishoujoker",
|
||||||
|
"aai",
|
||||||
"Todd Keck",
|
"Todd Keck",
|
||||||
"Briton Heilbrun",
|
"Briton Heilbrun",
|
||||||
"Tori",
|
"Tori",
|
||||||
"wildnut",
|
"wildnut",
|
||||||
"Aleksander Wujczyk",
|
"Aleksander Wujczyk",
|
||||||
"AM Kuro",
|
"AM Kuro",
|
||||||
"BadassArabianMofo",
|
|
||||||
"Pascal Dahle",
|
"Pascal Dahle",
|
||||||
"Sangheili460",
|
"Sangheili460",
|
||||||
"MagnaInsomnia",
|
"MagnaInsomnia",
|
||||||
"Akira_HentAI",
|
"Akira_HentAI",
|
||||||
"Karl P.",
|
"Karl P.",
|
||||||
"lmsupporter",
|
|
||||||
"andrew.tappan",
|
"andrew.tappan",
|
||||||
"N/A",
|
"N/A",
|
||||||
|
"The Spawn",
|
||||||
"graysock",
|
"graysock",
|
||||||
"Greenmoustache",
|
"Greenmoustache",
|
||||||
"zounic",
|
"zounic",
|
||||||
"fancypants",
|
"fancypants",
|
||||||
"Eldithor",
|
"Digital",
|
||||||
"Jack B Nimble",
|
|
||||||
"JaxMax",
|
"JaxMax",
|
||||||
"contrite831",
|
"contrite831",
|
||||||
"Jwk0205",
|
"Jwk0205",
|
||||||
"Starkselle",
|
"Bro Xie",
|
||||||
|
"batblue",
|
||||||
|
"carey6409",
|
||||||
"Olive",
|
"Olive",
|
||||||
"greebles",
|
"太郎 ゲーム",
|
||||||
"Some Guy Named Barry",
|
"Some Guy Named Barry",
|
||||||
"M Postkasse",
|
|
||||||
"AELOX",
|
"AELOX",
|
||||||
"Gooohokrbe",
|
"Gooohokrbe",
|
||||||
"Nicfit23",
|
"Nicfit23",
|
||||||
@@ -162,12 +169,12 @@
|
|||||||
"Serge Bekenkamp",
|
"Serge Bekenkamp",
|
||||||
"Jimmy Ledbetter",
|
"Jimmy Ledbetter",
|
||||||
"LeoZero",
|
"LeoZero",
|
||||||
|
"Antonio Pontes",
|
||||||
|
"nahinahi9",
|
||||||
"Dustin Chen",
|
"Dustin Chen",
|
||||||
"dan",
|
"dan",
|
||||||
"aai",
|
|
||||||
"Mouthlessman",
|
"Mouthlessman",
|
||||||
"otaku fra",
|
"otaku fra",
|
||||||
"jean jahren",
|
|
||||||
"MiraiKuriyamaSy",
|
"MiraiKuriyamaSy",
|
||||||
"Ran C",
|
"Ran C",
|
||||||
"ViperC",
|
"ViperC",
|
||||||
@@ -176,7 +183,6 @@
|
|||||||
"Adam Taylor",
|
"Adam Taylor",
|
||||||
"AbstractAss",
|
"AbstractAss",
|
||||||
"Weird_With_A_Beard",
|
"Weird_With_A_Beard",
|
||||||
"The Spawn",
|
|
||||||
"Pozadine1",
|
"Pozadine1",
|
||||||
"Qarob",
|
"Qarob",
|
||||||
"AIGooner",
|
"AIGooner",
|
||||||
@@ -187,18 +193,16 @@
|
|||||||
"Hasturkun",
|
"Hasturkun",
|
||||||
"Jon Sandman",
|
"Jon Sandman",
|
||||||
"Ubivis",
|
"Ubivis",
|
||||||
|
"CloudValley",
|
||||||
|
"linnfrey",
|
||||||
"Jackthemind",
|
"Jackthemind",
|
||||||
|
"griffin+dahlberg",
|
||||||
"Joboshy",
|
"Joboshy",
|
||||||
"Digital",
|
|
||||||
"takyamtom",
|
"takyamtom",
|
||||||
"Bohemian Corporal",
|
"Bohemian Corporal",
|
||||||
"Dan",
|
"Dan",
|
||||||
"Bro Xie",
|
|
||||||
"yer fey",
|
"yer fey",
|
||||||
"batblue",
|
|
||||||
"Error_Rule34_Not_found",
|
"Error_Rule34_Not_found",
|
||||||
"carey6409",
|
|
||||||
"太郎 ゲーム",
|
|
||||||
"Roslynd",
|
"Roslynd",
|
||||||
"jinxedx",
|
"jinxedx",
|
||||||
"Neco28",
|
"Neco28",
|
||||||
@@ -210,22 +214,21 @@
|
|||||||
"Frank Nitty",
|
"Frank Nitty",
|
||||||
"Magic Noob",
|
"Magic Noob",
|
||||||
"DougPeterson",
|
"DougPeterson",
|
||||||
"Antonio Pontes",
|
|
||||||
"ApathyJones",
|
"ApathyJones",
|
||||||
|
"Jeff",
|
||||||
"Bruce",
|
"Bruce",
|
||||||
"Julian V",
|
|
||||||
"Steven Owens",
|
"Steven Owens",
|
||||||
"nahinahi9",
|
|
||||||
"Kevin John Duck",
|
"Kevin John Duck",
|
||||||
"Kevin Christopher",
|
"Kevin Christopher",
|
||||||
"Blackfish95",
|
"Blackfish95",
|
||||||
|
"dd",
|
||||||
"Paul Kroll",
|
"Paul Kroll",
|
||||||
"Bas Imagineer",
|
"Bas Imagineer",
|
||||||
"John Statham",
|
"John Statham",
|
||||||
"yuxz69",
|
"yuxz69",
|
||||||
"esthe",
|
"esthe",
|
||||||
|
"AlexDuKaNa",
|
||||||
"decoy",
|
"decoy",
|
||||||
"CloudValley",
|
|
||||||
"thesoftwaredruid",
|
"thesoftwaredruid",
|
||||||
"wundershark",
|
"wundershark",
|
||||||
"mr_dinosaur",
|
"mr_dinosaur",
|
||||||
@@ -233,15 +236,20 @@
|
|||||||
"Ray Wing",
|
"Ray Wing",
|
||||||
"Ranzitho",
|
"Ranzitho",
|
||||||
"Gus",
|
"Gus",
|
||||||
|
"地獄の禄",
|
||||||
"MJG",
|
"MJG",
|
||||||
"David LaVallee",
|
"David LaVallee",
|
||||||
"linnfrey",
|
|
||||||
"ae",
|
"ae",
|
||||||
"Tr4shP4nda",
|
"Tr4shP4nda",
|
||||||
"capn",
|
"capn",
|
||||||
"Joseph",
|
"Joseph",
|
||||||
|
"Mirko Katzula",
|
||||||
|
"dan",
|
||||||
|
"Piccio08",
|
||||||
|
"kumakichi",
|
||||||
|
"cppbel",
|
||||||
"IamAyam",
|
"IamAyam",
|
||||||
"skaterb949",
|
"jeaness",
|
||||||
"Brian M",
|
"Brian M",
|
||||||
"Josef Lanzl",
|
"Josef Lanzl",
|
||||||
"Nerezza",
|
"Nerezza",
|
||||||
@@ -261,37 +269,32 @@
|
|||||||
"Eris3D",
|
"Eris3D",
|
||||||
"Max Marklund",
|
"Max Marklund",
|
||||||
"m",
|
"m",
|
||||||
|
"Pierce McBride",
|
||||||
"Pronredn",
|
"Pronredn",
|
||||||
|
"Mikko Hemilä",
|
||||||
"Jamie Ogletree",
|
"Jamie Ogletree",
|
||||||
"a _",
|
"a _",
|
||||||
"Jeff",
|
|
||||||
"lh qwe",
|
"lh qwe",
|
||||||
"James Coleman",
|
"James Coleman",
|
||||||
|
"Martial",
|
||||||
"conner",
|
"conner",
|
||||||
"Ouro Boros",
|
"Ouro Boros",
|
||||||
"Chad Idk",
|
"Chad Idk",
|
||||||
"dd",
|
|
||||||
"Princess Bright Eyes",
|
"Princess Bright Eyes",
|
||||||
"Yuji Kaneko",
|
"Yuji Kaneko",
|
||||||
"Dušan Ryban",
|
"Dušan Ryban",
|
||||||
"Felipe dos Santos",
|
"Felipe dos Santos",
|
||||||
|
"Rops Alot",
|
||||||
"Sam",
|
"Sam",
|
||||||
"sjon kreutz",
|
"sjon kreutz",
|
||||||
"Ace Ventura",
|
"Ace Ventura",
|
||||||
"Douglas Gaspar",
|
"Douglas Gaspar",
|
||||||
"Metryman55",
|
"Metryman55",
|
||||||
"AlexDuKaNa",
|
|
||||||
"George",
|
"George",
|
||||||
"dw",
|
"dw",
|
||||||
"地獄の禄",
|
|
||||||
"Gamalonia",
|
"Gamalonia",
|
||||||
"WRL_SPR",
|
"WRL_SPR",
|
||||||
"momokai",
|
"momokai",
|
||||||
"Mirko Katzula",
|
|
||||||
"dan",
|
|
||||||
"Piccio08",
|
|
||||||
"kumakichi",
|
|
||||||
"cppbel",
|
|
||||||
"Moon Knight",
|
"Moon Knight",
|
||||||
"몽타주",
|
"몽타주",
|
||||||
"Kland",
|
"Kland",
|
||||||
@@ -313,12 +316,15 @@
|
|||||||
"kyoumei",
|
"kyoumei",
|
||||||
"RadStorm04",
|
"RadStorm04",
|
||||||
"JohnDoe42054",
|
"JohnDoe42054",
|
||||||
|
"BillyHill",
|
||||||
|
"emyth",
|
||||||
"gzmzmvp",
|
"gzmzmvp",
|
||||||
"Richard",
|
"Richard",
|
||||||
"奚明 刘",
|
"奚明 刘",
|
||||||
"Andrew",
|
"Andrew",
|
||||||
"Robert Wegemund",
|
"Robert Wegemund",
|
||||||
"Littlehuggy",
|
"Littlehuggy",
|
||||||
|
"Gregory Kozhemiak",
|
||||||
"준희 김",
|
"준희 김",
|
||||||
"Brian Buie",
|
"Brian Buie",
|
||||||
"Sadlip",
|
"Sadlip",
|
||||||
@@ -328,16 +334,16 @@
|
|||||||
"Tomohiro Baba",
|
"Tomohiro Baba",
|
||||||
"Mike Simone",
|
"Mike Simone",
|
||||||
"Noora",
|
"Noora",
|
||||||
"Pierce McBride",
|
|
||||||
"Joshua Gray",
|
"Joshua Gray",
|
||||||
"Mattssn",
|
"Mattssn",
|
||||||
"Mikko Hemilä",
|
"Morgandel",
|
||||||
"Jacob McDaniel",
|
"Jacob McDaniel",
|
||||||
|
"X",
|
||||||
|
"Sloan Steddy",
|
||||||
"Temikus",
|
"Temikus",
|
||||||
"Artokun",
|
"Artokun",
|
||||||
"Michael Taylor",
|
"Michael Taylor",
|
||||||
"Derek Baker",
|
"Derek Baker",
|
||||||
"Martial",
|
|
||||||
"Michael Anthony Scott",
|
"Michael Anthony Scott",
|
||||||
"Emil Andersson",
|
"Emil Andersson",
|
||||||
"Atilla Berke Pekduyar",
|
"Atilla Berke Pekduyar",
|
||||||
@@ -345,7 +351,6 @@
|
|||||||
"CryptoTraderJK",
|
"CryptoTraderJK",
|
||||||
"Decx _",
|
"Decx _",
|
||||||
"Davaitamin",
|
"Davaitamin",
|
||||||
"Rops Alot",
|
|
||||||
"tedcor",
|
"tedcor",
|
||||||
"Fotek Design",
|
"Fotek Design",
|
||||||
"四糸凜音",
|
"四糸凜音",
|
||||||
@@ -358,8 +363,6 @@
|
|||||||
"starbugx",
|
"starbugx",
|
||||||
"dc7431",
|
"dc7431",
|
||||||
"Crocket",
|
"Crocket",
|
||||||
"BillyHill",
|
|
||||||
"emyth",
|
|
||||||
"chriphost",
|
"chriphost",
|
||||||
"KitKatM",
|
"KitKatM",
|
||||||
"socrasteeze",
|
"socrasteeze",
|
||||||
@@ -383,35 +386,40 @@
|
|||||||
"g9p0o",
|
"g9p0o",
|
||||||
"TheHolySheep",
|
"TheHolySheep",
|
||||||
"Monte Won",
|
"Monte Won",
|
||||||
"Gregory Kozhemiak",
|
"SpringBootisTrash",
|
||||||
|
"carsten",
|
||||||
|
"ikok",
|
||||||
|
"Wolfe7D1",
|
||||||
"Draven T",
|
"Draven T",
|
||||||
"mrjuan",
|
|
||||||
"elleshar666",
|
"elleshar666",
|
||||||
|
"ACTUALLY_the_Real_Willem_Dafoe",
|
||||||
"Aquatic Coffee",
|
"Aquatic Coffee",
|
||||||
|
"Kauffy",
|
||||||
"John J Linehan",
|
"John J Linehan",
|
||||||
"ethanfel",
|
"ethanfel",
|
||||||
"Elliot E",
|
"Elliot E",
|
||||||
"Morgandel",
|
|
||||||
"Theerat Jiramate",
|
"Theerat Jiramate",
|
||||||
"Focuschannel",
|
"Focuschannel",
|
||||||
"Edward Kennedy",
|
"Edward Kennedy",
|
||||||
"Noah",
|
"Noah",
|
||||||
"X",
|
|
||||||
"Sloan Steddy",
|
|
||||||
"Vane Holzer",
|
"Vane Holzer",
|
||||||
|
"psytrax",
|
||||||
"hexxish",
|
"hexxish",
|
||||||
"Anthony Faxlandez",
|
"Anthony Faxlandez",
|
||||||
"battu",
|
"battu",
|
||||||
"notedfakes",
|
"notedfakes",
|
||||||
"Nathan",
|
"Nathan",
|
||||||
"NICHOLAS BAXLEY",
|
"NICHOLAS BAXLEY",
|
||||||
|
"Michael Scott",
|
||||||
"Pat Hen",
|
"Pat Hen",
|
||||||
"Xeeosat",
|
"Xeeosat",
|
||||||
"Saya",
|
"Saya",
|
||||||
"Ed Wang",
|
"Ed Wang",
|
||||||
"Jordan Shaw",
|
"Jordan Shaw",
|
||||||
|
"Wes Sims",
|
||||||
"g unit",
|
"g unit",
|
||||||
"Srdb",
|
"Srdb",
|
||||||
|
"Filippo Ferrari",
|
||||||
"JC",
|
"JC",
|
||||||
"Prompt Pirate",
|
"Prompt Pirate",
|
||||||
"uwutismxd",
|
"uwutismxd",
|
||||||
@@ -427,9 +435,8 @@
|
|||||||
"Pkrsky",
|
"Pkrsky",
|
||||||
"nanana",
|
"nanana",
|
||||||
"raf8osz",
|
"raf8osz",
|
||||||
"SpringBootisTrash",
|
"FeralOpticsAI",
|
||||||
"carsten",
|
"Pavlaki",
|
||||||
"ikok",
|
|
||||||
"Doug+Rintoul",
|
"Doug+Rintoul",
|
||||||
"Noor",
|
"Noor",
|
||||||
"Yorunai",
|
"Yorunai",
|
||||||
@@ -446,36 +453,34 @@
|
|||||||
"cocona",
|
"cocona",
|
||||||
"ElitaSSJ4",
|
"ElitaSSJ4",
|
||||||
"David Schenck",
|
"David Schenck",
|
||||||
"Wolfe7D1",
|
|
||||||
"blikkies",
|
"blikkies",
|
||||||
"Chris",
|
"Chris",
|
||||||
"Time Valentine",
|
"Time Valentine",
|
||||||
"Shock Shockor",
|
"Shock Shockor",
|
||||||
"ACTUALLY_the_Real_Willem_Dafoe",
|
|
||||||
"Михал Михалыч",
|
"Михал Михалыч",
|
||||||
"Matt",
|
"Matt",
|
||||||
"Goldwaters",
|
"Goldwaters",
|
||||||
"Kauffy",
|
|
||||||
"Zude",
|
"Zude",
|
||||||
|
"Frogmilk",
|
||||||
"SPJ",
|
"SPJ",
|
||||||
"Kyler",
|
"Kyler",
|
||||||
|
"Bryan Rutkowski",
|
||||||
"Justin Blaylock",
|
"Justin Blaylock",
|
||||||
"aRtFuL_DodGeR",
|
"aRtFuL_DodGeR",
|
||||||
"Nick Kage",
|
"Nick Kage",
|
||||||
"psytrax",
|
|
||||||
"Cyrus Fett",
|
"Cyrus Fett",
|
||||||
"Xenon Xue",
|
"Xenon Xue",
|
||||||
"Edward Ten Eyck",
|
"Edward Ten Eyck",
|
||||||
"Billy Gladky",
|
"Billy Gladky",
|
||||||
"Michael Scott",
|
|
||||||
"Probis",
|
"Probis",
|
||||||
"Solixer",
|
"Solixer",
|
||||||
"Wes Sims",
|
|
||||||
"ItsGeneralButtNaked",
|
"ItsGeneralButtNaked",
|
||||||
"Donor4115",
|
"Donor4115",
|
||||||
|
"jinksta187",
|
||||||
"Distortik",
|
"Distortik",
|
||||||
"Manu Thetug",
|
"Manu Thetug",
|
||||||
"Filippo Ferrari",
|
"Karlanx",
|
||||||
|
"operationancut",
|
||||||
"Youguang",
|
"Youguang",
|
||||||
"andrewzpong",
|
"andrewzpong",
|
||||||
"BossGame",
|
"BossGame",
|
||||||
@@ -486,11 +491,18 @@
|
|||||||
"Kevinj",
|
"Kevinj",
|
||||||
"Mitchell Robson",
|
"Mitchell Robson",
|
||||||
"POPPIN",
|
"POPPIN",
|
||||||
|
"YassineKhaled",
|
||||||
|
"Y",
|
||||||
|
"MatteKey",
|
||||||
|
"Flob",
|
||||||
|
"ShiroSenpai",
|
||||||
|
"Inkognito",
|
||||||
"G",
|
"G",
|
||||||
"Tan+Huynh",
|
"Tan+Huynh",
|
||||||
"Bob+Barker",
|
"Bob+Barker",
|
||||||
"D",
|
"D",
|
||||||
"Dark_Pest",
|
"Dark_Pest",
|
||||||
|
"Eldithor",
|
||||||
"Alex",
|
"Alex",
|
||||||
"Karru",
|
"Karru",
|
||||||
"ChaChanoKo",
|
"ChaChanoKo",
|
||||||
@@ -503,13 +515,10 @@
|
|||||||
"g",
|
"g",
|
||||||
"J",
|
"J",
|
||||||
"Alan+Cano",
|
"Alan+Cano",
|
||||||
"FeralOpticsAI",
|
|
||||||
"Pavlaki",
|
|
||||||
"BillyBoy84",
|
"BillyBoy84",
|
||||||
"Buecyb99",
|
"Buecyb99",
|
||||||
"Welkor",
|
"Welkor",
|
||||||
"John Martin",
|
"John Martin",
|
||||||
"Ink Temptation",
|
|
||||||
"JBsuede",
|
"JBsuede",
|
||||||
"moranqianlong",
|
"moranqianlong",
|
||||||
"Kalli Core",
|
"Kalli Core",
|
||||||
@@ -519,13 +528,12 @@
|
|||||||
"Dave Abraham",
|
"Dave Abraham",
|
||||||
"Joaquin Hierrezuelo",
|
"Joaquin Hierrezuelo",
|
||||||
"Locrospiel",
|
"Locrospiel",
|
||||||
"Frogmilk",
|
|
||||||
"Sean voets",
|
"Sean voets",
|
||||||
|
"Jarrid Lee",
|
||||||
"Kor",
|
"Kor",
|
||||||
"Joseph Hanson",
|
"Joseph Hanson",
|
||||||
"John Rednoulf",
|
"John Rednoulf",
|
||||||
"Kyron Mahan",
|
"Kyron Mahan",
|
||||||
"Bryan Rutkowski",
|
|
||||||
"Boba Smith",
|
"Boba Smith",
|
||||||
"TBitz33",
|
"TBitz33",
|
||||||
"Anonym dkjglfleeoeldldldlkf",
|
"Anonym dkjglfleeoeldldldlkf",
|
||||||
@@ -544,15 +552,12 @@
|
|||||||
"Pete Pain",
|
"Pete Pain",
|
||||||
"Jacob Winter",
|
"Jacob Winter",
|
||||||
"Ryan Presley Ng",
|
"Ryan Presley Ng",
|
||||||
"jinksta187",
|
|
||||||
"RHopkirk",
|
"RHopkirk",
|
||||||
"Andrew Wilkinson",
|
"Andrew Wilkinson",
|
||||||
"Karlanx",
|
|
||||||
"Lyavph",
|
"Lyavph",
|
||||||
"Maxim",
|
"Maxim",
|
||||||
"David",
|
"David",
|
||||||
"Meilo",
|
"Meilo",
|
||||||
"operationancut",
|
|
||||||
"shinonomeiro",
|
"shinonomeiro",
|
||||||
"Snille",
|
"Snille",
|
||||||
"MaartenAlbers",
|
"MaartenAlbers",
|
||||||
@@ -571,6 +576,8 @@
|
|||||||
"Scott",
|
"Scott",
|
||||||
"Muratoraccio",
|
"Muratoraccio",
|
||||||
"D",
|
"D",
|
||||||
|
"Akkas+Haque",
|
||||||
|
"Kachac",
|
||||||
"SAVEagleBasement",
|
"SAVEagleBasement",
|
||||||
"Kevin+Isom",
|
"Kevin+Isom",
|
||||||
"Rune+Osnes",
|
"Rune+Osnes",
|
||||||
@@ -600,12 +607,6 @@
|
|||||||
"Lev+Lanevskiy",
|
"Lev+Lanevskiy",
|
||||||
"low9",
|
"low9",
|
||||||
"Winged",
|
"Winged",
|
||||||
"YassineKhaled",
|
|
||||||
"Y",
|
|
||||||
"MatteKey",
|
|
||||||
"Flob",
|
|
||||||
"ShiroSenpai",
|
|
||||||
"Inkognito",
|
|
||||||
"Jacky+Ho",
|
"Jacky+Ho",
|
||||||
"generic404",
|
"generic404",
|
||||||
"abattoirblues",
|
"abattoirblues",
|
||||||
@@ -614,29 +615,34 @@
|
|||||||
"hayden",
|
"hayden",
|
||||||
"Obsidian.Studios",
|
"Obsidian.Studios",
|
||||||
"ahoystan",
|
"ahoystan",
|
||||||
|
"Zomba Mann",
|
||||||
"edk",
|
"edk",
|
||||||
"Tú Nguyễn Lý Hoàng",
|
"Tú Nguyễn Lý Hoàng",
|
||||||
"shira1011",
|
"shira1011",
|
||||||
|
"Neko Desco",
|
||||||
"Ben D",
|
"Ben D",
|
||||||
"G",
|
"G",
|
||||||
|
"Vinarus",
|
||||||
"ja s",
|
"ja s",
|
||||||
"Leslie Andrew Ridings",
|
"Leslie Andrew Ridings",
|
||||||
"Doug Mason",
|
"Doug Mason",
|
||||||
"scoreswazey",
|
"scoreswazey",
|
||||||
"Owen Gwosdz",
|
"Owen Gwosdz",
|
||||||
"Jarrid Lee",
|
|
||||||
"Poophead27 Blyat",
|
"Poophead27 Blyat",
|
||||||
"Mythspire",
|
"Mythspire",
|
||||||
"AZ Party Oasis",
|
"AZ Party Oasis",
|
||||||
"Devil Lude",
|
"Devil Lude",
|
||||||
"David Murcko",
|
"David Murcko",
|
||||||
|
"TheFusion",
|
||||||
"MR.Bear",
|
"MR.Bear",
|
||||||
"Jack Dole",
|
"Jack Dole",
|
||||||
"matt",
|
"matt",
|
||||||
"somethingtosay8",
|
"somethingtosay8",
|
||||||
|
"3zS4QNQ4",
|
||||||
"Terminuz",
|
"Terminuz",
|
||||||
"ivistorm",
|
"ivistorm",
|
||||||
"max blo",
|
"max blo",
|
||||||
|
"Ivan Imes",
|
||||||
"CptNeo",
|
"CptNeo",
|
||||||
"Jack Lawfield",
|
"Jack Lawfield",
|
||||||
"Borte",
|
"Borte",
|
||||||
@@ -645,7 +651,7 @@
|
|||||||
"Sage Himeros",
|
"Sage Himeros",
|
||||||
"Eric Ketchum",
|
"Eric Ketchum",
|
||||||
"Kevin Wallace",
|
"Kevin Wallace",
|
||||||
"David Spearing",
|
"Zeeble",
|
||||||
"ChicRic",
|
"ChicRic",
|
||||||
"Tigon",
|
"Tigon",
|
||||||
"BastardSama",
|
"BastardSama",
|
||||||
@@ -706,6 +712,9 @@
|
|||||||
"SelfishMedic",
|
"SelfishMedic",
|
||||||
"adderleighn",
|
"adderleighn",
|
||||||
"EnragedAntelope",
|
"EnragedAntelope",
|
||||||
|
"Somebody",
|
||||||
|
"Jasper",
|
||||||
|
"megameganck",
|
||||||
"thomasand01",
|
"thomasand01",
|
||||||
"Shiba+Sama",
|
"Shiba+Sama",
|
||||||
"miduzza",
|
"miduzza",
|
||||||
@@ -730,8 +739,6 @@
|
|||||||
"matter",
|
"matter",
|
||||||
"SRCRCOSS",
|
"SRCRCOSS",
|
||||||
"imer",
|
"imer",
|
||||||
"Akkas+Haque",
|
|
||||||
"Kachac",
|
|
||||||
"jcx29",
|
"jcx29",
|
||||||
"Drizzly",
|
"Drizzly",
|
||||||
"Nebuleux",
|
"Nebuleux",
|
||||||
@@ -751,24 +758,23 @@
|
|||||||
"KUJYAKU",
|
"KUJYAKU",
|
||||||
"Coeur+de+cochon",
|
"Coeur+de+cochon",
|
||||||
"han b",
|
"han b",
|
||||||
"Zomba Mann",
|
|
||||||
"Aquaneo",
|
"Aquaneo",
|
||||||
"Nico",
|
"Nico",
|
||||||
"Maximilian Krischan",
|
"Maximilian Krischan",
|
||||||
"Banana Joe",
|
"Banana Joe",
|
||||||
"proto merp",
|
"proto merp",
|
||||||
"_ G3n",
|
"_ G3n",
|
||||||
|
"Brandon Thomas",
|
||||||
"Donovan Jenkins",
|
"Donovan Jenkins",
|
||||||
"Hans Meier",
|
"Hans Meier",
|
||||||
"sicarius",
|
"sicarius",
|
||||||
"Michael Eid",
|
"Michael Eid",
|
||||||
"Wolf and Fox Legends",
|
"Wolf and Fox Legends",
|
||||||
"beersandbacon",
|
"beersandbacon",
|
||||||
"Neko Desco",
|
|
||||||
"Bob barker",
|
"Bob barker",
|
||||||
"Ninja Tom",
|
"Ninja Tom",
|
||||||
"karim ben brik",
|
"karim ben brik",
|
||||||
"Vinarus",
|
"Elemnt",
|
||||||
"Josh Snyder",
|
"Josh Snyder",
|
||||||
"Michael Zhu",
|
"Michael Zhu",
|
||||||
"Nemisu",
|
"Nemisu",
|
||||||
@@ -792,16 +798,15 @@
|
|||||||
"Forbidden Atelier",
|
"Forbidden Atelier",
|
||||||
"Thomas Sankowski",
|
"Thomas Sankowski",
|
||||||
"DrB",
|
"DrB",
|
||||||
|
"Nimhloth",
|
||||||
"Adictedtohumping",
|
"Adictedtohumping",
|
||||||
"Snorklebort",
|
"Snorklebort",
|
||||||
"vinter",
|
"vinter",
|
||||||
"Towelie",
|
"Towelie",
|
||||||
"TheFusion",
|
|
||||||
"Jean-françois SEMA",
|
"Jean-françois SEMA",
|
||||||
"3zS4QNQ4",
|
|
||||||
"Kurt",
|
"Kurt",
|
||||||
|
"Andrew Ly",
|
||||||
"Matt M.",
|
"Matt M.",
|
||||||
"Ivan Imes",
|
|
||||||
"J M",
|
"J M",
|
||||||
"Slacks",
|
"Slacks",
|
||||||
"Bouya shaka",
|
"Bouya shaka",
|
||||||
@@ -837,5 +842,5 @@
|
|||||||
"Somebody",
|
"Somebody",
|
||||||
"CK"
|
"CK"
|
||||||
],
|
],
|
||||||
"totalCount": 834
|
"totalCount": 839
|
||||||
}
|
}
|
||||||
@@ -514,11 +514,21 @@ class CivitaiApiMetadataParser(RecipeMetadataParser):
|
|||||||
|
|
||||||
result["loras"].append(lora_entry)
|
result["loras"].append(lora_entry)
|
||||||
|
|
||||||
# Process modelVersionIds from Civitai image API
|
# Process modelVersionIds from Civitai image API.
|
||||||
# These are model version IDs returned at root level when meta doesn't contain resources
|
# These are version IDs returned at root level of the API response.
|
||||||
if "modelVersionIds" in metadata and isinstance(
|
# When resources or civitaiResources are already present in metadata
|
||||||
metadata["modelVersionIds"], list
|
# (which they are when ?withMeta=true is passed), those sections have
|
||||||
|
# complete hash/type information — modelVersionIds is a fallback for
|
||||||
|
# when meta is null and only the flat ID list is available. Skipping
|
||||||
|
# it here avoids duplicates: the same file hash often resolves to
|
||||||
|
# different version IDs via hash lookup (resources) vs the original
|
||||||
|
# version ID in modelVersionIds, and both paths would create entries.
|
||||||
|
if (
|
||||||
|
"modelVersionIds" in metadata
|
||||||
|
and isinstance(metadata["modelVersionIds"], list)
|
||||||
|
and not result.get("loras")
|
||||||
):
|
):
|
||||||
|
|
||||||
for version_id in metadata["modelVersionIds"]:
|
for version_id in metadata["modelVersionIds"]:
|
||||||
version_id_str = str(version_id)
|
version_id_str = str(version_id)
|
||||||
|
|
||||||
@@ -526,6 +536,13 @@ class CivitaiApiMetadataParser(RecipeMetadataParser):
|
|||||||
if version_id_str in added_loras:
|
if version_id_str in added_loras:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Skip if this version ID is already the recipe's checkpoint
|
||||||
|
# (resolved earlier from embedded resources/Model hash,
|
||||||
|
# avoiding a duplicate CivitAI API call).
|
||||||
|
existing_model = result.get("model")
|
||||||
|
if existing_model and str(existing_model.get("id")) == version_id_str:
|
||||||
|
continue
|
||||||
|
|
||||||
# Initialize lora entry with version ID
|
# Initialize lora entry with version ID
|
||||||
lora_entry = {
|
lora_entry = {
|
||||||
"id": version_id,
|
"id": version_id,
|
||||||
@@ -559,9 +576,40 @@ class CivitaiApiMetadataParser(RecipeMetadataParser):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if populated_entry is None:
|
if populated_entry is None:
|
||||||
continue # Skip invalid LoRA types
|
# Not a LoRA — try as checkpoint (only if we
|
||||||
|
# don't already have one). Reuses the same
|
||||||
|
# civitai_info from the API call above so no
|
||||||
|
# extra query is made.
|
||||||
|
if result["model"] is None:
|
||||||
|
checkpoint_entry = {
|
||||||
|
"id": version_id,
|
||||||
|
"modelId": 0,
|
||||||
|
"name": "Unknown Model",
|
||||||
|
"version": "",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"existsLocally": False,
|
||||||
|
"localPath": None,
|
||||||
|
"file_name": "",
|
||||||
|
"hash": "",
|
||||||
|
"thumbnailUrl": (
|
||||||
|
"/loras_static/images/no-preview.png"
|
||||||
|
),
|
||||||
|
"baseModel": "",
|
||||||
|
"size": 0,
|
||||||
|
"downloadUrl": "",
|
||||||
|
"isDeleted": False,
|
||||||
|
}
|
||||||
|
cp_populated = await (
|
||||||
|
self.populate_checkpoint_from_civitai(
|
||||||
|
checkpoint_entry, civitai_info
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if cp_populated.get("modelId"):
|
||||||
|
result["model"] = cp_populated
|
||||||
|
continue # Not a LoRA, don't add to loras
|
||||||
|
|
||||||
lora_entry = populated_entry
|
lora_entry = populated_entry
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Error fetching Civitai info for model version {version_id}: {e}"
|
f"Error fetching Civitai info for model version {version_id}: {e}"
|
||||||
|
|||||||
@@ -414,9 +414,10 @@ class PromptServerProtocol(Protocol):
|
|||||||
"""Subset of PromptServer used by the handlers."""
|
"""Subset of PromptServer used by the handlers."""
|
||||||
|
|
||||||
instance: "PromptServerProtocol"
|
instance: "PromptServerProtocol"
|
||||||
|
sockets: dict # maps clientId (sid) → WebSocketResponse
|
||||||
|
|
||||||
def send_sync(
|
def send_sync(
|
||||||
self, event: str, payload: dict
|
self, event: str, payload: dict | None = None, sid: str | None = None
|
||||||
) -> None: # pragma: no cover - protocol
|
) -> None: # pragma: no cover - protocol
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -471,23 +472,38 @@ class BackupServiceProtocol(Protocol):
|
|||||||
|
|
||||||
|
|
||||||
class NodeRegistry:
|
class NodeRegistry:
|
||||||
"""Thread-safe registry for tracking LoRA nodes in active workflows."""
|
"""Thread-safe registry for tracking LoRA nodes across ComfyUI tabs.
|
||||||
|
|
||||||
|
Each connected ComfyUI browser tab (identified by its ``sid`` / ``clientId``)
|
||||||
|
registers its own set of workflow nodes. Queries merge all known tabs into
|
||||||
|
a single result so that the calling LM panel always sees *every* available
|
||||||
|
target node, regardless of which tab responded fastest.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._lock = asyncio.Lock()
|
self._lock = asyncio.Lock()
|
||||||
self._nodes: Dict[str, dict] = {}
|
# sid → {unique_id → node_info}
|
||||||
self._registry_updated = asyncio.Event()
|
self._tab_nodes: Dict[str, Dict[str, dict]] = {}
|
||||||
|
self._ready = asyncio.Event()
|
||||||
|
self._waiting_clients: set[str] = set()
|
||||||
|
|
||||||
async def register_nodes(self, nodes: list[dict]) -> None:
|
@property
|
||||||
async with self._lock:
|
def pending_client_count(self) -> int:
|
||||||
self._nodes.clear()
|
"""Number of clients that have not yet responded in the current refresh cycle."""
|
||||||
for node in nodes:
|
return len(self._waiting_clients)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Helpers to build one node dict (extracted so it's reused for each tab)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def _build_node_dict(node: dict) -> dict:
|
||||||
node_id = node["node_id"]
|
node_id = node["node_id"]
|
||||||
graph_id = str(node["graph_id"])
|
graph_id = str(node["graph_id"])
|
||||||
unique_id = f"{graph_id}:{node_id}"
|
unique_id = f"{graph_id}:{node_id}"
|
||||||
node_type = node.get("type", "")
|
node_type = node.get("type", "")
|
||||||
type_id = NODE_TYPES.get(node_type, 0)
|
type_id = NODE_TYPES.get(node_type, 0)
|
||||||
bgcolor = node.get("bgcolor") or DEFAULT_NODE_COLOR
|
bgcolor = node.get("bgcolor") or DEFAULT_NODE_COLOR
|
||||||
|
|
||||||
raw_capabilities = node.get("capabilities")
|
raw_capabilities = node.get("capabilities")
|
||||||
capabilities: dict = {}
|
capabilities: dict = {}
|
||||||
if isinstance(raw_capabilities, dict):
|
if isinstance(raw_capabilities, dict):
|
||||||
@@ -522,7 +538,7 @@ class NodeRegistry:
|
|||||||
if not isinstance(comfy_class, str) or not comfy_class:
|
if not isinstance(comfy_class, str) or not comfy_class:
|
||||||
comfy_class = node_type if isinstance(node_type, str) else None
|
comfy_class = node_type if isinstance(node_type, str) else None
|
||||||
|
|
||||||
self._nodes[unique_id] = {
|
return {
|
||||||
"id": node_id,
|
"id": node_id,
|
||||||
"graph_id": graph_id,
|
"graph_id": graph_id,
|
||||||
"graph_name": node.get("graph_name"),
|
"graph_name": node.get("graph_name"),
|
||||||
@@ -537,24 +553,73 @@ class NodeRegistry:
|
|||||||
"mode": node.get("mode"),
|
"mode": node.get("mode"),
|
||||||
"marker_role": node.get("marker_role"),
|
"marker_role": node.get("marker_role"),
|
||||||
}
|
}
|
||||||
logger.debug("Registered %s nodes in registry", len(nodes))
|
|
||||||
self._registry_updated.set()
|
|
||||||
|
|
||||||
async def get_registry(self) -> dict:
|
# ------------------------------------------------------------------
|
||||||
|
# Public API
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
async def register_nodes(self, sid: str, nodes: list[dict]) -> None:
|
||||||
|
"""Register/replace the node list for a single ComfyUI tab (identified by *sid*)."""
|
||||||
|
tab_nodes: dict[str, dict] = {}
|
||||||
|
for node in nodes:
|
||||||
|
nd = self._build_node_dict(node)
|
||||||
|
tab_nodes[nd["unique_id"]] = nd
|
||||||
|
|
||||||
async with self._lock:
|
async with self._lock:
|
||||||
return {
|
self._tab_nodes[sid] = tab_nodes
|
||||||
"nodes": dict(self._nodes),
|
self._waiting_clients.discard(sid)
|
||||||
"node_count": len(self._nodes),
|
if not self._waiting_clients:
|
||||||
}
|
self._ready.set()
|
||||||
|
|
||||||
async def wait_for_update(self, timeout: float = 1.0) -> bool:
|
logger.debug("Registered %s nodes from client %s", len(nodes), sid)
|
||||||
self._registry_updated.clear()
|
|
||||||
|
def prepare_for_refresh(self, active_sids: list[str]) -> None:
|
||||||
|
"""Set the list of client IDs we expect to hear from during the next refresh cycle."""
|
||||||
|
self._ready.clear()
|
||||||
|
self._waiting_clients = set(active_sids)
|
||||||
|
|
||||||
|
async def wait_for_all(self, timeout: float = 2.0) -> bool:
|
||||||
|
"""Block until every client in the current waiting set has responded
|
||||||
|
(or *timeout* seconds elapse). Returns ``True`` if all responded."""
|
||||||
|
if not self._waiting_clients:
|
||||||
|
return True
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._registry_updated.wait(), timeout=timeout)
|
await asyncio.wait_for(self._ready.wait(), timeout=timeout)
|
||||||
return True
|
return True
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def get_merged_registry(self, active_sids: set[str] | None = None) -> dict:
|
||||||
|
"""Return the union of all known tab nodes, pruning any tab that is no
|
||||||
|
longer connected."""
|
||||||
|
async with self._lock:
|
||||||
|
# Garbage-collect stale entries (disconnected tabs)
|
||||||
|
if active_sids is not None:
|
||||||
|
for sid in list(self._tab_nodes):
|
||||||
|
if sid not in active_sids:
|
||||||
|
del self._tab_nodes[sid]
|
||||||
|
|
||||||
|
merged: dict[str, dict] = {}
|
||||||
|
tab_info: dict[str, dict] = {}
|
||||||
|
for sid, nodes in self._tab_nodes.items():
|
||||||
|
tab_info[sid] = {
|
||||||
|
"node_count": len(nodes),
|
||||||
|
"graph_names": list(
|
||||||
|
{
|
||||||
|
n.get("graph_name")
|
||||||
|
for n in nodes.values()
|
||||||
|
if n.get("graph_name")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
merged.update(nodes)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"nodes": merged,
|
||||||
|
"node_count": len(merged),
|
||||||
|
"tab_count": len(self._tab_nodes),
|
||||||
|
"tabs": tab_info,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class HealthCheckHandler:
|
class HealthCheckHandler:
|
||||||
async def health_check(self, request: web.Request) -> web.Response:
|
async def health_check(self, request: web.Request) -> web.Response:
|
||||||
@@ -2995,10 +3060,21 @@ class NodeRegistryHandler:
|
|||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
nodes = data.get("nodes", [])
|
nodes = data.get("nodes", [])
|
||||||
|
client_id = data.get("client_id")
|
||||||
if not isinstance(nodes, list):
|
if not isinstance(nodes, list):
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
{"success": False, "error": "nodes must be a list"}, status=400
|
{"success": False, "error": "nodes must be a list"}, status=400
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not isinstance(client_id, str) or not client_id:
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"error": "Missing client_id parameter",
|
||||||
|
},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
for index, node in enumerate(nodes):
|
for index, node in enumerate(nodes):
|
||||||
if not isinstance(node, dict):
|
if not isinstance(node, dict):
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
@@ -3042,7 +3118,7 @@ class NodeRegistryHandler:
|
|||||||
else:
|
else:
|
||||||
node["graph_name"] = str(graph_name)
|
node["graph_name"] = str(graph_name)
|
||||||
|
|
||||||
await self._node_registry.register_nodes(nodes)
|
await self._node_registry.register_nodes(client_id, nodes)
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
{
|
{
|
||||||
"success": True,
|
"success": True,
|
||||||
@@ -3066,9 +3142,15 @@ class NodeRegistryHandler:
|
|||||||
status=503,
|
status=503,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Snapshot of currently-connected ComfyUI tabs
|
||||||
|
active_sids = list(self._prompt_server.instance.sockets.keys())
|
||||||
|
self._node_registry.prepare_for_refresh(active_sids)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._prompt_server.instance.send_sync("lora_registry_refresh", {})
|
self._prompt_server.instance.send_sync("lora_registry_refresh", {})
|
||||||
logger.debug("Sent registry refresh request to frontend")
|
logger.debug(
|
||||||
|
"Sent registry refresh request (expecting %s clients)", len(active_sids)
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Failed to send registry refresh message: %s", exc)
|
logger.error("Failed to send registry refresh message: %s", exc)
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
@@ -3080,19 +3162,31 @@ class NodeRegistryHandler:
|
|||||||
status=500,
|
status=500,
|
||||||
)
|
)
|
||||||
|
|
||||||
registry_updated = await self._node_registry.wait_for_update(timeout=1.0)
|
if not await self._node_registry.wait_for_all(timeout=2.0):
|
||||||
if not registry_updated:
|
logger.warning(
|
||||||
logger.warning("Registry refresh timeout after 1 second")
|
"Registry refresh timeout after 2s (%s/%s clients responded)",
|
||||||
|
len(active_sids) - self._node_registry.pending_client_count,
|
||||||
|
len(active_sids),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Re-read current sockets after the wait: a tab may have connected
|
||||||
|
# while we were waiting, and we don't want to garbage-collect it.
|
||||||
|
current_sids = set(self._prompt_server.instance.sockets.keys())
|
||||||
|
registry_info = await self._node_registry.get_merged_registry(
|
||||||
|
active_sids=current_sids
|
||||||
|
)
|
||||||
|
|
||||||
|
if registry_info["node_count"] == 0:
|
||||||
|
logger.warning("No nodes registered after refresh")
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
{
|
{
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": "Timeout Error",
|
"error": "Empty Registry",
|
||||||
"message": "Registry refresh timeout - ComfyUI frontend may not be responsive",
|
"message": "No workflow nodes found — ensure ComfyUI is open and the extension is loaded.",
|
||||||
},
|
},
|
||||||
status=408,
|
status=408,
|
||||||
)
|
)
|
||||||
|
|
||||||
registry_info = await self._node_registry.get_registry()
|
|
||||||
return web.json_response({"success": True, "data": registry_info})
|
return web.json_response({"success": True, "data": registry_info})
|
||||||
except Exception as exc: # pragma: no cover - defensive logging
|
except Exception as exc: # pragma: no cover - defensive logging
|
||||||
logger.error("Failed to get registry: %s", exc, exc_info=True)
|
logger.error("Failed to get registry: %s", exc, exc_info=True)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from ...utils.civitai_utils import (
|
|||||||
extract_civitai_image_id_from_cdn_url,
|
extract_civitai_image_id_from_cdn_url,
|
||||||
rewrite_preview_url,
|
rewrite_preview_url,
|
||||||
)
|
)
|
||||||
|
from ...utils.constants import NSFW_LEVELS
|
||||||
from ...utils.exif_utils import ExifUtils
|
from ...utils.exif_utils import ExifUtils
|
||||||
from ...recipes.merger import GenParamsMerger
|
from ...recipes.merger import GenParamsMerger
|
||||||
from ...recipes.enrichment import RecipeEnricher
|
from ...recipes.enrichment import RecipeEnricher
|
||||||
@@ -1120,6 +1121,13 @@ class RecipeManagementHandler:
|
|||||||
if parsed_embedded.get("base_model") and not metadata.get("base_model"):
|
if parsed_embedded.get("base_model") and not metadata.get("base_model"):
|
||||||
metadata["base_model"] = parsed_embedded["base_model"]
|
metadata["base_model"] = parsed_embedded["base_model"]
|
||||||
|
|
||||||
|
# Extract preview_nsfw_level from the CivitAI API response
|
||||||
|
# (injected into civitai_meta_raw by _download_remote_media).
|
||||||
|
if isinstance(civitai_meta_raw, dict):
|
||||||
|
bl = civitai_meta_raw.get("browsingLevel")
|
||||||
|
if isinstance(bl, int) and bl > 0:
|
||||||
|
metadata["preview_nsfw_level"] = bl
|
||||||
|
|
||||||
civitai_client = self._civitai_client_getter()
|
civitai_client = self._civitai_client_getter()
|
||||||
await RecipeEnricher.enrich_recipe(
|
await RecipeEnricher.enrich_recipe(
|
||||||
recipe=metadata,
|
recipe=metadata,
|
||||||
@@ -1515,8 +1523,31 @@ class RecipeManagementHandler:
|
|||||||
# CivitAI API returns modelVersionIds at the root level of
|
# CivitAI API returns modelVersionIds at the root level of
|
||||||
# the image response, NOT inside the meta object.
|
# the image response, NOT inside the meta object.
|
||||||
mvids = image_info.get("modelVersionIds")
|
mvids = image_info.get("modelVersionIds")
|
||||||
if mvids and isinstance(civitai_meta_raw, dict):
|
if mvids:
|
||||||
|
if isinstance(civitai_meta_raw, dict):
|
||||||
civitai_meta_raw["modelVersionIds"] = mvids
|
civitai_meta_raw["modelVersionIds"] = mvids
|
||||||
|
else:
|
||||||
|
# meta is null but modelVersionIds exists — create a
|
||||||
|
# minimal dict so downstream parsers can discover
|
||||||
|
# LoRAs and checkpoints from the API response.
|
||||||
|
civitai_meta_raw = {"modelVersionIds": mvids}
|
||||||
|
|
||||||
|
# Inject browsingLevel (canonical integer) so the recipe's
|
||||||
|
# preview_nsfw_level can be set, enabling proper NSFW blur
|
||||||
|
# of the preview image. Fall back to nsfwLevel (string)
|
||||||
|
# when browsingLevel is absent.
|
||||||
|
if isinstance(civitai_meta_raw, dict):
|
||||||
|
browsing_level = image_info.get("browsingLevel")
|
||||||
|
nsfw_level_str = image_info.get("nsfwLevel")
|
||||||
|
if isinstance(browsing_level, int) and browsing_level > 0:
|
||||||
|
civitai_meta_raw["browsingLevel"] = browsing_level
|
||||||
|
elif (
|
||||||
|
isinstance(nsfw_level_str, str)
|
||||||
|
and nsfw_level_str in NSFW_LEVELS
|
||||||
|
):
|
||||||
|
civitai_meta_raw["browsingLevel"] = NSFW_LEVELS[
|
||||||
|
nsfw_level_str
|
||||||
|
]
|
||||||
|
|
||||||
original_url = (
|
original_url = (
|
||||||
image_info.get("url") if civitai_image_id and image_info else None
|
image_info.get("url") if civitai_image_id and image_info else None
|
||||||
@@ -1796,6 +1827,13 @@ class RecipeManagementHandler:
|
|||||||
"source_path": image_url,
|
"source_path": image_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Extract preview_nsfw_level from the CivitAI API response
|
||||||
|
# (injected into civitai_meta_raw by _download_remote_media).
|
||||||
|
if isinstance(civitai_meta_raw, dict):
|
||||||
|
bl = civitai_meta_raw.get("browsingLevel")
|
||||||
|
if isinstance(bl, int) and bl > 0:
|
||||||
|
metadata["preview_nsfw_level"] = bl
|
||||||
|
|
||||||
if civitai_parsed:
|
if civitai_parsed:
|
||||||
civitai_loras = civitai_parsed.get("loras", [])
|
civitai_loras = civitai_parsed.get("loras", [])
|
||||||
if civitai_loras and not metadata.get("loras"):
|
if civitai_loras and not metadata.get("loras"):
|
||||||
|
|||||||
@@ -523,6 +523,10 @@ class BatchImportService:
|
|||||||
if payload.get("checkpoint"):
|
if payload.get("checkpoint"):
|
||||||
metadata["checkpoint"] = payload["checkpoint"]
|
metadata["checkpoint"] = payload["checkpoint"]
|
||||||
|
|
||||||
|
nsfw = payload.get("preview_nsfw_level")
|
||||||
|
if isinstance(nsfw, int) and nsfw > 0:
|
||||||
|
metadata["preview_nsfw_level"] = nsfw
|
||||||
|
|
||||||
image_bytes = None
|
image_bytes = None
|
||||||
image_base64 = payload.get("image_base64")
|
image_base64 = payload.get("image_base64")
|
||||||
|
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ class CivArchiveClient:
|
|||||||
|
|
||||||
if version_id is not None:
|
if version_id is not None:
|
||||||
raw_id = version_data.get("id")
|
raw_id = version_data.get("id")
|
||||||
if raw_id != version_id:
|
if raw_id is not None and str(raw_id) != str(version_id):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Requested version %s doesn't match default version %s for model %s",
|
"Requested version %s doesn't match default version %s for model %s",
|
||||||
version_id,
|
version_id,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class CivitaiClient:
|
|||||||
self._MAX_CACHE_ENTRIES = 500
|
self._MAX_CACHE_ENTRIES = 500
|
||||||
|
|
||||||
def _build_image_info_url(self, image_id: str) -> str:
|
def _build_image_info_url(self, image_id: str) -> str:
|
||||||
return f"{self.base_url}/images?imageId={image_id}&nsfw=X"
|
return f"{self.base_url}/images?imageId={image_id}&nsfw=X&withMeta=true"
|
||||||
|
|
||||||
async def _make_request(
|
async def _make_request(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1288,10 +1288,24 @@ class DownloadManager:
|
|||||||
"download_id": download_id,
|
"download_id": download_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if this checkpoint should be treated as a diffusion model based on baseModel
|
# Check if this checkpoint should be treated as a diffusion model
|
||||||
|
# Priority: (1) any file has type "UNet" or "Diffusion Model",
|
||||||
|
# (2) baseModel is in DIFFUSION_MODEL_BASE_MODELS
|
||||||
is_diffusion_model = False
|
is_diffusion_model = False
|
||||||
if model_type == "checkpoint":
|
if model_type == "checkpoint":
|
||||||
if base_model_value in DIFFUSION_MODEL_BASE_MODELS:
|
# Check file types first (more direct signal from CivitAI)
|
||||||
|
version_files = version_info.get("files", [])
|
||||||
|
for f in version_files:
|
||||||
|
f_type = f.get("type", "")
|
||||||
|
if f_type in ("UNet", "Diffusion Model"):
|
||||||
|
is_diffusion_model = True
|
||||||
|
logger.info(
|
||||||
|
f"File type '{f_type}' detected, routing checkpoint to unet folder"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Fallback to baseModel name check
|
||||||
|
if not is_diffusion_model and base_model_value in DIFFUSION_MODEL_BASE_MODELS:
|
||||||
is_diffusion_model = True
|
is_diffusion_model = True
|
||||||
logger.info(
|
logger.info(
|
||||||
f"baseModel '{base_model_value}' is a known diffusion model, routing to unet folder"
|
f"baseModel '{base_model_value}' is a known diffusion model, routing to unet folder"
|
||||||
@@ -1420,7 +1434,7 @@ class DownloadManager:
|
|||||||
f
|
f
|
||||||
for f in files
|
for f in files
|
||||||
if f.get("primary")
|
if f.get("primary")
|
||||||
and f.get("type") in ("Model", "Negative", "Diffusion Model")
|
and f.get("type") in ("Model", "Negative", "Diffusion Model", "UNet")
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -1451,7 +1465,7 @@ class DownloadManager:
|
|||||||
(
|
(
|
||||||
f
|
f
|
||||||
for f in files
|
for f in files
|
||||||
if f.get("primary") and f.get("type") in ("Model", "Negative", "Diffusion Model")
|
if f.get("primary") and f.get("type") in ("Model", "Negative", "Diffusion Model", "UNet")
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -146,11 +146,38 @@ class RecipeAnalysisService:
|
|||||||
):
|
):
|
||||||
metadata = metadata["meta"]
|
metadata = metadata["meta"]
|
||||||
|
|
||||||
# Include modelVersionIds from root level if available
|
# Include modelVersionIds from root level if available.
|
||||||
# Civitai API returns modelVersionIds at root level, not in meta
|
# CivitAI API returns modelVersionIds at root level, not in meta.
|
||||||
|
# When meta is null (None), create a minimal dict so downstream
|
||||||
|
# parsers can still discover LoRAs and checkpoints.
|
||||||
model_version_ids = image_info.get("modelVersionIds")
|
model_version_ids = image_info.get("modelVersionIds")
|
||||||
if model_version_ids and isinstance(metadata, dict):
|
if model_version_ids:
|
||||||
|
if isinstance(metadata, dict):
|
||||||
metadata["modelVersionIds"] = model_version_ids
|
metadata["modelVersionIds"] = model_version_ids
|
||||||
|
else:
|
||||||
|
metadata = {"modelVersionIds": model_version_ids}
|
||||||
|
|
||||||
|
# Inject browsingLevel (canonical integer) so the recipe's
|
||||||
|
# preview_nsfw_level can be set, enabling proper NSFW blur
|
||||||
|
# of the preview image. Fall back to nsfwLevel (string)
|
||||||
|
# when browsingLevel is absent.
|
||||||
|
if isinstance(metadata, dict):
|
||||||
|
browsing_level = image_info.get("browsingLevel")
|
||||||
|
nsfw_level_str = image_info.get("nsfwLevel")
|
||||||
|
if isinstance(browsing_level, int) and browsing_level > 0:
|
||||||
|
metadata["browsingLevel"] = browsing_level
|
||||||
|
elif (
|
||||||
|
isinstance(nsfw_level_str, str)
|
||||||
|
and nsfw_level_str
|
||||||
|
in (
|
||||||
|
"PG", "PG13", "R", "X", "XXX", "Blocked",
|
||||||
|
)
|
||||||
|
):
|
||||||
|
from ...utils.constants import NSFW_LEVELS
|
||||||
|
|
||||||
|
metadata["browsingLevel"] = NSFW_LEVELS.get(
|
||||||
|
nsfw_level_str, 0
|
||||||
|
)
|
||||||
|
|
||||||
# Validate that metadata contains meaningful recipe fields
|
# Validate that metadata contains meaningful recipe fields
|
||||||
# If not, treat as None to trigger EXIF extraction from downloaded image
|
# If not, treat as None to trigger EXIF extraction from downloaded image
|
||||||
@@ -171,12 +198,19 @@ class RecipeAnalysisService:
|
|||||||
temp_path = self._create_temp_path(suffix=extension)
|
temp_path = self._create_temp_path(suffix=extension)
|
||||||
await self._download_image(url, temp_path)
|
await self._download_image(url, temp_path)
|
||||||
|
|
||||||
if metadata is None and not is_video:
|
# Always extract EXIF from the downloaded image for generation
|
||||||
metadata = await asyncio.to_thread(
|
# params (prompt, negative prompt, sampler, steps, etc.).
|
||||||
|
# Previously this was gated on ``metadata is None``, but that
|
||||||
|
# skipped EXIF entirely when API metadata (modelVersionIds,
|
||||||
|
# browsingLevel) is present, losing all generation parameters.
|
||||||
|
exif_metadata = None
|
||||||
|
if not is_video:
|
||||||
|
exif_metadata = await asyncio.to_thread(
|
||||||
self._exif_utils.extract_image_metadata, temp_path
|
self._exif_utils.extract_image_metadata, temp_path
|
||||||
)
|
)
|
||||||
|
|
||||||
if not metadata and civitai_image_id and image_info:
|
# Fallback: try the original (non-optimized) image for EXIF data
|
||||||
|
if not exif_metadata and civitai_image_id and image_info:
|
||||||
original_url = image_info.get("url")
|
original_url = image_info.get("url")
|
||||||
if original_url:
|
if original_url:
|
||||||
self._logger.debug(
|
self._logger.debug(
|
||||||
@@ -187,15 +221,38 @@ class RecipeAnalysisService:
|
|||||||
orig_temp_path = self._create_temp_path(suffix=".png")
|
orig_temp_path = self._create_temp_path(suffix=".png")
|
||||||
try:
|
try:
|
||||||
await self._download_image(original_url, orig_temp_path)
|
await self._download_image(original_url, orig_temp_path)
|
||||||
metadata = await asyncio.to_thread(
|
exif_metadata = await asyncio.to_thread(
|
||||||
self._exif_utils.extract_image_metadata,
|
self._exif_utils.extract_image_metadata,
|
||||||
orig_temp_path,
|
orig_temp_path,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
self._safe_cleanup(orig_temp_path)
|
self._safe_cleanup(orig_temp_path)
|
||||||
|
|
||||||
|
# Parse EXIF data (typically a string like parameters/prompt/workflow)
|
||||||
|
# and API metadata (dict with modelVersionIds, browsingLevel) separately,
|
||||||
|
# then merge: API loras/checkpoint override, EXIF gen_params fill in gaps.
|
||||||
|
# This mirrors the two-pass approach in _do_import_from_url.
|
||||||
|
exif_parsed_result = None
|
||||||
|
if isinstance(exif_metadata, str):
|
||||||
|
exif_parser = self._recipe_parser_factory.create_parser(exif_metadata)
|
||||||
|
if exif_parser:
|
||||||
|
exif_data = await exif_parser.parse_metadata(
|
||||||
|
exif_metadata, recipe_scanner=recipe_scanner,
|
||||||
|
)
|
||||||
|
if exif_data and not exif_data.get("error"):
|
||||||
|
exif_parsed_result = exif_data
|
||||||
|
|
||||||
|
# Merge API metadata (dict) with EXIF data (if dict) for the
|
||||||
|
# CivitaiApiMetadataParser. If EXIF data is a string it was
|
||||||
|
# parsed above — don't try to merge a string into a dict.
|
||||||
|
merged = {}
|
||||||
|
if isinstance(exif_metadata, dict):
|
||||||
|
merged.update(exif_metadata)
|
||||||
|
if isinstance(metadata, dict):
|
||||||
|
merged.update(metadata)
|
||||||
|
|
||||||
result = await self._parse_metadata(
|
result = await self._parse_metadata(
|
||||||
metadata or {},
|
merged,
|
||||||
recipe_scanner=recipe_scanner,
|
recipe_scanner=recipe_scanner,
|
||||||
image_path=temp_path,
|
image_path=temp_path,
|
||||||
include_image_base64=True,
|
include_image_base64=True,
|
||||||
@@ -203,13 +260,23 @@ class RecipeAnalysisService:
|
|||||||
extension=extension,
|
extension=extension,
|
||||||
)
|
)
|
||||||
|
|
||||||
if civitai_image_id and image_info and not result.payload.get("error"):
|
# Merge EXIF string-parsed gen_params into the API result.
|
||||||
mvid = image_info.get("modelVersionId")
|
# API gen_params take priority (they come later via update).
|
||||||
if not mvid:
|
if exif_parsed_result and not result.payload.get("error"):
|
||||||
mvids = image_info.get("modelVersionIds")
|
exif_gp = exif_parsed_result.get("gen_params") or {}
|
||||||
if isinstance(mvids, list) and mvids:
|
result_gp = result.payload.get("gen_params") or {}
|
||||||
mvid = mvids[0]
|
merged_gp = {**exif_gp, **result_gp}
|
||||||
|
if merged_gp:
|
||||||
|
result.payload["gen_params"] = merged_gp
|
||||||
|
|
||||||
|
if civitai_image_id and image_info and not result.payload.get("error"):
|
||||||
|
# Use the metadata dict we built (may contain modelVersionIds
|
||||||
|
# and browsingLevel from the API root level). Do NOT pass
|
||||||
|
# image_info.get("meta") — it is null for images whose meta
|
||||||
|
# lives at the root level only. Also do NOT derive
|
||||||
|
# model_version_id from modelVersionIds[0] — that array mixes
|
||||||
|
# checkpoints, LoRAs, and other types without ordering
|
||||||
|
# guarantees; the parser already resolved them correctly.
|
||||||
recipe_for_enrich = {
|
recipe_for_enrich = {
|
||||||
"gen_params": result.payload.get("gen_params", {}),
|
"gen_params": result.payload.get("gen_params", {}),
|
||||||
"loras": result.payload.get("loras", []),
|
"loras": result.payload.get("loras", []),
|
||||||
@@ -222,8 +289,10 @@ class RecipeAnalysisService:
|
|||||||
recipe=recipe_for_enrich,
|
recipe=recipe_for_enrich,
|
||||||
civitai_client=civitai_client,
|
civitai_client=civitai_client,
|
||||||
request_params=None,
|
request_params=None,
|
||||||
prefetched_civitai_meta_raw=image_info.get("meta"),
|
prefetched_civitai_meta_raw=(
|
||||||
prefetched_model_version_id=mvid,
|
metadata if isinstance(metadata, dict) else None
|
||||||
|
),
|
||||||
|
prefetched_model_version_id=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
result.payload["gen_params"] = recipe_for_enrich["gen_params"]
|
result.payload["gen_params"] = recipe_for_enrich["gen_params"]
|
||||||
@@ -232,6 +301,12 @@ class RecipeAnalysisService:
|
|||||||
if recipe_for_enrich.get("base_model"):
|
if recipe_for_enrich.get("base_model"):
|
||||||
result.payload["base_model"] = recipe_for_enrich["base_model"]
|
result.payload["base_model"] = recipe_for_enrich["base_model"]
|
||||||
|
|
||||||
|
# Extract browsingLevel from our constructed metadata for NSFW blur
|
||||||
|
if isinstance(metadata, dict):
|
||||||
|
bl = metadata.get("browsingLevel")
|
||||||
|
if isinstance(bl, int) and bl > 0:
|
||||||
|
result.payload["preview_nsfw_level"] = bl
|
||||||
|
|
||||||
return result
|
return result
|
||||||
finally:
|
finally:
|
||||||
if temp_path:
|
if temp_path:
|
||||||
@@ -314,6 +389,10 @@ class RecipeAnalysisService:
|
|||||||
"prompt_type",
|
"prompt_type",
|
||||||
"positive",
|
"positive",
|
||||||
"negative",
|
"negative",
|
||||||
|
# modelVersionIds is injected at the root level by CivitAI's image
|
||||||
|
# API when meta is null. It carries the version IDs of ALL models
|
||||||
|
# (checkpoint + LoRAs) used to generate the image.
|
||||||
|
"modelVersionIds",
|
||||||
}
|
}
|
||||||
return any(field in metadata for field in recipe_fields)
|
return any(field in metadata for field in recipe_fields)
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
|
|||||||
"Qwen",
|
"Qwen",
|
||||||
"ZImageBase",
|
"ZImageBase",
|
||||||
"ZImageTurbo",
|
"ZImageTurbo",
|
||||||
|
# Krea 2 — loaded via UNETLoader in ComfyUI
|
||||||
|
"Krea 2",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-lora-manager"
|
name = "comfyui-lora-manager"
|
||||||
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
|
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
|
||||||
version = "1.1.5"
|
version = "1.1.6"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp",
|
"aiohttp",
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
{
|
|
||||||
"id": 1746460,
|
|
||||||
"name": "Mixplin Style [Illustrious]",
|
|
||||||
"type": "LORA",
|
|
||||||
"description": "description",
|
|
||||||
"username": "Ty_Lee",
|
|
||||||
"downloadCount": 4207,
|
|
||||||
"favoriteCount": 0,
|
|
||||||
"commentCount": 8,
|
|
||||||
"ratingCount": 0,
|
|
||||||
"rating": 0,
|
|
||||||
"is_nsfw": true,
|
|
||||||
"nsfw_level": 31,
|
|
||||||
"createdAt": "2025-07-06T01:51:42.859Z",
|
|
||||||
"updatedAt": "2025-10-10T23:15:26.714Z",
|
|
||||||
"deletedAt": null,
|
|
||||||
"tags": [
|
|
||||||
"art",
|
|
||||||
"style",
|
|
||||||
"artist style",
|
|
||||||
"styles",
|
|
||||||
"mixplin",
|
|
||||||
"artiststyle"
|
|
||||||
],
|
|
||||||
"creator_id": "Ty_Lee",
|
|
||||||
"creator_username": "Ty_Lee",
|
|
||||||
"creator_name": "Ty_Lee",
|
|
||||||
"creator_url": "/users/Ty_Lee",
|
|
||||||
"versions": [
|
|
||||||
{
|
|
||||||
"id": 2042594,
|
|
||||||
"name": "v2.0",
|
|
||||||
"href": "/models/1746460?modelVersionId=2042594"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 1976567,
|
|
||||||
"name": "v1.0",
|
|
||||||
"href": "/models/1746460?modelVersionId=1976567"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version": {
|
|
||||||
"id": 1976567,
|
|
||||||
"modelId": 1746460,
|
|
||||||
"name": "v1.0",
|
|
||||||
"baseModel": "Illustrious",
|
|
||||||
"baseModelType": "Standard",
|
|
||||||
"description": null,
|
|
||||||
"downloadCount": 437,
|
|
||||||
"ratingCount": 0,
|
|
||||||
"rating": 0,
|
|
||||||
"is_nsfw": true,
|
|
||||||
"nsfw_level": 31,
|
|
||||||
"createdAt": "2025-07-05T10:17:28.716Z",
|
|
||||||
"updatedAt": "2025-10-10T23:15:26.756Z",
|
|
||||||
"deletedAt": null,
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"id": 1874043,
|
|
||||||
"name": "mxpln-illustrious-ty_lee.safetensors",
|
|
||||||
"type": "Model",
|
|
||||||
"sizeKB": 223124.37109375,
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1976567",
|
|
||||||
"modelId": 1746460,
|
|
||||||
"modelName": "Mixplin Style [Illustrious]",
|
|
||||||
"modelVersionId": 1976567,
|
|
||||||
"is_nsfw": true,
|
|
||||||
"nsfw_level": 31,
|
|
||||||
"sha256": "e2b7a280d6539556f23f380b3f71e4e22bc4524445c4c96526e117c6005c6ad3",
|
|
||||||
"createdAt": "2025-07-05T10:17:28.716Z",
|
|
||||||
"updatedAt": "2025-10-10T23:15:26.766Z",
|
|
||||||
"is_primary": false,
|
|
||||||
"mirrors": [
|
|
||||||
{
|
|
||||||
"filename": "mxpln-illustrious-ty_lee.safetensors",
|
|
||||||
"url": "https://civitai.com/api/download/models/1976567",
|
|
||||||
"source": "civitai",
|
|
||||||
"model_id": 1746460,
|
|
||||||
"model_version_id": 1976567,
|
|
||||||
"deletedAt": null,
|
|
||||||
"is_gated": false,
|
|
||||||
"is_paid": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"id": 86403595,
|
|
||||||
"url": "https://img.genur.art/sig/width:450/quality:85/aHR0cHM6Ly9jLmdlbnVyLmFydC9hNmE3Njc2YS0wMWQ3LTQ1YzAtOWEzYS1mNWJiYTU4MDNiMDE=",
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"width": 1560,
|
|
||||||
"height": 2280,
|
|
||||||
"hash": "U7G8Zp0w02%IA6%N00-;D]-W~VNG0nMw-.IV",
|
|
||||||
"type": "image",
|
|
||||||
"minor": false,
|
|
||||||
"poi": false,
|
|
||||||
"hasMeta": true,
|
|
||||||
"hasPositivePrompt": true,
|
|
||||||
"onSite": false,
|
|
||||||
"remixOfId": null,
|
|
||||||
"image_url": "https://img.genur.art/sig/width:450/quality:85/aHR0cHM6Ly9jLmdlbnVyLmFydC9hNmE3Njc2YS0wMWQ3LTQ1YzAtOWEzYS1mNWJiYTU4MDNiMDE=",
|
|
||||||
"link": "https://genur.art/posts/86403595"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"trigger": [
|
|
||||||
"mxpln"
|
|
||||||
],
|
|
||||||
"allow_download": true,
|
|
||||||
"download_url": "/api/download/models/1976567",
|
|
||||||
"platform_url": "https://civitai.com/models/1746460?modelVersionId=1976567",
|
|
||||||
"civitai_model_id": 1746460,
|
|
||||||
"civitai_model_version_id": 1976567,
|
|
||||||
"href": "/models/1746460?modelVersionId=1976567",
|
|
||||||
"mirrors": [
|
|
||||||
{
|
|
||||||
"platform": "tensorart",
|
|
||||||
"href": "/tensorart/models/904473536033245448/versions/904473536033245448",
|
|
||||||
"platform_url": "https://tensor.art/models/904473536033245448",
|
|
||||||
"name": "Mixplin Style MXP",
|
|
||||||
"version_name": "Mixplin",
|
|
||||||
"id": "904473536033245448",
|
|
||||||
"version_id": "904473536033245448"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"platform": "civitai",
|
|
||||||
"platform_name": "CivitAI",
|
|
||||||
"meta": {
|
|
||||||
"title": "Mixplin Style [Illustrious] - v1.0 - CivitAI Archive",
|
|
||||||
"description": "Mixplin Style [Illustrious] v1.0 is a Illustrious LORA AI model created by Ty_Lee for generating images of art, style, artist style, styles, mixplin, artiststyle",
|
|
||||||
"image": "https://img.genur.art/sig/width:450/quality:85/aHR0cHM6Ly9jLmdlbnVyLmFydC9hNmE3Njc2YS0wMWQ3LTQ1YzAtOWEzYS1mNWJiYTU4MDNiMDE=",
|
|
||||||
"canonical": "https://civarchive.com/models/1746460?modelVersionId=1976567"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
CREATE TABLE models (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
username TEXT,
|
|
||||||
data TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
) STRICT;
|
|
||||||
CREATE TABLE model_versions (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
model_id INTEGER NOT NULL,
|
|
||||||
position INTEGER NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
base_model TEXT NOT NULL,
|
|
||||||
published_at INTEGER,
|
|
||||||
data TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
) STRICT;
|
|
||||||
CREATE INDEX model_versions_model_id_idx ON model_versions (model_id);
|
|
||||||
CREATE TABLE model_files (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
model_id INTEGER NOT NULL,
|
|
||||||
version_id INTEGER NOT NULL,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
sha256 TEXT,
|
|
||||||
data TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
) STRICT;
|
|
||||||
CREATE INDEX model_files_model_id_idx ON model_files (model_id);
|
|
||||||
CREATE INDEX model_files_version_id_idx ON model_files (version_id);
|
|
||||||
CREATE TABLE archived_model_files (
|
|
||||||
file_id INTEGER PRIMARY KEY,
|
|
||||||
model_id INTEGER NOT NULL,
|
|
||||||
version_id INTEGER NOT NULL
|
|
||||||
) STRICT;
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
{
|
|
||||||
"id": 1231067,
|
|
||||||
"name": "Vivid Impressions Storybook Style",
|
|
||||||
"description": "<h3 id=\"if-you'd-like-to-support-me-feel-free-to-visit-my-ko-fi-page.-please-share-your-images-using-the-"+add-post"-button-below.-it-supports-the-creators.-thanks!-nnfwkvfly\">If you'd like to support me, feel free to visit my <a target=\"_blank\" rel=\"ugc\" href=\"https://ko-fi.com/pixelpawsai\">Ko-Fi</a> page. ❤️<br /><br />Please share your images using the \"<span style=\"color:rgb(250, 82, 82)\">+add post</span>\" button below. It supports the creators. Thanks! 💕</h3><h3 id=\"if-you-like-my-lora-please-like-comment-or-donate-some-buzz.-much-appreciated!-vyeqok3go\">If you like my LoRA, please<span style=\"color:rgb(230, 73, 128)\"> </span><span style=\"color:rgb(250, 82, 82)\">like</span>, <span style=\"color:rgb(250, 82, 82)\">comment</span>, or <span style=\"color:#fa5252\">donate some Buzz</span>. Much appreciated! ❤️</h3><h3 id=\"-lo912t8rj\"></h3><h3 id=\"trigger-word:-ppstorybook-wlggllim2\"><strong><span style=\"color:rgb(253, 126, 20)\">Trigger word: </span></strong>ppstorybook</h3><h3 id=\"strength:-0.8-experiment-as-you-like-luvhks6za\"><strong><span style=\"color:rgb(253, 126, 20)\">Strength: </span></strong>0.8, experiment as you like</h3>",
|
|
||||||
"allowNoCredit": true,
|
|
||||||
"allowCommercialUse": [
|
|
||||||
"Image",
|
|
||||||
"RentCivit",
|
|
||||||
"Rent",
|
|
||||||
"Sell"
|
|
||||||
],
|
|
||||||
"allowDerivatives": true,
|
|
||||||
"allowDifferentLicense": true,
|
|
||||||
"type": "LORA",
|
|
||||||
"minor": false,
|
|
||||||
"sfwOnly": false,
|
|
||||||
"poi": false,
|
|
||||||
"nsfw": false,
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"availability": "Public",
|
|
||||||
"cosmetic": null,
|
|
||||||
"supportsGeneration": true,
|
|
||||||
"stats": {
|
|
||||||
"downloadCount": 2183,
|
|
||||||
"favoriteCount": 0,
|
|
||||||
"thumbsUpCount": 416,
|
|
||||||
"thumbsDownCount": 0,
|
|
||||||
"commentCount": 12,
|
|
||||||
"ratingCount": 0,
|
|
||||||
"rating": 0,
|
|
||||||
"tippedAmountCount": 360
|
|
||||||
},
|
|
||||||
"creator": {
|
|
||||||
"username": "PixelPawsAI",
|
|
||||||
"image": "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/f3a1aa7c-0159-4dd8-884a-1e7ceb350f96/width=96/PixelPawsAI.jpeg"
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"style",
|
|
||||||
"illustration",
|
|
||||||
"storybook"
|
|
||||||
],
|
|
||||||
"modelVersions": [
|
|
||||||
{
|
|
||||||
"id": 1387174,
|
|
||||||
"index": 0,
|
|
||||||
"name": "v1.0",
|
|
||||||
"baseModel": "Flux.1 D",
|
|
||||||
"baseModelType": "Standard",
|
|
||||||
"createdAt": "2025-02-08T11:15:47.197Z",
|
|
||||||
"publishedAt": "2025-02-08T11:29:04.487Z",
|
|
||||||
"status": "Published",
|
|
||||||
"availability": "Public",
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"trainedWords": [
|
|
||||||
"ppstorybook"
|
|
||||||
],
|
|
||||||
"covered": true,
|
|
||||||
"stats": {
|
|
||||||
"downloadCount": 2183,
|
|
||||||
"ratingCount": 0,
|
|
||||||
"rating": 0,
|
|
||||||
"thumbsUpCount": 416,
|
|
||||||
"thumbsDownCount": 0
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"id": 1289799,
|
|
||||||
"sizeKB": 18829.1484375,
|
|
||||||
"name": "pp-storybook_rank2_bf16.safetensors",
|
|
||||||
"type": "Model",
|
|
||||||
"pickleScanResult": "Success",
|
|
||||||
"pickleScanMessage": "No Pickle imports",
|
|
||||||
"virusScanResult": "Success",
|
|
||||||
"virusScanMessage": null,
|
|
||||||
"scannedAt": "2025-02-08T11:21:04.247Z",
|
|
||||||
"metadata": {
|
|
||||||
"format": "SafeTensor"
|
|
||||||
},
|
|
||||||
"hashes": {
|
|
||||||
"AutoV1": "F414C813",
|
|
||||||
"AutoV2": "9753338AB6",
|
|
||||||
"SHA256": "9753338AB693CA82BF89ED77A5D1912879E40051463EC6E330FB9866CE798668",
|
|
||||||
"CRC32": "A65AE7B3",
|
|
||||||
"BLAKE3": "A5F8AB95AC2486345E4ACCAE541FF19D97ED53EFB0A7CC9226636975A0437591",
|
|
||||||
"AutoV3": "34A22376739D"
|
|
||||||
},
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1387174",
|
|
||||||
"primary": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"url": "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/42b875cf-c62b-41fa-a349-383b7f074351/original=true/56547310.jpeg",
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"width": 832,
|
|
||||||
"height": 1216,
|
|
||||||
"hash": "U5IiO6s-4Vn+0~EO^5xa00VsL#IU_O?E7yWC",
|
|
||||||
"type": "image",
|
|
||||||
"minor": false,
|
|
||||||
"poi": false,
|
|
||||||
"hasMeta": true,
|
|
||||||
"hasPositivePrompt": true,
|
|
||||||
"onSite": false,
|
|
||||||
"remixOfId": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1387174"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
{
|
|
||||||
"id": 1387174,
|
|
||||||
"modelId": 1231067,
|
|
||||||
"name": "v1.0",
|
|
||||||
"createdAt": "2025-02-08T11:15:47.197Z",
|
|
||||||
"updatedAt": "2025-02-08T11:29:04.526Z",
|
|
||||||
"status": "Published",
|
|
||||||
"publishedAt": "2025-02-08T11:29:04.487Z",
|
|
||||||
"trainedWords": [
|
|
||||||
"ppstorybook"
|
|
||||||
],
|
|
||||||
"trainingStatus": null,
|
|
||||||
"trainingDetails": null,
|
|
||||||
"baseModel": "Flux.1 D",
|
|
||||||
"baseModelType": null,
|
|
||||||
"earlyAccessEndsAt": null,
|
|
||||||
"earlyAccessConfig": null,
|
|
||||||
"description": null,
|
|
||||||
"uploadType": "Created",
|
|
||||||
"usageControl": "Download",
|
|
||||||
"air": "urn:air:flux1:lora:civitai:1231067@1387174",
|
|
||||||
"stats": {
|
|
||||||
"downloadCount": 1436,
|
|
||||||
"ratingCount": 0,
|
|
||||||
"rating": 0,
|
|
||||||
"thumbsUpCount": 316
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"name": "Vivid Impressions Storybook Style",
|
|
||||||
"type": "LORA",
|
|
||||||
"nsfw": false,
|
|
||||||
"poi": false
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"id": 1289799,
|
|
||||||
"sizeKB": 18829.1484375,
|
|
||||||
"name": "pp-storybook_rank2_bf16.safetensors",
|
|
||||||
"type": "Model",
|
|
||||||
"pickleScanResult": "Success",
|
|
||||||
"pickleScanMessage": "No Pickle imports",
|
|
||||||
"virusScanResult": "Success",
|
|
||||||
"virusScanMessage": null,
|
|
||||||
"scannedAt": "2025-02-08T11:21:04.247Z",
|
|
||||||
"metadata": {
|
|
||||||
"format": "SafeTensor",
|
|
||||||
"size": null,
|
|
||||||
"fp": null
|
|
||||||
},
|
|
||||||
"hashes": {
|
|
||||||
"AutoV1": "F414C813",
|
|
||||||
"AutoV2": "9753338AB6",
|
|
||||||
"SHA256": "9753338AB693CA82BF89ED77A5D1912879E40051463EC6E330FB9866CE798668",
|
|
||||||
"CRC32": "A65AE7B3",
|
|
||||||
"BLAKE3": "A5F8AB95AC2486345E4ACCAE541FF19D97ED53EFB0A7CC9226636975A0437591",
|
|
||||||
"AutoV3": "34A22376739D"
|
|
||||||
},
|
|
||||||
"primary": true,
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1387174"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"url": "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/42b875cf-c62b-41fa-a349-383b7f074351/width=832/56547310.jpeg",
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"width": 832,
|
|
||||||
"height": 1216,
|
|
||||||
"hash": "U5IiO6s-4Vn+0~EO^5xa00VsL#IU_O?E7yWC",
|
|
||||||
"type": "image",
|
|
||||||
"metadata": {
|
|
||||||
"hash": "U5IiO6s-4Vn+0~EO^5xa00VsL#IU_O?E7yWC",
|
|
||||||
"size": 1361590,
|
|
||||||
"width": 832,
|
|
||||||
"height": 1216
|
|
||||||
},
|
|
||||||
"meta": {
|
|
||||||
"Size": "832x1216",
|
|
||||||
"seed": 1116375220995209,
|
|
||||||
"Model": "flux_dev_fp8",
|
|
||||||
"steps": 23,
|
|
||||||
"hashes": {
|
|
||||||
"model": ""
|
|
||||||
},
|
|
||||||
"prompt": "ppstorybook,A dreamy bunny hopping across a rainbow bridge, with fluffy clouds surrounding it and tiny birds flying alongside, rendered in a magical, soft-focus style with pastel hues and glowing accents.",
|
|
||||||
"Version": "ComfyUI",
|
|
||||||
"sampler": "DPM++ 2M",
|
|
||||||
"cfgScale": 3.5,
|
|
||||||
"clipSkip": 1,
|
|
||||||
"resources": [],
|
|
||||||
"Model hash": ""
|
|
||||||
},
|
|
||||||
"availability": "Public",
|
|
||||||
"hasMeta": true,
|
|
||||||
"hasPositivePrompt": true,
|
|
||||||
"onSite": false,
|
|
||||||
"remixOfId": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1387174"
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
{
|
|
||||||
"resource-stack": {
|
|
||||||
"class_type": "CheckpointLoaderSimple",
|
|
||||||
"inputs": { "ckpt_name": "urn:air:sdxl:checkpoint:civitai:827184@1410435" }
|
|
||||||
},
|
|
||||||
"resource-stack-1": {
|
|
||||||
"class_type": "LoraLoader",
|
|
||||||
"inputs": {
|
|
||||||
"lora_name": "urn:air:sdxl:lora:civitai:1107767@1253442",
|
|
||||||
"strength_model": 1,
|
|
||||||
"strength_clip": 1,
|
|
||||||
"model": ["resource-stack", 0],
|
|
||||||
"clip": ["resource-stack", 1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"resource-stack-2": {
|
|
||||||
"class_type": "LoraLoader",
|
|
||||||
"inputs": {
|
|
||||||
"lora_name": "urn:air:sdxl:lora:civitai:1342708@1516344",
|
|
||||||
"strength_model": 1,
|
|
||||||
"strength_clip": 1,
|
|
||||||
"model": ["resource-stack-1", 0],
|
|
||||||
"clip": ["resource-stack-1", 1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"resource-stack-3": {
|
|
||||||
"class_type": "LoraLoader",
|
|
||||||
"inputs": {
|
|
||||||
"lora_name": "urn:air:sdxl:lora:civitai:122359@135867",
|
|
||||||
"strength_model": 1.55,
|
|
||||||
"strength_clip": 1,
|
|
||||||
"model": ["resource-stack-2", 0],
|
|
||||||
"clip": ["resource-stack-2", 1]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"class_type": "smZ CLIPTextEncode",
|
|
||||||
"inputs": {
|
|
||||||
"text": "masterpiece, best quality, amazing quality, detailed setting, detailed background, 1girl, yunyun (konosuba), nude, red eyes, hair ornament, braid, hair between eyes,low twintails, pink ribbon, bow, hair bow, pussy, frilled skirt, layered skirt, belt, pink thighhighs, (pussy juice), large insertion, vaginal tugging, pussy grip, detailed skin, detailed soles, stretched pussy, feet in stockings, ass, nipples, medium breasts, french kiss, anus, shocked, nervous, penis awe, BREAK Professor\u0027s office, college student, pornographic, 1boy, close eyes, (musscular male, detailed large cock), vaginal sex, college office setting, ass grab, fucking, riding, cowgirl, erotic, side view, deep fucking",
|
|
||||||
"parser": "comfy",
|
|
||||||
"text_g": "",
|
|
||||||
"text_l": "",
|
|
||||||
"ascore": 2.5,
|
|
||||||
"width": 0,
|
|
||||||
"height": 0,
|
|
||||||
"crop_w": 0,
|
|
||||||
"crop_h": 0,
|
|
||||||
"target_width": 0,
|
|
||||||
"target_height": 0,
|
|
||||||
"smZ_steps": 1,
|
|
||||||
"mean_normalization": true,
|
|
||||||
"multi_conditioning": true,
|
|
||||||
"use_old_emphasis_implementation": false,
|
|
||||||
"with_SDXL": false,
|
|
||||||
"clip": ["resource-stack-3", 1]
|
|
||||||
},
|
|
||||||
"_meta": { "title": "Positive" }
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"class_type": "smZ CLIPTextEncode",
|
|
||||||
"inputs": {
|
|
||||||
"text": "bad quality,worst quality,worst detail,sketch,censor",
|
|
||||||
"parser": "comfy",
|
|
||||||
"text_g": "",
|
|
||||||
"text_l": "",
|
|
||||||
"ascore": 2.5,
|
|
||||||
"width": 0,
|
|
||||||
"height": 0,
|
|
||||||
"crop_w": 0,
|
|
||||||
"crop_h": 0,
|
|
||||||
"target_width": 0,
|
|
||||||
"target_height": 0,
|
|
||||||
"smZ_steps": 1,
|
|
||||||
"mean_normalization": true,
|
|
||||||
"multi_conditioning": true,
|
|
||||||
"use_old_emphasis_implementation": false,
|
|
||||||
"with_SDXL": false,
|
|
||||||
"clip": ["resource-stack-3", 1]
|
|
||||||
},
|
|
||||||
"_meta": { "title": "Negative" }
|
|
||||||
},
|
|
||||||
"20": {
|
|
||||||
"class_type": "UpscaleModelLoader",
|
|
||||||
"inputs": { "model_name": "urn:air:other:upscaler:civitai:147759@164821" },
|
|
||||||
"_meta": { "title": "Load Upscale Model" }
|
|
||||||
},
|
|
||||||
"17": {
|
|
||||||
"class_type": "LoadImage",
|
|
||||||
"inputs": {
|
|
||||||
"image": "https://orchestration.civitai.com/v2/consumer/blobs/5KZ6358TW8CNEGPZKD08NVDB30",
|
|
||||||
"upload": "image"
|
|
||||||
},
|
|
||||||
"_meta": { "title": "Image Load" }
|
|
||||||
},
|
|
||||||
"19": {
|
|
||||||
"class_type": "ImageUpscaleWithModel",
|
|
||||||
"inputs": { "upscale_model": ["20", 0], "image": ["17", 0] },
|
|
||||||
"_meta": { "title": "Upscale Image (using Model)" }
|
|
||||||
},
|
|
||||||
"23": {
|
|
||||||
"class_type": "ImageScale",
|
|
||||||
"inputs": {
|
|
||||||
"upscale_method": "nearest-exact",
|
|
||||||
"crop": "disabled",
|
|
||||||
"width": 1280,
|
|
||||||
"height": 1856,
|
|
||||||
"image": ["19", 0]
|
|
||||||
},
|
|
||||||
"_meta": { "title": "Upscale Image" }
|
|
||||||
},
|
|
||||||
"21": {
|
|
||||||
"class_type": "VAEEncode",
|
|
||||||
"inputs": { "pixels": ["23", 0], "vae": ["resource-stack", 2] },
|
|
||||||
"_meta": { "title": "VAE Encode" }
|
|
||||||
},
|
|
||||||
"11": {
|
|
||||||
"class_type": "KSampler",
|
|
||||||
"inputs": {
|
|
||||||
"sampler_name": "euler_ancestral",
|
|
||||||
"scheduler": "normal",
|
|
||||||
"seed": 2088370631,
|
|
||||||
"steps": 47,
|
|
||||||
"cfg": 6.5,
|
|
||||||
"denoise": 0.3,
|
|
||||||
"model": ["resource-stack-3", 0],
|
|
||||||
"positive": ["6", 0],
|
|
||||||
"negative": ["7", 0],
|
|
||||||
"latent_image": ["21", 0]
|
|
||||||
},
|
|
||||||
"_meta": { "title": "KSampler" }
|
|
||||||
},
|
|
||||||
"13": {
|
|
||||||
"class_type": "VAEDecode",
|
|
||||||
"inputs": { "samples": ["11", 0], "vae": ["resource-stack", 2] },
|
|
||||||
"_meta": { "title": "VAE Decode" }
|
|
||||||
},
|
|
||||||
"12": {
|
|
||||||
"class_type": "SaveImage",
|
|
||||||
"inputs": { "filename_prefix": "ComfyUI", "images": ["13", 0] },
|
|
||||||
"_meta": { "title": "Save Image" }
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"airs": [
|
|
||||||
"urn:air:other:upscaler:civitai:147759@164821",
|
|
||||||
"urn:air:sdxl:checkpoint:civitai:827184@1410435",
|
|
||||||
"urn:air:sdxl:lora:civitai:1107767@1253442",
|
|
||||||
"urn:air:sdxl:lora:civitai:1342708@1516344",
|
|
||||||
"urn:air:sdxl:lora:civitai:122359@135867"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"extraMetadata": "{\u0022prompt\u0022:\u0022masterpiece, best quality, amazing quality, detailed setting, detailed background, 1girl, yunyun (konosuba), nude, red eyes, hair ornament, braid, hair between eyes,low twintails, pink ribbon, bow, hair bow, pussy, frilled skirt, layered skirt, belt, pink thighhighs, (pussy juice), large insertion, vaginal tugging, pussy grip, detailed skin, detailed soles, stretched pussy, feet in stockings, ass, nipples, medium breasts, french kiss, anus, shocked, nervous, penis awe, BREAK Professor\u0027s office, college student, pornographic, 1boy, close eyes, (musscular male, detailed large cock), vaginal sex, college office setting, ass grab, fucking, riding, cowgirl, erotic, side view, deep fucking\u0022,\u0022negativePrompt\u0022:\u0022bad quality,worst quality,worst detail,sketch,censor\u0022,\u0022steps\u0022:47,\u0022cfgScale\u0022:6.5,\u0022sampler\u0022:\u0022euler_ancestral\u0022,\u0022workflowId\u0022:\u0022img2img-hires\u0022,\u0022resources\u0022:[{\u0022modelVersionId\u0022:1410435,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:1410435,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:1253442,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:1516344,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:135867,\u0022strength\u0022:1.55}],\u0022remixOfId\u0022:32140259}"
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
a dynamic and dramatic digital artwork featuring a stylized anthropomorphic white tiger with striking yellow eyes. The tiger is depicted in a powerful stance, wielding a katana with one hand raised above its head. Its fur is detailed with black stripes, and its mane flows wildly, blending with the stormy background. The scene is set amidst swirling dark clouds and flashes of lightning, enhancing the sense of movement and energy. The composition is vertical, with the tiger positioned centrally, creating a sense of depth and intensity. The color palette is dominated by shades of blue, gray, and white, with bright highlights from the lightning. The overall style is reminiscent of fantasy or manga art, with a focus on dynamic action and dramatic lighting.
|
|
||||||
Negative prompt:
|
|
||||||
Steps: 30, Sampler: Undefined, CFG scale: 3.5, Seed: 90300501, Size: 832x1216, Clip skip: 2, Created Date: 2025-03-05T13:51:18.1770234Z, Civitai resources: [{"type":"checkpoint","modelVersionId":691639,"modelName":"FLUX","modelVersionName":"Dev"},{"type":"lora","weight":0.4,"modelVersionId":1202162,"modelName":"Velvet\u0027s Mythic Fantasy Styles | Flux \u002B Pony \u002B illustrious","modelVersionName":"Flux Gothic Lines"},{"type":"lora","weight":0.8,"modelVersionId":1470588,"modelName":"Velvet\u0027s Mythic Fantasy Styles | Flux \u002B Pony \u002B illustrious","modelVersionName":"Flux Retro"},{"type":"lora","weight":0.75,"modelVersionId":746484,"modelName":"Elden Ring - Yoshitaka Amano","modelVersionName":"V1"},{"type":"lora","weight":0.2,"modelVersionId":914935,"modelName":"Ink-style","modelVersionName":"ink-dynamic"},{"type":"lora","weight":0.2,"modelVersionId":1189379,"modelName":"Painterly Fantasy by ChronoKnight - [FLUX \u0026 IL]","modelVersionName":"FLUX"},{"type":"lora","weight":0.2,"modelVersionId":757030,"modelName":"Mezzotint Artstyle for Flux - by Ethanar","modelVersionName":"V1"}], Civitai metadata: {}
|
|
||||||
|
|
||||||
masterpiece, best quality, good quality, very aesthetic, absurdres, newest, 8K, depth of field, focused subject,
|
|
||||||
dynamic angle, dutch angle, from below, epic half body portrait, gritty, wabi sabi, looking at viewer, woman is a geisha, parted lips,
|
|
||||||
holographic skin, holofoil glitter, faint, glowing, ethereal, neon hair, glowing hair, otherworldly glow, she is dangerous
|
|
||||||
<lora:ck-shadow-circuit-IL:0.78>, <lora:ck-nc-cyberpunk-IL-000011:0.4>, <lora:ck-neon-retrowave-IL:0.2>, <lora:ck-yoneyama-mai-IL-000014:0.4>
|
|
||||||
Negative prompt: score_6, score_5, score_4, bad quality, worst quality, worst detail, sketch, censorship, furry, window, headphones,
|
|
||||||
Steps: 30, Sampler: Euler a, Schedule type: Simple, CFG scale: 7, Seed: 1405717592, Size: 832x1216, Model hash: 1ad6ca7f70, Model: waiNSFWIllustrious_v100, Denoising strength: 0.35, Hires CFG Scale: 5, Hires upscale: 1.3, Hires steps: 20, Hires upscaler: 4x-AnimeSharp, Lora hashes: "ck-shadow-circuit-IL: 88e247aa8c3d, ck-nc-cyberpunk-IL-000011: 935e6755554c, ck-neon-retrowave-IL: edafb9df7da1, ck-yoneyama-mai-IL-000014: 1b9305692a2e", Version: f2.0.1v1.10.1-1.10.1, Diffusion in Low Bits: Automatic (fp16 LoRA)
|
|
||||||
|
|
||||||
Masterpiece, best quality, high quality, newest, highres, 8K, HDR, absurdres, 1girl, solo, futuristic warrior, sleek exosuit with glowing energy cores, long braided hair flowing behind, gripping a high-tech bow with an energy arrow drawn, standing on a floating platform overlooking a massive space station, planets and nebulae in the distance, soft glow from distant stars, cinematic depth, foreshortening, dynamic pose, dramatic sci-fi lighting.
|
|
||||||
Negative prompt: worst quality, normal quality, anatomical nonsense, bad anatomy,interlocked fingers, extra fingers,watermark,simple background, loli,
|
|
||||||
Steps: 20, Sampler: euler_ancestral_karras, CFG scale: 8.0, Seed: 691121152183439, Model: il\waiNSFWIllustrious_v110.safetensors, Model hash: c3688ee04c, Lora_0 Model name: iLLMythAn1m3Style.safetensors, Lora_0 Model hash: ba7a040786, Lora_0 Strength model: 1.0, Lora_0 Strength clip: 1.0, Hashes: {"model": "c3688ee04c", "lora:iLLMythAn1m3Style": "ba7a040786"}
|
|
||||||
|
|
||||||
Immerse yourself in the enchanting journey, where harmonious transmutation of Bauhaus art unites photographic precision and contemporary illustration, capturing an enthralling blend between vivid abstract nature and urban landscapes. Let your eyes be captivated by a kaleidoscope of rich, deep reds and yellows, entwined with intriguing shades that beckon a somber atmosphere. As your spirit ventures along this haunting path, witness the mysterious, high-angle perspective dominated by scattered clouds – granting you a mesmerizing glimpse into the ever-transforming realm of metamorphosing environments. ,<lora:flux/fav/ck-charcoal-drawing-000014.safetensors:1.0:1.0>
|
|
||||||
Negative prompt:
|
|
||||||
Steps: 20, Sampler: Euler, CFG scale: 3.5, Seed: 885491426361006, Size: 832x1216, Model hash: 4610115bb0, Model: flux_dev, Hashes: {"LORA:flux/fav/ck-charcoal-drawing-000014.safetensors": "34d36c17c1", "model": "4610115bb0"}, Version: ComfyUI
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
In this ethereal masterpiece, metallic sculptures juxtapose effortlessly against a subtle backdrop of misty neutral hues. Exquisite curvatures and geometric shapes converge harmoniously, creating an illuminating realm of polished metallic surfaces. Shimmering copper, gleaming silver, and lustrous gold hues dance in perfect balance, highlighting the intricate play of light and shadow cast upon these celestial forms. A halo of diffused radiance envelops each piece, enhancing their textured depths and metallic brilliance while allowing delicate details to emerge from obscurity. The composition conveys a serene yet mesmerizing atmosphere, as if suspended in a dreamlike limbo between reality and fantasy. The tantalizing interplay of colors within this transcendent realm creates a profound sense of depth and grandeur that invites the viewer into an enchanting voyage through abstract metallic beauty. This captivating artwork evokes emotions of boundless curiosity and reverence reminiscent of the timeless works by artists such as Giorgio de Chirico or Paul Klee, while asserting a unique, modern artistic sensibility. With every observation, a new nuance unfolds, as if a never-ending story waiting to be discovered through the lens of metallic artistry.
|
|
||||||
Negative prompt:
|
|
||||||
Steps: 25, Sampler: dpmpp_2m_sgm_uniform, Seed: 471889513588087, Model: Fluxmania V5P.safetensors, Model hash: 8ae0583b06, VAE: ae.sft, VAE hash: afc8e28272, Lora_0 Model name: ArtVador I.safetensors, Lora_0 Model hash: 08f7133a58, Lora_0 Strength model: 0.65, Lora_0 Strength clip: 0.65, Lora_1 Model name: Kaoru Yamada.safetensors, Lora_1 Model hash: d4893f7202, Lora_1 Strength model: 0.75, Lora_1 Strength clip: 0.75, Hashes: {"model": "8ae0583b06", "vae": "afc8e28272", "lora:ArtVador I": "08f7133a58", "lora:Kaoru Yamada": "d4893f7202"}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "42803a29-02dc-49e1-b798-27da70e8b408",
|
|
||||||
"file_path": "/home/miao/workspace/ComfyUI/models/loras/recipes/test/42803a29-02dc-49e1-b798-27da70e8b408.webp",
|
|
||||||
"title": "masterpiece, best quality, amazing quality, very aesthetic, detailed eyes, perfect",
|
|
||||||
"modified": 1754897325.0507245,
|
|
||||||
"created_date": 1754897325.0507245,
|
|
||||||
"base_model": "Illustrious",
|
|
||||||
"loras": [
|
|
||||||
{
|
|
||||||
"file_name": "",
|
|
||||||
"hash": "1b5b763d83961bb5745f3af8271ba83f1d4fd69c16278dae6d5b4e194bdde97a",
|
|
||||||
"strength": 1.0,
|
|
||||||
"modelVersionId": 2007092,
|
|
||||||
"modelName": "Pony: People's Works +",
|
|
||||||
"modelVersionName": "v8_Illusv1.0",
|
|
||||||
"isDeleted": false,
|
|
||||||
"exclude": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"gen_params": {
|
|
||||||
"prompt": "masterpiece, best quality, amazing quality, very aesthetic, detailed eyes, perfect eyes, realistic eyes,\n(flat colors:1.5), (anime:1.5), (lineart:1.5),\nclose-up, solo, tongue, 1girl, food, (saliva:0.1), open mouth, candy, simple background, blue background, large lollipop, tongue out, fade background, lips, hand up, holding, looking at viewer, licking, seductive, half-closed eyes,",
|
|
||||||
"negative_prompt": "shiny skin,",
|
|
||||||
"steps": 19,
|
|
||||||
"sampler": "Euler a",
|
|
||||||
"cfg_scale": 5,
|
|
||||||
"seed": 1765271748,
|
|
||||||
"size": "832x1216",
|
|
||||||
"clip_skip": 2
|
|
||||||
},
|
|
||||||
"fingerprint": "1b5b763d83961bb5745f3af8271ba83f1d4fd69c16278dae6d5b4e194bdde97a:1.0",
|
|
||||||
"source_path": "https://civitai.com/images/92427432",
|
|
||||||
"folder": "test"
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"id": 2269146,
|
|
||||||
"modelId": 2004760,
|
|
||||||
"name": "v1.0 Illustrious",
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"trainedWords": ["PencilSketchDaal"],
|
|
||||||
"baseModel": "Illustrious",
|
|
||||||
"description": "<p>Illustrious. Your pencil may vary with your checkpoint. </p>",
|
|
||||||
"model": {
|
|
||||||
"name": "Pencil Sketch Anime",
|
|
||||||
"type": "LORA",
|
|
||||||
"nsfw": false,
|
|
||||||
"description": "description",
|
|
||||||
"tags": ["style"],
|
|
||||||
"allowNoCredit": true,
|
|
||||||
"allowCommercialUse": ["Sell"],
|
|
||||||
"allowDerivatives": true,
|
|
||||||
"allowDifferentLicense": true
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"id": 2161260,
|
|
||||||
"sizeKB": 223106.37890625,
|
|
||||||
"name": "Pencil-Sketch-Illustrious.safetensors",
|
|
||||||
"type": "Model",
|
|
||||||
"hashes": {
|
|
||||||
"SHA256": "2C70479CD673B0FE056EAF4FD97C7F33A39F14853805431AC9AB84226ECE3B82"
|
|
||||||
},
|
|
||||||
"primary": true,
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/2269146",
|
|
||||||
"mirrors": {}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"images": [
|
|
||||||
{},
|
|
||||||
{}
|
|
||||||
],
|
|
||||||
"creator": {
|
|
||||||
"username": "Daalis",
|
|
||||||
"image": "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/eb245b49-edc8-4ed6-ad7b-6d61eb8c51de/width=96/Daalis.jpeg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
"id": 1255556,
|
|
||||||
"modelId": 1117241,
|
|
||||||
"name": "v1.0",
|
|
||||||
"createdAt": "2025-01-08T06:13:08.839Z",
|
|
||||||
"updatedAt": "2025-01-08T06:28:54.156Z",
|
|
||||||
"status": "Published",
|
|
||||||
"publishedAt": "2025-01-08T06:28:54.155Z",
|
|
||||||
"trainedWords": ["in the style of ppWhimsy"],
|
|
||||||
"trainingStatus": null,
|
|
||||||
"trainingDetails": null,
|
|
||||||
"baseModel": "Flux.1 D",
|
|
||||||
"baseModelType": "Standard",
|
|
||||||
"earlyAccessEndsAt": null,
|
|
||||||
"earlyAccessConfig": null,
|
|
||||||
"description": null,
|
|
||||||
"uploadType": "Created",
|
|
||||||
"usageControl": "Download",
|
|
||||||
"air": "urn:air:flux1:lora:civitai:1117241@1255556",
|
|
||||||
"stats": {
|
|
||||||
"downloadCount": 210,
|
|
||||||
"ratingCount": 0,
|
|
||||||
"rating": 0,
|
|
||||||
"thumbsUpCount": 26
|
|
||||||
},
|
|
||||||
"model": {
|
|
||||||
"name": "Enchanted Whimsy style (Flux)",
|
|
||||||
"type": "LORA",
|
|
||||||
"nsfw": false,
|
|
||||||
"poi": false
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"id": 1160774,
|
|
||||||
"sizeKB": 38828.8125,
|
|
||||||
"name": "pp-enchanted-whimsy.safetensors",
|
|
||||||
"type": "Model",
|
|
||||||
"pickleScanResult": "Success",
|
|
||||||
"pickleScanMessage": "No Pickle imports",
|
|
||||||
"virusScanResult": "Success",
|
|
||||||
"virusScanMessage": null,
|
|
||||||
"scannedAt": "2025-01-08T06:16:27.731Z",
|
|
||||||
"metadata": {
|
|
||||||
"format": "SafeTensor",
|
|
||||||
"size": null,
|
|
||||||
"fp": null
|
|
||||||
},
|
|
||||||
"hashes": {
|
|
||||||
"AutoV1": "40CAF049",
|
|
||||||
"AutoV2": "3202778C3E",
|
|
||||||
"SHA256": "3202778C3EBE5CF7EBE5FC51561DEAE8611F4362036EB7C02EFA033C705E6240",
|
|
||||||
"CRC32": "69DCD953",
|
|
||||||
"BLAKE3": "ED04580DDB1AD36D8B87F4B0800F5930C7E5D4A7269BDC2BE26ED77EA1A34697",
|
|
||||||
"AutoV3": "BF82986F8597"
|
|
||||||
},
|
|
||||||
"primary": true,
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1255556"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"images": [
|
|
||||||
{
|
|
||||||
"url": "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/707aef9b-36fb-46c2-ac41-adcab539d3a6/width=832/50270101.jpeg",
|
|
||||||
"nsfwLevel": 1,
|
|
||||||
"width": 832,
|
|
||||||
"height": 1216,
|
|
||||||
"hash": "U7Am@@$^J3%100R;pLR.M]tQ-ps+?wRiVrof",
|
|
||||||
"type": "image",
|
|
||||||
"metadata": {
|
|
||||||
"hash": "U7Am@@$^J3%100R;pLR.M]tQ-ps+?wRiVrof",
|
|
||||||
"size": 702313,
|
|
||||||
"width": 832,
|
|
||||||
"height": 1216
|
|
||||||
},
|
|
||||||
"minor": false,
|
|
||||||
"poi": false,
|
|
||||||
"meta": {
|
|
||||||
"prompt": "in the style of ppWhimsy, a close-up of a boy with a crown of ferns and tiny horns, his eyes wide with wonder as a family of glowing hedgehogs nestle in his hands, their spines shimmering with soft pastel colors"
|
|
||||||
},
|
|
||||||
"availability": "Public",
|
|
||||||
"hasMeta": true,
|
|
||||||
"hasPositivePrompt": true,
|
|
||||||
"onSite": false,
|
|
||||||
"remixOfId": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"downloadUrl": "https://civitai.com/api/download/models/1255556",
|
|
||||||
"creator": {
|
|
||||||
"username": "PixelPawsAI",
|
|
||||||
"image": "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/f3a1aa7c-0159-4dd8-884a-1e7ceb350f96/width=96/PixelPawsAI.jpeg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,6 +21,7 @@ export class BulkManager {
|
|||||||
this.isMarqueeActive = false;
|
this.isMarqueeActive = false;
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.marqueeStart = { x: 0, y: 0 };
|
this.marqueeStart = { x: 0, y: 0 };
|
||||||
|
this.marqueeStartDoc = { x: 0, y: 0 }; // Marquee start in document coordinates
|
||||||
this.marqueeElement = null;
|
this.marqueeElement = null;
|
||||||
this.initialSelectedModels = new Set();
|
this.initialSelectedModels = new Set();
|
||||||
|
|
||||||
@@ -29,6 +30,11 @@ export class BulkManager {
|
|||||||
this.mouseDownTime = 0;
|
this.mouseDownTime = 0;
|
||||||
this.mouseDownPosition = { x: 0, y: 0 };
|
this.mouseDownPosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
// Auto-scroll properties for marquee
|
||||||
|
this.lastClientX = 0;
|
||||||
|
this.lastClientY = 0;
|
||||||
|
this.autoScrollRaf = null;
|
||||||
|
|
||||||
// Model type specific action configurations
|
// Model type specific action configurations
|
||||||
this.actionConfig = {
|
this.actionConfig = {
|
||||||
[MODEL_TYPES.LORA]: {
|
[MODEL_TYPES.LORA]: {
|
||||||
@@ -168,7 +174,10 @@ export class BulkManager {
|
|||||||
|
|
||||||
eventManager.addHandler('mousemove', 'bulkManager-marquee-move', (e) => {
|
eventManager.addHandler('mousemove', 'bulkManager-marquee-move', (e) => {
|
||||||
if (this.isMarqueeActive) {
|
if (this.isMarqueeActive) {
|
||||||
|
this.lastClientX = e.clientX;
|
||||||
|
this.lastClientY = e.clientY;
|
||||||
this.updateMarqueeSelection(e);
|
this.updateMarqueeSelection(e);
|
||||||
|
this.startAutoScroll();
|
||||||
} else if (this.mouseDownTime && !this.isDragging) {
|
} else if (this.mouseDownTime && !this.isDragging) {
|
||||||
// Check if we've moved enough to consider it a drag
|
// Check if we've moved enough to consider it a drag
|
||||||
const dx = e.clientX - this.mouseDownPosition.x;
|
const dx = e.clientX - this.mouseDownPosition.x;
|
||||||
@@ -237,6 +246,7 @@ export class BulkManager {
|
|||||||
* Clean up event handlers
|
* Clean up event handlers
|
||||||
*/
|
*/
|
||||||
cleanup() {
|
cleanup() {
|
||||||
|
this.stopAutoScroll();
|
||||||
eventManager.removeAllHandlersForSource('bulkManager-keyboard');
|
eventManager.removeAllHandlersForSource('bulkManager-keyboard');
|
||||||
eventManager.removeAllHandlersForSource('bulkManager-marquee-start');
|
eventManager.removeAllHandlersForSource('bulkManager-marquee-start');
|
||||||
eventManager.removeAllHandlersForSource('bulkManager-marquee-move');
|
eventManager.removeAllHandlersForSource('bulkManager-marquee-move');
|
||||||
@@ -1727,10 +1737,15 @@ export class BulkManager {
|
|||||||
* @param {boolean} isDragging - Whether this is triggered from a drag operation
|
* @param {boolean} isDragging - Whether this is triggered from a drag operation
|
||||||
*/
|
*/
|
||||||
startMarqueeSelection(e, isDragging = false) {
|
startMarqueeSelection(e, isDragging = false) {
|
||||||
// Store initial mouse position
|
// Store initial mouse position (viewport coordinates for visual element)
|
||||||
this.marqueeStart.x = this.mouseDownPosition.x;
|
this.marqueeStart.x = this.mouseDownPosition.x;
|
||||||
this.marqueeStart.y = this.mouseDownPosition.y;
|
this.marqueeStart.y = this.mouseDownPosition.y;
|
||||||
|
|
||||||
|
// Store initial mouse position in document coordinates (for logical selection)
|
||||||
|
const container = document.querySelector('.page-content');
|
||||||
|
this.marqueeStartDoc.x = this.mouseDownPosition.x + (container?.scrollLeft || 0);
|
||||||
|
this.marqueeStartDoc.y = this.mouseDownPosition.y + (container?.scrollTop || 0);
|
||||||
|
|
||||||
// Store initial selection state
|
// Store initial selection state
|
||||||
this.initialSelectedModels = new Set(state.selectedModels);
|
this.initialSelectedModels = new Set(state.selectedModels);
|
||||||
|
|
||||||
@@ -1776,46 +1791,67 @@ export class BulkManager {
|
|||||||
*/
|
*/
|
||||||
updateMarqueeSelection(e) {
|
updateMarqueeSelection(e) {
|
||||||
if (!this.marqueeElement) return;
|
if (!this.marqueeElement) return;
|
||||||
|
this.updateMarqueeSelectionFromPosition(e.clientX, e.clientY);
|
||||||
const currentX = e.clientX;
|
|
||||||
const currentY = e.clientY;
|
|
||||||
|
|
||||||
// Calculate rectangle bounds
|
|
||||||
const left = Math.min(this.marqueeStart.x, currentX);
|
|
||||||
const top = Math.min(this.marqueeStart.y, currentY);
|
|
||||||
const width = Math.abs(currentX - this.marqueeStart.x);
|
|
||||||
const height = Math.abs(currentY - this.marqueeStart.y);
|
|
||||||
|
|
||||||
// Update marquee element position and size
|
|
||||||
this.marqueeElement.style.left = left + 'px';
|
|
||||||
this.marqueeElement.style.top = top + 'px';
|
|
||||||
this.marqueeElement.style.width = width + 'px';
|
|
||||||
this.marqueeElement.style.height = height + 'px';
|
|
||||||
|
|
||||||
// Check which cards intersect with marquee
|
|
||||||
this.updateCardSelection(left, top, left + width, top + height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update card selection based on marquee bounds
|
* Update marquee from raw client coordinates (used by both mousemove and auto-scroll loop)
|
||||||
*/
|
*/
|
||||||
updateCardSelection(left, top, right, bottom) {
|
updateMarqueeSelectionFromPosition(clientX, clientY) {
|
||||||
const cards = document.querySelectorAll('.model-card');
|
if (!this.marqueeElement) return;
|
||||||
|
|
||||||
|
const container = document.querySelector('.page-content');
|
||||||
|
const scrollX = container?.scrollLeft || 0;
|
||||||
|
const scrollY = container?.scrollTop || 0;
|
||||||
|
|
||||||
|
// Current position in document coordinates
|
||||||
|
const currentDocX = clientX + scrollX;
|
||||||
|
const currentDocY = clientY + scrollY;
|
||||||
|
|
||||||
|
// Calculate marquee rectangle in document coordinates
|
||||||
|
const docLeft = Math.min(this.marqueeStartDoc.x, currentDocX);
|
||||||
|
const docTop = Math.min(this.marqueeStartDoc.y, currentDocY);
|
||||||
|
const docRight = Math.max(this.marqueeStartDoc.x, currentDocX);
|
||||||
|
const docBottom = Math.max(this.marqueeStartDoc.y, currentDocY);
|
||||||
|
|
||||||
|
// Update visual marquee element (position: fixed, so subtract scroll offset)
|
||||||
|
this.marqueeElement.style.left = (docLeft - scrollX) + 'px';
|
||||||
|
this.marqueeElement.style.top = (docTop - scrollY) + 'px';
|
||||||
|
this.marqueeElement.style.width = (docRight - docLeft) + 'px';
|
||||||
|
this.marqueeElement.style.height = (docBottom - docTop) + 'px';
|
||||||
|
|
||||||
|
// Check which cards intersect with marquee
|
||||||
|
this.updateCardSelection(docLeft, docTop, docRight, docBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update card selection based on marquee bounds (document coordinates).
|
||||||
|
* Uses dual detection: DOM cards for visible ones + VirtualScroller layout for off-screen cards.
|
||||||
|
*/
|
||||||
|
updateCardSelection(docLeft, docTop, docRight, docBottom) {
|
||||||
|
const vs = state.virtualScroller;
|
||||||
|
const container = document.querySelector('.page-content');
|
||||||
|
const scrollX = container?.scrollLeft || 0;
|
||||||
|
const scrollY = container?.scrollTop || 0;
|
||||||
const newSelection = new Set(this.initialSelectedModels);
|
const newSelection = new Set(this.initialSelectedModels);
|
||||||
|
const visibleFilepaths = new Set();
|
||||||
|
|
||||||
cards.forEach(card => {
|
// Step 1: Process visible DOM cards using getBoundingClientRect + scroll offset
|
||||||
const rect = card.getBoundingClientRect();
|
document.querySelectorAll('.model-card').forEach(card => {
|
||||||
|
|
||||||
// Check if card intersects with marquee rectangle
|
|
||||||
const intersects = !(rect.right < left ||
|
|
||||||
rect.left > right ||
|
|
||||||
rect.bottom < top ||
|
|
||||||
rect.top > bottom);
|
|
||||||
|
|
||||||
const filepath = card.dataset.filepath;
|
const filepath = card.dataset.filepath;
|
||||||
|
if (!filepath) return;
|
||||||
|
visibleFilepaths.add(filepath);
|
||||||
|
|
||||||
|
const rect = card.getBoundingClientRect();
|
||||||
|
const cardLeft = rect.left + scrollX;
|
||||||
|
const cardTop = rect.top + scrollY;
|
||||||
|
const cardRight = rect.right + scrollX;
|
||||||
|
const cardBottom = rect.bottom + scrollY;
|
||||||
|
|
||||||
|
const intersects = !(cardRight < docLeft || cardLeft > docRight ||
|
||||||
|
cardBottom < docTop || cardTop > docBottom);
|
||||||
|
|
||||||
if (intersects) {
|
if (intersects) {
|
||||||
// Add to selection if intersecting
|
|
||||||
newSelection.add(filepath);
|
newSelection.add(filepath);
|
||||||
card.classList.add('selected');
|
card.classList.add('selected');
|
||||||
|
|
||||||
@@ -1825,12 +1861,43 @@ export class BulkManager {
|
|||||||
this.updateMetadataCacheFromCard(filepath, card);
|
this.updateMetadataCacheFromCard(filepath, card);
|
||||||
}
|
}
|
||||||
} else if (!this.initialSelectedModels.has(filepath)) {
|
} else if (!this.initialSelectedModels.has(filepath)) {
|
||||||
// Remove from selection if not intersecting and wasn't initially selected
|
|
||||||
newSelection.delete(filepath);
|
newSelection.delete(filepath);
|
||||||
card.classList.remove('selected');
|
card.classList.remove('selected');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Step 2: Process off-screen cards via VirtualScroller layout calculation.
|
||||||
|
// Since VirtualScroller removes off-screen DOM elements, we compute
|
||||||
|
// each card's position from its index and the VS layout parameters.
|
||||||
|
if (vs?.gridElement && vs.items && vs.columnsCount > 0) {
|
||||||
|
const gridRect = vs.gridElement.getBoundingClientRect();
|
||||||
|
// Grid origin in scroll-container content coordinates
|
||||||
|
const originX = gridRect.left + scrollX;
|
||||||
|
const originY = gridRect.top + scrollY;
|
||||||
|
|
||||||
|
for (let i = 0; i < vs.items.length; i++) {
|
||||||
|
const filepath = vs.items[i]?.file_path;
|
||||||
|
if (!filepath || visibleFilepaths.has(filepath)) continue;
|
||||||
|
|
||||||
|
const row = Math.floor(i / vs.columnsCount);
|
||||||
|
const col = i % vs.columnsCount;
|
||||||
|
|
||||||
|
const cLeft = originX + col * (vs.itemWidth + vs.columnGap);
|
||||||
|
const cTop = originY + (vs.containerPaddingTop || 0) + row * (vs.itemHeight + (vs.rowGap || 0));
|
||||||
|
const cRight = cLeft + vs.itemWidth;
|
||||||
|
const cBottom = cTop + vs.itemHeight;
|
||||||
|
|
||||||
|
const intersects = !(cRight < docLeft || cLeft > docRight ||
|
||||||
|
cBottom < docTop || cTop > docBottom);
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
newSelection.add(filepath);
|
||||||
|
} else if (!this.initialSelectedModels.has(filepath)) {
|
||||||
|
newSelection.delete(filepath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update global selection state
|
// Update global selection state
|
||||||
state.selectedModels = newSelection;
|
state.selectedModels = newSelection;
|
||||||
|
|
||||||
@@ -1849,6 +1916,9 @@ export class BulkManager {
|
|||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.mouseDownTime = 0;
|
this.mouseDownTime = 0;
|
||||||
|
|
||||||
|
// Stop any active auto-scroll
|
||||||
|
this.stopAutoScroll();
|
||||||
|
|
||||||
// Update event manager state
|
// Update event manager state
|
||||||
eventManager.setState('marqueeActive', false);
|
eventManager.setState('marqueeActive', false);
|
||||||
|
|
||||||
@@ -1874,6 +1944,79 @@ export class BulkManager {
|
|||||||
// Clear initial selection state
|
// Clear initial selection state
|
||||||
this.initialSelectedModels.clear();
|
this.initialSelectedModels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start auto-scroll loop when mouse approaches viewport edge during marquee
|
||||||
|
*/
|
||||||
|
startAutoScroll() {
|
||||||
|
if (this.autoScrollRaf) return;
|
||||||
|
this.autoScrollLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop auto-scroll loop
|
||||||
|
*/
|
||||||
|
stopAutoScroll() {
|
||||||
|
if (this.autoScrollRaf) {
|
||||||
|
cancelAnimationFrame(this.autoScrollRaf);
|
||||||
|
this.autoScrollRaf = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-scroll loop: scrolls the page when mouse is near viewport edges
|
||||||
|
* and re-evaluates marquee selection after each scroll.
|
||||||
|
*/
|
||||||
|
autoScrollLoop() {
|
||||||
|
if (!this.isMarqueeActive) {
|
||||||
|
this.autoScrollRaf = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.querySelector('.page-content');
|
||||||
|
if (!container) {
|
||||||
|
this.autoScrollRaf = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MARGIN = 30; // Px from edge to trigger scroll
|
||||||
|
const BASE_SPEED = 12; // Pixels per frame at edge boundary
|
||||||
|
const MAX_SPEED = 40; // Maximum scroll speed
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
let dx = 0;
|
||||||
|
let dy = 0;
|
||||||
|
|
||||||
|
// Vertical auto-scroll - speed increases the further the cursor is past the edge
|
||||||
|
if (this.lastClientY !== undefined) {
|
||||||
|
if (this.lastClientY < rect.top + MARGIN) {
|
||||||
|
const dist = Math.max(0, (rect.top + MARGIN) - this.lastClientY);
|
||||||
|
dy = -Math.min(BASE_SPEED + dist * 0.5, MAX_SPEED);
|
||||||
|
} else if (this.lastClientY > rect.bottom - MARGIN) {
|
||||||
|
const dist = Math.max(0, this.lastClientY - (rect.bottom - MARGIN));
|
||||||
|
dy = Math.min(BASE_SPEED + dist * 0.5, MAX_SPEED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontal auto-scroll
|
||||||
|
if (this.lastClientX !== undefined) {
|
||||||
|
if (this.lastClientX < rect.left + MARGIN) {
|
||||||
|
const dist = Math.max(0, (rect.left + MARGIN) - this.lastClientX);
|
||||||
|
dx = -Math.min(BASE_SPEED + dist * 0.5, MAX_SPEED);
|
||||||
|
} else if (this.lastClientX > rect.right - MARGIN) {
|
||||||
|
const dist = Math.max(0, this.lastClientX - (rect.right - MARGIN));
|
||||||
|
dx = Math.min(BASE_SPEED + dist * 0.5, MAX_SPEED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dx !== 0 || dy !== 0) {
|
||||||
|
container.scrollBy(dx, dy);
|
||||||
|
// Re-evaluate marquee selection with the new scroll position
|
||||||
|
this.updateMarqueeSelectionFromPosition(this.lastClientX, this.lastClientY);
|
||||||
|
this.autoScrollRaf = requestAnimationFrame(() => this.autoScrollLoop());
|
||||||
|
} else {
|
||||||
|
this.autoScrollRaf = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkManager = new BulkManager();
|
export const bulkManager = new BulkManager();
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ export class DownloadManager {
|
|||||||
const thumbnailUrl = firstImage ? firstImage.url : '/loras_static/images/no-preview.png';
|
const thumbnailUrl = firstImage ? firstImage.url : '/loras_static/images/no-preview.png';
|
||||||
|
|
||||||
// Count model-type files per version
|
// Count model-type files per version
|
||||||
const modelFiles = (version.files || []).filter(f => f.type === 'Model');
|
const modelFiles = (version.files || []).filter(f => f.type === 'Model' || f.type === 'UNet' || f.type === 'Diffusion Model');
|
||||||
const primaryFile = modelFiles.find(f => f.primary) || modelFiles[0] || {};
|
const primaryFile = modelFiles.find(f => f.primary) || modelFiles[0] || {};
|
||||||
const fileSize = version.modelSizeKB ?
|
const fileSize = version.modelSizeKB ?
|
||||||
(version.modelSizeKB / 1024).toFixed(2) :
|
(version.modelSizeKB / 1024).toFixed(2) :
|
||||||
@@ -478,7 +478,7 @@ export class DownloadManager {
|
|||||||
if (!version) return;
|
if (!version) return;
|
||||||
|
|
||||||
this.currentVersion = version;
|
this.currentVersion = version;
|
||||||
const modelFiles = (version.files || []).filter(f => f.type === 'Model');
|
const modelFiles = (version.files || []).filter(f => f.type === 'Model' || f.type === 'UNet' || f.type === 'Diffusion Model');
|
||||||
|
|
||||||
document.getElementById('versionStep').style.display = 'none';
|
document.getElementById('versionStep').style.display = 'none';
|
||||||
document.getElementById('fileSelectionStep').style.display = 'block';
|
document.getElementById('fileSelectionStep').style.display = 'block';
|
||||||
@@ -534,7 +534,7 @@ export class DownloadManager {
|
|||||||
const version = this.currentVersion;
|
const version = this.currentVersion;
|
||||||
if (!version) return;
|
if (!version) return;
|
||||||
|
|
||||||
const modelFiles = (version.files || []).filter(f => f.type === 'Model');
|
const modelFiles = (version.files || []).filter(f => f.type === 'Model' || f.type === 'UNet' || f.type === 'Diffusion Model');
|
||||||
this.selectedFile = modelFiles.find(f => f.id.toString() === selectedRadio.value);
|
this.selectedFile = modelFiles.find(f => f.id.toString() === selectedRadio.value);
|
||||||
|
|
||||||
document.getElementById('fileSelectionStep').style.display = 'none';
|
document.getElementById('fileSelectionStep').style.display = 'none';
|
||||||
@@ -954,7 +954,7 @@ export class DownloadManager {
|
|||||||
}
|
}
|
||||||
if (!this.isBatchMode) {
|
if (!this.isBatchMode) {
|
||||||
const fileParams = this.selectedFile ? {
|
const fileParams = this.selectedFile ? {
|
||||||
type: 'Model',
|
type: this.selectedFile.type || 'Model',
|
||||||
format: this.selectedFile.metadata?.format || 'SafeTensor',
|
format: this.selectedFile.metadata?.format || 'SafeTensor',
|
||||||
size: this.selectedFile.metadata?.size || 'full',
|
size: this.selectedFile.metadata?.size || 'full',
|
||||||
fp: this.selectedFile.metadata?.fp,
|
fp: this.selectedFile.metadata?.fp,
|
||||||
|
|||||||
@@ -57,9 +57,16 @@ export class DownloadManager {
|
|||||||
base_model: this.importManager.recipeData.base_model || "",
|
base_model: this.importManager.recipeData.base_model || "",
|
||||||
loras: this.importManager.recipeData.loras || [],
|
loras: this.importManager.recipeData.loras || [],
|
||||||
gen_params: this.importManager.recipeData.gen_params || {},
|
gen_params: this.importManager.recipeData.gen_params || {},
|
||||||
raw_metadata: this.importManager.recipeData.raw_metadata || {}
|
raw_metadata: this.importManager.recipeData.raw_metadata || {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Preserve preview_nsfw_level from analysis so the saved
|
||||||
|
// recipe applies the correct NSFW blur on the preview image.
|
||||||
|
const nsfwLevel = this.importManager.recipeData.preview_nsfw_level;
|
||||||
|
if (nsfwLevel !== undefined && nsfwLevel !== null) {
|
||||||
|
completeMetadata.preview_nsfw_level = nsfwLevel;
|
||||||
|
}
|
||||||
|
|
||||||
const checkpointMetadata =
|
const checkpointMetadata =
|
||||||
this.importManager.recipeData.checkpoint ||
|
this.importManager.recipeData.checkpoint ||
|
||||||
this.importManager.recipeData.model ||
|
this.importManager.recipeData.model ||
|
||||||
|
|||||||
@@ -60,7 +60,9 @@ class FakePromptServer:
|
|||||||
sent = []
|
sent = []
|
||||||
|
|
||||||
class Instance:
|
class Instance:
|
||||||
def send_sync(self, event, payload):
|
sockets: dict = {}
|
||||||
|
|
||||||
|
def send_sync(self, event, payload, sid=None):
|
||||||
FakePromptServer.sent.append((event, payload))
|
FakePromptServer.sent.append((event, payload))
|
||||||
|
|
||||||
instance = Instance()
|
instance = Instance()
|
||||||
@@ -148,7 +150,8 @@ class TestNodeRegistryHandlerSnapshots:
|
|||||||
"type": "Lora Loader (LoraManager)",
|
"type": "Lora Loader (LoraManager)",
|
||||||
"title": "Test Loader",
|
"title": "Test Loader",
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"client_id": "test-client-1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -167,7 +170,7 @@ class TestNodeRegistryHandlerSnapshots:
|
|||||||
standalone_mode=False,
|
standalone_mode=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
request = FakeRequest(json_data={"nodes": []})
|
request = FakeRequest(json_data={"nodes": [], "client_id": "test-client-1"})
|
||||||
response = await handler.register_nodes(request)
|
response = await handler.register_nodes(request)
|
||||||
payload = json.loads(response.text)
|
payload = json.loads(response.text)
|
||||||
|
|
||||||
|
|||||||
@@ -586,7 +586,9 @@ class FakePromptServer:
|
|||||||
sent = []
|
sent = []
|
||||||
|
|
||||||
class Instance:
|
class Instance:
|
||||||
def send_sync(self, event, payload):
|
sockets: dict = {}
|
||||||
|
|
||||||
|
def send_sync(self, event, payload, sid=None):
|
||||||
FakePromptServer.sent.append((event, payload))
|
FakePromptServer.sent.append((event, payload))
|
||||||
|
|
||||||
instance = Instance()
|
instance = Instance()
|
||||||
@@ -601,7 +603,12 @@ async def test_register_nodes_requires_graph_id():
|
|||||||
standalone_mode=False,
|
standalone_mode=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
request = FakeRequest(json_data={"nodes": [{"node_id": 1}]})
|
request = FakeRequest(
|
||||||
|
json_data={
|
||||||
|
"nodes": [{"node_id": 1}],
|
||||||
|
"client_id": "test-client-1",
|
||||||
|
}
|
||||||
|
)
|
||||||
response = await handler.register_nodes(request)
|
response = await handler.register_nodes(request)
|
||||||
payload = json.loads(response.text)
|
payload = json.loads(response.text)
|
||||||
|
|
||||||
@@ -629,7 +636,8 @@ async def test_register_nodes_stores_graph_identifier():
|
|||||||
"type": "Lora Loader (LoraManager)",
|
"type": "Lora Loader (LoraManager)",
|
||||||
"title": "Loader",
|
"title": "Loader",
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"client_id": "test-client-1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -638,7 +646,7 @@ async def test_register_nodes_stores_graph_identifier():
|
|||||||
|
|
||||||
assert payload["success"] is True
|
assert payload["success"] is True
|
||||||
|
|
||||||
registry = await node_registry.get_registry()
|
registry = await node_registry.get_merged_registry()
|
||||||
assert registry["node_count"] == 1
|
assert registry["node_count"] == 1
|
||||||
stored_node = next(iter(registry["nodes"].values()))
|
stored_node = next(iter(registry["nodes"].values()))
|
||||||
assert stored_node["graph_id"] == "graph-123"
|
assert stored_node["graph_id"] == "graph-123"
|
||||||
@@ -664,7 +672,8 @@ async def test_register_nodes_defaults_graph_name_to_none():
|
|||||||
"type": "Lora Loader (LoraManager)",
|
"type": "Lora Loader (LoraManager)",
|
||||||
"title": "Root Loader",
|
"title": "Root Loader",
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"client_id": "test-client-1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -673,7 +682,7 @@ async def test_register_nodes_defaults_graph_name_to_none():
|
|||||||
|
|
||||||
assert payload["success"] is True
|
assert payload["success"] is True
|
||||||
|
|
||||||
registry = await node_registry.get_registry()
|
registry = await node_registry.get_merged_registry()
|
||||||
stored_node = next(iter(registry["nodes"].values()))
|
stored_node = next(iter(registry["nodes"].values()))
|
||||||
assert stored_node["graph_name"] is None
|
assert stored_node["graph_name"] is None
|
||||||
|
|
||||||
@@ -700,7 +709,8 @@ async def test_register_nodes_includes_capabilities():
|
|||||||
"widget_names": ["ckpt_name", "", 42],
|
"widget_names": ["ckpt_name", "", 42],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"client_id": "test-client-1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -709,7 +719,7 @@ async def test_register_nodes_includes_capabilities():
|
|||||||
|
|
||||||
assert payload["success"] is True
|
assert payload["success"] is True
|
||||||
|
|
||||||
registry = await node_registry.get_registry()
|
registry = await node_registry.get_merged_registry()
|
||||||
stored_node = next(iter(registry["nodes"].values()))
|
stored_node = next(iter(registry["nodes"].values()))
|
||||||
assert stored_node["capabilities"] == {
|
assert stored_node["capabilities"] == {
|
||||||
"supports_lora": False,
|
"supports_lora": False,
|
||||||
@@ -724,7 +734,9 @@ async def test_update_node_widget_sends_payload():
|
|||||||
|
|
||||||
class RecordingPromptServer:
|
class RecordingPromptServer:
|
||||||
class Instance:
|
class Instance:
|
||||||
def send_sync(self, event, payload):
|
sockets: dict = {}
|
||||||
|
|
||||||
|
def send_sync(self, event, payload, sid=None):
|
||||||
send_calls.append((event, payload))
|
send_calls.append((event, payload))
|
||||||
|
|
||||||
instance = Instance()
|
instance = Instance()
|
||||||
@@ -768,7 +780,9 @@ async def test_update_lora_code_includes_graph_identifier():
|
|||||||
|
|
||||||
class RecordingPromptServer:
|
class RecordingPromptServer:
|
||||||
class Instance:
|
class Instance:
|
||||||
def send_sync(self, event, payload):
|
sockets: dict = {}
|
||||||
|
|
||||||
|
def send_sync(self, event, payload, sid=None):
|
||||||
send_calls.append((event, payload))
|
send_calls.append((event, payload))
|
||||||
|
|
||||||
instance = Instance()
|
instance = Instance()
|
||||||
|
|||||||
@@ -568,7 +568,7 @@ async def test_get_image_info_prefers_red_host_for_red_source(monkeypatch, downl
|
|||||||
|
|
||||||
assert result == {"id": 124950237, "name": "target"}
|
assert result == {"id": 124950237, "name": "target"}
|
||||||
assert requested_urls == [
|
assert requested_urls == [
|
||||||
"https://civitai.red/api/v1/images?imageId=124950237&nsfw=X"
|
"https://civitai.red/api/v1/images?imageId=124950237&nsfw=X&withMeta=true"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ async def test_get_image_info_uses_red_host_even_for_red_source(monkeypatch, dow
|
|||||||
|
|
||||||
assert result == {"id": 124950237, "name": "target"}
|
assert result == {"id": 124950237, "name": "target"}
|
||||||
assert requested_urls == [
|
assert requested_urls == [
|
||||||
"https://civitai.red/api/v1/images?imageId=124950237&nsfw=X",
|
"https://civitai.red/api/v1/images?imageId=124950237&nsfw=X&withMeta=true",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -610,7 +610,7 @@ async def test_get_image_info_does_not_fall_back_after_request_failure(monkeypat
|
|||||||
|
|
||||||
assert result is None
|
assert result is None
|
||||||
assert requested_urls == [
|
assert requested_urls == [
|
||||||
"https://civitai.red/api/v1/images?imageId=124950237&nsfw=X",
|
"https://civitai.red/api/v1/images?imageId=124950237&nsfw=X&withMeta=true",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,10 @@ app.registerExtension({
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ nodes: workflowNodes }),
|
body: JSON.stringify({
|
||||||
|
nodes: workflowNodes,
|
||||||
|
client_id: api.clientId ?? api.initialClientId ?? "",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user