22
33import { zodResolver } from '@hookform/resolvers/zod'
44import { m } from 'framer-motion'
5- import { Brain , Expand } from 'lucide-react'
5+ import { Expand } from 'lucide-react'
66import { useState } from 'react'
77import { useForm , useWatch } from 'react-hook-form'
88import { z } from 'zod'
@@ -29,6 +29,7 @@ import { Switch } from '~/components/ui/switch'
2929import { Textarea } from '~/components/ui/textarea'
3030import { getProviderUrl } from '~/lib/llm-provider'
3131import { getSystemPrompt } from '~/lib/system-prompt'
32+ import { cn } from '~/lib/utils'
3233
3334const formSchema = z . object ( {
3435 apiKey : z
@@ -48,6 +49,7 @@ function SetModelProviderForm(props: { id: string; onSubmit: (values: FormSchema
4849
4950 const form = useForm < FormSchema > ( {
5051 resolver : zodResolver ( formSchema ) ,
52+ reValidateMode : 'onSubmit' ,
5153 defaultValues : {
5254 enabled : false ,
5355 system : getSystemPrompt ( ) ,
@@ -59,14 +61,22 @@ function SetModelProviderForm(props: { id: string; onSubmit: (values: FormSchema
5961
6062 const [ isPromptExpanded , setIsPromptExpanded ] = useState ( false )
6163
62- async function onSubmit ( values : z . infer < typeof formSchema > ) {
64+ async function onSubmit ( values : FormSchema ) {
6365 await modelProvider . set ( values )
6466 props . onSubmit ( values )
6567 }
6668
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+
6777 return (
6878 < 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" >
7080 < div className = "mt-4" >
7181 < FormField
7282 control = { form . control }
@@ -84,136 +94,134 @@ function SetModelProviderForm(props: { id: string; onSubmit: (values: FormSchema
8494 ) }
8595 />
8696 </ 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' )
171113 } }
172114 >
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 )
185211 } }
186- initial = "hidden"
187- animate = "show"
188- className = "flex-1 self-stretch flex"
189212 >
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 >
207215 </ 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 >
217225 </ form >
218226 </ Form >
219227 )
0 commit comments