Skip to content

Commit 06055c3

Browse files
feat: add pinning functionality for API configurations
Added the ability to pin/unpin API configurations, enabling prioritized appearance in dropdown menus. - modified state management to support storing pinned configurations persistently. - Updated UI components to display and toggle pin states - Adjusted backend handling for syncing pinned configuration states across sessions.
1 parent 0fd0800 commit 06055c3

File tree

8 files changed

+156
-12
lines changed

8 files changed

+156
-12
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,25 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
16751675
await this.updateGlobalState("maxReadFileLine", message.value)
16761676
await this.postStateToWebview()
16771677
break
1678+
case "toggleApiConfigPin":
1679+
if (message.text) {
1680+
const currentPinned = ((await this.getGlobalState("pinnedApiConfigs")) || {}) as Record<
1681+
string,
1682+
boolean
1683+
>
1684+
const updatedPinned: Record<string, boolean> = { ...currentPinned }
1685+
1686+
// Toggle the pinned state
1687+
if (currentPinned[message.text]) {
1688+
delete updatedPinned[message.text]
1689+
} else {
1690+
updatedPinned[message.text] = true
1691+
}
1692+
1693+
await this.updateGlobalState("pinnedApiConfigs", updatedPinned)
1694+
await this.postStateToWebview()
1695+
}
1696+
break
16781697
case "enhancementApiConfigId":
16791698
await this.updateGlobalState("enhancementApiConfigId", message.text)
16801699
await this.postStateToWebview()
@@ -2610,6 +2629,9 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
26102629
language,
26112630
renderContext: this.renderContext,
26122631
maxReadFileLine: maxReadFileLine ?? 500,
2632+
pinnedApiConfigs:
2633+
((await this.getGlobalState("pinnedApiConfigs")) as Record<string, boolean>) ??
2634+
({} as Record<string, boolean>),
26132635
}
26142636
}
26152637

src/exports/roo-code.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ export type GlobalStateKey =
259259
| "language"
260260
| "maxReadFileLine"
261261
| "fakeAi"
262+
| "pinnedApiConfigs" // Record of API config names that should be pinned to the top of the API provides dropdown
262263

