settings window

This commit is contained in:
2023-03-16 13:02:24 +08:00
parent 7747bfa1ac
commit e81044c4b5
3 changed files with 197 additions and 80 deletions

View File

@@ -3,6 +3,8 @@ import "./global.css";
import ChatGPT, { Message, ChunkMessage } from "./chatgpt"; import ChatGPT, { Message, ChunkMessage } from "./chatgpt";
import { createRef } from "preact"; import { createRef } from "preact";
import Settings from "./settings";
import getDefaultParams from "./getDefaultParam";
export interface ChatStore { export interface ChatStore {
systemMessageContent: string; systemMessageContent: string;
@@ -16,49 +18,23 @@ export interface ChatStore {
streamMode: boolean; streamMode: boolean;
} }
const defaultAPIKEY = () => {
const queryParameters = new URLSearchParams(window.location.search);
const key = queryParameters.get("key");
return key;
};
const defaultSysMessage = () => {
const queryParameters = new URLSearchParams(window.location.search);
const sys = queryParameters.get("sys");
return sys;
};
const defaultAPIEndpoint = () => {
const queryParameters = new URLSearchParams(window.location.search);
const sys = queryParameters.get("api");
return sys;
};
const defauleMode = () => {
const queryParameters = new URLSearchParams(window.location.search);
const sys = queryParameters.get("mode");
if (sys === "stream") return true;
if (sys === "fetch") return false;
return undefined;
};
const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions"; const _defaultAPIEndpoint = "https://api.openai.com/v1/chat/completions";
export const newChatStore = ( const newChatStore = (
apiKey = "", apiKey = "",
systemMessageContent = "你是一个猫娘,你要模仿猫娘的语气说话", systemMessageContent = "你是一个猫娘,你要模仿猫娘的语气说话",
apiEndpoint = _defaultAPIEndpoint, apiEndpoint = _defaultAPIEndpoint,
streamMode = true streamMode = true
): ChatStore => { ): ChatStore => {
return { return {
systemMessageContent: defaultSysMessage() || systemMessageContent, systemMessageContent: getDefaultParams("sys", systemMessageContent),
history: [], history: [],
postBeginIndex: 0, postBeginIndex: 0,
tokenMargin: 1024, tokenMargin: 1024,
totalTokens: 0, totalTokens: 0,
maxTokens: 4096, maxTokens: 4096,
apiKey: defaultAPIKEY() || apiKey, apiKey: getDefaultParams("key", apiKey),
apiEndpoint: defaultAPIEndpoint() || apiEndpoint, apiEndpoint: getDefaultParams("api", apiEndpoint),
streamMode: defauleMode() ?? streamMode, streamMode: getDefaultParams("mode", streamMode),
}; };
}; };
@@ -213,16 +189,16 @@ export function App() {
setChatStore({ ...chatStore }); setChatStore({ ...chatStore });
}; };
// change api key const [showSettings, setShowSettings] = useState(false);
const changAPIKEY = () => {
const newAPIKEY = prompt(`Current API KEY: ${chatStore.apiKey}`);
if (!newAPIKEY) return;
chatStore.apiKey = newAPIKEY;
setChatStore({ ...chatStore });
};
return ( return (
<div className="flex text-sm h-screen bg-slate-200"> <div className="flex text-sm h-screen bg-slate-200">
<Settings
chatStore={chatStore}
setChatStore={setChatStore}
show={showSettings}
setShow={setShowSettings}
/>
<div className="flex flex-col h-full p-4 border-r-indigo-500 border-2"> <div className="flex flex-col h-full p-4 border-r-indigo-500 border-2">
<div className="grow overflow-scroll"> <div className="grow overflow-scroll">
<button <button
@@ -273,10 +249,10 @@ export function App() {
if (allChatStore.length === 0) { if (allChatStore.length === 0) {
allChatStore.push( allChatStore.push(
newChatStore( newChatStore(
defaultAPIKEY() || oldAPIkey, getDefaultParams("api", oldAPIkey),
defaultSysMessage() || oldSystemMessageContent, getDefaultParams("sys", oldSystemMessageContent),
defaultAPIEndpoint() || oldAPIEndpoint, getDefaultParams("api", oldAPIEndpoint),
defauleMode() || oldMode getDefaultParams("mode", oldMode)
) )
); );
setSelectedChatIndex(0); setSelectedChatIndex(0);
@@ -290,49 +266,16 @@ export function App() {
</button> </button>
</div> </div>
<div className="grow flex flex-col p-4"> <div className="grow flex flex-col p-4">
<p> <p className="cursor-pointer" onClick={() => setShowSettings(true)}>
<div> <div>
<button <button className="underline">
className="underline"
onClick={() => {
const newSysMsgContent = prompt(
"Change system message content"
);
if (newSysMsgContent === null) return;
chatStore.systemMessageContent = newSysMsgContent;
setChatStore({ ...chatStore });
}}
>
{chatStore.systemMessageContent.length > 13 {chatStore.systemMessageContent.length > 13
? chatStore.systemMessageContent.slice(0, 10) + "..." ? chatStore.systemMessageContent.slice(0, 10) + "..."
: chatStore.systemMessageContent} : chatStore.systemMessageContent}
</button>{" "} </button>{" "}
<button className="underline" onClick={changAPIKEY}> <button className="underline">KEY</button>{" "}
KEY <button className="underline">ENDPOINT</button>{" "}
</button>{" "} <button className="underline">
<button
className="underline"
onClick={() => {
const newEndpoint = prompt(
`Enter new API endpoint\n(current: ${chatStore.apiEndpoint})\n(default: ${_defaultAPIEndpoint})`
);
if (!newEndpoint) return;
chatStore.apiEndpoint = newEndpoint;
setChatStore({ ...chatStore });
}}
>
ENDPOINT
</button>{" "}
<button
className="underline"
onClick={() => {
const choice = confirm(
"FETCH 模式单次请求一段完整的对话文本tokens 数量是准确的。\nSTREAM 模式下可以动态看到生成过程,但只能估算 tokens 数量token 过多时可能裁剪过多或过少历史消息。\n需要使用 STREAM 模式吗?"
);
chatStore.streamMode = !!choice;
setChatStore({ ...chatStore });
}}
>
{chatStore.streamMode ? "STREAM" : "FETCH"} {chatStore.streamMode ? "STREAM" : "FETCH"}
</button> </button>
</div> </div>

18
src/getDefaultParam.ts Normal file
View File

@@ -0,0 +1,18 @@
function getDefaultParams(param: string, val: string): string;
function getDefaultParams(param: string, val: number): number;
function getDefaultParams(param: string, val: boolean): boolean;
function getDefaultParams(param: any, val: any) {
const queryParameters = new URLSearchParams(window.location.search);
const get = queryParameters.get(param);
if (typeof val === "string") {
return get ?? val;
} else if (typeof val === "number") {
return parseInt(get ?? `${val}`);
} else if (typeof val === "boolean") {
if (get === "stream") return true;
if (get === "fetch") return false;
return val;
}
}
export default getDefaultParams;

156
src/settings.tsx Normal file
View File

@@ -0,0 +1,156 @@
import { StateUpdater } from "preact/hooks";
import { ChatStore } from "./app";
const Help = (props: { children: any; help: string }) => {
return (
<div>
<button
className="absolute"
onClick={() => {
alert(props.help);
}}
>
</button>
<p className="flex justify-between">{props.children}</p>
</div>
);
};
const Input = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field: "apiKey" | "systemMessageContent" | "apiEndpoint";
help: string;
}) => {
return (
<Help help={props.help}>
<label className="m-2 p-2">{props.field}</label>
<input
className="m-2 p-2 border rounded focus w-32 md:w-fit"
value={props.chatStore[props.field]}
onChange={(event: any) => {
props.chatStore[props.field] = event.target.value;
props.setChatStore({ ...props.chatStore });
}}
></input>
</Help>
);
};
const Number = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field: "totalTokens" | "maxTokens" | "tokenMargin" | "postBeginIndex";
readOnly: boolean;
help: string;
}) => {
return (
<Help help={props.help}>
<label className="m-2 p-2">{props.field}</label>
<input
readOnly={props.readOnly}
type="number"
className="m-2 p-2 border rounded focus w-28"
value={props.chatStore[props.field]}
onChange={(event: any) => {
console.log("type", typeof event.target.value);
let newNumber = parseInt(event.target.value);
if (newNumber < 0) newNumber = 0;
props.chatStore[props.field] = newNumber;
props.setChatStore({ ...props.chatStore });
}}
></input>
</Help>
);
};
const Choice = (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
field: "streamMode";
help: string;
}) => {
return (
<Help help={props.help}>
<label className="m-2 p-2">{props.field}</label>
<input
type="checkbox"
className="m-2 p-2 border rounded focus"
checked={props.chatStore[props.field]}
onChange={(event: any) => {
props.chatStore[props.field] = event.target.checked;
props.setChatStore({ ...props.chatStore });
}}
></input>
</Help>
);
};
export default (props: {
chatStore: ChatStore;
setChatStore: (cs: ChatStore) => void;
show: boolean;
setShow: StateUpdater<boolean>;
}) => {
if (!props.show) return <div></div>;
return (
<div className="overflow-scroll flex justify-center absolute w-screen h-screen bg-black bg-opacity-50 z-10">
<div className="m-2 p-2 bg-white rounded-lg h-fit">
<h3 className="text-xl">Settings</h3>
<hr />
<div className="box">
<Input
field="systemMessageContent"
help="系统消息用于指示ChatGPT的角色和一些前置条件"
{...props}
/>
<Input field="apiKey" help="OPEN AI API 密钥" {...props} />
<Input
field="apiEndpoint"
help="API 端点,方便在不支持的地区使用反向代理服务"
{...props}
/>
<Choice
field="streamMode"
help="流模式,使用 stream mode 将可以动态看到生成内容,但无法准确计算 token 数量,在 token 数量过多时可能会裁切过多或过少历史消息"
{...props}
/>
<Number
field="maxTokens"
help="最大 token 数量,这个详情参考 OPENAI API 文档"
readOnly={false}
{...props}
/>
<Number
field="tokenMargin"
help="当 totalTokens > maxTokens - tokenMargin 时会触发历史消息裁切chatgpt会“忘记”一部分对话中的消息但所有历史消息仍然保存在本地"
readOnly={false}
{...props}
/>
<Number
field="postBeginIndex"
help="指示发送 API 请求时要”忘记“多少历史消息"
readOnly={false}
{...props}
/>
<Number
field="totalTokens"
help="token总数每次对话都会更新此参数stream模式下该参数为估计值"
readOnly={true}
{...props}
/>
</div>
<hr />
<div className="flex justify-end">
<button
className="p-2 m-2 rounded bg-cyan-600 text-white"
onClick={() => {
props.setShow(false);
}}
>
Close
</button>
</div>
</div>
</div>
);
};