@@ -100,6 +100,8 @@ interface ApiConfigSelectorProps {
100100 listApiConfigMeta : Array < { id : string ; name : string ; modelId ?: string } >
101101 pinnedApiConfigs ?: Record < string , boolean >
102102 togglePinnedApiConfig : ( id : string ) => void
103+ onSortModeChange ?: ( mode : SortMode ) => void
104+ onCustomOrderChange ?: ( order : Array < { id : string ; index : number ; pinned : boolean } > ) => void
103105}
104106
105107export const ApiConfigSelector = ( {
@@ -112,6 +114,8 @@ export const ApiConfigSelector = ({
112114 listApiConfigMeta,
113115 pinnedApiConfigs,
114116 togglePinnedApiConfig,
117+ onSortModeChange,
118+ onCustomOrderChange,
115119} : ApiConfigSelectorProps ) => {
116120 const { t } = useAppTranslation ( )
117121 const { apiConfigsCustomOrder : customOrder = [ ] } = useExtensionState ( )
@@ -146,6 +150,17 @@ export const ApiConfigSelector = ({
146150 return sorted
147151 } , [ listApiConfigMeta , sortMode , customOrder ] )
148152
153+ // Current visible order for callbacks when switching to custom
154+ const currentOrder = useMemo (
155+ ( ) =>
156+ sortedConfigs . map ( ( config , index ) => ( {
157+ id : config . id ,
158+ index,
159+ pinned : Boolean ( pinnedApiConfigs ?. [ config . id ] ) ,
160+ } ) ) ,
161+ [ sortedConfigs , pinnedApiConfigs ] ,
162+ )
163+
149164 // Filter configs based on search.
150165 const filteredConfigs = useMemo ( ( ) => {
151166 if ( ! searchValue ) {
@@ -178,6 +193,22 @@ export const ApiConfigSelector = ({
178193 setOpen ( false )
179194 } , [ ] )
180195
196+ const handleSortModeChange = useCallback (
197+ ( mode : SortMode ) => {
198+ setSortMode ( mode )
199+ onSortModeChange ?.( mode )
200+ if ( mode === "custom" ) {
201+ // Persist current visible order as custom order baseline
202+ vscode . postMessage ( {
203+ type : "setApiConfigsCustomOrder" ,
204+ values : { customOrder : currentOrder } ,
205+ } )
206+ onCustomOrderChange ?.( currentOrder )
207+ }
208+ } ,
209+ [ onSortModeChange , onCustomOrderChange , currentOrder ] ,
210+ )
211+
181212 return (
182213 < Popover open = { open } onOpenChange = { setOpen } data-testid = "api-config-selector-root" >
183214 < StandardTooltip content = { title } >
@@ -230,54 +261,54 @@ export const ApiConfigSelector = ({
230261 </ div >
231262 ) }
232263
233- { /* Config list - single scroll container with a11y attributes and sticky pinned structure */ }
234- { filteredConfigs . length === 0 && searchValue ? (
235- < div className = "py-2 px-3 text-sm text-vscode-foreground/70" > { t ( "common:ui.no_results" ) } </ div >
236- ) : (
237- < div
238- className = "max-h-[300px] overflow-y-auto"
239- role = "listbox"
240- aria-label = { t ( "prompts:apiConfiguration.select" ) } >
241- { /* Pinned configs - sticky header */ }
242- { pinnedConfigs . length > 0 && (
243- < div
244- className = { cn (
245- "sticky top-0 z-10 bg-vscode-dropdown-background py-1" ,
246- unpinnedConfigs . length > 0 && "border-b border-vscode-dropdown-foreground/10" ,
247- ) }
248- aria-label = "Pinned configurations" >
249- { pinnedConfigs . map ( ( config , index ) => (
250- < ConfigItem
251- key = { config . id }
252- config = { config }
253- isPinned
254- index = { index }
255- value = { value }
256- onSelect = { handleSelect }
257- togglePinnedApiConfig = { togglePinnedApiConfig }
258- />
259- ) ) }
260- </ div >
261- ) }
264+ { /* Config list - single scroll container with a11y attributes and sticky pinned structure */ }
265+ { filteredConfigs . length === 0 && searchValue ? (
266+ < div className = "py-2 px-3 text-sm text-vscode-foreground/70" > { t ( "common:ui.no_results" ) } </ div >
267+ ) : (
268+ < div
269+ className = "max-h-[300px] overflow-y-auto"
270+ role = "listbox"
271+ aria-label = { t ( "prompts:apiConfiguration.select" ) } >
272+ { /* Pinned configs - sticky header */ }
273+ { pinnedConfigs . length > 0 && (
274+ < div
275+ className = { cn (
276+ "sticky top-0 z-10 bg-vscode-dropdown-background py-1" ,
277+ unpinnedConfigs . length > 0 && "border-b border-vscode-dropdown-foreground/10" ,
278+ ) }
279+ aria-label = "Pinned configurations" >
280+ { pinnedConfigs . map ( ( config , index ) => (
281+ < ConfigItem
282+ key = { config . id }
283+ config = { config }
284+ isPinned
285+ index = { index }
286+ value = { value }
287+ onSelect = { handleSelect }
288+ togglePinnedApiConfig = { togglePinnedApiConfig }
289+ />
290+ ) ) }
291+ </ div >
292+ ) }
262293
263- { /* Unpinned configs */ }
264- { unpinnedConfigs . length > 0 && (
265- < div className = "py-1" aria-label = "All configurations" >
266- { unpinnedConfigs . map ( ( config , index ) => (
267- < ConfigItem
268- key = { config . id }
269- config = { config }
270- isPinned = { false }
271- index = { pinnedConfigs . length + index }
272- value = { value }
273- onSelect = { handleSelect }
274- togglePinnedApiConfig = { togglePinnedApiConfig }
275- />
276- ) ) }
277- </ div >
278- ) }
279- </ div >
280- ) }
294+ { /* Unpinned configs */ }
295+ { unpinnedConfigs . length > 0 && (
296+ < div className = "py-1" aria-label = "All configurations" >
297+ { unpinnedConfigs . map ( ( config , index ) => (
298+ < ConfigItem
299+ key = { config . id }
300+ config = { config }
301+ isPinned = { false }
302+ index = { pinnedConfigs . length + index }
303+ value = { value }
304+ onSelect = { handleSelect }
305+ togglePinnedApiConfig = { togglePinnedApiConfig }
306+ />
307+ ) ) }
308+ </ div >
309+ ) }
310+ </ div >
311+ ) }
281312
282313 { /* Bottom bar with controls */ }
283314 < div className = "flex flex-col border-t border-vscode-dropdown-border" >
@@ -295,7 +326,7 @@ export const ApiConfigSelector = ({
295326 size = "sm"
296327 aria-label = { `${ t ( "chat:apiConfigSelector.sort" ) } ${ mode === "alphabetical" ? t ( "chat:apiConfigSelector.alphabetical" ) : t ( "chat:apiConfigSelector.custom" ) } ` }
297328 aria-pressed = { sortMode === mode }
298- onClick = { ( ) => setSortMode ( mode ) }
329+ onClick = { ( ) => handleSortModeChange ( mode ) }
299330 className = { cn (
300331 "h-6 px-2 text-xs" ,
301332 sortMode === mode &&
0 commit comments