mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-23 11:41:17 -03:00
Compare commits
20 Commits
818fa34a48
...
v1.0.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24e2909627 | ||
|
|
b768f1368f | ||
|
|
37ccd29fc0 | ||
|
|
7416080cfb | ||
|
|
26be187d42 | ||
|
|
d7caa1fa47 | ||
|
|
2629fcce23 | ||
|
|
438e7d07b9 | ||
|
|
e9932ea870 | ||
|
|
5dd8b96422 | ||
|
|
5e1cf68bbd | ||
|
|
1044fa3c83 | ||
|
|
397892bb7f | ||
|
|
f105500740 | ||
|
|
806555cf06 | ||
|
|
5cd7204101 | ||
|
|
3b602a3698 | ||
|
|
15dfaed462 | ||
|
|
0e51851025 | ||
|
|
0d0f4defca |
@@ -15,41 +15,60 @@
|
||||
"Phil",
|
||||
"Carl G.",
|
||||
"Arlecchino Shion",
|
||||
"$MetaSamsara",
|
||||
"Charles Blakemore",
|
||||
"Rob Williams",
|
||||
"$MetaSamsara",
|
||||
"stone9k",
|
||||
"Rosenthal",
|
||||
"Francisco Tatis",
|
||||
"runte3221",
|
||||
"Fraser Cross",
|
||||
"Polymorphic Indeterminate",
|
||||
"Marc Whiffen",
|
||||
"Skalabananen",
|
||||
"Birdy",
|
||||
"Kiba",
|
||||
"Mozzel",
|
||||
"itismyelement",
|
||||
"Gingko Biloba",
|
||||
"Reno Lam",
|
||||
"onesecondinosaur",
|
||||
"sig",
|
||||
"Christian Byrne",
|
||||
"DM",
|
||||
"Sen314",
|
||||
"Estragon",
|
||||
"J\\B/ 8r0wns0n",
|
||||
"Takkan",
|
||||
"Charles Blakemore",
|
||||
"Rosenthal",
|
||||
"ClockDaemon",
|
||||
"Francisco Tatis",
|
||||
"KD",
|
||||
"Omnidex",
|
||||
"Release Cabrakan",
|
||||
"Tobi_Swagg",
|
||||
"SG",
|
||||
"James Dooley",
|
||||
"zenbound",
|
||||
"jmack",
|
||||
"Andrew Wilson",
|
||||
"Greybush",
|
||||
"Mark Corneglio",
|
||||
"SarcasticHashtag",
|
||||
"iamresist",
|
||||
"Wolffen",
|
||||
"Ricky Carter",
|
||||
"James Todd",
|
||||
"JongWon Han",
|
||||
"VantAI",
|
||||
"Tim",
|
||||
"Lisster",
|
||||
"Michael Wong",
|
||||
"Illrigger",
|
||||
"Tom Corrigan",
|
||||
"JackieWang",
|
||||
"FreelancerZ",
|
||||
"fnkylove",
|
||||
"Yushio",
|
||||
"Vik71it",
|
||||
"Echo",
|
||||
"Lilleman",
|
||||
"Robert Stacey",
|
||||
@@ -58,59 +77,54 @@
|
||||
"Jorge Hussni",
|
||||
"Liam MacDougal",
|
||||
"Sterilized",
|
||||
"Fraser Cross",
|
||||
"Polymorphic Indeterminate",
|
||||
"Marc Whiffen",
|
||||
"Birdy",
|
||||
"Skalabananen",
|
||||
"BadassArabianMofo",
|
||||
"quarz",
|
||||
"Reno Lam",
|
||||
"Greg",
|
||||
"JSST",
|
||||
"sig",
|
||||
"J\\B/ 8r0wns0n",
|
||||
"Snaggwort",
|
||||
"lmsupporter",
|
||||
"wfpearl",
|
||||
"Baekdoosixt",
|
||||
"Jonathan Ross",
|
||||
"KD",
|
||||
"Omnidex",
|
||||
"Jack B Nimble",
|
||||
"Nazono_hito",
|
||||
"Melville Parrish",
|
||||
"daniel dove",
|
||||
"Lustre",
|
||||
"Tyler Trebuchon",
|
||||
"Release Cabrakan",
|
||||
"JW Sin",
|
||||
"contrite831",
|
||||
"Alex",
|
||||
"bh",
|
||||
"carozzz",
|
||||
"Marlon Daniels",
|
||||
"James Dooley",
|
||||
"zenbound",
|
||||
"Starkselle",
|
||||
"Buzzard",
|
||||
"Aaron Bleuer",
|
||||
"LacesOut!",
|
||||
"greebles",
|
||||
"Adam Shaw",
|
||||
"Mark Corneglio",
|
||||
"SarcasticHashtag",
|
||||
"Anthony Rizzo",
|
||||
"Gooohokrbe",
|
||||
"RedrockVP",
|
||||
"James Todd",
|
||||
"ASLPro3D",
|
||||
"OldBones",
|
||||
"Jacob Hoehler",
|
||||
"FinalyFree",
|
||||
"Weasyl",
|
||||
"Steven Pfeiffer",
|
||||
"Timmy",
|
||||
"Johnny",
|
||||
"Cory Paza",
|
||||
"Tak",
|
||||
"Lisster",
|
||||
"Zach Gonser",
|
||||
"Big Red",
|
||||
"whudunit",
|
||||
"Luc Job",
|
||||
"dl0901dm",
|
||||
"Philip Hempel",
|
||||
"corde",
|
||||
"Nick Walker",
|
||||
"Yushio",
|
||||
"Vik71it",
|
||||
"Bishoujoker",
|
||||
"Todd Keck",
|
||||
"Briton Heilbrun",
|
||||
@@ -119,47 +133,50 @@
|
||||
"jean jahren",
|
||||
"Aleksander Wujczyk",
|
||||
"AM Kuro",
|
||||
"BadassArabianMofo",
|
||||
"Pascal Dahle",
|
||||
"Penfore",
|
||||
"Greg",
|
||||
"Sangheili460",
|
||||
"MagnaInsomnia",
|
||||
"Karl P.",
|
||||
"Akira_HentAI",
|
||||
"Gordon Cole",
|
||||
"AbstractAss",
|
||||
"lmsupporter",
|
||||
"andrew.tappan",
|
||||
"N/A",
|
||||
"The Spawn",
|
||||
"graysock",
|
||||
"Greenmoustache",
|
||||
"zounic",
|
||||
"wfpearl",
|
||||
"fancypants",
|
||||
"Eldithor",
|
||||
"Jack B Nimble",
|
||||
"Digital",
|
||||
"JaxMax",
|
||||
"bh",
|
||||
"takyamtom",
|
||||
"Jwk0205",
|
||||
"Starkselle",
|
||||
"batblue",
|
||||
"carey6409",
|
||||
"Olive",
|
||||
"Aaron Bleuer",
|
||||
"LacesOut!",
|
||||
"greebles",
|
||||
"Some Guy Named Barry",
|
||||
"Cosmosis",
|
||||
"M Postkasse",
|
||||
"AELOX",
|
||||
"Nicfit23",
|
||||
"FloPro4Sho",
|
||||
"wamekukyouzin",
|
||||
"Jacob Hoehler",
|
||||
"drum matthieu",
|
||||
"Dogmaster",
|
||||
"Matt Wenzel",
|
||||
"Weasyl",
|
||||
"Lex Song",
|
||||
"Cory Paza",
|
||||
"Christopher Michel",
|
||||
"Gonzalo Andre Allendes Lopez",
|
||||
"Serge Bekenkamp",
|
||||
"Jimmy Ledbetter",
|
||||
"Philip Hempel",
|
||||
"LeoZero",
|
||||
"ApathyJones",
|
||||
"Julian V",
|
||||
"Steven Owens",
|
||||
"nahinahi9",
|
||||
"Dustin Chen",
|
||||
"dan",
|
||||
"aai",
|
||||
"Mouthlessman",
|
||||
@@ -167,32 +184,29 @@
|
||||
"ViperC",
|
||||
"Ran C",
|
||||
"MiraiKuriyamaSy",
|
||||
"Sangheili460",
|
||||
"Karl P.",
|
||||
"yuxz69",
|
||||
"Adam Taylor",
|
||||
"Weird_With_A_Beard",
|
||||
"esthe",
|
||||
"The Spawn",
|
||||
"graysock",
|
||||
"Pozadine1",
|
||||
"Qarob",
|
||||
"AIGooner",
|
||||
"Luc",
|
||||
"ProtonPrince",
|
||||
"DiffDuck",
|
||||
"fancypants",
|
||||
"elu3199",
|
||||
"Hasturkun",
|
||||
"Jon Sandman",
|
||||
"Ubivis",
|
||||
"CloudValley",
|
||||
"IamAyam",
|
||||
"Joboshy",
|
||||
"Digital",
|
||||
"takyamtom",
|
||||
"Bohemian Corporal",
|
||||
"Dan",
|
||||
"confiscated Zyra",
|
||||
"Bro Xie",
|
||||
"yer fey",
|
||||
"batblue",
|
||||
"carey6409",
|
||||
"Error_Rule34_Not_found",
|
||||
"太郎 ゲーム",
|
||||
"Roslynd",
|
||||
"Tee Gee",
|
||||
@@ -200,42 +214,31 @@
|
||||
"tarek helmi",
|
||||
"Neco28",
|
||||
"Max Marklund",
|
||||
"AELOX",
|
||||
"David Ortega",
|
||||
"Dankin",
|
||||
"Nicfit23",
|
||||
"Cristian Vazquez",
|
||||
"drum matthieu",
|
||||
"Dogmaster",
|
||||
"Frank Nitty",
|
||||
"Magic Noob",
|
||||
"Pronredn",
|
||||
"Christopher Michel",
|
||||
"DougPeterson",
|
||||
"LeoZero",
|
||||
"Antonio Pontes",
|
||||
"Bruce",
|
||||
"nahinahi9",
|
||||
"lh qwe",
|
||||
"Kevin John Duck",
|
||||
"conner",
|
||||
"Dustin Chen",
|
||||
"Kevin Christopher",
|
||||
"Blackfish95",
|
||||
"dd",
|
||||
"Princess Bright Eyes",
|
||||
"Paul Kroll",
|
||||
"Felipe dos Santos",
|
||||
"Bas Imagineer",
|
||||
"Markus",
|
||||
"John Statham",
|
||||
"Douglas Gaspar",
|
||||
"AlexDuKaNa",
|
||||
"George",
|
||||
"dw",
|
||||
"decoy",
|
||||
"elu3199",
|
||||
"Hasturkun",
|
||||
"Jon Sandman",
|
||||
"Ubivis",
|
||||
"CloudValley",
|
||||
"thesoftwaredruid",
|
||||
"wundershark",
|
||||
"mr_dinosaur",
|
||||
@@ -243,57 +246,62 @@
|
||||
"Ray Wing",
|
||||
"Ranzitho",
|
||||
"Gus",
|
||||
"地獄の禄",
|
||||
"MJG",
|
||||
"David LaVallee",
|
||||
"linnfrey",
|
||||
"ae",
|
||||
"Tr4shP4nda",
|
||||
"WRL_SPR",
|
||||
"capn",
|
||||
"Joseph",
|
||||
"Mirko Katzula",
|
||||
"奚明 刘",
|
||||
"Brian M",
|
||||
"Josef Lanzl",
|
||||
"Nerezza",
|
||||
"sanborondon",
|
||||
"Griffin Dahlberg",
|
||||
"준희 김",
|
||||
"Error_Rule34_Not_found",
|
||||
"Taylor Funk",
|
||||
"aezin",
|
||||
"Thought2Form",
|
||||
"jcay015",
|
||||
"Gerald Welly",
|
||||
"Kevin Picco",
|
||||
"Erik Lopez",
|
||||
"Mateo Curić",
|
||||
"Geolog",
|
||||
"Eris3D",
|
||||
"Tomohiro Baba",
|
||||
"David Ortega",
|
||||
"m",
|
||||
"Noora",
|
||||
"Pierce McBride",
|
||||
"Mattssn",
|
||||
"Jamie Ogletree",
|
||||
"a _",
|
||||
"Jeff",
|
||||
"James Coleman",
|
||||
"Kevin Christopher",
|
||||
"Emil Andersson",
|
||||
"Ouro Boros",
|
||||
"Chad Idk",
|
||||
"dd",
|
||||
"Steam Steam",
|
||||
"CryptoTraderJK",
|
||||
"Yuji Kaneko",
|
||||
"Davaitamin",
|
||||
"Dušan Ryban",
|
||||
"Rops Alot",
|
||||
"tedcor",
|
||||
"Sam",
|
||||
"Fotek Design",
|
||||
"sjon kreutz",
|
||||
"Ace Ventura",
|
||||
"MadSpin",
|
||||
"Metryman55",
|
||||
"inbijiburu",
|
||||
"Nick “Loadstone” D",
|
||||
"地獄の禄",
|
||||
"ae",
|
||||
"Tr4shP4nda",
|
||||
"Gamalonia",
|
||||
"WRL_SPR",
|
||||
"capn",
|
||||
"Joseph",
|
||||
"momokai",
|
||||
"Mirko Katzula",
|
||||
"dan",
|
||||
"Piccio08",
|
||||
"kumakichi",
|
||||
@@ -306,59 +314,13 @@
|
||||
"kudari",
|
||||
"Naomi Hale Danchi",
|
||||
"dc7431",
|
||||
"ken",
|
||||
"epicgamer0020690",
|
||||
"Joshua Porrata",
|
||||
"keemun",
|
||||
"SuBu",
|
||||
"RedPIXel",
|
||||
"Vir",
|
||||
"Richard",
|
||||
"Andrew",
|
||||
"Brian M",
|
||||
"Robert Wegemund",
|
||||
"Littlehuggy",
|
||||
"Draven T",
|
||||
"mrjuan",
|
||||
"Brian Buie",
|
||||
"Thought2Form",
|
||||
"Kevin Picco",
|
||||
"Sadlip",
|
||||
"Aquatic Coffee",
|
||||
"m",
|
||||
"ethanfel",
|
||||
"Pierce McBride",
|
||||
"Joshua Gray",
|
||||
"Focuschannel",
|
||||
"Mikko Hemilä",
|
||||
"Jacob McDaniel",
|
||||
"Jamie Ogletree",
|
||||
"Temikus",
|
||||
"Artokun",
|
||||
"Michael Taylor",
|
||||
"Derek Baker",
|
||||
"Martial",
|
||||
"Anthony Faxlandez",
|
||||
"battu",
|
||||
"Michael Anthony Scott",
|
||||
"Atilla Berke Pekduyar",
|
||||
"Decx _",
|
||||
"Yuji Kaneko",
|
||||
"Pat Hen",
|
||||
"Jordan Shaw",
|
||||
"Rops Alot",
|
||||
"Thesharingbrother",
|
||||
"Ace Ventura",
|
||||
"ResidentDeviant",
|
||||
"四糸凜音",
|
||||
"Nihongasuki",
|
||||
"JC",
|
||||
"Prompt Pirate",
|
||||
"uwutismxd",
|
||||
"zenobeus",
|
||||
"ken",
|
||||
"Crocket",
|
||||
"keemun",
|
||||
"Wind",
|
||||
"Jackthemind",
|
||||
"Nexus",
|
||||
"Ramneek“Guy”Ashok",
|
||||
"squid_actually",
|
||||
@@ -369,6 +331,53 @@
|
||||
"JohnDoe42054",
|
||||
"BillyHill",
|
||||
"emyth",
|
||||
"Vir",
|
||||
"gzmzmvp",
|
||||
"Richard",
|
||||
"Andrew",
|
||||
"Robert Wegemund",
|
||||
"Littlehuggy",
|
||||
"Draven T",
|
||||
"mrjuan",
|
||||
"Brian Buie",
|
||||
"Sadlip",
|
||||
"Eric Whitney",
|
||||
"Joey Callahan",
|
||||
"Aquatic Coffee",
|
||||
"Ivan Tadic",
|
||||
"Mike Simone",
|
||||
"ethanfel",
|
||||
"Joshua Gray",
|
||||
"Morgandel",
|
||||
"Focuschannel",
|
||||
"Mikko Hemilä",
|
||||
"Noah",
|
||||
"Jacob McDaniel",
|
||||
"X",
|
||||
"Sloan Steddy",
|
||||
"Temikus",
|
||||
"Artokun",
|
||||
"Michael Taylor",
|
||||
"Derek Baker",
|
||||
"Martial",
|
||||
"Anthony Faxlandez",
|
||||
"battu",
|
||||
"Michael Anthony Scott",
|
||||
"Atilla Berke Pekduyar",
|
||||
"Decx _",
|
||||
"Pat Hen",
|
||||
"Jordan Shaw",
|
||||
"Thesharingbrother",
|
||||
"ResidentDeviant",
|
||||
"四糸凜音",
|
||||
"Nihongasuki",
|
||||
"JC",
|
||||
"Prompt Pirate",
|
||||
"uwutismxd",
|
||||
"FrxzenSnxw",
|
||||
"zenobeus",
|
||||
"Crocket",
|
||||
"Jackthemind",
|
||||
"chriphost",
|
||||
"KitKatM",
|
||||
"ryoma",
|
||||
@@ -388,33 +397,42 @@
|
||||
"Menard",
|
||||
"Skyfire83",
|
||||
"Adam Rinehart",
|
||||
"gzmzmvp",
|
||||
"Pitpe11",
|
||||
"TheD1rtyD03",
|
||||
"moonpetal",
|
||||
"SomeDude",
|
||||
"g9p0o",
|
||||
"TheHolySheep",
|
||||
"raf8osz",
|
||||
"Monte Won",
|
||||
"SpringBootisTrash",
|
||||
"carsten",
|
||||
"ikok",
|
||||
"ElitaSSJ4",
|
||||
"Wolfe7D1",
|
||||
"blikkies",
|
||||
"Chris",
|
||||
"Gregory Kozhemiak",
|
||||
"elleshar666",
|
||||
"Shock Shockor",
|
||||
"ACTUALLY_the_Real_Willem_Dafoe",
|
||||
"Goldwaters",
|
||||
"Eric Whitney",
|
||||
"Joey Callahan",
|
||||
"Zude",
|
||||
"Ivan Tadic",
|
||||
"Mike Simone",
|
||||
"John J Linehan",
|
||||
"Kyler",
|
||||
"Elliot E",
|
||||
"Morgandel",
|
||||
"Theerat Jiramate",
|
||||
"Edward Kennedy",
|
||||
"Justin Blaylock",
|
||||
"aRtFuL_DodGeR",
|
||||
"Noah",
|
||||
"X",
|
||||
"Sloan Steddy",
|
||||
"Vane Holzer",
|
||||
"hexxish",
|
||||
"notedfakes",
|
||||
"DarkSunset",
|
||||
"Nathan",
|
||||
"Billy Gladky",
|
||||
"NICHOLAS BAXLEY",
|
||||
"Michael Scott",
|
||||
"Probis",
|
||||
"Ed Wang",
|
||||
"ItsGeneralButtNaked",
|
||||
@@ -424,7 +442,6 @@
|
||||
"Youguang",
|
||||
"Saya",
|
||||
"andrewzpong",
|
||||
"FrxzenSnxw",
|
||||
"BossGame",
|
||||
"lrdchs",
|
||||
"Tree Tagger",
|
||||
@@ -437,17 +454,12 @@
|
||||
"Ginnie",
|
||||
"Raku",
|
||||
"emadsultan",
|
||||
"Pitpe11",
|
||||
"TheD1rtyD03",
|
||||
"moonpetal",
|
||||
"SomeDude",
|
||||
"g9p0o",
|
||||
"Pkrsky",
|
||||
"TheHolySheep",
|
||||
"Monte Won",
|
||||
"SpringBootisTrash",
|
||||
"carsten",
|
||||
"ikok",
|
||||
"nanana",
|
||||
"Pavlaki",
|
||||
"Doug+Rintoul",
|
||||
"Noor",
|
||||
"Yorunai",
|
||||
"quantenmecha",
|
||||
"Jason+Nash",
|
||||
"BillyBoy84",
|
||||
@@ -465,31 +477,27 @@
|
||||
"Welkor",
|
||||
"David Schenck",
|
||||
"John Martin",
|
||||
"Wolfe7D1",
|
||||
"Ink Temptation",
|
||||
"moranqianlong",
|
||||
"Kalli Core",
|
||||
"Time Valentine",
|
||||
"elleshar666",
|
||||
"ACTUALLY_the_Real_Willem_Dafoe",
|
||||
"Михал Михалыч",
|
||||
"Matt",
|
||||
"Kauffy",
|
||||
"Frogmilk",
|
||||
"SPJ",
|
||||
"Kyron Mahan",
|
||||
"Edward Kennedy",
|
||||
"Justin Blaylock",
|
||||
"Bryan Rutkowski",
|
||||
"Nick Kage",
|
||||
"TBitz33",
|
||||
"Anonym dkjglfleeoeldldldlkf",
|
||||
"Vane Holzer",
|
||||
"psytrax",
|
||||
"Cyrus Fett",
|
||||
"Ezokewn",
|
||||
"SendingRavens",
|
||||
"Xenon Xue",
|
||||
"notedfakes",
|
||||
"Edward Ten Eyck",
|
||||
"Michael Docherty",
|
||||
"Michael Scott",
|
||||
"Paul Hartsuyker",
|
||||
"Henrique Faiolli",
|
||||
"elitassj",
|
||||
@@ -497,10 +505,13 @@
|
||||
"Jacob Winter",
|
||||
"Ryan Presley Ng",
|
||||
"Wes Sims",
|
||||
"jinksta187",
|
||||
"Donor4115",
|
||||
"Manu Thetug",
|
||||
"Lyavph",
|
||||
"David",
|
||||
"Meilo",
|
||||
"operationancut",
|
||||
"Filippo Ferrari",
|
||||
"shinonomeiro",
|
||||
"Snille",
|
||||
@@ -509,6 +520,7 @@
|
||||
"xybrightsummer",
|
||||
"jreedatchison",
|
||||
"PhilW",
|
||||
"Marcus thronico",
|
||||
"Janik",
|
||||
"Cruel",
|
||||
"MRBlack",
|
||||
@@ -519,7 +531,13 @@
|
||||
"Scott",
|
||||
"Muratoraccio",
|
||||
"D",
|
||||
"nanana",
|
||||
"MatteKey",
|
||||
"Flob",
|
||||
"ShiroSenpai",
|
||||
"Inkognito",
|
||||
"G",
|
||||
"Tan+Huynh",
|
||||
"D",
|
||||
"Dark_Pest",
|
||||
"Alex",
|
||||
"Jacky+Ho",
|
||||
@@ -535,11 +553,7 @@
|
||||
"sfasdfasfdsa",
|
||||
"Alan+Cano",
|
||||
"FeralOpticsAI",
|
||||
"Pavlaki",
|
||||
"generic404",
|
||||
"Doug+Rintoul",
|
||||
"Noor",
|
||||
"Yorunai",
|
||||
"abattoirblues",
|
||||
"zounik",
|
||||
"4IXplr0r3r",
|
||||
@@ -553,18 +567,21 @@
|
||||
"ja s",
|
||||
"Doug Mason",
|
||||
"Jeremy Townsend",
|
||||
"Dave Abraham",
|
||||
"Joaquin Hierrezuelo",
|
||||
"Locrospiel",
|
||||
"Frogmilk",
|
||||
"Sean voets",
|
||||
"Owen Gwosdz",
|
||||
"SPJ",
|
||||
"Jarrid Lee",
|
||||
"Kor",
|
||||
"Joseph Hanson",
|
||||
"Bryan Rutkowski",
|
||||
"John Rednoulf",
|
||||
"Boba Smith",
|
||||
"Devil Lude",
|
||||
"David Murcko",
|
||||
"Jack Dole",
|
||||
"max blo",
|
||||
"Sauv",
|
||||
"Steven",
|
||||
"CptNeo",
|
||||
"JackJohnnyJim",
|
||||
@@ -572,7 +589,6 @@
|
||||
"Dmitry Ryzhov",
|
||||
"Khánh Đặng",
|
||||
"Maso",
|
||||
"Edward Ten Eyck",
|
||||
"Eric Ketchum",
|
||||
"Kevin Wallace",
|
||||
"Jimmy Borup",
|
||||
@@ -580,14 +596,11 @@
|
||||
"mercur",
|
||||
"Pete Pain",
|
||||
"RHopkirk",
|
||||
"jinksta187",
|
||||
"Andrew Wilkinson",
|
||||
"Yavizu3d",
|
||||
"Maxim",
|
||||
"Manu Thetug",
|
||||
"Karlanx",
|
||||
"Yves Poezevara",
|
||||
"operationancut",
|
||||
"Teriak47",
|
||||
"Just me",
|
||||
"Raf Stahelin",
|
||||
@@ -611,7 +624,6 @@
|
||||
"pixl",
|
||||
"Robin",
|
||||
"chahknoir",
|
||||
"Marcus thronico",
|
||||
"nd",
|
||||
"keno94d",
|
||||
"James Melzer",
|
||||
@@ -635,6 +647,19 @@
|
||||
"SelfishMedic",
|
||||
"adderleighn",
|
||||
"EnragedAntelope",
|
||||
"Kachac",
|
||||
"tyrant2811",
|
||||
"Kevin",
|
||||
"Rune+Osnes",
|
||||
"jcx29",
|
||||
"cloudghost",
|
||||
"Yongkwan+Lee",
|
||||
"PoorStudent",
|
||||
"lucites",
|
||||
"Alex+Zaw",
|
||||
"Mobius2020",
|
||||
"ExLightSaber",
|
||||
"YaboiRay",
|
||||
"Drizzly",
|
||||
"Sildoren",
|
||||
"Darvidous",
|
||||
@@ -658,17 +683,10 @@
|
||||
"you+halo9",
|
||||
"YassineKhaled",
|
||||
"YK12",
|
||||
"MatteKey",
|
||||
"Flob",
|
||||
"ShiroSenpai",
|
||||
"Somebody",
|
||||
"Inkognito",
|
||||
"Somebody",
|
||||
"Gramer+Gumbyte",
|
||||
"Crescent~San",
|
||||
"Tan+Huynh",
|
||||
"AiGirlTS",
|
||||
"D",
|
||||
"datasl4ve",
|
||||
"Somebody",
|
||||
"koopa990",
|
||||
@@ -677,20 +695,25 @@
|
||||
"Bula",
|
||||
"KUJYAKU",
|
||||
"Coeur+de+cochon",
|
||||
"Obsidian.Studios",
|
||||
"han b",
|
||||
"Nico",
|
||||
"Maximilian Krischan",
|
||||
"Banana Joe",
|
||||
"_ G3n",
|
||||
"Donovan Jenkins",
|
||||
"Hans Meier",
|
||||
"Tú Nguyễn Lý Hoàng",
|
||||
"shira1011",
|
||||
"Michael Eid",
|
||||
"beersandbacon",
|
||||
"Neko Desco",
|
||||
"Bob barker",
|
||||
"Ben D",
|
||||
"G",
|
||||
"Ronan Delevacq",
|
||||
"karim ben brik",
|
||||
"Vinarus",
|
||||
"james",
|
||||
"Michael Zhu",
|
||||
"Nemisu",
|
||||
@@ -701,30 +724,30 @@
|
||||
"jumpd",
|
||||
"John C",
|
||||
"Rim",
|
||||
"Dave Abraham",
|
||||
"Joaquin Hierrezuelo",
|
||||
"Jairus Knudsen",
|
||||
"Jarrid Lee",
|
||||
"Poophead27 Blyat",
|
||||
"Xan Dionysus",
|
||||
"Nathan lee",
|
||||
"Lyle Liston",
|
||||
"Middo",
|
||||
"Forbidden Atelier",
|
||||
"John Rednoulf",
|
||||
"Thomas Sankowski",
|
||||
"Spire",
|
||||
"DrB",
|
||||
"AZ Party Oasis",
|
||||
"Adictedtohumping",
|
||||
"Boba Smith",
|
||||
"Towelie",
|
||||
"Ryan Smith",
|
||||
"MR.Bear",
|
||||
"matt",
|
||||
"dsffsdfsdfsdfsdfsdf",
|
||||
"somethingtosay8",
|
||||
"Jean-françois SEMA",
|
||||
"Terminuz",
|
||||
"Kurt",
|
||||
"ivistorm",
|
||||
"Sauv",
|
||||
"Faburizu",
|
||||
"Jack Lawfield",
|
||||
"jimyjomson",
|
||||
"Borte",
|
||||
"Chase Kwon",
|
||||
@@ -744,6 +767,7 @@
|
||||
"hannibal",
|
||||
"Jo+Example",
|
||||
"BrentBertram",
|
||||
"inusanorthcape",
|
||||
"Tigon",
|
||||
"eumelzocker",
|
||||
"dxjaymz",
|
||||
@@ -752,5 +776,5 @@
|
||||
"Somebody",
|
||||
"CK"
|
||||
],
|
||||
"totalCount": 749
|
||||
"totalCount": 773
|
||||
}
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "Lizenz",
|
||||
"noCreditRequired": "Kein Credit erforderlich",
|
||||
"allowSellingGeneratedContent": "Verkauf erlaubt",
|
||||
"allowSellingGeneratedContentTooltip": "Verkauf generierter Bilder erlauben",
|
||||
"noCreditRequiredTooltip": "Modell ohne Nennung des Erstellers verwenden",
|
||||
"noTags": "Keine Tags",
|
||||
"autoTags": "Auto-Tags",
|
||||
"noBaseModelMatches": "Keine Basismodelle entsprechen der aktuellen Suche.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "Download-Backend",
|
||||
"help": "Wähle aus, wie Modelldateien heruntergeladen werden. Python verwendet den eingebauten Downloader. aria2 verwendet den experimentellen externen Downloader-Prozess.",
|
||||
"help": "Wähle aus, wie Modelldateien heruntergeladen werden. Python verwendet den eingebauten Downloader. aria2 verwendet den empfohlenen externen Downloader-Prozess.",
|
||||
"options": {
|
||||
"python": "Python (integriert)",
|
||||
"aria2": "aria2 (experimentell)"
|
||||
"aria2": "aria2 (empfohlen)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "Trigger Words in LoRA-Syntax einschließen",
|
||||
"includeTriggerWordsHelp": "Trainierte Trigger Words beim Kopieren der LoRA-Syntax in die Zwischenablage einschließen"
|
||||
"includeTriggerWordsHelp": "Trainierte Trigger Words beim Kopieren der LoRA-Syntax in die Zwischenablage einschließen",
|
||||
"loraSyntaxFormat": "LoRA-Syntaxformat",
|
||||
"loraSyntaxFormatHelp": "LoRA-Syntaxformat. Der vollständige Pfad enthält den Unterordnerpfad (<lora:style/anime/x:1.0>) für verlustfreie Modellauflösung. Legacy verwendet nur den Dateinamen (<lora:x:1.0>) — A1111-Konvention, kann bei doppelten Dateinamen in verschiedenen Ordnern zu Mehrdeutigkeiten führen.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "Vollständiger Pfad (Unterordner/Name)",
|
||||
"legacy": "Legacy A1111 (nur Name)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "Metadaten-Archiv-Datenbank aktivieren",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "Modellname bearbeiten",
|
||||
"editFileName": "Dateiname bearbeiten",
|
||||
"editBaseModel": "Basis-Modell bearbeiten",
|
||||
"editVersionName": "Versionsname bearbeiten",
|
||||
"viewOnCivitai": "Auf Civitai anzeigen",
|
||||
"viewOnCivitaiText": "Auf Civitai anzeigen",
|
||||
"viewCreatorProfile": "Ersteller-Profil anzeigen",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "Handlungsbedarf",
|
||||
"error": "Aktion erforderlich"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API Key"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "Model Cache Health"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "Duplicate Filename Conflicts"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI Version"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "Erneut ausführen",
|
||||
"exportBundle": "Paket exportieren"
|
||||
"exportBundle": "Paket exportieren",
|
||||
"open-settings": "Open Settings",
|
||||
"open-settings-syntax-format": "Switch to Full Path Syntax",
|
||||
"repair-cache": "Rebuild Cache",
|
||||
"resolve-filename-conflicts": "Resolve Conflicts",
|
||||
"reload-page": "Reload UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "Conflicts",
|
||||
"version": "Version"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "Diagnose konnte nicht geladen werden: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "Auflösung der Dateinamenskonflikte fehlgeschlagen: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "Dateinamenskonflikte auflösen",
|
||||
"message": "Umbenennen durch Anhängen eines 4-stelligen Hashs an jeden doppelten Dateinamen.",
|
||||
"note": "Dieser Vorgang benennt Dateien auf der Festplatte um. Modellreferenzen in vorhandenen Workflows müssen möglicherweise aktualisiert werden, wenn Sie das A1111-Syntaxformat verwenden.",
|
||||
"detail": "Beispiel: <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "Benennt <strong>{count}</strong> Datei(en) in <strong>{groups}</strong> Duplikatgruppe(n) um",
|
||||
"confirm": "Dateien umbenennen",
|
||||
"cancel": "Abbrechen"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "Anwendungs-Update erkannt",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "License",
|
||||
"noCreditRequired": "No Credit Required",
|
||||
"allowSellingGeneratedContent": "Allow Selling",
|
||||
"allowSellingGeneratedContentTooltip": "Allow selling generated images",
|
||||
"noCreditRequiredTooltip": "Use the model without crediting the creator",
|
||||
"noTags": "No tags",
|
||||
"autoTags": "Auto Tags",
|
||||
"noBaseModelMatches": "No base models match the current search.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "Download backend",
|
||||
"help": "Choose how model files are downloaded. Python uses the built-in downloader. aria2 uses the experimental external downloader process.",
|
||||
"help": "Choose how model files are downloaded. Python uses the built-in downloader. aria2 uses the recommended external downloader process.",
|
||||
"options": {
|
||||
"python": "Python (built-in)",
|
||||
"aria2": "aria2 (experimental)"
|
||||
"aria2": "aria2 (recommended)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "Include Trigger Words in LoRA Syntax",
|
||||
"includeTriggerWordsHelp": "Include trained trigger words when copying LoRA syntax to clipboard"
|
||||
"includeTriggerWordsHelp": "Include trained trigger words when copying LoRA syntax to clipboard",
|
||||
"loraSyntaxFormat": "LoRA Syntax Format",
|
||||
"loraSyntaxFormatHelp": "LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "Full path (subfolder/name)",
|
||||
"legacy": "Legacy A1111 (name only)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "Enable Metadata Archive Database",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "Edit model name",
|
||||
"editFileName": "Edit file name",
|
||||
"editBaseModel": "Edit base model",
|
||||
"editVersionName": "Edit version name",
|
||||
"viewOnCivitai": "View on Civitai",
|
||||
"viewOnCivitaiText": "View on Civitai",
|
||||
"viewCreatorProfile": "View Creator Profile",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "Needs Attention",
|
||||
"error": "Action Required"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API Key"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "Model Cache Health"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "Duplicate Filename Conflicts"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI Version"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "Run Again",
|
||||
"exportBundle": "Export Bundle"
|
||||
"exportBundle": "Export Bundle",
|
||||
"open-settings": "Open Settings",
|
||||
"open-settings-syntax-format": "Switch to Full Path Syntax",
|
||||
"repair-cache": "Rebuild Cache",
|
||||
"resolve-filename-conflicts": "Resolve Conflicts",
|
||||
"reload-page": "Reload UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "Conflicts",
|
||||
"version": "Version"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "Failed to load diagnostics: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "Failed to resolve filename conflicts: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "Resolve Filename Conflicts",
|
||||
"message": "Renaming by appending a 4-character hash to each duplicate filename.",
|
||||
"note": "This operation renames files on disk. Model references in existing workflows may need updating if you use the A1111 syntax format.",
|
||||
"detail": "Example: <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "Will rename <strong>{count}</strong> file(s) across <strong>{groups}</strong> duplicate group(s).",
|
||||
"confirm": "Rename Files",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "Application Update Detected",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "Licencia",
|
||||
"noCreditRequired": "Sin crédito requerido",
|
||||
"allowSellingGeneratedContent": "Venta permitida",
|
||||
"allowSellingGeneratedContentTooltip": "Permitir la venta de imágenes generadas",
|
||||
"noCreditRequiredTooltip": "Usar el modelo sin atribuir al creador",
|
||||
"noTags": "Sin etiquetas",
|
||||
"autoTags": "Etiquetas automáticas",
|
||||
"noBaseModelMatches": "Ningún modelo base coincide con la búsqueda actual.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "Backend de descarga",
|
||||
"help": "Elige cómo se descargan los archivos del modelo. Python usa el descargador integrado. aria2 usa el proceso externo experimental de descarga.",
|
||||
"help": "Elige cómo se descargan los archivos del modelo. Python usa el descargador integrado. aria2 usa el proceso externo recomendado de descarga.",
|
||||
"options": {
|
||||
"python": "Python (integrado)",
|
||||
"aria2": "aria2 (experimental)"
|
||||
"aria2": "aria2 (recomendado)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "Incluir palabras clave en la sintaxis de LoRA",
|
||||
"includeTriggerWordsHelp": "Incluir palabras clave entrenadas al copiar la sintaxis de LoRA al portapapeles"
|
||||
"includeTriggerWordsHelp": "Incluir palabras clave entrenadas al copiar la sintaxis de LoRA al portapapeles",
|
||||
"loraSyntaxFormat": "Formato de sintaxis LoRA",
|
||||
"loraSyntaxFormatHelp": "Formato de sintaxis LoRA. El formato completo incluye la ruta de la subcarpeta (<lora:style/anime/x:1.0>) para una resolución de modelo sin pérdidas. El formato heredado usa solo el nombre del archivo (<lora:x:1.0>) — convención A1111, puede ser ambiguo con nombres de archivo duplicados entre carpetas.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "Ruta completa (subcarpeta/nombre)",
|
||||
"legacy": "A1111 heredado (solo nombre)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "Habilitar base de datos de archivo de metadatos",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "Editar nombre del modelo",
|
||||
"editFileName": "Editar nombre de archivo",
|
||||
"editBaseModel": "Editar modelo base",
|
||||
"editVersionName": "Editar nombre de versión",
|
||||
"viewOnCivitai": "Ver en Civitai",
|
||||
"viewOnCivitaiText": "Ver en Civitai",
|
||||
"viewCreatorProfile": "Ver perfil del creador",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "Requiere atención",
|
||||
"error": "Se requiere acción"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API Key"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "Model Cache Health"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "Duplicate Filename Conflicts"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI Version"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "Ejecutar de nuevo",
|
||||
"exportBundle": "Exportar paquete"
|
||||
"exportBundle": "Exportar paquete",
|
||||
"open-settings": "Open Settings",
|
||||
"open-settings-syntax-format": "Switch to Full Path Syntax",
|
||||
"repair-cache": "Rebuild Cache",
|
||||
"resolve-filename-conflicts": "Resolve Conflicts",
|
||||
"reload-page": "Reload UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "Conflicts",
|
||||
"version": "Version"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "Error al cargar los diagnósticos: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "Error al resolver conflictos de nombre de archivo: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "Resolver conflictos de nombres de archivo",
|
||||
"message": "Renombrar añadiendo un hash de 4 caracteres a cada nombre de archivo duplicado.",
|
||||
"note": "Esta operación renombra archivos en el disco. Es posible que las referencias a modelos en flujos de trabajo existentes deban actualizarse si usas el formato de sintaxis A1111.",
|
||||
"detail": "Ejemplo: <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "Renombrará <strong>{count}</strong> archivo(s) en <strong>{groups}</strong> grupo(s) de duplicados",
|
||||
"confirm": "Renombrar archivos",
|
||||
"cancel": "Cancelar"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "Actualización de la aplicación detectada",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "Licence",
|
||||
"noCreditRequired": "Crédit non requis",
|
||||
"allowSellingGeneratedContent": "Vente autorisée",
|
||||
"allowSellingGeneratedContentTooltip": "Autoriser la vente d\"images générées",
|
||||
"noCreditRequiredTooltip": "Utiliser le modèle sans créditer le créateur",
|
||||
"noTags": "Aucun tag",
|
||||
"autoTags": "Auto-Tags",
|
||||
"noBaseModelMatches": "Aucun modèle de base ne correspond à la recherche actuelle.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "Moteur de téléchargement",
|
||||
"help": "Choisissez comment les fichiers de modèles sont téléchargés. Python utilise le téléchargeur intégré. aria2 utilise le processus externe expérimental de téléchargement.",
|
||||
"help": "Choisissez comment les fichiers de modèles sont téléchargés. Python utilise le téléchargeur intégré. aria2 utilise le processus externe recommandé de téléchargement.",
|
||||
"options": {
|
||||
"python": "Python (intégré)",
|
||||
"aria2": "aria2 (expérimental)"
|
||||
"aria2": "aria2 (recommandé)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "Inclure les mots-clés dans la syntaxe LoRA",
|
||||
"includeTriggerWordsHelp": "Inclure les mots-clés d'entraînement lors de la copie de la syntaxe LoRA dans le presse-papiers"
|
||||
"includeTriggerWordsHelp": "Inclure les mots-clés d'entraînement lors de la copie de la syntaxe LoRA dans le presse-papiers",
|
||||
"loraSyntaxFormat": "Format de syntaxe LoRA",
|
||||
"loraSyntaxFormatHelp": "Format de syntaxe LoRA. Le format complet inclut le chemin du sous-dossier (<lora:style/anime/x:1.0>) pour une résolution de modèle sans perte. Le format hérité utilise uniquement le nom du fichier (<lora:x:1.0>) — convention A1111, peut être ambiguë en cas de noms de fichiers en double dans différents dossiers.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "Chemin complet (sous-dossier/nom)",
|
||||
"legacy": "A1111 hérité (nom uniquement)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "Activer la base de données d'archive des métadonnées",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "Modifier le nom du modèle",
|
||||
"editFileName": "Modifier le nom de fichier",
|
||||
"editBaseModel": "Modifier le modèle de base",
|
||||
"editVersionName": "Modifier le nom de la version",
|
||||
"viewOnCivitai": "Voir sur Civitai",
|
||||
"viewOnCivitaiText": "Voir sur Civitai",
|
||||
"viewCreatorProfile": "Voir le profil du créateur",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "Nécessite une attention",
|
||||
"error": "Action requise"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API Key"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "Model Cache Health"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "Duplicate Filename Conflicts"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI Version"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "Relancer",
|
||||
"exportBundle": "Exporter le lot"
|
||||
"exportBundle": "Exporter le lot",
|
||||
"open-settings": "Open Settings",
|
||||
"open-settings-syntax-format": "Switch to Full Path Syntax",
|
||||
"repair-cache": "Rebuild Cache",
|
||||
"resolve-filename-conflicts": "Resolve Conflicts",
|
||||
"reload-page": "Reload UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "Conflicts",
|
||||
"version": "Version"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "Échec du chargement des diagnostics : {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "Échec de la résolution des conflits de nom de fichier : {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "Résoudre les conflits de noms de fichiers",
|
||||
"message": "Renommer en ajoutant un hachage de 4 caractères à chaque nom de fichier en double.",
|
||||
"note": "Cette opération renomme les fichiers sur le disque. Les références de modèle dans les workflows existants peuvent nécessiter une mise à jour si vous utilisez le format de syntaxe A1111.",
|
||||
"detail": "Exemple : <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "Renommera <strong>{count}</strong> fichier(s) dans <strong>{groups}</strong> groupe(s) de doublons",
|
||||
"confirm": "Renommer les fichiers",
|
||||
"cancel": "Annuler"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "Mise à jour de l'application détectée",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "רישיון",
|
||||
"noCreditRequired": "ללא קרדיט נדרש",
|
||||
"allowSellingGeneratedContent": "אפשר מכירה",
|
||||
"allowSellingGeneratedContentTooltip": "אפשר מכירת תמונות שנוצרו",
|
||||
"noCreditRequiredTooltip": "שימוש במודל ללא מתן קרדיט ליוצר",
|
||||
"noTags": "ללא תגיות",
|
||||
"autoTags": "תגיות אוטומטיות",
|
||||
"noBaseModelMatches": "אין מודלי בסיס התואמים לחיפוש הנוכחי.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "מנגנון הורדה",
|
||||
"help": "בחר כיצד יורדים קבצי המודל. Python משתמש במוריד המובנה. aria2 משתמש בתהליך הורדה חיצוני ניסיוני.",
|
||||
"help": "בחר כיצד יורדים קבצי המודל. Python משתמש במוריד המובנה. aria2 משתמש בתהליך הורדה חיצוני מומלץ.",
|
||||
"options": {
|
||||
"python": "Python (מובנה)",
|
||||
"aria2": "aria2 (ניסיוני)"
|
||||
"aria2": "aria2 (מומלץ)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "כלול מילות טריגר בתחביר LoRA",
|
||||
"includeTriggerWordsHelp": "כלול מילות טריגר מאומנות בעת העתקת תחביר LoRA ללוח"
|
||||
"includeTriggerWordsHelp": "כלול מילות טריגר מאומנות בעת העתקת תחביר LoRA ללוח",
|
||||
"loraSyntaxFormat": "פורמט תחביר LoRA",
|
||||
"loraSyntaxFormatHelp": "פורמט תחביר LoRA. נתיב מלא כולל תת-תיקייה (<lora:style/anime/x:1.0>) לפתרון מודל ללא אובדן. גרסה ישנה משתמשת בשם קובץ בלבד (<lora:x:1.0>) — מוסכמת A1111, עלולה להיות לא חד משמעית עם שמות קבצים כפולים בתיקיות שונות.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "נתיב מלא (תת-תיקייה/שם)",
|
||||
"legacy": "A1111 ישן (שם בלבד)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "הפעל מסד נתונים של ארכיון מטא-דאטה",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "ערוך שם מודל",
|
||||
"editFileName": "ערוך שם קובץ",
|
||||
"editBaseModel": "ערוך מודל בסיס",
|
||||
"editVersionName": "ערוך שם גרסה",
|
||||
"viewOnCivitai": "הצג ב-Civitai",
|
||||
"viewOnCivitaiText": "הצג ב-Civitai",
|
||||
"viewCreatorProfile": "הצג פרופיל יוצר",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "דורש תשומת לב",
|
||||
"error": "נדרשת פעולה"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API Key"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "Model Cache Health"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "Duplicate Filename Conflicts"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI Version"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "הפעל שוב",
|
||||
"exportBundle": "ייצוא חבילה"
|
||||
"exportBundle": "ייצוא חבילה",
|
||||
"open-settings": "Open Settings",
|
||||
"open-settings-syntax-format": "Switch to Full Path Syntax",
|
||||
"repair-cache": "Rebuild Cache",
|
||||
"resolve-filename-conflicts": "Resolve Conflicts",
|
||||
"reload-page": "Reload UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "Conflicts",
|
||||
"version": "Version"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "טעינת האבחון נכשלה: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "פתרון התנגשויות שמות קבצים נכשל: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "פתור התנגשויות בשמות קבצים",
|
||||
"message": "שינוי שם על ידי הוספת האש באורך 4 תווים לכל שם קובץ כפול.",
|
||||
"note": "פעולה זו משנה שמות של קבצים בדיסק. ייתכן שיהיה צורך לעדכן הפניות למודלים בזרימות עבודה קיימות אם אתה משתמש בפורמט התחביר A1111.",
|
||||
"detail": "דוגמה: <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "ישנה שם של <strong>{count}</strong> קבצים ב-<strong>{groups}</strong> קבוצות כפולות",
|
||||
"confirm": "שנה שמות קבצים",
|
||||
"cancel": "ביטול"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "זוהה עדכון יישום",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "ライセンス",
|
||||
"noCreditRequired": "クレジット不要",
|
||||
"allowSellingGeneratedContent": "販売許可",
|
||||
"allowSellingGeneratedContentTooltip": "生成した画像の販売を許可",
|
||||
"noCreditRequiredTooltip": "クレジット表記なしでモデルを使用可能",
|
||||
"noTags": "タグなし",
|
||||
"autoTags": "自動タグ",
|
||||
"noBaseModelMatches": "現在の検索に一致するベースモデルはありません。",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "ダウンロードバックエンド",
|
||||
"help": "モデルファイルのダウンロード方法を選択します。Python は内蔵ダウンローダーを使用し、aria2 は実験的な外部ダウンローダープロセスを使用します。",
|
||||
"help": "モデルファイルのダウンロード方法を選択します。Python は内蔵ダウンローダーを使用し、aria2 は推奨の外部ダウンローダープロセスを使用します。",
|
||||
"options": {
|
||||
"python": "Python(内蔵)",
|
||||
"aria2": "aria2(実験的)"
|
||||
"aria2": "aria2(推奨)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "LoRA構文にトリガーワードを含める",
|
||||
"includeTriggerWordsHelp": "LoRA構文をクリップボードにコピーする際、学習済みトリガーワードを含めます"
|
||||
"includeTriggerWordsHelp": "LoRA構文をクリップボードにコピーする際、学習済みトリガーワードを含めます",
|
||||
"loraSyntaxFormat": "LoRA構文形式",
|
||||
"loraSyntaxFormatHelp": "LoRA構文形式。フルパスはサブフォルダパスを含み(<lora:style/anime/x:1.0>)、モデルをロスレスで解決します。レガシーはファイル名のみ(<lora:x:1.0>)— A1111規約ですが、フォルダ間でファイル名が重複する場合に曖昧になる可能性があります。",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "フルパス(サブフォルダ/名前)",
|
||||
"legacy": "レガシーA1111(名前のみ)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "メタデータアーカイブデータベースを有効化",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "モデル名を編集",
|
||||
"editFileName": "ファイル名を編集",
|
||||
"editBaseModel": "ベースモデルを編集",
|
||||
"editVersionName": "バージョン名を編集",
|
||||
"viewOnCivitai": "Civitaiで表示",
|
||||
"viewOnCivitaiText": "Civitaiで表示",
|
||||
"viewCreatorProfile": "作成者プロフィールを表示",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "要注意",
|
||||
"error": "対応が必要"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API キー"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "モデルキャッシュの健全性"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "ファイル名重複競合"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI バージョン"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "再実行",
|
||||
"exportBundle": "パッケージをエクスポート"
|
||||
"exportBundle": "パッケージをエクスポート",
|
||||
"open-settings": "設定を開く",
|
||||
"open-settings-syntax-format": "フルパス構文に切り替え",
|
||||
"repair-cache": "キャッシュを再構築",
|
||||
"resolve-filename-conflicts": "競合を解決",
|
||||
"reload-page": "UI をリロード"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "競合",
|
||||
"version": "バージョン"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "診断の読み込みに失敗しました: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "ファイル名競合の解決に失敗しました: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "ファイル名の競合を解決",
|
||||
"message": "重複したファイル名に4文字のハッシュを追加してリネームします。",
|
||||
"note": "この操作はディスク上のファイルをリネームします。A1111 構文形式を使用している場合、既存のワークフロー内のモデル参照を更新する必要があるかもしれません。",
|
||||
"detail": "例:<code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "<strong>{groups}</strong> 組の重複にわたって <strong>{count}</strong> 個のファイルをリネームします",
|
||||
"confirm": "ファイルをリネーム",
|
||||
"cancel": "キャンセル"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "アプリケーション更新が検出されました",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "라이선스",
|
||||
"noCreditRequired": "크레딧 표기 없음",
|
||||
"allowSellingGeneratedContent": "판매 허용",
|
||||
"allowSellingGeneratedContentTooltip": "생성된 이미지 판매 허용",
|
||||
"noCreditRequiredTooltip": "크리에이터 저작자 표시 없이 모델 사용 가능",
|
||||
"noTags": "태그 없음",
|
||||
"autoTags": "자동 태그",
|
||||
"noBaseModelMatches": "현재 검색과 일치하는 베이스 모델이 없습니다.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "다운로드 백엔드",
|
||||
"help": "모델 파일을 다운로드하는 방식을 선택합니다. Python은 내장 다운로더를 사용하고, aria2는 실험적인 외부 다운로더 프로세스를 사용합니다.",
|
||||
"help": "모델 파일을 다운로드하는 방식을 선택합니다. Python은 내장 다운로더를 사용하고, aria2는 권장되는 외부 다운로더 프로세스를 사용합니다.",
|
||||
"options": {
|
||||
"python": "Python(내장)",
|
||||
"aria2": "aria2(실험적)"
|
||||
"aria2": "aria2(권장)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "LoRA 문법에 트리거 단어 포함",
|
||||
"includeTriggerWordsHelp": "LoRA 문법을 클립보드에 복사할 때 학습된 트리거 단어를 포함합니다"
|
||||
"includeTriggerWordsHelp": "LoRA 문법을 클립보드에 복사할 때 학습된 트리거 단어를 포함합니다",
|
||||
"loraSyntaxFormat": "LoRA 구문 형식",
|
||||
"loraSyntaxFormatHelp": "LoRA 구문 형식. 전체 경로는 하위 폴더 경로(<lora:style/anime/x:1.0>)를 포함하여 손실 없는 모델 해상도를 제공합니다. 레거시는 파일 이름만(<lora:x:1.0>) 사용 — A1111 규칙이지만, 폴더 간 파일명 중복 시 모호할 수 있습니다.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "전체 경로(하위 폴더/이름)",
|
||||
"legacy": "레거시 A1111(이름만)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "메타데이터 아카이브 데이터베이스 활성화",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "모델명 편집",
|
||||
"editFileName": "파일명 편집",
|
||||
"editBaseModel": "베이스 모델 편집",
|
||||
"editVersionName": "버전명 편집",
|
||||
"viewOnCivitai": "Civitai에서 보기",
|
||||
"viewOnCivitaiText": "Civitai에서 보기",
|
||||
"viewCreatorProfile": "제작자 프로필 보기",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "주의 필요",
|
||||
"error": "조치 필요"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API 키"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "모델 캐시 상태"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "파일명 중복 충돌"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI 버전"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "다시 실행",
|
||||
"exportBundle": "번들 내보내기"
|
||||
"exportBundle": "번들 내보내기",
|
||||
"open-settings": "설정 열기",
|
||||
"open-settings-syntax-format": "전체 경로 구문으로 전환",
|
||||
"repair-cache": "캐시 재구축",
|
||||
"resolve-filename-conflicts": "충돌 해결",
|
||||
"reload-page": "UI 새로고침"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "충돌",
|
||||
"version": "버전"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "진단 로드 실패: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "파일명 충돌 해결 실패: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "파일명 충돌 해결",
|
||||
"message": "중복 파일명에 4자리 해시를 추가하여 이름을 변경합니다.",
|
||||
"note": "이 작업은 디스크에 있는 파일의 이름을 변경합니다. A1111 구문 형식을 사용하는 경우 기존 워크플로우의 모델 참조를 업데이트해야 할 수 있습니다.",
|
||||
"detail": "예시: <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "<strong>{groups}</strong>개 중복 그룹에서 <strong>{count}</strong>개 파일 이름을 변경합니다",
|
||||
"confirm": "파일 이름 변경",
|
||||
"cancel": "취소"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "애플리케이션 업데이트 감지",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "Лицензия",
|
||||
"noCreditRequired": "Без указания авторства",
|
||||
"allowSellingGeneratedContent": "Продажа разрешена",
|
||||
"allowSellingGeneratedContentTooltip": "Разрешить продажу сгенерированных изображений",
|
||||
"noCreditRequiredTooltip": "Использование модели без указания автора",
|
||||
"noTags": "Без тегов",
|
||||
"autoTags": "Авто-теги",
|
||||
"noBaseModelMatches": "Нет базовых моделей, соответствующих текущему поиску.",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "Бэкенд загрузки",
|
||||
"help": "Выберите способ загрузки файлов моделей. Python использует встроенный загрузчик. aria2 использует экспериментальный внешний процесс загрузки.",
|
||||
"help": "Выберите способ загрузки файлов моделей. Python использует встроенный загрузчик. aria2 использует рекомендуемый внешний процесс загрузки.",
|
||||
"options": {
|
||||
"python": "Python (встроенный)",
|
||||
"aria2": "aria2 (экспериментальный)"
|
||||
"aria2": "aria2 (рекомендуемый)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "Включать триггерные слова в синтаксис LoRA",
|
||||
"includeTriggerWordsHelp": "Включать обученные триггерные слова при копировании синтаксиса LoRA в буфер обмена"
|
||||
"includeTriggerWordsHelp": "Включать обученные триггерные слова при копировании синтаксиса LoRA в буфер обмена",
|
||||
"loraSyntaxFormat": "Формат синтаксиса LoRA",
|
||||
"loraSyntaxFormatHelp": "Формат синтаксиса LoRA. Полный путь включает подпапку (<lora:style/anime/x:1.0>) для безпотерьного разрешения модели. Устаревший использует только имя файла (<lora:x:1.0>) — соглашение A1111, может быть неоднозначным при дублировании имён файлов в разных папках.",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "Полный путь (подпапка/имя)",
|
||||
"legacy": "Устаревший A1111 (только имя)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "Включить архив метаданных",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "Редактировать название модели",
|
||||
"editFileName": "Редактировать имя файла",
|
||||
"editBaseModel": "Редактировать базовую модель",
|
||||
"editVersionName": "Редактировать название версии",
|
||||
"viewOnCivitai": "Посмотреть на Civitai",
|
||||
"viewOnCivitaiText": "Посмотреть на Civitai",
|
||||
"viewCreatorProfile": "Посмотреть профиль создателя",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "Требует внимания",
|
||||
"error": "Требуется действие"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API Key"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "Model Cache Health"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "Duplicate Filename Conflicts"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI Version"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "Запустить снова",
|
||||
"exportBundle": "Экспортировать пакет"
|
||||
"exportBundle": "Экспортировать пакет",
|
||||
"open-settings": "Open Settings",
|
||||
"open-settings-syntax-format": "Switch to Full Path Syntax",
|
||||
"repair-cache": "Rebuild Cache",
|
||||
"resolve-filename-conflicts": "Resolve Conflicts",
|
||||
"reload-page": "Reload UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "Conflicts",
|
||||
"version": "Version"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "Не удалось загрузить диагностику: {message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "Не удалось разрешить конфликты имён файлов: {message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "Разрешить конфликты имён файлов",
|
||||
"message": "Переименование с добавлением 4-символьного хеша к каждому дублирующемуся имени файла.",
|
||||
"note": "Эта операция переименовывает файлы на диске. Если вы используете синтаксис A1111, ссылки на модели в существующих рабочих процессах могут потребовать обновления.",
|
||||
"detail": "Пример: <code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "Будет переименовано <strong>{count}</strong> файл(ов) в <strong>{groups}</strong> группе(ах) дубликатов",
|
||||
"confirm": "Переименовать файлы",
|
||||
"cancel": "Отмена"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "Обнаружено обновление приложения",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "许可证",
|
||||
"noCreditRequired": "无需署名",
|
||||
"allowSellingGeneratedContent": "允许销售",
|
||||
"allowSellingGeneratedContentTooltip": "允许出售生成的图片",
|
||||
"noCreditRequiredTooltip": "使用模型时无需注明原作者",
|
||||
"noTags": "无标签",
|
||||
"autoTags": "自动标签",
|
||||
"noBaseModelMatches": "没有基础模型符合当前搜索。",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "下载后端",
|
||||
"help": "选择模型文件的下载方式。Python 使用内置下载器。aria2 使用实验性的外部下载进程。",
|
||||
"help": "选择模型文件的下载方式。Python 使用内置下载器。aria2 使用推荐的外部下载进程。",
|
||||
"options": {
|
||||
"python": "Python(内置)",
|
||||
"aria2": "aria2(实验性)"
|
||||
"aria2": "aria2(推荐)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "复制 LoRA 语法时包含触发词",
|
||||
"includeTriggerWordsHelp": "复制 LoRA 语法到剪贴板时包含训练触发词"
|
||||
"includeTriggerWordsHelp": "复制 LoRA 语法到剪贴板时包含训练触发词",
|
||||
"loraSyntaxFormat": "LoRA 语法格式",
|
||||
"loraSyntaxFormatHelp": "LoRA 语法格式。完整路径(Full)包含子文件夹路径 (<lora:style/anime/x:1.0>),解析精确无歧义。旧版(Legacy)仅使用文件名 (<lora:x:1.0>)——A1111 原始约定,同名文件跨文件夹时可能产生歧义。",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "完整路径(子文件夹/名称)",
|
||||
"legacy": "旧版 A1111(仅名称)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "启用元数据归档数据库",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "编辑模型名称",
|
||||
"editFileName": "编辑文件名",
|
||||
"editBaseModel": "编辑基础模型",
|
||||
"editVersionName": "编辑版本名称",
|
||||
"viewOnCivitai": "在 Civitai 查看",
|
||||
"viewOnCivitaiText": "在 Civitai 查看",
|
||||
"viewCreatorProfile": "查看创作者主页",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "需要关注",
|
||||
"error": "需要处理"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API 密钥"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "模型缓存健康状态"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "文件名重复冲突"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI 版本"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "重新检查",
|
||||
"exportBundle": "导出诊断包"
|
||||
"exportBundle": "导出诊断包",
|
||||
"open-settings": "打开设置",
|
||||
"open-settings-syntax-format": "切换为完整路径语法",
|
||||
"repair-cache": "重建缓存",
|
||||
"resolve-filename-conflicts": "解决冲突",
|
||||
"reload-page": "刷新 UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "冲突详情",
|
||||
"version": "版本信息"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "加载诊断结果失败:{message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "解决文件名冲突失败:{message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "解决文件名冲突",
|
||||
"message": "通过在每个重复文件名后附加 4 位哈希值来重命名文件。",
|
||||
"note": "此操作会重命名磁盘上的文件。如果使用 A1111 语法格式,现有工作流中的模型引用可能需要更新。",
|
||||
"detail": "示例:<code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "将重命名 <strong>{count}</strong> 个文件(共 <strong>{groups}</strong> 组重复)",
|
||||
"confirm": "重命名文件",
|
||||
"cancel": "取消"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "检测到应用更新",
|
||||
|
||||
@@ -232,6 +232,8 @@
|
||||
"license": "授權",
|
||||
"noCreditRequired": "無需署名",
|
||||
"allowSellingGeneratedContent": "允許銷售",
|
||||
"allowSellingGeneratedContentTooltip": "允許出售生成的圖片",
|
||||
"noCreditRequiredTooltip": "使用模型時無需註明原作者",
|
||||
"noTags": "無標籤",
|
||||
"autoTags": "自動標籤",
|
||||
"noBaseModelMatches": "沒有基礎模型符合目前的搜尋。",
|
||||
@@ -267,10 +269,10 @@
|
||||
},
|
||||
"downloadBackend": {
|
||||
"label": "下載後端",
|
||||
"help": "選擇模型檔案的下載方式。Python 使用內建下載器。aria2 使用實驗性的外部下載程序。",
|
||||
"help": "選擇模型檔案的下載方式。Python 使用內建下載器。aria2 使用推薦的外部下載程序。",
|
||||
"options": {
|
||||
"python": "Python(內建)",
|
||||
"aria2": "aria2(實驗性)"
|
||||
"aria2": "aria2(推薦)"
|
||||
}
|
||||
},
|
||||
"aria2cPath": {
|
||||
@@ -577,7 +579,13 @@
|
||||
},
|
||||
"misc": {
|
||||
"includeTriggerWords": "在 LoRA 語法中包含觸發詞",
|
||||
"includeTriggerWordsHelp": "複製 LoRA 語法到剪貼簿時包含訓練觸發詞"
|
||||
"includeTriggerWordsHelp": "複製 LoRA 語法到剪貼簿時包含訓練觸發詞",
|
||||
"loraSyntaxFormat": "LoRA 語法格式",
|
||||
"loraSyntaxFormatHelp": "LoRA 語法格式。完整路徑(Full)包含子資料夾路徑 (<lora:style/anime/x:1.0>),解析精確無歧義。舊版(Legacy)僅使用檔名 (<lora:x:1.0>)——A1111 原始約定,同名檔案跨資料夾時可能產生歧義。",
|
||||
"loraSyntaxFormatOptions": {
|
||||
"full": "完整路徑(子資料夾/名稱)",
|
||||
"legacy": "舊版 A1111(僅名稱)"
|
||||
}
|
||||
},
|
||||
"metadataArchive": {
|
||||
"enableArchiveDb": "啟用中繼資料封存資料庫",
|
||||
@@ -1172,6 +1180,7 @@
|
||||
"editModelName": "編輯模型名稱",
|
||||
"editFileName": "編輯檔案名稱",
|
||||
"editBaseModel": "編輯基礎模型",
|
||||
"editVersionName": "編輯版本名稱",
|
||||
"viewOnCivitai": "在 Civitai 查看",
|
||||
"viewOnCivitaiText": "在 Civitai 查看",
|
||||
"viewCreatorProfile": "查看創作者個人檔案",
|
||||
@@ -1921,9 +1930,32 @@
|
||||
"warning": "需要注意",
|
||||
"error": "需要處理"
|
||||
},
|
||||
"issues": {
|
||||
"civitai_api_key": {
|
||||
"title": "Civitai API 金鑰"
|
||||
},
|
||||
"cache_health": {
|
||||
"title": "模型快取健康狀態"
|
||||
},
|
||||
"filename_conflicts": {
|
||||
"title": "檔案名稱重複衝突"
|
||||
},
|
||||
"ui_version": {
|
||||
"title": "UI 版本"
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"runAgain": "重新執行",
|
||||
"exportBundle": "匯出套件"
|
||||
"exportBundle": "匯出套件",
|
||||
"open-settings": "開啟設定",
|
||||
"open-settings-syntax-format": "切換為完整路徑語法",
|
||||
"repair-cache": "重建快取",
|
||||
"resolve-filename-conflicts": "解決衝突",
|
||||
"reload-page": "重新載入 UI"
|
||||
},
|
||||
"labels": {
|
||||
"conflicts": "衝突詳情",
|
||||
"version": "版本"
|
||||
},
|
||||
"toast": {
|
||||
"loadFailed": "載入診斷失敗:{message}",
|
||||
@@ -1935,6 +1967,15 @@
|
||||
"conflictsResolveFailed": "解決檔案名稱衝突失敗:{message}"
|
||||
}
|
||||
},
|
||||
"conflictConfirm": {
|
||||
"title": "解決檔案名稱衝突",
|
||||
"message": "通過在每個重複檔案名稱後附加 4 位元哈希值來重新命名檔案。",
|
||||
"note": "此操作會重新命名磁碟上的檔案。如果使用 A1111 語法格式,現有工作流程中的模型參考可能需要更新。",
|
||||
"detail": "示例:<code>filename_v1.2</code> → <code>filename_v1.2-ab3c</code>",
|
||||
"impact": "將重新命名 <strong>{count}</strong> 個檔案(共 <strong>{groups}</strong> 組重複)",
|
||||
"confirm": "重新命名檔案",
|
||||
"cancel": "取消"
|
||||
},
|
||||
"banners": {
|
||||
"versionMismatch": {
|
||||
"title": "偵測到應用程式更新",
|
||||
|
||||
@@ -9,6 +9,7 @@ from ..utils.utils import get_lora_info_absolute
|
||||
from .utils import (
|
||||
FlexibleOptionalInputType,
|
||||
any_type,
|
||||
apply_lora_syntax_format,
|
||||
detect_nunchaku_model_kind,
|
||||
extract_lora_name,
|
||||
get_loras_list,
|
||||
@@ -52,7 +53,7 @@ def _collect_widget_entries(kwargs):
|
||||
for lora in get_loras_list(kwargs):
|
||||
if not lora.get("active", False):
|
||||
continue
|
||||
lora_name = lora["name"]
|
||||
lora_name = apply_lora_syntax_format(lora["name"])
|
||||
model_strength = float(lora["strength"])
|
||||
clip_strength = float(lora.get("clipStrength", model_strength))
|
||||
lora_path, trigger_words = get_lora_info_absolute(lora_name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
from ..utils.utils import get_lora_info
|
||||
from .utils import FlexibleOptionalInputType, any_type, extract_lora_name, get_loras_list
|
||||
from .utils import FlexibleOptionalInputType, any_type, apply_lora_syntax_format, extract_lora_name, get_loras_list
|
||||
|
||||
import logging
|
||||
|
||||
@@ -48,7 +48,7 @@ class LoraStackerLM:
|
||||
if not lora.get('active', False):
|
||||
continue
|
||||
|
||||
lora_name = lora['name']
|
||||
lora_name = apply_lora_syntax_format(lora['name'])
|
||||
model_strength = float(lora['strength'])
|
||||
# Get clip strength - use model strength as default if not specified
|
||||
clip_strength = float(lora.get('clipStrength', model_strength))
|
||||
|
||||
@@ -44,14 +44,29 @@ import folder_paths # type: ignore
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_lora_syntax_format():
|
||||
try:
|
||||
from ..services.settings_manager import get_settings_manager
|
||||
return get_settings_manager().get("lora_syntax_format", "legacy")
|
||||
except Exception:
|
||||
return "legacy"
|
||||
|
||||
|
||||
def apply_lora_syntax_format(name):
|
||||
fmt = get_lora_syntax_format()
|
||||
if fmt == "legacy":
|
||||
return name.replace("\\", "/").rstrip("/").split("/")[-1]
|
||||
return name
|
||||
|
||||
|
||||
def extract_lora_name(lora_path):
|
||||
normalized = lora_path.replace("\\", "/")
|
||||
basename = os.path.basename(normalized)
|
||||
name_no_ext = os.path.splitext(basename)[0]
|
||||
dirname = os.path.dirname(normalized)
|
||||
if dirname and dirname not in (".", "/") and not normalized.startswith("/"):
|
||||
return f"{dirname}/{name_no_ext}"
|
||||
return name_no_ext
|
||||
return apply_lora_syntax_format(f"{dirname}/{name_no_ext}")
|
||||
return apply_lora_syntax_format(name_no_ext)
|
||||
|
||||
|
||||
def get_loras_list(kwargs):
|
||||
|
||||
@@ -686,6 +686,9 @@ class DoctorHandler:
|
||||
)
|
||||
|
||||
async def resolve_filename_conflicts(self, request: web.Request) -> web.Response:
|
||||
if self._settings.get("lora_syntax_format", "legacy") == "full":
|
||||
return web.json_response({"success": True, "renamed": [], "count": 0})
|
||||
|
||||
renamed: list[dict[str, Any]] = []
|
||||
|
||||
try:
|
||||
@@ -990,6 +993,18 @@ class DoctorHandler:
|
||||
}
|
||||
|
||||
async def _check_filename_conflicts(self) -> dict[str, Any]:
|
||||
# When full path syntax is active, duplicate filenames across subfolders
|
||||
# are not ambiguous (<lora:subfolder/name:strength>), so skip the check.
|
||||
if self._settings.get("lora_syntax_format", "legacy") == "full":
|
||||
return {
|
||||
"id": "filename_conflicts",
|
||||
"title": "Duplicate Filename Conflicts",
|
||||
"status": "ok",
|
||||
"summary": "Full path syntax is active — duplicate filenames across folders are not ambiguous.",
|
||||
"details": [],
|
||||
"actions": [],
|
||||
}
|
||||
|
||||
all_conflicts: list[dict[str, Any]] = []
|
||||
total_conflict_groups = 0
|
||||
total_conflict_files = 0
|
||||
@@ -1048,12 +1063,22 @@ class DoctorHandler:
|
||||
"total_conflict_files": total_conflict_files,
|
||||
}
|
||||
]
|
||||
for conflict in all_conflicts:
|
||||
|
||||
# Show at most 5 conflict groups inline; note any remainder.
|
||||
MAX_VISIBLE_CONFLICTS = 5
|
||||
visible_conflicts = all_conflicts[:MAX_VISIBLE_CONFLICTS]
|
||||
for conflict in visible_conflicts:
|
||||
details.append(
|
||||
f"[{conflict['label']}] '{conflict['filename']}' "
|
||||
f"'{conflict['filename']}' "
|
||||
f"found in {len(conflict['paths'])} locations"
|
||||
)
|
||||
|
||||
hidden_count = len(all_conflicts) - MAX_VISIBLE_CONFLICTS
|
||||
if hidden_count > 0:
|
||||
details.append(
|
||||
f"...and {hidden_count} more duplicate filename group(s)"
|
||||
)
|
||||
|
||||
return {
|
||||
"id": "filename_conflicts",
|
||||
"title": "Duplicate Filename Conflicts",
|
||||
@@ -1064,7 +1089,11 @@ class DoctorHandler:
|
||||
{
|
||||
"id": "resolve-filename-conflicts",
|
||||
"label": "Resolve Conflicts",
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "open-settings-syntax-format",
|
||||
"label": "Switch to Full Path Syntax",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -1177,6 +1177,12 @@ class ModelQueryHandler:
|
||||
|
||||
async def find_filename_conflicts(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
settings = get_settings_manager()
|
||||
if settings.get("lora_syntax_format", "legacy") == "full":
|
||||
return web.json_response(
|
||||
{"success": True, "conflicts": [], "count": 0}
|
||||
)
|
||||
|
||||
duplicates = self._service.find_duplicate_filenames()
|
||||
result = []
|
||||
cache = await self._service.scanner.get_cached_data()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
@@ -12,6 +13,12 @@ from ...config import config as global_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_CHUNK_SIZE = 256 * 1024 # 256 KB
|
||||
|
||||
# Video file extensions that bypass native sendfile on Windows
|
||||
# to avoid IOCP/ProactorEventLoop crashes during client disconnect.
|
||||
_VIDEO_EXTENSIONS = frozenset({".mp4", ".webm", ".mov", ".avi", ".mkv"})
|
||||
|
||||
|
||||
class PreviewHandler:
|
||||
"""Serve preview assets for the active library at request time."""
|
||||
@@ -48,8 +55,51 @@ class PreviewHandler:
|
||||
logger.debug("Preview file not found at %s", str(resolved))
|
||||
raise web.HTTPNotFound(text="Preview file not found")
|
||||
|
||||
# Video files: stream manually to avoid Windows native sendfile crash.
|
||||
# aiohttp's FileResponse uses _sendfile_native on Windows (IOCP-based),
|
||||
# which breaks when the client disconnects mid-transfer — this happens
|
||||
# constantly when users scroll through a gallery of animated previews.
|
||||
suffix = resolved.suffix.lower()
|
||||
if suffix in _VIDEO_EXTENSIONS:
|
||||
return await self._stream_file(request, resolved)
|
||||
|
||||
# aiohttp's FileResponse handles range requests and content headers for us.
|
||||
return web.FileResponse(path=resolved, chunk_size=256 * 1024)
|
||||
return web.FileResponse(path=resolved, chunk_size=_CHUNK_SIZE)
|
||||
|
||||
async def _stream_file(
|
||||
self, request: web.Request, path: Path
|
||||
) -> web.StreamResponse:
|
||||
"""Stream a file chunk-by-chunk, bypassing native sendfile.
|
||||
|
||||
This avoids the Windows IOCP ``_sendfile_native`` crash that occurs
|
||||
when the client disconnects during a large file transfer.
|
||||
"""
|
||||
content_type, _ = mimetypes.guess_type(str(path))
|
||||
if content_type is None:
|
||||
content_type = "application/octet-stream"
|
||||
|
||||
file_size = path.stat().st_size
|
||||
resp = web.StreamResponse()
|
||||
resp.content_type = content_type
|
||||
resp.content_length = file_size
|
||||
|
||||
await resp.prepare(request)
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
while True:
|
||||
chunk = f.read(_CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
await resp.write(chunk)
|
||||
except (ConnectionResetError, ConnectionAbortedError):
|
||||
# Client disconnected during streaming — expected when scrolling
|
||||
# rapidly through a library with animated previews.
|
||||
pass
|
||||
except OSError as exc:
|
||||
logger.debug("I/O error streaming preview %s: %s", path, exc)
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
__all__ = ["PreviewHandler"]
|
||||
|
||||
@@ -39,7 +39,7 @@ class Aria2Transfer:
|
||||
|
||||
|
||||
class Aria2Downloader:
|
||||
"""Manage an aria2 RPC daemon for experimental model downloads."""
|
||||
"""Manage an aria2 RPC daemon for recommended model downloads."""
|
||||
|
||||
_instance = None
|
||||
_lock = asyncio.Lock()
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Optional, Dict, Tuple, List, Sequence
|
||||
from .connectivity_guard import (
|
||||
OFFLINE_FRIENDLY_MESSAGE,
|
||||
@@ -45,6 +46,14 @@ class CivitaiClient:
|
||||
self._initialized = True
|
||||
|
||||
self.base_url = "https://civitai.red/api/v1"
|
||||
# In-memory cache to avoid redundant get_model_version_info calls
|
||||
# within the same import/scan flow. Only successful results are cached.
|
||||
# Uses OrderedDict with LRU eviction at MAX_CACHE_ENTRIES to prevent
|
||||
# unbounded growth in long-running server processes.
|
||||
self._version_info_cache: OrderedDict[
|
||||
str, Tuple[Optional[Dict], Optional[str]]
|
||||
] = OrderedDict()
|
||||
self._MAX_CACHE_ENTRIES = 500
|
||||
|
||||
def _build_image_info_url(self, image_id: str) -> str:
|
||||
return f"{self.base_url}/images?imageId={image_id}&nsfw=X"
|
||||
@@ -57,22 +66,57 @@ class CivitaiClient:
|
||||
use_auth: bool = False,
|
||||
**kwargs,
|
||||
) -> Tuple[bool, Dict | str]:
|
||||
"""Wrapper around downloader.make_request that surfaces rate limits."""
|
||||
"""Wrapper around downloader.make_request that surfaces rate limits,
|
||||
with retry for transient server errors (5xx, Cloudflare 524, network flakiness)."""
|
||||
|
||||
downloader = await get_downloader()
|
||||
success, result = await downloader.make_request(
|
||||
method,
|
||||
url,
|
||||
use_auth=use_auth,
|
||||
**kwargs,
|
||||
)
|
||||
if not success and isinstance(result, RateLimitError):
|
||||
if result.provider is None:
|
||||
result.provider = "civitai_api"
|
||||
raise result
|
||||
if not success and is_offline_cooldown_error(result):
|
||||
return False, OFFLINE_FRIENDLY_MESSAGE
|
||||
return success, result
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
downloader = await get_downloader()
|
||||
success, result = await downloader.make_request(
|
||||
method,
|
||||
url,
|
||||
use_auth=use_auth,
|
||||
**kwargs,
|
||||
)
|
||||
if success:
|
||||
return True, result
|
||||
|
||||
if isinstance(result, RateLimitError):
|
||||
if result.provider is None:
|
||||
result.provider = "civitai_api"
|
||||
raise result
|
||||
|
||||
if is_offline_cooldown_error(result):
|
||||
return False, OFFLINE_FRIENDLY_MESSAGE
|
||||
|
||||
# Transient server error — retry with exponential backoff
|
||||
if self._is_transient_server_error(str(result)):
|
||||
if attempt < max_retries - 1:
|
||||
wait = 2**attempt # 1s, 2s, 4s
|
||||
logger.info(
|
||||
"Transient error on %s %s, retrying in %ds "
|
||||
"(attempt %d/%d): %s",
|
||||
method,
|
||||
url,
|
||||
wait,
|
||||
attempt + 1,
|
||||
max_retries,
|
||||
result,
|
||||
)
|
||||
await asyncio.sleep(wait)
|
||||
continue
|
||||
logger.warning(
|
||||
"All %d retries exhausted for %s %s: %s",
|
||||
max_retries,
|
||||
method,
|
||||
url,
|
||||
result,
|
||||
)
|
||||
return False, result
|
||||
|
||||
return False, result
|
||||
|
||||
return False, "Unexpected error in _make_request"
|
||||
|
||||
@staticmethod
|
||||
def _remove_comfy_metadata(model_version: Optional[Dict]) -> None:
|
||||
@@ -201,6 +245,29 @@ class CivitaiClient:
|
||||
|
||||
return _from_value(payload)
|
||||
|
||||
@staticmethod
|
||||
def _is_transient_server_error(message: str) -> bool:
|
||||
"""Return True when the message indicates a transient upstream failure.
|
||||
|
||||
Recognises Cloudflare 524, generic 5xx, and connectivity-level flakiness
|
||||
that should not be treated as a permanent failure.
|
||||
"""
|
||||
normalized = message.lower()
|
||||
if "status 5" in normalized or "status 524" in normalized:
|
||||
return True
|
||||
if any(
|
||||
keyword in normalized
|
||||
for keyword in (
|
||||
"connection refused",
|
||||
"connection reset",
|
||||
"temporary failure",
|
||||
"name resolution",
|
||||
"connection closed",
|
||||
)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_model_versions(self, model_id: str) -> Optional[Dict]:
|
||||
"""Get all versions of a model with local availability info"""
|
||||
try:
|
||||
@@ -223,6 +290,13 @@ class CivitaiClient:
|
||||
logger.info("Civitai request skipped: %s", OFFLINE_FRIENDLY_MESSAGE)
|
||||
return None
|
||||
if message:
|
||||
if self._is_transient_server_error(message):
|
||||
logger.info(
|
||||
"Transient server error for model %s: %s",
|
||||
model_id,
|
||||
message,
|
||||
)
|
||||
return None
|
||||
raise RuntimeError(message)
|
||||
return None
|
||||
except RateLimitError:
|
||||
@@ -482,6 +556,14 @@ class CivitaiClient:
|
||||
- The model version data or None if not found
|
||||
- An error message if there was an error, or None on success
|
||||
"""
|
||||
# In-memory cache avoids redundant API calls within the same
|
||||
# import/scan flow (e.g. _resolve_base_model_from_checkpoint
|
||||
# followed by _resolve_and_populate_checkpoint with the same id).
|
||||
if version_id in self._version_info_cache:
|
||||
logger.debug("Cache hit for model version info: %s", version_id)
|
||||
self._version_info_cache.move_to_end(version_id) # LRU bump
|
||||
return self._version_info_cache[version_id]
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}/model-versions/{version_id}"
|
||||
|
||||
@@ -491,6 +573,11 @@ class CivitaiClient:
|
||||
if success:
|
||||
logger.debug("Successfully fetched model version info for: %s", version_id)
|
||||
self._remove_comfy_metadata(result)
|
||||
self._version_info_cache[version_id] = (result, None)
|
||||
self._version_info_cache.move_to_end(version_id)
|
||||
# Evict oldest entry when over capacity
|
||||
if len(self._version_info_cache) > self._MAX_CACHE_ENTRIES:
|
||||
self._version_info_cache.popitem(last=False)
|
||||
return result, None
|
||||
|
||||
# Handle specific error cases
|
||||
@@ -532,6 +619,13 @@ class CivitaiClient:
|
||||
if not success:
|
||||
if is_expected_offline_error(result):
|
||||
return None
|
||||
if self._is_transient_server_error(str(result)):
|
||||
logger.info(
|
||||
"Transient server error fetching image info for ID %s: %s",
|
||||
image_id,
|
||||
result,
|
||||
)
|
||||
return None
|
||||
logger.error(
|
||||
"Failed to fetch image info for ID %s from civitai.red: %s",
|
||||
image_id,
|
||||
|
||||
@@ -1120,6 +1120,11 @@ class ModelScanner:
|
||||
if self._hash_index is None or self.model_type != "lora":
|
||||
return
|
||||
|
||||
# When full path syntax is active, duplicate filenames across subfolders
|
||||
# are fully qualified, so there is no ambiguity — skip the warning.
|
||||
if get_settings_manager().get("lora_syntax_format", "legacy") == "full":
|
||||
return
|
||||
|
||||
duplicates = self._hash_index.get_duplicate_filenames()
|
||||
if not duplicates:
|
||||
return
|
||||
|
||||
@@ -1000,12 +1000,11 @@ class ModelUpdateService:
|
||||
fallback_error_message = str(exc) or "resource not found"
|
||||
mark_model_as_ignored = True
|
||||
except Exception as exc: # pragma: no cover - defensive log
|
||||
logger.error(
|
||||
logger.warning(
|
||||
"Failed to fetch versions for model %s (%s): %s",
|
||||
model_id,
|
||||
model_type,
|
||||
exc,
|
||||
exc_info=True,
|
||||
)
|
||||
fallback_error_message = str(exc)
|
||||
if response is not None:
|
||||
|
||||
@@ -96,6 +96,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
||||
"compact_mode": False,
|
||||
"priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(),
|
||||
"model_name_display": "model_name",
|
||||
"lora_syntax_format": "legacy",
|
||||
"model_card_footer_action": "replace_preview",
|
||||
"show_version_on_card": True,
|
||||
"update_flag_strategy": "same_base",
|
||||
|
||||
@@ -239,9 +239,9 @@ def _resolve_commercial_bits(values: Sequence[str]) -> int:
|
||||
normalized_values.add(normalized)
|
||||
|
||||
has_sell = "sell" in normalized_values
|
||||
has_rent = has_sell or "rent" in normalized_values
|
||||
has_rentcivit = has_rent or "rentcivit" in normalized_values
|
||||
has_image = has_sell or "image" in normalized_values
|
||||
has_rent = "rent" in normalized_values
|
||||
has_rentcivit = "rentcivit" in normalized_values
|
||||
has_image = "image" in normalized_values
|
||||
|
||||
commercial_bits = (
|
||||
(1 if has_sell else 0) << 3
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-lora-manager"
|
||||
description = "Revolutionize your workflow with the ultimate LoRA companion for ComfyUI!"
|
||||
version = "1.0.7"
|
||||
version = "1.0.8"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = [
|
||||
"aiohttp",
|
||||
|
||||
@@ -255,25 +255,28 @@
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* File name copy styles */
|
||||
.file-name-wrapper {
|
||||
/* Editable inline field styles (file name, version name, etc.) */
|
||||
.file-name-wrapper,
|
||||
.version-name-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px;
|
||||
padding: 4px 0;
|
||||
border-radius: var(--border-radius-xs);
|
||||
transition: background-color 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.file-name-content {
|
||||
padding: 2px 4px;
|
||||
.file-name-content,
|
||||
.version-name-content {
|
||||
padding: 2px 4px 2px 0;
|
||||
border-radius: var(--border-radius-xs);
|
||||
border: 1px solid transparent;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-name-wrapper.editing .file-name-content {
|
||||
.file-name-wrapper.editing .file-name-content,
|
||||
.version-name-wrapper.editing .version-name-content {
|
||||
border: 1px solid var(--lora-accent);
|
||||
background: var(--bg-color);
|
||||
outline: none;
|
||||
@@ -283,7 +286,8 @@
|
||||
.edit-model-name-btn,
|
||||
.edit-file-name-btn,
|
||||
.edit-base-model-btn,
|
||||
.edit-model-description-btn {
|
||||
.edit-model-description-btn,
|
||||
.edit-version-name-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
@@ -299,9 +303,11 @@
|
||||
.edit-file-name-btn.visible,
|
||||
.edit-base-model-btn.visible,
|
||||
.edit-model-description-btn.visible,
|
||||
.edit-version-name-btn.visible,
|
||||
.model-name-header:hover .edit-model-name-btn,
|
||||
.file-name-wrapper:hover .edit-file-name-btn,
|
||||
.base-model-display:hover .edit-base-model-btn,
|
||||
.version-name-wrapper:hover .edit-version-name-btn,
|
||||
.model-name-header:hover .edit-model-description-btn {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -309,14 +315,16 @@
|
||||
.edit-model-name-btn:hover,
|
||||
.edit-file-name-btn:hover,
|
||||
.edit-base-model-btn:hover,
|
||||
.edit-model-description-btn:hover {
|
||||
.edit-model-description-btn:hover,
|
||||
.edit-version-name-btn:hover {
|
||||
opacity: 0.8 !important;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .edit-model-name-btn:hover,
|
||||
[data-theme="dark"] .edit-file-name-btn:hover,
|
||||
[data-theme="dark"] .edit-base-model-btn:hover {
|
||||
[data-theme="dark"] .edit-base-model-btn:hover,
|
||||
[data-theme="dark"] .edit-version-name-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
@@ -338,7 +346,7 @@
|
||||
}
|
||||
|
||||
.base-model-content {
|
||||
padding: 2px 4px;
|
||||
padding: 2px 4px 2px 0;
|
||||
border-radius: var(--border-radius-xs);
|
||||
border: 1px solid transparent;
|
||||
color: var(--text-color);
|
||||
|
||||
@@ -33,6 +33,39 @@
|
||||
animation: modalFadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
#resolveFilenameConflictsModal .confirmation-message {
|
||||
color: var(--text-color);
|
||||
margin: var(--space-2) 0;
|
||||
font-size: 1em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#resolveFilenameConflictsModal .resolve-conflicts-detail {
|
||||
color: var(--text-color);
|
||||
margin: var(--space-2) 0;
|
||||
font-size: 0.95em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#resolveFilenameConflictsModal .resolve-conflicts-detail code {
|
||||
background: var(--lora-surface);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
border: 1px solid var(--lora-border);
|
||||
}
|
||||
|
||||
#resolveFilenameConflictsModal .resolve-conflicts-impact {
|
||||
background: var(--lora-surface);
|
||||
border: 1px solid var(--lora-border);
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: var(--space-2);
|
||||
margin: var(--space-2) 0;
|
||||
color: var(--text-color);
|
||||
text-align: left;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.delete-model-info,
|
||||
.exclude-model-info {
|
||||
/* Update info display styling */
|
||||
|
||||
@@ -1369,3 +1369,14 @@ input:checked + .toggle-slider:before {
|
||||
background: var(--lora-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Highlight animation for setting items targeted from Doctor actions */
|
||||
@keyframes settings-highlight-pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(from var(--lora-accent) r g b / 0.4); }
|
||||
50% { box-shadow: 0 0 0 4px rgba(from var(--lora-accent) r g b / 0.2); }
|
||||
}
|
||||
|
||||
.settings-setting-highlight {
|
||||
animation: settings-highlight-pulse 1.5s ease-in-out 3;
|
||||
border-radius: var(--border-radius-xs);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@ function updateModalFilePathReferences(newFilePath) {
|
||||
fileNameContent.setAttribute('data-file-path', newFilePath);
|
||||
}
|
||||
|
||||
const versionNameContent = scopedQuery('.version-name-content');
|
||||
if (versionNameContent && versionNameContent.dataset) {
|
||||
versionNameContent.dataset.filePath = newFilePath;
|
||||
versionNameContent.setAttribute('data-file-path', newFilePath);
|
||||
}
|
||||
|
||||
const editTagsBtn = scopedQuery('.edit-tags-btn');
|
||||
if (editTagsBtn) {
|
||||
editTagsBtn.dataset.filePath = newFilePath;
|
||||
@@ -516,3 +522,127 @@ export function setupFileNameEditing(filePath) {
|
||||
editBtn.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up version name editing functionality
|
||||
* @param {string} filePath - File path
|
||||
*/
|
||||
export function setupVersionNameEditing(filePath) {
|
||||
const versionNameContent = document.querySelector('.version-name-content');
|
||||
const editBtn = document.querySelector('.edit-version-name-btn');
|
||||
|
||||
if (!versionNameContent || !editBtn) return;
|
||||
|
||||
// Store the file path in a data attribute for later use
|
||||
versionNameContent.dataset.filePath = filePath;
|
||||
|
||||
// Show edit button on hover
|
||||
const versionNameWrapper = document.querySelector('.version-name-wrapper');
|
||||
versionNameWrapper.addEventListener('mouseenter', () => {
|
||||
editBtn.classList.add('visible');
|
||||
});
|
||||
|
||||
versionNameWrapper.addEventListener('mouseleave', () => {
|
||||
if (!versionNameWrapper.classList.contains('editing')) {
|
||||
editBtn.classList.remove('visible');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle edit button click
|
||||
editBtn.addEventListener('click', () => {
|
||||
versionNameWrapper.classList.add('editing');
|
||||
versionNameContent.setAttribute('contenteditable', 'true');
|
||||
// Store original value for comparison later
|
||||
versionNameContent.dataset.originalValue = versionNameContent.textContent.trim();
|
||||
versionNameContent.focus();
|
||||
|
||||
// Place cursor at the end
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
if (versionNameContent.childNodes.length > 0) {
|
||||
range.setStart(versionNameContent.childNodes[0], versionNameContent.textContent.length);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
editBtn.classList.add('visible');
|
||||
});
|
||||
|
||||
// Handle keyboard events in edit mode
|
||||
versionNameContent.addEventListener('keydown', function(e) {
|
||||
if (!this.getAttribute('contenteditable')) return;
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.blur(); // Trigger save on Enter
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
// Restore original value
|
||||
this.textContent = this.dataset.originalValue;
|
||||
exitEditMode();
|
||||
}
|
||||
});
|
||||
|
||||
// Limit version name length
|
||||
versionNameContent.addEventListener('input', function() {
|
||||
if (!this.getAttribute('contenteditable')) return;
|
||||
|
||||
if (this.textContent.length > 100) {
|
||||
this.textContent = this.textContent.substring(0, 100);
|
||||
// Place cursor at the end
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.setStart(this.childNodes[0], 100);
|
||||
range.collapse(true);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
|
||||
showToast('toast.models.nameTooLong', {}, 'warning');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle focus out - save changes
|
||||
versionNameContent.addEventListener('blur', async function() {
|
||||
if (!this.getAttribute('contenteditable')) return;
|
||||
|
||||
const newVersionName = this.textContent.trim();
|
||||
const originalValue = this.dataset.originalValue;
|
||||
|
||||
// Basic validation
|
||||
if (!newVersionName) {
|
||||
// Restore original value if empty
|
||||
this.textContent = originalValue;
|
||||
showToast('toast.models.nameCannotBeEmpty', {}, 'error');
|
||||
exitEditMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newVersionName === originalValue) {
|
||||
// No changes, just exit edit mode
|
||||
exitEditMode();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Resolve current file path from modal state
|
||||
const filePath = getActiveModalFilePath(this.dataset.filePath);
|
||||
|
||||
await getModelApiClient().saveModelMetadata(filePath, { civitai: { name: newVersionName } });
|
||||
|
||||
showToast('toast.models.nameUpdatedSuccessfully', {}, 'success');
|
||||
} catch (error) {
|
||||
console.error('Error updating version name:', error);
|
||||
this.textContent = originalValue; // Restore original version name
|
||||
showToast('toast.models.nameUpdateFailed', {}, 'error');
|
||||
} finally {
|
||||
exitEditMode();
|
||||
}
|
||||
});
|
||||
|
||||
function exitEditMode() {
|
||||
versionNameContent.removeAttribute('contenteditable');
|
||||
versionNameWrapper.classList.remove('editing');
|
||||
editBtn.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import { setupTabSwitching } from './ModelDescription.js';
|
||||
import {
|
||||
setupModelNameEditing,
|
||||
setupBaseModelEditing,
|
||||
setupFileNameEditing
|
||||
setupFileNameEditing,
|
||||
setupVersionNameEditing
|
||||
} from './ModelMetadata.js';
|
||||
import { setupTagEditMode } from './ModelTags.js';
|
||||
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
||||
@@ -466,7 +467,12 @@ export async function showModelModal(model, modelType) {
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<label>${translate('modals.model.metadata.version', {}, 'Version')}</label>
|
||||
<span>${modelWithFullData.civitai?.name || 'N/A'}</span>
|
||||
<div class="version-name-wrapper">
|
||||
<span class="version-name-content">${modelWithFullData.civitai?.name || 'N/A'}</span>
|
||||
<button class="edit-version-name-btn" title="${translate('modals.model.actions.editVersionName', {}, 'Edit version name')}">
|
||||
<i class="fas fa-pencil-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<label>${translate('modals.model.metadata.fileName', {}, 'File Name')}</label>
|
||||
@@ -660,6 +666,7 @@ export async function showModelModal(model, modelType) {
|
||||
setupTagTooltip();
|
||||
setupTagEditMode(modelType);
|
||||
setupModelNameEditing(modelWithFullData.file_path);
|
||||
setupVersionNameEditing(modelWithFullData.file_path);
|
||||
setupBaseModelEditing(modelWithFullData.file_path);
|
||||
setupFileNameEditing(modelWithFullData.file_path);
|
||||
setupEventHandlers(modelWithFullData.file_path, modelType);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { showToast, copyToClipboard, sendLoraToWorkflow, buildLoraSyntax, getNSF
|
||||
import { updateCardsForBulkMode } from '../components/shared/ModelCard.js';
|
||||
import { modalManager } from './ModalManager.js';
|
||||
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
|
||||
import { RecipeSidebarApiClient, updateRecipeMetadata } from '../api/recipeApi.js';
|
||||
import { RecipeSidebarApiClient, updateRecipeMetadata, extractRecipeId } from '../api/recipeApi.js';
|
||||
import { MODEL_TYPES, MODEL_CONFIG } from '../api/apiConfig.js';
|
||||
import { BASE_MODEL_CATEGORIES } from '../utils/constants.js';
|
||||
import { getPriorityTagSuggestions } from '../utils/priorityTagHelpers.js';
|
||||
@@ -74,7 +74,7 @@ export class BulkManager {
|
||||
unfavorite: true
|
||||
},
|
||||
recipes: {
|
||||
addTags: false,
|
||||
addTags: true,
|
||||
sendToWorkflow: false,
|
||||
copyAll: false,
|
||||
refreshAll: false,
|
||||
@@ -1043,6 +1043,8 @@ export class BulkManager {
|
||||
cancelled = true;
|
||||
});
|
||||
|
||||
const isRecipes = state.currentPageType === 'recipes';
|
||||
|
||||
// Add or replace tags for each selected model based on mode
|
||||
for (const filePath of filePaths) {
|
||||
if (cancelled) {
|
||||
@@ -1050,7 +1052,9 @@ export class BulkManager {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (mode === 'replace') {
|
||||
if (isRecipes) {
|
||||
await this._saveRecipeTags(filePath, tags, mode);
|
||||
} else if (mode === 'replace') {
|
||||
await apiClient.saveModelMetadata(filePath, { tags: tags });
|
||||
} else {
|
||||
await apiClient.addTags(filePath, { tags: tags });
|
||||
@@ -1089,6 +1093,35 @@ export class BulkManager {
|
||||
}
|
||||
}
|
||||
|
||||
async _saveRecipeTags(filePath, newTags, mode) {
|
||||
const recipeId = extractRecipeId(filePath);
|
||||
if (!recipeId) throw new Error('Unable to determine recipe ID');
|
||||
|
||||
let finalTags = newTags;
|
||||
if (mode === 'append') {
|
||||
const recipeItem = state.virtualScroller?.items?.find(
|
||||
item => item.file_path === filePath
|
||||
);
|
||||
const existingTags = recipeItem?.tags || [];
|
||||
finalTags = [...new Set([...existingTags, ...newTags])];
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`/api/lm/recipe/${encodeURIComponent(recipeId)}/update`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tags: finalTags }),
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to update recipe tags');
|
||||
}
|
||||
|
||||
state.virtualScroller.updateSingleItem(filePath, { tags: finalTags });
|
||||
}
|
||||
|
||||
cleanupBulkAddTagsModal() {
|
||||
// Clear tags container
|
||||
const tagsContainer = document.getElementById('bulkTagsItems');
|
||||
|
||||
@@ -225,6 +225,13 @@ export class DoctorManager {
|
||||
renderIssueCard(item) {
|
||||
const status = item.status || 'ok';
|
||||
const tagLabel = this.getStatusLabel(status);
|
||||
|
||||
const titleKey = `doctor.issues.${item.id || ''}.title`;
|
||||
const displayTitle = translate(titleKey, {}, item.title || '');
|
||||
|
||||
const summaryKey = `doctor.issues.${item.id || ''}.summary.${status}`;
|
||||
const displaySummary = translate(summaryKey, {}, item.summary || '');
|
||||
|
||||
const details = Array.isArray(item.details) ? item.details : [];
|
||||
const listItems = details
|
||||
.filter((detail) => typeof detail === 'string')
|
||||
@@ -235,19 +242,22 @@ export class DoctorManager {
|
||||
.map((detail) => this.renderInlineDetail(detail))
|
||||
.join('');
|
||||
const actions = (item.actions || [])
|
||||
.map((action) => `
|
||||
.map((action) => {
|
||||
const actionLabel = translate(`doctor.actions.${action.id}`, {}, action.label);
|
||||
return `
|
||||
<button class="${action.id === 'repair-cache' || action.id === 'reload-page' ? 'primary-btn' : 'secondary-btn'}" data-doctor-action="${escapeHtml(action.id)}">
|
||||
${escapeHtml(action.label)}
|
||||
${escapeHtml(actionLabel)}
|
||||
</button>
|
||||
`)
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return `
|
||||
<section class="doctor-issue-card" data-status="${escapeHtml(status)}" data-issue-id="${escapeHtml(item.id || '')}">
|
||||
<div class="doctor-issue-header">
|
||||
<div>
|
||||
<h3>${escapeHtml(item.title || '')}</h3>
|
||||
<p class="doctor-issue-summary">${escapeHtml(item.summary || '')}</p>
|
||||
<h3>${escapeHtml(displayTitle)}</h3>
|
||||
<p class="doctor-issue-summary">${escapeHtml(displaySummary)}</p>
|
||||
</div>
|
||||
<span class="doctor-issue-tag">${escapeHtml(tagLabel)}</span>
|
||||
</div>
|
||||
@@ -262,7 +272,7 @@ export class DoctorManager {
|
||||
if (detail.conflict_groups || detail.total_conflict_files) {
|
||||
return `
|
||||
<div class="doctor-inline-detail">
|
||||
<strong>${escapeHtml(translate('doctor.status.warning', {}, 'Conflicts'))}</strong>
|
||||
<strong>${escapeHtml(translate('doctor.labels.conflicts', {}, 'Conflicts'))}</strong>
|
||||
<div>${escapeHtml(`${detail.conflict_groups || 0} filenames, ${detail.total_conflict_files || 0} files`)}</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -324,11 +334,42 @@ export class DoctorManager {
|
||||
}
|
||||
}, 100);
|
||||
break;
|
||||
case 'open-settings-syntax-format':
|
||||
modalManager.showModal('settingsModal');
|
||||
window.setTimeout(() => {
|
||||
// Switch to Interface section
|
||||
document.querySelectorAll('.settings-section').forEach((s) => s.classList.remove('active'));
|
||||
const interfaceSection = document.getElementById('section-interface');
|
||||
if (interfaceSection) {
|
||||
interfaceSection.classList.add('active');
|
||||
}
|
||||
document.querySelectorAll('.settings-nav-item').forEach((n) => n.classList.remove('active'));
|
||||
const interfaceNav = document.querySelector('.settings-nav-item[data-section="interface"]');
|
||||
if (interfaceNav) {
|
||||
interfaceNav.classList.add('active');
|
||||
}
|
||||
|
||||
// Focus and scroll to the LoRA Syntax Format dropdown
|
||||
const select = document.getElementById('loraSyntaxFormat');
|
||||
if (select) {
|
||||
select.focus();
|
||||
select.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// Add temporary highlight animation
|
||||
const settingItem = select.closest('.setting-item');
|
||||
if (settingItem) {
|
||||
settingItem.classList.add('settings-setting-highlight');
|
||||
setTimeout(() => {
|
||||
settingItem.classList.remove('settings-setting-highlight');
|
||||
}, 4500);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
break;
|
||||
case 'repair-cache':
|
||||
await this.repairCache();
|
||||
break;
|
||||
case 'resolve-filename-conflicts':
|
||||
await this.resolveFilenameConflicts();
|
||||
await this.promptResolveConflicts();
|
||||
break;
|
||||
case 'reload-page':
|
||||
this.reloadUi();
|
||||
@@ -358,6 +399,62 @@ export class DoctorManager {
|
||||
}
|
||||
}
|
||||
|
||||
_getConflictStats() {
|
||||
const conflict = (this.lastDiagnostics?.diagnostics || []).find(
|
||||
(d) => d.id === 'filename_conflicts'
|
||||
);
|
||||
if (!conflict || !Array.isArray(conflict.details)) {
|
||||
return { groups: 0, files: 0 };
|
||||
}
|
||||
const summary = conflict.details.find(
|
||||
(d) => d && typeof d === 'object' && d.conflict_groups !== undefined
|
||||
);
|
||||
return {
|
||||
groups: summary?.conflict_groups || 0,
|
||||
files: summary?.total_conflict_files || 0,
|
||||
};
|
||||
}
|
||||
|
||||
async promptResolveConflicts() {
|
||||
const stats = this._getConflictStats();
|
||||
if (stats.groups === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detailEl = document.getElementById('resolveConflictsDetail');
|
||||
if (detailEl) {
|
||||
detailEl.innerHTML = translate(
|
||||
'conflictConfirm.detail',
|
||||
{},
|
||||
'Example: <code>Add_Details_v1.2</code> \u2192 <code>Add_Details_v1.2-a3f7</code>'
|
||||
);
|
||||
}
|
||||
|
||||
const impactEl = document.getElementById('resolveConflictsImpact');
|
||||
if (impactEl) {
|
||||
impactEl.innerHTML = translate(
|
||||
'conflictConfirm.impact',
|
||||
{ count: stats.files, groups: stats.groups },
|
||||
`Will rename <strong>${stats.files}</strong> file(s) across <strong>${stats.groups}</strong> duplicate group(s).`
|
||||
);
|
||||
}
|
||||
|
||||
this._confirmResolveResolve = null;
|
||||
modalManager.showModal('resolveFilenameConflictsModal');
|
||||
return new Promise((resolve) => {
|
||||
this._confirmResolveResolve = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
async confirmResolveConflicts() {
|
||||
modalManager.closeModal('resolveFilenameConflictsModal');
|
||||
if (this._confirmResolveResolve) {
|
||||
this._confirmResolveResolve(true);
|
||||
this._confirmResolveResolve = null;
|
||||
}
|
||||
await this.resolveFilenameConflicts();
|
||||
}
|
||||
|
||||
async resolveFilenameConflicts() {
|
||||
try {
|
||||
this.setLoading(true);
|
||||
@@ -449,3 +546,8 @@ export class DoctorManager {
|
||||
}
|
||||
|
||||
export const doctorManager = new DoctorManager();
|
||||
|
||||
// Make available globally for HTML onclick handlers
|
||||
if (typeof window !== 'undefined') {
|
||||
window.doctorManager = doctorManager;
|
||||
}
|
||||
|
||||
@@ -316,6 +316,19 @@ export class ModalManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Register resolveFilenameConflictsModal
|
||||
const resolveFilenameConflictsModal = document.getElementById('resolveFilenameConflictsModal');
|
||||
if (resolveFilenameConflictsModal) {
|
||||
this.registerModal('resolveFilenameConflictsModal', {
|
||||
element: resolveFilenameConflictsModal,
|
||||
onClose: () => {
|
||||
this.getModal('resolveFilenameConflictsModal').element.classList.remove('show');
|
||||
document.body.classList.remove('modal-open');
|
||||
},
|
||||
closeOnOutsideClick: true
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', this.boundHandleEscape);
|
||||
this.initialized = true;
|
||||
}
|
||||
@@ -396,7 +409,8 @@ export class ModalManager {
|
||||
id === "modelDuplicateDeleteModal" ||
|
||||
id === "clearCacheModal" ||
|
||||
id === "bulkDeleteModal" ||
|
||||
id === "checkUpdatesConfirmModal"
|
||||
id === "checkUpdatesConfirmModal" ||
|
||||
id === "resolveFilenameConflictsModal"
|
||||
) {
|
||||
modal.element.classList.add("show");
|
||||
} else {
|
||||
|
||||
@@ -295,6 +295,13 @@ export class SettingsManager {
|
||||
// Update state
|
||||
state.global.settings[settingKey] = value;
|
||||
|
||||
if (settingKey === 'lora_syntax_format') {
|
||||
try {
|
||||
localStorage.setItem('lm:lora-syntax-format-changed', Date.now().toString());
|
||||
} catch (_) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isBackendSetting(settingKey)) {
|
||||
return;
|
||||
}
|
||||
@@ -949,6 +956,12 @@ export class SettingsManager {
|
||||
includeTriggerWordsCheckbox.checked = state.global.settings.include_trigger_words || false;
|
||||
}
|
||||
|
||||
// Set lora syntax format
|
||||
const loraSyntaxFormatSelect = document.getElementById('loraSyntaxFormat');
|
||||
if (loraSyntaxFormatSelect) {
|
||||
loraSyntaxFormatSelect.value = state.global.settings.lora_syntax_format || 'legacy';
|
||||
}
|
||||
|
||||
// Load metadata archive settings
|
||||
await this.loadMetadataArchiveSettings();
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
|
||||
card_info_display: 'always',
|
||||
show_folder_sidebar: true,
|
||||
model_name_display: 'model_name',
|
||||
lora_syntax_format: 'legacy',
|
||||
model_card_footer_action: 'example_images',
|
||||
show_version_on_card: true,
|
||||
include_trigger_words: false,
|
||||
|
||||
@@ -420,12 +420,16 @@ export function getLoraStrengthsFromUsageTips(usageTips = {}) {
|
||||
export function buildLoraSyntax(fileName, usageTips = {}) {
|
||||
const { strength, hasStrength, clipStrength, hasClipStrength } = getLoraStrengthsFromUsageTips(usageTips);
|
||||
|
||||
const effectiveName = state.global.settings?.lora_syntax_format === 'legacy'
|
||||
? fileName.split('/').pop()
|
||||
: fileName;
|
||||
|
||||
if (hasClipStrength) {
|
||||
const modelStrength = hasStrength ? strength : 1;
|
||||
return `<lora:${fileName}:${modelStrength}:${clipStrength}>`;
|
||||
return `<lora:${effectiveName}:${modelStrength}:${clipStrength}>`;
|
||||
}
|
||||
|
||||
return `<lora:${fileName}:${strength}>`;
|
||||
return `<lora:${effectiveName}:${strength}>`;
|
||||
}
|
||||
|
||||
export function copyLoraSyntax(card) {
|
||||
|
||||
@@ -218,10 +218,10 @@
|
||||
<div class="filter-section">
|
||||
<h4>{{ t('header.filter.license') }}</h4>
|
||||
<div class="filter-tags">
|
||||
<div class="filter-tag license-tag" data-license="noCredit">
|
||||
<div class="filter-tag license-tag" data-license="noCredit" title="{{ t('header.filter.noCreditRequiredTooltip') }}">
|
||||
{{ t('header.filter.noCreditRequired') }}
|
||||
</div>
|
||||
<div class="filter-tag license-tag" data-license="allowSelling">
|
||||
<div class="filter-tag license-tag" data-license="allowSelling" title="{{ t('header.filter.allowSellingGeneratedContentTooltip') }}">
|
||||
{{ t('header.filter.allowSellingGeneratedContent') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,4 +108,21 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resolve Filename Conflicts Confirmation Modal -->
|
||||
<div id="resolveFilenameConflictsModal" class="modal delete-modal">
|
||||
<div class="modal-content delete-modal-content">
|
||||
<h2>{{ t('conflictConfirm.title') }}</h2>
|
||||
<p class="confirmation-message">{{ t('conflictConfirm.message') }}</p>
|
||||
<p class="resolve-conflicts-detail" id="resolveConflictsDetail"></p>
|
||||
<div class="resolve-conflicts-impact" id="resolveConflictsImpact"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="cancel-btn" onclick="modalManager.closeModal('resolveFilenameConflictsModal')">{{ t('common.actions.cancel') }}</button>
|
||||
<button class="primary-btn" id="resolveConflictsConfirmBtn" onclick="doctorManager.confirmResolveConflicts()">
|
||||
<i class="fas fa-check"></i>
|
||||
{{ t('conflictConfirm.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -536,7 +536,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
@@ -595,6 +595,22 @@
|
||||
<div class="settings-subsection-header">
|
||||
<h4>{{ t('settings.sections.misc') }}</h4>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="loraSyntaxFormat">
|
||||
{{ t('settings.misc.loraSyntaxFormat') }}
|
||||
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.misc.loraSyntaxFormatHelp') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
<div class="setting-control select-control">
|
||||
<select id="loraSyntaxFormat" onchange="settingsManager.saveSelectSetting('loraSyntaxFormat', 'lora_syntax_format')">
|
||||
<option value="full">{{ t('settings.misc.loraSyntaxFormatOptions.full') }}</option>
|
||||
<option value="legacy">{{ t('settings.misc.loraSyntaxFormatOptions.legacy') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
|
||||
@@ -185,7 +185,7 @@ describe('AutoComplete widget interactions', () => {
|
||||
expect(fetchApiMock).toHaveBeenCalledWith(
|
||||
'/lm/loras/usage-tips-by-path?relative_path=models%2Fexample.safetensors',
|
||||
);
|
||||
expect(input.value).toContain('<lora:models/example:1.5:0.9>,');
|
||||
expect(input.value).toContain('<lora:example:1.5:0.9>,');
|
||||
expect(autoComplete.dropdown.style.display).toBe('none');
|
||||
expect(input.focus).toHaveBeenCalled();
|
||||
expect(input.setSelectionRange).toHaveBeenCalled();
|
||||
@@ -1624,8 +1624,8 @@ describe('AutoComplete widget interactions', () => {
|
||||
|
||||
await autoComplete.insertSelection('models/example.safetensors');
|
||||
|
||||
expect(input.value).toContain('<lora:models/example:1.2>');
|
||||
expect(input.value).not.toContain('<lora:models/example:1.2>,');
|
||||
expect(input.value).toContain('<lora:example:1.2>');
|
||||
expect(input.value).not.toContain('<lora:example:1.2>,');
|
||||
});
|
||||
|
||||
it('replaces entire phrase when selected tag ends with underscore version of search term (suffix match)', async () => {
|
||||
|
||||
@@ -65,32 +65,26 @@ async def test_allow_selling_filter():
|
||||
"""Test the allow selling generated content filtering logic."""
|
||||
service = DummyModelService()
|
||||
|
||||
# Create test data with different license flags
|
||||
# CommercialUse values are independent — Sell does NOT imply Image.
|
||||
test_data = [
|
||||
# Model allowing selling (contains Image in allowCommercialUse)
|
||||
{"file_path": "model1.safetensors", "license_flags": build_license_flags({"allowCommercialUse": ["Image"]})},
|
||||
# Model not allowing selling (doesn't contain Image in allowCommercialUse)
|
||||
{"file_path": "model2.safetensors", "license_flags": build_license_flags({"allowCommercialUse": ["RentCivit"]})},
|
||||
# Model with default license flags (includes Sell by default, which implies Image)
|
||||
{"file_path": "model3.safetensors", "license_flags": build_license_flags(None)},
|
||||
# Model allowing selling (contains Sell in allowCommercialUse, which implies Image)
|
||||
{"file_path": "model4.safetensors", "license_flags": build_license_flags({"allowCommercialUse": ["Sell"]})},
|
||||
# Model with empty allowCommercialUse (doesn't allow selling)
|
||||
{"file_path": "model5.safetensors", "license_flags": build_license_flags({"allowCommercialUse": []})},
|
||||
]
|
||||
|
||||
# Test allow_selling=True (should return models that allow selling - have Image permission)
|
||||
# Default and Sell permissions both include Image, so model3 and model4 will be included
|
||||
# Test allow_selling=True (should return only models with the Image permission)
|
||||
filtered = await service._apply_allow_selling_filter(test_data, allow_selling=True)
|
||||
assert len(filtered) == 3 # model1, model3 (default includes Sell which implies Image), model4
|
||||
assert len(filtered) == 1 # only model1 has Image permission
|
||||
file_paths = {item["file_path"] for item in filtered}
|
||||
assert file_paths == {"model1.safetensors", "model3.safetensors", "model4.safetensors"}
|
||||
assert file_paths == {"model1.safetensors"}
|
||||
|
||||
# Test allow_selling=False (should return models that don't allow selling - don't have Image permission)
|
||||
# Test allow_selling=False (should return models without the Image permission)
|
||||
filtered = await service._apply_allow_selling_filter(test_data, allow_selling=False)
|
||||
assert len(filtered) == 2 # model2 and model5
|
||||
assert len(filtered) == 4 # model2, model3, model4, model5
|
||||
file_paths = {item["file_path"] for item in filtered}
|
||||
assert file_paths == {"model2.safetensors", "model5.safetensors"}
|
||||
assert file_paths == {"model2.safetensors", "model3.safetensors", "model4.safetensors", "model5.safetensors"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -131,13 +131,12 @@ async def test_pool_filter_allow_selling_true(lora_service, sample_loras):
|
||||
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||
|
||||
# Should keep models with Image permission (allowSelling)
|
||||
# Models: no_credit_required_for_selling, credit_required_for_selling, default_license
|
||||
assert len(filtered) == 3
|
||||
# Sell alone does not imply Image, so default_license is excluded.
|
||||
assert len(filtered) == 2
|
||||
file_names = {lora["file_name"] for lora in filtered}
|
||||
assert file_names == {
|
||||
"no_credit_required_for_selling.safetensors",
|
||||
"credit_required_for_selling.safetensors",
|
||||
"default_license.safetensors",
|
||||
}
|
||||
|
||||
|
||||
@@ -178,12 +177,11 @@ async def test_pool_filter_both_license_filters(lora_service, sample_loras):
|
||||
# Should keep models where both conditions are met:
|
||||
# - allowNoCredit=True (no credit required)
|
||||
# - Image permission exists (allow selling)
|
||||
# Models: no_credit_required_for_selling, default_license
|
||||
assert len(filtered) == 2
|
||||
# default_license has ["Sell"] without Image, so it's excluded.
|
||||
assert len(filtered) == 1
|
||||
file_names = {lora["file_name"] for lora in filtered}
|
||||
assert file_names == {
|
||||
"no_credit_required_for_selling.safetensors",
|
||||
"default_license.safetensors",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -132,7 +132,8 @@ async def test_initialize_cache_populates_cache(tmp_path: Path):
|
||||
_normalize_path(tmp_path / "one.txt"),
|
||||
_normalize_path(tmp_path / "nested" / "two.txt"),
|
||||
}
|
||||
assert {item["license_flags"] for item in cache.raw_data} == {DEFAULT_LICENSE_FLAGS}
|
||||
# build_license_flags({}) returns 113 (defaults: allowNoCredit + ["Sell"] + derivatives + differentLicense)
|
||||
assert {item["license_flags"] for item in cache.raw_data} == {113}
|
||||
|
||||
assert scanner._hash_index.get_path("hash-one") == _normalize_path(tmp_path / "one.txt")
|
||||
assert scanner._hash_index.get_path("hash-two") == _normalize_path(tmp_path / "nested" / "two.txt")
|
||||
@@ -190,7 +191,8 @@ async def test_initialize_in_background_applies_scan_result(tmp_path: Path, monk
|
||||
_normalize_path(tmp_path / "one.txt"),
|
||||
_normalize_path(tmp_path / "nested" / "two.txt"),
|
||||
}
|
||||
assert {item["license_flags"] for item in cache.raw_data} == {DEFAULT_LICENSE_FLAGS}
|
||||
# build_license_flags({}) returns 113 (defaults: allowNoCredit + ["Sell"] + derivatives + differentLicense)
|
||||
assert {item["license_flags"] for item in cache.raw_data} == {113}
|
||||
assert scanner._hash_index.get_path("hash-two") == _normalize_path(tmp_path / "nested" / "two.txt")
|
||||
assert scanner._tags_count == {"alpha": 1, "beta": 1}
|
||||
assert scanner._excluded_models == [_normalize_path(tmp_path / "skip-file.txt")]
|
||||
|
||||
@@ -16,7 +16,9 @@ def test_resolve_license_payload_defaults():
|
||||
assert payload["allowDerivatives"] is True
|
||||
assert payload["allowDifferentLicense"] is True
|
||||
assert payload["allowCommercialUse"] == ["Sell"]
|
||||
assert flags == 127
|
||||
# Default ["Sell"] only sets the Sell bit (16), plus NoCredit (1),
|
||||
# Derivatives (32) and DifferentLicense (64) = 113.
|
||||
assert flags == 113
|
||||
|
||||
|
||||
def test_build_license_flags_custom_values():
|
||||
@@ -34,11 +36,10 @@ def test_build_license_flags_custom_values():
|
||||
assert payload["allowDifferentLicense"] is False
|
||||
|
||||
flags = build_license_flags(source)
|
||||
# Sell automatically enables all commercial bits including image.
|
||||
assert flags == 30
|
||||
assert flags == 18
|
||||
|
||||
|
||||
def test_build_license_flags_respects_commercial_hierarchy():
|
||||
def test_build_license_flags_independent_values():
|
||||
base = {
|
||||
"allowNoCredit": False,
|
||||
"allowDerivatives": False,
|
||||
@@ -46,14 +47,10 @@ def test_build_license_flags_respects_commercial_hierarchy():
|
||||
}
|
||||
|
||||
assert build_license_flags({**base, "allowCommercialUse": []}) == 0
|
||||
# Rent adds rent and rentcivit permissions.
|
||||
assert build_license_flags({**base, "allowCommercialUse": ["Rent"]}) == 12
|
||||
# RentCivit alone should only set its own bit.
|
||||
assert build_license_flags({**base, "allowCommercialUse": ["Rent"]}) == 8
|
||||
assert build_license_flags({**base, "allowCommercialUse": ["RentCivit"]}) == 4
|
||||
# Image only toggles the image bit.
|
||||
assert build_license_flags({**base, "allowCommercialUse": ["Image"]}) == 2
|
||||
# Sell forces all commercial bits regardless of image listing.
|
||||
assert build_license_flags({**base, "allowCommercialUse": ["Sell"]}) == 30
|
||||
assert build_license_flags({**base, "allowCommercialUse": ["Sell"]}) == 16
|
||||
|
||||
|
||||
def test_build_license_flags_parses_aggregate_string():
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="section__toggles">
|
||||
<label class="toggle-item">
|
||||
<span class="toggle-item__label">No Credit Required</span>
|
||||
<span class="toggle-item__label" title="Use the model without crediting the creator">No Credit Required</span>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-switch"
|
||||
@@ -20,7 +20,7 @@
|
||||
</label>
|
||||
|
||||
<label class="toggle-item">
|
||||
<span class="toggle-item__label">Allow Selling</span>
|
||||
<span class="toggle-item__label" title="Allow selling generated images">Allow Selling</span>
|
||||
<button
|
||||
type="button"
|
||||
class="toggle-switch"
|
||||
|
||||
@@ -104,6 +104,66 @@ function removeLoraExtension(fileName = '') {
|
||||
return fileName.replace(/\.(safetensors|ckpt|pt|bin)$/i, '');
|
||||
}
|
||||
|
||||
let _loraSyntaxFormatCache = null;
|
||||
let _loraSyntaxFormatRefreshPromise = null;
|
||||
|
||||
function _getLoraSyntaxFormat() {
|
||||
if (_loraSyntaxFormatCache !== null) {
|
||||
return _loraSyntaxFormatCache;
|
||||
}
|
||||
return 'legacy';
|
||||
}
|
||||
|
||||
async function _fetchLoraSyntaxFormat() {
|
||||
try {
|
||||
const response = await api.fetchApi('/lm/settings');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && data.settings) {
|
||||
_loraSyntaxFormatCache = data.settings.lora_syntax_format || 'legacy';
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
if (_loraSyntaxFormatCache === null) {
|
||||
_loraSyntaxFormatCache = 'legacy';
|
||||
}
|
||||
}
|
||||
|
||||
function _triggerBackgroundRefresh() {
|
||||
if (_loraSyntaxFormatRefreshPromise) {
|
||||
return;
|
||||
}
|
||||
_loraSyntaxFormatRefreshPromise = _fetchLoraSyntaxFormat().finally(() => {
|
||||
_loraSyntaxFormatRefreshPromise = null;
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshLoraSyntaxFormat() {
|
||||
await _fetchLoraSyntaxFormat();
|
||||
}
|
||||
|
||||
function _initLoraSyntaxFormat() {
|
||||
_triggerBackgroundRefresh();
|
||||
}
|
||||
_initLoraSyntaxFormat();
|
||||
|
||||
function _initLoraSyntaxFormatReactive() {
|
||||
window.addEventListener('storage', (e) => {
|
||||
if (e.key === 'lm:lora-syntax-format-changed') {
|
||||
_triggerBackgroundRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
_triggerBackgroundRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
_initLoraSyntaxFormatReactive();
|
||||
|
||||
function parseSearchTokens(term = '') {
|
||||
const include = [];
|
||||
const exclude = [];
|
||||
@@ -231,6 +291,10 @@ const MODEL_BEHAVIORS = {
|
||||
const folder = directories.length ? directories.join('/') + '/' : '';
|
||||
const loraName = folder + baseName;
|
||||
|
||||
const resultName = _getLoraSyntaxFormat() === 'legacy'
|
||||
? baseName
|
||||
: loraName;
|
||||
|
||||
let strength = 1.0;
|
||||
let hasStrength = false;
|
||||
let clipStrength = null;
|
||||
@@ -265,9 +329,9 @@ const MODEL_BEHAVIORS = {
|
||||
}
|
||||
|
||||
if (clipStrength !== null) {
|
||||
return formatAutocompleteInsertion(`<lora:${loraName}:${strength}:${clipStrength}>`);
|
||||
return formatAutocompleteInsertion(`<lora:${resultName}:${strength}:${clipStrength}>`);
|
||||
}
|
||||
return formatAutocompleteInsertion(`<lora:${loraName}:${strength}>`);
|
||||
return formatAutocompleteInsertion(`<lora:${resultName}:${strength}>`);
|
||||
}
|
||||
},
|
||||
embeddings: {
|
||||
@@ -1433,6 +1497,11 @@ class AutoComplete {
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
// Prevent textarea from losing focus - same fix as createItemElement
|
||||
itemEl.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
itemEl.addEventListener('mouseenter', () => {
|
||||
this.selectItem(index, { manual: true });
|
||||
});
|
||||
@@ -2161,6 +2230,16 @@ class AutoComplete {
|
||||
item.appendChild(nameSpan);
|
||||
}
|
||||
|
||||
// Prevent textarea from losing focus when clicking dropdown items.
|
||||
// Without this, the blur event fires before click, and the blur handler's
|
||||
// formatAutocompleteTextOnBlur() modifies the text and triggers hide()
|
||||
// via suppressAutocompleteOnce, removing this item from the DOM before
|
||||
// the click handler can execute. This specifically breaks the case where
|
||||
// the text has a comma not followed by a space (e.g. "<lora:X:1>,search").
|
||||
item.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// Hover and selection handlers
|
||||
item.addEventListener('mouseenter', () => {
|
||||
this.selectItem(index, { manual: true });
|
||||
@@ -2748,4 +2827,4 @@ class AutoComplete {
|
||||
}
|
||||
}
|
||||
|
||||
export { AutoComplete };
|
||||
export { AutoComplete, refreshLoraSyntaxFormat };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { forwardMiddleMouseToCanvas, forwardWheelToCanvas } from "./utils.js";
|
||||
import { copyToClipboard } from "./loras_widget_utils.js";
|
||||
|
||||
const MIN_HEIGHT = 150;
|
||||
const GROUP_EDITOR_ID = "lm-trigger-group-editor";
|
||||
@@ -568,6 +569,56 @@ function toggleGroupEditor(widget, index, anchorEl) {
|
||||
openGroupEditor(widget, index, anchorEl);
|
||||
}
|
||||
|
||||
function showTagContextMenu(event, tagData, index, widget, anchorEl) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
closeGroupEditor(widget);
|
||||
|
||||
const existingMenu = document.querySelector('.lm-lora-context-menu');
|
||||
if (existingMenu) {
|
||||
existingMenu.remove();
|
||||
}
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'lm-lora-context-menu';
|
||||
menu.style.left = `${event.clientX}px`;
|
||||
menu.style.top = `${event.clientY}px`;
|
||||
|
||||
const copyOption = document.createElement('div');
|
||||
copyOption.className = 'lm-lora-menu-item';
|
||||
copyOption.innerHTML = `<span class="lm-lora-menu-item-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></span>Copy`;
|
||||
copyOption.addEventListener('click', () => {
|
||||
menu.remove();
|
||||
document.removeEventListener('click', closeMenu);
|
||||
copyToClipboard(tagData.text, 'Copied to clipboard');
|
||||
});
|
||||
menu.appendChild(copyOption);
|
||||
|
||||
if (isGroupTag(tagData) && Array.isArray(tagData.items) && tagData.items.length > 1) {
|
||||
const editOption = document.createElement('div');
|
||||
editOption.className = 'lm-lora-menu-item';
|
||||
editOption.innerHTML = `<span class="lm-lora-menu-item-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></span>Edit Group`;
|
||||
editOption.addEventListener('click', () => {
|
||||
menu.remove();
|
||||
document.removeEventListener('click', closeMenu);
|
||||
toggleGroupEditor(widget, index, anchorEl);
|
||||
renderGroupEditor(widget, tagData, index);
|
||||
});
|
||||
menu.appendChild(editOption);
|
||||
}
|
||||
|
||||
document.body.appendChild(menu);
|
||||
|
||||
const closeMenu = (e) => {
|
||||
if (!menu.contains(e.target)) {
|
||||
menu.remove();
|
||||
document.removeEventListener('click', closeMenu);
|
||||
}
|
||||
};
|
||||
setTimeout(() => document.addEventListener('click', closeMenu), 0);
|
||||
}
|
||||
|
||||
export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.02, options = {}) {
|
||||
const container = document.createElement("div");
|
||||
container.className = "comfy-tags-container";
|
||||
@@ -618,6 +669,10 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
|
||||
});
|
||||
});
|
||||
|
||||
tagEl.addEventListener("contextmenu", (e) => {
|
||||
showTagContextMenu(e, tagData, index, widget, tagEl);
|
||||
});
|
||||
|
||||
if (showStrengthInfo) {
|
||||
tagEl.addEventListener("wheel", (e) => {
|
||||
e.preventDefault();
|
||||
@@ -728,11 +783,13 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
|
||||
};
|
||||
|
||||
editButton.addEventListener("click", openEditor);
|
||||
groupChip.addEventListener("contextmenu", openEditor);
|
||||
|
||||
groupChip.appendChild(editButton);
|
||||
}
|
||||
|
||||
groupChip.addEventListener("contextmenu", (e) => {
|
||||
showTagContextMenu(e, tagData, index, widget, groupChip);
|
||||
});
|
||||
|
||||
groupChip.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (editButton && e.target === editButton) {
|
||||
@@ -773,6 +830,11 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
|
||||
container.removeChild(container.firstChild);
|
||||
}
|
||||
|
||||
const existingMenu = document.querySelector('.lm-lora-context-menu');
|
||||
if (existingMenu) {
|
||||
existingMenu.remove();
|
||||
}
|
||||
|
||||
const normalizedTags = Array.isArray(tagsData) ? tagsData : [];
|
||||
const showStrengthInfo = widget.allowStrengthAdjustment ?? allowStrengthAdjustment;
|
||||
const groupAnchors = new Map();
|
||||
|
||||
@@ -398,13 +398,13 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section[data-v-dea4adf6] {
|
||||
.section[data-v-07ddd3df] {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.section__header[data-v-dea4adf6] {
|
||||
.section__header[data-v-07ddd3df] {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.section__title[data-v-dea4adf6] {
|
||||
.section__title[data-v-07ddd3df] {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
@@ -412,21 +412,21 @@
|
||||
color: var(--fg-color, #fff);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.section__toggles[data-v-dea4adf6] {
|
||||
.section__toggles[data-v-07ddd3df] {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
.toggle-item[data-v-dea4adf6] {
|
||||
.toggle-item[data-v-07ddd3df] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-item__label[data-v-dea4adf6] {
|
||||
.toggle-item__label[data-v-07ddd3df] {
|
||||
font-size: 12px;
|
||||
color: var(--fg-color, #fff);
|
||||
}
|
||||
.toggle-switch[data-v-dea4adf6] {
|
||||
.toggle-switch[data-v-07ddd3df] {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
@@ -435,7 +435,7 @@
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.toggle-switch__track[data-v-dea4adf6] {
|
||||
.toggle-switch__track[data-v-07ddd3df] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--comfy-input-bg, #333);
|
||||
@@ -443,11 +443,11 @@
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.toggle-switch--active .toggle-switch__track[data-v-dea4adf6] {
|
||||
.toggle-switch--active .toggle-switch__track[data-v-07ddd3df] {
|
||||
background: rgba(66, 153, 225, 0.3);
|
||||
border-color: rgba(66, 153, 225, 0.6);
|
||||
}
|
||||
.toggle-switch__thumb[data-v-dea4adf6] {
|
||||
.toggle-switch__thumb[data-v-07ddd3df] {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2px;
|
||||
@@ -458,12 +458,12 @@
|
||||
transition: all 0.2s;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.toggle-switch--active .toggle-switch__thumb[data-v-dea4adf6] {
|
||||
.toggle-switch--active .toggle-switch__thumb[data-v-07ddd3df] {
|
||||
transform: translateX(16px);
|
||||
background: #4299e1;
|
||||
opacity: 1;
|
||||
}
|
||||
.toggle-switch:hover .toggle-switch__thumb[data-v-dea4adf6] {
|
||||
.toggle-switch:hover .toggle-switch__thumb[data-v-07ddd3df] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -2223,7 +2223,7 @@ to { transform: rotate(360deg);
|
||||
})();
|
||||
var _a;
|
||||
import { app as app$1 } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import { api as api$1 } from "../../../scripts/api.js";
|
||||
/**
|
||||
* @vue/shared v3.5.26
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
@@ -11094,7 +11094,10 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
||||
], -1)),
|
||||
createBaseVNode("div", _hoisted_2$e, [
|
||||
createBaseVNode("label", _hoisted_3$c, [
|
||||
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "toggle-item__label" }, "No Credit Required", -1)),
|
||||
_cache[3] || (_cache[3] = createBaseVNode("span", {
|
||||
class: "toggle-item__label",
|
||||
title: "Use the model without crediting the creator"
|
||||
}, "No Credit Required", -1)),
|
||||
createBaseVNode("button", {
|
||||
type: "button",
|
||||
class: normalizeClass(["toggle-switch", { "toggle-switch--active": __props.noCreditRequired }]),
|
||||
@@ -11107,7 +11110,10 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
||||
])], 10, _hoisted_4$a)
|
||||
]),
|
||||
createBaseVNode("label", _hoisted_5$8, [
|
||||
_cache[5] || (_cache[5] = createBaseVNode("span", { class: "toggle-item__label" }, "Allow Selling", -1)),
|
||||
_cache[5] || (_cache[5] = createBaseVNode("span", {
|
||||
class: "toggle-item__label",
|
||||
title: "Allow selling generated images"
|
||||
}, "Allow Selling", -1)),
|
||||
createBaseVNode("button", {
|
||||
type: "button",
|
||||
class: normalizeClass(["toggle-switch", { "toggle-switch--active": __props.allowSelling }]),
|
||||
@@ -11124,7 +11130,7 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
||||
};
|
||||
}
|
||||
});
|
||||
const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["__scopeId", "data-v-dea4adf6"]]);
|
||||
const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["__scopeId", "data-v-07ddd3df"]]);
|
||||
const _hoisted_1$e = { class: "preview" };
|
||||
const _hoisted_2$d = { class: "preview__title" };
|
||||
const _hoisted_3$b = ["disabled"];
|
||||
@@ -15031,6 +15037,54 @@ function createModeChangeCallback(node, updateDownstreamLoaders2, nodeSpecificCa
|
||||
};
|
||||
}
|
||||
const app = {};
|
||||
const api = {
|
||||
fetchApi: (...args) => fetch(...args),
|
||||
addEventListener: (eventName, handler) => document.addEventListener(eventName, handler),
|
||||
removeEventListener: (eventName, handler) => document.removeEventListener(eventName, handler)
|
||||
};
|
||||
let _loraSyntaxFormatCache = null;
|
||||
let _loraSyntaxFormatRefreshPromise = null;
|
||||
async function _fetchLoraSyntaxFormat() {
|
||||
try {
|
||||
const response = await api.fetchApi("/lm/settings");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && data.settings) {
|
||||
_loraSyntaxFormatCache = data.settings.lora_syntax_format || "legacy";
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
if (_loraSyntaxFormatCache === null) {
|
||||
_loraSyntaxFormatCache = "legacy";
|
||||
}
|
||||
}
|
||||
function _triggerBackgroundRefresh() {
|
||||
if (_loraSyntaxFormatRefreshPromise) {
|
||||
return;
|
||||
}
|
||||
_loraSyntaxFormatRefreshPromise = _fetchLoraSyntaxFormat().finally(() => {
|
||||
_loraSyntaxFormatRefreshPromise = null;
|
||||
});
|
||||
}
|
||||
function _initLoraSyntaxFormat() {
|
||||
_triggerBackgroundRefresh();
|
||||
}
|
||||
_initLoraSyntaxFormat();
|
||||
function _initLoraSyntaxFormatReactive() {
|
||||
window.addEventListener("storage", (e) => {
|
||||
if (e.key === "lm:lora-syntax-format-changed") {
|
||||
_triggerBackgroundRefresh();
|
||||
}
|
||||
});
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
_triggerBackgroundRefresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
_initLoraSyntaxFormatReactive();
|
||||
const ROOT_GRAPH_ID = "root";
|
||||
const LORA_PROVIDER_NODE_TYPES = [
|
||||
"Lora Stacker (LoraManager)",
|
||||
@@ -15407,7 +15461,7 @@ function createLoraRandomizerWidget(node) {
|
||||
const vueApp = createApp(LoraRandomizerWidget, {
|
||||
widget,
|
||||
node,
|
||||
api
|
||||
api: api$1
|
||||
});
|
||||
vueApp.use(PrimeVue, {
|
||||
unstyled: true,
|
||||
@@ -15482,7 +15536,7 @@ function createLoraCyclerWidget(node) {
|
||||
const vueApp = createApp(LoraCyclerWidget, {
|
||||
widget,
|
||||
node,
|
||||
api
|
||||
api: api$1
|
||||
});
|
||||
vueApp.use(PrimeVue, {
|
||||
unstyled: true,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user