From 6ce2ded75c452326d1eec840d728a1616c7fb1db Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 09:38:36 +0000 Subject: [PATCH] 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. --- .../collapsible-model-picker-sections.md | 5 + .../src/components/shared/ModelSelector.tsx | 224 +++++++++++------- .../kilo-vscode/webview-ui/src/i18n/ar.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/br.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/bs.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/da.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/de.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/en.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/es.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/fr.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/ja.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/ko.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/nl.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/no.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/pl.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/ru.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/th.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/tr.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/uk.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/zh.ts | 2 + .../kilo-vscode/webview-ui/src/i18n/zht.ts | 2 + .../webview-ui/src/styles/model-selector.css | 27 +++ 22 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 .changeset/collapsible-model-picker-sections.md diff --git a/.changeset/collapsible-model-picker-sections.md b/.changeset/collapsible-model-picker-sections.md new file mode 100644 index 00000000000..f2400375d39 --- /dev/null +++ b/.changeset/collapsible-model-picker-sections.md @@ -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. diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx index 1513cc22781..ec881eab24c 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx @@ -120,6 +120,9 @@ export const ModelSelectorBase: Component = (props) => { const [preActiveKey, setPreActiveKey] = createSignal(null) const [previewKey, setPreviewKey] = createSignal(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>(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 = (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(() => { - 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 = (props) => { - {(group) => ( - <> -
{group.label}
- - {(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 ( -
{ - 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) - }} - > -
- - {(() => { - const full = sanitizeName(model.name) - const sep = full.indexOf(": ") - if (sep < 0) return {full} - return ( - <> - {full.slice(0, sep)} - {full.slice(sep + 2)} - - ) - })()} - - - {language.t("model.tag.free")} - - - {model.providerName} - -
- - + + + {(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 ( +
{ + 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) }} > - - - - - - -
- ) - }} -
- - )} +
+ + {(() => { + const full = sanitizeName(model.name) + const sep = full.indexOf(": ") + if (sep < 0) return {full} + return ( + <> + {full.slice(0, sep)} + {full.slice(sep + 2)} + + ) + })()} + + + {language.t("model.tag.free")} + + + {model.providerName} + +
+ + + + + + +
+ ) + }} +
+ + + ) + }}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 8702427303b..52b33d7d2ad 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 2bca3fe4609..f4db148b31e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 71749e4a187..c648cba55a6 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 8270b442cc0..64b2a52dba5 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index d30614336e2..1b188a2542d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 784b17fe3ca..37ca13c028a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 91313af992a..76e06b2843e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index 6de0bac6c66..027d9a1bf65 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 94cfe25a70e..bdc9df8ef90 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -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": "お気に入りから削除", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 565416de95e..faa45ff66d0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -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": "즐겨찾기에서 제거", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index ae02bc59c97..741ece75018 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index d2382cb5169..a226323e696 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index bfa4457e4c0..1a4f3dc3686 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 54c6c54c231..c0c242109b0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 5e02ca3f5af..46f737d657b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 33dac0a65a1..81af68f4b34 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index bbc17472597..080ea5e9153 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -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", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index a68d0f2143f..8e9ca08fb38 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -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": "从收藏夹中移除", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index d37e75f54a1..e8a0888d300 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -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": "從我的最愛中移除", diff --git a/packages/kilo-vscode/webview-ui/src/styles/model-selector.css b/packages/kilo-vscode/webview-ui/src/styles/model-selector.css index 4504a29c2da..f463c9df409 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/model-selector.css +++ b/packages/kilo-vscode/webview-ui/src/styles/model-selector.css @@ -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;