mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	refactor: close #643 use react router
This commit is contained in:
		@@ -10,6 +10,8 @@ import {
 | 
			
		||||
import { useChatStore } from "../store";
 | 
			
		||||
 | 
			
		||||
import Locale from "../locales";
 | 
			
		||||
import { Link, useNavigate } from "react-router-dom";
 | 
			
		||||
import { Path } from "../constant";
 | 
			
		||||
 | 
			
		||||
export function ChatItem(props: {
 | 
			
		||||
  onClick?: () => void;
 | 
			
		||||
@@ -59,6 +61,7 @@ export function ChatList() {
 | 
			
		||||
      state.moveSession,
 | 
			
		||||
    ]);
 | 
			
		||||
  const chatStore = useChatStore();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const onDragEnd: OnDragEndResponder = (result) => {
 | 
			
		||||
    const { destination, source } = result;
 | 
			
		||||
@@ -94,7 +97,10 @@ export function ChatList() {
 | 
			
		||||
                id={item.id}
 | 
			
		||||
                index={i}
 | 
			
		||||
                selected={i === selectedIndex}
 | 
			
		||||
                onClick={() => selectSession(i)}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  navigate(Path.Chat);
 | 
			
		||||
                  selectSession(i);
 | 
			
		||||
                }}
 | 
			
		||||
                onDelete={() => chatStore.deleteSession(i)}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,8 @@ import styles from "./home.module.scss";
 | 
			
		||||
import chatStyle from "./chat.module.scss";
 | 
			
		||||
 | 
			
		||||
import { Input, Modal, showModal } from "./ui-lib";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { Path } from "../constant";
 | 
			
		||||
 | 
			
		||||
const Markdown = dynamic(
 | 
			
		||||
  async () => memo((await import("./markdown")).Markdown),
 | 
			
		||||
@@ -418,10 +420,7 @@ export function ChatActions(props: {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Chat(props: {
 | 
			
		||||
  showSideBar?: () => void;
 | 
			
		||||
  sideBarShowing?: boolean;
 | 
			
		||||
}) {
 | 
			
		||||
export function Chat() {
 | 
			
		||||
  type RenderMessage = Message & { preview?: boolean };
 | 
			
		||||
 | 
			
		||||
  const chatStore = useChatStore();
 | 
			
		||||
@@ -439,6 +438,7 @@ export function Chat(props: {
 | 
			
		||||
  const { scrollRef, setAutoScroll, scrollToBottom } = useScrollToBottom();
 | 
			
		||||
  const [hitBottom, setHitBottom] = useState(false);
 | 
			
		||||
  const isMobileScreen = useMobileScreen();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
 | 
			
		||||
  const onChatBodyScroll = (e: HTMLElement) => {
 | 
			
		||||
    const isTouchBottom = e.scrollTop + e.clientHeight >= e.scrollHeight - 20;
 | 
			
		||||
@@ -641,7 +641,7 @@ export function Chat(props: {
 | 
			
		||||
 | 
			
		||||
  // Auto focus
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (props.sideBarShowing && isMobileScreen) return;
 | 
			
		||||
    if (isMobileScreen) return;
 | 
			
		||||
    inputRef.current?.focus();
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, []);
 | 
			
		||||
@@ -666,7 +666,7 @@ export function Chat(props: {
 | 
			
		||||
              icon={<ReturnIcon />}
 | 
			
		||||
              bordered
 | 
			
		||||
              title={Locale.Chat.Actions.ChatList}
 | 
			
		||||
              onClick={props?.showSideBar}
 | 
			
		||||
              onClick={() => navigate(Path.Home)}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["window-action-button"]}>
 | 
			
		||||
@@ -830,7 +830,7 @@ export function Chat(props: {
 | 
			
		||||
              setAutoScroll(false);
 | 
			
		||||
              setTimeout(() => setPromptHints([]), 500);
 | 
			
		||||
            }}
 | 
			
		||||
            autoFocus={!props?.sideBarShowing}
 | 
			
		||||
            autoFocus
 | 
			
		||||
            rows={inputRows}
 | 
			
		||||
          />
 | 
			
		||||
          <IconButton
 | 
			
		||||
 
 | 
			
		||||
@@ -2,32 +2,32 @@
 | 
			
		||||
 | 
			
		||||
require("../polyfill");
 | 
			
		||||
 | 
			
		||||
import { useState, useEffect, useRef } from "react";
 | 
			
		||||
import { useState, useEffect } from "react";
 | 
			
		||||
 | 
			
		||||
import { IconButton } from "./button";
 | 
			
		||||
import styles from "./home.module.scss";
 | 
			
		||||
 | 
			
		||||
import SettingsIcon from "../icons/settings.svg";
 | 
			
		||||
import GithubIcon from "../icons/github.svg";
 | 
			
		||||
import ChatGptIcon from "../icons/chatgpt.svg";
 | 
			
		||||
 | 
			
		||||
import BotIcon from "../icons/bot.svg";
 | 
			
		||||
import AddIcon from "../icons/add.svg";
 | 
			
		||||
import LoadingIcon from "../icons/three-dots.svg";
 | 
			
		||||
import CloseIcon from "../icons/close.svg";
 | 
			
		||||
 | 
			
		||||
import { useChatStore } from "../store";
 | 
			
		||||
import { getCSSVar, useMobileScreen } from "../utils";
 | 
			
		||||
import Locale from "../locales";
 | 
			
		||||
import { Chat } from "./chat";
 | 
			
		||||
 | 
			
		||||
import dynamic from "next/dynamic";
 | 
			
		||||
import { REPO_URL } from "../constant";
 | 
			
		||||
import { Path } from "../constant";
 | 
			
		||||
import { ErrorBoundary } from "./error";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  HashRouter as Router,
 | 
			
		||||
  Routes,
 | 
			
		||||
  Route,
 | 
			
		||||
  useNavigation,
 | 
			
		||||
  useLocation,
 | 
			
		||||
} from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
export function Loading(props: { noLogo?: boolean }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles["loading-content"]}>
 | 
			
		||||
    <div className={styles["loading-content"] + " no-dark"}>
 | 
			
		||||
      {!props.noLogo && <BotIcon />}
 | 
			
		||||
      <LoadingIcon />
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -38,7 +38,7 @@ const Settings = dynamic(async () => (await import("./settings")).Settings, {
 | 
			
		||||
  loading: () => <Loading noLogo />,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
 | 
			
		||||
const SideBar = dynamic(async () => (await import("./sidebar")).SideBar, {
 | 
			
		||||
  loading: () => <Loading noLogo />,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -73,50 +73,6 @@ function useSwitchTheme() {
 | 
			
		||||
  }, [config.theme]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useHasHydrated = () => {
 | 
			
		||||
  const [hasHydrated, setHasHydrated] = useState<boolean>(false);
 | 
			
		||||
 | 
			
		||||
@@ -127,130 +83,64 @@ const useHasHydrated = () => {
 | 
			
		||||
  return hasHydrated;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function _Home() {
 | 
			
		||||
  const [createNewSession, currentIndex, removeSession] = useChatStore(
 | 
			
		||||
    (state) => [
 | 
			
		||||
      state.newSession,
 | 
			
		||||
      state.currentSessionIndex,
 | 
			
		||||
      state.removeSession,
 | 
			
		||||
    ],
 | 
			
		||||
  );
 | 
			
		||||
  const chatStore = useChatStore();
 | 
			
		||||
  const loading = !useHasHydrated();
 | 
			
		||||
  const [showSideBar, setShowSideBar] = useState(true);
 | 
			
		||||
 | 
			
		||||
function WideScreen() {
 | 
			
		||||
  // setting
 | 
			
		||||
  const [openSettings, setOpenSettings] = useState(false);
 | 
			
		||||
  const config = useChatStore((state) => state.config);
 | 
			
		||||
 | 
			
		||||
  // drag side bar
 | 
			
		||||
  const { onDragMouseDown } = useDragSideBar();
 | 
			
		||||
  const isMobileScreen = useMobileScreen();
 | 
			
		||||
 | 
			
		||||
  useSwitchTheme();
 | 
			
		||||
 | 
			
		||||
  if (loading) {
 | 
			
		||||
    return <Loading />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      className={`${
 | 
			
		||||
        config.tightBorder && !isMobileScreen
 | 
			
		||||
          ? styles["tight-container"]
 | 
			
		||||
          : styles.container
 | 
			
		||||
        config.tightBorder ? styles["tight-container"] : styles.container
 | 
			
		||||
      }`}
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        className={styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}`}
 | 
			
		||||
      >
 | 
			
		||||
        <div className={styles["sidebar-header"]}>
 | 
			
		||||
          <div className={styles["sidebar-title"]}>ChatGPT Next</div>
 | 
			
		||||
          <div className={styles["sidebar-sub-title"]}>
 | 
			
		||||
            Build your own AI assistant.
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["sidebar-logo"]}>
 | 
			
		||||
            <ChatGptIcon />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles["sidebar-body"]}
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
            setOpenSettings(false);
 | 
			
		||||
            setShowSideBar(false);
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <ChatList />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div className={styles["sidebar-tail"]}>
 | 
			
		||||
          <div className={styles["sidebar-actions"]}>
 | 
			
		||||
            <div className={styles["sidebar-action"] + " " + styles.mobile}>
 | 
			
		||||
              <IconButton
 | 
			
		||||
                icon={<CloseIcon />}
 | 
			
		||||
                onClick={chatStore.deleteSession}
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles["sidebar-action"]}>
 | 
			
		||||
              <IconButton
 | 
			
		||||
                icon={<SettingsIcon />}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  setOpenSettings(true);
 | 
			
		||||
                  setShowSideBar(false);
 | 
			
		||||
                }}
 | 
			
		||||
                shadow
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles["sidebar-action"]}>
 | 
			
		||||
              <a href={REPO_URL} target="_blank">
 | 
			
		||||
                <IconButton icon={<GithubIcon />} shadow />
 | 
			
		||||
              </a>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div>
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<AddIcon />}
 | 
			
		||||
              text={Locale.Home.NewChat}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                createNewSession();
 | 
			
		||||
                setShowSideBar(false);
 | 
			
		||||
              }}
 | 
			
		||||
              shadow
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
          className={styles["sidebar-drag"]}
 | 
			
		||||
          onMouseDown={(e) => onDragMouseDown(e as any)}
 | 
			
		||||
        ></div>
 | 
			
		||||
      <div className={styles.sidebar}>
 | 
			
		||||
        <SideBar></SideBar>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className={styles["window-content"]}>
 | 
			
		||||
        {openSettings ? (
 | 
			
		||||
          <Settings
 | 
			
		||||
            closeSettings={() => {
 | 
			
		||||
              setOpenSettings(false);
 | 
			
		||||
              setShowSideBar(true);
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        ) : (
 | 
			
		||||
          <Chat
 | 
			
		||||
            key="chat"
 | 
			
		||||
            showSideBar={() => setShowSideBar(true)}
 | 
			
		||||
            sideBarShowing={showSideBar}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
        <Routes>
 | 
			
		||||
          <Route path={Path.Home} element={<Chat />} />
 | 
			
		||||
          <Route path={Path.Chat} element={<Chat />} />
 | 
			
		||||
          <Route path={Path.Settings} element={<Settings />} />
 | 
			
		||||
        </Routes>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function MobileScreen() {
 | 
			
		||||
  const location = useLocation();
 | 
			
		||||
  const isHome = location.pathname === Path.Home;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.container}>
 | 
			
		||||
      <div className={`${styles.sidebar} ${isHome && styles["sidebar-show"]}`}>
 | 
			
		||||
        <SideBar />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className={styles["window-content"]}>
 | 
			
		||||
        <Routes>
 | 
			
		||||
          <Route path={Path.Home} element={null} />
 | 
			
		||||
          <Route path={Path.Chat} element={<Chat />} />
 | 
			
		||||
          <Route path={Path.Settings} element={<Settings />} />
 | 
			
		||||
        </Routes>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Home() {
 | 
			
		||||
  useSwitchTheme();
 | 
			
		||||
 | 
			
		||||
  const isMobileScreen = useMobileScreen();
 | 
			
		||||
 | 
			
		||||
  if (!useHasHydrated()) {
 | 
			
		||||
    return <Loading />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ErrorBoundary>
 | 
			
		||||
      <_Home></_Home>
 | 
			
		||||
      <Router>{isMobileScreen ? <MobileScreen /> : <WideScreen />}</Router>
 | 
			
		||||
    </ErrorBoundary>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,10 +29,11 @@ import { Avatar } from "./chat";
 | 
			
		||||
import Locale, { AllLangs, changeLang, getLang } from "../locales";
 | 
			
		||||
import { copyToClipboard, getEmojiUrl } from "../utils";
 | 
			
		||||
import Link from "next/link";
 | 
			
		||||
import { UPDATE_URL } from "../constant";
 | 
			
		||||
import { Path, UPDATE_URL } from "../constant";
 | 
			
		||||
import { Prompt, SearchService, usePromptStore } from "../store/prompt";
 | 
			
		||||
import { ErrorBoundary } from "./error";
 | 
			
		||||
import { InputRange } from "./input-range";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
function UserPromptModal(props: { onClose?: () => void }) {
 | 
			
		||||
  const promptStore = usePromptStore();
 | 
			
		||||
@@ -176,7 +177,8 @@ function PasswordInput(props: HTMLProps<HTMLInputElement>) {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
export function Settings() {
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
 | 
			
		||||
  const [config, updateConfig, resetConfig, clearAllData, clearSessions] =
 | 
			
		||||
    useChatStore((state) => [
 | 
			
		||||
@@ -235,7 +237,7 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const keydownEvent = (e: KeyboardEvent) => {
 | 
			
		||||
      if (e.key === "Escape") {
 | 
			
		||||
        props.closeSettings();
 | 
			
		||||
        navigate(Path.Home);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
    document.addEventListener("keydown", keydownEvent);
 | 
			
		||||
@@ -290,7 +292,7 @@ export function Settings(props: { closeSettings: () => void }) {
 | 
			
		||||
          <div className={styles["window-action-button"]}>
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<CloseIcon />}
 | 
			
		||||
              onClick={props.closeSettings}
 | 
			
		||||
              onClick={() => navigate(Path.Home)}
 | 
			
		||||
              bordered
 | 
			
		||||
              title={Locale.Settings.Actions.Close}
 | 
			
		||||
            />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										135
									
								
								app/components/sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								app/components/sidebar.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -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 (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className={styles["sidebar-header"]}>
 | 
			
		||||
        <div className={styles["sidebar-title"]}>ChatGPT Next</div>
 | 
			
		||||
        <div className={styles["sidebar-sub-title"]}>
 | 
			
		||||
          Build your own AI assistant.
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className={styles["sidebar-logo"]}>
 | 
			
		||||
          <ChatGptIcon />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div
 | 
			
		||||
        className={styles["sidebar-body"]}
 | 
			
		||||
        onClick={(e) => {
 | 
			
		||||
          if (e.target === e.currentTarget) {
 | 
			
		||||
            navigate(Path.Home);
 | 
			
		||||
          }
 | 
			
		||||
          props.setShowSideBar?.(false);
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <ChatList />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className={styles["sidebar-tail"]}>
 | 
			
		||||
        <div className={styles["sidebar-actions"]}>
 | 
			
		||||
          <div className={styles["sidebar-action"] + " " + styles.mobile}>
 | 
			
		||||
            <IconButton
 | 
			
		||||
              icon={<CloseIcon />}
 | 
			
		||||
              onClick={chatStore.deleteSession}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["sidebar-action"]}>
 | 
			
		||||
            <Link to={Path.Settings}>
 | 
			
		||||
              <IconButton icon={<SettingsIcon />} shadow />
 | 
			
		||||
            </Link>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div className={styles["sidebar-action"]}>
 | 
			
		||||
            <a href={REPO_URL} target="_blank">
 | 
			
		||||
              <IconButton icon={<GithubIcon />} shadow />
 | 
			
		||||
            </a>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <IconButton
 | 
			
		||||
            icon={<AddIcon />}
 | 
			
		||||
            text={Locale.Home.NewChat}
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              chatStore.newSession();
 | 
			
		||||
              props.setShowSideBar?.(false);
 | 
			
		||||
            }}
 | 
			
		||||
            shadow
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div
 | 
			
		||||
        className={styles["sidebar-drag"]}
 | 
			
		||||
        onMouseDown={(e) => onDragMouseDown(e as any)}
 | 
			
		||||
      ></div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -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",
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								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"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user