2
2
3
3
import { zodResolver } from '@hookform/resolvers/zod'
4
4
import { m } from 'framer-motion'
5
- import { Brain , Expand } from 'lucide-react'
5
+ import { Expand } from 'lucide-react'
6
6
import { useState } from 'react'
7
7
import { useForm , useWatch } from 'react-hook-form'
8
8
import { z } from 'zod'
@@ -29,6 +29,7 @@ import { Switch } from '~/components/ui/switch'
29
29
import { Textarea } from '~/components/ui/textarea'
30
30
import { getProviderUrl } from '~/lib/llm-provider'
31
31
import { getSystemPrompt } from '~/lib/system-prompt'
32
+ import { cn } from '~/lib/utils'
32
33
33
34
const formSchema = z . object ( {
34
35
apiKey : z
@@ -48,6 +49,7 @@ function SetModelProviderForm(props: { id: string; onSubmit: (values: FormSchema
48
49
49
50
const form = useForm < FormSchema > ( {
50
51
resolver : zodResolver ( formSchema ) ,
52
+ reValidateMode : 'onSubmit' ,
51
53
defaultValues : {
52
54
enabled : false ,
53
55
system : getSystemPrompt ( ) ,
@@ -59,14 +61,22 @@ function SetModelProviderForm(props: { id: string; onSubmit: (values: FormSchema
59
61
60
62
const [ isPromptExpanded , setIsPromptExpanded ] = useState ( false )
61
63
62
- async function onSubmit ( values : z . infer < typeof formSchema > ) {
64
+ async function onSubmit ( values : FormSchema ) {
63
65
await modelProvider . set ( values )
64
66
props . onSubmit ( values )
65
67
}
66
68
69
+ async function onError ( ) {
70
+ const values = form . getValues ( )
71
+ if ( values . enabled === false ) {
72
+ await modelProvider . set ( values )
73
+ props . onSubmit ( values )
74
+ }
75
+ }
76
+
67
77
return (
68
78
< Form { ...form } >
69
- < form id = { props . id } onSubmit = { form . handleSubmit ( onSubmit ) } className = "min-w-0" >
79
+ < form id = { props . id } onSubmit = { form . handleSubmit ( onSubmit , onError ) } className = "min-w-0" >
70
80
< div className = "mt-4" >
71
81
< FormField
72
82
control = { form . control }
@@ -84,136 +94,134 @@ function SetModelProviderForm(props: { id: string; onSubmit: (values: FormSchema
84
94
) }
85
95
/>
86
96
</ div >
87
- { isEnabled && (
88
- < div className = "mt-8 pt-8 border-t space-y-4" >
89
- < FormField
90
- control = { form . control }
91
- name = "baseUrl"
92
- render = { ( { field } ) => (
93
- < FormItem >
94
- < FormLabel > Base URL</ FormLabel >
95
- < FormControl >
96
- < >
97
- < Input placeholder = "OpenAI compatible base URL" { ...field } />
98
- < div className = "flex gap-2" >
99
- < MiniButton
100
- onClick = { ( e ) => {
101
- e . preventDefault ( )
102
- form . setValue ( 'baseUrl' , getProviderUrl ( 'openai' ) )
103
- form . setValue ( 'model' , 'gpt-4o' )
104
- } }
105
- >
106
- OpenAI
107
- </ MiniButton >
108
- < MiniButton
109
- onClick = { ( e ) => {
110
- e . preventDefault ( )
111
- form . setValue ( 'baseUrl' , getProviderUrl ( 'x-ai' ) )
112
- form . setValue ( 'model' , 'grok-beta' )
113
- } }
114
- >
115
- xAI
116
- </ MiniButton >
117
- < MiniButton
118
- onClick = { ( e ) => {
119
- e . preventDefault ( )
120
- field . onChange ( { target : { value : getProviderUrl ( 'openrouter' ) } } )
121
- form . setValue ( 'model' , '' )
122
- } }
123
- >
124
- OpenRouter
125
- </ MiniButton >
126
- </ div >
127
- </ >
128
- </ FormControl >
129
- < FormMessage />
130
- </ FormItem >
131
- ) }
132
- />
133
- < FormField
134
- control = { form . control }
135
- name = "apiKey"
136
- render = { ( { field } ) => (
137
- < FormItem >
138
- < FormLabel > API key</ FormLabel >
139
- < FormControl >
140
- < Input placeholder = "API key" { ...field } />
141
- </ FormControl >
142
- < FormMessage />
143
- </ FormItem >
144
- ) }
145
- />
146
- < FormField
147
- control = { form . control }
148
- name = "model"
149
- render = { ( { field } ) => (
150
- < FormItem >
151
- < FormLabel > Model</ FormLabel >
152
- < FormControl >
153
- < Input placeholder = "Model" { ...field } />
154
- </ FormControl >
155
- < FormMessage />
156
- </ FormItem >
157
- ) }
158
- />
159
- < FormField
160
- control = { form . control }
161
- name = "system"
162
- render = { ( { field } ) => (
163
- < FormItem >
164
- < FormLabel > System prompt</ FormLabel >
165
- < FormControl >
166
- < >
167
- < m . div
168
- className = "flex gap-2 rounded-md bg-secondary p-4 text-sm text-primary/50 cursor-pointer"
169
- onClick = { ( ) => {
170
- setIsPromptExpanded ( true )
97
+ < div className = { cn ( 'mt-8 pt-8 border-t space-y-4' , ! isEnabled && 'hidden' ) } >
98
+ < FormField
99
+ control = { form . control }
100
+ name = "baseUrl"
101
+ render = { ( { field } ) => (
102
+ < FormItem >
103
+ < FormLabel > Base URL</ FormLabel >
104
+ < FormControl >
105
+ < >
106
+ < Input placeholder = "OpenAI compatible base URL" { ...field } />
107
+ < div className = "flex gap-2" >
108
+ < MiniButton
109
+ onClick = { ( e ) => {
110
+ e . preventDefault ( )
111
+ form . setValue ( 'baseUrl' , getProviderUrl ( 'openai' ) )
112
+ form . setValue ( 'model' , 'gpt-4o' )
171
113
} }
172
114
>
173
- < div className = "flex-1 max-h-24 overflow-hidden relative" >
174
- { field . value }
175
- < div className = "absolute inset-x-0 -bottom-6 h-16 bg-gradient-to-t from-secondary to-transparent" />
176
- </ div >
177
- < Expand size = { 16 } />
178
- </ m . div >
179
- { isPromptExpanded && (
180
- < div className = "absolute inset-0 bg-background z-20 flex flex-col items-end !mt-0" >
181
- < m . div
182
- variants = { {
183
- hidden : { opacity : 0 , y : 20 } ,
184
- show : { opacity : 1 , y : 0 } ,
115
+ OpenAI
116
+ </ MiniButton >
117
+ < MiniButton
118
+ onClick = { ( e ) => {
119
+ e . preventDefault ( )
120
+ form . setValue ( 'baseUrl' , getProviderUrl ( 'x-ai' ) )
121
+ form . setValue ( 'model' , 'grok-beta' )
122
+ } }
123
+ >
124
+ xAI
125
+ </ MiniButton >
126
+ < MiniButton
127
+ onClick = { ( e ) => {
128
+ e . preventDefault ( )
129
+ field . onChange ( { target : { value : getProviderUrl ( 'openrouter' ) } } )
130
+ form . setValue ( 'model' , '' )
131
+ } }
132
+ >
133
+ OpenRouter
134
+ </ MiniButton >
135
+ </ div >
136
+ </ >
137
+ </ FormControl >
138
+ < FormMessage />
139
+ </ FormItem >
140
+ ) }
141
+ />
142
+ < FormField
143
+ control = { form . control }
144
+ name = "apiKey"
145
+ render = { ( { field } ) => (
146
+ < FormItem >
147
+ < FormLabel > API key</ FormLabel >
148
+ < FormControl >
149
+ < Input placeholder = "API key" { ...field } />
150
+ </ FormControl >
151
+ < FormMessage />
152
+ </ FormItem >
153
+ ) }
154
+ />
155
+ < FormField
156
+ control = { form . control }
157
+ name = "model"
158
+ render = { ( { field } ) => (
159
+ < FormItem >
160
+ < FormLabel > Model</ FormLabel >
161
+ < FormControl >
162
+ < Input placeholder = "Model" { ...field } />
163
+ </ FormControl >
164
+ < FormMessage />
165
+ </ FormItem >
166
+ ) }
167
+ />
168
+ < FormField
169
+ control = { form . control }
170
+ name = "system"
171
+ render = { ( { field } ) => (
172
+ < FormItem >
173
+ < FormLabel > System prompt</ FormLabel >
174
+ < FormControl >
175
+ < >
176
+ < m . div
177
+ className = "flex gap-2 rounded-md bg-secondary p-4 text-sm text-primary/50 cursor-pointer"
178
+ onClick = { ( ) => {
179
+ setIsPromptExpanded ( true )
180
+ } }
181
+ >
182
+ < div className = "flex-1 max-h-24 overflow-hidden relative" >
183
+ { field . value }
184
+ < div className = "absolute inset-x-0 -bottom-6 h-16 bg-gradient-to-t from-secondary to-transparent" />
185
+ </ div >
186
+ < Expand size = { 16 } />
187
+ </ m . div >
188
+ { isPromptExpanded && (
189
+ < div className = "absolute inset-0 bg-background z-20 flex flex-col items-end !mt-0" >
190
+ < m . div
191
+ variants = { {
192
+ hidden : { opacity : 0 , y : 20 } ,
193
+ show : { opacity : 1 , y : 0 } ,
194
+ } }
195
+ initial = "hidden"
196
+ animate = "show"
197
+ className = "flex-1 self-stretch flex"
198
+ >
199
+ < Textarea
200
+ { ...field }
201
+ autoFocus
202
+ className = "resize-none border-none bg-muted rounded-none focus-visible:ring-0 p-8 focus-visible:outline-none focus-visible:ring-offset-0 focus-visible:border-none"
203
+ />
204
+ </ m . div >
205
+ < div className = "w-full p-8 border-t" >
206
+ < Button
207
+ className = "w-full"
208
+ onClick = { ( e ) => {
209
+ e . preventDefault ( )
210
+ setIsPromptExpanded ( false )
185
211
} }
186
- initial = "hidden"
187
- animate = "show"
188
- className = "flex-1 self-stretch flex"
189
212
>
190
- < Textarea
191
- { ...field }
192
- autoFocus
193
- className = "resize-none border-none bg-muted rounded-none focus-visible:ring-0 p-8 focus-visible:outline-none focus-visible:ring-offset-0 focus-visible:border-none"
194
- />
195
- </ m . div >
196
- < div className = "w-full p-8 border-t" >
197
- < Button
198
- className = "w-full"
199
- onClick = { ( e ) => {
200
- e . preventDefault ( )
201
- setIsPromptExpanded ( false )
202
- } }
203
- >
204
- Set Prompt
205
- </ Button >
206
- </ div >
213
+ Set Prompt
214
+ </ Button >
207
215
</ div >
208
- ) }
209
- </ >
210
- </ FormControl >
211
- < FormMessage / >
212
- </ FormItem >
213
- ) }
214
- />
215
- </ div >
216
- ) }
216
+ </ div >
217
+ ) }
218
+ </ >
219
+ </ FormControl >
220
+ < FormMessage / >
221
+ </ FormItem >
222
+ ) }
223
+ / >
224
+ </ div >
217
225
</ form >
218
226
</ Form >
219
227
)
0 commit comments