Model picker: search + recent + favorites for large catalogs

Replace the flat dump of every model in the chat-input picker with a
quick-switch. Opening the picker now shows a search box, an auto-tracked
Recent list (last 5 picks), and a manual Favorites list instead of every
available model crammed into a 280px dropdown. With large catalogs
(e.g. OpenRouter's 350+ models) this was unusable as both a quick-switch
and a browser.

- Recent: each pick is recorded most-recent-first (capped at 5) under a
  new odysseus-model-recent key, so the next open has it one click away.
- Favorites: an inline star on every row toggles favorite state and
  writes the existing odysseus-model-favorites key, so the sidebar Models
  section stays in sync. The star toggles only — it never picks the model.
- Search filters a flat list across the whole catalog; favorited rows
  keep their filled star while filtered.
- Small catalogs (<=12 models) still list everything in browse mode so
  tiny installs aren't forced to search for a model.
- Touch friendly: stars are always visible (no hover-reveal) and tap
  targets grow on narrow screens.

No changes to sidebar visibility defaults.

Closes #399
This commit is contained in:
Zeus-Deus
2026-06-01 20:39:34 +02:00
parent 70a71f603c
commit a4c2a6990a
2 changed files with 225 additions and 28 deletions
+86
View File
@@ -2711,6 +2711,92 @@ body.bg-pattern-sparkles {
opacity: 0.4;
padding: 6px 8px 2px;
}
.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-picker-list .model-switch-item .mp-model-name {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.model-picker-list .model-switch-item .model-switch-ep {
flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.9em;
opacity: 0.45;
}
/* Keyboard navigation highlight (Arrow keys in the search box). */
.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 {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin: -5px -4px -5px 2px;
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;
-webkit-tap-highlight-color: transparent;
}
.model-picker-list .mp-fav-star:hover {
color: var(--fg);
transform: scale(1.18);
}
.model-picker-list .mp-fav-star:focus-visible {
outline: none;
color: var(--fg);
}
.model-picker-list .mp-fav-star.active {
color: var(--red);
}
.model-picker-list .mp-fav-star.active:hover {
color: var(--red);
opacity: 0.7;
}
.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;
gap: 2px;
padding: 14px 8px;
text-align: center;
}
.model-picker-list .mp-empty-hint .mp-empty-title {
font-size: 1.05em;
color: color-mix(in srgb, var(--fg) 70%, transparent);
}
.model-picker-list .mp-empty-hint .mp-empty-sub {
font-size: 0.92em;
opacity: 0.7;
}
/* Comfortable touch targets on phones / narrow screens. */
@media (hover: none) and (pointer: coarse), (max-width: 768px) {
.model-picker-list .model-switch-item {
padding-top: 8px;
padding-bottom: 8px;
}
.model-picker-list .mp-fav-star {
width: 30px;
height: 30px;
margin: -7px -4px -7px 2px;
}
}
/* Overflow "+" menu */
.overflow-wrapper {
position: relative;