This commit is contained in:
GH Action - Upstream Sync 2023-04-03 18:19:00 +00:00
commit 8d94ac383b
5 changed files with 66 additions and 57 deletions

View File

@ -21,7 +21,7 @@ One-Click to deploy your own ChatGPT web UI.
- 在 1 分钟内使用 Vercel **免费一键部署** - 在 1 分钟内使用 Vercel **免费一键部署**
- 精心设计的 UI响应式设计支持深色模式 - 精心设计的 UI响应式设计支持深色模式
- 极快的首屏加载速度(~85kb - 极快的首屏加载速度(~100kb
- 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts) - 海量的内置 prompt 列表,来自[中文](https://github.com/PlexPt/awesome-chatgpt-prompts-zh)和[英文](https://github.com/f/awesome-chatgpt-prompts)
- 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话 - 自动压缩上下文聊天记录,在节省 Token 的同时支持超长对话
- 一键导出聊天记录,完整的 Markdown 支持 - 一键导出聊天记录,完整的 Markdown 支持
@ -31,7 +31,7 @@ One-Click to deploy your own ChatGPT web UI.
- **Deploy for free with one-click** on Vercel in under 1 minute - **Deploy for free with one-click** on Vercel in under 1 minute
- Responsive design, and dark mode - Responsive design, and dark mode
- Fast first screen loading speed (~85kb) - Fast first screen loading speed (~100kb)
- Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts) - Awesome prompts powered by [awesome-chatgpt-prompts-zh](https://github.com/PlexPt/awesome-chatgpt-prompts-zh) and [awesome-chatgpt-prompts](https://github.com/f/awesome-chatgpt-prompts)
- Automatically compresses chat history to support long conversations while also saving your tokens - Automatically compresses chat history to support long conversations while also saving your tokens
- One-click export all chat history with full Markdown support - One-click export all chat history with full Markdown support

View File

@ -18,3 +18,12 @@
.avatar { .avatar {
cursor: pointer; cursor: pointer;
} }
.password-input {
display: flex;
justify-content: flex-end;
.password-eye {
margin-right: 4px;
}
}

View File

@ -21,6 +21,7 @@ import {
ALL_MODELS, ALL_MODELS,
useUpdateStore, useUpdateStore,
useAccessStore, useAccessStore,
ModalConfigValidator,
} from "../store"; } from "../store";
import { Avatar } from "./chat"; import { Avatar } from "./chat";
@ -30,6 +31,7 @@ import Link from "next/link";
import { UPDATE_URL } from "../constant"; import { UPDATE_URL } from "../constant";
import { SearchService, usePromptStore } from "../store/prompt"; import { SearchService, usePromptStore } from "../store/prompt";
import { requestUsage } from "../requests"; import { requestUsage } from "../requests";
import { ErrorBoundary } from "./error";
function SettingItem(props: { function SettingItem(props: {
title: string; title: string;
@ -57,17 +59,14 @@ function PasswordInput(props: HTMLProps<HTMLInputElement>) {
} }
return ( return (
<span style={{ display: "flex", justifyContent: "end" }}> <div className={styles["password-input"]}>
<input
{...props}
style={{ minWidth: "150px" }}
type={visible ? "text" : "password"}
/>
<IconButton <IconButton
icon={visible ? <EyeIcon /> : <EyeOffIcon />} icon={visible ? <EyeIcon /> : <EyeOffIcon />}
onClick={changeVisibility} onClick={changeVisibility}
className={styles["password-eye"]}
/> />
</span> <input {...props} type={visible ? "text" : "password"} />
</div>
); );
} }
@ -115,11 +114,13 @@ export function Settings(props: { closeSettings: () => void }) {
useEffect(() => { useEffect(() => {
checkUpdate(); checkUpdate();
checkUsage(); checkUsage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const accessStore = useAccessStore(); const accessStore = useAccessStore();
const enabledAccessControl = useMemo( const enabledAccessControl = useMemo(
() => accessStore.enabledAccessControl(), () => accessStore.enabledAccessControl(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[], [],
); );
@ -135,7 +136,7 @@ export function Settings(props: { closeSettings: () => void }) {
}, [showUsage]); }, [showUsage]);
return ( return (
<> <ErrorBoundary>
<div className={styles["window-header"]}> <div className={styles["window-header"]}>
<div className={styles["window-header-title"]}> <div className={styles["window-header-title"]}>
<div className={styles["window-header-main-title"]}> <div className={styles["window-header-main-title"]}>
@ -453,7 +454,9 @@ export function Settings(props: { closeSettings: () => void }) {
onChange={(e) => { onChange={(e) => {
updateConfig( updateConfig(
(config) => (config) =>
(config.modelConfig.model = e.currentTarget.value), (config.modelConfig.model = ModalConfigValidator.model(
e.currentTarget.value,
)),
); );
}} }}
> >
@ -470,7 +473,7 @@ export function Settings(props: { closeSettings: () => void }) {
> >
<input <input
type="range" type="range"
value={config.modelConfig.temperature.toFixed(1)} value={config.modelConfig.temperature?.toFixed(1)}
min="0" min="0"
max="2" max="2"
step="0.1" step="0.1"
@ -478,7 +481,9 @@ export function Settings(props: { closeSettings: () => void }) {
updateConfig( updateConfig(
(config) => (config) =>
(config.modelConfig.temperature = (config.modelConfig.temperature =
e.currentTarget.valueAsNumber), ModalConfigValidator.temperature(
e.currentTarget.valueAsNumber,
)),
); );
}} }}
></input> ></input>
@ -490,13 +495,15 @@ export function Settings(props: { closeSettings: () => void }) {
<input <input
type="number" type="number"
min={100} min={100}
max={4096} max={32000}
value={config.modelConfig.max_tokens} value={config.modelConfig.max_tokens}
onChange={(e) => onChange={(e) =>
updateConfig( updateConfig(
(config) => (config) =>
(config.modelConfig.max_tokens = (config.modelConfig.max_tokens =
e.currentTarget.valueAsNumber), ModalConfigValidator.max_tokens(
e.currentTarget.valueAsNumber,
)),
) )
} }
></input> ></input>
@ -507,7 +514,7 @@ export function Settings(props: { closeSettings: () => void }) {
> >
<input <input
type="range" type="range"
value={config.modelConfig.presence_penalty.toFixed(1)} value={config.modelConfig.presence_penalty?.toFixed(1)}
min="-2" min="-2"
max="2" max="2"
step="0.5" step="0.5"
@ -515,13 +522,15 @@ export function Settings(props: { closeSettings: () => void }) {
updateConfig( updateConfig(
(config) => (config) =>
(config.modelConfig.presence_penalty = (config.modelConfig.presence_penalty =
e.currentTarget.valueAsNumber), ModalConfigValidator.presence_penalty(
e.currentTarget.valueAsNumber,
)),
); );
}} }}
></input> ></input>
</SettingItem> </SettingItem>
</List> </List>
</div> </div>
</> </ErrorBoundary>
); );
} }

View File

@ -1,5 +1,5 @@
import type { ChatRequest, ChatReponse } from "./api/openai/typing"; import type { ChatRequest, ChatReponse } from "./api/openai/typing";
import { filterConfig, Message, ModelConfig, useAccessStore } from "./store"; import { Message, ModelConfig, useAccessStore } from "./store";
import Locale from "./locales"; import Locale from "./locales";
import { showToast } from "./components/ui-lib"; import { showToast } from "./components/ui-lib";
@ -123,11 +123,6 @@ export async function requestChatStream(
filterBot: options?.filterBot, filterBot: options?.filterBot,
}); });
// valid and assign model config
if (options?.modelConfig) {
Object.assign(req, filterConfig(options.modelConfig));
}
console.log("[Request] ", req); console.log("[Request] ", req);
const controller = new AbortController(); const controller = new AbortController();

