2025-12-23 01:16:25 -08:00
"use client" ;
import { useAtomValue } from "jotai" ;
2026-02-10 19:06:21 +05:30
import { Bot , Check , ChevronDown , Edit3 , ImageIcon , Plus , Zap } from "lucide-react" ;
2026-03-07 12:57:27 +05:30
import { type UIEvent , useCallback , useMemo , useState } from "react" ;
2025-12-23 01:16:25 -08:00
import { toast } from "sonner" ;
2026-02-10 17:20:42 +05:30
import {
globalImageGenConfigsAtom ,
imageGenConfigsAtom ,
} from "@/atoms/image-gen-config/image-gen-config-query.atoms" ;
2025-12-23 01:16:25 -08:00
import { updateLLMPreferencesMutationAtom } from "@/atoms/new-llm-config/new-llm-config-mutation.atoms" ;
import {
globalNewLLMConfigsAtom ,
llmPreferencesAtom ,
newLLMConfigsAtom ,
} from "@/atoms/new-llm-config/new-llm-config-query.atoms" ;
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms" ;
import { Badge } from "@/components/ui/badge" ;
import { Button } from "@/components/ui/button" ;
import {
Command ,
CommandEmpty ,
CommandGroup ,
CommandInput ,
CommandItem ,
CommandList ,
CommandSeparator ,
} from "@/components/ui/command" ;
import { Popover , PopoverContent , PopoverTrigger } from "@/components/ui/popover" ;
2026-01-25 15:23:45 +05:30
import { Spinner } from "@/components/ui/spinner" ;
2026-02-10 17:20:42 +05:30
import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
2025-12-23 01:16:25 -08:00
import type {
2026-02-10 17:20:42 +05:30
GlobalImageGenConfig ,
2025-12-23 01:16:25 -08:00
GlobalNewLLMConfig ,
2026-02-10 17:20:42 +05:30
ImageGenerationConfig ,
2025-12-23 01:16:25 -08:00
NewLLMConfigPublic ,
} from "@/contracts/types/new-llm-config.types" ;
2026-02-09 20:48:42 +05:30
import { getProviderIcon } from "@/lib/provider-icons" ;
2026-02-10 17:20:42 +05:30
import { cn } from "@/lib/utils" ;
2025-12-23 01:16:25 -08:00
interface ModelSelectorProps {
2026-02-10 17:20:42 +05:30
onEditLLM : ( config : NewLLMConfigPublic | GlobalNewLLMConfig , isGlobal : boolean ) = > void ;
onAddNewLLM : ( ) = > void ;
onEditImage ? : ( config : ImageGenerationConfig | GlobalImageGenConfig , isGlobal : boolean ) = > void ;
onAddNewImage ? : ( ) = > void ;
2025-12-23 01:16:25 -08:00
className? : string ;
}
2026-02-10 17:20:42 +05:30
export function ModelSelector ( {
onEditLLM ,
onAddNewLLM ,
onEditImage ,
onAddNewImage ,
className ,
} : ModelSelectorProps ) {
2025-12-23 01:16:25 -08:00
const [ open , setOpen ] = useState ( false ) ;
2026-02-10 17:20:42 +05:30
const [ activeTab , setActiveTab ] = useState < "llm" | "image" > ( "llm" ) ;
const [ llmSearchQuery , setLlmSearchQuery ] = useState ( "" ) ;
const [ imageSearchQuery , setImageSearchQuery ] = useState ( "" ) ;
2026-03-06 22:22:28 +05:30
const [ llmScrollPos , setLlmScrollPos ] = useState < "top" | "middle" | "bottom" > ( "top" ) ;
const [ imageScrollPos , setImageScrollPos ] = useState < "top" | "middle" | "bottom" > ( "top" ) ;
const handleListScroll = useCallback (
( setter : typeof setLlmScrollPos ) = > ( e : UIEvent < HTMLDivElement > ) = > {
const el = e . currentTarget ;
const atTop = el . scrollTop <= 2 ;
const atBottom = el . scrollHeight - el . scrollTop - el . clientHeight <= 2 ;
setter ( atTop ? "top" : atBottom ? "bottom" : "middle" ) ;
} ,
[ ]
) ;
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
// LLM data
const { data : llmUserConfigs , isLoading : llmUserLoading } = useAtomValue ( newLLMConfigsAtom ) ;
const { data : llmGlobalConfigs , isLoading : llmGlobalLoading } =
2025-12-23 01:16:25 -08:00
useAtomValue ( globalNewLLMConfigsAtom ) ;
2026-02-10 17:20:42 +05:30
const { data : preferences , isLoading : prefsLoading } = useAtomValue ( llmPreferencesAtom ) ;
2025-12-23 01:16:25 -08:00
const searchSpaceId = useAtomValue ( activeSearchSpaceIdAtom ) ;
const { mutateAsync : updatePreferences } = useAtomValue ( updateLLMPreferencesMutationAtom ) ;
2026-02-10 17:20:42 +05:30
// Image data
const { data : imageGlobalConfigs , isLoading : imageGlobalLoading } =
useAtomValue ( globalImageGenConfigsAtom ) ;
2026-02-10 19:06:21 +05:30
const { data : imageUserConfigs , isLoading : imageUserLoading } = useAtomValue ( imageGenConfigsAtom ) ;
2025-12-23 01:16:25 -08:00
2026-02-10 19:06:21 +05:30
const isLoading =
llmUserLoading || llmGlobalLoading || prefsLoading || imageGlobalLoading || imageUserLoading ;
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
// ─── LLM current config ───
const currentLLMConfig = useMemo ( ( ) = > {
if ( ! preferences ) return null ;
2025-12-23 01:16:25 -08:00
const agentLlmId = preferences . agent_llm_id ;
if ( agentLlmId === null || agentLlmId === undefined ) return null ;
2026-01-29 15:28:31 -08:00
if ( agentLlmId <= 0 ) {
2026-02-10 17:20:42 +05:30
return llmGlobalConfigs ? . find ( ( c ) = > c . id === agentLlmId ) ? ? null ;
2025-12-23 01:16:25 -08:00
}
2026-02-10 17:20:42 +05:30
return llmUserConfigs ? . find ( ( c ) = > c . id === agentLlmId ) ? ? null ;
} , [ preferences , llmGlobalConfigs , llmUserConfigs ] ) ;
const isLLMAutoMode = useMemo ( ( ) = > {
return currentLLMConfig && "is_auto_mode" in currentLLMConfig && currentLLMConfig . is_auto_mode ;
} , [ currentLLMConfig ] ) ;
// ─── Image current config ───
const currentImageConfig = useMemo ( ( ) = > {
if ( ! preferences ) return null ;
const id = preferences . image_generation_config_id ;
if ( id === null || id === undefined ) return null ;
const globalMatch = imageGlobalConfigs ? . find ( ( c ) = > c . id === id ) ;
if ( globalMatch ) return globalMatch ;
return imageUserConfigs ? . find ( ( c ) = > c . id === id ) ? ? null ;
} , [ preferences , imageGlobalConfigs , imageUserConfigs ] ) ;
const isImageAutoMode = useMemo ( ( ) = > {
2026-02-10 19:06:21 +05:30
return (
currentImageConfig && "is_auto_mode" in currentImageConfig && currentImageConfig . is_auto_mode
) ;
2026-02-10 17:20:42 +05:30
} , [ currentImageConfig ] ) ;
// ─── LLM filtering ───
const filteredLLMGlobal = useMemo ( ( ) = > {
if ( ! llmGlobalConfigs ) return [ ] ;
if ( ! llmSearchQuery ) return llmGlobalConfigs ;
const q = llmSearchQuery . toLowerCase ( ) ;
return llmGlobalConfigs . filter (
2025-12-23 01:16:25 -08:00
( c ) = >
2026-02-10 17:20:42 +05:30
c . name . toLowerCase ( ) . includes ( q ) ||
c . model_name . toLowerCase ( ) . includes ( q ) ||
c . provider . toLowerCase ( ) . includes ( q )
2025-12-23 01:16:25 -08:00
) ;
2026-02-10 17:20:42 +05:30
} , [ llmGlobalConfigs , llmSearchQuery ] ) ;
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
const filteredLLMUser = useMemo ( ( ) = > {
if ( ! llmUserConfigs ) return [ ] ;
if ( ! llmSearchQuery ) return llmUserConfigs ;
const q = llmSearchQuery . toLowerCase ( ) ;
return llmUserConfigs . filter (
2025-12-23 01:16:25 -08:00
( c ) = >
2026-02-10 17:20:42 +05:30
c . name . toLowerCase ( ) . includes ( q ) ||
c . model_name . toLowerCase ( ) . includes ( q ) ||
c . provider . toLowerCase ( ) . includes ( q )
2025-12-23 01:16:25 -08:00
) ;
2026-02-10 17:20:42 +05:30
} , [ llmUserConfigs , llmSearchQuery ] ) ;
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
const totalLLMModels = ( llmGlobalConfigs ? . length ? ? 0 ) + ( llmUserConfigs ? . length ? ? 0 ) ;
2026-01-22 12:26:37 -05:00
2026-02-10 17:20:42 +05:30
// ─── Image filtering ───
const filteredImageGlobal = useMemo ( ( ) = > {
if ( ! imageGlobalConfigs ) return [ ] ;
if ( ! imageSearchQuery ) return imageGlobalConfigs ;
const q = imageSearchQuery . toLowerCase ( ) ;
return imageGlobalConfigs . filter (
( c ) = >
c . name . toLowerCase ( ) . includes ( q ) ||
c . model_name . toLowerCase ( ) . includes ( q ) ||
c . provider . toLowerCase ( ) . includes ( q )
) ;
} , [ imageGlobalConfigs , imageSearchQuery ] ) ;
const filteredImageUser = useMemo ( ( ) = > {
if ( ! imageUserConfigs ) return [ ] ;
if ( ! imageSearchQuery ) return imageUserConfigs ;
const q = imageSearchQuery . toLowerCase ( ) ;
return imageUserConfigs . filter (
( c ) = >
c . name . toLowerCase ( ) . includes ( q ) ||
c . model_name . toLowerCase ( ) . includes ( q ) ||
c . provider . toLowerCase ( ) . includes ( q )
) ;
} , [ imageUserConfigs , imageSearchQuery ] ) ;
const totalImageModels = ( imageGlobalConfigs ? . length ? ? 0 ) + ( imageUserConfigs ? . length ? ? 0 ) ;
// ─── Handlers ───
const handleSelectLLM = useCallback (
2025-12-23 01:16:25 -08:00
async ( config : NewLLMConfigPublic | GlobalNewLLMConfig ) = > {
2026-02-10 17:20:42 +05:30
if ( currentLLMConfig ? . id === config . id ) {
2025-12-23 01:16:25 -08:00
setOpen ( false ) ;
return ;
}
if ( ! searchSpaceId ) {
toast . error ( "No search space selected" ) ;
return ;
}
try {
await updatePreferences ( {
search_space_id : Number ( searchSpaceId ) ,
2026-02-10 17:20:42 +05:30
data : { agent_llm_id : config.id } ,
2025-12-23 01:16:25 -08:00
} ) ;
toast . success ( ` Switched to ${ config . name } ` ) ;
setOpen ( false ) ;
} catch ( error ) {
console . error ( "Failed to switch model:" , error ) ;
toast . error ( "Failed to switch model" ) ;
}
} ,
2026-02-10 17:20:42 +05:30
[ currentLLMConfig , searchSpaceId , updatePreferences ]
2025-12-23 01:16:25 -08:00
) ;
2026-02-10 17:20:42 +05:30
const handleEditLLMConfig = useCallback (
2025-12-23 01:16:25 -08:00
( e : React.MouseEvent , config : NewLLMConfigPublic | GlobalNewLLMConfig , isGlobal : boolean ) = > {
e . stopPropagation ( ) ;
2026-02-10 17:20:42 +05:30
onEditLLM ( config , isGlobal ) ;
2025-12-23 01:16:25 -08:00
setOpen ( false ) ;
} ,
2026-02-10 17:20:42 +05:30
[ onEditLLM ]
) ;
const handleSelectImage = useCallback (
async ( configId : number ) = > {
if ( currentImageConfig ? . id === configId ) {
setOpen ( false ) ;
return ;
}
if ( ! searchSpaceId ) {
toast . error ( "No search space selected" ) ;
return ;
}
try {
await updatePreferences ( {
search_space_id : Number ( searchSpaceId ) ,
data : { image_generation_config_id : configId } ,
} ) ;
toast . success ( "Image model updated" ) ;
setOpen ( false ) ;
} catch {
toast . error ( "Failed to switch image model" ) ;
}
} ,
[ currentImageConfig , searchSpaceId , updatePreferences ]
2025-12-23 01:16:25 -08:00
) ;
return (
< Popover open = { open } onOpenChange = { setOpen } >
< PopoverTrigger asChild >
< Button
2026-03-17 01:09:15 +05:30
variant = "ghost"
2025-12-23 01:16:25 -08:00
size = "sm"
role = "combobox"
aria - expanded = { open }
2026-03-17 04:40:46 +05:30
className = { cn (
"h-8 gap-2 px-3 text-sm bg-main-panel hover:bg-accent/50 dark:hover:bg-white/[0.06] border border-border/40 select-none" ,
className
) }
2025-12-23 01:16:25 -08:00
>
{ isLoading ? (
< >
2026-01-25 15:23:45 +05:30
< Spinner size = "sm" className = "text-muted-foreground" / >
2026-01-20 17:53:36 +05:30
< span className = "text-muted-foreground hidden md:inline" > Loading < / span >
2025-12-23 01:16:25 -08:00
< / >
2026-02-10 17:20:42 +05:30
) : (
2025-12-23 01:16:25 -08:00
< >
2026-02-10 17:20:42 +05:30
{ /* LLM section */ }
{ currentLLMConfig ? (
< >
2026-02-10 19:06:21 +05:30
{ getProviderIcon ( currentLLMConfig . provider , {
isAutoMode : isLLMAutoMode ? ? false ,
} ) }
2026-02-10 17:20:42 +05:30
< span className = "max-w-[100px] md:max-w-[120px] truncate hidden md:inline" >
{ currentLLMConfig . name }
< / span >
< / >
2026-01-29 15:28:31 -08:00
) : (
2026-02-10 17:20:42 +05:30
< >
< Bot className = "size-4 text-muted-foreground" / >
< span className = "text-muted-foreground hidden md:inline" > Select Model < / span >
< / >
) }
{ /* Divider */ }
2026-03-06 23:33:51 +05:30
< div className = "h-4 w-px bg-border/60 dark:bg-white/10 mx-0.5" / >
2026-02-10 17:20:42 +05:30
{ /* Image section */ }
{ currentImageConfig ? (
< >
2026-02-10 19:06:21 +05:30
{ getProviderIcon ( currentImageConfig . provider , {
isAutoMode : isImageAutoMode ? ? false ,
} ) }
2026-02-10 17:20:42 +05:30
< span className = "max-w-[80px] md:max-w-[100px] truncate hidden md:inline" >
{ currentImageConfig . name }
< / span >
< / >
) : (
< ImageIcon className = "size-4 text-muted-foreground" / >
2026-01-29 15:28:31 -08:00
) }
2025-12-23 01:16:25 -08:00
< / >
) }
2026-03-17 04:40:46 +05:30
< ChevronDown className = "h-3.5 w-3.5 text-muted-foreground ml-1 shrink-0" / >
2025-12-23 01:16:25 -08:00
< / Button >
< / PopoverTrigger >
< PopoverContent
2026-03-08 17:10:24 +05:30
className = "w-[280px] md:w-[360px] p-0 rounded-lg shadow-lg bg-white border-border/60 dark:bg-neutral-900 dark:border dark:border-white/5 select-none"
2025-12-23 01:16:25 -08:00
align = "start"
sideOffset = { 8 }
>
2026-02-10 17:20:42 +05:30
< Tabs
value = { activeTab }
onValueChange = { ( v ) = > setActiveTab ( v as "llm" | "image" ) }
className = "w-full"
2025-12-23 18:50:24 -08:00
>
2026-03-07 01:55:32 +05:30
< div className = "border-b border-border/80 dark:border-neutral-800" >
2026-02-22 03:58:04 +05:30
< TabsList className = "w-full grid grid-cols-2 rounded-none rounded-t-lg bg-transparent h-11 p-0 gap-0" >
2026-02-10 17:20:42 +05:30
< TabsTrigger
value = "llm"
2026-03-08 17:10:24 +05:30
className = "gap-2 text-sm font-medium rounded-none text-muted-foreground transition-all duration-200 h-full bg-transparent data-[state=active]:bg-transparent shadow-none data-[state=active]:shadow-none border-b-[1.5px] border-transparent data-[state=active]:border-foreground dark:data-[state=active]:border-white data-[state=active]:text-foreground"
2026-02-10 17:20:42 +05:30
>
< Zap className = "size-4" / >
LLM
< / TabsTrigger >
< TabsTrigger
value = "image"
2026-03-08 17:10:24 +05:30
className = "gap-2 text-sm font-medium rounded-none text-muted-foreground transition-all duration-200 h-full bg-transparent data-[state=active]:bg-transparent shadow-none data-[state=active]:shadow-none border-b-[1.5px] border-transparent data-[state=active]:border-foreground dark:data-[state=active]:border-white data-[state=active]:text-foreground"
2026-02-10 17:20:42 +05:30
>
< ImageIcon className = "size-4" / >
Image
< / TabsTrigger >
< / TabsList >
< / div >
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
{ /* ─── LLM Tab ─── */ }
< TabsContent value = "llm" className = "mt-0" >
< Command
shouldFilter = { false }
2026-03-07 01:55:32 +05:30
className = "rounded-none rounded-b-lg relative dark:bg-neutral-900 [&_[data-slot=command-input-wrapper]]:border-0 [&_[data-slot=command-input-wrapper]]:px-0 [&_[data-slot=command-input-wrapper]]:gap-2"
2026-02-10 17:20:42 +05:30
>
{ totalLLMModels > 3 && (
< div className = "px-2 md:px-3 py-1.5 md:py-2" >
< CommandInput
placeholder = "Search models"
value = { llmSearchQuery }
onValueChange = { setLlmSearchQuery }
className = "h-7 md:h-8 w-full text-xs md:text-sm border-0 bg-transparent focus:ring-0 placeholder:text-muted-foreground/60"
/ >
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
) }
2026-03-06 22:22:28 +05:30
< CommandList
className = "max-h-[300px] md:max-h-[400px] overflow-y-auto"
onScroll = { handleListScroll ( setLlmScrollPos ) }
style = { {
maskImage : ` linear-gradient(to bottom, ${ llmScrollPos === "top" ? "black" : "transparent" } , black 16px, black calc(100% - 16px), ${ llmScrollPos === "bottom" ? "black" : "transparent" } ) ` ,
WebkitMaskImage : ` linear-gradient(to bottom, ${ llmScrollPos === "top" ? "black" : "transparent" } , black 16px, black calc(100% - 16px), ${ llmScrollPos === "bottom" ? "black" : "transparent" } ) ` ,
} }
>
2026-02-10 17:20:42 +05:30
< CommandEmpty className = "py-8 text-center" >
< div className = "flex flex-col items-center gap-2" >
< Bot className = "size-8 text-muted-foreground" / >
< p className = "text-sm text-muted-foreground" > No models found < / p >
< p className = "text-xs text-muted-foreground/60" > Try a different search term < / p >
< / div >
< / CommandEmpty >
{ /* Global LLM Configs */ }
{ filteredLLMGlobal . length > 0 && (
< CommandGroup >
< div className = "flex items-center gap-2 px-3 py-2 text-xs font-semibold text-muted-foreground tracking-wider" >
Global Models
< / div >
{ filteredLLMGlobal . map ( ( config ) = > {
const isSelected = currentLLMConfig ? . id === config . id ;
const isAutoMode = "is_auto_mode" in config && config . is_auto_mode ;
return (
< CommandItem
key = { ` llm-g- ${ config . id } ` }
value = { ` llm-g- ${ config . id } ` }
onSelect = { ( ) = > handleSelectLLM ( config ) }
className = { cn (
"mx-2 rounded-lg mb-1 cursor-pointer group transition-all" ,
2026-03-07 01:55:32 +05:30
"hover:bg-accent/50 dark:hover:bg-white/[0.06]" ,
isSelected && "bg-accent/80 dark:bg-white/[0.06]" ,
2026-02-22 03:58:04 +05:30
isAutoMode && ""
2026-02-10 17:20:42 +05:30
) }
>
< div className = "flex items-center justify-between w-full gap-2" >
< div className = "flex items-center gap-3 min-w-0 flex-1" >
< div className = "shrink-0" >
{ getProviderIcon ( config . provider , { isAutoMode } ) }
< / div >
< div className = "min-w-0 flex-1" >
< div className = "flex items-center gap-2" >
< span className = "font-medium truncate" > { config . name } < / span >
{ isAutoMode && (
< Badge
variant = "secondary"
2026-02-22 03:58:04 +05:30
className = "text-[9px] px-1 py-0 h-3.5 bg-violet-800 text-white dark:bg-violet-800 dark:text-white border-0"
2026-02-10 17:20:42 +05:30
>
Recommended
< / Badge >
) }
2026-02-10 19:06:21 +05:30
{ isSelected && (
< Check className = "size-3.5 text-primary shrink-0" / >
) }
2026-02-10 17:20:42 +05:30
< / div >
< div className = "flex items-center gap-1.5 mt-0.5" >
< span className = "text-xs text-muted-foreground truncate" >
2026-02-10 23:04:15 +05:30
{ isAutoMode ? "Auto Mode" : config . model_name }
2026-02-10 17:20:42 +05:30
< / span >
{ ! isAutoMode && config . citations_enabled && (
< Badge
variant = "outline"
className = "text-[9px] px-1 py-0 h-3.5 bg-primary/10 text-primary border-primary/20"
>
Citations
< / Badge >
) }
< / div >
< / div >
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
{ ! isAutoMode && (
< Button
variant = "ghost"
size = "icon"
className = "size-7 shrink-0 rounded-md hover:bg-muted opacity-0 group-hover:opacity-100 transition-opacity"
onClick = { ( e ) = > handleEditLLMConfig ( e , config , true ) }
>
< Edit3 className = "size-3.5 text-muted-foreground" / >
< / Button >
) }
< / div >
< / CommandItem >
) ;
} ) }
< / CommandGroup >
) }
{ filteredLLMGlobal . length > 0 && filteredLLMUser . length > 0 && (
< CommandSeparator className = "my-1 mx-4 bg-border/60" / >
) }
{ /* User LLM Configs */ }
{ filteredLLMUser . length > 0 && (
< CommandGroup >
< div className = "flex items-center gap-2 px-3 py-2 text-xs font-semibold text-muted-foreground tracking-wider" >
Your Configurations
< / div >
{ filteredLLMUser . map ( ( config ) = > {
const isSelected = currentLLMConfig ? . id === config . id ;
return (
< CommandItem
key = { ` llm-u- ${ config . id } ` }
value = { ` llm-u- ${ config . id } ` }
onSelect = { ( ) = > handleSelectLLM ( config ) }
className = { cn (
"mx-2 rounded-lg mb-1 cursor-pointer group transition-all" ,
2026-03-07 01:55:32 +05:30
"hover:bg-accent/50 dark:hover:bg-white/[0.06]" ,
isSelected && "bg-accent/80 dark:bg-white/[0.06]"
2026-02-10 17:20:42 +05:30
) }
>
< div className = "flex items-center justify-between w-full gap-2" >
< div className = "flex items-center gap-3 min-w-0 flex-1" >
< div className = "shrink-0" > { getProviderIcon ( config . provider ) } < / div >
< div className = "min-w-0 flex-1" >
< div className = "flex items-center gap-2" >
< span className = "font-medium truncate" > { config . name } < / span >
2026-02-10 19:06:21 +05:30
{ isSelected && (
< Check className = "size-3.5 text-primary shrink-0" / >
) }
2026-02-10 17:20:42 +05:30
< / div >
< div className = "flex items-center gap-1.5 mt-0.5" >
< span className = "text-xs text-muted-foreground truncate" >
{ config . model_name }
< / span >
{ config . citations_enabled && (
< Badge
variant = "outline"
className = "text-[9px] px-1 py-0 h-3.5 bg-primary/10 text-primary border-primary/20"
>
Citations
< / Badge >
) }
< / div >
< / div >
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
< Button
variant = "ghost"
size = "icon"
className = "size-7 shrink-0 rounded-md hover:bg-muted opacity-0 group-hover:opacity-100 transition-opacity"
onClick = { ( e ) = > handleEditLLMConfig ( e , config , false ) }
>
< Edit3 className = "size-3.5 text-muted-foreground" / >
< / Button >
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
< / CommandItem >
) ;
} ) }
< / CommandGroup >
) }
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
{ /* Add New LLM Config */ }
2026-03-07 01:55:32 +05:30
< div className = "p-2 bg-muted/20 dark:bg-neutral-900" >
2026-02-10 17:20:42 +05:30
< Button
variant = "ghost"
size = "sm"
2026-03-07 01:55:32 +05:30
className = "w-full justify-start gap-2 h-9 rounded-lg hover:bg-accent/50 dark:hover:bg-white/[0.06]"
2026-02-10 17:20:42 +05:30
onClick = { ( ) = > {
setOpen ( false ) ;
onAddNewLLM ( ) ;
} }
>
< Plus className = "size-4 text-primary" / >
< span className = "text-sm font-medium" > Add New Configuration < / span >
< / Button >
< / div >
< / CommandList >
< / Command >
< / TabsContent >
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
{ /* ─── Image Tab ─── */ }
< TabsContent value = "image" className = "mt-0" >
2026-02-10 19:06:21 +05:30
< Command
shouldFilter = { false }
2026-03-07 01:55:32 +05:30
className = "rounded-none rounded-b-lg dark:bg-neutral-900 [&_[data-slot=command-input-wrapper]]:border-0 [&_[data-slot=command-input-wrapper]]:px-0 [&_[data-slot=command-input-wrapper]]:gap-2"
2026-02-10 19:06:21 +05:30
>
2026-02-10 17:20:42 +05:30
{ totalImageModels > 3 && (
< div className = "px-2 md:px-3 py-1.5 md:py-2" >
< CommandInput
placeholder = "Search models"
value = { imageSearchQuery }
onValueChange = { setImageSearchQuery }
className = "h-7 md:h-8 w-full text-xs md:text-sm border-0 bg-transparent focus:ring-0 placeholder:text-muted-foreground/60"
/ >
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
) }
2026-03-06 22:22:28 +05:30
< CommandList
className = "max-h-[300px] md:max-h-[400px] overflow-y-auto"
onScroll = { handleListScroll ( setImageScrollPos ) }
style = { {
maskImage : ` linear-gradient(to bottom, ${ imageScrollPos === "top" ? "black" : "transparent" } , black 16px, black calc(100% - 16px), ${ imageScrollPos === "bottom" ? "black" : "transparent" } ) ` ,
WebkitMaskImage : ` linear-gradient(to bottom, ${ imageScrollPos === "top" ? "black" : "transparent" } , black 16px, black calc(100% - 16px), ${ imageScrollPos === "bottom" ? "black" : "transparent" } ) ` ,
} }
>
2026-02-10 17:20:42 +05:30
< CommandEmpty className = "py-8 text-center" >
< div className = "flex flex-col items-center gap-2" >
< ImageIcon className = "size-8 text-muted-foreground" / >
< p className = "text-sm text-muted-foreground" > No image models found < / p >
< / div >
< / CommandEmpty >
{ /* Global Image Configs */ }
{ filteredImageGlobal . length > 0 && (
< CommandGroup >
< div className = "flex items-center gap-2 px-3 py-2 text-xs font-semibold text-muted-foreground tracking-wider" >
Global Image Models
< / div >
{ filteredImageGlobal . map ( ( config ) = > {
const isSelected = currentImageConfig ? . id === config . id ;
const isAuto = "is_auto_mode" in config && config . is_auto_mode ;
return (
< CommandItem
key = { ` img-g- ${ config . id } ` }
value = { ` img-g- ${ config . id } ` }
onSelect = { ( ) = > handleSelectImage ( config . id ) }
className = { cn (
2026-03-07 01:55:32 +05:30
"mx-2 rounded-lg mb-1 cursor-pointer group transition-all hover:bg-accent/50 dark:hover:bg-white/[0.06]" ,
isSelected && "bg-accent/80 dark:bg-white/[0.06]" ,
2026-02-22 03:58:04 +05:30
isAuto && ""
2026-02-10 17:20:42 +05:30
) }
>
< div className = "flex items-center gap-3 min-w-0 flex-1" >
< div className = "shrink-0" >
{ getProviderIcon ( config . provider , { isAutoMode : isAuto } ) }
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
< div className = "min-w-0 flex-1" >
< div className = "flex items-center gap-2" >
< span className = "font-medium truncate" > { config . name } < / span >
{ isAuto && (
< Badge
variant = "secondary"
2026-02-22 03:58:04 +05:30
className = "text-[9px] px-1 py-0 h-3.5 bg-violet-800 text-white dark:bg-violet-800 dark:text-white border-0"
2026-02-10 17:20:42 +05:30
>
Recommended
< / Badge >
) }
{ isSelected && < Check className = "size-3.5 text-primary shrink-0" / > }
< / div >
< span className = "text-xs text-muted-foreground truncate block" >
2026-02-10 23:04:15 +05:30
{ isAuto ? "Auto Mode" : config . model_name }
2025-12-23 01:16:25 -08:00
< / span >
< / div >
2026-02-10 17:20:42 +05:30
{ onEditImage && ! isAuto && (
< Button
variant = "ghost"
size = "icon"
className = "size-7 shrink-0 rounded-md hover:bg-muted opacity-0 group-hover:opacity-100 transition-opacity"
onClick = { ( e ) = > {
e . stopPropagation ( ) ;
setOpen ( false ) ;
onEditImage ( config , true ) ;
} }
>
< Edit3 className = "size-3.5 text-muted-foreground" / >
< / Button >
) }
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
< / CommandItem >
) ;
} ) }
< / CommandGroup >
) }
{ /* User Image Configs */ }
{ filteredImageUser . length > 0 && (
< >
2026-02-10 19:06:21 +05:30
{ filteredImageGlobal . length > 0 && (
< CommandSeparator className = "my-1 mx-4 bg-border/60" / >
) }
2026-02-10 17:20:42 +05:30
< CommandGroup >
< div className = "flex items-center gap-2 px-3 py-2 text-xs font-semibold text-muted-foreground tracking-wider" >
Your Image Models
2025-12-23 01:16:25 -08:00
< / div >
2026-02-10 17:20:42 +05:30
{ filteredImageUser . map ( ( config ) = > {
const isSelected = currentImageConfig ? . id === config . id ;
return (
< CommandItem
key = { ` img-u- ${ config . id } ` }
value = { ` img-u- ${ config . id } ` }
onSelect = { ( ) = > handleSelectImage ( config . id ) }
className = { cn (
2026-03-07 01:55:32 +05:30
"mx-2 rounded-lg mb-1 cursor-pointer group transition-all hover:bg-accent/50 dark:hover:bg-white/[0.06]" ,
isSelected && "bg-accent/80 dark:bg-white/[0.06]"
2026-02-10 17:20:42 +05:30
) }
>
< div className = "flex items-center gap-3 min-w-0 flex-1" >
2026-02-10 19:06:21 +05:30
< div className = "shrink-0" > { getProviderIcon ( config . provider ) } < / div >
2026-02-10 17:20:42 +05:30
< div className = "min-w-0 flex-1" >
< div className = "flex items-center gap-2" >
< span className = "font-medium truncate" > { config . name } < / span >
2026-02-10 19:06:21 +05:30
{ isSelected && (
< Check className = "size-3.5 text-primary shrink-0" / >
) }
2026-02-10 17:20:42 +05:30
< / div >
< span className = "text-xs text-muted-foreground truncate block" >
{ config . model_name }
< / span >
< / div >
{ onEditImage && (
< Button
variant = "ghost"
size = "icon"
className = "h-7 w-7 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
onClick = { ( e ) = > {
e . stopPropagation ( ) ;
setOpen ( false ) ;
onEditImage ( config , false ) ;
} }
>
< Edit3 className = "size-3.5 text-muted-foreground" / >
< / Button >
) }
< / div >
< / CommandItem >
) ;
} ) }
< / CommandGroup >
< / >
) }
2025-12-23 01:16:25 -08:00
2026-02-10 17:20:42 +05:30
{ /* Add New Image Config */ }
{ onAddNewImage && (
2026-03-07 01:55:32 +05:30
< div className = "p-2 bg-muted/20 dark:bg-neutral-900" >
2026-02-10 17:20:42 +05:30
< Button
variant = "ghost"
size = "sm"
2026-03-07 01:55:32 +05:30
className = "w-full justify-start gap-2 h-9 rounded-lg hover:bg-accent/50 dark:hover:bg-white/[0.06]"
2026-02-10 17:20:42 +05:30
onClick = { ( ) = > {
setOpen ( false ) ;
onAddNewImage ( ) ;
} }
>
< Plus className = "size-4 text-primary" / >
< span className = "text-sm font-medium" > Add Image Model < / span >
< / Button >
< / div >
) }
< / CommandList >
< / Command >
< / TabsContent >
< / Tabs >
2025-12-23 01:16:25 -08:00
< / PopoverContent >
< / Popover >
) ;
}