263264
export type ConfigurationKey = GlobalStateKey | SecretKey
264265

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface ExtensionMessage {
5858
| "ttsStop"
5959
| "maxReadFileLine"
6060
| "fileSearchResults"
61+
| "toggleApiConfigPin"
6162
text?: string
6263
action?:
6364
| "chatButtonClicked"
@@ -168,6 +169,7 @@ export interface ExtensionState {
168169
machineId?: string
169170
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
170171
renderContext: "sidebar" | "editor"
172+
pinnedApiConfigs?: Record<string, boolean> // Map of API config names to pinned state
171173
maxReadFileLine: number // Maximum number of lines to read from a file before truncating
172174
}
173175

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export interface WebviewMessage {
117117
| "language"
118118
| "maxReadFileLine"
119119
| "searchFiles"
120+
| "toggleApiConfigPin"
120121
text?: string
121122
disabled?: boolean
122123
askResponse?: ClineAskResponse

src/shared/globalState.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export const GLOBAL_STATE_KEYS = [
126126
"maxWorkspaceFiles",
127127
"maxReadFileLine",
128128
"fakeAi",
129+
"pinnedApiConfigs",
129130
] as const
130131

131132
export const PASS_THROUGH_STATE_KEYS = ["taskHistory"] as const

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { SelectDropdown, DropdownOptionType, Button } from "@/components/ui"
2424
import Thumbnails from "../common/Thumbnails"
2525
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
2626
import ContextMenu from "./ContextMenu"
27-
import { VolumeX } from "lucide-react"
27+
import { VolumeX, Pin, Check } from "lucide-react"
2828
import { IconButton } from "./IconButton"
2929
import { cn } from "@/lib/utils"
3030

@@ -64,7 +64,16 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
6464
ref,
6565
) => {
6666
const { t } = useAppTranslation()
67-
const { filePaths, openedTabs, currentApiConfigName, listApiConfigMeta, customModes, cwd } = useExtensionState()
67+
const {
68+
filePaths,
69+
openedTabs,
70+
currentApiConfigName,
71+
listApiConfigMeta,
72+
customModes,
73+
cwd,
74+
pinnedApiConfigs,
75+
togglePinnedApiConfig,
76+
} = useExtensionState()
6877
const [gitCommits, setGitCommits] = useState<any[]>([])
6978
const [showDropdown, setShowDropdown] = useState(false)
7079
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>([])
@@ -959,11 +968,37 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
959968
disabled={textAreaDisabled}
960969
title={t("chat:selectApiConfig")}
961970
options={[
962-
...(listApiConfigMeta || []).map((config) => ({
963-
value: config.name,
964-
label: config.name,
965-
type: DropdownOptionType.ITEM,
966-
})),
971+
// Pinned items first
972+
...(listApiConfigMeta || [])
973+
.filter((config) => pinnedApiConfigs && pinnedApiConfigs[config.name])
974+
.map((config) => ({
975+
value: config.name,
976+
label: config.name,
977+
type: DropdownOptionType.ITEM,
978+
pinned: true,
979+
})),
980+
// If we have pinned items and unpinned items, add a separator
981+
...(pinnedApiConfigs &&
982+
Object.keys(pinnedApiConfigs).length > 0 &&
983+
(listApiConfigMeta || []).some((config) => !pinnedApiConfigs[config.name])
984+
? [
985+
{
986+
value: "sep-pinned",
987+
label: t("chat:separator"),
988+
type: DropdownOptionType.SEPARATOR,
989+
},
990+
]
991+
: []),
992+
// Unpinned items sorted alphabetically
993+
...(listApiConfigMeta || [])
994+
.filter((config) => !pinnedApiConfigs || !pinnedApiConfigs[config.name])
995+
.map((config) => ({
996+
value: config.name,
997+
label: config.name,
998+
type: DropdownOptionType.ITEM,
999+
pinned: false,
1000+
}))
1001+
.sort((a, b) => a.label.localeCompare(b.label)),
9671002
{
9681003
value: "sep-2",
9691004
label: t("chat:separator"),
@@ -978,6 +1013,38 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
9781013
onChange={(value) => vscode.postMessage({ type: "loadApiConfiguration", text: value })}
9791014
contentClassName="max-h-[300px] overflow-y-auto"
9801015
triggerClassName="w-full text-ellipsis overflow-hidden"
1016+
renderItem={(option) => {
1017+
if (option.type !== DropdownOptionType.ITEM) {
1018+
return option.label
1019+
}
1020+
1021+
return (
1022+
<div className="flex items-center justify-between w-full">
1023+
<span>{option.label}</span>
1024+
<div
1025+
className={cn(
1026+
"ml-2 p-1 rounded-sm cursor-pointer hover:bg-[rgba(255,255,255,0.1)]",
1027+
option.pinned
1028+
? "text-vscode-focusBorder"
1029+
: "text-vscode-descriptionForeground opacity-50 hover:opacity-100",
1030+
)}
1031+
onClick={(e) => {
1032+
e.stopPropagation()
1033+
togglePinnedApiConfig(option.value)
1034+
vscode.postMessage({
1035+
type: "toggleApiConfigPin",
1036+
text: option.value,
1037+
})
1038+
}}
1039+
title={option.pinned ? t("chat:unpin") : t("chat:pin")}>
1040+
<Pin className="size-3" />
1041+
</div>
1042+
{option.value === currentApiConfigName && (
1043+
<Check className="size-4 p-0.5 ml-1" />
1044+
)}
1045+
</div>
1046+
)
1047+
}}
9811048
/>
9821049
</div>
9831050
</div>

webview-ui/src/components/ui/select-dropdown.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface DropdownOption {
2525
label: string
2626
disabled?: boolean
2727
type?: DropdownOptionType
28+
pinned?: boolean
2829
}
2930

3031
export interface SelectDropdownProps {
@@ -39,6 +40,7 @@ export interface SelectDropdownProps {
3940
align?: "start" | "center" | "end"
4041
placeholder?: string
4142
shortcutText?: string
43+
renderItem?: (option: DropdownOption) => React.ReactNode
4244
}
4345

4446
export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownMenuTrigger>, SelectDropdownProps>(
@@ -55,6 +57,7 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
5557
align = "start",
5658
placeholder = "",
5759
shortcutText = "",
60+
renderItem,
5861
},
5962
ref,
6063
) => {
@@ -121,11 +124,17 @@ export const SelectDropdown = React.forwardRef<React.ElementRef<typeof DropdownM
121124
key={`item-${option.value}`}
122125
disabled={option.disabled}
123126
onClick={() => handleSelect(option)}>
124-
{option.label}
125-
{option.value === value && (
126-
<DropdownMenuShortcut>
127-
<Check className="size-4 p-0.5" />
128-
</DropdownMenuShortcut>
127+
{renderItem ? (
128+
renderItem(option)
129+
) : (
130+
<>
131+
{option.label}
132+
{option.value === value && (
133+
<DropdownMenuShortcut>
134+
<Check className="size-4 p-0.5" />
135+
</DropdownMenuShortcut>
136+
)}
137+
</>
129138
)}
130139
</DropdownMenuItem>
131140
)

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export interface ExtensionStateContextType extends ExtensionState {
8181
maxReadFileLine: number
8282
setMaxReadFileLine: (value: number) => void
8383
machineId?: string
84+
pinnedApiConfigs?: Record<string, boolean>
85+
setPinnedApiConfigs: (value: Record<string, boolean>) => void
86+
togglePinnedApiConfig: (configName: string) => void
8487
}
8588

8689
export const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
@@ -155,6 +158,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
155158
showRooIgnoredFiles: true, // Default to showing .rooignore'd files with lock symbol (current behavior).
156159
renderContext: "sidebar",
157160
maxReadFileLine: 500, // Default max read file line limit
161+
pinnedApiConfigs: {}, // Empty object for pinned API configs
158162
})
159163

160164
const [didHydrateState, setDidHydrateState] = useState(false)
@@ -221,6 +225,27 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
221225
setListApiConfigMeta(message.listApiConfig ?? [])
222226
break
223227
}
228+
case "toggleApiConfigPin": {
229+
if (message.text) {
230+
setState((prevState) => {
231+
const currentPinned = prevState.pinnedApiConfigs || {}
232+
const newPinned = { ...currentPinned }
233+
234+
// Toggle the pinned state
235+
if (currentPinned[message.text!]) {
236+
delete newPinned[message.text!]
237+
} else {
238+
newPinned[message.text!] = true
239+
}
240+
241+
return {
242+
...prevState,
243+
pinnedApiConfigs: newPinned,
244+
}
245+
})
246+
}
247+
break
248+
}
224249
}
225250
},
226251
[setListApiConfigMeta],
@@ -307,6 +332,22 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
307332
setShowRooIgnoredFiles: (value) => setState((prevState) => ({ ...prevState, showRooIgnoredFiles: value })),
308333
setRemoteBrowserEnabled: (value) => setState((prevState) => ({ ...prevState, remoteBrowserEnabled: value })),
309334
setMaxReadFileLine: (value) => setState((prevState) => ({ ...prevState, maxReadFileLine: value })),
335+
setPinnedApiConfigs: (value) => setState((prevState) => ({ ...prevState, pinnedApiConfigs: value })),
336+
togglePinnedApiConfig: (configName) =>
337+
setState((prevState) => {
338+
const currentPinned = prevState.pinnedApiConfigs || {}
339+
const newPinned = {
340+
...currentPinned,
341+
[configName]: !currentPinned[configName],
342+
}
343+
344+
// If the config is now unpinned, remove it from the object
345+
if (!newPinned[configName]) {
346+
delete newPinned[configName]
347+
}
348+
349+
return { ...prevState, pinnedApiConfigs: newPinned }
350+
}),
310351
}
311352

312353
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>

0 commit comments

Comments
 (0)