home.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. "use client";
  2. import { useState, useRef, useEffect, useLayoutEffect } from "react";
  3. import { IconButton } from "./button";
  4. import styles from "./home.module.scss";
  5. import SettingsIcon from "../icons/settings.svg";
  6. import GithubIcon from "../icons/github.svg";
  7. import ChatGptIcon from "../icons/chatgpt.svg";
  8. import BotIcon from "../icons/bot.svg";
  9. import AddIcon from "../icons/add.svg";
  10. import LoadingIcon from "../icons/three-dots.svg";
  11. import CloseIcon from "../icons/close.svg";
  12. import {
  13. Message,
  14. SubmitKey,
  15. useChatStore,
  16. ChatSession,
  17. BOT_HELLO,
  18. } from "../store";
  19. import {
  20. copyToClipboard,
  21. downloadAs,
  22. isMobileScreen,
  23. selectOrCopy,
  24. } from "../utils";
  25. import Locale from "../locales";
  26. import { ChatList } from "./chat-list";
  27. import { Chat } from "./chat";
  28. import dynamic from "next/dynamic";
  29. import { REPO_URL } from "../constant";
  30. export function Loading(props: { noLogo?: boolean }) {
  31. return (
  32. <div className={styles["loading-content"]}>
  33. {!props.noLogo && <BotIcon />}
  34. <LoadingIcon />
  35. </div>
  36. );
  37. }
  38. const Settings = dynamic(async () => (await import("./settings")).Settings, {
  39. loading: () => <Loading noLogo />,
  40. });
  41. function useSwitchTheme() {
  42. const config = useChatStore((state) => state.config);
  43. useEffect(() => {
  44. document.body.classList.remove("light");
  45. document.body.classList.remove("dark");
  46. if (config.theme === "dark") {
  47. document.body.classList.add("dark");
  48. } else if (config.theme === "light") {
  49. document.body.classList.add("light");
  50. }
  51. const themeColor = getComputedStyle(document.body)
  52. .getPropertyValue("--theme-color")
  53. .trim();
  54. const metaDescription = document.querySelector('meta[name="theme-color"]');
  55. metaDescription?.setAttribute("content", themeColor);
  56. }, [config.theme]);
  57. }
  58. const useHasHydrated = () => {
  59. const [hasHydrated, setHasHydrated] = useState<boolean>(false);
  60. useEffect(() => {
  61. setHasHydrated(true);
  62. }, []);
  63. return hasHydrated;
  64. };
  65. export function Home() {
  66. const [createNewSession, currentIndex, removeSession] = useChatStore(
  67. (state) => [
  68. state.newSession,
  69. state.currentSessionIndex,
  70. state.removeSession,
  71. ],
  72. );
  73. const loading = !useHasHydrated();
  74. const [showSideBar, setShowSideBar] = useState(true);
  75. // setting
  76. const [openSettings, setOpenSettings] = useState(false);
  77. const config = useChatStore((state) => state.config);
  78. useSwitchTheme();
  79. if (loading) {
  80. return <Loading />;
  81. }
  82. return (
  83. <div
  84. className={`${
  85. config.tightBorder && !isMobileScreen()
  86. ? styles["tight-container"]
  87. : styles.container
  88. }`}
  89. >
  90. <div
  91. className={styles.sidebar + ` ${showSideBar && styles["sidebar-show"]}`}
  92. >
  93. <div className={styles["sidebar-header"]}>
  94. <div className={styles["sidebar-title"]}>ChatGPT Next</div>
  95. <div className={styles["sidebar-sub-title"]}>
  96. Build your own AI assistant.
  97. </div>
  98. <div className={styles["sidebar-logo"]}>
  99. <ChatGptIcon />
  100. </div>
  101. </div>
  102. <div
  103. className={styles["sidebar-body"]}
  104. onClick={() => {
  105. setOpenSettings(false);
  106. setShowSideBar(false);
  107. }}
  108. >
  109. <ChatList />
  110. </div>
  111. <div className={styles["sidebar-tail"]}>
  112. <div className={styles["sidebar-actions"]}>
  113. <div className={styles["sidebar-action"] + " " + styles.mobile}>
  114. <IconButton
  115. icon={<CloseIcon />}
  116. onClick={() => {
  117. if (confirm(Locale.Home.DeleteChat)) {
  118. removeSession(currentIndex);
  119. }
  120. }}
  121. />
  122. </div>
  123. <div className={styles["sidebar-action"]}>
  124. <IconButton
  125. icon={<SettingsIcon />}
  126. onClick={() => {
  127. setOpenSettings(true);
  128. setShowSideBar(false);
  129. }}
  130. shadow
  131. />
  132. </div>
  133. <div className={styles["sidebar-action"]}>
  134. <a href={REPO_URL} target="_blank">
  135. <IconButton icon={<GithubIcon />} shadow />
  136. </a>
  137. </div>
  138. </div>
  139. <div>
  140. <IconButton
  141. icon={<AddIcon />}
  142. text={Locale.Home.NewChat}
  143. onClick={() => {
  144. createNewSession();
  145. setShowSideBar(false);
  146. }}
  147. shadow
  148. />
  149. </div>
  150. </div>
  151. </div>
  152. <div className={styles["window-content"]}>
  153. {openSettings ? (
  154. <Settings
  155. closeSettings={() => {
  156. setOpenSettings(false);
  157. setShowSideBar(true);
  158. }}
  159. />
  160. ) : (
  161. <Chat
  162. key="chat"
  163. showSideBar={() => setShowSideBar(true)}
  164. sideBarShowing={showSideBar}
  165. />
  166. )}
  167. </div>
  168. </div>
  169. );
  170. }