settings window
This commit is contained in:
103
src/app.tsx
103
src/app.tsx
@@ -3,6 +3,8 @@ import "./global.css";
|
||||
|
||||
import ChatGPT, { Message, ChunkMessage } from "./chatgpt";
|
||||
import { createRef } from "preact";
|
||||
import Settings from "./settings";
|
||||
import getDefaultParams from "./getDefaultParam";
|
||||
|
||||
export interface ChatStore {
|
||||
systemMessageContent: string;
|
||||
@@ -16,49 +18,23 @@ export interface ChatStore {
|
||||
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";
|
||||
export const newChatStore = (
|
||||
const newChatStore = (
|
||||
apiKey = "",
|
||||
systemMessageContent = "你是一个猫娘,你要模仿猫娘的语气说话",
|
||||
apiEndpoint = _defaultAPIEndpoint,
|
||||
streamMode = true
|
||||
): ChatStore => {
|
||||
return {
|
||||
systemMessageContent: defaultSysMessage() || systemMessageContent,
|
||||
systemMessageContent: getDefaultParams("sys", systemMessageContent),
|
||||
history: [],
|
||||
postBeginIndex: 0,
|
||||
tokenMargin: 1024,
|
||||
totalTokens: 0,
|
||||
maxTokens: 4096,
|
||||
apiKey: defaultAPIKEY() || apiKey,
|
||||
apiEndpoint: defaultAPIEndpoint() || apiEndpoint,
|
||||
streamMode: defauleMode() ?? streamMode,
|
||||
apiKey: getDefaultParams("key", apiKey),
|
||||
apiEndpoint: getDefaultParams("api", apiEndpoint),
|
||||
streamMode: getDefaultParams("mode", streamMode),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -213,16 +189,16 @@ export function App() {
|
||||
setChatStore({ ...chatStore });
|
||||
};
|
||||
|
||||
// change api key
|
||||
const changAPIKEY = () => {
|
||||
const newAPIKEY = prompt(`Current API KEY: ${chatStore.apiKey}`);
|
||||
if (!newAPIKEY) return;
|
||||
chatStore.apiKey = newAPIKEY;
|
||||
setChatStore({ ...chatStore });
|
||||
};
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
|
||||
return (
|
||||
<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="grow overflow-scroll">
|
||||
<button
|
||||
@@ -273,10 +249,10 @@ export function App() {
|
||||
if (allChatStore.length === 0) {
|
||||
allChatStore.push(
|
||||
newChatStore(
|
||||
defaultAPIKEY() || oldAPIkey,
|
||||
defaultSysMessage() || oldSystemMessageContent,
|
||||
defaultAPIEndpoint() || oldAPIEndpoint,
|
||||
defauleMode() || oldMode
|
||||
getDefaultParams("api", oldAPIkey),
|
||||
getDefaultParams("sys", oldSystemMessageContent),
|
||||
getDefaultParams("api", oldAPIEndpoint),
|
||||
getDefaultParams("mode", oldMode)
|
||||
)
|
||||
);
|
||||
setSelectedChatIndex(0);
|
||||
@@ -290,49 +266,16 @@ export function App() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="grow flex flex-col p-4">
|
||||
<p>
|
||||
<p className="cursor-pointer" onClick={() => setShowSettings(true)}>
|
||||
<div>
|
||||
<button
|
||||
className="underline"
|
||||
onClick={() => {
|
||||
const newSysMsgContent = prompt(
|
||||
"Change system message content"
|
||||
);
|
||||
if (newSysMsgContent === null) return;
|
||||
chatStore.systemMessageContent = newSysMsgContent;
|
||||
setChatStore({ ...chatStore });
|
||||
}}
|
||||
>
|
||||
<button className="underline">
|
||||
{chatStore.systemMessageContent.length > 13
|
||||
? chatStore.systemMessageContent.slice(0, 10) + "..."
|
||||
: chatStore.systemMessageContent}
|
||||
</button>{" "}
|
||||
<button className="underline" onClick={changAPIKEY}>
|
||||
KEY
|
||||
</button>{" "}
|
||||
<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 });
|
||||
}}
|
||||
>
|
||||
<button className="underline">KEY</button>{" "}
|
||||
<button className="underline">ENDPOINT</button>{" "}
|
||||
<button className="underline">
|
||||
{chatStore.streamMode ? "STREAM" : "FETCH"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
18
src/getDefaultParam.ts
Normal file
18
src/getDefaultParam.ts
Normal 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
156
src/settings.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user