refactor(vscode): replace thinking cycle button with popover dropdown

Replaces the single cycle button with a ThinkingSelector popover
component matching the ModeSwitcher pattern. Lists all available
variants plus a Default option to disable thinking.
This commit is contained in:
Igor Šćekić
2026-02-20 18:02:22 +01:00
parent 1416ebc174
commit 4f9f7c3483
4 changed files with 118 additions and 26 deletions
@@ -13,6 +13,7 @@ import { useLanguage } from "../../context/language"
import { useVSCode } from "../../context/vscode"
import { ModelSelector } from "./ModelSelector"
import { ModeSwitcher } from "./ModeSwitcher"
import { ThinkingSelector } from "./ThinkingSelector"
import { useFileMention } from "../../hooks/useFileMention"
import { useImageAttachments } from "../../hooks/useImageAttachments"
import { fileName, dirName, buildHighlightSegments } from "./prompt-input-utils"
@@ -314,18 +315,7 @@ export const PromptInput: Component = () => {
<div class="prompt-input-hint-selectors">
<ModeSwitcher />
<ModelSelector />
<Show when={session.variantList().length > 0}>
<Tooltip value={language.t("command.model.variant.cycle")} placement="top">
<Button
variant="ghost"
size="small"
onClick={() => session.cycleVariant()}
aria-label={language.t("command.model.variant.cycle")}
>
{session.currentVariant() ?? language.t("common.default")}
</Button>
</Tooltip>
</Show>
<ThinkingSelector />
</div>
<div class="prompt-input-hint-actions">
<Show
@@ -0,0 +1,75 @@
/**
* ThinkingSelector component
* Popover-based dropdown for choosing a thinking effort variant.
* Only rendered when the selected model supports reasoning variants.
*/
import { Component, createSignal, For, Show } from "solid-js"
import { Popover } from "@kilocode/kilo-ui/popover"
import { Button } from "@kilocode/kilo-ui/button"
import { useSession } from "../../context/session"
import { useLanguage } from "../../context/language"
export const ThinkingSelector: Component = () => {
const session = useSession()
const language = useLanguage()
const [open, setOpen] = createSignal(false)
const variants = () => session.variantList()
const current = () => session.currentVariant()
function pick(value: string | undefined) {
session.selectVariant(value)
setOpen(false)
}
const triggerLabel = () => {
const v = current()
return v ? v.charAt(0).toUpperCase() + v.slice(1) : language.t("common.default")
}
return (
<Show when={variants().length > 0}>
<Popover
placement="top-start"
open={open()}
onOpenChange={setOpen}
triggerAs={Button}
triggerProps={{ variant: "ghost", size: "small" }}
trigger={
<>
<span class="thinking-selector-trigger-label">{triggerLabel()}</span>
<svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor" style={{ "flex-shrink": "0" }}>
<path d="M8 4l4 5H4l4-5z" />
</svg>
</>
}
>
<div class="thinking-selector-list" role="listbox">
<div
class={`thinking-selector-item${!current() ? " selected" : ""}`}
role="option"
aria-selected={!current()}
onClick={() => pick(undefined)}
>
<span class="thinking-selector-item-name" style={{ "font-style": "italic" }}>
{language.t("common.default")}
</span>
</div>
<For each={variants()}>
{(v) => (
<div
class={`thinking-selector-item${current() === v ? " selected" : ""}`}
role="option"
aria-selected={current() === v}
onClick={() => pick(v)}
>
<span class="thinking-selector-item-name">{v.charAt(0).toUpperCase() + v.slice(1)}</span>
</div>
)}
</For>
</div>
</Popover>
</Show>
)
}
@@ -109,7 +109,7 @@ interface SessionContextValue {
// Thinking variant for the selected model
variantList: () => string[]
currentVariant: () => string | undefined
cycleVariant: () => void
selectVariant: (value: string | undefined) => void
// Actions
sendMessage: (text: string, providerID?: string, modelID?: string, files?: FileAttachment[]) => void
@@ -296,21 +296,12 @@ export const SessionProvider: ParentComponent = (props) => {
return undefined
}
const cycleVariant = () => {
const selectVariant = (value: string | undefined) => {
const sel = selected()
if (!sel) return
const list = variantList()
if (list.length === 0) return
const key = variantKey(sel)
const current = store.variantSelections[key]
const next = (() => {
if (!current || !list.includes(current)) return list[0]
const idx = list.indexOf(current)
if (idx === list.length - 1) return undefined
return list[idx + 1]
})()
setStore("variantSelections", key, next)
vscode.postMessage({ type: "persistVariant", key, value: next })
setStore("variantSelections", key, value)
vscode.postMessage({ type: "persistVariant", key, value })
}
// Load persisted variants from extension globalState
@@ -926,7 +917,7 @@ export const SessionProvider: ParentComponent = (props) => {
allParts,
variantList,
currentVariant,
cycleVariant,
selectVariant,
sendMessage,
abort,
compact,
@@ -576,6 +576,42 @@
letter-spacing: 0.03em;
}
/* ============================================
Thinking Selector (uses kilo-ui Popover)
============================================ */
.thinking-selector-trigger-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-transform: capitalize;
}
.thinking-selector-list {
min-width: 120px;
}
.thinking-selector-item {
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
}
.thinking-selector-item:hover {
background: var(--surface-interactive-hover, var(--vscode-list-hoverBackground));
}
.thinking-selector-item.selected {
background: var(
--surface-interactive-active,
var(--vscode-list-activeSelectionBackground, var(--vscode-list-hoverBackground))
);
}
.thinking-selector-item-name {
font-weight: 500;
}
/* ============================================
Mode Switcher (uses kilo-ui Popover)
============================================ */