}
- onClick={props.closeSettings}
+ onClick={() => navigate(Path.Home)}
bordered
title={Locale.Settings.Actions.Close}
/>
diff --git a/app/components/sidebar.tsx b/app/components/sidebar.tsx
new file mode 100644
index 000000000..338dec193
--- /dev/null
+++ b/app/components/sidebar.tsx
@@ -0,0 +1,135 @@
+import { useState, useEffect, useRef } from "react";
+
+import styles from "./home.module.scss";
+
+import { IconButton } from "./button";
+import SettingsIcon from "../icons/settings.svg";
+import GithubIcon from "../icons/github.svg";
+import ChatGptIcon from "../icons/chatgpt.svg";
+import AddIcon from "../icons/add.svg";
+import CloseIcon from "../icons/close.svg";
+import Locale from "../locales";
+
+import { useChatStore } from "../store";
+
+import { Path, REPO_URL } from "../constant";
+
+import { HashRouter as Router, Link, useNavigate } from "react-router-dom";
+import { useMobileScreen } from "../utils";
+import { ChatList } from "./chat-list";
+
+function useDragSideBar() {
+ const limit = (x: number) => Math.min(500, Math.max(220, x));
+
+ const chatStore = useChatStore();
+ const startX = useRef(0);
+ const startDragWidth = useRef(chatStore.config.sidebarWidth ?? 300);
+ const lastUpdateTime = useRef(Date.now());
+
+ const handleMouseMove = useRef((e: MouseEvent) => {
+ if (Date.now() < lastUpdateTime.current + 100) {
+ return;
+ }
+ lastUpdateTime.current = Date.now();
+ const d = e.clientX - startX.current;
+ const nextWidth = limit(startDragWidth.current + d);
+ chatStore.updateConfig((config) => (config.sidebarWidth = nextWidth));
+ });
+
+ const handleMouseUp = useRef(() => {
+ startDragWidth.current = chatStore.config.sidebarWidth ?? 300;
+ window.removeEventListener("mousemove", handleMouseMove.current);
+ window.removeEventListener("mouseup", handleMouseUp.current);
+ });
+
+ const onDragMouseDown = (e: MouseEvent) => {
+ startX.current = e.clientX;
+
+ window.addEventListener("mousemove", handleMouseMove.current);
+ window.addEventListener("mouseup", handleMouseUp.current);
+ };
+ const isMobileScreen = useMobileScreen();
+
+ useEffect(() => {
+ const sideBarWidth = isMobileScreen
+ ? "100vw"
+ : `${limit(chatStore.config.sidebarWidth ?? 300)}px`;
+ document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
+ }, [chatStore.config.sidebarWidth, isMobileScreen]);
+
+ return {
+ onDragMouseDown,
+ };
+}
+
+export function SideBar(props: { setShowSideBar?: (_: boolean) => void }) {
+ const chatStore = useChatStore();
+
+ // drag side bar
+ const { onDragMouseDown } = useDragSideBar();
+ const navigate = useNavigate();
+ const isMobileScreen = useMobileScreen();
+
+ return (
+ <>
+
+
ChatGPT Next
+
+ Build your own AI assistant.
+
+
+
+
+
+
+
{
+ if (e.target === e.currentTarget) {
+ navigate(Path.Home);
+ }
+ props.setShowSideBar?.(false);
+ }}
+ >
+
+
+
+
+
+
+ }
+ onClick={chatStore.deleteSession}
+ />
+
+
+
+ } shadow />
+
+
+
+
+
+ }
+ text={Locale.Home.NewChat}
+ onClick={() => {
+ chatStore.newSession();
+ props.setShowSideBar?.(false);
+ }}
+ shadow
+ />
+
+
+
+
onDragMouseDown(e as any)}
+ >
+ >
+ );
+}
diff --git a/app/constant.ts b/app/constant.ts
index 6f08ad756..687445462 100644
--- a/app/constant.ts
+++ b/app/constant.ts
@@ -6,3 +6,9 @@ export const UPDATE_URL = `${REPO_URL}#keep-updated`;
export const FETCH_COMMIT_URL = `https://api.github.com/repos/${OWNER}/${REPO}/commits?per_page=1`;
export const FETCH_TAG_URL = `https://api.github.com/repos/${OWNER}/${REPO}/tags?per_page=1`;
export const RUNTIME_CONFIG_DOM = "danger-runtime-config";
+
+export enum Path {
+ Home = "/",
+ Chat = "/chat",
+ Settings = "/settings",
+}
diff --git a/app/utils.ts b/app/utils.ts
index 9d6e90622..0af9fef5b 100644
--- a/app/utils.ts
+++ b/app/utils.ts
@@ -49,7 +49,7 @@ export function isIOS() {
}
export function useMobileScreen() {
- const [isMobileScreen_, setIsMobileScreen] = useState(false);
+ const [isMobileScreen_, setIsMobileScreen] = useState(isMobileScreen());
useEffect(() => {
const onResize = () => {
setIsMobileScreen(isMobileScreen());
diff --git a/package.json b/package.json
index 19047ad11..785005519 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.5",
+ "react-router-dom": "^6.10.0",
"rehype-highlight": "^6.0.0",
"rehype-katex": "^6.0.2",
"remark-breaks": "^3.0.2",
diff --git a/yarn.lock b/yarn.lock
index 342ea4a44..b7d9f8309 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1189,6 +1189,11 @@
tiny-glob "^0.2.9"
tslib "^2.4.0"
+"@remix-run/router@1.5.0":
+ version "1.5.0"
+ resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc"
+ integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==
+
"@rushstack/eslint-patch@^1.1.3":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
@@ -4296,6 +4301,21 @@ react-redux@^8.0.4:
react-is "^18.0.0"
use-sync-external-store "^1.0.0"
+react-router-dom@^6.10.0:
+ version "6.10.0"
+ resolved "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.10.0.tgz#090ddc5c84dc41b583ce08468c4007c84245f61f"
+ integrity sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==
+ dependencies:
+ "@remix-run/router" "1.5.0"
+ react-router "6.10.0"
+
+react-router@6.10.0:
+ version "6.10.0"
+ resolved "https://registry.npmmirror.com/react-router/-/react-router-6.10.0.tgz#230f824fde9dd0270781b5cb497912de32c0a971"
+ integrity sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==
+ dependencies:
+ "@remix-run/router" "1.5.0"
+
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"