1- import { Component , createMemo , Show } from "solid-js"
1+ import { Popover as Kobalte } from "@kobalte/core/popover"
2+ import { Component , createMemo , createSignal , JSX , Show } from "solid-js"
23import { useLocal } from "@/context/local"
34import { useDialog } from "@opencode-ai/ui/context/dialog"
45import { popularProviders } from "@/hooks/use-providers"
@@ -9,9 +10,12 @@ import { List } from "@opencode-ai/ui/list"
910import { DialogSelectProvider } from "./dialog-select-provider"
1011import { DialogManageModels } from "./dialog-manage-models"
1112
12- export const DialogSelectModel : Component < { provider ?: string } > = ( props ) => {
13+ const ModelList : Component < {
14+ provider ?: string
15+ class ?: string
16+ onSelect : ( ) => void
17+ } > = ( props ) => {
1318 const local = useLocal ( )
14- const dialog = useDialog ( )
1519
1620 const models = createMemo ( ( ) =>
1721 local . model
@@ -20,6 +24,70 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
2024 . filter ( ( m ) => ( props . provider ? m . provider . id === props . provider : true ) ) ,
2125 )
2226
27+ return (
28+ < List
29+ class = { `flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0 ${ props . class ?? "" } ` }
30+ search = { { placeholder : "Search models" , autofocus : true } }
31+ emptyMessage = "No model results"
32+ key = { ( x ) => `${ x . provider . id } :${ x . id } ` }
33+ items = { models }
34+ current = { local . model . current ( ) }
35+ filterKeys = { [ "provider.name" , "name" , "id" ] }
36+ sortBy = { ( a , b ) => a . name . localeCompare ( b . name ) }
37+ groupBy = { ( x ) => x . provider . name }
38+ sortGroupsBy = { ( a , b ) => {
39+ if ( a . category === "Recent" && b . category !== "Recent" ) return - 1
40+ if ( b . category === "Recent" && a . category !== "Recent" ) return 1
41+ const aProvider = a . items [ 0 ] . provider . id
42+ const bProvider = b . items [ 0 ] . provider . id
43+ if ( popularProviders . includes ( aProvider ) && ! popularProviders . includes ( bProvider ) ) return - 1
44+ if ( ! popularProviders . includes ( aProvider ) && popularProviders . includes ( bProvider ) ) return 1
45+ return popularProviders . indexOf ( aProvider ) - popularProviders . indexOf ( bProvider )
46+ } }
47+ onSelect = { ( x ) => {
48+ local . model . set ( x ? { modelID : x . id , providerID : x . provider . id } : undefined , {
49+ recent : true ,
50+ } )
51+ props . onSelect ( )
52+ } }
53+ >
54+ { ( i ) => (
55+ < div class = "w-full flex items-center gap-x-2 text-13-regular" >
56+ < span class = "truncate" > { i . name } </ span >
57+ < Show when = { i . provider . id === "opencode" && ( ! i . cost || i . cost ?. input === 0 ) } >
58+ < Tag > Free</ Tag >
59+ </ Show >
60+ < Show when = { i . latest } >
61+ < Tag > Latest</ Tag >
62+ </ Show >
63+ </ div >
64+ ) }
65+ </ List >
66+ )
67+ }
68+
69+ export const ModelSelectorPopover : Component < {
70+ provider ?: string
71+ children : JSX . Element
72+ } > = ( props ) => {
73+ const [ open , setOpen ] = createSignal ( false )
74+
75+ return (
76+ < Kobalte open = { open ( ) } onOpenChange = { setOpen } placement = "top-start" gutter = { 8 } >
77+ < Kobalte . Trigger as = "div" > { props . children } </ Kobalte . Trigger >
78+ < Kobalte . Portal >
79+ < Kobalte . Content class = "w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none" >
80+ < Kobalte . Title class = "sr-only" > Select model</ Kobalte . Title >
81+ < ModelList provider = { props . provider } onSelect = { ( ) => setOpen ( false ) } class = "p-1" />
82+ </ Kobalte . Content >
83+ </ Kobalte . Portal >
84+ </ Kobalte >
85+ )
86+ }
87+
88+ export const DialogSelectModel : Component < { provider ?: string } > = ( props ) => {
89+ const dialog = useDialog ( )
90+
2391 return (
2492 < Dialog
2593 title = "Select model"
@@ -34,43 +102,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
34102 </ Button >
35103 }
36104 >
37- < List
38- search = { { placeholder : "Search models" , autofocus : true } }
39- emptyMessage = "No model results"
40- key = { ( x ) => `${ x . provider . id } :${ x . id } ` }
41- items = { models }
42- current = { local . model . current ( ) }
43- filterKeys = { [ "provider.name" , "name" , "id" ] }
44- sortBy = { ( a , b ) => a . name . localeCompare ( b . name ) }
45- groupBy = { ( x ) => x . provider . name }
46- sortGroupsBy = { ( a , b ) => {
47- if ( a . category === "Recent" && b . category !== "Recent" ) return - 1
48- if ( b . category === "Recent" && a . category !== "Recent" ) return 1
49- const aProvider = a . items [ 0 ] . provider . id
50- const bProvider = b . items [ 0 ] . provider . id
51- if ( popularProviders . includes ( aProvider ) && ! popularProviders . includes ( bProvider ) ) return - 1
52- if ( ! popularProviders . includes ( aProvider ) && popularProviders . includes ( bProvider ) ) return 1
53- return popularProviders . indexOf ( aProvider ) - popularProviders . indexOf ( bProvider )
54- } }
55- onSelect = { ( x ) => {
56- local . model . set ( x ? { modelID : x . id , providerID : x . provider . id } : undefined , {
57- recent : true ,
58- } )
59- dialog . close ( )
60- } }
61- >
62- { ( i ) => (
63- < div class = "w-full flex items-center gap-x-3" >
64- < span > { i . name } </ span >
65- < Show when = { i . provider . id === "opencode" && ( ! i . cost || i . cost ?. input === 0 ) } >
66- < Tag > Free</ Tag >
67- </ Show >
68- < Show when = { i . latest } >
69- < Tag > Latest</ Tag >
70- </ Show >
71- </ div >
72- ) }
73- </ List >
105+ < ModelList provider = { props . provider } onSelect = { ( ) => dialog . close ( ) } />
74106 < Button
75107 variant = "ghost"
76108 class = "ml-3 mt-5 mb-6 text-text-base self-start"
0 commit comments