Compare commits
16 Commits
0cc640cfaa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d5b4b7312 | ||
|
|
7803bd542d | ||
|
|
f0a86dbbc0 | ||
|
|
682e964f89 | ||
|
|
908464bc0a | ||
|
|
0ffee3a854 | ||
|
|
8aa9739c44 | ||
|
|
50739bbb43 | ||
|
|
e849303763 | ||
|
|
241b2e15d2 | ||
|
|
88da754504 | ||
|
|
b4a706651f | ||
|
|
ff7cc6d9bb | ||
|
|
454210a47c | ||
|
|
2d7c404ebb | ||
|
|
e23d803ecf |
@@ -12,33 +12,39 @@
|
||||
"2018cfh",
|
||||
"W+K+White",
|
||||
"wackop",
|
||||
"Takkan",
|
||||
"Phil",
|
||||
"Carl G.",
|
||||
"Arlecchino Shion",
|
||||
"stone9k",
|
||||
"$MetaSamsara",
|
||||
"itismyelement",
|
||||
"Gingko Biloba",
|
||||
"onesecondinosaur",
|
||||
"stone9k",
|
||||
"Takkan",
|
||||
"Charles Blakemore",
|
||||
"Rob Williams",
|
||||
"Rosenthal",
|
||||
"Francisco Tatis",
|
||||
"Tobi_Swagg",
|
||||
"Andrew Wilson",
|
||||
"Greybush",
|
||||
"Gooohokrbe",
|
||||
"Ricky Carter",
|
||||
"JongWon Han",
|
||||
"OldBones",
|
||||
"VantAI",
|
||||
"runte3221",
|
||||
"Illrigger",
|
||||
"FreelancerZ",
|
||||
"Edgar Tejeda",
|
||||
"Jorge Hussni",
|
||||
"Liam MacDougal",
|
||||
"Fraser Cross",
|
||||
"Polymorphic Indeterminate",
|
||||
"Birdy",
|
||||
"Marc Whiffen",
|
||||
"Jorge Hussni",
|
||||
"Kiba",
|
||||
"Birdy",
|
||||
"Skalabananen",
|
||||
"Kiba",
|
||||
"Reno Lam",
|
||||
"Mozzel",
|
||||
"sig",
|
||||
"Christian Byrne",
|
||||
"DM",
|
||||
@@ -46,39 +52,41 @@
|
||||
"Estragon",
|
||||
"J\\B/ 8r0wns0n",
|
||||
"Snaggwort",
|
||||
"Arlecchino Shion",
|
||||
"Charles Blakemore",
|
||||
"Rob Williams",
|
||||
"ClockDaemon",
|
||||
"Jonathan Ross",
|
||||
"KD",
|
||||
"Omnidex",
|
||||
"Nazono_hito",
|
||||
"Tyler Trebuchon",
|
||||
"Release Cabrakan",
|
||||
"Tobi_Swagg",
|
||||
"contrite831",
|
||||
"SG",
|
||||
"carozzz",
|
||||
"James Dooley",
|
||||
"zenbound",
|
||||
"Buzzard",
|
||||
"jmack",
|
||||
"Adam Shaw",
|
||||
"Mark Corneglio",
|
||||
"SarcasticHashtag",
|
||||
"Cosmosis",
|
||||
"Anthony Rizzo",
|
||||
"iamresist",
|
||||
"Gooohokrbe",
|
||||
"RedrockVP",
|
||||
"Wolffen",
|
||||
"FloPro4Sho",
|
||||
"James Todd",
|
||||
"OldBones",
|
||||
"Steven Pfeiffer",
|
||||
"Tim",
|
||||
"Timmy",
|
||||
"Johnny",
|
||||
"Lisster",
|
||||
"Michael Wong",
|
||||
"Illrigger",
|
||||
"whudunit",
|
||||
"Tom Corrigan",
|
||||
"dl0901dm",
|
||||
"JackieWang",
|
||||
"fnkylove",
|
||||
"Julian V",
|
||||
"Steven Owens",
|
||||
"Yushio",
|
||||
"Vik71it",
|
||||
"Echo",
|
||||
@@ -86,147 +94,137 @@
|
||||
"Robert Stacey",
|
||||
"PM",
|
||||
"Todd Keck",
|
||||
"Mozzel",
|
||||
"Gingko Biloba",
|
||||
"Sterilized",
|
||||
"Briton Heilbrun",
|
||||
"Aleksander Wujczyk",
|
||||
"BadassArabianMofo",
|
||||
"Sterilized",
|
||||
"Pascal Dahle",
|
||||
"quarz",
|
||||
"Greg",
|
||||
"Penfore",
|
||||
"Greg",
|
||||
"JSST",
|
||||
"esthe",
|
||||
"lmsupporter",
|
||||
"IamAyam",
|
||||
"zounic",
|
||||
"wfpearl",
|
||||
"Baekdoosixt",
|
||||
"Jonathan Ross",
|
||||
"Jack B Nimble",
|
||||
"Nazono_hito",
|
||||
"Melville Parrish",
|
||||
"daniel dove",
|
||||
"Lustre",
|
||||
"JW Sin",
|
||||
"contrite831",
|
||||
"Alex",
|
||||
"bh",
|
||||
"confiscated Zyra",
|
||||
"Marlon Daniels",
|
||||
"Starkselle",
|
||||
"Aaron Bleuer",
|
||||
"LacesOut!",
|
||||
"greebles",
|
||||
"Adam Shaw",
|
||||
"Tee Gee",
|
||||
"Anthony Rizzo",
|
||||
"tarek helmi",
|
||||
"Cosmosis",
|
||||
"M Postkasse",
|
||||
"FloPro4Sho",
|
||||
"ASLPro3D",
|
||||
"Jacob Hoehler",
|
||||
"FinalyFree",
|
||||
"Weasyl",
|
||||
"Timmy",
|
||||
"Johnny",
|
||||
"Lex Song",
|
||||
"Cory Paza",
|
||||
"Tak",
|
||||
"Gonzalo Andre Allendes Lopez",
|
||||
"Zach Gonser",
|
||||
"Big Red",
|
||||
"whudunit",
|
||||
"Jimmy Ledbetter",
|
||||
"Luc Job",
|
||||
"dl0901dm",
|
||||
"Philip Hempel",
|
||||
"corde",
|
||||
"Nick Walker",
|
||||
"lh qwe",
|
||||
"Julian V",
|
||||
"Steven Owens",
|
||||
"Bishoujoker",
|
||||
"conner",
|
||||
"aai",
|
||||
"Briton Heilbrun",
|
||||
"Tori",
|
||||
"wildnut",
|
||||
"Princess Bright Eyes",
|
||||
"AbstractAss",
|
||||
"Felipe dos Santos",
|
||||
"ViperC",
|
||||
"jean jahren",
|
||||
"Aleksander Wujczyk",
|
||||
"AM Kuro",
|
||||
"Markus",
|
||||
"S Sang",
|
||||
"ViperC",
|
||||
"Ran C",
|
||||
"Sangheili460",
|
||||
"MagnaInsomnia",
|
||||
"Karl P.",
|
||||
"Akira_HentAI",
|
||||
"MagnaInsomnia",
|
||||
"Gordon Cole",
|
||||
"yuxz69",
|
||||
"Douglas Gaspar",
|
||||
"AlexDuKaNa",
|
||||
"George",
|
||||
"esthe",
|
||||
"andrew.tappan",
|
||||
"dw",
|
||||
"N/A",
|
||||
"The Spawn",
|
||||
"Phil",
|
||||
"graysock",
|
||||
"Pozadine1",
|
||||
"Greenmoustache",
|
||||
"zounic",
|
||||
"fancypants",
|
||||
"IamAyam",
|
||||
"Eldithor",
|
||||
"Joboshy",
|
||||
"Digital",
|
||||
"JaxMax",
|
||||
"takyamtom",
|
||||
"奚明 刘",
|
||||
"Bohemian Corporal",
|
||||
"Dan",
|
||||
"confiscated Zyra",
|
||||
"Jwk0205",
|
||||
"Bro Xie",
|
||||
"준희 김",
|
||||
"yer fey",
|
||||
"batblue",
|
||||
"carey6409",
|
||||
"Olive",
|
||||
"太郎 ゲーム",
|
||||
"Tee Gee",
|
||||
"Some Guy Named Barry",
|
||||
"jinxedx",
|
||||
"tarek helmi",
|
||||
"Max Marklund",
|
||||
"Tomohiro Baba",
|
||||
"David Ortega",
|
||||
"AELOX",
|
||||
"Dankin",
|
||||
"Nicfit23",
|
||||
"Noora",
|
||||
"wamekukyouzin",
|
||||
"drum matthieu",
|
||||
"Dogmaster",
|
||||
"Matt Wenzel",
|
||||
"Mattssn",
|
||||
"Lex Song",
|
||||
"John Saveas",
|
||||
"Frank Nitty",
|
||||
"Pronredn",
|
||||
"Christopher Michel",
|
||||
"Serge Bekenkamp",
|
||||
"Jimmy Ledbetter",
|
||||
"DougPeterson",
|
||||
"LeoZero",
|
||||
"Antonio Pontes",
|
||||
"ApathyJones",
|
||||
"nahinahi9",
|
||||
"lh qwe",
|
||||
"Kevin John Duck",
|
||||
"conner",
|
||||
"Dustin Chen",
|
||||
"dan",
|
||||
"Yaboi",
|
||||
"Blackfish95",
|
||||
"Mouthlessman",
|
||||
"Steam Steam",
|
||||
"Damon Cunliffe",
|
||||
"CryptoTraderJK",
|
||||
"Davaitamin",
|
||||
"Princess Bright Eyes",
|
||||
"Paul Kroll",
|
||||
"AbstractAss",
|
||||
"otaku fra",
|
||||
"Ran C",
|
||||
"tedcor",
|
||||
"Fotek Design",
|
||||
"Felipe dos Santos",
|
||||
"Bas Imagineer",
|
||||
"Markus",
|
||||
"MiraiKuriyamaSy",
|
||||
"Adam Taylor",
|
||||
"Douglas Gaspar",
|
||||
"Weird_With_A_Beard",
|
||||
"MadSpin",
|
||||
"Pozadine1",
|
||||
"AlexDuKaNa",
|
||||
"George",
|
||||
"dw",
|
||||
"Qarob",
|
||||
"AIGooner",
|
||||
"inbijiburu",
|
||||
"Luc",
|
||||
"ProtonPrince",
|
||||
"DiffDuck",
|
||||
"elu3199",
|
||||
"Nick “Loadstone” D",
|
||||
"Hasturkun",
|
||||
"Jon Sandman",
|
||||
"Ubivis",
|
||||
@@ -234,54 +232,45 @@
|
||||
"thesoftwaredruid",
|
||||
"wundershark",
|
||||
"mr_dinosaur",
|
||||
"Tyrswood",
|
||||
"linnfrey",
|
||||
"Gamalonia",
|
||||
"Vir",
|
||||
"Pkrsky",
|
||||
"Joboshy",
|
||||
"Bohemian Corporal",
|
||||
"Dan",
|
||||
"奚明 刘",
|
||||
"Josef Lanzl",
|
||||
"Seth Christensen",
|
||||
"Nerezza",
|
||||
"Griffin Dahlberg",
|
||||
"Draven T",
|
||||
"yer fey",
|
||||
"준희 김",
|
||||
"Error_Rule34_Not_found",
|
||||
"Gerald Welly",
|
||||
"Roslynd",
|
||||
"Geolog",
|
||||
"jinxedx",
|
||||
"Neco28",
|
||||
"Aquatic Coffee",
|
||||
"Dankin",
|
||||
"ethanfel",
|
||||
"Tomohiro Baba",
|
||||
"David Ortega",
|
||||
"Noora",
|
||||
"Cristian Vazquez",
|
||||
"Frank Nitty",
|
||||
"Mattssn",
|
||||
"Magic Noob",
|
||||
"Focuschannel",
|
||||
"DougPeterson",
|
||||
"Jeff",
|
||||
"Bruce",
|
||||
"Kevin John Duck",
|
||||
"Anthony Faxlandez",
|
||||
"Kevin Christopher",
|
||||
"Ouro Boros",
|
||||
"Blackfish95",
|
||||
"Chad Idk",
|
||||
"Yaboi",
|
||||
"dd",
|
||||
"Paul Kroll",
|
||||
"MiraiKuriyamaSy",
|
||||
"semicolon drainpipe",
|
||||
"Thesharingbrother",
|
||||
"Bas Imagineer",
|
||||
"Pat Hen",
|
||||
"Steam Steam",
|
||||
"CryptoTraderJK",
|
||||
"Davaitamin",
|
||||
"Dušan Ryban",
|
||||
"tedcor",
|
||||
"Fotek Design",
|
||||
"sjon kreutz",
|
||||
"John Statham",
|
||||
"ResidentDeviant",
|
||||
"Nihongasuki",
|
||||
"JC",
|
||||
"Prompt Pirate",
|
||||
"uwutismxd",
|
||||
"MadSpin",
|
||||
"Metryman55",
|
||||
"inbijiburu",
|
||||
"decoy",
|
||||
"Tyrswood",
|
||||
"Nick “Loadstone” D",
|
||||
"Ray Wing",
|
||||
"Ranzitho",
|
||||
"Gus",
|
||||
@@ -290,6 +279,7 @@
|
||||
"David LaVallee",
|
||||
"ae",
|
||||
"Tr4shP4nda",
|
||||
"Gamalonia",
|
||||
"WRL_SPR",
|
||||
"capn",
|
||||
"Joseph",
|
||||
@@ -302,77 +292,60 @@
|
||||
"Moon Knight",
|
||||
"몽타주",
|
||||
"Kland",
|
||||
"zenobeus",
|
||||
"Jackthemind",
|
||||
"ryoma",
|
||||
"Stryker",
|
||||
"raf8osz",
|
||||
"ElitaSSJ4",
|
||||
"blikkies",
|
||||
"Chris",
|
||||
"Hailshem",
|
||||
"kudari",
|
||||
"Naomi Hale Danchi",
|
||||
"dc7431",
|
||||
"Vir",
|
||||
"Brian M",
|
||||
"Nerezza",
|
||||
"sanborondon",
|
||||
"Seth Christensen",
|
||||
"Draven T",
|
||||
"Taylor Funk",
|
||||
"aezin",
|
||||
"Thought2Form",
|
||||
"jcay015",
|
||||
"Kevin Picco",
|
||||
"Erik Lopez",
|
||||
"Shock Shockor",
|
||||
"Mateo Curić",
|
||||
"Goldwaters",
|
||||
"Zude",
|
||||
"Aquatic Coffee",
|
||||
"Eris3D",
|
||||
"m",
|
||||
"ethanfel",
|
||||
"Pierce McBride",
|
||||
"Joshua Gray",
|
||||
"Kyler",
|
||||
"Focuschannel",
|
||||
"Mikko Hemilä",
|
||||
"aRtFuL_DodGeR",
|
||||
"Jamie Ogletree",
|
||||
"a _",
|
||||
"James Coleman",
|
||||
"CrimsonDX",
|
||||
"Martial",
|
||||
"Anthony Faxlandez",
|
||||
"battu",
|
||||
"Emil Andersson",
|
||||
"Chad Idk",
|
||||
"DarkSunset",
|
||||
"Billy Gladky",
|
||||
"Yuji Kaneko",
|
||||
"Probis",
|
||||
"Dušan Ryban",
|
||||
"ItsGeneralButtNaked",
|
||||
"Pat Hen",
|
||||
"semicolon drainpipe",
|
||||
"Jordan Shaw",
|
||||
"Rops Alot",
|
||||
"Thesharingbrother",
|
||||
"Sam",
|
||||
"sjon kreutz",
|
||||
"Nimess",
|
||||
"SRDB",
|
||||
"Ace Ventura",
|
||||
"g unit",
|
||||
"Youguang",
|
||||
"Metryman55",
|
||||
"andrewzpong",
|
||||
"FrxzenSnxw",
|
||||
"BossGame",
|
||||
"lrdchs",
|
||||
"ResidentDeviant",
|
||||
"Nihongasuki",
|
||||
"JC",
|
||||
"Prompt Pirate",
|
||||
"uwutismxd",
|
||||
"momokai",
|
||||
"Hailshem",
|
||||
"kudari",
|
||||
"Naomi Hale Danchi",
|
||||
"dc7431",
|
||||
"zenobeus",
|
||||
"ken",
|
||||
"Inversity",
|
||||
"AIVORY3D",
|
||||
"epicgamer0020690",
|
||||
"Joshua Porrata",
|
||||
"keemun",
|
||||
"SuBu",
|
||||
"RedPIXel",
|
||||
"Kevinj",
|
||||
"Wind",
|
||||
"Jackthemind",
|
||||
"Nexus",
|
||||
"Ramneek“Guy”Ashok",
|
||||
"squid_actually",
|
||||
@@ -385,80 +358,81 @@
|
||||
"emyth",
|
||||
"chriphost",
|
||||
"KitKatM",
|
||||
"ryoma",
|
||||
"socrasteeze",
|
||||
"ResidentDeviant",
|
||||
"OrganicArtifact",
|
||||
"Stryker",
|
||||
"MudkipMedkitz",
|
||||
"gzmzmvp",
|
||||
"Welkor",
|
||||
"John Martin",
|
||||
"raf8osz",
|
||||
"ElitaSSJ4",
|
||||
"Richard",
|
||||
"blikkies",
|
||||
"Andrew",
|
||||
"Chris",
|
||||
"Robert Wegemund",
|
||||
"Littlehuggy",
|
||||
"moranqianlong",
|
||||
"Gregory Kozhemiak",
|
||||
"mrjuan",
|
||||
"Brian Buie",
|
||||
"Shock Shockor",
|
||||
"Sadlip",
|
||||
"Haru Yotu",
|
||||
"Goldwaters",
|
||||
"Eric Whitney",
|
||||
"Joey Callahan",
|
||||
"Zude",
|
||||
"Ivan Tadic",
|
||||
"Mike Simone",
|
||||
"John J Linehan",
|
||||
"Kyler",
|
||||
"Elliot E",
|
||||
"Morgandel",
|
||||
"Kyron Mahan",
|
||||
"Matura Arbeit",
|
||||
"Theerat Jiramate",
|
||||
"aRtFuL_DodGeR",
|
||||
"Noah",
|
||||
"Jacob McDaniel",
|
||||
"X",
|
||||
"Sloan Steddy",
|
||||
"TBitz33",
|
||||
"Anonym dkjglfleeoeldldldlkf",
|
||||
"Temikus",
|
||||
"Artokun",
|
||||
"Michael Taylor",
|
||||
"SendingRavens",
|
||||
"Derek Baker",
|
||||
"CrimsonDX",
|
||||
"Michael Anthony Scott",
|
||||
"DarkSunset",
|
||||
"Atilla Berke Pekduyar",
|
||||
"Michael Docherty",
|
||||
"Nathan",
|
||||
"Billy Gladky",
|
||||
"NICHOLAS BAXLEY",
|
||||
"Decx _",
|
||||
"Paul Hartsuyker",
|
||||
"elitassj",
|
||||
"Jacob Winter",
|
||||
"Probis",
|
||||
"Ed Wang",
|
||||
"ItsGeneralButtNaked",
|
||||
"Nimess",
|
||||
"SRDB",
|
||||
"g unit",
|
||||
"Distortik",
|
||||
"David",
|
||||
"Meilo",
|
||||
"Pen Bouryoung",
|
||||
"Youguang",
|
||||
"四糸凜音",
|
||||
"shinonomeiro",
|
||||
"Snille",
|
||||
"MaartenAlbers",
|
||||
"khanh duy",
|
||||
"xybrightsummer",
|
||||
"jreedatchison",
|
||||
"PhilW",
|
||||
"Saya",
|
||||
"andrewzpong",
|
||||
"FrxzenSnxw",
|
||||
"BossGame",
|
||||
"lrdchs",
|
||||
"Tree Tagger",
|
||||
"Janik",
|
||||
"Inversity",
|
||||
"Crocket",
|
||||
"Cruel",
|
||||
"MRBlack",
|
||||
"AIVORY3D",
|
||||
"Kevinj",
|
||||
"Mitchell Robson",
|
||||
"Kiyoe",
|
||||
"humptynutz",
|
||||
"michael.isaza",
|
||||
"Kalnei",
|
||||
"Whitepinetrader",
|
||||
"OrganicArtifact",
|
||||
"Scott",
|
||||
"MudkipMedkitz",
|
||||
"ResidentDeviant",
|
||||
"deanbrian",
|
||||
"POPPIN",
|
||||
"Alex Wortman",
|
||||
"Cody",
|
||||
"Raku",
|
||||
"smart.edge5178",
|
||||
"emadsultan",
|
||||
"InformedViewz",
|
||||
"CHKeeho80",
|
||||
"Bubbafett",
|
||||
@@ -466,76 +440,152 @@
|
||||
"Menard",
|
||||
"Skyfire83",
|
||||
"Adam Rinehart",
|
||||
"D",
|
||||
"Pitpe11",
|
||||
"TheD1rtyD03",
|
||||
"moonpetal",
|
||||
"SomeDude",
|
||||
"g9p0o",
|
||||
"nanana",
|
||||
"TheHolySheep",
|
||||
"Monte Won",
|
||||
"SpringBootisTrash",
|
||||
"carsten",
|
||||
"ikok",
|
||||
"Nathen+Choi",
|
||||
"T",
|
||||
"LarsesFPC",
|
||||
"cocona",
|
||||
"sfasdfasfdsa",
|
||||
"Buecyb99",
|
||||
"4IXplr0r3r",
|
||||
"dfklsjfkljslfjd",
|
||||
"hayden",
|
||||
"ahoystan",
|
||||
"Leland Saunders",
|
||||
"Welkor",
|
||||
"David Schenck",
|
||||
"John Martin",
|
||||
"Wolfe7D1",
|
||||
"Ink Temptation",
|
||||
"Bob Barker",
|
||||
"edk",
|
||||
"moranqianlong",
|
||||
"Kalli Core",
|
||||
"Aeternyx",
|
||||
"elleshar666",
|
||||
"YOU SINWOO",
|
||||
"ja s",
|
||||
"Doug Mason",
|
||||
"ACTUALLY_the_Real_Willem_Dafoe",
|
||||
"Haru Yotu",
|
||||
"Kauffy",
|
||||
"Jeremy Townsend",
|
||||
"EpicElric",
|
||||
"Sean voets",
|
||||
"Owen Gwosdz",
|
||||
"John J Linehan",
|
||||
"Elliot E",
|
||||
"Thomas Wanner",
|
||||
"Theerat Jiramate",
|
||||
"Kyron Mahan",
|
||||
"Edward Kennedy",
|
||||
"Justin Blaylock",
|
||||
"Devil Lude",
|
||||
"Matura Arbeit",
|
||||
"Nick Kage",
|
||||
"kevin stoddard",
|
||||
"Jack Dole",
|
||||
"TBitz33",
|
||||
"Anonym dkjglfleeoeldldldlkf",
|
||||
"Vane Holzer",
|
||||
"psytrax",
|
||||
"Cyrus Fett",
|
||||
"Ezokewn",
|
||||
"SendingRavens",
|
||||
"hexxish",
|
||||
"CptNeo",
|
||||
"notedfakes",
|
||||
"Maso",
|
||||
"Eric Ketchum",
|
||||
"NICHOLAS BAXLEY",
|
||||
"Michael Docherty",
|
||||
"Michael Scott",
|
||||
"Kevin Wallace",
|
||||
"Matheus Couto",
|
||||
"Saya",
|
||||
"ChicRic",
|
||||
"mercur",
|
||||
"J C",
|
||||
"Ed Wang",
|
||||
"Paul Hartsuyker",
|
||||
"elitassj",
|
||||
"Jacob Winter",
|
||||
"Ryan Presley Ng",
|
||||
"Wes Sims",
|
||||
"Donor4115",
|
||||
"Lyavph",
|
||||
"David",
|
||||
"Meilo",
|
||||
"Filippo Ferrari",
|
||||
"Pen Bouryoung",
|
||||
"shinonomeiro",
|
||||
"Snille",
|
||||
"MaartenAlbers",
|
||||
"khanh duy",
|
||||
"xybrightsummer",
|
||||
"jreedatchison",
|
||||
"PhilW",
|
||||
"Janik",
|
||||
"Cruel",
|
||||
"MRBlack",
|
||||
"Kiyoe",
|
||||
"humptynutz",
|
||||
"michael.isaza",
|
||||
"Kalnei",
|
||||
"Scott",
|
||||
"Muratoraccio",
|
||||
"Ginnie",
|
||||
"emadsultan",
|
||||
"D",
|
||||
"nanana",
|
||||
"Fthehappy",
|
||||
"rsamerica",
|
||||
"Alan+Cano",
|
||||
"FeralOpticsAI",
|
||||
"Pavlaki",
|
||||
"generic404",
|
||||
"Doug+Rintoul",
|
||||
"Noor",
|
||||
"Yorunai",
|
||||
"quantenmecha",
|
||||
"abattoirblues",
|
||||
"Jason+Nash",
|
||||
"BillyBoy84",
|
||||
"zounik",
|
||||
"DarkRoast",
|
||||
"letzte",
|
||||
"Nasty+Hobbit",
|
||||
"Sora+Yori",
|
||||
"lrdchs2",
|
||||
"Duk3+Rand0m",
|
||||
"4IXplr0r3r",
|
||||
"hayden",
|
||||
"ahoystan",
|
||||
"Leland Saunders",
|
||||
"Bob Barker",
|
||||
"edk",
|
||||
"JBsuede",
|
||||
"Time Valentine",
|
||||
"Aeternyx",
|
||||
"YOU SINWOO",
|
||||
"りん あめ",
|
||||
"ja s",
|
||||
"Михал Михалыч",
|
||||
"Matt",
|
||||
"Doug Mason",
|
||||
"Jeremy Townsend",
|
||||
"Frogmilk",
|
||||
"Sean voets",
|
||||
"Owen Gwosdz",
|
||||
"SPJ",
|
||||
"Thomas Wanner",
|
||||
"Bryan Rutkowski",
|
||||
"Devil Lude",
|
||||
"David Murcko",
|
||||
"kevin stoddard",
|
||||
"Jack Dole",
|
||||
"max blo",
|
||||
"Xenon Xue",
|
||||
"CptNeo",
|
||||
"JackJohnnyJim",
|
||||
"Dmitry Ryzhov",
|
||||
"Maso",
|
||||
"Edward Ten Eyck",
|
||||
"Eric Ketchum",
|
||||
"Kevin Wallace",
|
||||
"Matheus Couto",
|
||||
"ChicRic",
|
||||
"Henrique Faiolli",
|
||||
"mercur",
|
||||
"Solixer",
|
||||
"J C",
|
||||
"jinksta187",
|
||||
"Andrew Wilkinson",
|
||||
"Manu Thetug",
|
||||
"Karlanx",
|
||||
"Yves Poezevara",
|
||||
"operationancut",
|
||||
"Teriak47",
|
||||
"Just me",
|
||||
"Raf Stahelin",
|
||||
"Вячеслав Маринин",
|
||||
"Lyavph",
|
||||
"Filippo Ferrari",
|
||||
"Cola Matthew",
|
||||
"OniNoKen",
|
||||
"Iain Wisely",
|
||||
@@ -576,98 +626,121 @@
|
||||
"dg",
|
||||
"Maarten Harms",
|
||||
"Israel",
|
||||
"Muratoraccio",
|
||||
"SelfishMedic",
|
||||
"Ginnie",
|
||||
"adderleighn",
|
||||
"EnragedAntelope",
|
||||
"Alan+Cano",
|
||||
"FeralOpticsAI",
|
||||
"Pavlaki",
|
||||
"generic404",
|
||||
"lighthawke",
|
||||
"Terraformer",
|
||||
"GDS+DEV",
|
||||
"4rt+r3d",
|
||||
"low9",
|
||||
"Winged",
|
||||
"you+halo9",
|
||||
"YassineKhaled",
|
||||
"YK12",
|
||||
"MatteKey",
|
||||
"Flob",
|
||||
"ShiroSenpai",
|
||||
"Somebody",
|
||||
"Inkognito",
|
||||
"Somebody",
|
||||
"Gramer+Gumbyte",
|
||||
"Crescent~San",
|
||||
"Tan+Huynh",
|
||||
"AiGirlTS",
|
||||
"D",
|
||||
"datasl4ve",
|
||||
"Somebody",
|
||||
"Dark_Pest",
|
||||
"Aza",
|
||||
"Jacky+Ho",
|
||||
"koopa990",
|
||||
"Karru",
|
||||
"ChaChanoKo",
|
||||
"null",
|
||||
"bo",
|
||||
"The+Forgetful+Dev",
|
||||
"redcarrot",
|
||||
"powerbot99",
|
||||
"Mateusz+Kosela",
|
||||
"Doug+Rintoul",
|
||||
"Noor",
|
||||
"Yorunai",
|
||||
"Bula",
|
||||
"quantenmecha",
|
||||
"abattoirblues",
|
||||
"Jason+Nash",
|
||||
"BillyBoy84",
|
||||
"DarkRoast",
|
||||
"zounik",
|
||||
"letzte",
|
||||
"Nasty+Hobbit",
|
||||
"SgtFluffles",
|
||||
"lrdchs2",
|
||||
"Duk3+Rand0m",
|
||||
"KUJYAKU",
|
||||
"NathenChoi",
|
||||
"Thomas+Reck",
|
||||
"Larses",
|
||||
"cocona",
|
||||
"Coeur+de+cochon",
|
||||
"David Schenck",
|
||||
"han b",
|
||||
"Nico",
|
||||
"Banana Joe",
|
||||
"_ G3n",
|
||||
"Donovan Jenkins",
|
||||
"JBsuede",
|
||||
"Tú Nguyễn Lý Hoàng",
|
||||
"Michael Eid",
|
||||
"beersandbacon",
|
||||
"Maximilian Pyko",
|
||||
"Invis",
|
||||
"Justin Houston",
|
||||
"Time Valentine",
|
||||
"Bob barker",
|
||||
"Ben D",
|
||||
"Garrett Wood",
|
||||
"Ronan Delevacq",
|
||||
"james",
|
||||
"Christian Schäfer",
|
||||
"OrochiNights",
|
||||
"Michael Zhu",
|
||||
"ACTUALLY_the_Real_Willem_Dafoe",
|
||||
"gonzalo",
|
||||
"Seraphy",
|
||||
"Михал Михалыч",
|
||||
"雨の心 落",
|
||||
"Matt",
|
||||
"AllTimeNoobie",
|
||||
"jumpd",
|
||||
"John C",
|
||||
"Rim",
|
||||
"Dave Abraham",
|
||||
"Joaquin Hierrezuelo",
|
||||
"Dismem",
|
||||
"Frogmilk",
|
||||
"SPJ",
|
||||
"Locrospiel",
|
||||
"Jairus Knudsen",
|
||||
"Jarrid Lee",
|
||||
"Xan Dionysus",
|
||||
"Nathan lee",
|
||||
"Kor",
|
||||
"Joseph Hanson",
|
||||
"Mewtora",
|
||||
"Middo",
|
||||
"Forbidden Atelier",
|
||||
"Bryan Rutkowski",
|
||||
"John Rednoulf",
|
||||
"Spire",
|
||||
"Adictedtohumping",
|
||||
"Boba Smith",
|
||||
"Towelie",
|
||||
"Cyrus Fett",
|
||||
"MR.Bear",
|
||||
"dsffsdfsdfsdfsdfsdf",
|
||||
"Jean-françois SEMA",
|
||||
"Kurt",
|
||||
"max blo",
|
||||
"Xenon Xue",
|
||||
"JackJohnnyJim",
|
||||
"Edward Ten Eyck",
|
||||
"ivistorm",
|
||||
"Sauv",
|
||||
"Steven",
|
||||
"TenaciousD",
|
||||
"Khánh Đặng",
|
||||
"Chase Kwon",
|
||||
"Ted Cart",
|
||||
"Inyoshu",
|
||||
"Goober719",
|
||||
"Chad Barnes",
|
||||
"Person Y",
|
||||
"David Spearing",
|
||||
"James Ming",
|
||||
"vanditking",
|
||||
"kripitonga",
|
||||
"Rizzi",
|
||||
"nimin",
|
||||
"OMAR LUCIANO",
|
||||
"Ken+Suzuki",
|
||||
"hannibal",
|
||||
"Jo+Example",
|
||||
"BrentBertram",
|
||||
"Tigon",
|
||||
"eumelzocker",
|
||||
"dxjaymz",
|
||||
"L C",
|
||||
"Dude"
|
||||
"Dude",
|
||||
"CK"
|
||||
],
|
||||
"totalCount": 666
|
||||
"totalCount": 739
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
## Overview
|
||||
|
||||
The **LoRA Manager Civitai Extension** is a Browser extension designed to work seamlessly with [LoRA Manager](https://github.com/willmiao/ComfyUI-Lora-Manager) to significantly enhance your browsing experience on [Civitai](https://civitai.com). With this extension, you can:
|
||||
|
||||
✅ Instantly see which models are already present in your local library
|
||||
✅ Download new models with a single click
|
||||
✅ Manage downloads efficiently with queue and parallel download support
|
||||
✅ Keep your downloaded models automatically organized according to your custom settings
|
||||
|
||||

|
||||
|
||||
**Update:** It now also supports browsing on [CivArchive](https://civarchive.com/) (formerly CivitaiArchive).
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Why Supporter Access?
|
||||
|
||||
LoRA Manager is built with love for the Stable Diffusion and ComfyUI communities. Your support makes it possible for me to keep improving and maintaining the tool full-time.
|
||||
|
||||
Supporter-exclusive features help ensure the long-term sustainability of LoRA Manager, allowing continuous updates, new features, and better performance for everyone.
|
||||
|
||||
Every contribution directly fuels development and keeps the core LoRA Manager free and open-source. In addition to monthly supporters, one-time donation supporters will also receive a license key, with the duration scaling according to the contribution amount. Thank you for helping keep this project alive and growing. ❤️
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Supported Browsers & Installation Methods
|
||||
|
||||
| Browser | Installation Method |
|
||||
|--------------------|-------------------------------------------------------------------------------------|
|
||||
| **Google Chrome** | [Chrome Web Store link](https://chromewebstore.google.com/detail/capigligggeijgmocnaflanlbghnamgm?utm_source=item-share-cb) |
|
||||
| **Microsoft Edge** | Install via Chrome Web Store (compatible) |
|
||||
| **Brave Browser** | Install via Chrome Web Store (compatible) |
|
||||
| **Opera** | Install via Chrome Web Store (compatible) |
|
||||
| **Firefox** | <div id="firefox-install" class="install-ok"><a href="https://github.com/willmiao/lm-civitai-extension-firefox/releases/latest/download/extension.xpi">📦 Install Firefox Extension (reviewed and verified by Mozilla)</a></div> |
|
||||
|
||||
For non-Chrome browsers (e.g., Microsoft Edge), you can typically install extensions from the Chrome Web Store by following these steps: open the extension’s Chrome Web Store page, click 'Get extension', then click 'Allow' when prompted to enable installations from other stores, and finally click 'Add extension' to complete the installation.
|
||||
|
||||
---
|
||||
|
||||
## Privacy & Security
|
||||
|
||||
I understand concerns around browser extensions and privacy, and I want to be fully transparent about how the **LM Civitai Extension** works:
|
||||
|
||||
- **Reviewed and Verified**
|
||||
This extension has been **manually reviewed and approved by the Chrome Web Store**. The Firefox version uses the **exact same code** (only the packaging format differs) and has passed **Mozilla’s Add-on review**.
|
||||
|
||||
- **Minimal Network Access**
|
||||
The only external server this extension connects to is:
|
||||
**`https://willmiao.shop`** — used solely for **license validation**.
|
||||
|
||||
It does **not collect, transmit, or store any personal or usage data**.
|
||||
No browsing history, no user IDs, no analytics, no hidden trackers.
|
||||
|
||||
- **Local-Only Model Detection**
|
||||
Model detection and LoRA Manager communication all happen **locally** within your browser, directly interacting with your local LoRA Manager backend.
|
||||
|
||||
I value your trust and are committed to keeping your local setup private and secure. If you have any questions, feel free to reach out!
|
||||
|
||||
---
|
||||
|
||||
## How to Use
|
||||
|
||||
After installing the extension, you'll automatically receive a **7-day trial** to explore all features.
|
||||
|
||||
When the extension is correctly installed and your license is valid:
|
||||
|
||||
- Open **Civitai**, and you'll see visual indicators added by the extension on model cards, showing:
|
||||
- ✅ Models already present in your local library
|
||||
- ⬇️ A download button for models not in your library
|
||||
|
||||
Clicking the download button adds the corresponding model version to the download queue, waiting to be downloaded. You can set up to **5 models to download simultaneously**.
|
||||
|
||||
### Visual Indicators Appear On:
|
||||
|
||||
- **Home Page** — Featured models
|
||||
- **Models Page**
|
||||
- **Creator Profiles** — If the creator has set their models to be visible
|
||||
- **Recommended Resources** — On individual model pages
|
||||
|
||||
### Version Buttons on Model Pages
|
||||
|
||||
On a specific model page, visual indicators also appear on version buttons, showing which versions are already in your local library.
|
||||
|
||||
**Starting from v0.4.8**, model pages use a dedicated download button for better compatibility. When switching to a specific version by clicking a version button:
|
||||
|
||||
- The new **dedicated download button** directly triggers download via **LoRA Manager**
|
||||
- The **original download button** remains unchanged for standard browser downloads
|
||||
|
||||

|
||||
|
||||
### Hide Models Already in Library (Beta)
|
||||
|
||||
**New in v0.4.8**: A new **Hide models already in library (Beta)** option makes it easier to focus on models you haven't added yet. It can be enabled from Settings, or toggled quickly using **Ctrl + Shift + H** (macOS: **Command + Shift + H**).
|
||||
|
||||
### Resources on Image Pages — now shows in-library indicators for image resources plus one-click recipe import
|
||||
|
||||
- **One-Click Import Civitai Image as Recipe** — Import any Civitai image as a recipe with a single click in the Resources Used panel.
|
||||
- **Auto-Queue Missing Assets** — In Settings you can decide if LoRAs or checkpoints referenced by that image should automatically be added to your download queue.
|
||||
- **More Accurate Metadata** — Importing directly from the page is faster than copying inside LM and keeps on-site tags and other metadata perfectly aligned.
|
||||
|
||||

|
||||
|
||||
[](https://github.com/user-attachments/assets/41fd4240-c949-4f83-bde7-8f3124c09494)
|
||||
|
||||
---
|
||||
|
||||
## Model Download Location & LoRA Manager Settings
|
||||
|
||||
To use the **one-click download function**, you must first set:
|
||||
|
||||
- Your **Default LoRAs Root**
|
||||
- Your **Default Checkpoints Root**
|
||||
|
||||
These are set within LoRA Manager's settings.
|
||||
|
||||
When everything is configured, downloaded model files will be placed in:
|
||||
|
||||
`<Default_Models_Root>/<Base_Model_of_the_Model>/<First_Tag_of_the_Model>`
|
||||
|
||||
|
||||
### Update: Default Path Customization (2025-07-21)
|
||||
|
||||
A new setting to customize the default download path has been added in the nightly version. You can now personalize where models are saved when downloading via the LM Civitai Extension.
|
||||
|
||||

|
||||
|
||||
The previous YAML path mapping file will be deprecated—settings will now be unified in settings.json to simplify configuration.
|
||||
|
||||
---
|
||||
|
||||
## Backend Port Configuration
|
||||
|
||||
If your **ComfyUI** or **LoRA Manager** backend is running on a port **other than the default 8188**, you must configure the backend port in the extension's settings.
|
||||
|
||||
After correctly setting and saving the port, you'll see in the extension's header area:
|
||||
- A **Healthy** status with the tooltip: `Connected to LoRA Manager on port xxxx`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Connecting to a Remote LoRA Manager
|
||||
|
||||
If your LoRA Manager is running on another computer, you can still connect from your browser using port forwarding.
|
||||
|
||||
> **Why can't you set a remote IP directly?**
|
||||
>
|
||||
> For privacy and security, the extension only requests access to `http://127.0.0.1/*`. Supporting remote IPs would require much broader permissions, which may be rejected by browser stores and could raise user concerns.
|
||||
|
||||
**Solution: Port Forwarding with `socat`**
|
||||
|
||||
On your browser computer, run:
|
||||
|
||||
`socat TCP-LISTEN:8188,bind=127.0.0.1,fork TCP:REMOTE.IP.ADDRESS.HERE:8188`
|
||||
|
||||
- Replace `REMOTE.IP.ADDRESS.HERE` with the IP of the machine running LoRA Manager.
|
||||
- Adjust the port if needed.
|
||||
|
||||
This lets the extension connect to `127.0.0.1:8188` as usual, with traffic forwarded to your remote server.
|
||||
|
||||
_Thanks to user **Temikus** for sharing this solution!_
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
The extension will evolve alongside **LoRA Manager** improvements. Planned features include:
|
||||
|
||||
- [x] Support for **additional model types** (e.g., embeddings)
|
||||
- [x] One-click **Recipe Import**
|
||||
- [x] Display of in-library status for all resources in the **Resources Used** section of the image page
|
||||
- [x] One-click **Auto-organize Models**
|
||||
- [x] **Hide models already in library (Beta)** - Focus on models you haven't added yet
|
||||
|
||||
**Stay tuned — and thank you for your support!**
|
||||
|
||||
---
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "Automatisch organisieren",
|
||||
"skipMetadataRefresh": "Metadaten-Aktualisierung für ausgewählte Modelle überspringen",
|
||||
"resumeMetadataRefresh": "Metadaten-Aktualisierung für ausgewählte Modelle fortsetzen",
|
||||
"setFavorite": "Als Favorit setzen",
|
||||
"setFavoriteCount": "Als Favorit setzen ({favorited}/{total})",
|
||||
"unfavorite": "Aus Favoriten entfernen",
|
||||
"deleteAll": "Ausgewählte löschen",
|
||||
"downloadMissingLoras": "Fehlende LoRAs herunterladen",
|
||||
"clear": "Auswahl löschen",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "Inhaltsbewertung auf {level} für {count} Modell(e) gesetzt",
|
||||
"bulkContentRatingPartial": "Inhaltsbewertung auf {level} für {success} Modell(e) gesetzt, {failed} fehlgeschlagen",
|
||||
"bulkContentRatingFailed": "Inhaltsbewertung für ausgewählte Modelle konnte nicht aktualisiert werden",
|
||||
"bulkFavoriteUpdating": "Füge {count} Modell(e) zu Favoriten hinzu...",
|
||||
"bulkUnfavoriteUpdating": "Entferne {count} Modell(e) aus Favoriten...",
|
||||
"bulkFavoritePartialAdded": "{success} Modell(e) zu Favoriten hinzugefügt, {failed} fehlgeschlagen",
|
||||
"bulkFavoritePartialRemoved": "{success} Modell(e) aus Favoriten entfernt, {failed} fehlgeschlagen",
|
||||
"bulkFavoriteFailed": "Fehler beim Aktualisieren des Favoritenstatus",
|
||||
"bulkUpdatesChecking": "Ausgewählte {type}-Modelle werden auf Updates geprüft...",
|
||||
"bulkUpdatesSuccess": "Updates für {count} ausgewählte {type}-Modelle verfügbar",
|
||||
"bulkUpdatesNone": "Keine Updates für ausgewählte {type}-Modelle gefunden",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "Auto-Organize Selected",
|
||||
"skipMetadataRefresh": "Skip Metadata Refresh for Selected",
|
||||
"resumeMetadataRefresh": "Resume Metadata Refresh for Selected",
|
||||
"setFavorite": "Set as Favorite",
|
||||
"setFavoriteCount": "Set as Favorite ({favorited}/{total})",
|
||||
"unfavorite": "Remove from Favorites",
|
||||
"deleteAll": "Delete Selected",
|
||||
"downloadMissingLoras": "Download Missing LoRAs",
|
||||
"clear": "Clear Selection",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "Set content rating to {level} for {count} model(s)",
|
||||
"bulkContentRatingPartial": "Set content rating to {level} for {success} model(s), {failed} failed",
|
||||
"bulkContentRatingFailed": "Failed to update content rating for selected models",
|
||||
"bulkFavoriteUpdating": "Adding {count} model(s) to favorites...",
|
||||
"bulkUnfavoriteUpdating": "Removing {count} model(s) from favorites...",
|
||||
"bulkFavoritePartialAdded": "Added {success} model(s) to favorites, {failed} failed",
|
||||
"bulkFavoritePartialRemoved": "Removed {success} model(s) from favorites, {failed} failed",
|
||||
"bulkFavoriteFailed": "Failed to update favorite status for selected models",
|
||||
"bulkUpdatesChecking": "Checking selected {type}(s) for updates...",
|
||||
"bulkUpdatesSuccess": "Updates available for {count} selected {type}(s)",
|
||||
"bulkUpdatesNone": "No updates found for selected {type}(s)",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "Auto-organizar seleccionados",
|
||||
"skipMetadataRefresh": "Omitir actualización de metadatos para seleccionados",
|
||||
"resumeMetadataRefresh": "Reanudar actualización de metadatos para seleccionados",
|
||||
"setFavorite": "Marcar como favorito",
|
||||
"setFavoriteCount": "Marcar como favorito ({favorited}/{total})",
|
||||
"unfavorite": "Quitar de favoritos",
|
||||
"deleteAll": "Eliminar seleccionados",
|
||||
"downloadMissingLoras": "Descargar LoRAs faltantes",
|
||||
"clear": "Limpiar selección",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "Clasificación de contenido establecida en {level} para {count} modelo(s)",
|
||||
"bulkContentRatingPartial": "Clasificación de contenido establecida en {level} para {success} modelo(s), {failed} fallaron",
|
||||
"bulkContentRatingFailed": "No se pudo actualizar la clasificación de contenido para los modelos seleccionados",
|
||||
"bulkFavoriteUpdating": "Añadiendo {count} modelo(s) a favoritos...",
|
||||
"bulkUnfavoriteUpdating": "Eliminando {count} modelo(s) de favoritos...",
|
||||
"bulkFavoritePartialAdded": "{success} modelo(s) añadido(s) a favoritos, {failed} fallido(s)",
|
||||
"bulkFavoritePartialRemoved": "{success} modelo(s) eliminado(s) de favoritos, {failed} fallido(s)",
|
||||
"bulkFavoriteFailed": "Error al actualizar el estado de favorito",
|
||||
"bulkUpdatesChecking": "Comprobando actualizaciones para {type} seleccionados...",
|
||||
"bulkUpdatesSuccess": "Actualizaciones disponibles para {count} {type} seleccionados",
|
||||
"bulkUpdatesNone": "No se encontraron actualizaciones para los {type} seleccionados",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "Auto-organiser la sélection",
|
||||
"skipMetadataRefresh": "Ignorer l'actualisation des métadonnées pour la sélection",
|
||||
"resumeMetadataRefresh": "Reprendre l'actualisation des métadonnées pour la sélection",
|
||||
"setFavorite": "Définir comme favori",
|
||||
"setFavoriteCount": "Définir comme favori ({favorited}/{total})",
|
||||
"unfavorite": "Retirer des favoris",
|
||||
"deleteAll": "Supprimer la sélection",
|
||||
"downloadMissingLoras": "Télécharger les LoRAs manquants",
|
||||
"clear": "Effacer la sélection",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "Classification du contenu définie sur {level} pour {count} modèle(s)",
|
||||
"bulkContentRatingPartial": "Classification du contenu définie sur {level} pour {success} modèle(s), {failed} échec(s)",
|
||||
"bulkContentRatingFailed": "Impossible de mettre à jour la classification du contenu pour les modèles sélectionnés",
|
||||
"bulkFavoriteUpdating": "Ajout de {count} modèle(s) aux favoris...",
|
||||
"bulkUnfavoriteUpdating": "Suppression de {count} modèle(s) des favoris...",
|
||||
"bulkFavoritePartialAdded": "{success} modèle(s) ajouté(s) aux favoris, {failed} échec(s)",
|
||||
"bulkFavoritePartialRemoved": "{success} modèle(s) retiré(s) des favoris, {failed} échec(s)",
|
||||
"bulkFavoriteFailed": "Échec de la mise à jour du statut de favori",
|
||||
"bulkUpdatesChecking": "Vérification des mises à jour pour les {type} sélectionnés...",
|
||||
"bulkUpdatesSuccess": "Mises à jour disponibles pour {count} {type} sélectionnés",
|
||||
"bulkUpdatesNone": "Aucune mise à jour trouvée pour les {type} sélectionnés",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "ארגן אוטומטית נבחרים",
|
||||
"skipMetadataRefresh": "דילוג על רענון מטא-נתונים לנבחרים",
|
||||
"resumeMetadataRefresh": "המשך רענון מטא-נתונים לנבחרים",
|
||||
"setFavorite": "הגדר כמועדף",
|
||||
"setFavoriteCount": "הגדר כמועדף ({favorited}/{total})",
|
||||
"unfavorite": "הסר ממועדפים",
|
||||
"deleteAll": "מחק נבחרים",
|
||||
"downloadMissingLoras": "הורדת LoRAs חסרים",
|
||||
"clear": "נקה בחירה",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "דירוג התוכן הוגדר ל-{level} עבור {count} מודלים",
|
||||
"bulkContentRatingPartial": "דירוג התוכן הוגדר ל-{level} עבור {success} מודלים, {failed} נכשלו",
|
||||
"bulkContentRatingFailed": "עדכון דירוג התוכן עבור המודלים שנבחרו נכשל",
|
||||
"bulkFavoriteUpdating": "מוסיף {count} דגמים למועדפים...",
|
||||
"bulkUnfavoriteUpdating": "מסיר {count} דגמים ממועדפים...",
|
||||
"bulkFavoritePartialAdded": "{success} דגמים נוספו למועדפים, {failed} נכשלו",
|
||||
"bulkFavoritePartialRemoved": "{success} דגמים הוסרו ממועדפים, {failed} נכשלו",
|
||||
"bulkFavoriteFailed": "עדכון סטטוס מועדפים נכשל",
|
||||
"bulkUpdatesChecking": "בודק עדכונים עבור {type} שנבחרו...",
|
||||
"bulkUpdatesSuccess": "יש עדכונים עבור {count} {type} שנבחרו",
|
||||
"bulkUpdatesNone": "לא נמצאו עדכונים עבור {type} שנבחרו",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "自動整理を実行",
|
||||
"skipMetadataRefresh": "選択したモデルのメタデータ更新をスキップ",
|
||||
"resumeMetadataRefresh": "選択したモデルのメタデータ更新を再開",
|
||||
"setFavorite": "お気に入りに設定",
|
||||
"setFavoriteCount": "お気に入りに設定 ({favorited}/{total})",
|
||||
"unfavorite": "お気に入りから削除",
|
||||
"deleteAll": "選択したものを削除",
|
||||
"downloadMissingLoras": "不足している LoRA をダウンロード",
|
||||
"clear": "選択をクリア",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "{count} 件のモデルのコンテンツレーティングを {level} に設定しました",
|
||||
"bulkContentRatingPartial": "{success} 件のモデルのコンテンツレーティングを {level} に設定、{failed} 件は失敗しました",
|
||||
"bulkContentRatingFailed": "選択したモデルのコンテンツレーティングを更新できませんでした",
|
||||
"bulkFavoriteUpdating": "{count} 個のモデルをお気に入りに追加中...",
|
||||
"bulkUnfavoriteUpdating": "{count} 個のモデルをお気に入りから削除中...",
|
||||
"bulkFavoritePartialAdded": "{success} 個のモデルをお気に入りに追加、{failed} 個失敗",
|
||||
"bulkFavoritePartialRemoved": "{success} 個のモデルをお気に入りから削除、{failed} 個失敗",
|
||||
"bulkFavoriteFailed": "お気に入り状態の更新に失敗しました",
|
||||
"bulkUpdatesChecking": "選択された{type}の更新を確認しています...",
|
||||
"bulkUpdatesSuccess": "{count} 件の選択された{type}に利用可能な更新があります",
|
||||
"bulkUpdatesNone": "選択された{type}には更新が見つかりませんでした",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "자동 정리 선택",
|
||||
"skipMetadataRefresh": "선택한 모델의 메타데이터 새로고침 건너뛰기",
|
||||
"resumeMetadataRefresh": "선택한 모델의 메타데이터 새로고침 재개",
|
||||
"setFavorite": "즐겨찾기로 설정",
|
||||
"setFavoriteCount": "즐겨찾기로 설정 ({favorited}/{total})",
|
||||
"unfavorite": "즐겨찾기 해제",
|
||||
"deleteAll": "선택된 항목 삭제",
|
||||
"downloadMissingLoras": "누락된 LoRA 다운로드",
|
||||
"clear": "선택 지우기",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "{count}개 모델의 콘텐츠 등급을 {level}(으)로 설정했습니다",
|
||||
"bulkContentRatingPartial": "{success}개 모델의 콘텐츠 등급을 {level}(으)로 설정했고, {failed}개는 실패했습니다",
|
||||
"bulkContentRatingFailed": "선택한 모델의 콘텐츠 등급을 업데이트하지 못했습니다",
|
||||
"bulkFavoriteUpdating": "{count}개 모델을 즐겨찾기에 추가 중...",
|
||||
"bulkUnfavoriteUpdating": "{count}개 모델을 즐겨찾기에서 제거 중...",
|
||||
"bulkFavoritePartialAdded": "{success}개 모델을 즐겨찾기에 추가, {failed}개 실패",
|
||||
"bulkFavoritePartialRemoved": "{success}개 모델을 즐겨찾기에서 제거, {failed}개 실패",
|
||||
"bulkFavoriteFailed": "즐겨찾기 상태 업데이트 실패",
|
||||
"bulkUpdatesChecking": "선택한 {type}의 업데이트를 확인하는 중...",
|
||||
"bulkUpdatesSuccess": "선택한 {count}개의 {type}에 사용할 수 있는 업데이트가 있습니다",
|
||||
"bulkUpdatesNone": "선택한 {type}에 대한 업데이트가 없습니다",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "Автоматически организовать выбранные",
|
||||
"skipMetadataRefresh": "Пропустить обновление метаданных для выбранных",
|
||||
"resumeMetadataRefresh": "Возобновить обновление метаданных для выбранных",
|
||||
"setFavorite": "Добавить в избранное",
|
||||
"setFavoriteCount": "Добавить в избранное ({favorited}/{total})",
|
||||
"unfavorite": "Удалить из избранного",
|
||||
"deleteAll": "Удалить выбранные",
|
||||
"downloadMissingLoras": "Скачать отсутствующие LoRAs",
|
||||
"clear": "Очистить выбор",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "Рейтинг контента установлен на {level} для {count} модель(ей)",
|
||||
"bulkContentRatingPartial": "Рейтинг контента {level} установлен для {success} модель(ей), {failed} не удалось",
|
||||
"bulkContentRatingFailed": "Не удалось обновить рейтинг контента для выбранных моделей",
|
||||
"bulkFavoriteUpdating": "Добавление {count} моделей в избранное...",
|
||||
"bulkUnfavoriteUpdating": "Удаление {count} моделей из избранного...",
|
||||
"bulkFavoritePartialAdded": "{success} моделей добавлено в избранное, {failed} не удалось",
|
||||
"bulkFavoritePartialRemoved": "{success} моделей удалено из избранного, {failed} не удалось",
|
||||
"bulkFavoriteFailed": "Не удалось обновить статус избранного",
|
||||
"bulkUpdatesChecking": "Проверка обновлений для выбранных {type}...",
|
||||
"bulkUpdatesSuccess": "Доступны обновления для {count} выбранных {type}",
|
||||
"bulkUpdatesNone": "Обновления для выбранных {type} не найдены",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "自动整理所选模型",
|
||||
"skipMetadataRefresh": "跳过所选模型的元数据刷新",
|
||||
"resumeMetadataRefresh": "恢复所选模型的元数据刷新",
|
||||
"setFavorite": "设为收藏",
|
||||
"setFavoriteCount": "设为收藏 ({favorited}/{total})",
|
||||
"unfavorite": "取消收藏",
|
||||
"deleteAll": "删除已选",
|
||||
"downloadMissingLoras": "下载缺失的 LoRAs",
|
||||
"clear": "清除选择",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "已将 {count} 个模型的内容评级设置为 {level}",
|
||||
"bulkContentRatingPartial": "已将 {success} 个模型的内容评级设置为 {level},{failed} 个失败",
|
||||
"bulkContentRatingFailed": "未能更新所选模型的内容评级",
|
||||
"bulkFavoriteUpdating": "正在将 {count} 个模型添加到收藏...",
|
||||
"bulkUnfavoriteUpdating": "正在将 {count} 个模型从收藏移除...",
|
||||
"bulkFavoritePartialAdded": "已将 {success} 个模型添加到收藏,{failed} 个失败",
|
||||
"bulkFavoritePartialRemoved": "已将 {success} 个模型从收藏移除,{failed} 个失败",
|
||||
"bulkFavoriteFailed": "更新收藏状态失败",
|
||||
"bulkUpdatesChecking": "正在检查所选 {type} 的更新...",
|
||||
"bulkUpdatesSuccess": "{count} 个所选 {type} 有可用更新",
|
||||
"bulkUpdatesNone": "所选 {type} 未发现更新",
|
||||
|
||||
@@ -687,6 +687,9 @@
|
||||
"autoOrganize": "自動整理所選模型",
|
||||
"skipMetadataRefresh": "跳過所選模型的元數據更新",
|
||||
"resumeMetadataRefresh": "恢復所選模型的元數據更新",
|
||||
"setFavorite": "設為收藏",
|
||||
"setFavoriteCount": "設為收藏 ({favorited}/{total})",
|
||||
"unfavorite": "取消收藏",
|
||||
"deleteAll": "刪除所選",
|
||||
"downloadMissingLoras": "下載缺失的 LoRAs",
|
||||
"clear": "清除選取",
|
||||
@@ -1699,6 +1702,11 @@
|
||||
"bulkContentRatingSet": "已將 {count} 個模型的內容分級設定為 {level}",
|
||||
"bulkContentRatingPartial": "已將 {success} 個模型的內容分級設定為 {level},{failed} 個失敗",
|
||||
"bulkContentRatingFailed": "無法更新所選模型的內容分級",
|
||||
"bulkFavoriteUpdating": "正在將 {count} 個模型加入收藏...",
|
||||
"bulkUnfavoriteUpdating": "正在將 {count} 個模型從收藏移除...",
|
||||
"bulkFavoritePartialAdded": "已將 {success} 個模型加入收藏,{failed} 個失敗",
|
||||
"bulkFavoritePartialRemoved": "已將 {success} 個模型從收藏移除,{failed} 個失敗",
|
||||
"bulkFavoriteFailed": "更新收藏狀態失敗",
|
||||
"bulkUpdatesChecking": "正在檢查所選 {type} 的更新...",
|
||||
"bulkUpdatesSuccess": "{count} 個所選 {type} 有可用更新",
|
||||
"bulkUpdatesNone": "所選 {type} 未找到更新",
|
||||
|
||||
@@ -33,6 +33,7 @@ from ...services.metadata_service import (
|
||||
update_metadata_providers,
|
||||
)
|
||||
from ...services.service_registry import ServiceRegistry
|
||||
from ...services.model_lifecycle_service import delete_model_artifacts
|
||||
from ...services.settings_manager import get_settings_manager
|
||||
from ...services.websocket_manager import ws_manager
|
||||
from ...services.downloader import get_downloader
|
||||
@@ -2082,6 +2083,78 @@ class ModelLibraryHandler:
|
||||
)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
async def delete_model_version(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
model_version_id_str = request.query.get("modelVersionId")
|
||||
if not model_version_id_str:
|
||||
return web.json_response(
|
||||
{"success": False, "error": "Missing required parameter: modelVersionId"},
|
||||
status=400,
|
||||
)
|
||||
try:
|
||||
model_version_id = int(model_version_id_str)
|
||||
except ValueError:
|
||||
return web.json_response(
|
||||
{"success": False, "error": "Parameter modelVersionId must be an integer"},
|
||||
status=400,
|
||||
)
|
||||
|
||||
lora_scanner = await self._service_registry.get_lora_scanner()
|
||||
checkpoint_scanner = await self._service_registry.get_checkpoint_scanner()
|
||||
embedding_scanner = await self._service_registry.get_embedding_scanner()
|
||||
|
||||
found_type = None
|
||||
file_path = None
|
||||
found_cache = None
|
||||
|
||||
for model_type, scanner in (
|
||||
("lora", lora_scanner),
|
||||
("checkpoint", checkpoint_scanner),
|
||||
("embedding", embedding_scanner),
|
||||
):
|
||||
cache = await scanner.get_cached_data()
|
||||
if cache and model_version_id in cache.version_index:
|
||||
found_type = model_type
|
||||
found_cache = cache
|
||||
entry = cache.version_index[model_version_id]
|
||||
file_path = entry.get("file_path")
|
||||
break
|
||||
|
||||
if not file_path:
|
||||
return web.json_response(
|
||||
{"success": False, "error": "Model version not found in any scanner cache"},
|
||||
status=404,
|
||||
)
|
||||
|
||||
target_dir = os.path.dirname(file_path)
|
||||
base_name = os.path.basename(file_path)
|
||||
file_name, extension = os.path.splitext(base_name)
|
||||
await delete_model_artifacts(target_dir, file_name, main_extension=extension)
|
||||
|
||||
if found_cache:
|
||||
found_cache.raw_data = [
|
||||
item
|
||||
for item in found_cache.raw_data
|
||||
if item.get("file_path") != file_path
|
||||
]
|
||||
await found_cache.resort()
|
||||
|
||||
history_service = await self._get_download_history_service()
|
||||
await history_service.mark_not_downloaded(found_type, model_version_id)
|
||||
|
||||
return web.json_response(
|
||||
{
|
||||
"success": True,
|
||||
"modelType": found_type,
|
||||
"modelVersionId": model_version_id,
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"Failed to delete model version: %s", exc, exc_info=True
|
||||
)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
async def get_model_versions_status(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
model_id_str = request.query.get("modelId")
|
||||
@@ -3118,6 +3191,7 @@ class MiscHandlerSet:
|
||||
"check_models_exist": self.model_library.check_models_exist,
|
||||
"get_model_version_download_status": self.model_library.get_model_version_download_status,
|
||||
"set_model_version_download_status": self.model_library.set_model_version_download_status,
|
||||
"delete_model_version": self.model_library.delete_model_version,
|
||||
"get_civitai_user_models": self.model_library.get_civitai_user_models,
|
||||
"download_metadata_archive": self.metadata_archive.download_metadata_archive,
|
||||
"remove_metadata_archive": self.metadata_archive.remove_metadata_archive,
|
||||
|
||||
@@ -91,6 +91,9 @@ MISC_ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
||||
RouteDefinition(
|
||||
"GET", "/api/lm/base-models/cache-status", "get_base_model_cache_status"
|
||||
),
|
||||
RouteDefinition(
|
||||
"GET", "/api/lm/delete-model-version", "delete_model_version"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -193,6 +193,9 @@ class CivitaiBaseModelService:
|
||||
"zimageturbo": "ZIT",
|
||||
"zimagebase": "ZIB",
|
||||
"anima": "ANI",
|
||||
"ernie": "ERNI",
|
||||
"ernie turbo": "ETRB",
|
||||
"nucleus": "NUCL",
|
||||
"svd": "SVD",
|
||||
"ltxv": "LTXV",
|
||||
"ltxv2": "LTV2",
|
||||
@@ -418,6 +421,9 @@ class CivitaiBaseModelService:
|
||||
"Kolors",
|
||||
"NoobAI",
|
||||
"Anima",
|
||||
"Ernie",
|
||||
"Ernie Turbo",
|
||||
"Nucleus",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -577,6 +577,59 @@ class CivitaiClient:
|
||||
logger.error(error_msg)
|
||||
return None
|
||||
|
||||
async def get_model_versions_by_hashes(
|
||||
self, hashes: List[str]
|
||||
) -> Optional[List[Dict]]:
|
||||
"""Fetch full version details for up to 100 SHA256 hashes via the batch endpoint.
|
||||
|
||||
Uses POST /api/v1/model-versions/by-hash which returns full version
|
||||
details including ``usageControl`` and ``earlyAccessEndsAt`` that are
|
||||
not available from the model-level API.
|
||||
|
||||
Args:
|
||||
hashes: List of SHA256 hashes (max 100 per batch; auto-split).
|
||||
|
||||
Returns:
|
||||
List of version dicts or None on failure.
|
||||
"""
|
||||
if not hashes:
|
||||
return []
|
||||
|
||||
BATCH_SIZE = 100
|
||||
all_versions: List[Dict] = []
|
||||
|
||||
for start in range(0, len(hashes), BATCH_SIZE):
|
||||
batch = hashes[start : start + BATCH_SIZE]
|
||||
try:
|
||||
success, result = await self._make_request(
|
||||
"POST",
|
||||
f"{self.base_url}/model-versions/by-hash",
|
||||
use_auth=True,
|
||||
json=batch,
|
||||
)
|
||||
if not success:
|
||||
logger.warning(
|
||||
"Batch by-hash request failed for %d hashes: %s",
|
||||
len(batch),
|
||||
result,
|
||||
)
|
||||
continue
|
||||
|
||||
if isinstance(result, list):
|
||||
all_versions.extend(result)
|
||||
else:
|
||||
logger.debug(
|
||||
"Unexpected by-hash response type: %s", type(result)
|
||||
)
|
||||
except RateLimitError:
|
||||
raise
|
||||
except Exception as exc: # pragma: no cover - defensive logging
|
||||
logger.error(
|
||||
"Error fetching model versions by hashes: %s", exc
|
||||
)
|
||||
|
||||
return all_versions if all_versions else None
|
||||
|
||||
async def get_user_models(self, username: str) -> Optional[List[Dict]]:
|
||||
"""Fetch all models for a specific Civitai user."""
|
||||
if not username:
|
||||
|
||||
@@ -108,6 +108,18 @@ class ModelMetadataProvider(ABC):
|
||||
) -> Optional[Dict[int, Dict]]:
|
||||
"""Fetch model versions for multiple model ids when supported."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_model_versions_by_hashes(
|
||||
self, hashes: List[str]
|
||||
) -> Optional[List[Dict]]:
|
||||
"""Fetch full version details for multiple SHA256 hashes.
|
||||
|
||||
Used specifically to retrieve ``usageControl`` which is only
|
||||
available from the per-version / by-hash API, not from model-level
|
||||
responses. Providers that cannot resolve hashes should let the
|
||||
default ``NotImplementedError`` propagate.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def get_model_version(self, model_id: int = None, version_id: int = None) -> Optional[Dict]:
|
||||
@@ -140,6 +152,11 @@ class CivitaiModelMetadataProvider(ModelMetadataProvider):
|
||||
self, model_ids: Sequence[int]
|
||||
) -> Optional[Dict[int, Dict]]:
|
||||
return await self.client.get_model_versions_bulk(model_ids)
|
||||
|
||||
async def get_model_versions_by_hashes(
|
||||
self, hashes: List[str]
|
||||
) -> Optional[List[Dict]]:
|
||||
return await self.client.get_model_versions_by_hashes(hashes)
|
||||
|
||||
async def get_model_version(self, model_id: int = None, version_id: int = None) -> Optional[Dict]:
|
||||
return await self.client.get_model_version(model_id, version_id)
|
||||
@@ -519,6 +536,32 @@ class FallbackMetadataProvider(ModelMetadataProvider):
|
||||
continue
|
||||
return None, "No provider could retrieve the data"
|
||||
|
||||
async def get_model_versions_by_hashes(
|
||||
self, hashes: List[str]
|
||||
) -> Optional[List[Dict]]:
|
||||
for provider, label in self._iter_providers():
|
||||
try:
|
||||
result = await self._call_with_rate_limit(
|
||||
label,
|
||||
provider.get_model_versions_by_hashes,
|
||||
hashes,
|
||||
)
|
||||
if result is not None:
|
||||
return result
|
||||
except NotImplementedError:
|
||||
continue
|
||||
except RateLimitError as exc:
|
||||
exc.provider = exc.provider or label
|
||||
raise exc
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
"Provider %s failed for get_model_versions_by_hashes: %s",
|
||||
label,
|
||||
e,
|
||||
)
|
||||
continue
|
||||
return None
|
||||
|
||||
async def get_user_models(self, username: str) -> Optional[List[Dict]]:
|
||||
for provider, label in self._iter_providers():
|
||||
try:
|
||||
@@ -593,6 +636,15 @@ class RateLimitRetryingProvider(ModelMetadataProvider):
|
||||
model_ids,
|
||||
)
|
||||
|
||||
async def get_model_versions_by_hashes(
|
||||
self, hashes: List[str]
|
||||
) -> Optional[List[Dict]]:
|
||||
return await self._rate_limit_helper.run(
|
||||
self._label,
|
||||
self._provider.get_model_versions_by_hashes,
|
||||
hashes,
|
||||
)
|
||||
|
||||
async def get_model_version(self, model_id: int = None, version_id: int = None) -> Optional[Dict]:
|
||||
return await self._rate_limit_helper.run(
|
||||
self._label,
|
||||
@@ -669,6 +721,17 @@ class ModelMetadataProviderManager:
|
||||
provider = self._get_provider(provider_name)
|
||||
return await provider.get_model_version_info(version_id)
|
||||
|
||||
async def get_model_versions_by_hashes(
|
||||
self,
|
||||
hashes: List[str],
|
||||
provider_name: str = None,
|
||||
) -> Optional[List[Dict]]:
|
||||
provider = self._get_provider(provider_name)
|
||||
try:
|
||||
return await provider.get_model_versions_by_hashes(hashes)
|
||||
except NotImplementedError:
|
||||
return None
|
||||
|
||||
async def get_user_models(self, username: str, provider_name: str = None) -> Optional[List[Dict]]:
|
||||
"""Fetch models owned by the specified user"""
|
||||
provider = self._get_provider(provider_name)
|
||||
|
||||
@@ -989,6 +989,11 @@ class ModelUpdateService:
|
||||
fallback_attempted = True
|
||||
try:
|
||||
response = await metadata_provider.get_model_versions(model_id)
|
||||
if response is not None:
|
||||
await self._enrich_version_entries(
|
||||
metadata_provider,
|
||||
{model_id: response},
|
||||
)
|
||||
except RateLimitError:
|
||||
raise
|
||||
except ResourceNotFoundError as exc:
|
||||
@@ -1083,6 +1088,136 @@ class ModelUpdateService:
|
||||
self._upsert_record(record)
|
||||
return record
|
||||
|
||||
async def _enrich_version_entries(
|
||||
self,
|
||||
metadata_provider,
|
||||
responses_by_model_id: Dict[int, Mapping],
|
||||
) -> None:
|
||||
"""Enrich version entries with ``usageControl`` via batch hash endpoint.
|
||||
|
||||
The model-level API does not include ``usageControl`` on version
|
||||
entries. This method collects SHA256 hashes from every version's
|
||||
primary model file, calls ``POST /api/v1/model-versions/by-hash``
|
||||
(up to 100 hashes per request), and injects ``usageControl`` +
|
||||
``earlyAccessEndsAt`` into each version entry dict in-place.
|
||||
"""
|
||||
if not metadata_provider or not responses_by_model_id:
|
||||
return
|
||||
|
||||
hashes_by_version: Dict[int, str] = {}
|
||||
for response in responses_by_model_id.values():
|
||||
hashes_by_version.update(
|
||||
self._collect_hashes_from_response(response)
|
||||
)
|
||||
|
||||
if not hashes_by_version:
|
||||
return
|
||||
|
||||
version_ids_by_hash: Dict[str, List[int]] = {}
|
||||
for version_id, sha256 in hashes_by_version.items():
|
||||
version_ids_by_hash.setdefault(sha256, []).append(version_id)
|
||||
|
||||
all_hashes = list(version_ids_by_hash.keys())
|
||||
BATCH_SIZE = 100
|
||||
|
||||
enrichment: Dict[int, Dict] = {}
|
||||
try:
|
||||
for start in range(0, len(all_hashes), BATCH_SIZE):
|
||||
batch = all_hashes[start : start + BATCH_SIZE]
|
||||
try:
|
||||
enriched = await metadata_provider.get_model_versions_by_hashes(
|
||||
batch
|
||||
)
|
||||
except NotImplementedError:
|
||||
return
|
||||
except RateLimitError:
|
||||
raise
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not enriched:
|
||||
continue
|
||||
|
||||
for entry in enriched:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
version_id = entry.get("id")
|
||||
if version_id is None:
|
||||
continue
|
||||
enrichment[version_id] = {
|
||||
"usageControl": _normalize_string(
|
||||
entry.get("usageControl")
|
||||
),
|
||||
"earlyAccessEndsAt": _normalize_string(
|
||||
entry.get("earlyAccessEndsAt")
|
||||
),
|
||||
}
|
||||
except RateLimitError:
|
||||
raise
|
||||
|
||||
if not enrichment:
|
||||
return
|
||||
|
||||
for response in responses_by_model_id.values():
|
||||
versions = response.get("modelVersions")
|
||||
if not isinstance(versions, list):
|
||||
continue
|
||||
for version in versions:
|
||||
if not isinstance(version, dict):
|
||||
continue
|
||||
version_id = version.get("id")
|
||||
if version_id not in enrichment:
|
||||
continue
|
||||
extra = enrichment[version_id]
|
||||
if extra.get("usageControl") and not version.get("usageControl"):
|
||||
version["usageControl"] = extra["usageControl"]
|
||||
if extra.get("earlyAccessEndsAt") and not version.get(
|
||||
"earlyAccessEndsAt"
|
||||
):
|
||||
version["earlyAccessEndsAt"] = extra["earlyAccessEndsAt"]
|
||||
|
||||
@staticmethod
|
||||
def _collect_hashes_from_response(response: Mapping) -> Dict[int, str]:
|
||||
"""Extract ``{version_id: sha256}`` from a model-level API response.
|
||||
|
||||
Returns an empty dict if the response structure is unexpected.
|
||||
"""
|
||||
result: Dict[int, str] = {}
|
||||
versions = response.get("modelVersions")
|
||||
if not isinstance(versions, list):
|
||||
return result
|
||||
for entry in versions:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
version_id = _normalize_int(entry.get("id"))
|
||||
if version_id is None:
|
||||
continue
|
||||
sha256 = ModelUpdateService._extract_sha256_from_version_entry(entry)
|
||||
if sha256:
|
||||
result[version_id] = sha256
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _extract_sha256_from_version_entry(entry: Mapping) -> Optional[str]:
|
||||
"""Return the SHA256 hash from the primary model file of a version entry."""
|
||||
files = entry.get("files")
|
||||
if not isinstance(files, list):
|
||||
return None
|
||||
for file_info in files:
|
||||
if not isinstance(file_info, dict):
|
||||
continue
|
||||
if file_info.get("type") != "Model":
|
||||
continue
|
||||
primary = file_info.get("primary")
|
||||
if primary is not True and str(primary).strip().lower() != "true":
|
||||
continue
|
||||
hashes = file_info.get("hashes")
|
||||
if isinstance(hashes, dict):
|
||||
sha256 = hashes.get("SHA256")
|
||||
if sha256:
|
||||
return sha256
|
||||
return None
|
||||
|
||||
async def _fetch_model_versions_bulk(
|
||||
self,
|
||||
metadata_provider,
|
||||
@@ -1134,6 +1269,7 @@ class ModelUpdateService:
|
||||
len(aggregated),
|
||||
provider_name,
|
||||
)
|
||||
await self._enrich_version_entries(metadata_provider, aggregated)
|
||||
return aggregated
|
||||
|
||||
async def _collect_local_versions(
|
||||
@@ -1261,6 +1397,7 @@ class ModelUpdateService:
|
||||
sort_index=sort_map.get(version_id, index),
|
||||
early_access_ends_at=remote_version.early_access_ends_at,
|
||||
is_early_access=remote_version.is_early_access,
|
||||
usage_control=remote_version.usage_control,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -178,5 +178,8 @@ SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
|
||||
"Wan Video 2.5 I2V",
|
||||
"Hunyuan Video",
|
||||
"Anima",
|
||||
"Ernie",
|
||||
"Ernie Turbo",
|
||||
"Nucleus",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
.checkbox-label input[type="checkbox"]:checked + .checkmark::after {
|
||||
content: '\f00c';
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-family: 'Font Awesome 6 Free', sans-serif;
|
||||
font-weight: 900;
|
||||
color: var(--lora-text);
|
||||
font-size: 12px;
|
||||
|
||||
@@ -329,7 +329,6 @@
|
||||
}
|
||||
|
||||
.card-actions i {
|
||||
margin-left: var(--space-1);
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
transition: opacity 0.2s, transform 0.15s ease;
|
||||
|
||||
@@ -141,8 +141,7 @@
|
||||
|
||||
.header-search .search-container:focus-within {
|
||||
border-color: var(--lora-accent);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12), 0 0 0 1px var(--lora-accent);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--lora-accent);
|
||||
}
|
||||
|
||||
.header-search input {
|
||||
|
||||
@@ -387,6 +387,10 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.version-action-disabled-wrapper {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.versions-loading-state,
|
||||
.versions-empty,
|
||||
.versions-error {
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
|
||||
.early-access-info {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--card-bg);
|
||||
@@ -97,7 +96,6 @@
|
||||
|
||||
.local-path {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--card-bg);
|
||||
|
||||
@@ -371,6 +371,14 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Elevate the controls stacking context above breadcrumb nav when a dropdown is open,
|
||||
so the dropdown menu isn't obscured. Only active when dropdown is shown to avoid
|
||||
the entire controls bar (which can wrap to 2 rows on narrow viewports) covering
|
||||
the sticky breadcrumb. */
|
||||
.controls:has(.dropdown-group.active) {
|
||||
z-index: var(--z-header);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
padding: 6px 15px;
|
||||
|
||||
@@ -74,6 +74,34 @@ export class BulkContextMenu extends BaseContextMenu {
|
||||
if (setContentRatingItem) {
|
||||
setContentRatingItem.style.display = config.setContentRating ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
const setFavoriteItem = this.menu.querySelector('[data-action="set-favorite"]');
|
||||
|
||||
if (setFavoriteItem && config.setFavorite) {
|
||||
setFavoriteItem.style.display = 'flex';
|
||||
|
||||
const total = state.selectedModels.size;
|
||||
const favoritedCount = this.countFavoritedInSelection();
|
||||
const allFavorited = total > 0 && favoritedCount === total;
|
||||
|
||||
const icon = setFavoriteItem.querySelector('i');
|
||||
const label = setFavoriteItem.querySelector('span');
|
||||
|
||||
if (allFavorited) {
|
||||
if (icon) { icon.className = 'far fa-star'; }
|
||||
if (label) { label.textContent = translate('loras.bulkOperations.unfavorite'); }
|
||||
} else {
|
||||
if (icon) { icon.className = 'fas fa-star'; }
|
||||
if (label) {
|
||||
label.textContent = favoritedCount > 0
|
||||
? translate('loras.bulkOperations.setFavoriteCount', { favorited: favoritedCount, total })
|
||||
: translate('loras.bulkOperations.setFavorite');
|
||||
}
|
||||
}
|
||||
} else if (setFavoriteItem) {
|
||||
setFavoriteItem.style.display = 'none';
|
||||
}
|
||||
|
||||
if (downloadMissingLorasItem) {
|
||||
// Only show for recipes page
|
||||
downloadMissingLorasItem.style.display = currentModelType === 'recipes' ? 'flex' : 'none';
|
||||
@@ -138,6 +166,20 @@ export class BulkContextMenu extends BaseContextMenu {
|
||||
return count;
|
||||
}
|
||||
|
||||
countFavoritedInSelection() {
|
||||
let count = 0;
|
||||
for (const filePath of state.selectedModels) {
|
||||
const escapedPath = window.CSS && typeof window.CSS.escape === 'function'
|
||||
? window.CSS.escape(filePath)
|
||||
: filePath.replace(/["\\]/g, '\\$&');
|
||||
const card = document.querySelector(`.model-card[data-filepath="${escapedPath}"]`);
|
||||
if (card && card.dataset.favorite === 'true') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
showMenu(x, y, card) {
|
||||
this.updateMenuItemsForModelType();
|
||||
this.updateSelectedCountHeader();
|
||||
@@ -185,6 +227,11 @@ export class BulkContextMenu extends BaseContextMenu {
|
||||
case 'delete-all':
|
||||
bulkManager.showBulkDeleteModal();
|
||||
break;
|
||||
case 'set-favorite': {
|
||||
const allFavorited = this.countFavoritedInSelection() === state.selectedModels.size;
|
||||
bulkManager.setBulkFavorites(!allFavorited);
|
||||
break;
|
||||
}
|
||||
case 'download-missing-loras':
|
||||
this.handleDownloadMissingLoras();
|
||||
break;
|
||||
|
||||
@@ -241,7 +241,7 @@ function buildActionButton(label, variant, action, options = {}) {
|
||||
if (action) {
|
||||
attributes.push(`data-version-action="${escapeHtml(action)}"`);
|
||||
}
|
||||
if (options.title) {
|
||||
if (!options.disabled && options.title) {
|
||||
attributes.push(`title="${escapeHtml(options.title)}"`);
|
||||
attributes.push(`aria-label="${escapeHtml(options.title)}"`);
|
||||
}
|
||||
@@ -251,7 +251,11 @@ function buildActionButton(label, variant, action, options = {}) {
|
||||
if (options.extraAttributes) {
|
||||
attributes.push(options.extraAttributes);
|
||||
}
|
||||
return `<button ${attributes.join(' ')}>${options.iconMarkup || ''}${escapeHtml(label)}</button>`;
|
||||
const buttonHtml = `<button ${attributes.join(' ')}>${options.iconMarkup || ''}${escapeHtml(label)}</button>`;
|
||||
if (options.disabled && options.title) {
|
||||
return `<span class="version-action-disabled-wrapper" title="${escapeHtml(options.title)}" aria-label="${escapeHtml(options.title)}">${buttonHtml}</span>`;
|
||||
}
|
||||
return buttonHtml;
|
||||
}
|
||||
|
||||
const DISPLAY_FILTER_MODES = Object.freeze({
|
||||
|
||||
@@ -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 } from '../api/recipeApi.js';
|
||||
import { RecipeSidebarApiClient, updateRecipeMetadata } 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';
|
||||
@@ -41,7 +41,9 @@ export class BulkManager {
|
||||
autoOrganize: true,
|
||||
deleteAll: true,
|
||||
setContentRating: true,
|
||||
skipMetadataRefresh: true
|
||||
skipMetadataRefresh: true,
|
||||
setFavorite: true,
|
||||
unfavorite: true
|
||||
},
|
||||
[MODEL_TYPES.EMBEDDING]: {
|
||||
addTags: true,
|
||||
@@ -53,7 +55,9 @@ export class BulkManager {
|
||||
autoOrganize: true,
|
||||
deleteAll: true,
|
||||
setContentRating: false,
|
||||
skipMetadataRefresh: true
|
||||
skipMetadataRefresh: true,
|
||||
setFavorite: true,
|
||||
unfavorite: true
|
||||
},
|
||||
[MODEL_TYPES.CHECKPOINT]: {
|
||||
addTags: true,
|
||||
@@ -65,7 +69,9 @@ export class BulkManager {
|
||||
autoOrganize: true,
|
||||
deleteAll: true,
|
||||
setContentRating: true,
|
||||
skipMetadataRefresh: true
|
||||
skipMetadataRefresh: true,
|
||||
setFavorite: true,
|
||||
unfavorite: true
|
||||
},
|
||||
recipes: {
|
||||
addTags: false,
|
||||
@@ -77,7 +83,9 @@ export class BulkManager {
|
||||
autoOrganize: false,
|
||||
deleteAll: true,
|
||||
setContentRating: false,
|
||||
skipMetadataRefresh: false
|
||||
skipMetadataRefresh: false,
|
||||
setFavorite: true,
|
||||
unfavorite: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1090,6 +1098,60 @@ export class BulkManager {
|
||||
}
|
||||
}
|
||||
|
||||
async setBulkFavorites(value) {
|
||||
if (state.selectedModels.size === 0) {
|
||||
showToast('toast.models.noModelsSelected', {}, 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const totalCount = state.selectedModels.size;
|
||||
const isRecipesPage = state.currentPageType === 'recipes';
|
||||
|
||||
state.loadingManager.showSimpleLoading(
|
||||
translate(value ? 'toast.models.bulkFavoriteUpdating' : 'toast.models.bulkUnfavoriteUpdating', { count: totalCount })
|
||||
);
|
||||
let cancelled = false;
|
||||
state.loadingManager.showCancelButton(() => {
|
||||
cancelled = true;
|
||||
});
|
||||
|
||||
let successCount = 0;
|
||||
let failureCount = 0;
|
||||
|
||||
try {
|
||||
for (const filePath of state.selectedModels) {
|
||||
if (cancelled) {
|
||||
showToast('toast.api.operationCancelled', {}, 'info');
|
||||
break;
|
||||
}
|
||||
try {
|
||||
if (isRecipesPage) {
|
||||
await updateRecipeMetadata(filePath, { favorite: value });
|
||||
} else {
|
||||
const apiClient = getModelApiClient();
|
||||
await apiClient.saveModelMetadata(filePath, { favorite: value });
|
||||
}
|
||||
successCount++;
|
||||
} catch (error) {
|
||||
failureCount++;
|
||||
console.error(`Failed to set favorite=${value} for ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
state.loadingManager?.hide?.();
|
||||
}
|
||||
|
||||
if (successCount === totalCount) {
|
||||
const toastKey = value ? 'modelCard.favorites.added' : 'modelCard.favorites.removed';
|
||||
showToast(toastKey, {}, 'success');
|
||||
} else if (successCount > 0) {
|
||||
const toastKey = value ? 'toast.models.bulkFavoritePartialAdded' : 'toast.models.bulkFavoritePartialRemoved';
|
||||
showToast(toastKey, { success: successCount, failed: failureCount }, 'warning');
|
||||
} else {
|
||||
showToast('toast.models.bulkFavoriteFailed', {}, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show bulk base model modal
|
||||
*/
|
||||
|
||||
@@ -599,7 +599,7 @@ export class FilterManager {
|
||||
|
||||
// Call the appropriate manager's load method based on page type
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
await window.recipeManager.loadRecipes(true);
|
||||
await window.recipeManager.loadRecipes({ preserveScroll: true });
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
||||
// For models page, reset the page and reload
|
||||
await getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
||||
@@ -682,7 +682,7 @@ export class FilterManager {
|
||||
|
||||
// Reload data using the appropriate method for the current page
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
await window.recipeManager.loadRecipes(true);
|
||||
await window.recipeManager.loadRecipes({ preserveScroll: true });
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings') {
|
||||
await getModelApiClient().loadMoreWithVirtualScroll(true, true);
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ export class SearchManager {
|
||||
|
||||
// Call the appropriate manager's load method based on page type
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
window.recipeManager.loadRecipes(true); // true to reset pagination
|
||||
window.recipeManager.loadRecipes({ preserveScroll: true });
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
||||
// For models page, reset the page and reload
|
||||
getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
||||
|
||||
@@ -2863,7 +2863,7 @@ export class SettingsManager {
|
||||
await resetAndReload(false);
|
||||
} else if (this.currentPage === 'recipes') {
|
||||
// Reload the recipes without updating folders
|
||||
await window.recipeManager.loadRecipes();
|
||||
await window.recipeManager.loadRecipes({ preserveScroll: true });
|
||||
} else if (this.currentPage === 'checkpoints') {
|
||||
// Reload the checkpoints without updating folders
|
||||
await resetAndReload(false);
|
||||
|
||||
@@ -19,7 +19,7 @@ class RecipePageControls {
|
||||
}
|
||||
|
||||
async resetAndReload() {
|
||||
refreshVirtualScroll();
|
||||
await refreshVirtualScroll({ preserveScroll: true });
|
||||
}
|
||||
|
||||
async refreshModels(fullRebuild = false) {
|
||||
|
||||
@@ -66,6 +66,9 @@ export const BASE_MODELS = {
|
||||
HUNYUAN_VIDEO: "Hunyuan Video",
|
||||
// Other models
|
||||
ANIMA: "Anima",
|
||||
ERNIE: "Ernie",
|
||||
ERNIE_TURBO: "Ernie Turbo",
|
||||
NUCLEUS: "Nucleus",
|
||||
PONY_V7: "Pony V7",
|
||||
// Default
|
||||
UNKNOWN: "Other"
|
||||
@@ -191,6 +194,9 @@ export const BASE_MODEL_ABBREVIATIONS = {
|
||||
[BASE_MODELS.ZIMAGE_TURBO]: 'ZIT',
|
||||
[BASE_MODELS.ZIMAGE_BASE]: 'ZIB',
|
||||
[BASE_MODELS.ANIMA]: 'ANI',
|
||||
[BASE_MODELS.ERNIE]: 'ERNI',
|
||||
[BASE_MODELS.ERNIE_TURBO]: 'ETRB',
|
||||
[BASE_MODELS.NUCLEUS]: 'NUCL',
|
||||
|
||||
// Default
|
||||
[BASE_MODELS.UNKNOWN]: 'OTH'
|
||||
@@ -394,6 +400,7 @@ export const BASE_MODEL_CATEGORIES = {
|
||||
BASE_MODELS.QWEN, BASE_MODELS.AURAFLOW, BASE_MODELS.CHROMA, BASE_MODELS.ZIMAGE_TURBO, BASE_MODELS.ZIMAGE_BASE,
|
||||
BASE_MODELS.PIXART_A, BASE_MODELS.PIXART_E, BASE_MODELS.HUNYUAN_1,
|
||||
BASE_MODELS.LUMINA, BASE_MODELS.KOLORS, BASE_MODELS.NOOBAI, BASE_MODELS.ANIMA,
|
||||
BASE_MODELS.ERNIE, BASE_MODELS.ERNIE_TURBO, BASE_MODELS.NUCLEUS,
|
||||
BASE_MODELS.UNKNOWN
|
||||
]
|
||||
};
|
||||
|
||||
@@ -77,6 +77,9 @@
|
||||
<div class="context-menu-item" data-action="set-base-model">
|
||||
<i class="fas fa-layer-group"></i> <span>{{ t('loras.bulkOperations.setBaseModel') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="set-favorite">
|
||||
<i class="fas fa-star"></i> <span>{{ t('loras.bulkOperations.setFavorite') }}</span>
|
||||
</div>
|
||||
<div class="context-menu-item" data-action="set-content-rating">
|
||||
<i class="fas fa-exclamation-triangle"></i> <span>{{ t('loras.bulkOperations.setContentRating') }}</span>
|
||||
</div>
|
||||
|
||||
@@ -114,7 +114,8 @@ describe('LoRA widget drag interactions', () => {
|
||||
dragEl.dispatchEvent(new PointerEvent('pointerup', { pointerId: 1 }));
|
||||
expect(document.body.classList.contains('lm-lora-strength-dragging')).toBe(false);
|
||||
expect(onDragEnd).toHaveBeenCalledTimes(1);
|
||||
expect(renderSpy).toHaveBeenCalledWith(widget.value, widget);
|
||||
// 454210a4 replaced renderFunction() with widget.value setter + widget.callback()
|
||||
expect(widget.callback).toHaveBeenCalledWith(widget.value);
|
||||
});
|
||||
|
||||
it('deletes the selected LoRA when backspace is pressed outside of strength inputs', async () => {
|
||||
|
||||
@@ -232,9 +232,13 @@ export function initDrag(
|
||||
onDragEnd();
|
||||
}
|
||||
|
||||
// Now do the re-render after drag is complete
|
||||
if (renderFunction) {
|
||||
renderFunction(widget.value, widget);
|
||||
// Commit final value through options.setValue so external observers are notified.
|
||||
// During drag, handleStrengthDrag mutates widgetValue in-place (updateWidget=false),
|
||||
// bypassing widget.value setter and options.setValue entirely. This assignment
|
||||
// flushes the in-place mutation through the setter so any setValue wrappers fire.
|
||||
widget.value = widget.value;
|
||||
if (typeof widget.callback === 'function') {
|
||||
widget.callback(widget.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -349,11 +353,15 @@ export function initHeaderDrag(headerEl, widget, renderFunction) {
|
||||
document.body.classList.remove('lm-lora-strength-dragging');
|
||||
|
||||
// Only re-render if we actually dragged
|
||||
if (wasDragging && renderFunction) {
|
||||
renderFunction(widget.value, widget);
|
||||
if (wasDragging) {
|
||||
// Commit final value through options.setValue so external observers are notified.
|
||||
widget.value = widget.value;
|
||||
if (typeof widget.callback === 'function') {
|
||||
widget.callback(widget.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Handle pointer up to end dragging
|
||||
headerEl.addEventListener('pointerup', endDrag);
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 597 KiB |
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 872 KiB |
|
Before Width: | Height: | Size: 362 KiB |
|
Before Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 639 KiB |
|
Before Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 529 KiB |