Spaces:
Running
Running
Download app.js from Hamster4Doom/Browser: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/spaces/Hamster4Doom/Browser/resolve/main/app.js
- Command line
-
hf download hf://spaces/Hamster4Doom/Browser/app.js
-
curl -L -o app.js https://huggingface.co/spaces/Hamster4Doom/Browser/resolve/main/app.js
11.5 kB
| const CONFIG={owner:"Hamster4Doom",repo:"Krea2",branch:"main"}; | |
| const API=`https://huggingface.co/api/models/${CONFIG.owner}/${CONFIG.repo}/tree/${CONFIG.branch}`; | |
| const REPO=`https://huggingface.co/${CONFIG.owner}/${CONFIG.repo}`; | |
| const IMAGE_EXT=/\.(png|jpe?g|webp|avif)$/i; | |
| const MODEL_EXT=/\.safetensors$/i; | |
| const state={items:[],filtered:[]}; | |
| const $=id=>document.getElementById(id); | |
| const els={grid:$("grid"),empty:$("emptyState"),emptyMessage:$("emptyMessage"),notice:$("notice"),search:$("searchInput"),sort:$("sortSelect"),results:$("resultLabel"),updated:$("updatedLabel"),count:$("totalCount"),size:$("totalSize"),downloads:$("downloadCount"),refresh:$("refreshButton"),theme:$("themeButton"),dialog:$("detailDialog"),dialogContent:$("dialogContent"),close:$("closeDialog")}; | |
| function prettyName(path){return path.split("/").pop().replace(/\.safetensors$/i,"").replace(/^(krea[-_ ]?2[-_ ]?)/i,"").replace(/[-_ ]?krea[-_ ]?2$/i,"").replace(/[_-]+/g," ").replace(/\b\w/g,c=>c.toUpperCase()).trim()||"Untitled LoRA"} | |
| function base(path){return path.replace(/\.[^.]+$/,"").toLowerCase().replace(/(?:[-_](?:preview|cover|thumb|thumbnail|sample(?:[-_]?\d+)?))$/i,"")} | |
| function formatBytes(bytes=0){if(!bytes)return "—";const units=["B","KB","MB","GB"];const i=Math.min(Math.floor(Math.log(bytes)/Math.log(1024)),3);return `${(bytes/1024**i).toFixed(i>1?2:0)} ${units[i]}`} | |
| function esc(value=""){return String(value).replace(/[&<>'"]/g,c=>({"&":"&","<":"<",">":">","'":"'",'"':"""}[c]))} | |
| function fileUrl(path,download=false){return `${REPO}/${download?"resolve":"blob"}/${CONFIG.branch}/${path}${download?"?download=true":""}`} | |
| function showSkeleton(){els.grid.innerHTML=Array.from({length:8},()=>'<div class="skeleton"></div>').join("")} | |
| async function loadDownloadCount(){try{const response=await fetch(`https://huggingface.co/api/models/${CONFIG.owner}/${CONFIG.repo}`);if(!response.ok)throw new Error("stats");const data=await response.json();els.downloads.textContent=Number(data.downloads||0).toLocaleString()}catch{els.downloads.textContent="—"}} | |
| async function listAllFiles(){let files=[],cursor="";do{const url=new URL(API);url.searchParams.set("recursive","true");url.searchParams.set("expand","true");url.searchParams.set("limit","50");if(cursor)url.searchParams.set("cursor",cursor);const response=await fetch(url,{headers:{Accept:"application/json"}});if(!response.ok)throw new Error(response.status===401||response.status===403?"private":"http");const batch=await response.json();files.push(...batch);const link=response.headers.get("link")||"";const match=link.match(/[?&]cursor=([^&>]+)>;\s*rel="next"/);cursor=match?decodeURIComponent(match[1]):""}while(cursor);return files.filter(f=>f.type==="file")} | |
| function buildItems(files){const images=files.filter(f=>IMAGE_EXT.test(f.path));const metadata=files.filter(f=>/\.json$/i.test(f.path));return files.filter(f=>MODEL_EXT.test(f.path)).map(file=>{const stem=base(file.path);const filenameStem=stem.split("/").pop();const image=images.find(img=>base(img.path)===stem&&/[-_]preview\./i.test(img.path))||images.find(img=>base(img.path)===stem)||null;const meta=metadata.find(item=>base(item.path)===stem)||null;return{path:file.path,name:prettyName(file.path),size:file.size||0,modified:file.lastCommit?.date||file.lastModified||null,image:image?.path||null,metaPath:meta?.path||null,samples:[]}})} | |
| async function addMetadata(items){await Promise.all(items.map(async item=>{if(!item.metaPath)return;try{const response=await fetch(fileUrl(item.metaPath,true));if(!response.ok)return;const data=await response.json();const folder=item.metaPath.includes("/")?item.metaPath.slice(0,item.metaPath.lastIndexOf("/")+1):"";const resolve=name=>name?(name.includes("/")?name:`${folder}${name}`):null;item.name=data.name||item.name;item.baseModel=data.base_model||"Krea 2";item.triggers=Array.isArray(data.triggers)?data.triggers:[];item.strength=data.recommended_strength||"";item.steps=data.training_steps||"";item.description=data.description||"";item.image=resolve(data.preview)||item.image;item.samples=(Array.isArray(data.samples)?data.samples:[]).map(resolve).filter(Boolean)}catch{}}));return items} | |
| function applyFilters(){const query=els.search.value.trim().toLowerCase();state.filtered=state.items.filter(item=>!query||`${item.name} ${item.path} ${(item.triggers||[]).join(" ")}`.toLowerCase().includes(query));const mode=els.sort.value;state.filtered.sort((a,b)=>mode==="largest"?b.size-a.size:mode==="newest"?new Date(b.modified||0)-new Date(a.modified||0):a.name.localeCompare(b.name));render()} | |
| function render(){els.grid.innerHTML="";els.results.textContent=`${state.filtered.length} of ${state.items.length} LoRA${state.items.length===1?"":"s"}`;els.empty.classList.toggle("hidden",state.filtered.length>0);if(!state.filtered.length){els.emptyMessage.textContent=state.items.length?"No LoRAs match that search.":"Upload .safetensors files to the Krea2 repository and they will appear here automatically.";return}const fragment=document.createDocumentFragment();state.filtered.forEach((item,index)=>{const article=document.createElement("article");article.className="card";article.tabIndex=0;article.setAttribute("role","button");article.setAttribute("aria-label",`View ${item.name}`);article.innerHTML=`<div class="card-image">${item.image?`<img src="${esc(fileUrl(item.image,true))}" alt="${esc(item.name)} preview" loading="lazy">`:`<div class="placeholder"><span>AR</span></div>`}<span class="format-badge">KREA 2</span></div><div class="card-body"><h2 class="card-title" title="${esc(item.name)}">${esc(item.name)}</h2><div class="card-meta"><span>.safetensors</span><span>${formatBytes(item.size)}</span></div></div><span class="card-arrow">↗</span>`;article.addEventListener("click",()=>openDetail(index));article.addEventListener("keydown",e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();openDetail(index)}});fragment.append(article)});els.grid.append(fragment)} | |
| function openLightbox(item,gallery,startIndex=0){let current=startIndex;const overlay=document.createElement("div");overlay.className="gallery-lightbox";overlay.setAttribute("role","dialog");overlay.setAttribute("aria-modal","true");overlay.setAttribute("aria-label",`${item.name} image gallery`); | |
| const draw=()=>{const path=gallery[current];overlay.innerHTML=`<button class="lightbox-close" type="button" aria-label="Close gallery">×</button><button class="lightbox-nav lightbox-prev" type="button" aria-label="Previous image">‹</button><div class="lightbox-stage"><img src="${esc(fileUrl(path,true))}" alt="${esc(item.name)} ${current===0?"preview":`sample ${current}`}"><div class="lightbox-counter">${current+1} / ${gallery.length}</div></div><button class="lightbox-nav lightbox-next" type="button" aria-label="Next image">›</button>`;overlay.querySelector(".lightbox-close").addEventListener("click",close);overlay.querySelector(".lightbox-prev").addEventListener("click",()=>move(-1));overlay.querySelector(".lightbox-next").addEventListener("click",()=>move(1))}; | |
| const move=delta=>{current=(current+delta+gallery.length)%gallery.length;draw()};const onKey=e=>{if(e.key==="Escape")close();if(e.key==="ArrowLeft")move(-1);if(e.key==="ArrowRight")move(1)};const close=()=>{document.removeEventListener("keydown",onKey);overlay.remove()};overlay.addEventListener("click",e=>{if(e.target===overlay)close()});document.addEventListener("keydown",onKey);els.dialog.appendChild(overlay);draw()} | |
| function openDetail(index){ | |
| const item = state.filtered[index]; | |
| const date = item.modified | |
| ? new Date(item.modified).toLocaleDateString(undefined, {year:"numeric", month:"short", day:"numeric"}) | |
| : "—"; | |
| const gallery = [item.image, ...(item.samples||[])].filter(Boolean); | |
| const media = gallery.length | |
| ? `<div class="detail-gallery">${gallery.map((path,i)=>`<button class="gallery-image-button ${i===0?"gallery-hero":""}" type="button" data-gallery-index="${i}" aria-label="Open ${esc(item.name)} ${i?"sample "+i:"preview"} full size"><img src="${esc(fileUrl(path,true))}" alt="${esc(item.name)} ${i?`sample ${i}`:"preview"}" loading="lazy"></button>`).join("")}</div>` | |
| : `<div class="placeholder"><span>AR</span></div>`; | |
| const description = (item.description || "").trim() || "—"; | |
| els.dialogContent.innerHTML = `<div class="dialog-layout"><div class="dialog-media">${media}</div><div class="dialog-info"> | |
| <div class="eyebrow"><span></span>${esc(item.baseModel||"Krea 2")} CHARACTER LORA</div> | |
| <h2>${esc(item.name)}</h2> | |
| <div class="filename">${esc(item.path)}</div> | |
| <div class="detail-list"> | |
| <div><span>Recommended strength</span><strong>${esc(item.strength||"—")}</strong></div> | |
| <div><span>File size</span><strong>${formatBytes(item.size)}</strong></div> | |
| <div><span>Updated</span><strong>${date}</strong></div> | |
| </div> | |
| <div class="detail-description"> | |
| <span>Description</span> | |
| <p>${esc((item.description||"").trim() || "—")}</p> | |
| </div> | |
| <div class="detail-actions"> | |
| <a class="primary-action" href="${esc(fileUrl(item.path,true))}">↓ Download LoRA</a> | |
| <a class="secondary-action" href="${esc(fileUrl(item.path))}" target="_blank" rel="noopener">View on Hugging Face ↗</a> | |
| <button class="secondary-action" id="copyFilename" type="button">Copy filename</button> | |
| </div> | |
| </div></div>`; | |
| els.dialog.showModal(); | |
| els.dialogContent.querySelectorAll("[data-gallery-index]").forEach(button => | |
| button.addEventListener("click", () => openLightbox(item, gallery, Number(button.dataset.galleryIndex))) | |
| ); | |
| $("copyFilename").addEventListener("click", async e => { | |
| await navigator.clipboard.writeText(item.path.split("/").pop()); | |
| e.currentTarget.textContent = "Copied ✓"; | |
| }); | |
| } | |
| async function load(){showSkeleton();els.notice.classList.add("hidden");els.refresh.disabled=true;try{const files=await listAllFiles();state.items=await addMetadata(buildItems(files));els.count.textContent=state.items.length;els.size.textContent=formatBytes(state.items.reduce((sum,item)=>sum+item.size,0));els.updated.textContent=`Checked ${new Date().toLocaleTimeString([],{hour:"numeric",minute:"2-digit"})}`;applyFilters()}catch(error){state.items=[];els.grid.innerHTML="";els.count.textContent="0";els.size.textContent="—";els.results.textContent="Collection unavailable";els.notice.classList.remove("hidden");els.notice.textContent=error.message==="private"?"The Krea2 model repository is private. Make it public so this public Static Space can read and download its LoRAs.":"The Hugging Face repository could not be reached. Try Refresh in a moment.";els.empty.classList.remove("hidden");els.emptyMessage.textContent="The catalog will populate automatically when the repository is accessible."}finally{els.refresh.disabled=false}} | |
| els.search.addEventListener("input",applyFilters);els.sort.addEventListener("change",applyFilters);els.refresh.addEventListener("click",()=>{loadDownloadCount();load()});els.close.addEventListener("click",()=>els.dialog.close());els.dialog.addEventListener("click",e=>{if(e.target===els.dialog)els.dialog.close()});document.addEventListener("keydown",e=>{if(e.key==="/"&&document.activeElement!==els.search){e.preventDefault();els.search.focus()}});els.theme.addEventListener("click",()=>{const next=document.documentElement.dataset.theme==="light"?"dark":"light";document.documentElement.dataset.theme=next;localStorage.setItem("theme",next)});document.documentElement.dataset.theme=localStorage.getItem("theme")||"dark";loadDownloadCount();load(); | |