View File

@ -85,43 +85,39 @@ export const ALL_MODELS = [
}, },
]; ];
export function isValidModel(name: string) { export function limitNumber(
return ALL_MODELS.some((m) => m.name === name && m.available); x: number,
min: number,
max: number,
defaultValue: number,
) {
if (typeof x !== "number" || isNaN(x)) {
return defaultValue;
}
return Math.min(max, Math.max(min, x));
} }
export function isValidNumber(x: number, min: number, max: number) { export function limitModel(name: string) {
return typeof x === "number" && x <= max && x >= min; return ALL_MODELS.some((m) => m.name === name && m.available)
? name
: ALL_MODELS[4].name;
} }
export function filterConfig(oldConfig: ModelConfig): Partial<ModelConfig> { export const ModalConfigValidator = {
const config = Object.assign({}, oldConfig); model(x: string) {
return limitModel(x);
const validator: { },
[k in keyof ModelConfig]: (x: ModelConfig[keyof ModelConfig]) => boolean; max_tokens(x: number) {
} = { return limitNumber(x, 0, 32000, 2000);
model(x) { },
return isValidModel(x as string); presence_penalty(x: number) {
}, return limitNumber(x, -2, 2, 0);
max_tokens(x) { },
return isValidNumber(x as number, 100, 32000); temperature(x: number) {
}, return limitNumber(x, 0, 2, 1);
presence_penalty(x) { },
return isValidNumber(x as number, -2, 2); };
},
temperature(x) {
return isValidNumber(x as number, 0, 2);
},
};
Object.keys(validator).forEach((k) => {
const key = k as keyof ModelConfig;
if (!validator[key](config[key])) {
delete config[key];
}
});
return config;
}
const DEFAULT_CONFIG: ChatConfig = { const DEFAULT_CONFIG: ChatConfig = {
historyMessageCount: 4, historyMessageCount: 4,