Compare commits

...

2 Commits

Author SHA1 Message Date
Arafatkatze 1db152b00f Adding minor fix 2025-11-30 15:40:34 -05:00
nickbaumann98 1dda3f6ad3 feat: add tabbed model picker with Recommended and Free tabs 2025-11-30 13:46:21 -06:00
2 changed files with 97 additions and 32 deletions
@@ -11,7 +11,7 @@ export interface FeaturedModelCardProps {
const CardContainer = styled.div<{ isSelected: boolean }>`
padding: 2px 4px;
margin-bottom: 2px;
margin-bottom: 6px;
border-radius: 3px;
border: 1px solid var(--vscode-textLink-foreground);
opacity: ${(props) => (props.isSelected ? 1 : 0.6)};
@@ -44,31 +44,44 @@ export interface OpenRouterModelPickerProps {
currentMode: Mode
}
// Featured models for Cline provider
const featuredModels = [
// Featured models for Cline provider organized by tabs
const recommendedModels = [
{
id: "anthropic/claude-sonnet-4.5",
description: "Recommended for agentic coding in Cline",
label: "Best",
description: "Best balance of speed, cost, and quality",
label: "BEST",
},
{
id: "anthropic/claude-opus-4.5",
description: "SOTA performance on coding at 3x lower cost than Opus 4.1",
label: "New",
description: "State-of-the-art for complex coding",
label: "NEW",
},
{
id: "openai/gpt-5.1",
description: "OpenAI's latest with strong coding abilities",
label: "HOT",
},
{
id: "google/gemini-3-pro-preview",
description: "Google's latest reasoning model with 1M context window",
label: "Trending",
},
{
id: "x-ai/grok-code-fast-1",
description: "Advanced model with 262K context for complex coding",
label: "Free",
isFree: true,
description: "1M context window for large codebases",
label: "1M CTX",
},
]
const FREE_CLINE_MODELS = featuredModels.filter((m) => m.isFree)
const freeModels = [
{
id: "x-ai/grok-code-fast-1",
description: "Fast inference with strong coding performance",
label: "FAST",
},
{
id: "minimax/minimax-m2",
description: "Open source model with solid performance",
label: "OSS",
},
]
const FREE_CLINE_MODELS = freeModels.map((m) => m.id)
const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup, currentMode }) => {
const { handleModeFieldChange, handleModeFieldsChange } = useApiConfigurationHandlers()
@@ -77,6 +90,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
const [searchTerm, setSearchTerm] = useState(modeFields.openRouterModelId || openRouterDefaultModelId)
const [isDropdownVisible, setIsDropdownVisible] = useState(false)
const [selectedIndex, setSelectedIndex] = useState(-1)
const [activeTab, setActiveTab] = useState<"recommended" | "free">("recommended")
const dropdownRef = useRef<HTMLDivElement>(null)
const itemRefs = useRef<(HTMLDivElement | null)[]>([])
const dropdownListRef = useRef<HTMLDivElement>(null)
@@ -103,7 +117,7 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
const selected = normalizeApiConfiguration(apiConfiguration, currentMode)
const isCline = selected.selectedProvider === "cline"
// Makes sure "Free" featured models have $0 pricing for Cline provider
if (isCline && FREE_CLINE_MODELS.some((fm) => fm.id === selected.selectedModelId)) {
if (isCline && FREE_CLINE_MODELS.includes(selected.selectedModelId)) {
return {
...selected,
selectedModelInfo: {
@@ -293,21 +307,49 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup,
</label>
{modeFields.apiProvider === "cline" && (
<div style={{ marginBottom: "6px", marginTop: 4 }}>
{featuredModels.map((model) => (
<FeaturedModelCard
description={model.description}
isSelected={selectedModelId === model.id}
key={model.id}
label={model.label}
modelId={model.id}
onClick={() => {
handleModelChange(model.id)
setIsDropdownVisible(false)
}}
/>
))}
</div>
<>
{/* Tabs */}
<TabsContainer style={{ marginTop: 4 }}>
<Tab active={activeTab === "recommended"} onClick={() => setActiveTab("recommended")}>
Recommended
</Tab>
<Tab active={activeTab === "free"} onClick={() => setActiveTab("free")}>
Free
</Tab>
</TabsContainer>
{/* Model Cards */}
<div style={{ marginBottom: "6px" }}>
{activeTab === "recommended" &&
recommendedModels.map((model) => (
<FeaturedModelCard
description={model.description}
isSelected={selectedModelId === model.id}
key={model.id}
label={model.label}
modelId={model.id}
onClick={() => {
handleModelChange(model.id)
setIsDropdownVisible(false)
}}
/>
))}
{activeTab === "free" &&
freeModels.map((model) => (
<FeaturedModelCard
description={model.description}
isSelected={selectedModelId === model.id}
key={model.id}
label={model.label}
modelId={model.id}
onClick={() => {
handleModelChange(model.id)
setIsDropdownVisible(false)
}}
/>
))}
</div>
</>
)}
<DropdownWrapper ref={dropdownRef}>
@@ -503,3 +545,26 @@ const DropdownItem = styled.div<{ isSelected: boolean }>`
background-color: var(--vscode-list-activeSelectionBackground);
}
`
// Tabs
const TabsContainer = styled.div`
display: flex;
gap: 0;
margin-bottom: 12px;
border-bottom: 1px solid var(--vscode-panel-border);
`
const Tab = styled.div<{ active: boolean }>`
padding: 8px 16px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
color: ${({ active }) => (active ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")};
border-bottom: 2px solid ${({ active }) => (active ? "var(--vscode-textLink-foreground)" : "transparent")};
transition: all 0.15s ease;
&:hover {
color: var(--vscode-foreground);
}
`