@@ -8,6 +8,16 @@ import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/comp
88import { useAppTranslation } from "@/i18n/TranslationContext"
99import { vscode } from "@/utils/vscode"
1010import { Button } from "@/components/ui"
11+ import {
12+ AlertDialog ,
13+ AlertDialogAction ,
14+ AlertDialogCancel ,
15+ AlertDialogContent ,
16+ AlertDialogDescription ,
17+ AlertDialogFooter ,
18+ AlertDialogHeader ,
19+ AlertDialogTitle ,
20+ } from "@/components/ui/alert-dialog"
1121
1222import { IconButton } from "./IconButton"
1323
@@ -37,6 +47,7 @@ export const ApiConfigSelector = ({
3747 const { t } = useAppTranslation ( )
3848 const [ open , setOpen ] = useState ( false )
3949 const [ searchValue , setSearchValue ] = useState ( "" )
50+ const [ showConfirmDialog , setShowConfirmDialog ] = useState ( false )
4051 const portalContainer = useRooPortal ( "roo-portal" )
4152
4253 // Create searchable items for fuzzy search.
@@ -86,6 +97,16 @@ export const ApiConfigSelector = ({
8697 setOpen ( false )
8798 } , [ ] )
8899
100+ const handleApplyToAllModes = useCallback ( ( ) => {
101+ setOpen ( false )
102+ setShowConfirmDialog ( true )
103+ } , [ ] )
104+
105+ const handleConfirmApplyToAllModes = useCallback ( ( ) => {
106+ vscode . postMessage ( { type : "applyConfigToAllModes" , configId : value } )
107+ setShowConfirmDialog ( false )
108+ } , [ value ] )
109+
89110 const renderConfigItem = useCallback (
90111 ( config : { id : string ; name : string ; modelId ?: string } , isPinned : boolean ) => {
91112 const isCurrentConfig = config . id === value
@@ -143,110 +164,139 @@ export const ApiConfigSelector = ({
143164 )
144165
145166 return (
146- < Popover open = { open } onOpenChange = { setOpen } data-testid = "api-config-selector-root" >
147- < StandardTooltip content = { title } >
148- < PopoverTrigger
149- disabled = { disabled }
150- data-testid = "dropdown-trigger"
151- className = { cn (
152- "w-full min-w-0 max-w-full inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs" ,
153- "bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground" ,
154- "transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset" ,
155- disabled
156- ? "opacity-50 cursor-not-allowed"
157- : "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer" ,
158- triggerClassName ,
159- ) } >
160- < ChevronUp
167+ < >
168+ < Popover open = { open } onOpenChange = { setOpen } data-testid = "api-config-selector-root" >
169+ < StandardTooltip content = { title } >
170+ < PopoverTrigger
171+ disabled = { disabled }
172+ data-testid = "dropdown-trigger"
161173 className = { cn (
162- "pointer-events-none opacity-80 flex-shrink-0 size-3 transition-transform duration-200" ,
163- open && "rotate-180" ,
174+ "w-full min-w-0 max-w-full inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs" ,
175+ "bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground" ,
176+ "transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset" ,
177+ disabled
178+ ? "opacity-50 cursor-not-allowed"
179+ : "opacity-90 hover:opacity-100 hover:bg-[rgba(255,255,255,0.03)] hover:border-[rgba(255,255,255,0.15)] cursor-pointer" ,
180+ triggerClassName ,
181+ ) } >
182+ < ChevronUp
183+ className = { cn (
184+ "pointer-events-none opacity-80 flex-shrink-0 size-3 transition-transform duration-200" ,
185+ open && "rotate-180" ,
186+ ) }
187+ />
188+ < span className = "truncate" > { displayName } </ span >
189+ </ PopoverTrigger >
190+ </ StandardTooltip >
191+ < PopoverContent
192+ align = "start"
193+ sideOffset = { 4 }
194+ container = { portalContainer }
195+ className = "p-0 overflow-hidden w-[300px]" >
196+ < div className = "flex flex-col w-full" >
197+ { /* Search input or info blurb */ }
198+ { listApiConfigMeta . length > 6 ? (
199+ < div className = "relative p-2 border-b border-vscode-dropdown-border" >
200+ < input
201+ aria-label = { t ( "common:ui.search_placeholder" ) }
202+ value = { searchValue }
203+ onChange = { ( e ) => setSearchValue ( e . target . value ) }
204+ placeholder = { t ( "common:ui.search_placeholder" ) }
205+ className = "w-full h-8 px-2 py-1 text-xs bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded focus:outline-0"
206+ autoFocus
207+ />
208+ { searchValue . length > 0 && (
209+ < div className = "absolute right-4 top-0 bottom-0 flex items-center justify-center" >
210+ < span
211+ className = "codicon codicon-close text-vscode-input-foreground opacity-50 hover:opacity-100 text-xs cursor-pointer"
212+ onClick = { ( ) => setSearchValue ( "" ) }
213+ />
214+ </ div >
215+ ) }
216+ </ div >
217+ ) : (
218+ < div className = "p-3 border-b border-vscode-dropdown-border" >
219+ < p className = "text-xs text-vscode-descriptionForeground m-0" >
220+ { t ( "prompts:apiConfiguration.select" ) }
221+ </ p >
222+ </ div >
164223 ) }
165- />
166- < span className = "truncate" > { displayName } </ span >
167- </ PopoverTrigger >
168- </ StandardTooltip >
169- < PopoverContent
170- align = "start"
171- sideOffset = { 4 }
172- container = { portalContainer }
173- className = "p-0 overflow-hidden w-[300px]" >
174- < div className = "flex flex-col w-full" >
175- { /* Search input or info blurb */ }
176- { listApiConfigMeta . length > 6 ? (
177- < div className = "relative p-2 border-b border-vscode-dropdown-border" >
178- < input
179- aria-label = { t ( "common:ui.search_placeholder" ) }
180- value = { searchValue }
181- onChange = { ( e ) => setSearchValue ( e . target . value ) }
182- placeholder = { t ( "common:ui.search_placeholder" ) }
183- className = "w-full h-8 px-2 py-1 text-xs bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded focus:outline-0"
184- autoFocus
185- />
186- { searchValue . length > 0 && (
187- < div className = "absolute right-4 top-0 bottom-0 flex items-center justify-center" >
188- < span
189- className = "codicon codicon-close text-vscode-input-foreground opacity-50 hover:opacity-100 text-xs cursor-pointer"
190- onClick = { ( ) => setSearchValue ( "" ) }
191- />
224+
225+ { /* Config list */ }
226+ < div className = "max-h-[300px] overflow-y-auto" >
227+ { filteredConfigs . length === 0 && searchValue ? (
228+ < div className = "py-2 px-3 text-sm text-vscode-foreground/70" >
229+ { t ( "common:ui.no_results" ) }
230+ </ div >
231+ ) : (
232+ < div className = "py-1" >
233+ { /* Pinned configs */ }
234+ { pinnedConfigs . map ( ( config ) => renderConfigItem ( config , true ) ) }
235+
236+ { /* Separator between pinned and unpinned */ }
237+ { pinnedConfigs . length > 0 && unpinnedConfigs . length > 0 && (
238+ < div className = "mx-1 my-1 h-px bg-vscode-dropdown-foreground/10" />
239+ ) }
240+
241+ { /* Unpinned configs */ }
242+ { unpinnedConfigs . map ( ( config ) => renderConfigItem ( config , false ) ) }
192243 </ div >
193244 ) }
194245 </ div >
195- ) : (
196- < div className = "p-3 border-b border-vscode-dropdown-border" >
197- < p className = "text-xs text-vscode-descriptionForeground m-0" >
198- { t ( "prompts:apiConfiguration.select" ) }
199- </ p >
200- </ div >
201- ) }
202246
203- { /* Config list */ }
204- < div className = "max-h-[300px] overflow-y-auto" >
205- { filteredConfigs . length === 0 && searchValue ? (
206- < div className = "py-2 px-3 text-sm text-vscode-foreground/70" >
207- { t ( "common:ui.no_results" ) }
247+ { /* Bottom bar with buttons on left and title on right */ }
248+ < div className = "flex flex-row items-center justify-between px-2 py-2 border-t border-vscode-dropdown-border" >
249+ < div className = "flex flex-row gap-1" >
250+ < IconButton
251+ iconClass = "codicon-settings-gear"
252+ title = { t ( "chat:edit" ) }
253+ onClick = { handleEditClick }
254+ tooltip = { false }
255+ />
256+ < IconButton
257+ iconClass = "codicon-layers"
258+ title = { t ( "prompts:apiConfiguration.applyToAllModes" ) }
259+ onClick = { handleApplyToAllModes }
260+ tooltip = { false }
261+ />
208262 </ div >
209- ) : (
210- < div className = "py-1" >
211- { /* Pinned configs */ }
212- { pinnedConfigs . map ( ( config ) => renderConfigItem ( config , true ) ) }
213263
214- { /* Separator between pinned and unpinned */ }
215- { pinnedConfigs . length > 0 && unpinnedConfigs . length > 0 && (
216- < div className = "mx-1 my-1 h-px bg-vscode-dropdown-foreground/10" />
264+ { /* Info icon and title on the right with matching spacing */ }
265+ < div className = "flex items-center gap-1 pr-1" >
266+ { listApiConfigMeta . length > 6 && (
267+ < StandardTooltip content = { t ( "prompts:apiConfiguration.select" ) } >
268+ < span className = "codicon codicon-info text-xs text-vscode-descriptionForeground opacity-70 hover:opacity-100 cursor-help" />
269+ </ StandardTooltip >
217270 ) }
218-
219- { /* Unpinned configs */ }
220- { unpinnedConfigs . map ( ( config ) => renderConfigItem ( config , false ) ) }
271+ < h4 className = "m-0 font-medium text-sm text-vscode-descriptionForeground" >
272+ { t ( "prompts:apiConfiguration.title" ) }
273+ </ h4 >
221274 </ div >
222- ) }
223- </ div >
224-
225- { /* Bottom bar with buttons on left and title on right */ }
226- < div className = "flex flex-row items-center justify-between px-2 py-2 border-t border-vscode-dropdown-border" >
227- < div className = "flex flex-row gap-1" >
228- < IconButton
229- iconClass = "codicon-settings-gear"
230- title = { t ( "chat:edit" ) }
231- onClick = { handleEditClick }
232- tooltip = { false }
233- />
234- </ div >
235-
236- { /* Info icon and title on the right with matching spacing */ }
237- < div className = "flex items-center gap-1 pr-1" >
238- { listApiConfigMeta . length > 6 && (
239- < StandardTooltip content = { t ( "prompts:apiConfiguration.select" ) } >
240- < span className = "codicon codicon-info text-xs text-vscode-descriptionForeground opacity-70 hover:opacity-100 cursor-help" />
241- </ StandardTooltip >
242- ) }
243- < h4 className = "m-0 font-medium text-sm text-vscode-descriptionForeground" >
244- { t ( "prompts:apiConfiguration.title" ) }
245- </ h4 >
246275 </ div >
247276 </ div >
248- </ div >
249- </ PopoverContent >
250- </ Popover >
277+ </ PopoverContent >
278+ </ Popover >
279+
280+ < AlertDialog open = { showConfirmDialog } onOpenChange = { setShowConfirmDialog } >
281+ < AlertDialogContent >
282+ < AlertDialogHeader >
283+ < AlertDialogTitle >
284+ { t ( "prompts:apiConfiguration.confirmApplyToAllModes.title" ) }
285+ </ AlertDialogTitle >
286+ < AlertDialogDescription >
287+ { t ( "prompts:apiConfiguration.confirmApplyToAllModes.description" ) }
288+ </ AlertDialogDescription >
289+ </ AlertDialogHeader >
290+ < AlertDialogFooter >
291+ < AlertDialogCancel >
292+ { t ( "prompts:apiConfiguration.confirmApplyToAllModes.cancel" ) }
293+ </ AlertDialogCancel >
294+ < AlertDialogAction onClick = { handleConfirmApplyToAllModes } >
295+ { t ( "prompts:apiConfiguration.confirmApplyToAllModes.confirm" ) }
296+ </ AlertDialogAction >
297+ </ AlertDialogFooter >
298+ </ AlertDialogContent >
299+ </ AlertDialog >
300+ </ >
251301 )
252302}
0 commit comments