|
(() => { |
|
const ID = 'ollama-model-filter'; |
|
const OBSERVER_KEY = '__ollamaModelFilterObserver'; |
|
|
|
const restore = () => { |
|
document.querySelectorAll('[data-omf-original-display]').forEach((card) => { |
|
card.style.display = card.dataset.omfOriginalDisplay; |
|
delete card.dataset.omfOriginalDisplay; |
|
}); |
|
window[OBSERVER_KEY]?.disconnect(); |
|
delete window[OBSERVER_KEY]; |
|
document.getElementById(ID)?.remove(); |
|
}; |
|
|
|
if (document.getElementById(ID)) { |
|
restore(); |
|
return; |
|
} |
|
|
|
const getCards = () => { |
|
const cards = [...document.querySelectorAll('li')].filter((el) => { |
|
const text = el.innerText || ''; |
|
return /\bPulls\b/.test(text) && /\bTags?\b/.test(text) && /\bUpdated\b/.test(text); |
|
}); |
|
|
|
if (cards.length) return cards; |
|
|
|
return [...new Set( |
|
[...document.querySelectorAll('a[href^="/library/"]')] |
|
.map((a) => a.closest('li') || a) |
|
)]; |
|
}; |
|
|
|
const getTags = (card) => { |
|
const exactTexts = [...card.querySelectorAll('*')] |
|
.map((el) => (el.textContent || '').trim().toLowerCase()) |
|
.filter((text) => text.length <= 16); |
|
|
|
const sizes = [...new Set(exactTexts |
|
.map((text) => text.match(/^e?(\d+(?:\.\d+)?)b$/)) |
|
.filter(Boolean) |
|
.map((match) => Number(match[1])) |
|
.filter(Number.isFinite))]; |
|
|
|
return { |
|
cloud: exactTexts.includes('cloud'), |
|
sizes, |
|
}; |
|
}; |
|
|
|
const panel = document.createElement('div'); |
|
panel.id = ID; |
|
panel.innerHTML = ` |
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:9px"> |
|
<strong style="font-size:14px">Ollama filter</strong> |
|
<span data-count style="margin-left:auto;color:#888"></span> |
|
<button data-close title="Zavřít a obnovit seznam" style="border:0;background:none;color:inherit;font-size:18px;cursor:pointer;padding:0 2px">×</button> |
|
</div> |
|
<label style="display:flex;align-items:center;gap:8px;margin:6px 0"> |
|
<span style="width:72px">Max. B</span> |
|
<input data-max type="number" min="0" step="0.1" placeholder="bez limitu" style="width:110px;padding:4px 6px;border:1px solid #888;border-radius:5px;background:inherit;color:inherit"> |
|
</label> |
|
<label style="display:flex;align-items:center;gap:8px;margin:6px 0"> |
|
<span style="width:72px">Cloud</span> |
|
<select data-cloud style="width:124px;padding:4px 6px;border:1px solid #888;border-radius:5px;background:inherit;color:inherit"> |
|
<option value="all">všechny</option> |
|
<option value="yes">jen cloud</option> |
|
<option value="no">bez cloud</option> |
|
</select> |
|
</label> |
|
<label style="display:flex;align-items:center;gap:7px;margin-top:8px"> |
|
<input data-unknown type="checkbox"> |
|
ponechat modely bez B tagu |
|
</label>`; |
|
|
|
Object.assign(panel.style, { |
|
position: 'fixed', |
|
top: '16px', |
|
right: '16px', |
|
zIndex: '2147483647', |
|
width: '245px', |
|
padding: '12px 14px', |
|
border: '1px solid #777', |
|
borderRadius: '9px', |
|
background: 'Canvas', |
|
color: 'CanvasText', |
|
boxShadow: '0 6px 24px rgba(0,0,0,.25)', |
|
font: '13px system-ui,sans-serif', |
|
}); |
|
|
|
document.body.append(panel); |
|
|
|
const maxInput = panel.querySelector('[data-max]'); |
|
const cloudSelect = panel.querySelector('[data-cloud]'); |
|
const unknownInput = panel.querySelector('[data-unknown]'); |
|
const count = panel.querySelector('[data-count]'); |
|
|
|
const apply = () => { |
|
const max = Number.parseFloat(maxInput.value); |
|
const hasMax = Number.isFinite(max); |
|
const cloudMode = cloudSelect.value; |
|
let visible = 0; |
|
let total = 0; |
|
|
|
getCards().forEach((card) => { |
|
total += 1; |
|
if (!('omfOriginalDisplay' in card.dataset)) { |
|
card.dataset.omfOriginalDisplay = card.style.display || ''; |
|
} |
|
|
|
const { cloud, sizes } = getTags(card); |
|
const cloudOK = cloudMode === 'all' || (cloudMode === 'yes' ? cloud : !cloud); |
|
const sizeOK = !hasMax || sizes.some((size) => size <= max) || (unknownInput.checked && sizes.length === 0); |
|
const show = cloudOK && sizeOK; |
|
|
|
card.style.display = show ? card.dataset.omfOriginalDisplay : 'none'; |
|
if (show) visible += 1; |
|
}); |
|
|
|
count.textContent = `${visible}/${total}`; |
|
}; |
|
|
|
panel.addEventListener('input', apply); |
|
panel.addEventListener('change', apply); |
|
panel.querySelector('[data-close]').addEventListener('click', restore); |
|
|
|
let scheduled = false; |
|
window[OBSERVER_KEY] = new MutationObserver((mutations) => { |
|
if (!mutations.some((mutation) => !panel.contains(mutation.target))) return; |
|
if (scheduled) return; |
|
scheduled = true; |
|
requestAnimationFrame(() => { |
|
scheduled = false; |
|
apply(); |
|
}); |
|
}); |
|
window[OBSERVER_KEY].observe(document.body, { childList: true, subtree: true }); |
|
|
|
apply(); |
|
})(); |