File size: 16,500 Bytes
c441f1b f03bed6 c441f1b f03bed6 5700340 f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b f03bed6 c441f1b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
import gradio as gr
from gradio_modal import Modal
from civitai_api import (search_civitai, download_civitai, select_civitai_item, add_civitai_item, get_civitai_tag, select_civitai_all_item,
update_civitai_selection, update_civitai_checkbox, from_civitai_checkbox)
from civitai_constants import (TYPE, BASEMODEL, SORT, PERIOD, FILETYPE)
import time
css = """
.title { padding: 30px 00px; font-size: 3em; align-items: center; text-align: center; }
.info { align-items: center; text-align: center; }
.desc [src$='#float'] { float: right; margin: 20px; }
#modal-window{ position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); max-height: inherit; }
.modal-container{ max-width:60vw; height:100vh; }
.prose.desc{ padding: 0px 20px; }
/* Video hover styles */
.media-container {
position: relative;
width: 100%;
height: 100%;
}
.result{ padding: 16px; }
.media-container img,
.media-container video {
width: 100%;
height: 100%;
object-fit: contain;
}
.media-container video {
display: none;
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
.gallery-item:hover .media-container video {
display: block;
}
.gallery-item:hover .media-container img {
opacity: 0.3;
}
.gallery-item {
position: relative;
cursor: pointer;
overflow: hidden;
}
"""
# js = """
# (() => {
# function setupGalleryHover() {
# const gallery = document.getElementById('gallery');
# if (!gallery) return;
# function handleMediaContainer(container) {
# const video = container.querySelector('video');
# if (!video) return;
# video.muted = true;
# video.preload = "metadata";
# container.addEventListener('mouseenter', () => {
# if (video.paused) {
# video.play().catch(() => {});
# }
# });
# container.addEventListener('mouseleave', () => {
# if (!video.paused) {
# video.pause();
# video.currentTime = 0;
# }
# });
# }
# function setupContainers() {
# const containers = gallery.querySelectorAll('.media-container');
# containers.forEach(handleMediaContainer);
# }
# setupContainers();
# const observer = new MutationObserver(setupContainers);
# observer.observe(gallery, { childList: true, subtree: true });
# }
# if (document.readyState === 'loading') {
# document.addEventListener('DOMContentLoaded', setupGalleryHover);
# } else {
# setupGalleryHover();
# }
# })();
# """
common_sense = """
## The Common Sense Agreement
This tool was created in response to recent censorship and model deactivations on Civitai. This Space should be treated as a tool for artists and creators to easily backup and archive models -- this is NOT a tool to blindly scrape and download every asset your heart desires. For those that wish to do so, may do so on their own local machines. Users of the tool are responsible for their own actions - you have been warned.
By continuing on to the tool, you are assumed to have **acknowledged, understood, and are in agreeance** with the following common sense keypoints:
1. **Storage is not free.** You are responsible for understanding your storage limits and the costs that will be incurred if you exceed your tier's free allocation.
2. **Bandwidth is not free.** Misuse of the tool will likely be frowned upon by both Huggingface as well as Civitai, potentially leading to negative consequences for all. Don't be that guy. There are better ways if you must do so.
3. **Not all models are created equally.** Respect the licensing agreements and the wishes of creators, both big and small.
4. **Respect the FOSS community**, give back where you can, and be aware of your surroundings. Protect this industry as best you can, otherwise prepare for a wicked spanking when AGI is here.
### For those with common sense, please continue.
"""
header_md = """
# 🚨 Civitai Models & Workflow Conservation / Archiving Tool
### WIP: Expect changes and adjustments. Planned: S3 buckets, B2 storage, IPFS, magnet tools for torrents.
---
* Provided by [AI Without Borders](https://huggingface.co/aiwithoutborders-xyz). Run locally or on Huggingface Spaces.
* This tool is ideal for creators with many LORAs. One-click backup your entire portfolio.
* Keys are always stored locally on your browser. PRs welcome and appreciated.
* Usage: easy as 1️⃣->2️⃣->3️⃣, just follow the step numbers.
"""
with gr.Blocks(fill_width=True, css=css, delete_cache=(3600, 3600)) as demo:
gr.Markdown(header_md, elem_classes="title")
state = gr.State(value={})
# Create browser state for API keys with error handling
api_keys_state = gr.BrowserState(["", ""], storage_key="civitai2hf_api_keys")
with gr.Row():
show_btn = gr.Button("Show Modal", visible=False)
with gr.Tabs() as tabs:
with gr.TabItem("Search & Download"):
with gr.Row():
# Left column for filters (1/3 width)
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("## 2️⃣ Search Filters", container=True)
with gr.Accordion("Type & Model", open=True):
search_civitai_type = gr.CheckboxGroup(label="Type", choices=TYPE, value=["Checkpoint", "LORA"])
search_civitai_basemodel = gr.CheckboxGroup(label="Base Model", choices=BASEMODEL, value=[])
search_civitai_filetype = gr.CheckboxGroup(label="File type", visible=False, choices=FILETYPE, value=["Model"])
with gr.Accordion("Search Options", open=True):
search_civitai_sort = gr.Radio(label="Sort", choices=SORT, value=SORT[0])
search_civitai_period = gr.Radio(label="Period", choices=PERIOD, value="Month")
search_civitai_limit = gr.Slider(0, 100, value=50, step=10, label="Limit", info="Maximum items to query per page via API.")
search_civitai_page = gr.Slider(0, 10, 1, step=1, label="Num Pages", info="If 0, retrieve all pages")
# Right column for results and download (2/3 width)
with gr.Column(scale=2):
with gr.Group():
gr.Markdown("## 3️⃣ Search Query", container=True)
with gr.Row():
search_civitai_query = gr.Textbox(label="Query", placeholder="wan", lines=1)
search_civitai_tag = gr.Dropdown(label="Tag", choices=get_civitai_tag(), value=get_civitai_tag()[0], allow_custom_value=True)
search_civitai_user = gr.Textbox(label="Username", lines=1)
search_civitai_submit = gr.Button("Search Civitai API", variant="primary")
with gr.Group():
gr.Markdown("## 4️⃣ Select Models to Backup from Search Results", container=True)
with gr.Row():
search_civitai_desc = gr.Markdown(value="", visible=False, elem_classes="desc")
search_civitai_json = gr.JSON(value={}, visible=False)
with gr.Row(equal_height=True):
with gr.Column(scale=9):
with gr.Accordion("👇 Gallery View ⚠️ Surprise NSFW + RAM Warning ⚠️", open=False):
search_civitai_gallery = gr.Gallery(
[],
label="Select from Results",
allow_preview=False,
columns=5,
elem_id="gallery",
object_fit="contain",
show_share_button=False,
interactive=True,
preview=False,
height="auto"
)
with gr.Accordion("👇 List View", open=False):
search_civitai_result_checkbox = gr.CheckboxGroup(label="", choices=[], value=[])
search_civitai_result = gr.Dropdown(label="Selected Models from Search", choices=[("", "")], value=[],
allow_custom_value=True, visible=True, multiselect=True)
search_civitai_result_info = gr.Markdown("Standing by.", elem_classes="info")
with gr.Row():
search_civitai_add = gr.Button("5️⃣ Add Selected Models to DL List", variant="primary")
search_civitai_select_all = gr.Button("Select All", variant="secondary")
with gr.Group():
gr.Markdown("## 6️⃣🚨 Ensure you have set your correct API key settings before moving on.", container=True)
dl_url = gr.Textbox(label="Download URL(s)", placeholder="https://civitai.com/api/download/models/28907\n...", value="", lines=3, max_lines=255, interactive=True)
with gr.Row():
newrepo_id = gr.Textbox(label="Your Repo ID", placeholder="yourid/yourrepo", value="", max_lines=1)
newrepo_type = gr.Radio(label="Repo Type", choices=["model", "dataset"], value="model")
with gr.Group():
is_private = gr.Checkbox(label="Private Repo", value=False)
is_rename = gr.Checkbox(label="Auto rename", value=True)
with gr.Row():
is_info = gr.Checkbox(label="Upload Civitai Metadata Files to HF", value=True)
run_button = gr.Button(value="7️⃣ Create Repo & Backup Models", variant="primary")
uploaded_urls = gr.CheckboxGroup(visible=False, choices=[], value=[]) # hidden
urls_md = gr.Markdown("<br><br>", elem_classes="result")
urls_remain = gr.Textbox("Remaining URLs", value="", show_copy_button=True, visible=False)
with gr.TabItem("Settings 1️⃣"):
with gr.Column():
with gr.Group():
with gr.Row():
with gr.Column():
civitai_key = gr.Textbox(label="Your Civitai Key", value="", max_lines=1)
gr.Markdown("Your Civitai API key is available at [https://civitai.com/user/account](https://civitai.com/user/account).", elem_classes="info")
with gr.Column():
hf_token = gr.Textbox(label="Your HF write token", placeholder="hf_...", value="", max_lines=1)
gr.Markdown("Your token is available at [hf.co/settings/tokens](https://huggingface.co/settings/tokens).", elem_classes="info")
# Add save status message
saved_message = gr.Markdown("✅ API keys saved to browser storage", visible=False)
# Load saved keys when the app starts with error handling
@demo.load(inputs=[api_keys_state], outputs=[civitai_key, hf_token])
def load_api_keys(saved_keys):
try:
if not saved_keys or not isinstance(saved_keys, list) or len(saved_keys) != 2:
return "", ""
# Ensure we're not trying to use None values
civitai = saved_keys[0] or ""
hf = saved_keys[1] or ""
return civitai, hf
except Exception as e:
print(f"Error loading API keys: {e}")
return "", ""
# Save keys when they change with improved error handling
@gr.on([civitai_key.change, hf_token.change], inputs=[civitai_key, hf_token], outputs=[api_keys_state, saved_message])
def save_api_keys(civitai_key, hf_token):
try:
# Ensure we're storing strings, not None
civitai = str(civitai_key or "")
hf = str(hf_token or "")
timestamp = time.strftime("%I:%M:%S %p")
if civitai or hf:
return [civitai, hf], gr.Markdown(
f"✅ API keys saved to browser storage at {timestamp}",
visible=True
)
return ["", ""], gr.Markdown("", visible=False)
except Exception as e:
print(f"Error saving API keys: {e}")
return ["", ""], gr.Markdown("Error saving API keys", visible=True)
with gr.TabItem("Known Issues"):
with gr.Column():
gr.Markdown("* Local browser key storage logic sometimes fails.\n* JSON Metadata for models fail to import.\n* WAN models seem to have a mind of its own. For the time being, it's recommended when searching for WAN models to UNCHECK `Wan Video` from filter and either use model tags or query directly.")
with Modal(visible=True, elem_id="modal-window") as modal:
gr.Markdown(common_sense)
gr.on(
triggers=[run_button.click],
fn=download_civitai,
inputs=[dl_url, civitai_key, hf_token, uploaded_urls, newrepo_id, newrepo_type, is_private, is_info, is_rename],
outputs=[uploaded_urls, urls_md, urls_remain],
queue=True,
)
gr.on(
triggers=[search_civitai_submit.click, search_civitai_query.submit, search_civitai_user.submit],
fn=search_civitai,
inputs=[search_civitai_query, search_civitai_type, search_civitai_basemodel, search_civitai_sort,
search_civitai_period, search_civitai_tag, search_civitai_user, search_civitai_limit,
search_civitai_page, search_civitai_filetype, civitai_key, search_civitai_gallery, state],
outputs=[search_civitai_result, search_civitai_desc, search_civitai_submit, search_civitai_query, search_civitai_gallery,
search_civitai_result_checkbox, search_civitai_result_info, state],
queue=False,
show_api=False,
)
search_civitai_result.change(select_civitai_item, [search_civitai_result, state], [search_civitai_desc, search_civitai_json, state], queue=False, show_api=False)\
.success(update_civitai_checkbox, [search_civitai_result], [search_civitai_result_checkbox], queue=True, show_api=False)
search_civitai_result_checkbox.select(from_civitai_checkbox, [search_civitai_result_checkbox], [search_civitai_result], queue=False, show_api=False)
search_civitai_add.click(add_civitai_item, [search_civitai_result, dl_url], [dl_url], queue=False, show_api=False)
search_civitai_select_all.click(select_civitai_all_item, [search_civitai_select_all, state], [search_civitai_select_all, search_civitai_result], queue=False, show_api=False)
search_civitai_gallery.select(update_civitai_selection, [search_civitai_result, state], [search_civitai_result], queue=False, show_api=False)
show_btn.click(lambda: Modal(visible=True), None, modal)
demo.queue()
demo.launch(ssr_mode=False)
|