Skip to content

Commit 156593f

Browse files
committed
Update setting page saving UX
1 parent f6d017b commit 156593f

4 files changed

Lines changed: 115 additions & 116 deletions

File tree

src/app/components/Page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const PagePanel: FC<PropsWithChildren<{ title: string }>> = (props) => {
66
<div className="text-center border-b border-solid border-primary-border flex flex-col justify-center mx-10 py-2">
77
<span className="font-semibold text-lg">{props.title}</span>
88
</div>
9-
<div className="px-10 h-full overflow-auto">{props.children}</div>
9+
<div className="h-full overflow-auto">{props.children}</div>
1010
</div>
1111
)
1212
}

src/app/i18n/locales/japanese.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,6 @@
9898
"Login to Claude.ai": "Claude.aiにログインする",
9999
"Login to Bard": "Bardにログインする",
100100
"Recent Updates": "最近の更新",
101-
"Added a separate Gemini Pro bot, can be enabled in the settings": "独立したGemini Proボットを追加しました。設定で有効にできます"
101+
"Added a separate Gemini Pro bot, can be enabled in the settings": "独立したGemini Proボットを追加しました。設定で有効にできます",
102+
"Save changes": "変更を保存する"
102103
}

src/app/i18n/locales/simplified-chinese.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,6 @@
9999
"Login to Claude.ai": "登录Claude.ai",
100100
"Login to Bard": "登录Bard",
101101
"Recent Updates": "最近更新",
102-
"Added a separate Gemini Pro bot, can be enabled in the settings": "添加了Gemini Pro,可在设置中启用"
102+
"Added a separate Gemini Pro bot, can be enabled in the settings": "添加了Gemini Pro,可在设置中启用",
103+
"Save changes": "保存更改"
103104
}

src/app/pages/SettingPage.tsx

