(type, anchorBtn)
| 709 | dropdown.className = 'absolute bottom-[102%] left-2 z-50 transition-all opacity-0 pointer-events-none scale-95 origin-bottom-left glass rounded-3xl p-3 translate-y-2 w-[calc(100vw-3rem)] max-w-xs shadow-4xl border border-white/10 flex flex-col'; |
| 710 | |
| 711 | const showDropdown = (type, anchorBtn) => { |
| 712 | dropdown.innerHTML = ''; |
| 713 | dropdown.classList.remove('opacity-0', 'pointer-events-none'); |
| 714 | dropdown.classList.add('opacity-100', 'pointer-events-auto'); |
| 715 | |
| 716 | if (type === 'model') { |
| 717 | dropdown.classList.add('w-[calc(100vw-3rem)]', 'max-w-xs'); |
| 718 | dropdown.classList.remove('max-w-[240px]', 'max-w-[200px]'); |
| 719 | dropdown.innerHTML = ` |
| 720 | <div class="flex flex-col h-full max-h-[70vh]"> |
| 721 | <div class="px-2 pb-3 mb-2 border-b border-white/5 shrink-0"> |
| 722 | <div class="flex items-center gap-3 bg-white/5 rounded-xl px-4 py-2.5 border border-white/5 focus-within:border-primary/50 transition-colors"> |
| 723 | <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" class="text-muted"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg> |
| 724 | <input type="text" id="model-search" placeholder="${t('common.searchModels')}" class="bg-transparent border-none text-xs text-white focus:ring-0 w-full p-0"> |
| 725 | </div> |
| 726 | </div> |
| 727 | <div class="text-[10px] font-bold text-secondary uppercase tracking-widest px-3 py-2 shrink-0">Available models</div> |
| 728 | <div id="model-list-container" class="flex flex-col gap-1.5 overflow-y-auto custom-scrollbar pr-1 pb-2"></div> |
| 729 | </div> |
| 730 | `; |
| 731 | const list = dropdown.querySelector('#model-list-container'); |
| 732 | |
| 733 | const renderModels = (filter = '') => { |
| 734 | list.innerHTML = ''; |
| 735 | |
| 736 | if (useLocalModel) { |
| 737 | // ── Local model list (Wan2GP image-capable models only) ─── |
| 738 | const filtered = LOCAL_IMAGE_MODELS.filter(m => |
| 739 | m.name.toLowerCase().includes(filter.toLowerCase()) || |
| 740 | m.id.toLowerCase().includes(filter.toLowerCase()) |
| 741 | ); |
| 742 | if (filtered.length === 0) { |
| 743 | list.innerHTML = `<div class="text-xs text-muted text-center py-4">${t('common.noResults')}</div>`; |
| 744 | return; |
| 745 | } |
| 746 | filtered.forEach(m => { |
| 747 | const item = document.createElement('div'); |
| 748 | item.className = `flex items-center justify-between p-3.5 hover:bg-white/5 rounded-2xl cursor-pointer transition-all border border-transparent hover:border-white/5 ${selectedLocalModel === m.id ? 'bg-white/5 border-white/5' : ''}`; |
| 749 | item.innerHTML = ` |
| 750 | <div class="flex items-center gap-3.5"> |
| 751 | <div class="w-10 h-10 ${m.featured ? 'bg-primary/10 text-primary' : 'bg-green-500/10 text-green-400'} border border-white/5 rounded-xl flex items-center justify-center font-black text-sm shadow-inner uppercase">${m.featured ? '⚡' : m.name.charAt(0)}</div> |
| 752 | <div class="flex flex-col gap-0.5"> |
| 753 | <div class="flex items-center gap-1.5"> |
| 754 | <span class="text-xs font-bold text-white tracking-tight">${m.name}</span> |
| 755 | ${m.featured ? '<span class="text-[9px] font-black px-1 py-0.5 rounded bg-primary/20 text-primary">FEATURED</span>' : ''} |
| 756 | </div> |
| 757 | <span class="text-[10px] text-muted">${m.type.toUpperCase()} · ${m.family}</span> |
| 758 | </div> |
| 759 | </div> |
| 760 | ${selectedLocalModel === m.id ? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22d3ee" stroke-width="4"><polyline points="20 6 9 17 4 12"/></svg>' : ''} |
| 761 | `; |
| 762 | item.onclick = (e) => { |
| 763 | e.stopPropagation(); |
| 764 | selectedLocalModel = m.id; |
| 765 | document.getElementById('model-btn-label').textContent = m.name; |
| 766 | selectedAr = m.aspectRatios[0]; |
| 767 | document.getElementById('ar-btn-label').textContent = selectedAr; |
| 768 | qualityBtn.style.display = 'none'; |
no test coverage detected