Skip to content

Commit 57626f4

Browse files
committed
fix: validate url
1 parent 8c25804 commit 57626f4

File tree

7 files changed

+138
-107
lines changed

7 files changed

+138
-107
lines changed

public/locales/en/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
"share": "Share",
2929
"sign-in": "Sign In",
3030
"sign-out": "Sign Out",
31-
"export": "Export"
31+
"export": "Export",
32+
"reset": "Reset"
3233
}

public/locales/zh/common.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@
2828
"share": "分享",
2929
"sign-in": "登录",
3030
"sign-out": "登出",
31-
"export": "导出"
31+
"export": "导出",
32+
"reset": "重置"
3233
}

src/components/Dialog.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
import React from "react";
22
import Button from "./Button";
33
import { useTranslation } from "next-i18next";
4+
import clsx from "clsx";
45

56
export default function Dialog({
67
header,
78
children,
89
isShown,
910
close,
1011
footerButton,
12+
contentClassName,
1113
}: {
1214
header: React.ReactNode;
1315
children: React.ReactNode;
1416
isShown: boolean;
1517
close: () => void;
1618
footerButton?: React.ReactNode;
19+
contentClassName?: string;
1720
}) {
1821
const { t } = useTranslation();
1922
if (!isShown) {
@@ -42,7 +45,12 @@ export default function Dialog({
4245
</button>
4346
</div>
4447
{/*body*/}
45-
<div className="text-md relative my-3 max-h-[50vh] flex-auto overflow-y-auto p-3 leading-relaxed">
48+
<div
49+
className={clsx(
50+
"text-md relative max-h-[50vh] flex-auto overflow-y-auto p-3 leading-relaxed",
51+
contentClassName
52+
)}
53+
>
4654
{children}
4755
</div>
4856
{/*footer*/}

src/components/SettingsDialog.tsx

Lines changed: 98 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -15,59 +15,70 @@ import Dialog from "./Dialog";
1515
import Input from "./Input";
1616
import { GPT_MODEL_NAMES, GPT_4 } from "../utils/constants";
1717
import Accordion from "./Accordion";
18-
import type { ModelSettings } from "../utils/types";
18+
import type { ModelSettings, SettingModel } from "../utils/types";
1919
import { useGuestMode } from "../hooks/useGuestMode";
20-
import { DEFAULT_SETTINGS } from "../hooks/useSettings";
20+
import clsx from "clsx";
2121

2222
export const SettingsDialog: React.FC<{
2323
show: boolean;
2424
close: () => void;
25-
customSettings: [ModelSettings, (settings: ModelSettings) => void];
26-
}> = ({ show, close, customSettings: [customSettings, setCustomSettings] }) => {
25+
customSettings: SettingModel;
26+
}> = ({ show, close, customSettings }) => {
2727
const [settings, setSettings] = React.useState<ModelSettings>({
28-
...customSettings,
28+
...customSettings.settings,
2929
});
3030
const { isGuestMode } = useGuestMode(settings.customGuestKey);
3131
const { t } = useTranslation(["settings", "common"]);
3232

3333
useEffect(() => {
34-
setSettings(customSettings);
34+
setSettings(customSettings.settings);
3535
}, [customSettings, close]);
3636

3737
const updateSettings = <Key extends keyof ModelSettings>(
3838
key: Key,
3939
value: ModelSettings[Key]
4040
) => {
4141
setSettings((prev) => {
42-
if (key === "customApiKey" && !value) {
43-
const {
44-
customTemperature,
45-
customMaxLoops,
46-
customEndPoint,
47-
customMaxTokens,
48-
} = DEFAULT_SETTINGS;
49-
return {
50-
...prev,
51-
[key]: value,
52-
customTemperature,
53-
customMaxLoops,
54-
customEndPoint,
55-
customMaxTokens,
56-
};
57-
}
5842
return { ...prev, [key]: value };
5943
});
6044
};
6145

46+
function urlIsValid(url: string | undefined) {
47+
if (url) {
48+
const pattern = /^(https?:\/\/)?[\w.-]+\.[a-zA-Z]{2,}(\/\S*)?$/;
49+
return pattern.test(url);
50+
}
51+
return true;
52+
}
53+
6254
const handleSave = () => {
63-
setCustomSettings(settings);
55+
if (!urlIsValid(settings.customEndPoint)) {
56+
alert(
57+
t(
58+
"Endpoint URL is invalid. Please ensure that you have set a correct URL."
59+
)
60+
);
61+
return;
62+
}
63+
64+
if (!settings.customApiKey) {
65+
customSettings.resetSettings();
66+
} else {
67+
customSettings.saveSettings(settings);
68+
}
6469
close();
6570
return;
6671
};
6772

73+
const handleReset = () => {
74+
customSettings.resetSettings();
75+
updateSettings("customApiKey", "");
76+
close();
77+
};
78+
6879
const disabled = !settings.customApiKey;
6980
const advancedSettings = (
70-
<>
81+
<div className="flex flex-col gap-2">
7182
<Input
7283
left={
7384
<>
@@ -79,7 +90,6 @@ export const SettingsDialog: React.FC<{
7990
value={settings.customEndPoint}
8091
onChange={(e) => updateSettings("customEndPoint", e.target.value)}
8192
/>
82-
<br />
8393
<Input
8494
left={
8595
<>
@@ -102,7 +112,6 @@ export const SettingsDialog: React.FC<{
102112
step: 0.01,
103113
}}
104114
/>
105-
<br />
106115
<Input
107116
left={
108117
<>
@@ -126,7 +135,6 @@ export const SettingsDialog: React.FC<{
126135
step: 1,
127136
}}
128137
/>
129-
<br />
130138
<Input
131139
left={
132140
<>
@@ -150,24 +158,31 @@ export const SettingsDialog: React.FC<{
150158
step: 100,
151159
}}
152160
/>
153-
</>
161+
</div>
154162
);
155163

156164
return (
157165
<Dialog
158166
header={`${t("settings")} ⚙`}
159167
isShown={show}
160168
close={close}
161-
footerButton={<Button onClick={handleSave}>{t("common:save")}</Button>}
169+
footerButton={
170+
<>
171+
<Button className="bg-red-400 hover:bg-red-500" onClick={handleReset}>
172+
{t("common:reset")}
173+
</Button>
174+
<Button onClick={handleSave}>{t("common:save")}</Button>
175+
</>
176+
}
177+
contentClassName="text-md relative flex flex-col gap-2 p-2 leading-relaxed"
162178
>
163179
<p>{t("usage")}</p>
164-
<br />
165180
<p
166-
className={
167-
settings.customModelName === GPT_4
168-
? "rounded-md border-[2px] border-white/10 bg-yellow-300 text-black"
169-
: ""
170-
}
181+
className={clsx(
182+
"my-2",
183+
settings.customModelName === GPT_4 &&
184+
"rounded-md border-[2px] border-white/10 bg-yellow-300 text-black"
185+
)}
171186
>
172187
<FaExclamationCircle className="inline-block" />
173188
&nbsp;
@@ -185,67 +200,60 @@ export const SettingsDialog: React.FC<{
185200
</b>
186201
</Trans>
187202
</p>
188-
<br />
189-
<div className="text-md relative flex-auto p-2 leading-relaxed">
190-
<Input
191-
left={
192-
<>
193-
<FaKey />
194-
<span className="ml-2">{t("key")}</span>
195-
</>
196-
}
197-
placeholder={"sk-..."}
198-
value={settings.customApiKey}
199-
onChange={(e) => updateSettings("customApiKey", e.target.value)}
200-
/>
201-
<br className="md:inline" />
203+
<Input
204+
left={
205+
<>
206+
<FaKey />
207+
<span className="ml-2">{t("key")}</span>
208+
</>
209+
}
210+
placeholder={"sk-..."}
211+
value={settings.customApiKey}
212+
onChange={(e) => updateSettings("customApiKey", e.target.value)}
213+
/>
214+
<Input
215+
left={
216+
<>
217+
<FaMicrochip />
218+
<span className="ml-2">{t("model")}</span>
219+
</>
220+
}
221+
type="combobox"
222+
value={settings.customModelName}
223+
onChange={() => null}
224+
setValue={(e) => updateSettings("customModelName", e)}
225+
attributes={{ options: GPT_MODEL_NAMES }}
226+
disabled={disabled}
227+
/>
228+
{isGuestMode && (
202229
<Input
203230
left={
204231
<>
205-
<FaMicrochip />
206-
<span className="ml-2">{t("model")}</span>
232+
<FaCode />
233+
<span className="ml-2">{t("guest-key")}</span>
207234
</>
208235
}
209-
type="combobox"
210-
value={settings.customModelName}
211-
onChange={() => null}
212-
setValue={(e) => updateSettings("customModelName", e)}
213-
attributes={{ options: GPT_MODEL_NAMES }}
214-
disabled={disabled}
236+
value={settings.customGuestKey}
237+
onChange={(e) => updateSettings("customGuestKey", e.target.value)}
215238
/>
216-
<br className="md:inline" />
217-
{isGuestMode && (
218-
<Input
219-
left={
220-
<>
221-
<FaCode />
222-
<span className="ml-2">{t("guest-key")}</span>
223-
</>
224-
}
225-
value={settings.customGuestKey}
226-
onChange={(e) => updateSettings("customGuestKey", e.target.value)}
227-
/>
228-
)}
229-
<br className="hidden md:inline" />
230-
<Accordion
231-
child={advancedSettings}
232-
name={t("advanced-settings")}
233-
></Accordion>
234-
<br />
235-
<Trans i18nKey="api-key-notice" ns="settings">
236-
<strong className="mt-10">
237-
NOTE: To get a key, sign up for an OpenAI account and visit the
238-
following
239-
<a
240-
href="https://platform.openai.com/account/api-keys"
241-
className="text-blue-500"
242-
>
243-
link.
244-
</a>
245-
This key is only used in the current browser session
246-
</strong>
247-
</Trans>
248-
</div>
239+
)}
240+
<Accordion
241+
child={advancedSettings}
242+
name={t("advanced-settings")}
243+
></Accordion>
244+
<Trans i18nKey="api-key-notice" ns="settings">
245+
<strong className="mt-10">
246+
NOTE: To get a key, sign up for an OpenAI account and visit the
247+
following
248+
<a
249+
href="https://platform.openai.com/account/api-keys"
250+
className="text-blue-500"
251+
>
252+
link.
253+
</a>
254+
This key is only used in the current browser session
255+
</strong>
256+
</Trans>
249257
</Dialog>
250258
);
251259
};

src/hooks/useSettings.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { useGuestMode } from "./useGuestMode";
99

1010
const SETTINGS_KEY = "AGENTGPT_SETTINGS";
11-
export const DEFAULT_SETTINGS: ModelSettings = {
11+
const DEFAULT_SETTINGS: ModelSettings = {
1212
customApiKey: "",
1313
customModelName: GPT_35_TURBO,
1414
customTemperature: 0.9,
@@ -20,34 +20,33 @@ export const DEFAULT_SETTINGS: ModelSettings = {
2020
};
2121

2222
const loadSettings = () => {
23-
const defaultSettions = DEFAULT_SETTINGS;
2423
if (typeof window === "undefined") {
25-
return defaultSettions;
24+
return DEFAULT_SETTINGS;
2625
}
2726

2827
const data = localStorage.getItem(SETTINGS_KEY);
2928
if (!data) {
30-
return defaultSettions;
29+
return DEFAULT_SETTINGS;
3130
}
3231

3332
try {
3433
const obj = JSON.parse(data) as ModelSettings;
3534
Object.entries(obj).forEach(([key, value]) => {
36-
if (defaultSettions.hasOwnProperty(key)) {
35+
if (DEFAULT_SETTINGS.hasOwnProperty(key)) {
3736
// @ts-ignore
38-
defaultSettions[key] = value;
37+
DEFAULT_SETTINGS[key] = value;
3938
}
4039
});
4140
} catch (error) {}
4241

4342
if (
44-
defaultSettions.customApiKey &&
45-
defaultSettions.customMaxLoops === DEFAULT_MAX_LOOPS_FREE
43+
DEFAULT_SETTINGS.customApiKey &&
44+
DEFAULT_SETTINGS.customMaxLoops === DEFAULT_MAX_LOOPS_FREE
4645
) {
47-
defaultSettions.customMaxLoops = DEFAULT_MAX_LOOPS_CUSTOM_API_KEY;
46+
DEFAULT_SETTINGS.customMaxLoops = DEFAULT_MAX_LOOPS_CUSTOM_API_KEY;
4847
}
4948

50-
return defaultSettions;
49+
return DEFAULT_SETTINGS;
5150
};
5251

5352
export function useSettings({ customLanguage }: { customLanguage: string }) {
@@ -69,8 +68,14 @@ export function useSettings({ customLanguage }: { customLanguage: string }) {
6968
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
7069
};
7170

71+
const resetSettings = () => {
72+
localStorage.removeItem(SETTINGS_KEY);
73+
setSettings(rewriteSettings(DEFAULT_SETTINGS));
74+
};
75+
7276
return {
7377
settings,
7478
saveSettings,
79+
resetSettings,
7580
};
7681
}

0 commit comments

Comments
 (0)