Kamil Furtak commited on
Commit
b25db9b
·
1 Parent(s): 294adfd

add model search

Browse files
Files changed (1) hide show
  1. app/components/chat/ModelSelector.tsx +106 -23
app/components/chat/ModelSelector.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import type { ProviderInfo } from '~/types/model';
2
- import { useEffect } from 'react';
3
  import type { ModelInfo } from '~/lib/modules/llm/types';
 
4
 
5
  interface ModelSelectorProps {
6
  model?: string;
@@ -22,12 +23,30 @@ export const ModelSelector = ({
22
  providerList,
23
  modelLoading,
24
  }: ModelSelectorProps) => {
25
- // Load enabled providers from cookies
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  // Update enabled providers when cookies change
28
  useEffect(() => {
29
  // If current provider is disabled, switch to first enabled provider
30
- if (providerList.length == 0) {
31
  return;
32
  }
33
 
@@ -80,27 +99,91 @@ export const ModelSelector = ({
80
  </option>
81
  ))}
82
  </select>
83
- <select
84
- key={provider?.name}
85
- value={model}
86
- onChange={(e) => setModel?.(e.target.value)}
87
- className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all lg:max-w-[70%]"
88
- disabled={modelLoading === 'all' || modelLoading === provider?.name}
89
- >
90
- {modelLoading == 'all' || modelLoading == provider?.name ? (
91
- <option key={0} value="">
92
- Loading...
93
- </option>
94
- ) : (
95
- [...modelList]
96
- .filter((e) => e.provider == provider?.name && e.name)
97
- .map((modelOption, index) => (
98
- <option key={index} value={modelOption.name}>
99
- {modelOption.label}
100
- </option>
101
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  )}
103
- </select>
104
  </div>
105
  );
106
  };
 
1
  import type { ProviderInfo } from '~/types/model';
2
+ import { useEffect, useState, useRef } from 'react';
3
  import type { ModelInfo } from '~/lib/modules/llm/types';
4
+ import { classNames } from '~/utils/classNames';
5
 
6
  interface ModelSelectorProps {
7
  model?: string;
 
23
  providerList,
24
  modelLoading,
25
  }: ModelSelectorProps) => {
26
+ const [modelSearchQuery, setModelSearchQuery] = useState('');
27
+ const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false);
28
+ const searchInputRef = useRef<HTMLInputElement>(null);
29
+
30
+ // Filter models based on search query
31
+ const filteredModels = [...modelList]
32
+ .filter((e) => e.provider === provider?.name && e.name)
33
+ .filter(
34
+ (model) =>
35
+ model.label.toLowerCase().includes(modelSearchQuery.toLowerCase()) ||
36
+ model.name.toLowerCase().includes(modelSearchQuery.toLowerCase()),
37
+ );
38
+
39
+ // Focus search input when dropdown opens
40
+ useEffect(() => {
41
+ if (isModelDropdownOpen && searchInputRef.current) {
42
+ searchInputRef.current.focus();
43
+ }
44
+ }, [isModelDropdownOpen]);
45
 
46
  // Update enabled providers when cookies change
47
  useEffect(() => {
48
  // If current provider is disabled, switch to first enabled provider
49
+ if (providerList.length === 0) {
50
  return;
51
  }
52
 
 
99
  </option>
100
  ))}
101
  </select>
102
+
103
+ <div className="relative flex-1 lg:max-w-[70%]">
104
+ <div
105
+ className={classNames(
106
+ 'w-full p-2 rounded-lg border border-bolt-elements-borderColor',
107
+ 'bg-bolt-elements-prompt-background text-bolt-elements-textPrimary',
108
+ 'focus-within:outline-none focus-within:ring-2 focus-within:ring-bolt-elements-focus',
109
+ 'transition-all cursor-pointer',
110
+ isModelDropdownOpen ? 'ring-2 ring-bolt-elements-focus' : undefined,
111
+ )}
112
+ onClick={() => setIsModelDropdownOpen(!isModelDropdownOpen)}
113
+ >
114
+ <div className="flex items-center justify-between">
115
+ <span>{modelList.find((m) => m.name === model)?.label || 'Select model'}</span>
116
+ <span
117
+ className={classNames(
118
+ 'i-ph:caret-down transition-transform',
119
+ isModelDropdownOpen ? 'rotate-180' : undefined,
120
+ )}
121
+ />
122
+ </div>
123
+ </div>
124
+
125
+ {isModelDropdownOpen && (
126
+ <div className="absolute z-10 w-full mt-1 py-1 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background shadow-lg">
127
+ <div className="px-2 pb-2">
128
+ <div className="relative">
129
+ <input
130
+ ref={searchInputRef}
131
+ type="text"
132
+ value={modelSearchQuery}
133
+ onChange={(e) => setModelSearchQuery(e.target.value)}
134
+ placeholder="Search models..."
135
+ className={classNames(
136
+ 'w-full pl-8 pr-3 py-1.5 rounded-md text-sm',
137
+ 'bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor',
138
+ 'text-bolt-elements-textPrimary placeholder:text-bolt-elements-textTertiary',
139
+ 'focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus',
140
+ 'transition-all',
141
+ )}
142
+ onClick={(e) => e.stopPropagation()}
143
+ />
144
+ <div className="absolute left-2.5 top-1/2 -translate-y-1/2">
145
+ <span className="i-ph:magnifying-glass text-bolt-elements-textTertiary" />
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <div
151
+ className={classNames(
152
+ 'max-h-60 overflow-y-auto',
153
+ 'scrollbar-thin scrollbar-track-bolt-elements-background-depth-2',
154
+ 'scrollbar-thumb-bolt-elements-borderColor hover:scrollbar-thumb-bolt-elements-borderColorHover',
155
+ 'scrollbar-thumb-rounded-full scrollbar-track-rounded-full',
156
+ )}
157
+ >
158
+ {modelLoading === 'all' || modelLoading === provider?.name ? (
159
+ <div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">Loading...</div>
160
+ ) : filteredModels.length === 0 ? (
161
+ <div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">No models found</div>
162
+ ) : (
163
+ filteredModels.map((modelOption, index) => (
164
+ <div
165
+ key={index}
166
+ className={classNames(
167
+ 'px-3 py-2 text-sm cursor-pointer',
168
+ 'hover:bg-bolt-elements-background-depth-3',
169
+ 'text-bolt-elements-textPrimary',
170
+ model === modelOption.name ? 'bg-bolt-elements-background-depth-2' : undefined,
171
+ )}
172
+ onClick={(e) => {
173
+ e.stopPropagation();
174
+ setModel?.(modelOption.name);
175
+ setIsModelDropdownOpen(false);
176
+ setModelSearchQuery('');
177
+ }}
178
+ >
179
+ {modelOption.label}
180
+ </div>
181
+ ))
182
+ )}
183
+ </div>
184
+ </div>
185
  )}
186
+ </div>
187
  </div>
188
  );
189
  };