lora-library / index.html
Zero-000's picture
Update gallery
b802bff verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Library — Zero-000</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#1a1a1a;color:#e8e8e8;font-family:Consolas,'Courier New',monospace;min-height:100vh}
header{background:#242424;border-bottom:1px solid #606060;padding:12px 20px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
header h1{font-size:15px;font-weight:bold}
#search{margin-left:auto;background:#2e2e2e;border:1px solid #606060;border-radius:4px;color:#e8e8e8;padding:6px 12px;font-family:inherit;font-size:12px;width:220px;outline:none}
#search:focus{border-color:#378ADD}
.section{padding:12px 16px 0}
.section-hdr{font-size:13px;font-weight:bold;color:#e8e8e8;margin-bottom:6px;display:flex;align-items:baseline;gap:10px}
.section-count{font-size:11px;color:#a0a0a0;font-weight:normal}
.grid{display:flex;flex-wrap:wrap;gap:10px;padding:8px 0 20px}
.sep{height:1px;background:#333;margin:0 16px}
.card{width:178px;background:#2e2e2e;border:2px solid #2e2e2e;border-radius:6px;cursor:pointer;overflow:hidden;text-decoration:none;color:inherit;display:block;transition:border-color .15s}
.card:hover{border-color:#378ADD}
.img-wrap{width:170px;height:210px;margin:4px;background:#242424;overflow:hidden;border-radius:3px;display:flex;align-items:center;justify-content:center}
.img-wrap img{width:100%;height:100%;object-fit:cover}
.no-img{color:#606060;font-size:11px}
.card-body{padding:4px 6px 6px}
.card-name{font-size:12px;font-weight:bold;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:3px}
.card-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:3px}
.card-base{font-size:10px;border-radius:3px;padding:1px 5px;color:#e8e8e8;display:inline-block}
.card-dl{font-size:10px;color:#a0a0a0}
.trig-row{display:flex;align-items:center;gap:4px}
.card-triggers{flex:1;font-size:10px;color:#a0a0a0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;user-select:text;cursor:text}
.copy-btn{flex-shrink:0;display:inline-block;background:#2a2a2a;border:1px solid #555;border-radius:3px;color:#909090;cursor:pointer;font-size:9px;padding:1px 5px;line-height:14px;white-space:nowrap}
.copy-btn:hover{background:#378ADD;color:#fff;border-color:#378ADD}
#status{padding:60px 20px;color:#606060;text-align:center;font-size:13px}
</style>
</head>
<body>
<header>
<h1>🎨 Library — Zero-000</h1>
<input id="search" type="text" placeholder="Search name, trigger, model…" oninput="doFilter()">
</header>
<div id="status">Loading…</div>
<div id="main" style="display:none">
<div class="section">
<div class="section-hdr">🔗 LoRA Library <span id="lora-count" class="section-count"></span></div>
<div id="lora-grid" class="grid"></div>
</div>
<div class="sep"></div>
<div class="section">
<div class="section-hdr">🧠 Models Library <span id="model-count" class="section-count"></span></div>
<div id="model-grid" class="grid"></div>
</div>
</div>
<script>
const AUTHOR='Zero-000';
const BASE_COLORS={
'SDXL':'#378ADD','Pony':'#639922','Illustrious':'#E24B4A',
'SD1.5':'#8855cc','SD2.0':'#cc8800','SD2.1':'#338866',
'SD3':'#cc3355','Flux':'#cc6622'
};
let loraCards=[], modelCards=[];
function cpyTxt(text,btn){
try{
const ta=document.createElement('textarea');
ta.value=text;ta.style.cssText='position:fixed;opacity:0;top:0;left:0;width:1px;height:1px';
document.body.appendChild(ta);ta.select();document.execCommand('copy');
document.body.removeChild(ta);
if(btn){btn.textContent='✓';setTimeout(function(){btn.textContent='Copy';},1500);}
}catch(err){console.warn('copy failed',err);}
}
async function fetchModels(){
const r=await fetch(`https://huggingface.co/api/models?author=${AUTHOR}&limit=500&sort=lastModified&cardData=true`);
if(!r.ok) throw new Error('API '+r.status);
return r.json();
}
function getBase(m){
const cd=m.cardData||{};
if(cd.base_model_tag) return cd.base_model_tag;
const tags=m.tags||[];
if(tags.includes('illustrious')) return 'Illustrious';
if(tags.some(t=>t.toLowerCase().includes('pony'))) return 'Pony';
if(tags.includes('stable-diffusion-xl')) return 'SDXL';
if(tags.includes('stable-diffusion')) return 'SD1.5';
if(tags.includes('flux')) return 'Flux';
return '';
}
function fmtDl(n){
if(!n) return '0';
if(n>=1e6) return (n/1e6).toFixed(1).replace(/\.0$/,'')+'M';
if(n>=1e3) return (n/1e3).toFixed(1).replace(/\.0$/,'')+'k';
return ''+n;
}
function makeCard(m){
const repoId=m.modelId||m.id;
const cd=m.cardData||{};
const name=cd.lora_name||(repoId.split('/').pop().replace(/-/g,' '));
const base=getBase(m);
const triggers=(cd.trigger_words||'').replace(/^"|"$/g,'');
const color=BASE_COLORS[base]||'#606060';
const img=`https://huggingface.co/${repoId}/resolve/main/preview_01.jpg`;
const dl=fmtDl(m.downloads||0);
const a=document.createElement('a');
a.className='card';
a.href=`https://huggingface.co/${repoId}`;
a.target='_blank';
a.dataset.name=name.toLowerCase();
a.dataset.triggers=triggers.toLowerCase();
a.dataset.base=base.toLowerCase();
a.innerHTML=`
<div class="img-wrap">
<img src="${img}" alt="${name}"
onerror="this.parentNode.innerHTML='<span class=no-img>No Preview</span>'">
</div>
<div class="card-body">
<div class="card-name" title="${name}">${name}</div>
<div class="card-row">
<span class="card-base" style="background:${color}">${base||'Unknown'}</span>
<span class="card-dl">⬇ ${dl}</span>
</div>
<div class="trig-row">
<div class="card-triggers" title="${triggers}">${triggers||'—'}</div>
</div>
</div>`;
if(triggers){
const btn=document.createElement('button');
btn.className='copy-btn';
btn.textContent='Copy';
btn.title='Copy trigger words';
btn.addEventListener('click',function(e){
e.preventDefault();e.stopPropagation();
cpyTxt(triggers,btn);
});
a.querySelector('.trig-row').appendChild(btn);
}
return a;
}
function filterSection(cards, countId, q){
let n=0;
for(const el of cards){
const show=!q||el.dataset.name.includes(q)||el.dataset.triggers.includes(q)||el.dataset.base.includes(q);
el.style.display=show?'':'none';
if(show)n++;
}
document.getElementById(countId).textContent=q?`${n} / ${cards.length}`:`${cards.length}`;
}
function doFilter(){
const q=document.getElementById('search').value.toLowerCase().trim();
filterSection(loraCards,'lora-count',q);
filterSection(modelCards,'model-count',q);
}
async function main(){
try{
const models=await fetchModels();
const loraGrid=document.getElementById('lora-grid');
const modelGrid=document.getElementById('model-grid');
document.getElementById('status').style.display='none';
document.getElementById('main').style.display='';
for(const m of models){
const tags=m.tags||[];
if(tags.includes('lora')){
const c=makeCard(m); loraCards.push(c); loraGrid.appendChild(c);
} else if(tags.includes('checkpoint')){
const c=makeCard(m); modelCards.push(c); modelGrid.appendChild(c);
}
}
document.getElementById('lora-count').textContent=loraCards.length;
document.getElementById('model-count').textContent=modelCards.length;
}catch(e){
document.getElementById('status').textContent='Error: '+e.message;
}
}
main();
</script>
</body>
</html>