mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-07 00:46:44 -03:00
Compare commits
5 Commits
14cb7fec47
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8e5fe1247 | ||
|
|
3e9210394a | ||
|
|
4dd2c0526f | ||
|
|
9bdb337962 | ||
|
|
f93baf5fc0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ model_cache/
|
|||||||
# agent
|
# agent
|
||||||
.opencode/
|
.opencode/
|
||||||
.claude/
|
.claude/
|
||||||
|
.codex
|
||||||
|
|
||||||
# Vue widgets development cache (but keep build output)
|
# Vue widgets development cache (but keep build output)
|
||||||
vue-widgets/node_modules/
|
vue-widgets/node_modules/
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
"Insomnia Art Designs",
|
"Insomnia Art Designs",
|
||||||
"megakirbs",
|
"megakirbs",
|
||||||
"Brennok",
|
"Brennok",
|
||||||
"wackop",
|
|
||||||
"2018cfh",
|
"2018cfh",
|
||||||
|
"W+K+White",
|
||||||
|
"wackop",
|
||||||
"Takkan",
|
"Takkan",
|
||||||
"stone9k",
|
"Carl G.",
|
||||||
"$MetaSamsara",
|
"$MetaSamsara",
|
||||||
"itismyelement",
|
"itismyelement",
|
||||||
"onesecondinosaur",
|
"onesecondinosaur",
|
||||||
"Carl G.",
|
"stone9k",
|
||||||
"Rosenthal",
|
"Rosenthal",
|
||||||
"Francisco Tatis",
|
"Francisco Tatis",
|
||||||
"Tobi_Swagg",
|
|
||||||
"Andrew Wilson",
|
"Andrew Wilson",
|
||||||
"Greybush",
|
"Greybush",
|
||||||
"Gooohokrbe",
|
"Gooohokrbe",
|
||||||
@@ -29,18 +29,16 @@
|
|||||||
"VantAI",
|
"VantAI",
|
||||||
"runte3221",
|
"runte3221",
|
||||||
"FreelancerZ",
|
"FreelancerZ",
|
||||||
"Julian V",
|
|
||||||
"Edgar Tejeda",
|
"Edgar Tejeda",
|
||||||
"Birdy",
|
|
||||||
"Liam MacDougal",
|
"Liam MacDougal",
|
||||||
"Fraser Cross",
|
"Fraser Cross",
|
||||||
"Polymorphic Indeterminate",
|
"Polymorphic Indeterminate",
|
||||||
|
"Birdy",
|
||||||
"Marc Whiffen",
|
"Marc Whiffen",
|
||||||
"Kiba",
|
|
||||||
"Jorge Hussni",
|
"Jorge Hussni",
|
||||||
"Reno Lam",
|
"Kiba",
|
||||||
"Skalabananen",
|
"Skalabananen",
|
||||||
"esthe",
|
"Reno Lam",
|
||||||
"sig",
|
"sig",
|
||||||
"Christian Byrne",
|
"Christian Byrne",
|
||||||
"DM",
|
"DM",
|
||||||
@@ -49,24 +47,22 @@
|
|||||||
"J\\B/ 8r0wns0n",
|
"J\\B/ 8r0wns0n",
|
||||||
"Snaggwort",
|
"Snaggwort",
|
||||||
"Arlecchino Shion",
|
"Arlecchino Shion",
|
||||||
|
"Charles Blakemore",
|
||||||
|
"Rob Williams",
|
||||||
"ClockDaemon",
|
"ClockDaemon",
|
||||||
"KD",
|
"KD",
|
||||||
"Omnidex",
|
"Omnidex",
|
||||||
"Tyler Trebuchon",
|
"Tyler Trebuchon",
|
||||||
"Release Cabrakan",
|
"Release Cabrakan",
|
||||||
"confiscated Zyra",
|
"Tobi_Swagg",
|
||||||
"SG",
|
"SG",
|
||||||
"carozzz",
|
"carozzz",
|
||||||
"James Dooley",
|
"James Dooley",
|
||||||
"zenbound",
|
"zenbound",
|
||||||
"Buzzard",
|
"Buzzard",
|
||||||
"jmack",
|
"jmack",
|
||||||
"Adam Shaw",
|
|
||||||
"Tee Gee",
|
|
||||||
"Mark Corneglio",
|
"Mark Corneglio",
|
||||||
"SarcasticHashtag",
|
"SarcasticHashtag",
|
||||||
"Anthony Rizzo",
|
|
||||||
"tarek helmi",
|
|
||||||
"Cosmosis",
|
"Cosmosis",
|
||||||
"iamresist",
|
"iamresist",
|
||||||
"RedrockVP",
|
"RedrockVP",
|
||||||
@@ -75,45 +71,34 @@
|
|||||||
"James Todd",
|
"James Todd",
|
||||||
"Steven Pfeiffer",
|
"Steven Pfeiffer",
|
||||||
"Tim",
|
"Tim",
|
||||||
"Timmy",
|
|
||||||
"Johnny",
|
|
||||||
"Lisster",
|
"Lisster",
|
||||||
"Michael Wong",
|
"Michael Wong",
|
||||||
"Illrigger",
|
"Illrigger",
|
||||||
"whudunit",
|
|
||||||
"Tom Corrigan",
|
"Tom Corrigan",
|
||||||
"JackieWang",
|
"JackieWang",
|
||||||
"fnkylove",
|
"fnkylove",
|
||||||
|
"Julian V",
|
||||||
"Steven Owens",
|
"Steven Owens",
|
||||||
"Yushio",
|
"Yushio",
|
||||||
"Vik71it",
|
"Vik71it",
|
||||||
"lh qwe",
|
|
||||||
"Echo",
|
"Echo",
|
||||||
"Lilleman",
|
"Lilleman",
|
||||||
"Robert Stacey",
|
"Robert Stacey",
|
||||||
"PM",
|
"PM",
|
||||||
"Todd Keck",
|
"Todd Keck",
|
||||||
"Briton Heilbrun",
|
|
||||||
"Mozzel",
|
"Mozzel",
|
||||||
"Gingko Biloba",
|
"Gingko Biloba",
|
||||||
"Felipe dos Santos",
|
|
||||||
"Penfore",
|
|
||||||
"BadassArabianMofo",
|
|
||||||
"Sterilized",
|
"Sterilized",
|
||||||
|
"BadassArabianMofo",
|
||||||
"Pascal Dahle",
|
"Pascal Dahle",
|
||||||
"Markus",
|
|
||||||
"quarz",
|
"quarz",
|
||||||
"Greg",
|
"Greg",
|
||||||
"Douglas Gaspar",
|
"Penfore",
|
||||||
"JSST",
|
"JSST",
|
||||||
"AlexDuKaNa",
|
"esthe",
|
||||||
"George",
|
|
||||||
"lmsupporter",
|
"lmsupporter",
|
||||||
"Phil",
|
|
||||||
"Charles Blakemore",
|
|
||||||
"IamAyam",
|
"IamAyam",
|
||||||
"wfpearl",
|
"wfpearl",
|
||||||
"Rob Williams",
|
|
||||||
"Baekdoosixt",
|
"Baekdoosixt",
|
||||||
"Jonathan Ross",
|
"Jonathan Ross",
|
||||||
"Jack B Nimble",
|
"Jack B Nimble",
|
||||||
@@ -125,127 +110,118 @@
|
|||||||
"contrite831",
|
"contrite831",
|
||||||
"Alex",
|
"Alex",
|
||||||
"bh",
|
"bh",
|
||||||
|
"confiscated Zyra",
|
||||||
"Marlon Daniels",
|
"Marlon Daniels",
|
||||||
"Starkselle",
|
"Starkselle",
|
||||||
"Aaron Bleuer",
|
"Aaron Bleuer",
|
||||||
"LacesOut!",
|
"LacesOut!",
|
||||||
"Graham Colehour",
|
"greebles",
|
||||||
|
"Adam Shaw",
|
||||||
|
"Tee Gee",
|
||||||
|
"Anthony Rizzo",
|
||||||
|
"tarek helmi",
|
||||||
"M Postkasse",
|
"M Postkasse",
|
||||||
"Tomohiro Baba",
|
|
||||||
"David Ortega",
|
|
||||||
"ASLPro3D",
|
"ASLPro3D",
|
||||||
"Jacob Hoehler",
|
"Jacob Hoehler",
|
||||||
"FinalyFree",
|
"FinalyFree",
|
||||||
"Weasyl",
|
"Weasyl",
|
||||||
"Lex Song",
|
"Timmy",
|
||||||
|
"Johnny",
|
||||||
"Cory Paza",
|
"Cory Paza",
|
||||||
"Tak",
|
"Tak",
|
||||||
"Gonzalo Andre Allendes Lopez",
|
"Gonzalo Andre Allendes Lopez",
|
||||||
"Zach Gonser",
|
"Zach Gonser",
|
||||||
"Big Red",
|
"Big Red",
|
||||||
"Jimmy Ledbetter",
|
"whudunit",
|
||||||
"Luc Job",
|
"Luc Job",
|
||||||
"dl0901dm",
|
"dl0901dm",
|
||||||
"Philip Hempel",
|
"Philip Hempel",
|
||||||
"corde",
|
"corde",
|
||||||
"Nick Walker",
|
"Nick Walker",
|
||||||
|
"lh qwe",
|
||||||
"Bishoujoker",
|
"Bishoujoker",
|
||||||
"conner",
|
"conner",
|
||||||
"aai",
|
"aai",
|
||||||
"Yaboi",
|
"Briton Heilbrun",
|
||||||
"Tori",
|
"Tori",
|
||||||
"wildnut",
|
"wildnut",
|
||||||
"Princess Bright Eyes",
|
"Princess Bright Eyes",
|
||||||
"Damon Cunliffe",
|
|
||||||
"CryptoTraderJK",
|
|
||||||
"Davaitamin",
|
|
||||||
"AbstractAss",
|
"AbstractAss",
|
||||||
|
"Felipe dos Santos",
|
||||||
"ViperC",
|
"ViperC",
|
||||||
|
"jean jahren",
|
||||||
"Aleksander Wujczyk",
|
"Aleksander Wujczyk",
|
||||||
"AM Kuro",
|
"AM Kuro",
|
||||||
"jean jahren",
|
"Markus",
|
||||||
"Ran C",
|
|
||||||
"tedcor",
|
|
||||||
"S Sang",
|
"S Sang",
|
||||||
"MagnaInsomnia",
|
|
||||||
"Akira_HentAI",
|
|
||||||
"Karl P.",
|
"Karl P.",
|
||||||
|
"Akira_HentAI",
|
||||||
|
"MagnaInsomnia",
|
||||||
"Gordon Cole",
|
"Gordon Cole",
|
||||||
"yuxz69",
|
"yuxz69",
|
||||||
"MadSpin",
|
"Douglas Gaspar",
|
||||||
|
"AlexDuKaNa",
|
||||||
|
"George",
|
||||||
"andrew.tappan",
|
"andrew.tappan",
|
||||||
"dw",
|
"dw",
|
||||||
"N/A",
|
"N/A",
|
||||||
"The Spawn",
|
"The Spawn",
|
||||||
|
"Phil",
|
||||||
"graysock",
|
"graysock",
|
||||||
"Greenmoustache",
|
"Greenmoustache",
|
||||||
"zounic",
|
"zounic",
|
||||||
"Gamalonia",
|
|
||||||
"fancypants",
|
"fancypants",
|
||||||
"Vir",
|
|
||||||
"Joboshy",
|
|
||||||
"Digital",
|
"Digital",
|
||||||
"JaxMax",
|
"JaxMax",
|
||||||
"takyamtom",
|
"takyamtom",
|
||||||
"Bohemian Corporal",
|
|
||||||
"奚明 刘",
|
"奚明 刘",
|
||||||
"Dan",
|
|
||||||
"Seth Christensen",
|
|
||||||
"Jwk0205",
|
"Jwk0205",
|
||||||
"Bro Xie",
|
"Bro Xie",
|
||||||
"Draven T",
|
"준희 김",
|
||||||
"yer fey",
|
|
||||||
"batblue",
|
"batblue",
|
||||||
"carey6409",
|
"carey6409",
|
||||||
"Olive",
|
"Olive",
|
||||||
"太郎 ゲーム",
|
"太郎 ゲーム",
|
||||||
"Some Guy Named Barry",
|
"Some Guy Named Barry",
|
||||||
"jinxedx",
|
|
||||||
"Aquatic Coffee",
|
|
||||||
"Max Marklund",
|
"Max Marklund",
|
||||||
|
"Tomohiro Baba",
|
||||||
|
"David Ortega",
|
||||||
"AELOX",
|
"AELOX",
|
||||||
"Dankin",
|
|
||||||
"Nicfit23",
|
"Nicfit23",
|
||||||
"Noora",
|
"Noora",
|
||||||
"ethanfel",
|
|
||||||
"wamekukyouzin",
|
"wamekukyouzin",
|
||||||
"drum matthieu",
|
"drum matthieu",
|
||||||
"Dogmaster",
|
"Dogmaster",
|
||||||
"Matt Wenzel",
|
"Matt Wenzel",
|
||||||
"Mattssn",
|
"Mattssn",
|
||||||
"Frank Nitty",
|
"Lex Song",
|
||||||
"John Saveas",
|
"John Saveas",
|
||||||
"Focuschannel",
|
|
||||||
"Christopher Michel",
|
"Christopher Michel",
|
||||||
"Serge Bekenkamp",
|
"Serge Bekenkamp",
|
||||||
|
"Jimmy Ledbetter",
|
||||||
"LeoZero",
|
"LeoZero",
|
||||||
"Antonio Pontes",
|
"Antonio Pontes",
|
||||||
"ApathyJones",
|
"ApathyJones",
|
||||||
"nahinahi9",
|
"nahinahi9",
|
||||||
"Anthony Faxlandez",
|
|
||||||
"Dustin Chen",
|
"Dustin Chen",
|
||||||
"dan",
|
"dan",
|
||||||
"Blackfish95",
|
"Yaboi",
|
||||||
"Mouthlessman",
|
"Mouthlessman",
|
||||||
"Steam Steam",
|
"Steam Steam",
|
||||||
"Paul Kroll",
|
"Damon Cunliffe",
|
||||||
|
"CryptoTraderJK",
|
||||||
|
"Davaitamin",
|
||||||
"otaku fra",
|
"otaku fra",
|
||||||
"semicolon drainpipe",
|
"Ran C",
|
||||||
"Thesharingbrother",
|
"tedcor",
|
||||||
"Fotek Design",
|
"Fotek Design",
|
||||||
"Bas Imagineer",
|
|
||||||
"Pat Hen",
|
|
||||||
"ResidentDeviant",
|
|
||||||
"Adam Taylor",
|
"Adam Taylor",
|
||||||
"JC",
|
|
||||||
"Weird_With_A_Beard",
|
"Weird_With_A_Beard",
|
||||||
"Prompt Pirate",
|
"MadSpin",
|
||||||
"Pozadine1",
|
"Pozadine1",
|
||||||
"uwutismxd",
|
|
||||||
"Qarob",
|
"Qarob",
|
||||||
"AIGooner",
|
"AIGooner",
|
||||||
"inbijiburu",
|
"inbijiburu",
|
||||||
"decoy",
|
|
||||||
"Luc",
|
"Luc",
|
||||||
"ProtonPrince",
|
"ProtonPrince",
|
||||||
"DiffDuck",
|
"DiffDuck",
|
||||||
@@ -258,53 +234,54 @@
|
|||||||
"thesoftwaredruid",
|
"thesoftwaredruid",
|
||||||
"wundershark",
|
"wundershark",
|
||||||
"mr_dinosaur",
|
"mr_dinosaur",
|
||||||
"Tyrswood",
|
|
||||||
"linnfrey",
|
"linnfrey",
|
||||||
"zenobeus",
|
"Gamalonia",
|
||||||
"Jackthemind",
|
"Vir",
|
||||||
"Stryker",
|
|
||||||
"Pkrsky",
|
"Pkrsky",
|
||||||
"raf8osz",
|
"Joboshy",
|
||||||
"blikkies",
|
"Bohemian Corporal",
|
||||||
|
"Dan",
|
||||||
"Josef Lanzl",
|
"Josef Lanzl",
|
||||||
|
"Seth Christensen",
|
||||||
"Griffin Dahlberg",
|
"Griffin Dahlberg",
|
||||||
"준희 김",
|
"Draven T",
|
||||||
|
"yer fey",
|
||||||
"Error_Rule34_Not_found",
|
"Error_Rule34_Not_found",
|
||||||
"Gerald Welly",
|
"Gerald Welly",
|
||||||
"Shock Shockor",
|
|
||||||
"Roslynd",
|
"Roslynd",
|
||||||
"Geolog",
|
"Geolog",
|
||||||
"Goldwaters",
|
"jinxedx",
|
||||||
"Neco28",
|
"Neco28",
|
||||||
"Zude",
|
"Aquatic Coffee",
|
||||||
|
"Dankin",
|
||||||
|
"ethanfel",
|
||||||
"Cristian Vazquez",
|
"Cristian Vazquez",
|
||||||
"Kyler",
|
"Frank Nitty",
|
||||||
"Magic Noob",
|
"Magic Noob",
|
||||||
"aRtFuL_DodGeR",
|
"Focuschannel",
|
||||||
"X",
|
|
||||||
"DougPeterson",
|
"DougPeterson",
|
||||||
"Jeff",
|
"Jeff",
|
||||||
"Bruce",
|
"Bruce",
|
||||||
"CrimsonDX",
|
|
||||||
"Kevin John Duck",
|
"Kevin John Duck",
|
||||||
|
"Anthony Faxlandez",
|
||||||
"Kevin Christopher",
|
"Kevin Christopher",
|
||||||
"Ouro Boros",
|
"Ouro Boros",
|
||||||
"DarkSunset",
|
"Blackfish95",
|
||||||
"dd",
|
"dd",
|
||||||
"Billy Gladky",
|
"Paul Kroll",
|
||||||
"Probis",
|
"MiraiKuriyamaSy",
|
||||||
"shrshpp",
|
"semicolon drainpipe",
|
||||||
"Dušan Ryban",
|
"Thesharingbrother",
|
||||||
"ItsGeneralButtNaked",
|
"Bas Imagineer",
|
||||||
"sjon kreutz",
|
"Pat Hen",
|
||||||
"Nimess",
|
|
||||||
"John Statham",
|
"John Statham",
|
||||||
"Youguang",
|
"ResidentDeviant",
|
||||||
"Nihongasuki",
|
"Nihongasuki",
|
||||||
"Metryman55",
|
"JC",
|
||||||
"andrewzpong",
|
"Prompt Pirate",
|
||||||
"FrxzenSnxw",
|
"uwutismxd",
|
||||||
"BossGame",
|
"decoy",
|
||||||
|
"Tyrswood",
|
||||||
"Ray Wing",
|
"Ray Wing",
|
||||||
"Ranzitho",
|
"Ranzitho",
|
||||||
"Gus",
|
"Gus",
|
||||||
@@ -316,7 +293,6 @@
|
|||||||
"WRL_SPR",
|
"WRL_SPR",
|
||||||
"capn",
|
"capn",
|
||||||
"Joseph",
|
"Joseph",
|
||||||
"lrdchs",
|
|
||||||
"Mirko Katzula",
|
"Mirko Katzula",
|
||||||
"dan",
|
"dan",
|
||||||
"Piccio08",
|
"Piccio08",
|
||||||
@@ -326,51 +302,135 @@
|
|||||||
"Moon Knight",
|
"Moon Knight",
|
||||||
"몽타주",
|
"몽타주",
|
||||||
"Kland",
|
"Kland",
|
||||||
"Hailshem",
|
"zenobeus",
|
||||||
|
"Jackthemind",
|
||||||
"ryoma",
|
"ryoma",
|
||||||
"John Martin",
|
"Stryker",
|
||||||
|
"raf8osz",
|
||||||
|
"ElitaSSJ4",
|
||||||
|
"blikkies",
|
||||||
"Chris",
|
"Chris",
|
||||||
"Brian M",
|
"Brian M",
|
||||||
"Nerezza",
|
"Nerezza",
|
||||||
"sanborondon",
|
"sanborondon",
|
||||||
"moranqianlong",
|
|
||||||
"Taylor Funk",
|
"Taylor Funk",
|
||||||
"aezin",
|
"aezin",
|
||||||
"Thought2Form",
|
"Thought2Form",
|
||||||
"jcay015",
|
"jcay015",
|
||||||
"Kevin Picco",
|
"Kevin Picco",
|
||||||
"Erik Lopez",
|
"Erik Lopez",
|
||||||
|
"Shock Shockor",
|
||||||
"Mateo Curić",
|
"Mateo Curić",
|
||||||
"Haru Yotu",
|
"Goldwaters",
|
||||||
|
"Zude",
|
||||||
"Eris3D",
|
"Eris3D",
|
||||||
"m",
|
"m",
|
||||||
"Pierce McBride",
|
"Pierce McBride",
|
||||||
"Joshua Gray",
|
"Joshua Gray",
|
||||||
|
"Kyler",
|
||||||
"Mikko Hemilä",
|
"Mikko Hemilä",
|
||||||
"Matura Arbeit",
|
"aRtFuL_DodGeR",
|
||||||
"Jamie Ogletree",
|
"Jamie Ogletree",
|
||||||
"TBitz33",
|
|
||||||
"Emil Bernhoff",
|
|
||||||
"a _",
|
"a _",
|
||||||
"SendingRavens",
|
|
||||||
"James Coleman",
|
"James Coleman",
|
||||||
|
"CrimsonDX",
|
||||||
"Martial",
|
"Martial",
|
||||||
"battu",
|
"battu",
|
||||||
"Emil Andersson",
|
"Emil Andersson",
|
||||||
"Chad Idk",
|
"Chad Idk",
|
||||||
"Michael Docherty",
|
"DarkSunset",
|
||||||
|
"Billy Gladky",
|
||||||
"Yuji Kaneko",
|
"Yuji Kaneko",
|
||||||
|
"Probis",
|
||||||
|
"Dušan Ryban",
|
||||||
|
"ItsGeneralButtNaked",
|
||||||
|
"Jordan Shaw",
|
||||||
|
"Rops Alot",
|
||||||
|
"Sam",
|
||||||
|
"sjon kreutz",
|
||||||
|
"Nimess",
|
||||||
|
"SRDB",
|
||||||
|
"Ace Ventura",
|
||||||
|
"g unit",
|
||||||
|
"Youguang",
|
||||||
|
"Metryman55",
|
||||||
|
"andrewzpong",
|
||||||
|
"FrxzenSnxw",
|
||||||
|
"BossGame",
|
||||||
|
"lrdchs",
|
||||||
|
"momokai",
|
||||||
|
"Hailshem",
|
||||||
|
"kudari",
|
||||||
|
"Naomi Hale Danchi",
|
||||||
|
"dc7431",
|
||||||
|
"ken",
|
||||||
|
"Inversity",
|
||||||
|
"AIVORY3D",
|
||||||
|
"epicgamer0020690",
|
||||||
|
"Joshua Porrata",
|
||||||
|
"keemun",
|
||||||
|
"SuBu",
|
||||||
|
"RedPIXel",
|
||||||
|
"Kevinj",
|
||||||
|
"Wind",
|
||||||
|
"Nexus",
|
||||||
|
"Ramneek“Guy”Ashok",
|
||||||
|
"squid_actually",
|
||||||
|
"Nat_20",
|
||||||
|
"Edward Weeks",
|
||||||
|
"kyoumei",
|
||||||
|
"RadStorm04",
|
||||||
|
"JohnDoe42054",
|
||||||
|
"BillyHill",
|
||||||
|
"emyth",
|
||||||
|
"chriphost",
|
||||||
|
"KitKatM",
|
||||||
|
"socrasteeze",
|
||||||
|
"ResidentDeviant",
|
||||||
|
"gzmzmvp",
|
||||||
|
"Welkor",
|
||||||
|
"John Martin",
|
||||||
|
"Richard",
|
||||||
|
"Andrew",
|
||||||
|
"Robert Wegemund",
|
||||||
|
"Littlehuggy",
|
||||||
|
"moranqianlong",
|
||||||
|
"Gregory Kozhemiak",
|
||||||
|
"mrjuan",
|
||||||
|
"Brian Buie",
|
||||||
|
"Sadlip",
|
||||||
|
"Haru Yotu",
|
||||||
|
"Eric Whitney",
|
||||||
|
"Joey Callahan",
|
||||||
|
"Ivan Tadic",
|
||||||
|
"Mike Simone",
|
||||||
|
"Morgandel",
|
||||||
|
"Kyron Mahan",
|
||||||
|
"Matura Arbeit",
|
||||||
|
"Noah",
|
||||||
|
"Jacob McDaniel",
|
||||||
|
"X",
|
||||||
|
"Sloan Steddy",
|
||||||
|
"TBitz33",
|
||||||
|
"Anonym dkjglfleeoeldldldlkf",
|
||||||
|
"Temikus",
|
||||||
|
"Artokun",
|
||||||
|
"Michael Taylor",
|
||||||
|
"SendingRavens",
|
||||||
|
"Derek Baker",
|
||||||
|
"Michael Anthony Scott",
|
||||||
|
"Atilla Berke Pekduyar",
|
||||||
|
"Michael Docherty",
|
||||||
|
"Nathan",
|
||||||
|
"Decx _",
|
||||||
|
"Paul Hartsuyker",
|
||||||
"elitassj",
|
"elitassj",
|
||||||
"Jacob Winter",
|
"Jacob Winter",
|
||||||
"Jordan Shaw",
|
"Distortik",
|
||||||
"Sam",
|
|
||||||
"Rops Alot",
|
|
||||||
"SRDB",
|
|
||||||
"g unit",
|
|
||||||
"Ace Ventura",
|
|
||||||
"David",
|
"David",
|
||||||
"Meilo",
|
"Meilo",
|
||||||
"Pen Bouryoung",
|
"Pen Bouryoung",
|
||||||
|
"四糸凜音",
|
||||||
"shinonomeiro",
|
"shinonomeiro",
|
||||||
"Snille",
|
"Snille",
|
||||||
"MaartenAlbers",
|
"MaartenAlbers",
|
||||||
@@ -378,101 +438,104 @@
|
|||||||
"xybrightsummer",
|
"xybrightsummer",
|
||||||
"jreedatchison",
|
"jreedatchison",
|
||||||
"PhilW",
|
"PhilW",
|
||||||
"momokai",
|
"Tree Tagger",
|
||||||
"Janik",
|
"Janik",
|
||||||
"kudari",
|
|
||||||
"Naomi Hale Danchi",
|
|
||||||
"dc7431",
|
|
||||||
"ken",
|
|
||||||
"Inversity",
|
|
||||||
"Crocket",
|
"Crocket",
|
||||||
"AIVORY3D",
|
|
||||||
"epicgamer0020690",
|
|
||||||
"Joshua Porrata",
|
|
||||||
"Cruel",
|
"Cruel",
|
||||||
"keemun",
|
|
||||||
"SuBu",
|
|
||||||
"RedPIXel",
|
|
||||||
"MRBlack",
|
"MRBlack",
|
||||||
"Kevinj",
|
|
||||||
"Wind",
|
|
||||||
"Nexus",
|
|
||||||
"Mitchell Robson",
|
"Mitchell Robson",
|
||||||
"Ramneek“Guy”Ashok",
|
|
||||||
"squid_actually",
|
|
||||||
"Nat_20",
|
|
||||||
"Kiyoe",
|
"Kiyoe",
|
||||||
"Edward Weeks",
|
|
||||||
"kyoumei",
|
|
||||||
"RadStorm04",
|
|
||||||
"JohnDoe42054",
|
|
||||||
"BillyHill",
|
|
||||||
"humptynutz",
|
"humptynutz",
|
||||||
"emyth",
|
|
||||||
"michael.isaza",
|
"michael.isaza",
|
||||||
"Kalnei",
|
"Kalnei",
|
||||||
"chriphost",
|
"Whitepinetrader",
|
||||||
"KitKatM",
|
"OrganicArtifact",
|
||||||
"socrasteeze",
|
|
||||||
"ResidentDeviant",
|
|
||||||
"Scott",
|
"Scott",
|
||||||
"gzmzmvp",
|
"MudkipMedkitz",
|
||||||
"Welkor",
|
"deanbrian",
|
||||||
|
"POPPIN",
|
||||||
|
"Alex Wortman",
|
||||||
|
"Cody",
|
||||||
|
"Raku",
|
||||||
|
"smart.edge5178",
|
||||||
|
"emadsultan",
|
||||||
|
"InformedViewz",
|
||||||
|
"CHKeeho80",
|
||||||
|
"Bubbafett",
|
||||||
|
"leaf",
|
||||||
|
"Menard",
|
||||||
|
"Skyfire83",
|
||||||
|
"Adam Rinehart",
|
||||||
|
"D",
|
||||||
|
"Pitpe11",
|
||||||
|
"TheD1rtyD03",
|
||||||
|
"moonpetal",
|
||||||
|
"SomeDude",
|
||||||
|
"g9p0o",
|
||||||
|
"nanana",
|
||||||
|
"TheHolySheep",
|
||||||
|
"Monte Won",
|
||||||
|
"SpringBootisTrash",
|
||||||
|
"carsten",
|
||||||
|
"ikok",
|
||||||
|
"Buecyb99",
|
||||||
|
"4IXplr0r3r",
|
||||||
|
"dfklsjfkljslfjd",
|
||||||
"hayden",
|
"hayden",
|
||||||
"Richard",
|
|
||||||
"ahoystan",
|
"ahoystan",
|
||||||
"Leland Saunders",
|
"Leland Saunders",
|
||||||
"Andrew",
|
"Wolfe7D1",
|
||||||
|
"Ink Temptation",
|
||||||
"Bob Barker",
|
"Bob Barker",
|
||||||
"Robert Wegemund",
|
"edk",
|
||||||
"Littlehuggy",
|
"Kalli Core",
|
||||||
"Gregory Kozhemiak",
|
|
||||||
"mrjuan",
|
|
||||||
"Aeternyx",
|
"Aeternyx",
|
||||||
"Brian Buie",
|
"elleshar666",
|
||||||
"YOU SINWOO",
|
"YOU SINWOO",
|
||||||
"Sadlip",
|
|
||||||
"ja s",
|
"ja s",
|
||||||
"Eric Whitney",
|
|
||||||
"Doug Mason",
|
"Doug Mason",
|
||||||
"Joey Callahan",
|
"Kauffy",
|
||||||
"Ivan Tadic",
|
|
||||||
"y2Rxy7FdXzWo",
|
|
||||||
"Jeremy Townsend",
|
"Jeremy Townsend",
|
||||||
"Mike Simone",
|
"EpicElric",
|
||||||
"Sean voets",
|
"Sean voets",
|
||||||
"Owen Gwosdz",
|
"Owen Gwosdz",
|
||||||
"Morgandel",
|
"John J Linehan",
|
||||||
|
"Elliot E",
|
||||||
"Thomas Wanner",
|
"Thomas Wanner",
|
||||||
"Kyron Mahan",
|
|
||||||
"Theerat Jiramate",
|
"Theerat Jiramate",
|
||||||
"Noah",
|
"Edward Kennedy",
|
||||||
"Jacob McDaniel",
|
"Justin Blaylock",
|
||||||
|
"Devil Lude",
|
||||||
|
"Nick Kage",
|
||||||
"kevin stoddard",
|
"kevin stoddard",
|
||||||
"Sloan Steddy",
|
|
||||||
"Jack Dole",
|
"Jack Dole",
|
||||||
|
"Vane Holzer",
|
||||||
|
"psytrax",
|
||||||
"Ezokewn",
|
"Ezokewn",
|
||||||
"Temikus",
|
"hexxish",
|
||||||
"Artokun",
|
"CptNeo",
|
||||||
"Michael Taylor",
|
"notedfakes",
|
||||||
"Derek Baker",
|
|
||||||
"Michael Anthony Scott",
|
|
||||||
"Atilla Berke Pekduyar",
|
|
||||||
"Maso",
|
"Maso",
|
||||||
"Nathan",
|
"Eric Ketchum",
|
||||||
"Decx _",
|
"NICHOLAS BAXLEY",
|
||||||
|
"Michael Scott",
|
||||||
"Kevin Wallace",
|
"Kevin Wallace",
|
||||||
"Matheus Couto",
|
"Matheus Couto",
|
||||||
"Paul Hartsuyker",
|
"Saya",
|
||||||
"ChicRic",
|
"ChicRic",
|
||||||
"mercur",
|
"mercur",
|
||||||
"J C",
|
"J C",
|
||||||
"Distortik",
|
"Ed Wang",
|
||||||
|
"Ryan Presley Ng",
|
||||||
|
"Wes Sims",
|
||||||
|
"Donor4115",
|
||||||
"Yves Poezevara",
|
"Yves Poezevara",
|
||||||
"Teriak47",
|
"Teriak47",
|
||||||
"Just me",
|
"Just me",
|
||||||
"Raf Stahelin",
|
"Raf Stahelin",
|
||||||
"Вячеслав Маринин",
|
"Вячеслав Маринин",
|
||||||
|
"Lyavph",
|
||||||
|
"Filippo Ferrari",
|
||||||
"Cola Matthew",
|
"Cola Matthew",
|
||||||
"OniNoKen",
|
"OniNoKen",
|
||||||
"Iain Wisely",
|
"Iain Wisely",
|
||||||
@@ -505,117 +568,100 @@
|
|||||||
"RevyHiep",
|
"RevyHiep",
|
||||||
"Captain_Swag",
|
"Captain_Swag",
|
||||||
"obkircher",
|
"obkircher",
|
||||||
"Tree Tagger",
|
|
||||||
"gwyar",
|
"gwyar",
|
||||||
"D",
|
"D",
|
||||||
"edgecase",
|
"edgecase",
|
||||||
"Neoxena",
|
"Neoxena",
|
||||||
"mrmhalo",
|
"mrmhalo",
|
||||||
"dg",
|
"dg",
|
||||||
"Whitepinetrader",
|
|
||||||
"Maarten Harms",
|
"Maarten Harms",
|
||||||
"OrganicArtifact",
|
|
||||||
"四糸凜音",
|
|
||||||
"MudkipMedkitz",
|
|
||||||
"Israel",
|
"Israel",
|
||||||
"deanbrian",
|
|
||||||
"POPPIN",
|
|
||||||
"Muratoraccio",
|
"Muratoraccio",
|
||||||
"SelfishMedic",
|
"SelfishMedic",
|
||||||
"Ginnie",
|
"Ginnie",
|
||||||
"Alex Wortman",
|
|
||||||
"Cody",
|
|
||||||
"adderleighn",
|
"adderleighn",
|
||||||
"Raku",
|
|
||||||
"smart.edge5178",
|
|
||||||
"emadsultan",
|
|
||||||
"InformedViewz",
|
|
||||||
"CHKeeho80",
|
|
||||||
"Bubbafett",
|
|
||||||
"leaf",
|
|
||||||
"Menard",
|
|
||||||
"Skyfire83",
|
|
||||||
"Adam Rinehart",
|
|
||||||
"D",
|
|
||||||
"Pitpe11",
|
|
||||||
"TheD1rtyD03",
|
|
||||||
"EnragedAntelope",
|
"EnragedAntelope",
|
||||||
"moonpetal",
|
"Alan+Cano",
|
||||||
"SomeDude",
|
"FeralOpticsAI",
|
||||||
"g9p0o",
|
"Pavlaki",
|
||||||
"nanana",
|
"generic404",
|
||||||
"TheHolySheep",
|
"Mateusz+Kosela",
|
||||||
"Monte Won",
|
"Doug+Rintoul",
|
||||||
"SpringBootisTrash",
|
"Noor",
|
||||||
"carsten",
|
"Yorunai",
|
||||||
"ikok",
|
"Bula",
|
||||||
"Buecyb99",
|
"quantenmecha",
|
||||||
"4IXplr0r3r",
|
"abattoirblues",
|
||||||
|
"Jason+Nash",
|
||||||
|
"BillyBoy84",
|
||||||
|
"DarkRoast",
|
||||||
|
"zounik",
|
||||||
|
"letzte",
|
||||||
|
"Nasty+Hobbit",
|
||||||
|
"SgtFluffles",
|
||||||
|
"lrdchs2",
|
||||||
|
"Duk3+Rand0m",
|
||||||
|
"KUJYAKU",
|
||||||
|
"NathenChoi",
|
||||||
|
"Thomas+Reck",
|
||||||
|
"Larses",
|
||||||
|
"cocona",
|
||||||
"Coeur+de+cochon",
|
"Coeur+de+cochon",
|
||||||
"David Schenck",
|
"David Schenck",
|
||||||
"han b",
|
"han b",
|
||||||
"Nico",
|
"Nico",
|
||||||
"Wolfe7D1",
|
|
||||||
"Banana Joe",
|
"Banana Joe",
|
||||||
"_ G3n",
|
"_ G3n",
|
||||||
"Donovan Jenkins",
|
"Donovan Jenkins",
|
||||||
"Ink Temptation",
|
"JBsuede",
|
||||||
"edk",
|
|
||||||
"Michael Eid",
|
"Michael Eid",
|
||||||
"beersandbacon",
|
"beersandbacon",
|
||||||
"Maximilian Pyko",
|
"Maximilian Pyko",
|
||||||
"Invis",
|
"Invis",
|
||||||
"Kalli Core",
|
|
||||||
"Justin Houston",
|
"Justin Houston",
|
||||||
|
"Time Valentine",
|
||||||
"james",
|
"james",
|
||||||
"elleshar666",
|
|
||||||
"OrochiNights",
|
"OrochiNights",
|
||||||
"Michael Zhu",
|
"Michael Zhu",
|
||||||
"ACTUALLY_the_Real_Willem_Dafoe",
|
"ACTUALLY_the_Real_Willem_Dafoe",
|
||||||
"gonzalo",
|
"gonzalo",
|
||||||
"Seraphy",
|
"Seraphy",
|
||||||
|
"Михал Михалыч",
|
||||||
"雨の心 落",
|
"雨の心 落",
|
||||||
|
"Matt",
|
||||||
"AllTimeNoobie",
|
"AllTimeNoobie",
|
||||||
"jumpd",
|
"jumpd",
|
||||||
"John C",
|
"John C",
|
||||||
"Kauffy",
|
|
||||||
"Rim",
|
"Rim",
|
||||||
"Dismem",
|
"Dismem",
|
||||||
"EpicElric",
|
"Frogmilk",
|
||||||
"John J Linehan",
|
"SPJ",
|
||||||
"Xan Dionysus",
|
"Xan Dionysus",
|
||||||
"Nathan lee",
|
"Nathan lee",
|
||||||
"Mewtora",
|
"Mewtora",
|
||||||
"Elliot E",
|
|
||||||
"Middo",
|
"Middo",
|
||||||
"Forbidden Atelier",
|
"Forbidden Atelier",
|
||||||
"Edward Kennedy",
|
"Bryan Rutkowski",
|
||||||
"Justin Blaylock",
|
|
||||||
"Adictedtohumping",
|
"Adictedtohumping",
|
||||||
"Devil Lude",
|
|
||||||
"Nick Kage",
|
|
||||||
"Towelie",
|
"Towelie",
|
||||||
"Vane Holzer",
|
|
||||||
"psytrax",
|
|
||||||
"Cyrus Fett",
|
"Cyrus Fett",
|
||||||
"Jean-françois SEMA",
|
"Jean-françois SEMA",
|
||||||
"Kurt",
|
"Kurt",
|
||||||
"hexxish",
|
"max blo",
|
||||||
"giani kidd",
|
"Xenon Xue",
|
||||||
"CptNeo",
|
"JackJohnnyJim",
|
||||||
"notedfakes",
|
"Edward Ten Eyck",
|
||||||
"Chase Kwon",
|
"Chase Kwon",
|
||||||
|
"Inyoshu",
|
||||||
"Goober719",
|
"Goober719",
|
||||||
"Eric Ketchum",
|
|
||||||
"Chad Barnes",
|
"Chad Barnes",
|
||||||
"NICHOLAS BAXLEY",
|
|
||||||
"Michael Scott",
|
|
||||||
"James Ming",
|
"James Ming",
|
||||||
"vanditking",
|
"vanditking",
|
||||||
"kripitonga",
|
"kripitonga",
|
||||||
"Rizzi",
|
"Rizzi",
|
||||||
"nimin",
|
"nimin",
|
||||||
"OMAR LUCIANO",
|
"OMAR LUCIANO",
|
||||||
|
"hannibal",
|
||||||
"Jo+Example",
|
"Jo+Example",
|
||||||
"BrentBertram",
|
"BrentBertram",
|
||||||
"eumelzocker",
|
"eumelzocker",
|
||||||
@@ -623,5 +669,5 @@
|
|||||||
"L C",
|
"L C",
|
||||||
"Dude"
|
"Dude"
|
||||||
],
|
],
|
||||||
"totalCount": 620
|
"totalCount": 666
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "Zusätzliche Ordnerpfade",
|
"title": "Zusätzliche Ordnerpfade",
|
||||||
"help": "Fügen Sie zusätzliche Modellordner außerhalb der Standardpfade von ComfyUI hinzu. Diese Pfade werden separat gespeichert und zusammen mit den Standardordnern gescannt.",
|
"description": "Zusätzliche Modellstammverzeichnisse, die ausschließlich für LoRA Manager gelten. Laden Sie Modelle von Speicherorten außerhalb der Standardordner von ComfyUI – ideal für große Bibliotheken, die ComfyUI sonst verlangsamen würden.",
|
||||||
"description": "Konfigurieren Sie zusätzliche Ordner zum Scannen von Modellen. Diese Pfade sind spezifisch für LoRA Manager und werden mit den Standardpfaden von ComfyUI zusammengeführt.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "LoRA-Pfade",
|
"lora": "LoRA-Pfade",
|
||||||
"checkpoint": "Checkpoint-Pfade",
|
"checkpoint": "Checkpoint-Pfade",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Embedding-Pfade"
|
"embedding": "Embedding-Pfade"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/pfad/zu/extra/modellen",
|
"pathPlaceholder": "/pfad/zu/extra/modellen",
|
||||||
"saveSuccess": "Zusätzliche Ordnerpfade aktualisiert.",
|
"saveSuccess": "Zusätzliche Ordnerpfade aktualisiert. Neustart erforderlich, um Änderungen anzuwenden.",
|
||||||
"saveError": "Fehler beim Aktualisieren der zusätzlichen Ordnerpfade: {message}",
|
"saveError": "Fehler beim Aktualisieren der zusätzlichen Ordnerpfade: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "Dieser Pfad ist bereits konfiguriert"
|
"duplicatePath": "Dieser Pfad ist bereits konfiguriert"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "Extra Folder Paths",
|
"title": "Extra Folder Paths",
|
||||||
"help": "Add additional model folders outside of ComfyUI's standard paths. These paths are stored separately and scanned alongside the default folders.",
|
"description": "Additional model root paths exclusive to LoRA Manager. Load models from locations outside ComfyUI's standard folders—ideal for large libraries that would otherwise slow down ComfyUI.",
|
||||||
"description": "Configure additional folders to scan for models. These paths are specific to LoRA Manager and will be merged with ComfyUI's default paths.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "LoRA Paths",
|
"lora": "LoRA Paths",
|
||||||
"checkpoint": "Checkpoint Paths",
|
"checkpoint": "Checkpoint Paths",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Embedding Paths"
|
"embedding": "Embedding Paths"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/path/to/extra/models",
|
"pathPlaceholder": "/path/to/extra/models",
|
||||||
"saveSuccess": "Extra folder paths updated.",
|
"saveSuccess": "Extra folder paths updated. Restart required to apply changes.",
|
||||||
"saveError": "Failed to update extra folder paths: {message}",
|
"saveError": "Failed to update extra folder paths: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "This path is already configured"
|
"duplicatePath": "This path is already configured"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "Rutas de carpetas adicionales",
|
"title": "Rutas de carpetas adicionales",
|
||||||
"help": "Agregue carpetas de modelos adicionales fuera de las rutas estándar de ComfyUI. Estas rutas se almacenan por separado y se escanean junto con las carpetas predeterminadas.",
|
"description": "Rutas raíz de modelos adicionales exclusivas para LoRA Manager. Cargue modelos desde ubicaciones fuera de las carpetas estándar de ComfyUI, ideal para bibliotecas grandes que de otro modo ralentizarían ComfyUI.",
|
||||||
"description": "Configure carpetas adicionales para escanear modelos. Estas rutas son específicas de LoRA Manager y se fusionarán con las rutas predeterminadas de ComfyUI.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "Rutas de LoRA",
|
"lora": "Rutas de LoRA",
|
||||||
"checkpoint": "Rutas de Checkpoint",
|
"checkpoint": "Rutas de Checkpoint",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Rutas de Embedding"
|
"embedding": "Rutas de Embedding"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/ruta/a/modelos/extra",
|
"pathPlaceholder": "/ruta/a/modelos/extra",
|
||||||
"saveSuccess": "Rutas de carpetas adicionales actualizadas.",
|
"saveSuccess": "Rutas de carpetas adicionales actualizadas. Se requiere reinicio para aplicar los cambios.",
|
||||||
"saveError": "Error al actualizar las rutas de carpetas adicionales: {message}",
|
"saveError": "Error al actualizar las rutas de carpetas adicionales: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "Esta ruta ya está configurada"
|
"duplicatePath": "Esta ruta ya está configurada"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "Chemins de dossiers supplémentaires",
|
"title": "Chemins de dossiers supplémentaires",
|
||||||
"help": "Ajoutez des dossiers de modèles supplémentaires en dehors des chemins standard de ComfyUI. Ces chemins sont stockés séparément et analysés aux côtés des dossiers par défaut.",
|
"description": "Chemins racine de modèles supplémentaires exclusifs à LoRA Manager. Chargez des modèles depuis des emplacements en dehors des dossiers standard de ComfyUI, idéal pour les grandes bibliothèques qui ralentiraient autrement ComfyUI.",
|
||||||
"description": "Configurez des dossiers supplémentaires pour l'analyse de modèles. Ces chemins sont spécifiques à LoRA Manager et seront fusionnés avec les chemins par défaut de ComfyUI.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "Chemins LoRA",
|
"lora": "Chemins LoRA",
|
||||||
"checkpoint": "Chemins Checkpoint",
|
"checkpoint": "Chemins Checkpoint",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Chemins Embedding"
|
"embedding": "Chemins Embedding"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/chemin/vers/modèles/supplémentaires",
|
"pathPlaceholder": "/chemin/vers/modèles/supplémentaires",
|
||||||
"saveSuccess": "Chemins de dossiers supplémentaires mis à jour.",
|
"saveSuccess": "Chemins de dossiers supplémentaires mis à jour. Redémarrage requis pour appliquer les changements.",
|
||||||
"saveError": "Échec de la mise à jour des chemins de dossiers supplémentaires: {message}",
|
"saveError": "Échec de la mise à jour des chemins de dossiers supplémentaires: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "Ce chemin est déjà configuré"
|
"duplicatePath": "Ce chemin est déjà configuré"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "נתיבי תיקיות נוספים",
|
"title": "נתיבי תיקיות נוספים",
|
||||||
"help": "הוסף תיקיות מודלים נוספות מחוץ לנתיבים הסטנדרטיים של ComfyUI. נתיבים אלה נשמרים בנפרד ונסרקים לצד תיקיות ברירת המחדל.",
|
"description": "נתיבי שורש מודלים נוספים בלעדיים ל-LoRA Manager. טען מודלים ממיקומים מחוץ לתיקיות הסטנדרטיות של ComfyUI - אידיאלי לספריות גדולות שאחרת יאטו את ComfyUI.",
|
||||||
"description": "הגדר תיקיות נוספות לסריקת מודלים. נתיבים אלה ספציפיים ל-LoRA Manager וימוזגו עם נתיבי ברירת המחדל של ComfyUI.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "נתיבי LoRA",
|
"lora": "נתיבי LoRA",
|
||||||
"checkpoint": "נתיבי Checkpoint",
|
"checkpoint": "נתיבי Checkpoint",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "נתיבי Embedding"
|
"embedding": "נתיבי Embedding"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/נתיב/למודלים/נוספים",
|
"pathPlaceholder": "/נתיב/למודלים/נוספים",
|
||||||
"saveSuccess": "נתיבי תיקיות נוספים עודכנו.",
|
"saveSuccess": "נתיבי תיקיות נוספים עודכנו. נדרשת הפעלה מחדש כדי להחיל את השינויים.",
|
||||||
"saveError": "נכשל בעדכון נתיבי תיקיות נוספים: {message}",
|
"saveError": "נכשל בעדכון נתיבי תיקיות נוספים: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "נתיב זה כבר מוגדר"
|
"duplicatePath": "נתיב זה כבר מוגדר"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "追加フォルダーパス",
|
"title": "追加フォルダーパス",
|
||||||
"help": "ComfyUIの標準パスの外部に追加のモデルフォルダを追加します。これらのパスは別々に保存され、デフォルトのフォルダと一緒にスキャンされます。",
|
"description": "LoRA Manager専用の追加モデルルートパス。ComfyUIの標準フォルダー外の場所からモデルを読み込みます。ComfyUIの動作を低下させる可能性のある大規模ライブラリに最適です。",
|
||||||
"description": "モデルをスキャンするための追加フォルダを設定します。これらのパスはLoRA Manager固有であり、ComfyUIのデフォルトパスとマージされます。",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "LoRAパス",
|
"lora": "LoRAパス",
|
||||||
"checkpoint": "Checkpointパス",
|
"checkpoint": "Checkpointパス",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Embeddingパス"
|
"embedding": "Embeddingパス"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/追加モデルへのパス",
|
"pathPlaceholder": "/追加モデルへのパス",
|
||||||
"saveSuccess": "追加フォルダーパスを更新しました。",
|
"saveSuccess": "追加フォルダーパスを更新しました。変更を適用するには再起動が必要です。",
|
||||||
"saveError": "追加フォルダーパスの更新に失敗しました: {message}",
|
"saveError": "追加フォルダーパスの更新に失敗しました: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "このパスはすでに設定されています"
|
"duplicatePath": "このパスはすでに設定されています"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "추가 폴다 경로",
|
"title": "추가 폴다 경로",
|
||||||
"help": "ComfyUI의 표준 경로 외부에 추가 모델 폴드를 추가하세요. 이러한 경로는 별도로 저장되며 기본 폴와 함께 스캔됩니다.",
|
"description": "LoRA Manager 전용 추가 모델 루트 경로입니다. ComfyUI의 표준 폴더 외부 위치에서 모델을 로드하여 대규모 라이브러리로 인한 성능 저하를 방지합니다.",
|
||||||
"description": "모델을 스캔하기 위한 추가 폴를 설정하세요. 이러한 경로는 LoRA Manager 특유의 것이며 ComfyUI의 기본 경로와 병합됩니다.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "LoRA 경로",
|
"lora": "LoRA 경로",
|
||||||
"checkpoint": "Checkpoint 경로",
|
"checkpoint": "Checkpoint 경로",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Embedding 경로"
|
"embedding": "Embedding 경로"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/추가/모델/경로",
|
"pathPlaceholder": "/추가/모델/경로",
|
||||||
"saveSuccess": "추가 폴다 경로가 업데이트되었습니다.",
|
"saveSuccess": "추가 폴다 경로가 업데이트되었습니다. 변경 사항을 적용하려면 재시작이 필요합니다.",
|
||||||
"saveError": "추가 폴다 경로 업데이트 실패: {message}",
|
"saveError": "추가 폴다 경로 업데이트 실패: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "이 경로는 이미 구성되어 있습니다"
|
"duplicatePath": "이 경로는 이미 구성되어 있습니다"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "Дополнительные пути к папкам",
|
"title": "Дополнительные пути к папкам",
|
||||||
"help": "Добавьте дополнительные папки моделей за пределами стандартных путей ComfyUI. Эти пути хранятся отдельно и сканируются вместе с папками по умолчанию.",
|
"description": "Дополнительные корневые пути моделей, эксклюзивные для LoRA Manager. Загружайте модели из расположений за пределами стандартных папок ComfyUI — идеально подходит для больших библиотек, которые иначе замедлили бы ComfyUI.",
|
||||||
"description": "Настройте дополнительные папки для сканирования моделей. Эти пути специфичны для LoRA Manager и будут объединены с путями по умолчанию ComfyUI.",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "Пути LoRA",
|
"lora": "Пути LoRA",
|
||||||
"checkpoint": "Пути Checkpoint",
|
"checkpoint": "Пути Checkpoint",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Пути Embedding"
|
"embedding": "Пути Embedding"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/путь/к/дополнительным/моделям",
|
"pathPlaceholder": "/путь/к/дополнительным/моделям",
|
||||||
"saveSuccess": "Дополнительные пути к папкам обновлены.",
|
"saveSuccess": "Дополнительные пути к папкам обновлены. Требуется перезапуск для применения изменений.",
|
||||||
"saveError": "Не удалось обновить дополнительные пути к папкам: {message}",
|
"saveError": "Не удалось обновить дополнительные пути к папкам: {message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "Этот путь уже настроен"
|
"duplicatePath": "Этот путь уже настроен"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "额外文件夹路径",
|
"title": "额外文件夹路径",
|
||||||
"help": "在 ComfyUI 的标准路径之外添加额外的模型文件夹。这些路径单独存储,并与默认文件夹一起扫描。",
|
"description": "LoRA Manager 专属的额外模型根目录。从 ComfyUI 标准文件夹之外的位置加载模型,特别适合管理大型模型库,避免影响 ComfyUI 性能。",
|
||||||
"description": "配置额外的文件夹以扫描模型。这些路径是 LoRA Manager 特有的,将与 ComfyUI 的默认路径合并。",
|
"restartRequired": "需要重启才能生效",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "LoRA 路径",
|
"lora": "LoRA 路径",
|
||||||
"checkpoint": "Checkpoint 路径",
|
"checkpoint": "Checkpoint 路径",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Embedding 路径"
|
"embedding": "Embedding 路径"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/额外/模型/路径",
|
"pathPlaceholder": "/额外/模型/路径",
|
||||||
"saveSuccess": "额外文件夹路径已更新。",
|
"saveSuccess": "额外文件夹路径已更新,需要重启才能生效。",
|
||||||
"saveError": "更新额外文件夹路径失败:{message}",
|
"saveError": "更新额外文件夹路径失败:{message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "此路径已配置"
|
"duplicatePath": "此路径已配置"
|
||||||
|
|||||||
@@ -393,8 +393,8 @@
|
|||||||
},
|
},
|
||||||
"extraFolderPaths": {
|
"extraFolderPaths": {
|
||||||
"title": "額外資料夾路徑",
|
"title": "額外資料夾路徑",
|
||||||
"help": "在 ComfyUI 的標準路徑之外新增額外的模型資料夾。這些路徑單獨儲存,並與預設資料夾一起掃描。",
|
"description": "LoRA Manager 專屬的額外模型根目錄。從 ComfyUI 標準資料夾之外的位置載入模型,特別適合管理大型模型庫,避免影響 ComfyUI 效能。",
|
||||||
"description": "設定額外的資料夾以掃描模型。這些路徑是 LoRA Manager 特有的,將與 ComfyUI 的預設路徑合併。",
|
"restartRequired": "Requires restart to take effect",
|
||||||
"modelTypes": {
|
"modelTypes": {
|
||||||
"lora": "LoRA 路徑",
|
"lora": "LoRA 路徑",
|
||||||
"checkpoint": "Checkpoint 路徑",
|
"checkpoint": "Checkpoint 路徑",
|
||||||
@@ -402,7 +402,7 @@
|
|||||||
"embedding": "Embedding 路徑"
|
"embedding": "Embedding 路徑"
|
||||||
},
|
},
|
||||||
"pathPlaceholder": "/額外/模型/路徑",
|
"pathPlaceholder": "/額外/模型/路徑",
|
||||||
"saveSuccess": "額外資料夾路徑已更新。",
|
"saveSuccess": "額外資料夾路徑已更新,需要重啟才能生效。",
|
||||||
"saveError": "更新額外資料夾路徑失敗:{message}",
|
"saveError": "更新額外資料夾路徑失敗:{message}",
|
||||||
"validation": {
|
"validation": {
|
||||||
"duplicatePath": "此路徑已設定"
|
"duplicatePath": "此路徑已設定"
|
||||||
|
|||||||
61
py/config.py
61
py/config.py
@@ -25,6 +25,31 @@ standalone_mode = (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_valid_default_root(
|
||||||
|
current: str, primary_paths: List[str], name: str
|
||||||
|
) -> str:
|
||||||
|
"""Return a valid default root from the current primary path set."""
|
||||||
|
|
||||||
|
valid_paths = [path for path in primary_paths if isinstance(path, str) and path.strip()]
|
||||||
|
if not valid_paths:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if current in valid_paths:
|
||||||
|
return current
|
||||||
|
|
||||||
|
if current:
|
||||||
|
logger.info(
|
||||||
|
"Repaired stale %s from '%s' to '%s'",
|
||||||
|
name,
|
||||||
|
current,
|
||||||
|
valid_paths[0],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info("Auto-setting %s to '%s'", name, valid_paths[0])
|
||||||
|
|
||||||
|
return valid_paths[0]
|
||||||
|
|
||||||
|
|
||||||
def _normalize_folder_paths_for_comparison(
|
def _normalize_folder_paths_for_comparison(
|
||||||
folder_paths: Mapping[str, Iterable[str]],
|
folder_paths: Mapping[str, Iterable[str]],
|
||||||
) -> Dict[str, Set[str]]:
|
) -> Dict[str, Set[str]]:
|
||||||
@@ -197,25 +222,23 @@ class Config:
|
|||||||
"Failed to rename legacy 'default' library: %s", rename_error
|
"Failed to rename legacy 'default' library: %s", rename_error
|
||||||
)
|
)
|
||||||
|
|
||||||
default_lora_root = comfy_library.get("default_lora_root", "")
|
default_lora_root = _resolve_valid_default_root(
|
||||||
if not default_lora_root and len(self.loras_roots) == 1:
|
comfy_library.get("default_lora_root", ""),
|
||||||
default_lora_root = self.loras_roots[0]
|
list(self.loras_roots or []),
|
||||||
|
"default_lora_root",
|
||||||
|
)
|
||||||
|
|
||||||
default_checkpoint_root = comfy_library.get("default_checkpoint_root", "")
|
default_checkpoint_root = _resolve_valid_default_root(
|
||||||
if (
|
comfy_library.get("default_checkpoint_root", ""),
|
||||||
not default_checkpoint_root
|
list(self.checkpoints_roots or []),
|
||||||
and self.checkpoints_roots
|
"default_checkpoint_root",
|
||||||
and len(self.checkpoints_roots) == 1
|
)
|
||||||
):
|
|
||||||
default_checkpoint_root = self.checkpoints_roots[0]
|
|
||||||
|
|
||||||
default_embedding_root = comfy_library.get("default_embedding_root", "")
|
default_embedding_root = _resolve_valid_default_root(
|
||||||
if (
|
comfy_library.get("default_embedding_root", ""),
|
||||||
not default_embedding_root
|
list(self.embeddings_roots or []),
|
||||||
and self.embeddings_roots
|
"default_embedding_root",
|
||||||
and len(self.embeddings_roots) == 1
|
)
|
||||||
):
|
|
||||||
default_embedding_root = self.embeddings_roots[0]
|
|
||||||
|
|
||||||
metadata = dict(comfy_library.get("metadata", {}))
|
metadata = dict(comfy_library.get("metadata", {}))
|
||||||
metadata.setdefault("display_name", "ComfyUI")
|
metadata.setdefault("display_name", "ComfyUI")
|
||||||
@@ -706,7 +729,9 @@ class Config:
|
|||||||
return unique_paths
|
return unique_paths
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_path_for_comparison(path: str, *, resolve_realpath: bool = False) -> str:
|
def _normalize_path_for_comparison(
|
||||||
|
path: str, *, resolve_realpath: bool = False
|
||||||
|
) -> str:
|
||||||
"""Normalize a path for equality checks across platforms."""
|
"""Normalize a path for equality checks across platforms."""
|
||||||
candidate = os.path.realpath(path) if resolve_realpath else path
|
candidate = os.path.realpath(path) if resolve_realpath else path
|
||||||
return os.path.normcase(os.path.normpath(candidate)).replace(os.sep, "/")
|
return os.path.normcase(os.path.normpath(candidate)).replace(os.sep, "/")
|
||||||
|
|||||||
@@ -7,7 +7,17 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Any, Awaitable, Dict, Iterable, List, Mapping, Optional, Sequence, Tuple
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Awaitable,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Sequence,
|
||||||
|
Tuple,
|
||||||
|
)
|
||||||
|
|
||||||
from platformdirs import user_config_dir
|
from platformdirs import user_config_dir
|
||||||
|
|
||||||
@@ -17,7 +27,11 @@ from ..utils.constants import (
|
|||||||
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS,
|
SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS,
|
||||||
)
|
)
|
||||||
from ..utils.preview_selection import VALID_MATURE_BLUR_LEVELS
|
from ..utils.preview_selection import VALID_MATURE_BLUR_LEVELS
|
||||||
from ..utils.settings_paths import APP_NAME, ensure_settings_file, get_legacy_settings_path
|
from ..utils.settings_paths import (
|
||||||
|
APP_NAME,
|
||||||
|
ensure_settings_file,
|
||||||
|
get_legacy_settings_path,
|
||||||
|
)
|
||||||
from ..utils.tag_priorities import (
|
from ..utils.tag_priorities import (
|
||||||
PriorityTagEntry,
|
PriorityTagEntry,
|
||||||
collect_canonical_tags,
|
collect_canonical_tags,
|
||||||
@@ -94,7 +108,9 @@ class SettingsManager:
|
|||||||
self._template_payload_cache_loaded = False
|
self._template_payload_cache_loaded = False
|
||||||
self._original_disk_payload: Optional[Dict[str, Any]] = None
|
self._original_disk_payload: Optional[Dict[str, Any]] = None
|
||||||
self._preserve_disk_template = False
|
self._preserve_disk_template = False
|
||||||
self._template_path = Path(__file__).resolve().parents[2] / "settings.json.example"
|
self._template_path = (
|
||||||
|
Path(__file__).resolve().parents[2] / "settings.json.example"
|
||||||
|
)
|
||||||
self.settings = self._load_settings()
|
self.settings = self._load_settings()
|
||||||
self._migrate_setting_keys()
|
self._migrate_setting_keys()
|
||||||
self._ensure_default_settings()
|
self._ensure_default_settings()
|
||||||
@@ -120,7 +136,7 @@ class SettingsManager:
|
|||||||
"""Load settings from file"""
|
"""Load settings from file"""
|
||||||
if os.path.exists(self.settings_file):
|
if os.path.exists(self.settings_file):
|
||||||
try:
|
try:
|
||||||
with open(self.settings_file, 'r', encoding='utf-8') as f:
|
with open(self.settings_file, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
self._original_disk_payload = copy.deepcopy(data)
|
self._original_disk_payload = copy.deepcopy(data)
|
||||||
@@ -198,7 +214,9 @@ class SettingsManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
logger.debug("settings.json.example is not a JSON object; ignoring template")
|
logger.debug(
|
||||||
|
"settings.json.example is not a JSON object; ignoring template"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self._template_payload_cache = copy.deepcopy(data)
|
self._template_payload_cache = copy.deepcopy(data)
|
||||||
@@ -274,7 +292,9 @@ class SettingsManager:
|
|||||||
normalized_skip_paths = self.normalize_metadata_refresh_skip_paths(
|
normalized_skip_paths = self.normalize_metadata_refresh_skip_paths(
|
||||||
self.settings.get("metadata_refresh_skip_paths")
|
self.settings.get("metadata_refresh_skip_paths")
|
||||||
)
|
)
|
||||||
if normalized_skip_paths != self.settings.get("metadata_refresh_skip_paths"):
|
if normalized_skip_paths != self.settings.get(
|
||||||
|
"metadata_refresh_skip_paths"
|
||||||
|
):
|
||||||
self.settings["metadata_refresh_skip_paths"] = normalized_skip_paths
|
self.settings["metadata_refresh_skip_paths"] = normalized_skip_paths
|
||||||
updated_existing = True
|
updated_existing = True
|
||||||
else:
|
else:
|
||||||
@@ -288,9 +308,7 @@ class SettingsManager:
|
|||||||
if normalized_skip_base_models != self.settings.get(
|
if normalized_skip_base_models != self.settings.get(
|
||||||
"download_skip_base_models"
|
"download_skip_base_models"
|
||||||
):
|
):
|
||||||
self.settings["download_skip_base_models"] = (
|
self.settings["download_skip_base_models"] = normalized_skip_base_models
|
||||||
normalized_skip_base_models
|
|
||||||
)
|
|
||||||
updated_existing = True
|
updated_existing = True
|
||||||
else:
|
else:
|
||||||
self.settings["download_skip_base_models"] = []
|
self.settings["download_skip_base_models"] = []
|
||||||
@@ -330,19 +348,19 @@ class SettingsManager:
|
|||||||
raw_top_level_paths = self.settings.get("folder_paths", {})
|
raw_top_level_paths = self.settings.get("folder_paths", {})
|
||||||
normalized_top_level_paths: Dict[str, List[str]] = {}
|
normalized_top_level_paths: Dict[str, List[str]] = {}
|
||||||
if isinstance(raw_top_level_paths, Mapping):
|
if isinstance(raw_top_level_paths, Mapping):
|
||||||
normalized_top_level_paths = self._normalize_folder_paths(raw_top_level_paths)
|
normalized_top_level_paths = self._normalize_folder_paths(
|
||||||
|
raw_top_level_paths
|
||||||
|
)
|
||||||
if normalized_top_level_paths != raw_top_level_paths:
|
if normalized_top_level_paths != raw_top_level_paths:
|
||||||
self.settings["folder_paths"] = copy.deepcopy(normalized_top_level_paths)
|
self.settings["folder_paths"] = copy.deepcopy(
|
||||||
|
normalized_top_level_paths
|
||||||
|
)
|
||||||
|
|
||||||
top_level_has_paths = self._has_configured_paths(normalized_top_level_paths)
|
top_level_has_paths = self._has_configured_paths(normalized_top_level_paths)
|
||||||
|
|
||||||
needs_library_bootstrap = not isinstance(libraries, dict) or not libraries
|
needs_library_bootstrap = not isinstance(libraries, dict) or not libraries
|
||||||
|
|
||||||
if (
|
if not needs_library_bootstrap and top_level_has_paths and len(libraries) == 1:
|
||||||
not needs_library_bootstrap
|
|
||||||
and top_level_has_paths
|
|
||||||
and len(libraries) == 1
|
|
||||||
):
|
|
||||||
only_library_payload = next(iter(libraries.values()))
|
only_library_payload = next(iter(libraries.values()))
|
||||||
if isinstance(only_library_payload, Mapping):
|
if isinstance(only_library_payload, Mapping):
|
||||||
folder_payload = only_library_payload.get("folder_paths")
|
folder_payload = only_library_payload.get("folder_paths")
|
||||||
@@ -354,7 +372,9 @@ class SettingsManager:
|
|||||||
library_payload = self._build_library_payload(
|
library_payload = self._build_library_payload(
|
||||||
folder_paths=normalized_top_level_paths,
|
folder_paths=normalized_top_level_paths,
|
||||||
default_lora_root=self.settings.get("default_lora_root", ""),
|
default_lora_root=self.settings.get("default_lora_root", ""),
|
||||||
default_checkpoint_root=self.settings.get("default_checkpoint_root", ""),
|
default_checkpoint_root=self.settings.get(
|
||||||
|
"default_checkpoint_root", ""
|
||||||
|
),
|
||||||
default_unet_root=self.settings.get("default_unet_root", ""),
|
default_unet_root=self.settings.get("default_unet_root", ""),
|
||||||
default_embedding_root=self.settings.get("default_embedding_root", ""),
|
default_embedding_root=self.settings.get("default_embedding_root", ""),
|
||||||
)
|
)
|
||||||
@@ -376,7 +396,11 @@ class SettingsManager:
|
|||||||
|
|
||||||
if target_name:
|
if target_name:
|
||||||
candidate_payload = libraries.get(target_name)
|
candidate_payload = libraries.get(target_name)
|
||||||
if isinstance(candidate_payload, Mapping) and not self._has_configured_paths(candidate_payload.get("folder_paths")):
|
if isinstance(
|
||||||
|
candidate_payload, Mapping
|
||||||
|
) and not self._has_configured_paths(
|
||||||
|
candidate_payload.get("folder_paths")
|
||||||
|
):
|
||||||
seed_library_name = target_name
|
seed_library_name = target_name
|
||||||
|
|
||||||
sanitized_libraries: Dict[str, Dict[str, Any]] = {}
|
sanitized_libraries: Dict[str, Dict[str, Any]] = {}
|
||||||
@@ -435,11 +459,17 @@ class SettingsManager:
|
|||||||
active_library = libraries.get(active_name, {})
|
active_library = libraries.get(active_name, {})
|
||||||
folder_paths = copy.deepcopy(active_library.get("folder_paths", {}))
|
folder_paths = copy.deepcopy(active_library.get("folder_paths", {}))
|
||||||
self.settings["folder_paths"] = folder_paths
|
self.settings["folder_paths"] = folder_paths
|
||||||
self.settings["extra_folder_paths"] = copy.deepcopy(active_library.get("extra_folder_paths", {}))
|
self.settings["extra_folder_paths"] = copy.deepcopy(
|
||||||
|
active_library.get("extra_folder_paths", {})
|
||||||
|
)
|
||||||
self.settings["default_lora_root"] = active_library.get("default_lora_root", "")
|
self.settings["default_lora_root"] = active_library.get("default_lora_root", "")
|
||||||
self.settings["default_checkpoint_root"] = active_library.get("default_checkpoint_root", "")
|
self.settings["default_checkpoint_root"] = active_library.get(
|
||||||
|
"default_checkpoint_root", ""
|
||||||
|
)
|
||||||
self.settings["default_unet_root"] = active_library.get("default_unet_root", "")
|
self.settings["default_unet_root"] = active_library.get("default_unet_root", "")
|
||||||
self.settings["default_embedding_root"] = active_library.get("default_embedding_root", "")
|
self.settings["default_embedding_root"] = active_library.get(
|
||||||
|
"default_embedding_root", ""
|
||||||
|
)
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
@@ -468,7 +498,9 @@ class SettingsManager:
|
|||||||
payload.setdefault("folder_paths", {})
|
payload.setdefault("folder_paths", {})
|
||||||
|
|
||||||
if extra_folder_paths is not None:
|
if extra_folder_paths is not None:
|
||||||
payload["extra_folder_paths"] = self._normalize_folder_paths(extra_folder_paths)
|
payload["extra_folder_paths"] = self._normalize_folder_paths(
|
||||||
|
extra_folder_paths
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
payload.setdefault("extra_folder_paths", {})
|
payload.setdefault("extra_folder_paths", {})
|
||||||
|
|
||||||
@@ -577,7 +609,9 @@ class SettingsManager:
|
|||||||
}
|
}
|
||||||
overlap = existing.intersection(new_paths.keys())
|
overlap = existing.intersection(new_paths.keys())
|
||||||
if overlap:
|
if overlap:
|
||||||
collisions = ", ".join(sorted(new_paths[value] for value in overlap))
|
collisions = ", ".join(
|
||||||
|
sorted(new_paths[value] for value in overlap)
|
||||||
|
)
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Folder path(s) {collisions} already assigned to library '{other_name}'"
|
f"Folder path(s) {collisions} already assigned to library '{other_name}'"
|
||||||
)
|
)
|
||||||
@@ -612,19 +646,31 @@ class SettingsManager:
|
|||||||
library["extra_folder_paths"] = normalized_extra_paths
|
library["extra_folder_paths"] = normalized_extra_paths
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if default_lora_root is not None and library.get("default_lora_root") != default_lora_root:
|
if (
|
||||||
|
default_lora_root is not None
|
||||||
|
and library.get("default_lora_root") != default_lora_root
|
||||||
|
):
|
||||||
library["default_lora_root"] = default_lora_root
|
library["default_lora_root"] = default_lora_root
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if default_checkpoint_root is not None and library.get("default_checkpoint_root") != default_checkpoint_root:
|
if (
|
||||||
|
default_checkpoint_root is not None
|
||||||
|
and library.get("default_checkpoint_root") != default_checkpoint_root
|
||||||
|
):
|
||||||
library["default_checkpoint_root"] = default_checkpoint_root
|
library["default_checkpoint_root"] = default_checkpoint_root
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if default_unet_root is not None and library.get("default_unet_root") != default_unet_root:
|
if (
|
||||||
|
default_unet_root is not None
|
||||||
|
and library.get("default_unet_root") != default_unet_root
|
||||||
|
):
|
||||||
library["default_unet_root"] = default_unet_root
|
library["default_unet_root"] = default_unet_root
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if default_embedding_root is not None and library.get("default_embedding_root") != default_embedding_root:
|
if (
|
||||||
|
default_embedding_root is not None
|
||||||
|
and library.get("default_embedding_root") != default_embedding_root
|
||||||
|
):
|
||||||
library["default_embedding_root"] = default_embedding_root
|
library["default_embedding_root"] = default_embedding_root
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
@@ -637,16 +683,16 @@ class SettingsManager:
|
|||||||
def _migrate_setting_keys(self) -> None:
|
def _migrate_setting_keys(self) -> None:
|
||||||
"""Migrate legacy camelCase setting keys to snake_case"""
|
"""Migrate legacy camelCase setting keys to snake_case"""
|
||||||
key_migrations = {
|
key_migrations = {
|
||||||
'optimizeExampleImages': 'optimize_example_images',
|
"optimizeExampleImages": "optimize_example_images",
|
||||||
'autoDownloadExampleImages': 'auto_download_example_images',
|
"autoDownloadExampleImages": "auto_download_example_images",
|
||||||
'blurMatureContent': 'blur_mature_content',
|
"blurMatureContent": "blur_mature_content",
|
||||||
'matureBlurLevel': 'mature_blur_level',
|
"matureBlurLevel": "mature_blur_level",
|
||||||
'autoplayOnHover': 'autoplay_on_hover',
|
"autoplayOnHover": "autoplay_on_hover",
|
||||||
'displayDensity': 'display_density',
|
"displayDensity": "display_density",
|
||||||
'cardInfoDisplay': 'card_info_display',
|
"cardInfoDisplay": "card_info_display",
|
||||||
'includeTriggerWords': 'include_trigger_words',
|
"includeTriggerWords": "include_trigger_words",
|
||||||
'compactMode': 'compact_mode',
|
"compactMode": "compact_mode",
|
||||||
'modelCardFooterAction': 'model_card_footer_action',
|
"modelCardFooterAction": "model_card_footer_action",
|
||||||
}
|
}
|
||||||
|
|
||||||
updated = False
|
updated = False
|
||||||
@@ -663,65 +709,77 @@ class SettingsManager:
|
|||||||
|
|
||||||
def _migrate_download_path_template(self):
|
def _migrate_download_path_template(self):
|
||||||
"""Migrate old download_path_template to new download_path_templates"""
|
"""Migrate old download_path_template to new download_path_templates"""
|
||||||
old_template = self.settings.get('download_path_template')
|
old_template = self.settings.get("download_path_template")
|
||||||
templates = self.settings.get('download_path_templates')
|
templates = self.settings.get("download_path_templates")
|
||||||
|
|
||||||
# If old template exists and new templates don't exist, migrate
|
# If old template exists and new templates don't exist, migrate
|
||||||
if old_template is not None and not templates:
|
if old_template is not None and not templates:
|
||||||
logger.info("Migrating download_path_template to download_path_templates")
|
logger.info("Migrating download_path_template to download_path_templates")
|
||||||
self.settings['download_path_templates'] = {
|
self.settings["download_path_templates"] = {
|
||||||
'lora': old_template,
|
"lora": old_template,
|
||||||
'checkpoint': old_template,
|
"checkpoint": old_template,
|
||||||
'embedding': old_template
|
"embedding": old_template,
|
||||||
}
|
}
|
||||||
# Remove old setting
|
# Remove old setting
|
||||||
del self.settings['download_path_template']
|
del self.settings["download_path_template"]
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
logger.info("Migration completed")
|
logger.info("Migration completed")
|
||||||
|
|
||||||
def _auto_set_default_roots(self):
|
def _auto_set_default_roots(self):
|
||||||
"""Auto set default root paths when the current default is unset or not among the options.
|
"""Ensure default root paths always point at a current valid root.
|
||||||
|
|
||||||
For single-path cases, always use that path.
|
Empty or stale defaults are repaired to the first configured root.
|
||||||
For multi-path cases, only set if current default is empty or invalid.
|
Skips auto-setting when the settings file matches the template
|
||||||
|
(user hasn't customized yet).
|
||||||
"""
|
"""
|
||||||
folder_paths = self.settings.get('folder_paths', {})
|
# Skip auto-setting if the user hasn't customized settings yet (template preserved)
|
||||||
|
if self._preserve_disk_template:
|
||||||
|
return
|
||||||
|
|
||||||
|
folder_paths = self.settings.get("folder_paths", {})
|
||||||
updated = False
|
updated = False
|
||||||
# loras
|
|
||||||
loras = folder_paths.get('loras', [])
|
def _check_and_auto_set(key: str, setting_key: str) -> bool:
|
||||||
if isinstance(loras, list) and len(loras) == 1:
|
"""Repair default roots when empty or no longer present."""
|
||||||
current_lora_root = self.settings.get('default_lora_root')
|
current = self.settings.get(setting_key, "")
|
||||||
if current_lora_root not in loras:
|
candidates = folder_paths.get(key, [])
|
||||||
self.settings['default_lora_root'] = loras[0]
|
if not isinstance(candidates, list) or not candidates:
|
||||||
updated = True
|
return False
|
||||||
# checkpoints
|
|
||||||
checkpoints = folder_paths.get('checkpoints', [])
|
# Filter valid path strings
|
||||||
if isinstance(checkpoints, list) and len(checkpoints) == 1:
|
valid_paths = [p for p in candidates if isinstance(p, str) and p.strip()]
|
||||||
current_checkpoint_root = self.settings.get('default_checkpoint_root')
|
if not valid_paths:
|
||||||
if current_checkpoint_root not in checkpoints:
|
return False
|
||||||
self.settings['default_checkpoint_root'] = checkpoints[0]
|
|
||||||
updated = True
|
if current in valid_paths:
|
||||||
# unet (diffusion models) - auto-set if empty or invalid
|
return False
|
||||||
unet_paths = folder_paths.get('unet', [])
|
|
||||||
if isinstance(unet_paths, list) and len(unet_paths) >= 1:
|
self.settings[setting_key] = valid_paths[0]
|
||||||
current_unet_root = self.settings.get('default_unet_root')
|
if current:
|
||||||
# Set to first path if current is empty or not in the valid paths
|
logger.info(
|
||||||
if not current_unet_root or current_unet_root not in unet_paths:
|
"Repaired stale %s from '%s' to '%s'",
|
||||||
self.settings['default_unet_root'] = unet_paths[0]
|
setting_key,
|
||||||
updated = True
|
current,
|
||||||
# embeddings
|
valid_paths[0],
|
||||||
embeddings = folder_paths.get('embeddings', [])
|
)
|
||||||
if isinstance(embeddings, list) and len(embeddings) == 1:
|
else:
|
||||||
current_embedding_root = self.settings.get('default_embedding_root')
|
logger.info("Auto-set %s to '%s'", setting_key, valid_paths[0])
|
||||||
if current_embedding_root not in embeddings:
|
return True
|
||||||
self.settings['default_embedding_root'] = embeddings[0]
|
|
||||||
updated = True
|
# Process all model types
|
||||||
|
updated = _check_and_auto_set("loras", "default_lora_root") or updated
|
||||||
|
updated = (
|
||||||
|
_check_and_auto_set("checkpoints", "default_checkpoint_root") or updated
|
||||||
|
)
|
||||||
|
updated = _check_and_auto_set("unet", "default_unet_root") or updated
|
||||||
|
updated = _check_and_auto_set("embeddings", "default_embedding_root") or updated
|
||||||
|
|
||||||
if updated:
|
if updated:
|
||||||
self._update_active_library_entry(
|
self._update_active_library_entry(
|
||||||
default_lora_root=self.settings.get('default_lora_root'),
|
default_lora_root=self.settings.get("default_lora_root"),
|
||||||
default_checkpoint_root=self.settings.get('default_checkpoint_root'),
|
default_checkpoint_root=self.settings.get("default_checkpoint_root"),
|
||||||
default_unet_root=self.settings.get('default_unet_root'),
|
default_unet_root=self.settings.get("default_unet_root"),
|
||||||
default_embedding_root=self.settings.get('default_embedding_root'),
|
default_embedding_root=self.settings.get("default_embedding_root"),
|
||||||
)
|
)
|
||||||
if self._bootstrap_reason == "missing":
|
if self._bootstrap_reason == "missing":
|
||||||
self._needs_initial_save = True
|
self._needs_initial_save = True
|
||||||
@@ -730,11 +788,11 @@ class SettingsManager:
|
|||||||
|
|
||||||
def _check_environment_variables(self) -> None:
|
def _check_environment_variables(self) -> None:
|
||||||
"""Check for environment variables and update settings if needed"""
|
"""Check for environment variables and update settings if needed"""
|
||||||
env_api_key = os.environ.get('CIVITAI_API_KEY')
|
env_api_key = os.environ.get("CIVITAI_API_KEY")
|
||||||
if env_api_key: # Check if the environment variable exists and is not empty
|
if env_api_key: # Check if the environment variable exists and is not empty
|
||||||
logger.info("Found CIVITAI_API_KEY environment variable")
|
logger.info("Found CIVITAI_API_KEY environment variable")
|
||||||
# Always use the environment variable if it exists
|
# Always use the environment variable if it exists
|
||||||
self.settings['civitai_api_key'] = env_api_key
|
self.settings["civitai_api_key"] = env_api_key
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
|
|
||||||
def _default_settings_actions(self) -> List[Dict[str, Any]]:
|
def _default_settings_actions(self) -> List[Dict[str, Any]]:
|
||||||
@@ -799,7 +857,9 @@ class SettingsManager:
|
|||||||
disk_value = self._original_disk_payload.get(key)
|
disk_value = self._original_disk_payload.get(key)
|
||||||
default_value = defaults.get(key)
|
default_value = defaults.get(key)
|
||||||
# Compare using JSON serialization for complex objects
|
# Compare using JSON serialization for complex objects
|
||||||
if json.dumps(disk_value, sort_keys=True, default=str) == json.dumps(default_value, sort_keys=True, default=str):
|
if json.dumps(disk_value, sort_keys=True, default=str) == json.dumps(
|
||||||
|
default_value, sort_keys=True, default=str
|
||||||
|
):
|
||||||
default_value_keys.add(key)
|
default_value_keys.add(key)
|
||||||
|
|
||||||
# Only cleanup if there are "many" default keys (indicating a bloated file)
|
# Only cleanup if there are "many" default keys (indicating a bloated file)
|
||||||
@@ -807,7 +867,7 @@ class SettingsManager:
|
|||||||
if len(default_value_keys) >= DEFAULT_KEYS_CLEANUP_THRESHOLD:
|
if len(default_value_keys) >= DEFAULT_KEYS_CLEANUP_THRESHOLD:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Cleaning up %d default value(s) from settings.json to keep it minimal",
|
"Cleaning up %d default value(s) from settings.json to keep it minimal",
|
||||||
len(default_value_keys)
|
len(default_value_keys),
|
||||||
)
|
)
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
# Update original payload to match what we just saved
|
# Update original payload to match what we just saved
|
||||||
@@ -817,8 +877,8 @@ class SettingsManager:
|
|||||||
if not self._standalone_mode:
|
if not self._standalone_mode:
|
||||||
return
|
return
|
||||||
|
|
||||||
folder_paths = self.settings.get('folder_paths', {}) or {}
|
folder_paths = self.settings.get("folder_paths", {}) or {}
|
||||||
monitored_keys = ('loras', 'checkpoints', 'embeddings')
|
monitored_keys = ("loras", "checkpoints", "embeddings")
|
||||||
|
|
||||||
has_valid_paths = False
|
has_valid_paths = False
|
||||||
for key in monitored_keys:
|
for key in monitored_keys:
|
||||||
@@ -829,7 +889,10 @@ class SettingsManager:
|
|||||||
iterator = list(raw_paths)
|
iterator = list(raw_paths)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
continue
|
continue
|
||||||
if any(isinstance(path, str) and path and os.path.exists(path) for path in iterator):
|
if any(
|
||||||
|
isinstance(path, str) and path and os.path.exists(path)
|
||||||
|
for path in iterator
|
||||||
|
):
|
||||||
has_valid_paths = True
|
has_valid_paths = True
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -860,13 +923,13 @@ class SettingsManager:
|
|||||||
def _get_default_settings(self) -> Dict[str, Any]:
|
def _get_default_settings(self) -> Dict[str, Any]:
|
||||||
"""Return default settings"""
|
"""Return default settings"""
|
||||||
defaults = copy.deepcopy(DEFAULT_SETTINGS)
|
defaults = copy.deepcopy(DEFAULT_SETTINGS)
|
||||||
defaults['base_model_path_mappings'] = {}
|
defaults["base_model_path_mappings"] = {}
|
||||||
defaults['download_path_templates'] = {}
|
defaults["download_path_templates"] = {}
|
||||||
defaults['priority_tags'] = DEFAULT_PRIORITY_TAG_CONFIG.copy()
|
defaults["priority_tags"] = DEFAULT_PRIORITY_TAG_CONFIG.copy()
|
||||||
defaults.setdefault('folder_paths', {})
|
defaults.setdefault("folder_paths", {})
|
||||||
defaults.setdefault('extra_folder_paths', {})
|
defaults.setdefault("extra_folder_paths", {})
|
||||||
defaults['auto_organize_exclusions'] = []
|
defaults["auto_organize_exclusions"] = []
|
||||||
defaults['metadata_refresh_skip_paths'] = []
|
defaults["metadata_refresh_skip_paths"] = []
|
||||||
|
|
||||||
library_name = defaults.get("active_library") or "default"
|
library_name = defaults.get("active_library") or "default"
|
||||||
default_library = self._build_library_payload(
|
default_library = self._build_library_payload(
|
||||||
@@ -876,8 +939,8 @@ class SettingsManager:
|
|||||||
default_checkpoint_root=defaults.get("default_checkpoint_root"),
|
default_checkpoint_root=defaults.get("default_checkpoint_root"),
|
||||||
default_embedding_root=defaults.get("default_embedding_root"),
|
default_embedding_root=defaults.get("default_embedding_root"),
|
||||||
)
|
)
|
||||||
defaults['libraries'] = {library_name: default_library}
|
defaults["libraries"] = {library_name: default_library}
|
||||||
defaults['active_library'] = library_name
|
defaults["active_library"] = library_name
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
def _normalize_priority_tag_config(self, value: Any) -> Dict[str, str]:
|
def _normalize_priority_tag_config(self, value: Any) -> Dict[str, str]:
|
||||||
@@ -908,7 +971,9 @@ class SettingsManager:
|
|||||||
candidates: Iterable[str] = (
|
candidates: Iterable[str] = (
|
||||||
value.replace("\n", ",").replace(";", ",").split(",")
|
value.replace("\n", ",").replace(";", ",").split(",")
|
||||||
)
|
)
|
||||||
elif isinstance(value, Sequence) and not isinstance(value, (bytes, bytearray, str)):
|
elif isinstance(value, Sequence) and not isinstance(
|
||||||
|
value, (bytes, bytearray, str)
|
||||||
|
):
|
||||||
candidates = value
|
candidates = value
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@@ -954,7 +1019,9 @@ class SettingsManager:
|
|||||||
candidates: Iterable[str] = (
|
candidates: Iterable[str] = (
|
||||||
value.replace("\n", ",").replace(";", ",").split(",")
|
value.replace("\n", ",").replace(";", ",").split(",")
|
||||||
)
|
)
|
||||||
elif isinstance(value, Sequence) and not isinstance(value, (bytes, bytearray, str)):
|
elif isinstance(value, Sequence) and not isinstance(
|
||||||
|
value, (bytes, bytearray, str)
|
||||||
|
):
|
||||||
candidates = value
|
candidates = value
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@@ -1060,7 +1127,9 @@ class SettingsManager:
|
|||||||
continue
|
continue
|
||||||
normalized = os.path.normcase(os.path.normpath(stripped))
|
normalized = os.path.normcase(os.path.normpath(stripped))
|
||||||
if os.path.exists(stripped):
|
if os.path.exists(stripped):
|
||||||
normalized = os.path.normcase(os.path.normpath(os.path.realpath(stripped)))
|
normalized = os.path.normcase(
|
||||||
|
os.path.normpath(os.path.realpath(stripped))
|
||||||
|
)
|
||||||
primary_real_paths.add(normalized)
|
primary_real_paths.add(normalized)
|
||||||
|
|
||||||
primary_symlink_targets = set()
|
primary_symlink_targets = set()
|
||||||
@@ -1096,8 +1165,13 @@ class SettingsManager:
|
|||||||
continue
|
continue
|
||||||
normalized = os.path.normcase(os.path.normpath(stripped))
|
normalized = os.path.normcase(os.path.normpath(stripped))
|
||||||
if os.path.exists(stripped):
|
if os.path.exists(stripped):
|
||||||
normalized = os.path.normcase(os.path.normpath(os.path.realpath(stripped)))
|
normalized = os.path.normcase(
|
||||||
if normalized in primary_real_paths or normalized in primary_symlink_targets:
|
os.path.normpath(os.path.realpath(stripped))
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
normalized in primary_real_paths
|
||||||
|
or normalized in primary_symlink_targets
|
||||||
|
):
|
||||||
overlapping_paths.append(stripped)
|
overlapping_paths.append(stripped)
|
||||||
|
|
||||||
if overlapping_paths:
|
if overlapping_paths:
|
||||||
@@ -1161,19 +1235,19 @@ class SettingsManager:
|
|||||||
if key == "use_portable_settings" and isinstance(value, bool):
|
if key == "use_portable_settings" and isinstance(value, bool):
|
||||||
portable_switch_pending = True
|
portable_switch_pending = True
|
||||||
self._prepare_portable_switch(value)
|
self._prepare_portable_switch(value)
|
||||||
if key == 'folder_paths' and isinstance(value, Mapping):
|
if key == "folder_paths" and isinstance(value, Mapping):
|
||||||
self._update_active_library_entry(folder_paths=value) # type: ignore[arg-type]
|
self._update_active_library_entry(folder_paths=value) # type: ignore[arg-type]
|
||||||
elif key == 'extra_folder_paths' and isinstance(value, Mapping):
|
elif key == "extra_folder_paths" and isinstance(value, Mapping):
|
||||||
self._update_active_library_entry(extra_folder_paths=value) # type: ignore[arg-type]
|
self._update_active_library_entry(extra_folder_paths=value) # type: ignore[arg-type]
|
||||||
elif key == 'default_lora_root':
|
elif key == "default_lora_root":
|
||||||
self._update_active_library_entry(default_lora_root=str(value))
|
self._update_active_library_entry(default_lora_root=str(value))
|
||||||
elif key == 'default_checkpoint_root':
|
elif key == "default_checkpoint_root":
|
||||||
self._update_active_library_entry(default_checkpoint_root=str(value))
|
self._update_active_library_entry(default_checkpoint_root=str(value))
|
||||||
elif key == 'default_unet_root':
|
elif key == "default_unet_root":
|
||||||
self._update_active_library_entry(default_unet_root=str(value))
|
self._update_active_library_entry(default_unet_root=str(value))
|
||||||
elif key == 'default_embedding_root':
|
elif key == "default_embedding_root":
|
||||||
self._update_active_library_entry(default_embedding_root=str(value))
|
self._update_active_library_entry(default_embedding_root=str(value))
|
||||||
elif key == 'model_name_display':
|
elif key == "model_name_display":
|
||||||
self._notify_model_name_display_change(value)
|
self._notify_model_name_display_change(value)
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
if portable_switch_pending:
|
if portable_switch_pending:
|
||||||
@@ -1249,10 +1323,9 @@ class SettingsManager:
|
|||||||
|
|
||||||
source_cache_dir = os.path.join(source_dir, "model_cache")
|
source_cache_dir = os.path.join(source_dir, "model_cache")
|
||||||
target_cache_dir = os.path.join(target_dir, "model_cache")
|
target_cache_dir = os.path.join(target_dir, "model_cache")
|
||||||
if (
|
if os.path.isdir(source_cache_dir) and os.path.abspath(
|
||||||
os.path.isdir(source_cache_dir)
|
source_cache_dir
|
||||||
and os.path.abspath(source_cache_dir) != os.path.abspath(target_cache_dir)
|
) != os.path.abspath(target_cache_dir):
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
shutil.copytree(
|
shutil.copytree(
|
||||||
source_cache_dir,
|
source_cache_dir,
|
||||||
@@ -1270,10 +1343,9 @@ class SettingsManager:
|
|||||||
|
|
||||||
source_cache_file = os.path.join(source_dir, "model_cache.sqlite")
|
source_cache_file = os.path.join(source_dir, "model_cache.sqlite")
|
||||||
target_cache_file = os.path.join(target_dir, "model_cache.sqlite")
|
target_cache_file = os.path.join(target_dir, "model_cache.sqlite")
|
||||||
if (
|
if os.path.isfile(source_cache_file) and os.path.abspath(
|
||||||
os.path.isfile(source_cache_file)
|
source_cache_file
|
||||||
and os.path.abspath(source_cache_file) != os.path.abspath(target_cache_file)
|
) != os.path.abspath(target_cache_file):
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
shutil.copy2(source_cache_file, target_cache_file)
|
shutil.copy2(source_cache_file, target_cache_file)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -1299,7 +1371,9 @@ class SettingsManager:
|
|||||||
try:
|
try:
|
||||||
os.makedirs(config_dir, exist_ok=True)
|
os.makedirs(config_dir, exist_ok=True)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("Failed to create user config directory %s: %s", config_dir, exc)
|
logger.warning(
|
||||||
|
"Failed to create user config directory %s: %s", config_dir, exc
|
||||||
|
)
|
||||||
|
|
||||||
return config_dir
|
return config_dir
|
||||||
|
|
||||||
@@ -1359,7 +1433,9 @@ class SettingsManager:
|
|||||||
try:
|
try:
|
||||||
asyncio.run(coroutine)
|
asyncio.run(coroutine)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
logger.debug("Skipping name display update due to missing event loop")
|
logger.debug(
|
||||||
|
"Skipping name display update due to missing event loop"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if loop is not None and target_loop is loop:
|
if loop is not None and target_loop is loop:
|
||||||
@@ -1382,7 +1458,7 @@ class SettingsManager:
|
|||||||
"""Save settings to file"""
|
"""Save settings to file"""
|
||||||
try:
|
try:
|
||||||
payload = self._serialize_settings_for_disk()
|
payload = self._serialize_settings_for_disk()
|
||||||
with open(self.settings_file, 'w', encoding='utf-8') as f:
|
with open(self.settings_file, "w", encoding="utf-8") as f:
|
||||||
json.dump(payload, f, indent=2)
|
json.dump(payload, f, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error saving settings: {e}")
|
logger.error(f"Error saving settings: {e}")
|
||||||
@@ -1423,7 +1499,9 @@ class SettingsManager:
|
|||||||
minimal[key] = copy.deepcopy(value)
|
minimal[key] = copy.deepcopy(value)
|
||||||
# Complex objects need deep comparison
|
# Complex objects need deep comparison
|
||||||
elif isinstance(value, (dict, list)) and default_value is not None:
|
elif isinstance(value, (dict, list)) and default_value is not None:
|
||||||
if json.dumps(value, sort_keys=True, default=str) != json.dumps(default_value, sort_keys=True, default=str):
|
if json.dumps(value, sort_keys=True, default=str) != json.dumps(
|
||||||
|
default_value, sort_keys=True, default=str
|
||||||
|
):
|
||||||
minimal[key] = copy.deepcopy(value)
|
minimal[key] = copy.deepcopy(value)
|
||||||
# Simple values use direct comparison
|
# Simple values use direct comparison
|
||||||
elif value != default_value:
|
elif value != default_value:
|
||||||
@@ -1500,9 +1578,15 @@ class SettingsManager:
|
|||||||
existing = libraries.get(name, {})
|
existing = libraries.get(name, {})
|
||||||
|
|
||||||
payload = self._build_library_payload(
|
payload = self._build_library_payload(
|
||||||
folder_paths=folder_paths if folder_paths is not None else existing.get("folder_paths"),
|
folder_paths=folder_paths
|
||||||
extra_folder_paths=extra_folder_paths if extra_folder_paths is not None else existing.get("extra_folder_paths"),
|
if folder_paths is not None
|
||||||
default_lora_root=default_lora_root if default_lora_root is not None else existing.get("default_lora_root"),
|
else existing.get("folder_paths"),
|
||||||
|
extra_folder_paths=extra_folder_paths
|
||||||
|
if extra_folder_paths is not None
|
||||||
|
else existing.get("extra_folder_paths"),
|
||||||
|
default_lora_root=default_lora_root
|
||||||
|
if default_lora_root is not None
|
||||||
|
else existing.get("default_lora_root"),
|
||||||
default_checkpoint_root=(
|
default_checkpoint_root=(
|
||||||
default_checkpoint_root
|
default_checkpoint_root
|
||||||
if default_checkpoint_root is not None
|
if default_checkpoint_root is not None
|
||||||
@@ -1662,7 +1746,9 @@ class SettingsManager:
|
|||||||
if service and hasattr(service, "on_library_changed"):
|
if service and hasattr(service, "on_library_changed"):
|
||||||
try:
|
try:
|
||||||
service.on_library_changed()
|
service.on_library_changed()
|
||||||
except Exception as service_exc: # pragma: no cover - defensive logging
|
except (
|
||||||
|
Exception
|
||||||
|
) as service_exc: # pragma: no cover - defensive logging
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Service %s failed to handle library change: %s",
|
"Service %s failed to handle library change: %s",
|
||||||
service_name,
|
service_name,
|
||||||
@@ -1673,15 +1759,15 @@ class SettingsManager:
|
|||||||
|
|
||||||
def get_download_path_template(self, model_type: str) -> str:
|
def get_download_path_template(self, model_type: str) -> str:
|
||||||
"""Get download path template for specific model type
|
"""Get download path template for specific model type
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model_type: The type of model ('lora', 'checkpoint', 'embedding')
|
model_type: The type of model ('lora', 'checkpoint', 'embedding')
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Template string for the model type, defaults to '{base_model}/{first_tag}'
|
Template string for the model type, defaults to '{base_model}/{first_tag}'
|
||||||
"""
|
"""
|
||||||
templates = self.settings.get('download_path_templates', {})
|
templates = self.settings.get("download_path_templates", {})
|
||||||
|
|
||||||
# Handle edge case where templates might be stored as JSON string
|
# Handle edge case where templates might be stored as JSON string
|
||||||
if isinstance(templates, str):
|
if isinstance(templates, str):
|
||||||
try:
|
try:
|
||||||
@@ -1689,36 +1775,40 @@ class SettingsManager:
|
|||||||
parsed_templates = json.loads(templates)
|
parsed_templates = json.loads(templates)
|
||||||
if isinstance(parsed_templates, dict):
|
if isinstance(parsed_templates, dict):
|
||||||
# Update settings with parsed dictionary
|
# Update settings with parsed dictionary
|
||||||
self.settings['download_path_templates'] = parsed_templates
|
self.settings["download_path_templates"] = parsed_templates
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
templates = parsed_templates
|
templates = parsed_templates
|
||||||
logger.info("Successfully parsed download_path_templates from JSON string")
|
logger.info(
|
||||||
|
"Successfully parsed download_path_templates from JSON string"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Parsed JSON is not a dictionary")
|
raise ValueError("Parsed JSON is not a dictionary")
|
||||||
except (json.JSONDecodeError, ValueError) as e:
|
except (json.JSONDecodeError, ValueError) as e:
|
||||||
# If parsing fails, set default values
|
# If parsing fails, set default values
|
||||||
logger.warning(f"Failed to parse download_path_templates JSON string: {e}. Setting default values.")
|
logger.warning(
|
||||||
default_template = '{base_model}/{first_tag}'
|
f"Failed to parse download_path_templates JSON string: {e}. Setting default values."
|
||||||
|
)
|
||||||
|
default_template = "{base_model}/{first_tag}"
|
||||||
templates = {
|
templates = {
|
||||||
'lora': default_template,
|
"lora": default_template,
|
||||||
'checkpoint': default_template,
|
"checkpoint": default_template,
|
||||||
'embedding': default_template
|
"embedding": default_template,
|
||||||
}
|
}
|
||||||
self.settings['download_path_templates'] = templates
|
self.settings["download_path_templates"] = templates
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
|
|
||||||
# Ensure templates is a dictionary
|
# Ensure templates is a dictionary
|
||||||
if not isinstance(templates, dict):
|
if not isinstance(templates, dict):
|
||||||
default_template = '{base_model}/{first_tag}'
|
default_template = "{base_model}/{first_tag}"
|
||||||
templates = {
|
templates = {
|
||||||
'lora': default_template,
|
"lora": default_template,
|
||||||
'checkpoint': default_template,
|
"checkpoint": default_template,
|
||||||
'embedding': default_template
|
"embedding": default_template,
|
||||||
}
|
}
|
||||||
self.settings['download_path_templates'] = templates
|
self.settings["download_path_templates"] = templates
|
||||||
self._save_settings()
|
self._save_settings()
|
||||||
|
|
||||||
return templates.get(model_type, '{base_model}/{first_tag}')
|
return templates.get(model_type, "{base_model}/{first_tag}")
|
||||||
|
|
||||||
|
|
||||||
_SETTINGS_MANAGER: Optional["SettingsManager"] = None
|
_SETTINGS_MANAGER: Optional["SettingsManager"] = None
|
||||||
|
|||||||
@@ -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.0.0"
|
version = "1.0.1"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp",
|
"aiohttp",
|
||||||
|
|||||||
@@ -1246,10 +1246,7 @@ export class SettingsManager {
|
|||||||
throw new Error('No LoRA roots found');
|
throw new Error('No LoRA roots found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing options except the first one (No Default)
|
|
||||||
const noDefaultOption = defaultLoraRootSelect.querySelector('option[value=""]');
|
|
||||||
defaultLoraRootSelect.innerHTML = '';
|
defaultLoraRootSelect.innerHTML = '';
|
||||||
defaultLoraRootSelect.appendChild(noDefaultOption);
|
|
||||||
|
|
||||||
// Add options for each root
|
// Add options for each root
|
||||||
data.roots.forEach(root => {
|
data.roots.forEach(root => {
|
||||||
@@ -1259,9 +1256,8 @@ export class SettingsManager {
|
|||||||
defaultLoraRootSelect.appendChild(option);
|
defaultLoraRootSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set selected value from settings
|
|
||||||
const defaultRoot = state.global.settings.default_lora_root || '';
|
const defaultRoot = state.global.settings.default_lora_root || '';
|
||||||
defaultLoraRootSelect.value = defaultRoot;
|
defaultLoraRootSelect.value = data.roots.includes(defaultRoot) ? defaultRoot : data.roots[0];
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading LoRA roots:', error);
|
console.error('Error loading LoRA roots:', error);
|
||||||
@@ -1285,10 +1281,7 @@ export class SettingsManager {
|
|||||||
throw new Error('No checkpoint roots found');
|
throw new Error('No checkpoint roots found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing options except first one (No Default)
|
|
||||||
const noDefaultOption = defaultCheckpointRootSelect.querySelector('option[value=""]');
|
|
||||||
defaultCheckpointRootSelect.innerHTML = '';
|
defaultCheckpointRootSelect.innerHTML = '';
|
||||||
defaultCheckpointRootSelect.appendChild(noDefaultOption);
|
|
||||||
|
|
||||||
// Add options for each root
|
// Add options for each root
|
||||||
data.roots.forEach(root => {
|
data.roots.forEach(root => {
|
||||||
@@ -1298,9 +1291,8 @@ export class SettingsManager {
|
|||||||
defaultCheckpointRootSelect.appendChild(option);
|
defaultCheckpointRootSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set selected value from settings
|
|
||||||
const defaultRoot = state.global.settings.default_checkpoint_root || '';
|
const defaultRoot = state.global.settings.default_checkpoint_root || '';
|
||||||
defaultCheckpointRootSelect.value = defaultRoot;
|
defaultCheckpointRootSelect.value = data.roots.includes(defaultRoot) ? defaultRoot : data.roots[0];
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading checkpoint roots:', error);
|
console.error('Error loading checkpoint roots:', error);
|
||||||
@@ -1324,10 +1316,7 @@ export class SettingsManager {
|
|||||||
throw new Error('No diffusion model roots found');
|
throw new Error('No diffusion model roots found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing options except first one (No Default)
|
|
||||||
const noDefaultOption = defaultUnetRootSelect.querySelector('option[value=""]');
|
|
||||||
defaultUnetRootSelect.innerHTML = '';
|
defaultUnetRootSelect.innerHTML = '';
|
||||||
defaultUnetRootSelect.appendChild(noDefaultOption);
|
|
||||||
|
|
||||||
// Add options for each root
|
// Add options for each root
|
||||||
data.roots.forEach(root => {
|
data.roots.forEach(root => {
|
||||||
@@ -1337,9 +1326,8 @@ export class SettingsManager {
|
|||||||
defaultUnetRootSelect.appendChild(option);
|
defaultUnetRootSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set selected value from settings
|
|
||||||
const defaultRoot = state.global.settings.default_unet_root || '';
|
const defaultRoot = state.global.settings.default_unet_root || '';
|
||||||
defaultUnetRootSelect.value = defaultRoot;
|
defaultUnetRootSelect.value = data.roots.includes(defaultRoot) ? defaultRoot : data.roots[0];
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading diffusion model roots:', error);
|
console.error('Error loading diffusion model roots:', error);
|
||||||
@@ -1363,10 +1351,7 @@ export class SettingsManager {
|
|||||||
throw new Error('No embedding roots found');
|
throw new Error('No embedding roots found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing options except first one (No Default)
|
|
||||||
const noDefaultOption = defaultEmbeddingRootSelect.querySelector('option[value=""]');
|
|
||||||
defaultEmbeddingRootSelect.innerHTML = '';
|
defaultEmbeddingRootSelect.innerHTML = '';
|
||||||
defaultEmbeddingRootSelect.appendChild(noDefaultOption);
|
|
||||||
|
|
||||||
// Add options for each root
|
// Add options for each root
|
||||||
data.roots.forEach(root => {
|
data.roots.forEach(root => {
|
||||||
@@ -1376,9 +1361,8 @@ export class SettingsManager {
|
|||||||
defaultEmbeddingRootSelect.appendChild(option);
|
defaultEmbeddingRootSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set selected value from settings
|
|
||||||
const defaultRoot = state.global.settings.default_embedding_root || '';
|
const defaultRoot = state.global.settings.default_embedding_root || '';
|
||||||
defaultEmbeddingRootSelect.value = defaultRoot;
|
defaultEmbeddingRootSelect.value = data.roots.includes(defaultRoot) ? defaultRoot : data.roots[0];
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading embedding roots:', error);
|
console.error('Error loading embedding roots:', error);
|
||||||
@@ -1477,7 +1461,7 @@ export class SettingsManager {
|
|||||||
try {
|
try {
|
||||||
// Save to backend - this triggers path validation
|
// Save to backend - this triggers path validation
|
||||||
await this.saveSetting('extra_folder_paths', extraFolderPaths);
|
await this.saveSetting('extra_folder_paths', extraFolderPaths);
|
||||||
showToast('toast.settings.settingsUpdated', { setting: 'Extra Folder Paths' }, 'success');
|
showToast('settings.extraFolderPaths.saveSuccess', {}, 'success');
|
||||||
|
|
||||||
// Add empty row if no valid paths exist for the changed type
|
// Add empty row if no valid paths exist for the changed type
|
||||||
const container = document.getElementById(`extraFolderPaths-${changedModelType}`);
|
const container = document.getElementById(`extraFolderPaths-${changedModelType}`);
|
||||||
|
|||||||
@@ -484,9 +484,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')">
|
<select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')"></select>
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -500,9 +498,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultCheckpointRoot" onchange="settingsManager.saveSelectSetting('defaultCheckpointRoot', 'default_checkpoint_root')">
|
<select id="defaultCheckpointRoot" onchange="settingsManager.saveSelectSetting('defaultCheckpointRoot', 'default_checkpoint_root')"></select>
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,9 +512,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultUnetRoot" onchange="settingsManager.saveSelectSetting('defaultUnetRoot', 'default_unet_root')">
|
<select id="defaultUnetRoot" onchange="settingsManager.saveSelectSetting('defaultUnetRoot', 'default_unet_root')"></select>
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -532,9 +526,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-control select-control">
|
<div class="setting-control select-control">
|
||||||
<select id="defaultEmbeddingRoot" onchange="settingsManager.saveSelectSetting('defaultEmbeddingRoot', 'default_embedding_root')">
|
<select id="defaultEmbeddingRoot" onchange="settingsManager.saveSelectSetting('defaultEmbeddingRoot', 'default_embedding_root')"></select>
|
||||||
<option value="">{{ t('settings.folderSettings.noDefault') }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -545,7 +537,7 @@
|
|||||||
<div class="settings-subsection-header">
|
<div class="settings-subsection-header">
|
||||||
<h4>
|
<h4>
|
||||||
{{ t('settings.extraFolderPaths.title') }}
|
{{ t('settings.extraFolderPaths.title') }}
|
||||||
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.extraFolderPaths.help') }}"></i>
|
<i class="fas fa-sync-alt restart-required-icon" title="{{ t('settings.extraFolderPaths.restartRequired') }}"></i>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
|||||||
@@ -131,6 +131,102 @@ def test_save_paths_logs_warning_when_upsert_fails(
|
|||||||
assert "Failed to save folder paths: boom" in caplog.text
|
assert "Failed to save folder paths: boom" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_paths_repairs_empty_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||||
|
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
|
||||||
|
|
||||||
|
class FakeSettingsService:
|
||||||
|
def get_libraries(self):
|
||||||
|
return {
|
||||||
|
"comfyui": {
|
||||||
|
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
|
||||||
|
"default_lora_root": "",
|
||||||
|
"default_checkpoint_root": "",
|
||||||
|
"default_embedding_root": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def rename_library(self, *_):
|
||||||
|
raise AssertionError("rename_library should not be invoked")
|
||||||
|
|
||||||
|
def upsert_library(self, name: str, **payload):
|
||||||
|
self.name = name
|
||||||
|
self.payload = payload
|
||||||
|
|
||||||
|
fake_settings = FakeSettingsService()
|
||||||
|
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
|
||||||
|
|
||||||
|
config_module.Config()
|
||||||
|
|
||||||
|
assert fake_settings.name == "comfyui"
|
||||||
|
assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
|
||||||
|
assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/")
|
||||||
|
assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/")
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_paths_repairs_stale_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||||
|
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
|
||||||
|
|
||||||
|
class FakeSettingsService:
|
||||||
|
def get_libraries(self):
|
||||||
|
return {
|
||||||
|
"comfyui": {
|
||||||
|
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
|
||||||
|
"default_lora_root": "/stale/loras",
|
||||||
|
"default_checkpoint_root": "/stale/checkpoints",
|
||||||
|
"default_embedding_root": "/stale/embeddings",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def rename_library(self, *_):
|
||||||
|
raise AssertionError("rename_library should not be invoked")
|
||||||
|
|
||||||
|
def upsert_library(self, name: str, **payload):
|
||||||
|
self.name = name
|
||||||
|
self.payload = payload
|
||||||
|
|
||||||
|
fake_settings = FakeSettingsService()
|
||||||
|
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
|
||||||
|
|
||||||
|
config_module.Config()
|
||||||
|
|
||||||
|
assert fake_settings.name == "comfyui"
|
||||||
|
assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
|
||||||
|
assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/")
|
||||||
|
assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/")
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_paths_keeps_valid_default_roots(monkeypatch: pytest.MonkeyPatch, tmp_path):
|
||||||
|
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
|
||||||
|
|
||||||
|
class FakeSettingsService:
|
||||||
|
def get_libraries(self):
|
||||||
|
return {
|
||||||
|
"comfyui": {
|
||||||
|
"folder_paths": {key: list(value) for key, value in folder_paths.items()},
|
||||||
|
"default_lora_root": folder_paths["loras"][0],
|
||||||
|
"default_checkpoint_root": folder_paths["checkpoints"][0],
|
||||||
|
"default_embedding_root": folder_paths["embeddings"][0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def rename_library(self, *_):
|
||||||
|
raise AssertionError("rename_library should not be invoked")
|
||||||
|
|
||||||
|
def upsert_library(self, name: str, **payload):
|
||||||
|
self.name = name
|
||||||
|
self.payload = payload
|
||||||
|
|
||||||
|
fake_settings = FakeSettingsService()
|
||||||
|
monkeypatch.setattr(settings_manager_module, "settings", fake_settings)
|
||||||
|
|
||||||
|
config_module.Config()
|
||||||
|
|
||||||
|
assert fake_settings.name == "comfyui"
|
||||||
|
assert fake_settings.payload["default_lora_root"] == folder_paths["loras"][0].replace("\\", "/")
|
||||||
|
assert fake_settings.payload["default_checkpoint_root"] == folder_paths["checkpoints"][0].replace("\\", "/")
|
||||||
|
assert fake_settings.payload["default_embedding_root"] == folder_paths["embeddings"][0].replace("\\", "/")
|
||||||
|
|
||||||
|
|
||||||
def test_save_paths_removes_template_default_library(monkeypatch, tmp_path):
|
def test_save_paths_removes_template_default_library(monkeypatch, tmp_path):
|
||||||
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
|
folder_paths = _setup_config_environment(monkeypatch, tmp_path)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ def test_portable_settings_use_project_root(tmp_path, monkeypatch):
|
|||||||
from importlib import reload
|
from importlib import reload
|
||||||
|
|
||||||
settings_paths_module = reload(settings_paths)
|
settings_paths_module = reload(settings_paths)
|
||||||
monkeypatch.setattr(settings_paths_module, "get_project_root", lambda: str(tmp_path))
|
monkeypatch.setattr(
|
||||||
|
settings_paths_module, "get_project_root", lambda: str(tmp_path)
|
||||||
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
settings_paths_module,
|
settings_paths_module,
|
||||||
"user_config_dir",
|
"user_config_dir",
|
||||||
@@ -25,7 +27,9 @@ def test_portable_settings_use_project_root(tmp_path, monkeypatch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
portable_settings = {"use_portable_settings": True}
|
portable_settings = {"use_portable_settings": True}
|
||||||
(tmp_path / "settings.json").write_text(json.dumps(portable_settings), encoding="utf-8")
|
(tmp_path / "settings.json").write_text(
|
||||||
|
json.dumps(portable_settings), encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
config_dir = settings_paths_module.get_settings_dir(create=True)
|
config_dir = settings_paths_module.get_settings_dir(create=True)
|
||||||
assert config_dir == str(tmp_path)
|
assert config_dir == str(tmp_path)
|
||||||
@@ -74,7 +78,9 @@ def test_initial_save_persists_minimal_template(tmp_path, monkeypatch):
|
|||||||
self._seed_template = copy.deepcopy(template)
|
self._seed_template = copy.deepcopy(template)
|
||||||
return copy.deepcopy(template)
|
return copy.deepcopy(template)
|
||||||
|
|
||||||
monkeypatch.setattr(SettingsManager, "_load_settings_template", fake_template_loader)
|
monkeypatch.setattr(
|
||||||
|
SettingsManager, "_load_settings_template", fake_template_loader
|
||||||
|
)
|
||||||
|
|
||||||
manager = SettingsManager()
|
manager = SettingsManager()
|
||||||
|
|
||||||
@@ -118,7 +124,10 @@ def test_existing_folder_paths_seed_default_library(tmp_path, monkeypatch):
|
|||||||
assert "default" in libraries
|
assert "default" in libraries
|
||||||
assert libraries["default"]["folder_paths"]["loras"] == [str(lora_dir)]
|
assert libraries["default"]["folder_paths"]["loras"] == [str(lora_dir)]
|
||||||
assert libraries["default"]["folder_paths"]["checkpoints"] == [str(checkpoint_dir)]
|
assert libraries["default"]["folder_paths"]["checkpoints"] == [str(checkpoint_dir)]
|
||||||
assert libraries["default"]["folder_paths"]["unet"] == [str(diffusion_dir), str(unet_dir)]
|
assert libraries["default"]["folder_paths"]["unet"] == [
|
||||||
|
str(diffusion_dir),
|
||||||
|
str(unet_dir),
|
||||||
|
]
|
||||||
assert libraries["default"]["folder_paths"]["embeddings"] == [str(embedding_dir)]
|
assert libraries["default"]["folder_paths"]["embeddings"] == [str(embedding_dir)]
|
||||||
|
|
||||||
assert manager.get_startup_messages() == []
|
assert manager.get_startup_messages() == []
|
||||||
@@ -138,7 +147,9 @@ def test_environment_variable_overrides_settings(tmp_path, monkeypatch):
|
|||||||
assert mgr.get("civitai_api_key") == "secret"
|
assert mgr.get("civitai_api_key") == "secret"
|
||||||
|
|
||||||
|
|
||||||
def _create_manager_with_settings(tmp_path, monkeypatch, initial_settings, *, save_spy=None):
|
def _create_manager_with_settings(
|
||||||
|
tmp_path, monkeypatch, initial_settings, *, save_spy=None
|
||||||
|
):
|
||||||
"""Helper to instantiate SettingsManager with predefined settings."""
|
"""Helper to instantiate SettingsManager with predefined settings."""
|
||||||
|
|
||||||
fake_settings_path = tmp_path / "settings.json"
|
fake_settings_path = tmp_path / "settings.json"
|
||||||
@@ -203,7 +214,9 @@ def test_switch_to_portable_mode_copies_cache(tmp_path, monkeypatch):
|
|||||||
assert manager.settings_file == str(project_root / "settings.json")
|
assert manager.settings_file == str(project_root / "settings.json")
|
||||||
marker_copy = project_root / "model_cache" / "user_marker.txt"
|
marker_copy = project_root / "model_cache" / "user_marker.txt"
|
||||||
assert marker_copy.read_text(encoding="utf-8") == "user_marker.txt"
|
assert marker_copy.read_text(encoding="utf-8") == "user_marker.txt"
|
||||||
assert (project_root / "model_cache.sqlite").read_text(encoding="utf-8") == "user_db"
|
assert (project_root / "model_cache.sqlite").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
) == "user_db"
|
||||||
assert user_settings.exists()
|
assert user_settings.exists()
|
||||||
|
|
||||||
|
|
||||||
@@ -216,13 +229,17 @@ def test_switching_back_to_user_config_moves_cache(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
project_cache_dir = project_root / "model_cache"
|
project_cache_dir = project_root / "model_cache"
|
||||||
project_cache_dir.mkdir(exist_ok=True)
|
project_cache_dir.mkdir(exist_ok=True)
|
||||||
(project_cache_dir / "project_marker.txt").write_text("project_marker", encoding="utf-8")
|
(project_cache_dir / "project_marker.txt").write_text(
|
||||||
|
"project_marker", encoding="utf-8"
|
||||||
|
)
|
||||||
(project_root / "model_cache.sqlite").write_text("project_db", encoding="utf-8")
|
(project_root / "model_cache.sqlite").write_text("project_db", encoding="utf-8")
|
||||||
|
|
||||||
manager.set("use_portable_settings", False)
|
manager.set("use_portable_settings", False)
|
||||||
|
|
||||||
assert manager.settings_file == str(user_settings)
|
assert manager.settings_file == str(user_settings)
|
||||||
assert (user_dir / "model_cache" / "project_marker.txt").read_text(encoding="utf-8") == "project_marker"
|
assert (user_dir / "model_cache" / "project_marker.txt").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
) == "project_marker"
|
||||||
assert (user_dir / "model_cache.sqlite").read_text(encoding="utf-8") == "project_db"
|
assert (user_dir / "model_cache.sqlite").read_text(encoding="utf-8") == "project_db"
|
||||||
|
|
||||||
|
|
||||||
@@ -242,10 +259,19 @@ def test_download_path_template_invalid_json(manager):
|
|||||||
template = manager.get_download_path_template("checkpoint")
|
template = manager.get_download_path_template("checkpoint")
|
||||||
|
|
||||||
assert template == "{base_model}/{first_tag}"
|
assert template == "{base_model}/{first_tag}"
|
||||||
assert manager.settings["download_path_templates"]["lora"] == "{base_model}/{first_tag}"
|
assert (
|
||||||
|
manager.settings["download_path_templates"]["lora"]
|
||||||
|
== "{base_model}/{first_tag}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_auto_set_default_roots(manager):
|
def test_auto_set_default_roots(manager):
|
||||||
|
# Clear any previously auto-set values to test fresh behavior
|
||||||
|
manager.settings["default_lora_root"] = ""
|
||||||
|
manager.settings["default_checkpoint_root"] = ""
|
||||||
|
manager.settings["default_embedding_root"] = ""
|
||||||
|
manager.settings["default_unet_root"] = ""
|
||||||
|
|
||||||
manager.settings["folder_paths"] = {
|
manager.settings["folder_paths"] = {
|
||||||
"loras": ["/loras"],
|
"loras": ["/loras"],
|
||||||
"checkpoints": ["/checkpoints"],
|
"checkpoints": ["/checkpoints"],
|
||||||
@@ -259,6 +285,48 @@ def test_auto_set_default_roots(manager):
|
|||||||
assert manager.get("default_embedding_root") == "/embeddings"
|
assert manager.get("default_embedding_root") == "/embeddings"
|
||||||
|
|
||||||
|
|
||||||
|
def test_auto_set_default_roots_repairs_stale_values(manager):
|
||||||
|
manager.settings["default_lora_root"] = "/stale-lora"
|
||||||
|
manager.settings["default_checkpoint_root"] = "/stale-checkpoint"
|
||||||
|
manager.settings["default_embedding_root"] = "/stale-embedding"
|
||||||
|
manager.settings["default_unet_root"] = "/stale-unet"
|
||||||
|
|
||||||
|
manager.settings["folder_paths"] = {
|
||||||
|
"loras": ["/loras"],
|
||||||
|
"checkpoints": ["/checkpoints"],
|
||||||
|
"unet": ["/unet"],
|
||||||
|
"embeddings": ["/embeddings"],
|
||||||
|
}
|
||||||
|
|
||||||
|
manager._auto_set_default_roots()
|
||||||
|
|
||||||
|
assert manager.get("default_lora_root") == "/loras"
|
||||||
|
assert manager.get("default_checkpoint_root") == "/checkpoints"
|
||||||
|
assert manager.get("default_unet_root") == "/unet"
|
||||||
|
assert manager.get("default_embedding_root") == "/embeddings"
|
||||||
|
|
||||||
|
|
||||||
|
def test_auto_set_default_roots_keeps_valid_values(manager):
|
||||||
|
manager.settings["default_lora_root"] = "/loras"
|
||||||
|
manager.settings["default_checkpoint_root"] = "/checkpoints"
|
||||||
|
manager.settings["default_embedding_root"] = "/embeddings"
|
||||||
|
manager.settings["default_unet_root"] = "/unet"
|
||||||
|
|
||||||
|
manager.settings["folder_paths"] = {
|
||||||
|
"loras": ["/loras", "/other-loras"],
|
||||||
|
"checkpoints": ["/checkpoints"],
|
||||||
|
"unet": ["/unet", "/other-unet"],
|
||||||
|
"embeddings": ["/embeddings"],
|
||||||
|
}
|
||||||
|
|
||||||
|
manager._auto_set_default_roots()
|
||||||
|
|
||||||
|
assert manager.get("default_lora_root") == "/loras"
|
||||||
|
assert manager.get("default_checkpoint_root") == "/checkpoints"
|
||||||
|
assert manager.get("default_unet_root") == "/unet"
|
||||||
|
assert manager.get("default_embedding_root") == "/embeddings"
|
||||||
|
|
||||||
|
|
||||||
def test_delete_setting(manager):
|
def test_delete_setting(manager):
|
||||||
manager.set("example", 1)
|
manager.set("example", 1)
|
||||||
manager.delete("example")
|
manager.delete("example")
|
||||||
@@ -293,7 +361,14 @@ def test_invalid_mature_blur_level_is_normalized_to_r(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
def test_model_name_display_setting_notifies_scanners(tmp_path, monkeypatch):
|
def test_model_name_display_setting_notifies_scanners(tmp_path, monkeypatch):
|
||||||
initial = {
|
initial = {
|
||||||
"libraries": {"default": {"folder_paths": {}, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": ""}},
|
"libraries": {
|
||||||
|
"default": {
|
||||||
|
"folder_paths": {},
|
||||||
|
"default_lora_root": "",
|
||||||
|
"default_checkpoint_root": "",
|
||||||
|
"default_embedding_root": "",
|
||||||
|
}
|
||||||
|
},
|
||||||
"active_library": "default",
|
"active_library": "default",
|
||||||
"model_name_display": "model_name",
|
"model_name_display": "model_name",
|
||||||
}
|
}
|
||||||
@@ -315,6 +390,7 @@ def test_model_name_display_setting_notifies_scanners(tmp_path, monkeypatch):
|
|||||||
|
|
||||||
dispatched_loops = []
|
dispatched_loops = []
|
||||||
futures = []
|
futures = []
|
||||||
|
|
||||||
def tracking_run_coroutine_threadsafe(coro, target_loop):
|
def tracking_run_coroutine_threadsafe(coro, target_loop):
|
||||||
dispatched_loops.append(target_loop)
|
dispatched_loops.append(target_loop)
|
||||||
future = Future()
|
future = Future()
|
||||||
@@ -335,7 +411,9 @@ def test_model_name_display_setting_notifies_scanners(tmp_path, monkeypatch):
|
|||||||
"get_service_sync",
|
"get_service_sync",
|
||||||
classmethod(fake_get_service_sync),
|
classmethod(fake_get_service_sync),
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(asyncio, "run_coroutine_threadsafe", tracking_run_coroutine_threadsafe)
|
monkeypatch.setattr(
|
||||||
|
asyncio, "run_coroutine_threadsafe", tracking_run_coroutine_threadsafe
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
manager.set("model_name_display", "file_name")
|
manager.set("model_name_display", "file_name")
|
||||||
@@ -354,12 +432,14 @@ def test_migrates_legacy_settings_file(tmp_path, monkeypatch):
|
|||||||
legacy_root = tmp_path / "legacy"
|
legacy_root = tmp_path / "legacy"
|
||||||
legacy_root.mkdir()
|
legacy_root.mkdir()
|
||||||
legacy_file = legacy_root / "settings.json"
|
legacy_file = legacy_root / "settings.json"
|
||||||
legacy_file.write_text("{\"value\": 1}", encoding="utf-8")
|
legacy_file.write_text('{"value": 1}', encoding="utf-8")
|
||||||
|
|
||||||
target_dir = tmp_path / "config"
|
target_dir = tmp_path / "config"
|
||||||
|
|
||||||
monkeypatch.setattr(settings_paths, "get_project_root", lambda: str(legacy_root))
|
monkeypatch.setattr(settings_paths, "get_project_root", lambda: str(legacy_root))
|
||||||
monkeypatch.setattr(settings_paths, "user_config_dir", lambda *_, **__: str(target_dir))
|
monkeypatch.setattr(
|
||||||
|
settings_paths, "user_config_dir", lambda *_, **__: str(target_dir)
|
||||||
|
)
|
||||||
|
|
||||||
migrated_path = settings_paths.ensure_settings_file()
|
migrated_path = settings_paths.ensure_settings_file()
|
||||||
|
|
||||||
@@ -380,7 +460,9 @@ def test_uses_portable_settings_file_when_enabled(tmp_path, monkeypatch):
|
|||||||
user_dir = tmp_path / "user"
|
user_dir = tmp_path / "user"
|
||||||
|
|
||||||
monkeypatch.setattr(settings_paths, "get_project_root", lambda: str(repo_root))
|
monkeypatch.setattr(settings_paths, "get_project_root", lambda: str(repo_root))
|
||||||
monkeypatch.setattr(settings_paths, "user_config_dir", lambda *_, **__: str(user_dir))
|
monkeypatch.setattr(
|
||||||
|
settings_paths, "user_config_dir", lambda *_, **__: str(user_dir)
|
||||||
|
)
|
||||||
|
|
||||||
resolved = settings_paths.ensure_settings_file()
|
resolved = settings_paths.ensure_settings_file()
|
||||||
|
|
||||||
@@ -393,7 +475,9 @@ def test_migrate_creates_default_library(manager):
|
|||||||
libraries = manager.get_libraries()
|
libraries = manager.get_libraries()
|
||||||
assert "default" in libraries
|
assert "default" in libraries
|
||||||
assert manager.get_active_library_name() == "default"
|
assert manager.get_active_library_name() == "default"
|
||||||
assert libraries["default"].get("folder_paths", {}) == manager.settings.get("folder_paths", {})
|
assert libraries["default"].get("folder_paths", {}) == manager.settings.get(
|
||||||
|
"folder_paths", {}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_sanitizes_legacy_libraries(tmp_path, monkeypatch):
|
def test_migrate_sanitizes_legacy_libraries(tmp_path, monkeypatch):
|
||||||
@@ -464,12 +548,21 @@ def test_refresh_environment_variables_updates_stored_value(tmp_path, monkeypatc
|
|||||||
|
|
||||||
initial = {
|
initial = {
|
||||||
"civitai_api_key": "stale",
|
"civitai_api_key": "stale",
|
||||||
"libraries": {"default": {"folder_paths": {}, "default_lora_root": "", "default_checkpoint_root": "", "default_embedding_root": ""}},
|
"libraries": {
|
||||||
|
"default": {
|
||||||
|
"folder_paths": {},
|
||||||
|
"default_lora_root": "",
|
||||||
|
"default_checkpoint_root": "",
|
||||||
|
"default_embedding_root": "",
|
||||||
|
}
|
||||||
|
},
|
||||||
"active_library": "default",
|
"active_library": "default",
|
||||||
}
|
}
|
||||||
|
|
||||||
monkeypatch.setenv("CIVITAI_API_KEY", "from-init")
|
monkeypatch.setenv("CIVITAI_API_KEY", "from-init")
|
||||||
manager = _create_manager_with_settings(tmp_path, monkeypatch, initial, save_spy=save_spy)
|
manager = _create_manager_with_settings(
|
||||||
|
tmp_path, monkeypatch, initial, save_spy=save_spy
|
||||||
|
)
|
||||||
|
|
||||||
assert calls[-1] == "from-init"
|
assert calls[-1] == "from-init"
|
||||||
|
|
||||||
@@ -590,7 +683,9 @@ def test_extra_paths_validation_no_overlap_with_other_libraries(manager, tmp_pat
|
|||||||
manager.update_extra_folder_paths({"loras": [str(lora_dir1)]})
|
manager.update_extra_folder_paths({"loras": [str(lora_dir1)]})
|
||||||
|
|
||||||
|
|
||||||
def test_extra_paths_validation_no_overlap_with_active_primary_lora_root(manager, tmp_path):
|
def test_extra_paths_validation_no_overlap_with_active_primary_lora_root(
|
||||||
|
manager, tmp_path
|
||||||
|
):
|
||||||
"""Test that extra LoRA paths cannot overlap the active library primary LoRA roots."""
|
"""Test that extra LoRA paths cannot overlap the active library primary LoRA roots."""
|
||||||
real_lora_dir = tmp_path / "loras_real"
|
real_lora_dir = tmp_path / "loras_real"
|
||||||
real_lora_dir.mkdir()
|
real_lora_dir.mkdir()
|
||||||
@@ -603,7 +698,9 @@ def test_extra_paths_validation_no_overlap_with_active_primary_lora_root(manager
|
|||||||
activate=True,
|
activate=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="overlap with the active library's primary LoRA roots"):
|
with pytest.raises(
|
||||||
|
ValueError, match="overlap with the active library's primary LoRA roots"
|
||||||
|
):
|
||||||
manager.update_extra_folder_paths({"loras": [str(real_lora_dir)]})
|
manager.update_extra_folder_paths({"loras": [str(real_lora_dir)]})
|
||||||
|
|
||||||
|
|
||||||
@@ -627,7 +724,10 @@ def test_extra_paths_validation_no_overlap_with_active_primary_lora_root_case_in
|
|||||||
original_normcase = settings_manager_module.os.path.normcase
|
original_normcase = settings_manager_module.os.path.normcase
|
||||||
|
|
||||||
def fake_exists(path):
|
def fake_exists(path):
|
||||||
if isinstance(path, str) and path.lower() in {str(lora_link).lower(), str(real_lora_dir).lower()}:
|
if isinstance(path, str) and path.lower() in {
|
||||||
|
str(lora_link).lower(),
|
||||||
|
str(real_lora_dir).lower(),
|
||||||
|
}:
|
||||||
return True
|
return True
|
||||||
return original_exists(path)
|
return original_exists(path)
|
||||||
|
|
||||||
@@ -638,13 +738,21 @@ def test_extra_paths_validation_no_overlap_with_active_primary_lora_root_case_in
|
|||||||
|
|
||||||
monkeypatch.setattr(settings_manager_module.os.path, "exists", fake_exists)
|
monkeypatch.setattr(settings_manager_module.os.path, "exists", fake_exists)
|
||||||
monkeypatch.setattr(settings_manager_module.os.path, "realpath", fake_realpath)
|
monkeypatch.setattr(settings_manager_module.os.path, "realpath", fake_realpath)
|
||||||
monkeypatch.setattr(settings_manager_module.os.path, "normcase", lambda value: original_normcase(value).lower())
|
monkeypatch.setattr(
|
||||||
|
settings_manager_module.os.path,
|
||||||
|
"normcase",
|
||||||
|
lambda value: original_normcase(value).lower(),
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="overlap with the active library's primary LoRA roots"):
|
with pytest.raises(
|
||||||
|
ValueError, match="overlap with the active library's primary LoRA roots"
|
||||||
|
):
|
||||||
manager.update_extra_folder_paths({"loras": [str(real_lora_dir).upper()]})
|
manager.update_extra_folder_paths({"loras": [str(real_lora_dir).upper()]})
|
||||||
|
|
||||||
|
|
||||||
def test_extra_paths_validation_allows_missing_non_overlapping_lora_root(manager, tmp_path):
|
def test_extra_paths_validation_allows_missing_non_overlapping_lora_root(
|
||||||
|
manager, tmp_path
|
||||||
|
):
|
||||||
"""Missing non-overlapping extra LoRA paths should not be rejected."""
|
"""Missing non-overlapping extra LoRA paths should not be rejected."""
|
||||||
lora_dir = tmp_path / "loras"
|
lora_dir = tmp_path / "loras"
|
||||||
lora_dir.mkdir()
|
lora_dir.mkdir()
|
||||||
@@ -662,7 +770,9 @@ def test_extra_paths_validation_allows_missing_non_overlapping_lora_root(manager
|
|||||||
assert extra_paths["loras"] == [str(missing_extra)]
|
assert extra_paths["loras"] == [str(missing_extra)]
|
||||||
|
|
||||||
|
|
||||||
def test_extra_paths_validation_rejects_primary_root_first_level_symlink_target(manager, tmp_path):
|
def test_extra_paths_validation_rejects_primary_root_first_level_symlink_target(
|
||||||
|
manager, tmp_path
|
||||||
|
):
|
||||||
"""Extra LoRA paths should be rejected when already reachable via a first-level symlink under the primary root."""
|
"""Extra LoRA paths should be rejected when already reachable via a first-level symlink under the primary root."""
|
||||||
lora_dir = tmp_path / "loras"
|
lora_dir = tmp_path / "loras"
|
||||||
lora_dir.mkdir()
|
lora_dir.mkdir()
|
||||||
@@ -677,7 +787,9 @@ def test_extra_paths_validation_rejects_primary_root_first_level_symlink_target(
|
|||||||
activate=True,
|
activate=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="overlap with the active library's primary LoRA roots"):
|
with pytest.raises(
|
||||||
|
ValueError, match="overlap with the active library's primary LoRA roots"
|
||||||
|
):
|
||||||
manager.update_extra_folder_paths({"loras": [str(external_dir)]})
|
manager.update_extra_folder_paths({"loras": [str(external_dir)]})
|
||||||
|
|
||||||
|
|
||||||
@@ -698,7 +810,6 @@ def test_delete_library_switches_active(manager, tmp_path):
|
|||||||
assert manager.get_active_library_name() == "default"
|
assert manager.get_active_library_name() == "default"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_download_skip_base_models_are_normalized(manager):
|
def test_download_skip_base_models_are_normalized(manager):
|
||||||
manager.settings["download_skip_base_models"] = [
|
manager.settings["download_skip_base_models"] = [
|
||||||
"SDXL 1.0",
|
"SDXL 1.0",
|
||||||
@@ -715,9 +826,6 @@ def test_download_skip_base_models_are_normalized(manager):
|
|||||||
|
|
||||||
|
|
||||||
def test_setting_download_skip_base_models_normalizes_string_input(manager):
|
def test_setting_download_skip_base_models_normalizes_string_input(manager):
|
||||||
manager.set(
|
manager.set("download_skip_base_models", "SDXL 1.0, Pony; Invalid\nSDXL 1.0")
|
||||||
"download_skip_base_models",
|
|
||||||
"SDXL 1.0, Pony; Invalid\nSDXL 1.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
assert manager.get("download_skip_base_models") == ["SDXL 1.0", "Pony"]
|
assert manager.get("download_skip_base_models") == ["SDXL 1.0", "Pony"]
|
||||||
|
|||||||
Reference in New Issue
Block a user