feat(vscode): make model picker sections collapsible

Click any section header (Favorites, Recommended, or a provider) to
collapse it and skip an entire provider with one click. Search ignores
collapse state. Collapse state resets each time the picker opens.
This commit is contained in:
kiloconnect[bot]
2026-05-19 09:38:36 +00:00
parent 4c0e6987b1
commit 6ce2ded75c
22 changed files with 204 additions and 90 deletions
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---
Make sections in the model picker collapsible. Click any section header (Favorites, Recommended, or a provider like Kilo Gateway) to hide its models. Collapse state resets each time the picker opens.
@@ -120,6 +120,9 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
const [preActiveKey, setPreActiveKey] = createSignal<string | null>(null)
const [previewKey, setPreviewKey] = createSignal<string | null>(null)
const [previewHeight, setPreviewHeight] = createSignal(500)
// Per-group collapse state. Not persisted — resets every time the
// selector mounts so groups are always expanded on reopen.
const [collapsed, setCollapsed] = createSignal<Set<string>>(new Set())
// Snapshot of the active model key captured when the popover opens.
// Used to reorder favorites so the current model appears first — but only
// based on the state at open-time, not reactively, to avoid list jumps
@@ -281,8 +284,23 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
return [...result, ...rest]
})
// While a search is active, collapse state is ignored — otherwise users
// get confusing "no results" when their match lives inside a collapsed group.
const isGroupOpen = (key: string) => !!debouncedSearch() || !collapsed().has(key)
function toggleGroup(key: string) {
setCollapsed((prev) => {
const next = new Set(prev)
if (next.has(key)) next.delete(key)
else next.add(key)
return next
})
}
const rows = createMemo<ModelRow[]>(() => {
const list = groups().flatMap((g) => g.rows)
const search = !!debouncedSearch()
const c = collapsed()
const list = groups().flatMap((g) => (search || !c.has(g.key) ? g.rows : []))
if (!props.allowClear) return list
return [{ key: CLEAR_KEY, kind: "clear" }, ...list]
})
@@ -633,97 +651,123 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
</Show>
<For each={groups()}>
{(group) => (
<>
<div class="model-selector-group-label">{group.label}</div>
<For each={group.rows}>
{(row) => {
if (!row.model) return null
const model = row.model
const hovered = () => isSelected(row.key)
const preActive = () => isPreActive(row.key)
const showSelectBtn = () => expanded() && preActive() && !isActive(model)
const starred = () => favoriteKeys().has(modelKey(model.providerID, model.id))
const showProvider = () => row.kind === "favorite"
return (
<div
ref={(el) => {
refs.set(row.key, el)
onCleanup(() => refs.delete(row.key))
}}
class={`model-selector-item${(hovered() && !pointer()) || preActive() ? " keyboard-focused" : ""}${hovered() || preActive() ? " selected" : ""}${isActive(model) && row.kind === "model" ? " active" : ""}`}
role="option"
aria-selected={isActive(model) && row.kind === "model"}
onClick={() => {
setRow(row.key)
setPreviewKey(row.key)
if (!expanded()) selectRow(row)
searchRef?.focus()
}}
onDblClick={() => {
if (expanded()) selectRow(row)
}}
onMouseMove={() => {
setPointer(true)
}}
onMouseEnter={() => {
if (pointer()) setSelectedKey(row.key)
}}
>
<div class="model-selector-item-left">
<span class="model-selector-item-name">
{(() => {
const full = sanitizeName(model.name)
const sep = full.indexOf(": ")
if (sep < 0) return <span class="model-selector-item-name-main">{full}</span>
return (
<>
<span class="model-selector-item-name-provider">{full.slice(0, sep)}</span>
<span class="model-selector-item-name-main">{full.slice(sep + 2)}</span>
</>
)
})()}
</span>
<Show when={isFree(model)}>
<Tag data-variant="member">{language.t("model.tag.free")}</Tag>
</Show>
<Show when={showProvider()}>
<span class="model-selector-item-provider-tag">{model.providerName}</span>
</Show>
</div>
<Show when={session && props.favorites !== false}>
<button
type="button"
class={`model-selector-star${starred() ? " model-selector-star--active" : ""}`}
aria-label={
starred() ? language.t("model.favorite.remove") : language.t("model.favorite.add")
}
aria-pressed={starred()}
onClick={(e) => {
e.stopPropagation()
toggleFavorite(model, row)
{(group) => {
const shown = () => isGroupOpen(group.key)
return (
<>
<button
type="button"
class="model-selector-group-label"
aria-expanded={shown()}
aria-label={language.t(shown() ? "model.group.collapse" : "model.group.expand", {
group: group.label,
})}
onMouseDown={(e) => e.preventDefault()}
onClick={() => toggleGroup(group.key)}
>
<svg
class={`model-selector-group-chevron${shown() ? "" : " model-selector-group-chevron--collapsed"}`}
width="10"
height="10"
viewBox="0 0 16 16"
fill="currentColor"
aria-hidden="true"
>
<path d="M4 6l4 5 4-5H4z" />
</svg>
<span>{group.label}</span>
</button>
<Show when={shown()}>
<For each={group.rows}>
{(row) => {
if (!row.model) return null
const model = row.model
const hovered = () => isSelected(row.key)
const preActive = () => isPreActive(row.key)
const showSelectBtn = () => expanded() && preActive() && !isActive(model)
const starred = () => favoriteKeys().has(modelKey(model.providerID, model.id))
const showProvider = () => row.kind === "favorite"
return (
<div
ref={(el) => {
refs.set(row.key, el)
onCleanup(() => refs.delete(row.key))
}}
class={`model-selector-item${(hovered() && !pointer()) || preActive() ? " keyboard-focused" : ""}${hovered() || preActive() ? " selected" : ""}${isActive(model) && row.kind === "model" ? " active" : ""}`}
role="option"
aria-selected={isActive(model) && row.kind === "model"}
onClick={() => {
setRow(row.key)
setPreviewKey(row.key)
if (!expanded()) selectRow(row)
searchRef?.focus()
}}
onDblClick={() => {
if (expanded()) selectRow(row)
}}
onMouseMove={() => {
setPointer(true)
}}
onMouseEnter={() => {
if (pointer()) setSelectedKey(row.key)
}}
>
<Icon name={starred() ? "star-filled" : "star"} size="small" />
</button>
</Show>
<Show when={expanded()}>
<button
class={`model-selector-item-select-btn${showSelectBtn() ? "" : " model-selector-item-select-btn--hidden"}`}
onClick={(e) => {
e.stopPropagation()
selectRow(row)
}}
>
{language.t("dialog.model.select")}
</button>
</Show>
</div>
)
}}
</For>
</>
)}
<div class="model-selector-item-left">
<span class="model-selector-item-name">
{(() => {
const full = sanitizeName(model.name)
const sep = full.indexOf(": ")
if (sep < 0) return <span class="model-selector-item-name-main">{full}</span>
return (
<>
<span class="model-selector-item-name-provider">{full.slice(0, sep)}</span>
<span class="model-selector-item-name-main">{full.slice(sep + 2)}</span>
</>
)
})()}
</span>
<Show when={isFree(model)}>
<Tag data-variant="member">{language.t("model.tag.free")}</Tag>
</Show>
<Show when={showProvider()}>
<span class="model-selector-item-provider-tag">{model.providerName}</span>
</Show>
</div>
<Show when={session && props.favorites !== false}>
<button
type="button"
class={`model-selector-star${starred() ? " model-selector-star--active" : ""}`}
aria-label={
starred() ? language.t("model.favorite.remove") : language.t("model.favorite.add")
}
aria-pressed={starred()}
onClick={(e) => {
e.stopPropagation()
toggleFavorite(model, row)
}}
>
<Icon name={starred() ? "star-filled" : "star"} size="small" />
</button>
</Show>
<Show when={expanded()}>
<button
class={`model-selector-item-select-btn${showSelectBtn() ? "" : " model-selector-item-select-btn--hidden"}`}
onClick={(e) => {
e.stopPropagation()
selectRow(row)
}}
>
{language.t("dialog.model.select")}
</button>
</Show>
</div>
)
}}
</For>
</Show>
</>
)
}}
</For>
</div>
+2
View File
@@ -176,6 +176,8 @@ export const dict = {
"model.tag.latest": "الأحدث",
"model.group.recommended": "موصى به",
"model.group.favorites": "المفضلة",
"model.group.collapse": "طي {{group}}",
"model.group.expand": "توسيع {{group}}",
"model.favorite.add": "إضافة إلى المفضلة",
"model.favorite.remove": "إزالة من المفضلة",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "Mais recente",
"model.group.recommended": "Recomendado",
"model.group.favorites": "Favoritos",
"model.group.collapse": "Recolher {{group}}",
"model.group.expand": "Expandir {{group}}",
"model.favorite.add": "Adicionar aos favoritos",
"model.favorite.remove": "Remover dos favoritos",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -178,6 +178,8 @@ export const dict = {
"model.tag.latest": "Najnovije",
"model.group.recommended": "Preporučeno",
"model.group.favorites": "Favoriti",
"model.group.collapse": "Sakrij {{group}}",
"model.group.expand": "Prikaži {{group}}",
"model.favorite.add": "Dodaj u favorite",
"model.favorite.remove": "Ukloni iz favorita",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "Nyeste",
"model.group.recommended": "Anbefalet",
"model.group.favorites": "Favoritter",
"model.group.collapse": "Skjul {{group}}",
"model.group.expand": "Vis {{group}}",
"model.favorite.add": "Føj til favoritter",
"model.favorite.remove": "Fjern fra favoritter",
+2
View File
@@ -181,6 +181,8 @@ export const dict = {
"model.tag.latest": "Neueste",
"model.group.recommended": "Empfohlen",
"model.group.favorites": "Favoriten",
"model.group.collapse": "{{group}} einklappen",
"model.group.expand": "{{group}} ausklappen",
"model.favorite.add": "Zu Favoriten hinzufügen",
"model.favorite.remove": "Aus Favoriten entfernen",
@@ -178,6 +178,8 @@ export const dict = {
"model.tag.latest": "Latest",
"model.group.recommended": "Recommended",
"model.group.favorites": "Favorites",
"model.group.collapse": "Collapse {{group}}",
"model.group.expand": "Expand {{group}}",
"model.favorite.add": "Add to favorites",
"model.favorite.remove": "Remove from favorites",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -178,6 +178,8 @@ export const dict = {
"model.tag.latest": "Último",
"model.group.recommended": "Recomendado",
"model.group.favorites": "Favoritos",
"model.group.collapse": "Contraer {{group}}",
"model.group.expand": "Expandir {{group}}",
"model.favorite.add": "Añadir a favoritos",
"model.favorite.remove": "Eliminar de favoritos",
+2
View File
@@ -179,6 +179,8 @@ export const dict = {
"model.tag.latest": "Dernier",
"model.group.recommended": "Recommandé",
"model.group.favorites": "Favoris",
"model.group.collapse": "Réduire {{group}}",
"model.group.expand": "Développer {{group}}",
"model.favorite.add": "Ajouter aux favoris",
"model.favorite.remove": "Retirer des favoris",
+2
View File
@@ -176,6 +176,8 @@ export const dict = {
"model.tag.latest": "最新",
"model.group.recommended": "推奨",
"model.group.favorites": "お気に入り",
"model.group.collapse": "{{group}} を折りたたむ",
"model.group.expand": "{{group}} を展開",
"model.favorite.add": "お気に入りに追加",
"model.favorite.remove": "お気に入りから削除",
+2
View File
@@ -180,6 +180,8 @@ export const dict = {
"model.tag.latest": "최신",
"model.group.recommended": "추천",
"model.group.favorites": "즐겨찾기",
"model.group.collapse": "{{group}} 접기",
"model.group.expand": "{{group}} 펼치기",
"model.favorite.add": "즐겨찾기에 추가",
"model.favorite.remove": "즐겨찾기에서 제거",
+2
View File
@@ -178,6 +178,8 @@ export const dict = {
"model.tag.latest": "Nieuwste",
"model.group.recommended": "Aanbevolen",
"model.group.favorites": "Favorieten",
"model.group.collapse": "{{group}} inklappen",
"model.group.expand": "{{group}} uitklappen",
"model.favorite.add": "Toevoegen aan favorieten",
"model.favorite.remove": "Verwijderen uit favorieten",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -180,6 +180,8 @@ export const dict = {
"model.tag.latest": "Nyeste",
"model.group.recommended": "Anbefalt",
"model.group.favorites": "Favoritter",
"model.group.collapse": "Skjul {{group}}",
"model.group.expand": "Vis {{group}}",
"model.favorite.add": "Legg til i favoritter",
"model.favorite.remove": "Fjern fra favoritter",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "Najnowszy",
"model.group.recommended": "Zalecane",
"model.group.favorites": "Ulubione",
"model.group.collapse": "Zwiń {{group}}",
"model.group.expand": "Rozwiń {{group}}",
"model.favorite.add": "Dodaj do ulubionych",
"model.favorite.remove": "Usuń z ulubionych",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "Последняя",
"model.group.recommended": "Рекомендуемые",
"model.group.favorites": "Избранное",
"model.group.collapse": "Свернуть {{group}}",
"model.group.expand": "Развернуть {{group}}",
"model.favorite.add": "Добавить в избранное",
"model.favorite.remove": "Удалить из избранного",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "ล่าสุด",
"model.group.recommended": "แนะนำ",
"model.group.favorites": "รายการโปรด",
"model.group.collapse": "ย่อ {{group}}",
"model.group.expand": "ขยาย {{group}}",
"model.favorite.add": "เพิ่มในรายการโปรด",
"model.favorite.remove": "ลบออกจากรายการโปรด",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "En yeni",
"model.group.recommended": "Önerilen",
"model.group.favorites": "Favoriler",
"model.group.collapse": "{{group}} grubunu daralt",
"model.group.expand": "{{group}} grubunu genişlet",
"model.favorite.add": "Favorilere ekle",
"model.favorite.remove": "Favorilerden çıkar",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -178,6 +178,8 @@ export const dict = {
"model.tag.latest": "Остання",
"model.group.recommended": "Рекомендовані",
"model.group.favorites": "Обране",
"model.group.collapse": "Згорнути {{group}}",
"model.group.expand": "Розгорнути {{group}}",
"model.favorite.add": "Додати до обраного",
"model.favorite.remove": "Видалити з обраного",
"model.provider.anthropic": "Anthropic",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "最新",
"model.group.recommended": "推荐",
"model.group.favorites": "收藏夹",
"model.group.collapse": "折叠 {{group}}",
"model.group.expand": "展开 {{group}}",
"model.favorite.add": "添加到收藏夹",
"model.favorite.remove": "从收藏夹中移除",
+2
View File
@@ -177,6 +177,8 @@ export const dict = {
"model.tag.latest": "最新",
"model.group.recommended": "推薦",
"model.group.favorites": "我的最愛",
"model.group.collapse": "折疊 {{group}}",
"model.group.expand": "展開 {{group}}",
"model.favorite.add": "加入我的最愛",
"model.favorite.remove": "從我的最愛中移除",
@@ -253,6 +253,10 @@
}
.model-selector-group-label {
display: flex;
align-items: center;
gap: 4px;
width: 100%;
padding: 4px 12px;
font-size: var(--font-size-x-small, 11px);
font-weight: var(--font-weight-medium, 500);
@@ -260,6 +264,20 @@
letter-spacing: var(--letter-spacing-normal, 0);
color: var(--text-weak, var(--vscode-descriptionForeground));
user-select: none;
background: transparent;
border: none;
text-align: left;
cursor: pointer;
font-family: inherit;
}
.model-selector-group-label:hover {
color: var(--text-base, var(--vscode-foreground));
}
.model-selector-group-label:focus-visible {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: -1px;
}
.model-selector-group-label:not(:first-child) {
@@ -268,6 +286,15 @@
padding-top: 8px;
}
.model-selector-group-chevron {
flex-shrink: 0;
transition: transform 0.12s ease;
}
.model-selector-group-chevron--collapsed {
transform: rotate(-90deg);
}
.model-selector-item {
display: flex;
align-items: center;