11import { useBlocker } from '@tanstack/react-router'
2+ import { motion } from 'framer-motion'
23import { FC , PropsWithChildren , useCallback , useEffect , useState } from 'react'
34import toast , { Toaster } from 'react-hot-toast'
45import { useTranslation } from 'react-i18next'
56import { BiExport , BiImport } from 'react-icons/bi'
67import Browser from 'webextension-polyfill'
7- import Button from '~app/components/Button'
8+ import Button , { MotionButton } from '~app/components/Button'
89import RadioGroup from '~app/components/RadioGroup'
910import Select from '~app/components/Select'
1011import ChatGPTAPISettings from '~app/components/Settings/ChatGPTAPISettings'
@@ -93,6 +94,7 @@ function SettingPage() {
9394 apiHost = undefined
9495 }
9596 await updateUserConfig ( { ...userConfig ! , openaiApiHost : apiHost } )
97+ setDirty ( false )
9698 toast . success ( 'Saved' )
9799 setTimeout ( ( ) => location . reload ( ) , 500 )
98100 } , [ userConfig ] )
@@ -116,94 +118,108 @@ function SettingPage() {
116118
117119 return (
118120 < PagePanel title = { `${ t ( 'Settings' ) } (v${ getVersion ( ) } )` } >
119- < div className = "flex flex-col gap-5 mt-3" >
120- < div >
121- < p className = "font-bold mb-1 text-lg" > { t ( 'Export/Import All Data' ) } </ p >
122- < p className = "mb-3 opacity-80" > { t ( 'Data includes all your settings, chat histories, and local prompts' ) } </ p >
123- < div className = "flex flex-row gap-3" >
124- < Button size = "small" text = { t ( 'Export' ) } icon = { < BiExport /> } onClick = { exportData } />
125- < Button size = "small" text = { t ( 'Import' ) } icon = { < BiImport /> } onClick = { importData } />
126- </ div >
127- </ div >
128- < div className = "flex flex-col gap-2" >
129- < p className = "font-bold text-lg" > { t ( 'Shortcut to open this app' ) } </ p >
130- < div className = "flex flex-row gap-2 items-center" >
131- { shortcuts . length > 0 && (
132- < div className = "flex flex-row gap-1" >
133- { shortcuts . map ( ( s ) => (
134- < KDB key = { s } text = { s } />
135- ) ) }
136- </ div >
137- ) }
138- < Button text = { t ( 'Change shortcut' ) } size = "small" onClick = { openShortcutPage } />
121+ < div className = "flex flex-row mt-3 mb-5 gap-3" >
122+ < div className = "flex flex-col gap-5" >
123+ < div >
124+ < p className = "font-bold mb-1 text-lg" > { t ( 'Export/Import All Data' ) } </ p >
125+ < p className = "mb-3 opacity-80" > { t ( 'Data includes all your settings, chat histories, and local prompts' ) } </ p >
126+ < div className = "flex flex-row gap-3" >
127+ < Button size = "small" text = { t ( 'Export' ) } icon = { < BiExport /> } onClick = { exportData } />
128+ < Button size = "small" text = { t ( 'Import' ) } icon = { < BiImport /> } onClick = { importData } />
129+ </ div >
139130 </ div >
140- </ div >
141- < div >
142- < p className = "font-bold mb -2 text-lg" > { t ( 'Startup page' ) } </ p >
143- < div className = "w-[200px]" >
144- < Select
145- options = { [
146- { name : 'All-In-One' , value : ALL_IN_ONE_PAGE_ID } ,
147- ... Object . entries ( CHATBOTS ) . map ( ( [ botId , bot ] ) => ( { name : bot . name , value : botId } ) ) ,
148- ] }
149- value = { userConfig . startupPage }
150- onChange = { ( v ) => updateConfigValue ( { startupPage : v } ) }
151- / >
131+ < div className = "flex flex-col gap-2" >
132+ < p className = "font-bold text-lg" > { t ( 'Shortcut to open this app' ) } </ p >
133+ < div className = "flex flex-row gap -2 items-center" >
134+ { shortcuts . length > 0 && (
135+ < div className = "flex flex-row gap-1" >
136+ { shortcuts . map ( ( s ) => (
137+ < KDB key = { s } text = { s } />
138+ ) ) }
139+ </ div >
140+ ) }
141+ < Button text = { t ( 'Change shortcut' ) } size = "small" onClick = { openShortcutPage } />
142+ </ div >
152143 </ div >
153- </ div >
154- < div className = "flex flex-col gap-2" >
155- < p className = "font-bold text-lg" > { t ( 'Chatbots' ) } </ p >
156- < EnabledBotsSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
157- </ div >
158- < ChatBotSettingPanel title = "ChatGPT" >
159- < RadioGroup
160- options = { Object . entries ( ChatGPTMode ) . map ( ( [ k , v ] ) => ( { label : `${ k } ${ t ( 'Mode' ) } ` , value : v } ) ) }
161- value = { userConfig . chatgptMode }
162- onChange = { ( v ) => updateConfigValue ( { chatgptMode : v as ChatGPTMode } ) }
163- />
164- { userConfig . chatgptMode === ChatGPTMode . API ? (
165- < ChatGPTAPISettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
166- ) : userConfig . chatgptMode === ChatGPTMode . Azure ? (
167- < ChatGPTAzureSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
168- ) : userConfig . chatgptMode === ChatGPTMode . Poe ? (
169- < ChatGPTPoeSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
170- ) : userConfig . chatgptMode === ChatGPTMode . OpenRouter ? (
171- < ChatGPTOpenRouterSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
172- ) : (
173- < ChatGPWebSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
174- ) }
175- </ ChatBotSettingPanel >
176- < ChatBotSettingPanel title = "Claude" >
177- < RadioGroup
178- options = { Object . entries ( ClaudeMode ) . map ( ( [ k , v ] ) => ( { label : `${ k } ${ t ( 'Mode' ) } ` , value : v } ) ) }
179- value = { userConfig . claudeMode }
180- onChange = { ( v ) => updateConfigValue ( { claudeMode : v as ClaudeMode } ) }
181- />
182- { userConfig . claudeMode === ClaudeMode . API ? (
183- < ClaudeAPISettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
184- ) : userConfig . claudeMode === ClaudeMode . Webapp ? (
185- < ClaudeWebappSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
186- ) : userConfig . claudeMode === ClaudeMode . OpenRouter ? (
187- < ClaudeOpenRouterSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
188- ) : (
189- < ClaudePoeSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
190- ) }
191- </ ChatBotSettingPanel >
192- < ChatBotSettingPanel title = "Bing" >
193- < div className = "flex flex-row gap-3 items-center justify-between w-[250px]" >
194- < p className = "font-medium text-base" > { t ( 'Chat style' ) } </ p >
195- < div className = "w-[150px]" >
144+ < div >
145+ < p className = "font-bold mb-2 text-lg" > { t ( 'Startup page' ) } </ p >
146+ < div className = "w-[200px]" >
196147 < Select
197- options = { BING_STYLE_OPTIONS }
198- value = { userConfig . bingConversationStyle }
199- onChange = { ( v ) => updateConfigValue ( { bingConversationStyle : v } ) }
148+ options = { [
149+ { name : 'All-In-One' , value : ALL_IN_ONE_PAGE_ID } ,
150+ ...Object . entries ( CHATBOTS ) . map ( ( [ botId , bot ] ) => ( { name : bot . name , value : botId } ) ) ,
151+ ] }
152+ value = { userConfig . startupPage }
153+ onChange = { ( v ) => updateConfigValue ( { startupPage : v } ) }
200154 />
201155 </ div >
202156 </ div >
203- </ ChatBotSettingPanel >
157+ < div className = "flex flex-col gap-2" >
158+ < p className = "font-bold text-lg" > { t ( 'Chatbots' ) } </ p >
159+ < EnabledBotsSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
160+ </ div >
161+ < ChatBotSettingPanel title = "ChatGPT" >
162+ < RadioGroup
163+ options = { Object . entries ( ChatGPTMode ) . map ( ( [ k , v ] ) => ( { label : `${ k } ${ t ( 'Mode' ) } ` , value : v } ) ) }
164+ value = { userConfig . chatgptMode }
165+ onChange = { ( v ) => updateConfigValue ( { chatgptMode : v as ChatGPTMode } ) }
166+ />
167+ { userConfig . chatgptMode === ChatGPTMode . API ? (
168+ < ChatGPTAPISettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
169+ ) : userConfig . chatgptMode === ChatGPTMode . Azure ? (
170+ < ChatGPTAzureSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
171+ ) : userConfig . chatgptMode === ChatGPTMode . Poe ? (
172+ < ChatGPTPoeSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
173+ ) : userConfig . chatgptMode === ChatGPTMode . OpenRouter ? (
174+ < ChatGPTOpenRouterSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
175+ ) : (
176+ < ChatGPWebSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
177+ ) }
178+ </ ChatBotSettingPanel >
179+ < ChatBotSettingPanel title = "Claude" >
180+ < RadioGroup
181+ options = { Object . entries ( ClaudeMode ) . map ( ( [ k , v ] ) => ( { label : `${ k } ${ t ( 'Mode' ) } ` , value : v } ) ) }
182+ value = { userConfig . claudeMode }
183+ onChange = { ( v ) => updateConfigValue ( { claudeMode : v as ClaudeMode } ) }
184+ />
185+ { userConfig . claudeMode === ClaudeMode . API ? (
186+ < ClaudeAPISettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
187+ ) : userConfig . claudeMode === ClaudeMode . Webapp ? (
188+ < ClaudeWebappSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
189+ ) : userConfig . claudeMode === ClaudeMode . OpenRouter ? (
190+ < ClaudeOpenRouterSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
191+ ) : (
192+ < ClaudePoeSettings userConfig = { userConfig } updateConfigValue = { updateConfigValue } />
193+ ) }
194+ </ ChatBotSettingPanel >
195+ < ChatBotSettingPanel title = "Bing" >
196+ < div className = "flex flex-row gap-3 items-center justify-between w-[250px]" >
197+ < p className = "font-medium text-base" > { t ( 'Chat style' ) } </ p >
198+ < div className = "w-[150px]" >
199+ < Select
200+ options = { BING_STYLE_OPTIONS }
201+ value = { userConfig . bingConversationStyle }
202+ onChange = { ( v ) => updateConfigValue ( { bingConversationStyle : v } ) }
203+ position = "top"
204+ />
205+ </ div >
206+ </ div >
207+ </ ChatBotSettingPanel >
208+ </ div >
209+ < div >
210+ { dirty && (
211+ < MotionButton
212+ color = "primary"
213+ text = { t ( 'Save' ) }
214+ className = "w-fit fixed bottom-10"
215+ onClick = { save }
216+ animate = { { opacity : [ 0 , 1 ] } }
217+ transition = { { duration : 0.3 } }
218+ />
219+ ) }
220+ </ div >
221+ < Toaster position = "top-right" />
204222 </ div >
205- < Button color = { dirty ? 'primary' : 'flat' } text = { t ( 'Save' ) } className = "w-fit my-8" onClick = { save } />
206- < Toaster position = "top-right" />
207223 </ PagePanel >
208224 )
209225}
0 commit comments