mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 17:55:26 -04:00
Polish model picker favorites
This commit is contained in:
+31
-23
@@ -11,7 +11,7 @@ const API_BASE = window.location.origin;
|
|||||||
// ── Recent + Favorites persistence ──
|
// ── Recent + Favorites persistence ──
|
||||||
// Recent is auto-tracked (last 5 picks, most-recent-first) and lives in its
|
// 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
|
// 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 RECENT_KEY = 'odysseus-model-recent';
|
||||||
const FAVORITES_KEY = 'odysseus-model-favorites';
|
const FAVORITES_KEY = 'odysseus-model-favorites';
|
||||||
const RECENT_MAX = 5;
|
const RECENT_MAX = 5;
|
||||||
@@ -51,11 +51,6 @@ function _toggleFavorite(mid) {
|
|||||||
return i < 0; // true when now favorited
|
return i < 0; // true when now favorited
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filled star (favorited) + outline star (not) — CSS toggles which shows.
|
|
||||||
const _STAR_SVG =
|
|
||||||
'<svg class="mp-star-outline" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>'
|
|
||||||
+ '<svg class="mp-star-filled" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>';
|
|
||||||
|
|
||||||
// ── Shared keyboard nav for model pickers ──
|
// ── Shared keyboard nav for model pickers ──
|
||||||
function _handlePickerKeydown(e, listEl, itemSelector, closeFn) {
|
function _handlePickerKeydown(e, listEl, itemSelector, closeFn) {
|
||||||
if (e.key === 'Escape') { closeFn(); return; }
|
if (e.key === 'Escape') { closeFn(); return; }
|
||||||
@@ -200,6 +195,12 @@ function _initModelPickerDropdown() {
|
|||||||
url: item.url,
|
url: item.url,
|
||||||
endpointId: item.endpoint_id,
|
endpointId: item.endpoint_id,
|
||||||
epName: item.endpoint_name || '',
|
epName: item.endpoint_name || '',
|
||||||
|
providerText: [
|
||||||
|
item.endpoint_name || '',
|
||||||
|
item.category || '',
|
||||||
|
item.host || '',
|
||||||
|
item.url || '',
|
||||||
|
].filter(Boolean).join(' '),
|
||||||
stale: isLocalDead,
|
stale: isLocalDead,
|
||||||
staleReason: isLocalDead ? (probeResult.error || 'not responding') : '',
|
staleReason: isLocalDead ? (probeResult.error || 'not responding') : '',
|
||||||
});
|
});
|
||||||
@@ -277,22 +278,22 @@ function _initModelPickerDropdown() {
|
|||||||
epSpan.textContent = _epDisplay;
|
epSpan.textContent = _epDisplay;
|
||||||
row.appendChild(epSpan);
|
row.appendChild(epSpan);
|
||||||
|
|
||||||
// Inline favorite star — toggles favorite, never picks the model.
|
// Inline favorite dot — toggles favorite, never picks the model.
|
||||||
const star = document.createElement('button');
|
const favDot = document.createElement('button');
|
||||||
star.type = 'button';
|
favDot.type = 'button';
|
||||||
star.className = 'mp-fav-star' + (favs.includes(m.mid) ? ' active' : '');
|
favDot.className = 'mp-fav-dot' + (favs.includes(m.mid) ? ' active' : '');
|
||||||
const _setStarState = (on) => {
|
favDot.textContent = '●';
|
||||||
star.classList.toggle('active', on);
|
const _setFavState = (on) => {
|
||||||
star.title = on ? 'Remove from favorites' : 'Add to favorites';
|
favDot.classList.toggle('active', on);
|
||||||
star.setAttribute('aria-label', on ? 'Remove from favorites' : 'Add to favorites');
|
favDot.title = on ? 'Remove from favorites' : 'Add to favorites';
|
||||||
star.setAttribute('aria-pressed', on ? 'true' : 'false');
|
favDot.setAttribute('aria-label', on ? 'Remove from favorites' : 'Add to favorites');
|
||||||
|
favDot.setAttribute('aria-pressed', on ? 'true' : 'false');
|
||||||
};
|
};
|
||||||
star.innerHTML = _STAR_SVG;
|
_setFavState(favs.includes(m.mid));
|
||||||
_setStarState(favs.includes(m.mid));
|
favDot.addEventListener('click', (e) => {
|
||||||
star.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const nowFav = _toggleFavorite(m.mid);
|
const nowFav = _toggleFavorite(m.mid);
|
||||||
_setStarState(nowFav);
|
_setFavState(nowFav);
|
||||||
// Keep our in-memory copy aligned so a follow-up re-render is correct.
|
// Keep our in-memory copy aligned so a follow-up re-render is correct.
|
||||||
const idx = favs.indexOf(m.mid);
|
const idx = favs.indexOf(m.mid);
|
||||||
if (nowFav && idx < 0) favs.push(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');
|
if (uiModule && uiModule.showToast) uiModule.showToast(nowFav ? 'Favorited' : 'Unfavorited');
|
||||||
// In browse mode the Favorites section membership changed — rebuild
|
// In browse mode the Favorites section membership changed — rebuild
|
||||||
// (cheap: Recent + Favorites). In search mode the row stays put, so
|
// (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) {
|
if (!q) {
|
||||||
const st = listEl.scrollTop;
|
const st = listEl.scrollTop;
|
||||||
_populate('');
|
_populate('');
|
||||||
listEl.scrollTop = st;
|
listEl.scrollTop = st;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
row.appendChild(star);
|
row.appendChild(favDot);
|
||||||
|
|
||||||
row.addEventListener('click', () => _pick(m));
|
row.addEventListener('click', () => _pick(m));
|
||||||
listEl.appendChild(row);
|
listEl.appendChild(row);
|
||||||
@@ -316,7 +317,12 @@ function _initModelPickerDropdown() {
|
|||||||
// ── Search mode: flat, filtered results across the whole catalog ──
|
// ── Search mode: flat, filtered results across the whole catalog ──
|
||||||
if (q) {
|
if (q) {
|
||||||
const matches = all.filter(m =>
|
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');
|
if (matches.length === 0) _addEmpty('No matching models');
|
||||||
else matches.forEach(_addRow);
|
else matches.forEach(_addRow);
|
||||||
return;
|
return;
|
||||||
@@ -352,7 +358,7 @@ function _initModelPickerDropdown() {
|
|||||||
hint.className = 'model-switch-empty mp-empty-hint';
|
hint.className = 'model-switch-empty mp-empty-hint';
|
||||||
hint.innerHTML =
|
hint.innerHTML =
|
||||||
'<span class="mp-empty-title">Search ' + all.length + ' models</span>'
|
'<span class="mp-empty-title">Search ' + all.length + ' models</span>'
|
||||||
+ '<span class="mp-empty-sub">Picks land in Recent · tap ☆ to favorite</span>';
|
+ '<span class="mp-empty-sub">Picks land in Recent · tap the dot to favorite</span>';
|
||||||
listEl.appendChild(hint);
|
listEl.appendChild(hint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -441,6 +447,7 @@ function _initModelPickerDropdown() {
|
|||||||
url: item.url || detail.url || '',
|
url: item.url || detail.url || '',
|
||||||
endpointId: item.endpoint_id || detail.endpointId || '',
|
endpointId: item.endpoint_id || detail.endpointId || '',
|
||||||
epName: item.endpoint_name || detail.endpointName || '',
|
epName: item.endpoint_name || detail.endpointName || '',
|
||||||
|
providerText: [item.endpoint_name || detail.endpointName || '', item.url || detail.url || ''].filter(Boolean).join(' '),
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -452,6 +459,7 @@ function _initModelPickerDropdown() {
|
|||||||
url: detail.url,
|
url: detail.url,
|
||||||
endpointId: detail.endpointId || '',
|
endpointId: detail.endpointId || '',
|
||||||
epName: detail.endpointName || '',
|
epName: detail.endpointName || '',
|
||||||
|
providerText: [detail.endpointName || '', detail.url || ''].filter(Boolean).join(' '),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (match) await _pick(match);
|
if (match) await _pick(match);
|
||||||
|
|||||||
+23
-22
@@ -2718,7 +2718,7 @@ body.bg-pattern-sparkles {
|
|||||||
.model-picker-list .mp-section-label:first-child {
|
.model-picker-list .mp-section-label:first-child {
|
||||||
padding-top: 2px;
|
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 {
|
.model-picker-list .model-switch-item .mp-model-name {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -2739,41 +2739,42 @@ body.bg-pattern-sparkles {
|
|||||||
.model-picker-list .model-switch-item.kb-active {
|
.model-picker-list .model-switch-item.kb-active {
|
||||||
background: color-mix(in srgb, var(--red) 14%, transparent);
|
background: color-mix(in srgb, var(--red) 14%, transparent);
|
||||||
}
|
}
|
||||||
/* Inline favorite star — always visible (works on touch), filled when on. */
|
/* Inline favorite dot — always visible (works on touch), active when on. */
|
||||||
.model-picker-list .mp-fav-star {
|
.model-picker-list .mp-fav-dot {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 24px;
|
width: 30px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
margin: -5px -4px -5px 2px;
|
margin: -5px 0 -5px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: color-mix(in srgb, var(--fg) 26%, transparent);
|
color: color-mix(in srgb, var(--fg) 22%, transparent);
|
||||||
transition: color 0.15s ease, transform 0.12s ease;
|
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;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
.model-picker-list .mp-fav-star:hover {
|
.model-picker-list .mp-fav-dot:hover {
|
||||||
color: var(--fg);
|
color: color-mix(in srgb, var(--fg) 68%, transparent);
|
||||||
transform: scale(1.18);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
.model-picker-list .mp-fav-star:focus-visible {
|
.model-picker-list .mp-fav-dot:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
color: var(--fg);
|
color: color-mix(in srgb, var(--fg) 68%, transparent);
|
||||||
}
|
}
|
||||||
.model-picker-list .mp-fav-star.active {
|
.model-picker-list .mp-fav-dot.active {
|
||||||
color: var(--red);
|
color: var(--accent, var(--red));
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.model-picker-list .mp-fav-star.active:hover {
|
.model-picker-list .mp-fav-dot.active:hover {
|
||||||
color: var(--red);
|
color: var(--accent, var(--red));
|
||||||
opacity: 0.7;
|
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. */
|
/* First-run hint when a large catalog has no Recent/Favorites yet. */
|
||||||
.model-picker-list .mp-empty-hint {
|
.model-picker-list .mp-empty-hint {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2795,10 +2796,10 @@ body.bg-pattern-sparkles {
|
|||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
.model-picker-list .mp-fav-star {
|
.model-picker-list .mp-fav-dot {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
margin: -7px -4px -7px 2px;
|
margin: -7px 0 -7px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Overflow "+" menu */
|
/* Overflow "+" menu */
|
||||||
|
|||||||
Reference in New Issue
Block a user