home.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. "use client";
  2. require("../../polyfill");
  3. import { useState, useEffect } from "react";
  4. import styles from "./home.module.scss";
  5. import BotIcon from "@/app/icons/bot.svg";
  6. import LoadingIcon from "@/app/icons/three-dots.svg";
  7. import { getCSSVar, useMobileScreen } from "@/app/utils";
  8. import dynamic from "next/dynamic";
  9. import { Path, SlotID } from "@/app/constant";
  10. import { ErrorBoundary } from "@/app/components/error";
  11. import { getISOLang, getLang } from "@/app/locales";
  12. import {
  13. HashRouter as Router,
  14. Routes,
  15. Route,
  16. useLocation,
  17. } from "react-router-dom";
  18. import { SideBar } from "@/app/components/sidebar";
  19. import { useAppConfig } from "@/app/store/config";
  20. import { AuthPage } from "@/app/components/auth/auth";
  21. import { getClientConfig } from "@/app/config/client";
  22. import { type ClientApi, getClientApi } from "@/app/client/api";
  23. import { useAccessStore } from "@/app/store";
  24. export function Loading(props: { noLogo?: boolean }) {
  25. return (
  26. <div className={styles["loading-content"] + " no-dark"}>
  27. {!props.noLogo && <BotIcon />}
  28. <LoadingIcon />
  29. </div>
  30. );
  31. }
  32. const Settings = dynamic(
  33. async () => (await import("@/app/components/settings/settings")).Settings,
  34. {
  35. loading: () => <Loading noLogo />,
  36. },
  37. );
  38. const Chat = dynamic(
  39. async () => (await import("@/app/components/chat/chat")).Chat,
  40. {
  41. loading: () => <Loading noLogo />,
  42. },
  43. );
  44. const NewChat = dynamic(
  45. async () => (await import("@/app/components/new-chat/new-chat")).NewChat,
  46. {
  47. loading: () => <Loading noLogo />,
  48. },
  49. );
  50. const MaskPage = dynamic(
  51. async () => (await import("@/app/components/mask/mask")).MaskPage,
  52. {
  53. loading: () => <Loading noLogo />,
  54. },
  55. );
  56. export function useSwitchTheme() {
  57. const config = useAppConfig();
  58. useEffect(() => {
  59. document.body.classList.remove("light");
  60. document.body.classList.remove("dark");
  61. if (config.theme === "dark") {
  62. document.body.classList.add("dark");
  63. } else if (config.theme === "light") {
  64. document.body.classList.add("light");
  65. }
  66. const metaDescriptionDark = document.querySelector(
  67. 'meta[name="theme-color"][media*="dark"]',
  68. );
  69. const metaDescriptionLight = document.querySelector(
  70. 'meta[name="theme-color"][media*="light"]',
  71. );
  72. if (config.theme === "auto") {
  73. metaDescriptionDark?.setAttribute("content", "#151515");
  74. metaDescriptionLight?.setAttribute("content", "#fafafa");
  75. } else {
  76. const themeColor = getCSSVar("--theme-color");
  77. metaDescriptionDark?.setAttribute("content", themeColor);
  78. metaDescriptionLight?.setAttribute("content", themeColor);
  79. }
  80. }, [config.theme]);
  81. }
  82. function useHtmlLang() {
  83. useEffect(() => {
  84. const lang = getISOLang();
  85. const htmlLang = document.documentElement.lang;
  86. if (lang !== htmlLang) {
  87. document.documentElement.lang = lang;
  88. }
  89. }, []);
  90. }
  91. const useHasHydrated = () => {
  92. const [hasHydrated, setHasHydrated] = useState<boolean>(false);
  93. useEffect(() => {
  94. setHasHydrated(true);
  95. }, []);
  96. return hasHydrated;
  97. };
  98. const loadAsyncGoogleFont = () => {
  99. const linkEl = document.createElement("link");
  100. const proxyFontUrl = "/google-fonts";
  101. const remoteFontUrl = "https://fonts.googleapis.com";
  102. const googleFontUrl =
  103. getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
  104. linkEl.rel = "stylesheet";
  105. linkEl.href =
  106. googleFontUrl +
  107. "/css2?family=" +
  108. encodeURIComponent("Noto Sans:wght@300;400;700;900") +
  109. "&display=swap";
  110. document.head.appendChild(linkEl);
  111. };
  112. function Screen() {
  113. const config = useAppConfig();
  114. const location = useLocation();
  115. const isHome = location.pathname === Path.Home;
  116. const isAuth = location.pathname === Path.Auth;
  117. const isMobileScreen = useMobileScreen();
  118. const shouldTightBorder =
  119. getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
  120. useEffect(() => {
  121. loadAsyncGoogleFont();
  122. }, []);
  123. return (
  124. <div
  125. className={
  126. styles.container +
  127. ` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
  128. getLang() === "ar" ? styles["rtl-screen"] : ""
  129. }`
  130. }
  131. >
  132. {isAuth ? (
  133. <>
  134. <AuthPage />
  135. </>
  136. ) : (
  137. <>
  138. <SideBar className={isHome ? styles["sidebar-show"] : ""} />
  139. <div className={styles["window-content"]} id={SlotID.AppBody}>
  140. <Routes>
  141. <Route path={Path.Home} element={<Chat />} />
  142. <Route path={Path.NewChat} element={<NewChat />} />
  143. <Route path={Path.Masks} element={<MaskPage />} />
  144. <Route path={Path.Chat} element={<Chat />} />
  145. <Route path={Path.Settings} element={<Settings />} />
  146. </Routes>
  147. </div>
  148. </>
  149. )}
  150. </div>
  151. );
  152. }
  153. export function useLoadData() {
  154. const config = useAppConfig();
  155. const api: ClientApi = getClientApi(config.modelConfig.providerName);
  156. useEffect(() => {
  157. (async () => {
  158. const models = await api.llm.models();
  159. config.mergeModels(models);
  160. })();
  161. // eslint-disable-next-line react-hooks/exhaustive-deps
  162. }, []);
  163. }
  164. export function Home() {
  165. useSwitchTheme();
  166. useLoadData();
  167. useHtmlLang();
  168. useEffect(() => {
  169. console.log("[Config] got config from build time", getClientConfig());
  170. useAccessStore.getState().fetch();
  171. }, []);
  172. if (!useHasHydrated()) {
  173. return <Loading />;
  174. }
  175. return (
  176. <ErrorBoundary>
  177. <Router>
  178. <Screen />
  179. </Router>
  180. </ErrorBoundary>
  181. );
  182. }