This commit is contained in:
GH Action - Upstream Sync 2023-04-22 00:58:24 +00:00
commit 4fcb1498c2
21 changed files with 224 additions and 214 deletions

View File

@ -59,6 +59,4 @@ export async function POST(req: NextRequest) {
} }
} }
export const config = { export const runtime = "experimental-edge";
runtime: "edge",
};

View File

@ -30,6 +30,4 @@ export async function GET(req: NextRequest) {
return makeRequest(req); return makeRequest(req);
} }
export const config = { export const runtime = "experimental-edge";
runtime: "edge",
};

View File

@ -32,6 +32,7 @@ import {
useAccessStore, useAccessStore,
Theme, Theme,
ModelType, ModelType,
useAppConfig,
} from "../store"; } from "../store";
import { import {
@ -69,7 +70,7 @@ const Emoji = dynamic(async () => (await import("emoji-picker-react")).Emoji, {
}); });
export function Avatar(props: { role: Message["role"]; model?: ModelType }) { export function Avatar(props: { role: Message["role"]; model?: ModelType }) {
const config = useChatStore((state) => state.config); const config = useAppConfig();
if (props.role !== "user") { if (props.role !== "user") {
return ( return (
@ -285,7 +286,7 @@ function PromptToast(props: {
} }
function useSubmitHandler() { function useSubmitHandler() {
const config = useChatStore((state) => state.config); const config = useAppConfig();
const submitKey = config.submitKey; const submitKey = config.submitKey;
const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => { const shouldSubmit = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
@ -361,16 +362,16 @@ export function ChatActions(props: {
scrollToBottom: () => void; scrollToBottom: () => void;
hitBottom: boolean; hitBottom: boolean;
}) { }) {
const chatStore = useChatStore(); const config = useAppConfig();
// switch themes // switch themes
const theme = chatStore.config.theme; const theme = config.theme;
function nextTheme() { function nextTheme() {
const themes = [Theme.Auto, Theme.Light, Theme.Dark]; const themes = [Theme.Auto, Theme.Light, Theme.Dark];
const themeIndex = themes.indexOf(theme); const themeIndex = themes.indexOf(theme);
const nextIndex = (themeIndex + 1) % themes.length; const nextIndex = (themeIndex + 1) % themes.length;
const nextTheme = themes[nextIndex]; const nextTheme = themes[nextIndex];
chatStore.updateConfig((config) => (config.theme = nextTheme)); config.update((config) => (config.theme = nextTheme));
} }
// stop all responses // stop all responses
@ -428,7 +429,8 @@ export function Chat() {
state.currentSession(), state.currentSession(),
state.currentSessionIndex, state.currentSessionIndex,
]); ]);
const fontSize = useChatStore((state) => state.config.fontSize); const config = useAppConfig();
const fontSize = config.fontSize;
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const [userInput, setUserInput] = useState(""); const [userInput, setUserInput] = useState("");
@ -492,7 +494,7 @@ export function Chat() {
// clear search results // clear search results
if (n === 0) { if (n === 0) {
setPromptHints([]); setPromptHints([]);
} else if (!chatStore.config.disablePromptHint && n < SEARCH_TEXT_LIMIT) { } else if (!config.disablePromptHint && n < SEARCH_TEXT_LIMIT) {
// check if need to trigger auto completion // check if need to trigger auto completion
if (text.startsWith("/")) { if (text.startsWith("/")) {
let searchText = text.slice(1); let searchText = text.slice(1);
@ -543,7 +545,7 @@ export function Chat() {
} }
}; };
const findLastUesrIndex = (messageId: number) => { const findLastUserIndex = (messageId: number) => {
// find last user input message and resend // find last user input message and resend
let lastUserMessageIndex: number | null = null; let lastUserMessageIndex: number | null = null;
for (let i = 0; i < session.messages.length; i += 1) { for (let i = 0; i < session.messages.length; i += 1) {
@ -566,14 +568,14 @@ export function Chat() {
}; };
const onDelete = (botMessageId: number) => { const onDelete = (botMessageId: number) => {
const userIndex = findLastUesrIndex(botMessageId); const userIndex = findLastUserIndex(botMessageId);
if (userIndex === null) return; if (userIndex === null) return;
deleteMessage(userIndex); deleteMessage(userIndex);
}; };
const onResend = (botMessageId: number) => { const onResend = (botMessageId: number) => {
// find last user input message and resend // find last user input message and resend
const userIndex = findLastUesrIndex(botMessageId); const userIndex = findLastUserIndex(botMessageId);
if (userIndex === null) return; if (userIndex === null) return;
setIsLoading(true); setIsLoading(true);
@ -583,8 +585,6 @@ export function Chat() {
inputRef.current?.focus(); inputRef.current?.focus();
}; };
const config = useChatStore((state) => state.config);
const context: RenderMessage[] = session.context.slice(); const context: RenderMessage[] = session.context.slice();
const accessStore = useAccessStore(); const accessStore = useAccessStore();
@ -692,10 +692,10 @@ export function Chat() {
{!isMobileScreen && ( {!isMobileScreen && (
<div className={styles["window-action-button"]}> <div className={styles["window-action-button"]}>
<IconButton <IconButton
icon={chatStore.config.tightBorder ? <MinIcon /> : <MaxIcon />} icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
bordered bordered
onClick={() => { onClick={() => {
chatStore.updateConfig( config.update(
(config) => (config.tightBorder = !config.tightBorder), (config) => (config.tightBorder = !config.tightBorder),
); );
}} }}

View File

@ -313,6 +313,10 @@
.chat-message { .chat-message {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
&:last-child {
animation: slide-in ease 0.3s;
}
} }
.chat-message-user { .chat-message-user {
@ -325,7 +329,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
animation: slide-in ease 0.3s;
&:hover { &:hover {
.chat-message-top-actions { .chat-message-top-actions {

View File

@ -2,14 +2,13 @@
require("../polyfill"); require("../polyfill");
import { useState, useEffect } from "react"; import { useState, useEffect, StyleHTMLAttributes } from "react";
import styles from "./home.module.scss"; import styles from "./home.module.scss";
import BotIcon from "../icons/bot.svg"; import BotIcon from "../icons/bot.svg";
import LoadingIcon from "../icons/three-dots.svg"; import LoadingIcon from "../icons/three-dots.svg";
import { useChatStore } from "../store";
import { getCSSVar, useMobileScreen } from "../utils"; import { getCSSVar, useMobileScreen } from "../utils";
import { Chat } from "./chat"; import { Chat } from "./chat";
@ -23,6 +22,8 @@ import {
Route, Route,
useLocation, useLocation,
} from "react-router-dom"; } from "react-router-dom";
import { SideBar } from "./sidebar";
import { useAppConfig } from "../store/config";
export function Loading(props: { noLogo?: boolean }) { export function Loading(props: { noLogo?: boolean }) {
return ( return (
@ -37,12 +38,8 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
loading: () => <Loading noLogo />, loading: () => <Loading noLogo />,
}); });
const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, {
loading: () => <Loading noLogo />,
});
export function useSwitchTheme() { export function useSwitchTheme() {
const config = useChatStore((state) => state.config); const config = useAppConfig();
useEffect(() => { useEffect(() => {
document.body.classList.remove("light"); document.body.classList.remove("light");
@ -83,7 +80,7 @@ const useHasHydrated = () => {
}; };
function WideScreen() { function WideScreen() {
const config = useChatStore((state) => state.config); const config = useAppConfig();
return ( return (
<div <div

View File

@ -23,6 +23,7 @@ import {
useUpdateStore, useUpdateStore,
useAccessStore, useAccessStore,
ModalConfigValidator, ModalConfigValidator,
useAppConfig,
} from "../store"; } from "../store";
import { Avatar } from "./chat"; import { Avatar } from "./chat";
@ -180,11 +181,10 @@ function PasswordInput(props: HTMLProps<HTMLInputElement>) {
export function Settings() { export function Settings() {
const navigate = useNavigate(); const navigate = useNavigate();
const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [config, updateConfig, resetConfig, clearAllData, clearSessions] = const config = useAppConfig();
useChatStore((state) => [ const updateConfig = config.update;
state.config, const resetConfig = config.reset;
state.updateConfig, const [clearAllData, clearSessions] = useChatStore((state) => [
state.resetConfig,
state.clearAllData, state.clearAllData,
state.clearSessions, state.clearSessions,
]); ]);
@ -645,7 +645,7 @@ export function Settings() {
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.1"
onChange={(e) => { onChange={(e) => {
updateConfig( updateConfig(
(config) => (config) =>

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import styles from "./home.module.scss"; import styles from "./home.module.scss";
@ -10,7 +10,7 @@ import AddIcon from "../icons/add.svg";
import CloseIcon from "../icons/close.svg"; import CloseIcon from "../icons/close.svg";
import Locale from "../locales"; import Locale from "../locales";
import { useChatStore } from "../store"; import { useAppConfig, useChatStore } from "../store";
import { import {
MAX_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH,
@ -20,16 +20,20 @@ import {
REPO_URL, REPO_URL,
} from "../constant"; } from "../constant";
import { HashRouter as Router, Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { useMobileScreen } from "../utils"; import { useMobileScreen } from "../utils";
import { ChatList } from "./chat-list"; import dynamic from "next/dynamic";
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
loading: () => null,
});
function useDragSideBar() { function useDragSideBar() {
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x); const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
const chatStore = useChatStore(); const config = useAppConfig();
const startX = useRef(0); const startX = useRef(0);
const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300); const startDragWidth = useRef(config.sidebarWidth ?? 300);
const lastUpdateTime = useRef(Date.now()); const lastUpdateTime = useRef(Date.now());
const handleMouseMove = useRef((e: MouseEvent) => { const handleMouseMove = useRef((e: MouseEvent) => {
@ -39,11 +43,11 @@ function useDragSideBar() {
lastUpdateTime.current = Date.now(); lastUpdateTime.current = Date.now();
const d = e.clientX - startX.current; const d = e.clientX - startX.current;
const nextWidth = limit(startDragWidth.current + d); const nextWidth = limit(startDragWidth.current + d);
chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth)); config.update((config) => (config.sidebarWidth = nextWidth));
}); });
const handleMouseUp = useRef(() => { const handleMouseUp = useRef(() => {
startDragWidth.current = chatStore.config.sidebarWidth ?? 300; startDragWidth.current = config.sidebarWidth ?? 300;
window.removeEventListener("mousemove", handleMouseMove.current); window.removeEventListener("mousemove", handleMouseMove.current);
window.removeEventListener("mouseup", handleMouseUp.current); window.removeEventListener("mouseup", handleMouseUp.current);
}); });
@ -56,15 +60,15 @@ function useDragSideBar() {
}; };
const isMobileScreen = useMobileScreen(); const isMobileScreen = useMobileScreen();
const shouldNarrow = const shouldNarrow =
!isMobileScreen && chatStore.config.sidebarWidth < MIN_SIDEBAR_WIDTH; !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;
useEffect(() => { useEffect(() => {
const barWidth = shouldNarrow const barWidth = shouldNarrow
? NARROW_SIDEBAR_WIDTH ? NARROW_SIDEBAR_WIDTH
: limit(chatStore.config.sidebarWidth ?? 300); : limit(config.sidebarWidth ?? 300);
const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`; const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
document.documentElement.style.setProperty("--sidebar-width", sideBarWidth); document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
}, [chatStore.config.sidebarWidth, isMobileScreen, shouldNarrow]); }, [config.sidebarWidth, isMobileScreen, shouldNarrow]);
return { return {
onDragMouseDown, onDragMouseDown,

View File

@ -2,7 +2,7 @@ import styles from "./ui-lib.module.scss";
import LoadingIcon from "../icons/three-dots.svg"; import LoadingIcon from "../icons/three-dots.svg";
import CloseIcon from "../icons/close.svg"; import CloseIcon from "../icons/close.svg";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import React from "react"; import React, { useEffect } from "react";
export function Popover(props: { export function Popover(props: {
children: JSX.Element; children: JSX.Element;
@ -64,6 +64,21 @@ interface ModalProps {
onClose?: () => void; onClose?: () => void;
} }
export function Modal(props: ModalProps) { export function Modal(props: ModalProps) {
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
props.onClose?.();
}
};
window.addEventListener("keydown", onKeyDown);
return () => {
window.removeEventListener("keydown", onKeyDown);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<div className={styles["modal-container"]}> <div className={styles["modal-container"]}>
<div className={styles["modal-header"]}> <div className={styles["modal-header"]}>

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
const cn = { const cn = {
WIP: "该功能仍在开发中……", WIP: "该功能仍在开发中……",

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index"; import type { LocaleType } from "./index";
const de: LocaleType = { const de: LocaleType = {

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index"; import type { LocaleType } from "./index";
const en: LocaleType = { const en: LocaleType = {

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index"; import type { LocaleType } from "./index";
const es: LocaleType = { const es: LocaleType = {

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index"; import type { LocaleType } from "./index";
const it: LocaleType = { const it: LocaleType = {

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
const jp = { const jp = {
WIP: "この機能は開発中です……", WIP: "この機能は開発中です……",

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index"; import type { LocaleType } from "./index";
const tr: LocaleType = { const tr: LocaleType = {

View File

@ -1,4 +1,4 @@
import { SubmitKey } from "../store/app"; import { SubmitKey } from "../store/config";
import type { LocaleType } from "./index"; import type { LocaleType } from "./index";
const tw: LocaleType = { const tw: LocaleType = {

View File

@ -4,6 +4,7 @@ import {
ModelConfig, ModelConfig,
ModelType, ModelType,
useAccessStore, useAccessStore,
useAppConfig,
useChatStore, useChatStore,
} from "./store"; } from "./store";
import { showToast } from "./components/ui-lib"; import { showToast } from "./components/ui-lib";
@ -27,7 +28,7 @@ const makeRequestParam = (
sendMessages = sendMessages.filter((m) => m.role !== "assistant"); sendMessages = sendMessages.filter((m) => m.role !== "assistant");
} }
const modelConfig = { ...useChatStore.getState().config.modelConfig }; const modelConfig = { ...useAppConfig.getState().modelConfig };
// @yidadaa: wont send max_tokens, because it is nonsense for Muggles // @yidadaa: wont send max_tokens, because it is nonsense for Muggles
// @ts-expect-error // @ts-expect-error
@ -149,6 +150,7 @@ export async function requestChatStream(
options?: { options?: {
filterBot?: boolean; filterBot?: boolean;
modelConfig?: ModelConfig; modelConfig?: ModelConfig;
model?: ModelType;
onMessage: (message: string, done: boolean) => void; onMessage: (message: string, done: boolean) => void;
onError: (error: Error, statusCode?: number) => void; onError: (error: Error, statusCode?: number) => void;
onController?: (controller: AbortController) => void; onController?: (controller: AbortController) => void;
@ -157,6 +159,7 @@ export async function requestChatStream(
const req = makeRequestParam(messages, { const req = makeRequestParam(messages, {
stream: true, stream: true,
filterBot: options?.filterBot, filterBot: options?.filterBot,
model: options?.model,
}); });
console.log("[Request] ", req); console.log("[Request] ", req);

View File

@ -11,6 +11,7 @@ import { isMobileScreen, trimTopic } from "../utils";
import Locale from "../locales"; import Locale from "../locales";
import { showToast } from "../components/ui-lib"; import { showToast } from "../components/ui-lib";
import { ModelType, useAppConfig } from "./config";
export type Message = ChatCompletionResponseMessage & { export type Message = ChatCompletionResponseMessage & {
date: string; date: string;
@ -30,133 +31,8 @@ export function createMessage(override: Partial<Message>): Message {
}; };
} }
export enum SubmitKey {
Enter = "Enter",
CtrlEnter = "Ctrl + Enter",
ShiftEnter = "Shift + Enter",
AltEnter = "Alt + Enter",
MetaEnter = "Meta + Enter",
}
export enum Theme {
Auto = "auto",
Dark = "dark",
Light = "light",
}
export interface ChatConfig {
historyMessageCount: number; // -1 means all
compressMessageLengthThreshold: number;
sendBotMessages: boolean; // send bot's message or not
submitKey: SubmitKey;
avatar: string;
fontSize: number;
theme: Theme;
tightBorder: boolean;
sendPreviewBubble: boolean;
sidebarWidth: number;
disablePromptHint: boolean;
modelConfig: {
model: ModelType;
temperature: number;
max_tokens: number;
presence_penalty: number;
};
}
export type ModelConfig = ChatConfig["modelConfig"];
export const ROLES: Message["role"][] = ["system", "user", "assistant"]; export const ROLES: Message["role"][] = ["system", "user", "assistant"];
const ENABLE_GPT4 = true;
export const ALL_MODELS = [
{
name: "gpt-4",
available: ENABLE_GPT4,
},
{
name: "gpt-4-0314",
available: ENABLE_GPT4,
},
{
name: "gpt-4-32k",
available: ENABLE_GPT4,
},
{
name: "gpt-4-32k-0314",
available: ENABLE_GPT4,
},
{
name: "gpt-3.5-turbo",
available: true,
},
{
name: "gpt-3.5-turbo-0301",
available: true,
},
] as const;
export type ModelType = (typeof ALL_MODELS)[number]["name"];
export function limitNumber(
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 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) as ModelType;
},
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 = {
historyMessageCount: 4,
compressMessageLengthThreshold: 1000,
sendBotMessages: true as boolean,
submitKey: SubmitKey.CtrlEnter as SubmitKey,
avatar: "1f603",
fontSize: 14,
theme: Theme.Auto as Theme,
tightBorder: false,
sendPreviewBubble: true,
sidebarWidth: 300,
disablePromptHint: false,
modelConfig: {
model: "gpt-3.5-turbo",
temperature: 1,
max_tokens: 2000,
presence_penalty: 0,
},
};
export interface ChatStat { export interface ChatStat {
tokenCount: number; tokenCount: number;
wordCount: number; wordCount: number;
@ -202,7 +78,6 @@ function createEmptySession(): ChatSession {
} }
interface ChatStore { interface ChatStore {
config: ChatConfig;
sessions: ChatSession[]; sessions: ChatSession[];
currentSessionIndex: number; currentSessionIndex: number;
clearSessions: () => void; clearSessions: () => void;
@ -226,9 +101,6 @@ interface ChatStore {
getMessagesWithMemory: () => Message[]; getMessagesWithMemory: () => Message[];
getMemoryPrompt: () => Message; getMemoryPrompt: () => Message;
getConfig: () => ChatConfig;
resetConfig: () => void;
updateConfig: (updater: (config: ChatConfig) => void) => void;
clearAllData: () => void; clearAllData: () => void;
} }
@ -243,9 +115,6 @@ export const useChatStore = create<ChatStore>()(
(set, get) => ({ (set, get) => ({
sessions: [createEmptySession()], sessions: [createEmptySession()],
currentSessionIndex: 0, currentSessionIndex: 0,
config: {
...DEFAULT_CONFIG,
},
clearSessions() { clearSessions() {
set(() => ({ set(() => ({
@ -254,20 +123,6 @@ export const useChatStore = create<ChatStore>()(
})); }));
}, },
resetConfig() {
set(() => ({ config: { ...DEFAULT_CONFIG } }));
},
getConfig() {
return get().config;
},
updateConfig(updater) {
const config = get().config;
updater(config);
set(() => ({ config }));
},
selectSession(index: number) { selectSession(index: number) {
set({ set({
currentSessionIndex: index, currentSessionIndex: index,
@ -390,7 +245,7 @@ export const useChatStore = create<ChatStore>()(
role: "assistant", role: "assistant",
streaming: true, streaming: true,
id: userMessage.id! + 1, id: userMessage.id! + 1,
model: get().config.modelConfig.model, model: useAppConfig.getState().modelConfig.model,
}); });
// get recent messages // get recent messages
@ -443,8 +298,8 @@ export const useChatStore = create<ChatStore>()(
controller, controller,
); );
}, },
filterBot: !get().config.sendBotMessages, filterBot: !useAppConfig.getState().sendBotMessages,
modelConfig: get().config.modelConfig, modelConfig: useAppConfig.getState().modelConfig,
}); });
}, },
@ -460,7 +315,7 @@ export const useChatStore = create<ChatStore>()(
getMessagesWithMemory() { getMessagesWithMemory() {
const session = get().currentSession(); const session = get().currentSession();
const config = get().config; const config = useAppConfig.getState();
const messages = session.messages.filter((msg) => !msg.isError); const messages = session.messages.filter((msg) => !msg.isError);
const n = messages.length; const n = messages.length;
@ -545,14 +400,14 @@ export const useChatStore = create<ChatStore>()(
}); });
} }
const config = get().config; const config = useAppConfig.getState();
let toBeSummarizedMsgs = session.messages.slice( let toBeSummarizedMsgs = session.messages.slice(
session.lastSummarizeIndex, session.lastSummarizeIndex,
); );
const historyMsgLength = countMessages(toBeSummarizedMsgs); const historyMsgLength = countMessages(toBeSummarizedMsgs);
if (historyMsgLength > get().config?.modelConfig?.max_tokens ?? 4000) { if (historyMsgLength > config?.modelConfig?.max_tokens ?? 4000) {
const n = toBeSummarizedMsgs.length; const n = toBeSummarizedMsgs.length;
toBeSummarizedMsgs = toBeSummarizedMsgs.slice( toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
Math.max(0, n - config.historyMessageCount), Math.max(0, n - config.historyMessageCount),
@ -583,6 +438,7 @@ export const useChatStore = create<ChatStore>()(
}), }),
{ {
filterBot: false, filterBot: false,
model: "gpt-3.5-turbo",
onMessage(message, done) { onMessage(message, done) {
session.memoryPrompt = message; session.memoryPrompt = message;
if (done) { if (done) {

135
app/store/config.ts Normal file
View File

@ -0,0 +1,135 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export enum SubmitKey {
Enter = "Enter",
CtrlEnter = "Ctrl + Enter",
ShiftEnter = "Shift + Enter",
AltEnter = "Alt + Enter",
MetaEnter = "Meta + Enter",
}
export enum Theme {
Auto = "auto",
Dark = "dark",
Light = "light",
}
const DEFAULT_CONFIG = {
historyMessageCount: 4,
compressMessageLengthThreshold: 1000,
sendBotMessages: true as boolean,
submitKey: SubmitKey.CtrlEnter as SubmitKey,
avatar: "1f603",
fontSize: 14,
theme: Theme.Auto as Theme,
tightBorder: false,
sendPreviewBubble: true,
sidebarWidth: 300,
disablePromptHint: false,
modelConfig: {
model: "gpt-3.5-turbo" as ModelType,
temperature: 1,
max_tokens: 2000,
presence_penalty: 0,
},
};
export type ChatConfig = typeof DEFAULT_CONFIG;
export type ChatConfigStore = ChatConfig & {
reset: () => void;
update: (updater: (config: ChatConfig) => void) => void;
};
export type ModelConfig = ChatConfig["modelConfig"];
const ENABLE_GPT4 = true;
export const ALL_MODELS = [
{
name: "gpt-4",
available: ENABLE_GPT4,
},
{
name: "gpt-4-0314",
available: ENABLE_GPT4,
},
{
name: "gpt-4-32k",
available: ENABLE_GPT4,
},
{
name: "gpt-4-32k-0314",
available: ENABLE_GPT4,
},
{
name: "gpt-3.5-turbo",
available: true,
},
{
name: "gpt-3.5-turbo-0301",
available: true,
},
] as const;
export type ModelType = (typeof ALL_MODELS)[number]["name"];
export function limitNumber(
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 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) as ModelType;
},
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 CONFIG_KEY = "app-config";
export const useAppConfig = create<ChatConfigStore>()(
persist(
(set, get) => ({
...DEFAULT_CONFIG,
reset() {
set(() => ({ ...DEFAULT_CONFIG }));
},
update(updater) {
const config = { ...get() };
updater(config);
set(() => config);
},
}),
{
name: CONFIG_KEY,
},
),
);

View File

@ -1,3 +1,4 @@
export * from "./app"; export * from "./app";
export * from "./update"; export * from "./update";
export * from "./access"; export * from "./access";
export * from "./config";

View File

@ -29,13 +29,13 @@ esac
if ! command -v node >/dev/null || ! command -v git >/dev/null || ! command -v yarn >/dev/null; then if ! command -v node >/dev/null || ! command -v git >/dev/null || ! command -v yarn >/dev/null; then
case "$(uname -s)" in case "$(uname -s)" in
Linux) Linux)
if [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=\"ubuntu\"" ]]; then if [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=ubuntu" ]]; then
sudo apt-get update sudo apt-get update
sudo apt-get -y install nodejs git yarn sudo apt-get -y install nodejs git yarn
elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=\"centos\"" ]]; then elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=centos" ]]; then
sudo yum -y install epel-release sudo yum -y install epel-release
sudo yum -y install nodejs git yarn sudo yum -y install nodejs git yarn
elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=\"arch\"" ]]; then elif [[ "$(cat /etc/*-release | grep '^ID=')" = "ID=arch" ]]; then
sudo pacman -Syu -y sudo pacman -Syu -y
sudo pacman -S -y nodejs git yarn sudo pacman -S -y nodejs git yarn
else else