mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: show model ID in API configuration dropdown (#7423)
This commit is contained in:
parent
46b6fdd21e
commit
faf5734bdc
4 changed files with 113 additions and 55 deletions
|
|
@ -81,6 +81,7 @@ export const providerSettingsEntrySchema = z.object({
|
|||
id: z.string(),
|
||||
name: z.string(),
|
||||
apiProvider: providerNamesSchema.optional(),
|
||||
modelId: z.string().optional(),
|
||||
})
|
||||
|
||||
export type ProviderSettingsEntry = z.infer<typeof providerSettingsEntrySchema>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
isSecretStateKey,
|
||||
ProviderSettingsEntry,
|
||||
DEFAULT_CONSECUTIVE_MISTAKE_LIMIT,
|
||||
getModelId,
|
||||
} from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
|
|
@ -274,6 +275,20 @@ export class ProviderSettingsManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean model ID by removing prefix before "/"
|
||||
*/
|
||||
private cleanModelId(modelId: string | undefined): string | undefined {
|
||||
if (!modelId) return undefined
|
||||
|
||||
// Check for "/" and take the part after it
|
||||
if (modelId.includes("/")) {
|
||||
return modelId.split("/").pop()
|
||||
}
|
||||
|
||||
return modelId
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available configs with metadata.
|
||||
*/
|
||||
|
|
@ -286,6 +301,7 @@ export class ProviderSettingsManager {
|
|||
name,
|
||||
id: apiConfig.id || "",
|
||||
apiProvider: apiConfig.apiProvider,
|
||||
modelId: this.cleanModelId(getModelId(apiConfig)),
|
||||
}))
|
||||
})
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface ApiConfigSelectorProps {
|
|||
title: string
|
||||
onChange: (value: string) => void
|
||||
triggerClassName?: string
|
||||
listApiConfigMeta: Array<{ id: string; name: string }>
|
||||
listApiConfigMeta: Array<{ id: string; name: string; modelId?: string }>
|
||||
pinnedApiConfigs?: Record<string, boolean>
|
||||
togglePinnedApiConfig: (id: string) => void
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ export const ApiConfigSelector = ({
|
|||
}, [])
|
||||
|
||||
const renderConfigItem = useCallback(
|
||||
(config: { id: string; name: string }, isPinned: boolean) => {
|
||||
(config: { id: string; name: string; modelId?: string }, isPinned: boolean) => {
|
||||
const isCurrentConfig = config.id === value
|
||||
|
||||
return (
|
||||
|
|
@ -100,7 +100,19 @@ export const ApiConfigSelector = ({
|
|||
isCurrentConfig &&
|
||||
"bg-vscode-list-activeSelectionBackground text-vscode-list-activeSelectionForeground",
|
||||
)}>
|
||||
<span className="flex-1 truncate">{config.name}</span>
|
||||
<div className="flex-1 min-w-0 flex items-center gap-1 overflow-hidden">
|
||||
<span className="flex-shrink-0">{config.name}</span>
|
||||
{config.modelId && (
|
||||
<>
|
||||
<span className="text-vscode-descriptionForeground opacity-70 flex-shrink-0">·</span>
|
||||
<span
|
||||
className="text-vscode-descriptionForeground opacity-70 min-w-0 overflow-hidden"
|
||||
style={{ direction: "rtl", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{config.modelId}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{isCurrentConfig && (
|
||||
<div className="size-5 p-1 flex items-center justify-center">
|
||||
|
|
|
|||
|
|
@ -21,6 +21,21 @@ vi.mock("@/components/ui/hooks/useRooPortal", () => ({
|
|||
useRooPortal: () => document.body,
|
||||
}))
|
||||
|
||||
// Mock the ExtensionStateContext
|
||||
vi.mock("@/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
apiConfiguration: {
|
||||
apiProvider: "anthropic",
|
||||
apiModelId: "claude-3-opus-20240229",
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock the getModelId function from @roo-code/types
|
||||
vi.mock("@roo-code/types", () => ({
|
||||
getModelId: (config: any) => config?.apiModelId || undefined,
|
||||
}))
|
||||
|
||||
// Mock Popover components to be testable
|
||||
vi.mock("@/components/ui", () => ({
|
||||
Popover: ({ children, open }: any) => (
|
||||
|
|
@ -52,9 +67,9 @@ describe("ApiConfigSelector", () => {
|
|||
title: "API Config",
|
||||
onChange: mockOnChange,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
],
|
||||
pinnedApiConfigs: { config1: true },
|
||||
togglePinnedApiConfig: mockTogglePinnedApiConfig,
|
||||
|
|
@ -120,13 +135,13 @@ describe("ApiConfigSelector", () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config4", name: "Config 4" },
|
||||
{ id: "config5", name: "Config 5" },
|
||||
{ id: "config6", name: "Config 6" },
|
||||
{ id: "config7", name: "Config 7" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
|
||||
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
|
||||
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
|
||||
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
|
||||
],
|
||||
}
|
||||
render(<ApiConfigSelector {...props} />)
|
||||
|
|
@ -154,13 +169,13 @@ describe("ApiConfigSelector", () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config4", name: "Config 4" },
|
||||
{ id: "config5", name: "Config 5" },
|
||||
{ id: "config6", name: "Config 6" },
|
||||
{ id: "config7", name: "Config 7" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
|
||||
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
|
||||
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
|
||||
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
|
||||
],
|
||||
}
|
||||
render(<ApiConfigSelector {...props} />)
|
||||
|
|
@ -184,13 +199,13 @@ describe("ApiConfigSelector", () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config4", name: "Config 4" },
|
||||
{ id: "config5", name: "Config 5" },
|
||||
{ id: "config6", name: "Config 6" },
|
||||
{ id: "config7", name: "Config 7" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
|
||||
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
|
||||
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
|
||||
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
|
||||
],
|
||||
}
|
||||
render(<ApiConfigSelector {...props} />)
|
||||
|
|
@ -210,13 +225,13 @@ describe("ApiConfigSelector", () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config4", name: "Config 4" },
|
||||
{ id: "config5", name: "Config 5" },
|
||||
{ id: "config6", name: "Config 6" },
|
||||
{ id: "config7", name: "Config 7" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
|
||||
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
|
||||
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
|
||||
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
|
||||
],
|
||||
}
|
||||
render(<ApiConfigSelector {...props} />)
|
||||
|
|
@ -263,7 +278,8 @@ describe("ApiConfigSelector", () => {
|
|||
const config1Elements = screen.getAllByText("Config 1")
|
||||
// Find the one that's in the dropdown content (not the trigger)
|
||||
const configInDropdown = config1Elements.find((el) => el.closest('[data-testid="popover-content"]'))
|
||||
const selectedConfigRow = configInDropdown?.closest("div")
|
||||
// Navigate up to find the parent row that contains both the text and the check icon
|
||||
const selectedConfigRow = configInDropdown?.closest(".group")
|
||||
const checkIcon = selectedConfigRow?.querySelector(".codicon-check")
|
||||
expect(checkIcon).toBeInTheDocument()
|
||||
})
|
||||
|
|
@ -280,13 +296,24 @@ describe("ApiConfigSelector", () => {
|
|||
fireEvent.click(trigger)
|
||||
|
||||
const content = screen.getByTestId("popover-content")
|
||||
const configTexts = content.querySelectorAll(".truncate")
|
||||
// Get all config items by looking for the group class
|
||||
const configRows = content.querySelectorAll(".group")
|
||||
|
||||
// Extract the config names from each row
|
||||
const configNames: string[] = []
|
||||
configRows.forEach((row) => {
|
||||
// Find the first span that's flex-shrink-0 (the profile name)
|
||||
const nameElement = row.querySelector(".flex-1 span.flex-shrink-0")
|
||||
if (nameElement?.textContent) {
|
||||
configNames.push(nameElement.textContent)
|
||||
}
|
||||
})
|
||||
|
||||
// Pinned configs should appear first
|
||||
expect(configTexts[0]).toHaveTextContent("Config 1")
|
||||
expect(configTexts[1]).toHaveTextContent("Config 3")
|
||||
expect(configNames[0]).toBe("Config 1")
|
||||
expect(configNames[1]).toBe("Config 3")
|
||||
// Unpinned config should appear after separator
|
||||
expect(configTexts[2]).toHaveTextContent("Config 2")
|
||||
expect(configNames[2]).toBe("Config 2")
|
||||
})
|
||||
|
||||
test("toggles pin status when pin button is clicked", () => {
|
||||
|
|
@ -296,8 +323,10 @@ describe("ApiConfigSelector", () => {
|
|||
fireEvent.click(trigger)
|
||||
|
||||
// Find the pin button for Config 2 (unpinned)
|
||||
const config2Row = screen.getByText("Config 2").closest("div")
|
||||
const pinButton = config2Row?.querySelector("button")
|
||||
const config2Row = screen.getByText("Config 2").closest(".group")
|
||||
// Find the button with the pin icon (it's the second button, first is the row itself)
|
||||
const buttons = config2Row?.querySelectorAll("button")
|
||||
const pinButton = Array.from(buttons || []).find((btn) => btn.querySelector(".codicon-pin"))
|
||||
|
||||
if (pinButton) {
|
||||
fireEvent.click(pinButton)
|
||||
|
|
@ -332,13 +361,13 @@ describe("ApiConfigSelector", () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config4", name: "Config 4" },
|
||||
{ id: "config5", name: "Config 5" },
|
||||
{ id: "config6", name: "Config 6" },
|
||||
{ id: "config7", name: "Config 7" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
|
||||
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
|
||||
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
|
||||
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
|
||||
],
|
||||
}
|
||||
render(<ApiConfigSelector {...props} />)
|
||||
|
|
@ -389,13 +418,13 @@ describe("ApiConfigSelector", () => {
|
|||
const props = {
|
||||
...defaultProps,
|
||||
listApiConfigMeta: [
|
||||
{ id: "config1", name: "Config 1" },
|
||||
{ id: "config2", name: "Config 2" },
|
||||
{ id: "config3", name: "Config 3" },
|
||||
{ id: "config4", name: "Config 4" },
|
||||
{ id: "config5", name: "Config 5" },
|
||||
{ id: "config6", name: "Config 6" },
|
||||
{ id: "config7", name: "Config 7" },
|
||||
{ id: "config1", name: "Config 1", modelId: "claude-3-opus-20240229" },
|
||||
{ id: "config2", name: "Config 2", modelId: "gpt-4" },
|
||||
{ id: "config3", name: "Config 3", modelId: "claude-3-sonnet-20240229" },
|
||||
{ id: "config4", name: "Config 4", modelId: "gpt-3.5-turbo" },
|
||||
{ id: "config5", name: "Config 5", modelId: "claude-3-haiku-20240307" },
|
||||
{ id: "config6", name: "Config 6", modelId: "gpt-4-turbo" },
|
||||
{ id: "config7", name: "Config 7", modelId: "claude-2.1" },
|
||||
],
|
||||
}
|
||||
render(<ApiConfigSelector {...props} />)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue