sidebar.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { useEffect, useRef } from "react";
  2. import styles from "./home.module.scss";
  3. import { IconButton } from "./button";
  4. import SettingsIcon from "../icons/settings.svg";
  5. import GithubIcon from "../icons/github.svg";
  6. import ChatGptIcon from "../icons/chatgpt.svg";
  7. import AddIcon from "../icons/add.svg";
  8. import CloseIcon from "../icons/close.svg";
  9. import Locale from "../locales";
  10. import { useAppConfig, useChatStore } from "../store";
  11. import {
  12. MAX_SIDEBAR_WIDTH,
  13. MIN_SIDEBAR_WIDTH,
  14. NARROW_SIDEBAR_WIDTH,
  15. Path,
  16. REPO_URL,
  17. } from "../constant";
  18. import { Link, useNavigate } from "react-router-dom";
  19. import { useMobileScreen } from "../utils";
  20. import dynamic from "next/dynamic";
  21. const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
  22. loading: () => null,
  23. });
  24. function useDragSideBar() {
  25. const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
  26. const config = useAppConfig();
  27. const startX = useRef(0);
  28. const startDragWidth = useRef(config.sidebarWidth ?? 300);
  29. const lastUpdateTime = useRef(Date.now());
  30. const handleMouseMove = useRef((e: MouseEvent) => {
  31. if (Date.now() < lastUpdateTime.current + 50) {
  32. return;
  33. }
  34. lastUpdateTime.current = Date.now();
  35. const d = e.clientX - startX.current;
  36. const nextWidth = limit(startDragWidth.current + d);
  37. config.update((config) => (config.sidebarWidth = nextWidth));
  38. });
  39. const handleMouseUp = useRef(() => {
  40. startDragWidth.current = config.sidebarWidth ?? 300;
  41. window.removeEventListener("mousemove", handleMouseMove.current);
  42. window.removeEventListener("mouseup", handleMouseUp.current);
  43. });
  44. const onDragMouseDown = (e: MouseEvent) => {
  45. startX.current = e.clientX;
  46. window.addEventListener("mousemove", handleMouseMove.current);
  47. window.addEventListener("mouseup", handleMouseUp.current);
  48. };
  49. const isMobileScreen = useMobileScreen();
  50. const shouldNarrow =
  51. !isMobileScreen && config.sidebarWidth < MIN_SIDEBAR_WIDTH;
  52. useEffect(() => {
  53. const barWidth = shouldNarrow
  54. ? NARROW_SIDEBAR_WIDTH
  55. : limit(config.sidebarWidth ?? 300);
  56. const sideBarWidth = isMobileScreen ? "100vw" : `${barWidth}px`;
  57. document.documentElement.style.setProperty("--sidebar-width", sideBarWidth);
  58. }, [config.sidebarWidth, isMobileScreen, shouldNarrow]);
  59. return {
  60. onDragMouseDown,
  61. shouldNarrow,
  62. };
  63. }
  64. export function SideBar(props: { className?: string }) {
  65. const chatStore = useChatStore();
  66. // drag side bar
  67. const { onDragMouseDown, shouldNarrow } = useDragSideBar();
  68. const navigate = useNavigate();
  69. return (
  70. <div
  71. className={`${styles.sidebar} ${props.className} ${
  72. shouldNarrow && styles["narrow-sidebar"]
  73. }`}
  74. >
  75. <div className={styles["sidebar-header"]}>
  76. <div className={styles["sidebar-title"]}>ChatGPT Next</div>
  77. <div className={styles["sidebar-sub-title"]}>
  78. Build your own AI assistant.
  79. </div>
  80. <div className={styles["sidebar-logo"]}>
  81. <ChatGptIcon />
  82. </div>
  83. </div>
  84. <div
  85. className={styles["sidebar-body"]}
  86. onClick={(e) => {
  87. if (e.target === e.currentTarget) {
  88. navigate(Path.Home);
  89. }
  90. }}
  91. >
  92. <ChatList narrow={shouldNarrow} />
  93. </div>
  94. <div className={styles["sidebar-tail"]}>
  95. <div className={styles["sidebar-actions"]}>
  96. <div className={styles["sidebar-action"] + " " + styles.mobile}>
  97. <IconButton
  98. icon={<CloseIcon />}
  99. onClick={chatStore.deleteSession}
  100. />
  101. </div>
  102. <div className={styles["sidebar-action"]}>
  103. <Link to={Path.Settings}>
  104. <IconButton icon={<SettingsIcon />} shadow />
  105. </Link>
  106. </div>
  107. <div className={styles["sidebar-action"]}>
  108. <a href={REPO_URL} target="_blank">
  109. <IconButton icon={<GithubIcon />} shadow />
  110. </a>
  111. </div>
  112. </div>
  113. <div>
  114. <IconButton
  115. icon={<AddIcon />}
  116. text={shouldNarrow ? undefined : Locale.Home.NewChat}
  117. onClick={() => {
  118. chatStore.newSession();
  119. }}
  120. shadow
  121. />
  122. </div>
  123. </div>
  124. <div
  125. className={styles["sidebar-drag"]}
  126. onMouseDown={(e) => onDragMouseDown(e as any)}
  127. ></div>
  128. </div>
  129. );
  130. }