Lines changed: 110 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import { motion } from 'framer-motion'
12
import { FC, PropsWithChildren, useCallback, useEffect, useState } from 'react'
23
import toast, { Toaster } from 'react-hot-toast'
34
import { useTranslation } from 'react-i18next'
45
import Browser from 'webextension-polyfill'
5-
import { MotionButton } from '~app/components/Button'
6+
import Button from '~app/components/Button'
67
import { Input } from '~app/components/Input'
78
import RadioGroup from '~app/components/RadioGroup'
89
import Select from '~app/components/Select'
@@ -93,125 +94,121 @@ function SettingPage() {
9394

9495
return (
9596
<PagePanel title={`${t('Settings')} (v${getVersion()})`}>
96-
<div className="flex flex-row mt-3 mb-5 gap-3">
97-
<div className="flex flex-col gap-5">
98-
<div>
99-
<p className="font-bold mb-2 text-lg">{t('Startup page')}</p>
100-
<div className="w-[200px]">
101-
<Select
102-
options={[
103-
{ name: 'All-In-One', value: ALL_IN_ONE_PAGE_ID },
104-
...Object.entries(CHATBOTS).map(([botId, bot]) => ({ name: bot.name, value: botId })),
105-
]}
106-
value={userConfig.startupPage}
107-
onChange={(v) => updateConfigValue({ startupPage: v })}
108-
/>
109-
</div>
110-
</div>
111-
<div className="flex flex-col gap-2 max-w-[700px]">
112-
<p className="font-bold text-lg">{t('Chatbots')}</p>
113-
<EnabledBotsSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
97+
<div className="flex flex-col gap-5 mt-3 mb-10 px-10">
98+
<div>
99+
<p className="font-bold mb-2 text-lg">{t('Startup page')}</p>
100+
<div className="w-[200px]">
101+
<Select
102+
options={[
103+
{ name: 'All-In-One', value: ALL_IN_ONE_PAGE_ID },
104+
...Object.entries(CHATBOTS).map(([botId, bot]) => ({ name: bot.name, value: botId })),
105+
]}
106+
value={userConfig.startupPage}
107+
onChange={(v) => updateConfigValue({ startupPage: v })}
108+
/>
114109
</div>
115-
<div className="flex flex-col gap-5 w-fit max-w-[700px]">
116-
<ChatBotSettingPanel title="ChatGPT">
117-
<RadioGroup
118-
options={Object.entries(ChatGPTMode).map(([k, v]) => ({ label: `${k} ${t('Mode')}`, value: v }))}
119-
value={userConfig.chatgptMode}
120-
onChange={(v) => updateConfigValue({ chatgptMode: v as ChatGPTMode })}
121-
/>
122-
{userConfig.chatgptMode === ChatGPTMode.API ? (
123-
<ChatGPTAPISettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
124-
) : userConfig.chatgptMode === ChatGPTMode.Azure ? (
125-
<ChatGPTAzureSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
126-
) : userConfig.chatgptMode === ChatGPTMode.Poe ? (
127-
<ChatGPTPoeSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
128-
) : userConfig.chatgptMode === ChatGPTMode.OpenRouter ? (
129-
<ChatGPTOpenRouterSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
130-
) : (
131-
<ChatGPWebSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
132-
)}
133-
</ChatBotSettingPanel>
134-
<ChatBotSettingPanel title="Claude">
135-
<RadioGroup
136-
options={Object.entries(ClaudeMode).map(([k, v]) => ({ label: `${k} ${t('Mode')}`, value: v }))}
137-
value={userConfig.claudeMode}
138-
onChange={(v) => updateConfigValue({ claudeMode: v as ClaudeMode })}
110+
</div>
111+
<div className="flex flex-col gap-2 max-w-[700px]">
112+
<p className="font-bold text-lg">{t('Chatbots')}</p>
113+
<EnabledBotsSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
114+
</div>
115+
<div className="flex flex-col gap-5 w-fit max-w-[700px]">
116+
<ChatBotSettingPanel title="ChatGPT">
117+
<RadioGroup
118+
options={Object.entries(ChatGPTMode).map(([k, v]) => ({ label: `${k} ${t('Mode')}`, value: v }))}
119+
value={userConfig.chatgptMode}
120+
onChange={(v) => updateConfigValue({ chatgptMode: v as ChatGPTMode })}
121+
/>
122+
{userConfig.chatgptMode === ChatGPTMode.API ? (
123+
<ChatGPTAPISettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
124+
) : userConfig.chatgptMode === ChatGPTMode.Azure ? (
125+
<ChatGPTAzureSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
126+
) : userConfig.chatgptMode === ChatGPTMode.Poe ? (
127+
<ChatGPTPoeSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
128+
) : userConfig.chatgptMode === ChatGPTMode.OpenRouter ? (
129+
<ChatGPTOpenRouterSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
130+
) : (
131+
<ChatGPWebSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
132+
)}
133+
</ChatBotSettingPanel>
134+
<ChatBotSettingPanel title="Claude">
135+
<RadioGroup
136+
options={Object.entries(ClaudeMode).map(([k, v]) => ({ label: `${k} ${t('Mode')}`, value: v }))}
137+
value={userConfig.claudeMode}
138+
onChange={(v) => updateConfigValue({ claudeMode: v as ClaudeMode })}
139+
/>
140+
{userConfig.claudeMode === ClaudeMode.API ? (
141+
<ClaudeAPISettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
142+
) : userConfig.claudeMode === ClaudeMode.Webapp ? (
143+
<ClaudeWebappSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
144+
) : userConfig.claudeMode === ClaudeMode.OpenRouter ? (
145+
<ClaudeOpenRouterSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
146+
) : (
147+
<ClaudePoeSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
148+
)}
149+
</ChatBotSettingPanel>
150+
<ChatBotSettingPanel title="Gemini Pro">
151+
<div className="flex flex-col gap-1">
152+
<p className="font-medium text-sm">
153+
API Key (
154+
<a
155+
href="https://makersuite.google.com/app/apikey"
156+
target="_blank"
157+
rel="noreferrer"
158+
className="underline"
159+
>
160+
how to create key
161+
</a>
162+
)
163+
</p>
164+
<Input
165+
className="w-[400px]"
166+
placeholder="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
167+
value={userConfig.geminiApiKey}
168+
onChange={(e) => updateConfigValue({ geminiApiKey: e.currentTarget.value })}
169+
type="password"
139170
/>
140-
{userConfig.claudeMode === ClaudeMode.API ? (
141-
<ClaudeAPISettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
142-
) : userConfig.claudeMode === ClaudeMode.Webapp ? (
143-
<ClaudeWebappSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
144-
) : userConfig.claudeMode === ClaudeMode.OpenRouter ? (
145-
<ClaudeOpenRouterSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
146-
) : (
147-
<ClaudePoeSettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
148-
)}
149-
</ChatBotSettingPanel>
150-
<ChatBotSettingPanel title="Gemini Pro">
151-
<div className="flex flex-col gap-1">
152-
<p className="font-medium text-sm">
153-
API Key (
154-
<a
155-
href="https://makersuite.google.com/app/apikey"
156-
target="_blank"
157-
rel="noreferrer"
158-
className="underline"
159-
>
160-
how to create key
161-
</a>
162-
)
163-
</p>
164-
<Input
165-
className="w-[400px]"
166-
placeholder="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
167-
value={userConfig.geminiApiKey}
168-
onChange={(e) => updateConfigValue({ geminiApiKey: e.currentTarget.value })}
169-
type="password"
171+
<Blockquote className="mt-1">{t('Your keys are stored locally')}</Blockquote>
172+
</div>
173+
</ChatBotSettingPanel>
174+
<ChatBotSettingPanel title="Bing">
175+
<div className="flex flex-row gap-5 items-center">
176+
<p className="font-medium">{t('Chat style')}</p>
177+
<div className="w-[150px]">
178+
<Select
179+
options={BING_STYLE_OPTIONS}
180+
value={userConfig.bingConversationStyle}
181+
onChange={(v) => updateConfigValue({ bingConversationStyle: v })}
182+
position="top"
170183
/>
171-
<Blockquote className="mt-1">{t('Your keys are stored locally')}</Blockquote>
172184
</div>
173-
</ChatBotSettingPanel>
174-
<ChatBotSettingPanel title="Bing">
175-
<div className="flex flex-row gap-5 items-center">
176-
<p className="font-medium">{t('Chat style')}</p>
177-
<div className="w-[150px]">
178-
<Select
179-
options={BING_STYLE_OPTIONS}
180-
value={userConfig.bingConversationStyle}
181-
onChange={(v) => updateConfigValue({ bingConversationStyle: v })}
182-
position="top"
183-
/>
184-
</div>
185-
</div>
186-
</ChatBotSettingPanel>
187-
<ChatBotSettingPanel title="Perplexity">
188-
<RadioGroup
189-
options={Object.entries(PerplexityMode).map(([k, v]) => ({ label: `${k} ${t('Mode')}`, value: v }))}
190-
value={userConfig.perplexityMode}
191-
onChange={(v) => updateConfigValue({ perplexityMode: v as PerplexityMode })}
192-
/>
193-
{userConfig.perplexityMode === PerplexityMode.API && (
194-
<PerplexityAPISettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
195-
)}
196-
</ChatBotSettingPanel>
197-
</div>
198-
<ShortcutPanel />
199-
<ExportDataPanel />
200-
</div>
201-
<div>
202-
{dirty && (
203-
<MotionButton
204-
color="primary"
205-
text={t('Save')}
206-
className="w-fit fixed bottom-10"
207-
onClick={save}
208-
animate={{ opacity: [0, 1] }}
209-
transition={{ duration: 0.3 }}
185+
</div>
186+
</ChatBotSettingPanel>
187+
<ChatBotSettingPanel title="Perplexity">
188+
<RadioGroup
189+
options={Object.entries(PerplexityMode).map(([k, v]) => ({ label: `${k} ${t('Mode')}`, value: v }))}
190+
value={userConfig.perplexityMode}
191+
onChange={(v) => updateConfigValue({ perplexityMode: v as PerplexityMode })}
210192
/>
211-
)}
193+
{userConfig.perplexityMode === PerplexityMode.API && (
194+
<PerplexityAPISettings userConfig={userConfig} updateConfigValue={updateConfigValue} />
195+
)}
196+
</ChatBotSettingPanel>
212197
</div>
198+
<ShortcutPanel />
199+
<ExportDataPanel />
213200
</div>
214-
<Toaster position="bottom-right" />
201+
{dirty && (
202+
<motion.div
203+
className="sticky bottom-0 w-full bg-primary-background border-t-2 border-primary-border px-5 py-4 drop-shadow flex flex-row items-center justify-center"
204+
initial={{ y: 100 }}
205+
animate={{ y: 0 }}
206+
transition={{ type: 'tween', ease: 'easeInOut' }}
207+
>
208+
<Button color="primary" size="small" text={t('Save changes')} onClick={save} className="py-2" />
209+
</motion.div>
210+
)}
211+
<Toaster position="bottom-center" />
215212
</PagePanel>
216213
)
217214
}

0 commit comments

Comments
 (0)