forked from XiaoMo/ChatGPT-Next-Web
Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
This commit is contained in:
commit
4fcb1498c2
@ -59,6 +59,4 @@ export async function POST(req: NextRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const runtime = "experimental-edge";
|
||||||
runtime: "edge",
|
|
||||||
};
|
|
||||||
|
@ -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",
|
|
||||||
};
|
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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,14 +181,13 @@ 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,
|
]);
|
||||||
]);
|
|
||||||
|
|
||||||
const updateStore = useUpdateStore();
|
const updateStore = useUpdateStore();
|
||||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||||
@ -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) =>
|
||||||
|
@ -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,
|
||||||
|
@ -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"]}>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SubmitKey } from "../store/app";
|
import { SubmitKey } from "../store/config";
|
||||||
|
|
||||||
const cn = {
|
const cn = {
|
||||||
WIP: "该功能仍在开发中……",
|
WIP: "该功能仍在开发中……",
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SubmitKey } from "../store/app";
|
import { SubmitKey } from "../store/config";
|
||||||
|
|
||||||
const jp = {
|
const jp = {
|
||||||
WIP: "この機能は開発中です……",
|
WIP: "この機能は開発中です……",
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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);
|
||||||
|
160
app/store/app.ts
160
app/store/app.ts
@ -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
135
app/store/config.ts
Normal 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,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
@ -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";
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user