diff --git a/static/js/modelPicker.js b/static/js/modelPicker.js index 41dcfca..6bf4c83 100644 --- a/static/js/modelPicker.js +++ b/static/js/modelPicker.js @@ -11,7 +11,7 @@ const API_BASE = window.location.origin; // ── Recent + Favorites persistence ── // Recent is auto-tracked (last 5 picks, most-recent-first) and lives in its // own key. Favorites is the SAME key the sidebar Models section uses, so a -// star toggled here shows up there and vice-versa. +// favorite toggled here shows up there and vice-versa. const RECENT_KEY = 'odysseus-model-recent'; const FAVORITES_KEY = 'odysseus-model-favorites'; const RECENT_MAX = 5; @@ -51,11 +51,6 @@ function _toggleFavorite(mid) { return i < 0; // true when now favorited } -// Filled star (favorited) + outline star (not) — CSS toggles which shows. -const _STAR_SVG = - '' - + ''; - // ── Shared keyboard nav for model pickers ── function _handlePickerKeydown(e, listEl, itemSelector, closeFn) { if (e.key === 'Escape') { closeFn(); return; } @@ -200,6 +195,12 @@ function _initModelPickerDropdown() { url: item.url, endpointId: item.endpoint_id, epName: item.endpoint_name || '', + providerText: [ + item.endpoint_name || '', + item.category || '', + item.host || '', + item.url || '', + ].filter(Boolean).join(' '), stale: isLocalDead, staleReason: isLocalDead ? (probeResult.error || 'not responding') : '', }); @@ -277,22 +278,22 @@ function _initModelPickerDropdown() { epSpan.textContent = _epDisplay; row.appendChild(epSpan); - // Inline favorite star — toggles favorite, never picks the model. - const star = document.createElement('button'); - star.type = 'button'; - star.className = 'mp-fav-star' + (favs.includes(m.mid) ? ' active' : ''); - const _setStarState = (on) => { - star.classList.toggle('active', on); - star.title = on ? 'Remove from favorites' : 'Add to favorites'; - star.setAttribute('aria-label', on ? 'Remove from favorites' : 'Add to favorites'); - star.setAttribute('aria-pressed', on ? 'true' : 'false'); + // Inline favorite dot — toggles favorite, never picks the model. + const favDot = document.createElement('button'); + favDot.type = 'button'; + favDot.className = 'mp-fav-dot' + (favs.includes(m.mid) ? ' active' : ''); + favDot.textContent = '●'; + const _setFavState = (on) => { + favDot.classList.toggle('active', on); + favDot.title = on ? 'Remove from favorites' : 'Add to favorites'; + favDot.setAttribute('aria-label', on ? 'Remove from favorites' : 'Add to favorites'); + favDot.setAttribute('aria-pressed', on ? 'true' : 'false'); }; - star.innerHTML = _STAR_SVG; - _setStarState(favs.includes(m.mid)); - star.addEventListener('click', (e) => { + _setFavState(favs.includes(m.mid)); + favDot.addEventListener('click', (e) => { e.stopPropagation(); const nowFav = _toggleFavorite(m.mid); - _setStarState(nowFav); + _setFavState(nowFav); // Keep our in-memory copy aligned so a follow-up re-render is correct. const idx = favs.indexOf(m.mid); if (nowFav && idx < 0) favs.push(m.mid); @@ -300,14 +301,14 @@ function _initModelPickerDropdown() { if (uiModule && uiModule.showToast) uiModule.showToast(nowFav ? 'Favorited' : 'Unfavorited'); // In browse mode the Favorites section membership changed — rebuild // (cheap: Recent + Favorites). In search mode the row stays put, so - // the in-place star update above is enough. + // the in-place favorite update above is enough. if (!q) { const st = listEl.scrollTop; _populate(''); listEl.scrollTop = st; } }); - row.appendChild(star); + row.appendChild(favDot); row.addEventListener('click', () => _pick(m)); listEl.appendChild(row); @@ -316,7 +317,12 @@ function _initModelPickerDropdown() { // ── Search mode: flat, filtered results across the whole catalog ── if (q) { const matches = all.filter(m => - m.mid.toLowerCase().includes(q) || m.display.toLowerCase().includes(q)); + [ + m.mid, + m.display, + m.epName, + m.providerText, + ].filter(Boolean).join(' ').toLowerCase().includes(q)); if (matches.length === 0) _addEmpty('No matching models'); else matches.forEach(_addRow); return; @@ -352,7 +358,7 @@ function _initModelPickerDropdown() { hint.className = 'model-switch-empty mp-empty-hint'; hint.innerHTML = 'Search ' + all.length + ' models' - + 'Picks land in Recent · tap ☆ to favorite'; + + 'Picks land in Recent · tap the dot to favorite'; listEl.appendChild(hint); } } @@ -441,6 +447,7 @@ function _initModelPickerDropdown() { url: item.url || detail.url || '', endpointId: item.endpoint_id || detail.endpointId || '', epName: item.endpoint_name || detail.endpointName || '', + providerText: [item.endpoint_name || detail.endpointName || '', item.url || detail.url || ''].filter(Boolean).join(' '), }; break; } @@ -452,6 +459,7 @@ function _initModelPickerDropdown() { url: detail.url, endpointId: detail.endpointId || '', epName: detail.endpointName || '', + providerText: [detail.endpointName || '', detail.url || ''].filter(Boolean).join(' '), }; } if (match) await _pick(match); diff --git a/static/style.css b/static/style.css index 8cff262..472ef25 100644 --- a/static/style.css +++ b/static/style.css @@ -2718,7 +2718,7 @@ body.bg-pattern-sparkles { .model-picker-list .mp-section-label:first-child { padding-top: 2px; } - /* Model name takes the slack so the endpoint label + star sit on the right. */ + /* Model name takes the slack so the endpoint label + favorite dot sit on the right. */ .model-picker-list .model-switch-item .mp-model-name { flex: 1 1 auto; min-width: 0; @@ -2739,41 +2739,42 @@ body.bg-pattern-sparkles { .model-picker-list .model-switch-item.kb-active { background: color-mix(in srgb, var(--red) 14%, transparent); } - /* Inline favorite star — always visible (works on touch), filled when on. */ - .model-picker-list .mp-fav-star { + /* Inline favorite dot — always visible (works on touch), active when on. */ + .model-picker-list .mp-fav-dot { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; - width: 24px; + width: 30px; height: 24px; - margin: -5px -4px -5px 2px; + margin: -5px 0 -5px 0; padding: 0; border: none; background: none; cursor: pointer; - color: color-mix(in srgb, var(--fg) 26%, transparent); - transition: color 0.15s ease, transform 0.12s ease; + color: color-mix(in srgb, var(--fg) 22%, transparent); + font-family: inherit; + font-size: 13px; + line-height: 1; + transition: color 0.15s ease, opacity 0.15s ease, transform 0.12s ease; -webkit-tap-highlight-color: transparent; } - .model-picker-list .mp-fav-star:hover { - color: var(--fg); - transform: scale(1.18); + .model-picker-list .mp-fav-dot:hover { + color: color-mix(in srgb, var(--fg) 68%, transparent); + transform: scale(1.15); } - .model-picker-list .mp-fav-star:focus-visible { + .model-picker-list .mp-fav-dot:focus-visible { outline: none; - color: var(--fg); + color: color-mix(in srgb, var(--fg) 68%, transparent); } - .model-picker-list .mp-fav-star.active { - color: var(--red); + .model-picker-list .mp-fav-dot.active { + color: var(--accent, var(--red)); + opacity: 1; } - .model-picker-list .mp-fav-star.active:hover { - color: var(--red); - opacity: 0.7; + .model-picker-list .mp-fav-dot.active:hover { + color: var(--accent, var(--red)); + opacity: 0.72; } - .model-picker-list .mp-fav-star .mp-star-filled { display: none; } - .model-picker-list .mp-fav-star.active .mp-star-filled { display: inline-flex; } - .model-picker-list .mp-fav-star.active .mp-star-outline { display: none; } /* First-run hint when a large catalog has no Recent/Favorites yet. */ .model-picker-list .mp-empty-hint { flex-direction: column; @@ -2795,10 +2796,10 @@ body.bg-pattern-sparkles { padding-top: 8px; padding-bottom: 8px; } - .model-picker-list .mp-fav-star { + .model-picker-list .mp-fav-dot { width: 30px; height: 30px; - margin: -7px -4px -7px 2px; + margin: -7px 0 -7px 0; } } /* Overflow "+" menu */