fix: #418 valid model config

This commit is contained in:
yidadaa 2023-04-04 01:05:33 +08:00
parent 7572c99f4d
commit 4e644cfca7
4 changed files with 64 additions and 55 deletions

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