PageLayout.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import HeaderBar from './HeaderBar.js';
  16. import { Layout } from '@douyinfe/semi-ui';
  17. import SiderBar from './SiderBar.js';
  18. import App from '../../App.js';
  19. import FooterBar from './Footer.js';
  20. import { ToastContainer } from 'react-toastify';
  21. import React, { useContext, useEffect, useState } from 'react';
  22. import { useIsMobile } from '../../hooks/common/useIsMobile.js';
  23. import { useSidebarCollapsed } from '../../hooks/common/useSidebarCollapsed.js';
  24. import { useTranslation } from 'react-i18next';
  25. import { API, getLogo, getSystemName, showError, setStatusData } from '../../helpers/index.js';
  26. import { UserContext } from '../../context/User/index.js';
  27. import { StatusContext } from '../../context/Status/index.js';
  28. import { useLocation } from 'react-router-dom';
  29. const { Sider, Content, Header } = Layout;
  30. const PageLayout = () => {
  31. const [, userDispatch] = useContext(UserContext);
  32. const [, statusDispatch] = useContext(StatusContext);
  33. const isMobile = useIsMobile();
  34. const [collapsed, , setCollapsed] = useSidebarCollapsed();
  35. const [drawerOpen, setDrawerOpen] = useState(false);
  36. const { i18n } = useTranslation();
  37. const location = useLocation();
  38. const shouldHideFooter = location.pathname.startsWith('/console') || location.pathname === '/pricing';
  39. const shouldInnerPadding = location.pathname.includes('/console') &&
  40. !location.pathname.startsWith('/console/chat') &&
  41. location.pathname !== '/console/playground';
  42. const isConsoleRoute = location.pathname.startsWith('/console');
  43. const showSider = isConsoleRoute && (!isMobile || drawerOpen);
  44. useEffect(() => {
  45. if (isMobile && drawerOpen && collapsed) {
  46. setCollapsed(false);
  47. }
  48. }, [isMobile, drawerOpen, collapsed, setCollapsed]);
  49. const loadUser = () => {
  50. let user = localStorage.getItem('user');
  51. if (user) {
  52. let data = JSON.parse(user);
  53. userDispatch({ type: 'login', payload: data });
  54. }
  55. };
  56. const loadStatus = async () => {
  57. try {
  58. const res = await API.get('/api/status');
  59. const { success, data } = res.data;
  60. if (success) {
  61. statusDispatch({ type: 'set', payload: data });
  62. setStatusData(data);
  63. } else {
  64. showError('Unable to connect to server');
  65. }
  66. } catch (error) {
  67. showError('Failed to load status');
  68. }
  69. };
  70. useEffect(() => {
  71. loadUser();
  72. loadStatus().catch(console.error);
  73. let systemName = getSystemName();
  74. if (systemName) {
  75. document.title = systemName;
  76. }
  77. let logo = getLogo();
  78. if (logo) {
  79. let linkElement = document.querySelector("link[rel~='icon']");
  80. if (linkElement) {
  81. linkElement.href = logo;
  82. }
  83. }
  84. const savedLang = localStorage.getItem('i18nextLng');
  85. if (savedLang) {
  86. i18n.changeLanguage(savedLang);
  87. }
  88. }, [i18n]);
  89. return (
  90. <Layout
  91. style={{
  92. height: '100vh',
  93. display: 'flex',
  94. flexDirection: 'column',
  95. overflow: isMobile ? 'visible' : 'hidden',
  96. }}
  97. >
  98. <Header
  99. style={{
  100. padding: 0,
  101. height: 'auto',
  102. lineHeight: 'normal',
  103. position: 'fixed',
  104. width: '100%',
  105. top: 0,
  106. zIndex: 100,
  107. }}
  108. >
  109. <HeaderBar onMobileMenuToggle={() => setDrawerOpen(prev => !prev)} drawerOpen={drawerOpen} />
  110. </Header>
  111. <Layout
  112. style={{
  113. overflow: isMobile ? 'visible' : 'auto',
  114. display: 'flex',
  115. flexDirection: 'column',
  116. }}
  117. >
  118. {showSider && (
  119. <Sider
  120. style={{
  121. position: 'fixed',
  122. left: 0,
  123. top: '64px',
  124. zIndex: 99,
  125. border: 'none',
  126. paddingRight: '0',
  127. height: 'calc(100vh - 64px)',
  128. width: 'var(--sidebar-current-width)',
  129. }}
  130. >
  131. <SiderBar onNavigate={() => { if (isMobile) setDrawerOpen(false); }} />
  132. </Sider>
  133. )}
  134. <Layout
  135. style={{
  136. marginLeft: isMobile ? '0' : showSider ? 'var(--sidebar-current-width)' : '0',
  137. flex: '1 1 auto',
  138. display: 'flex',
  139. flexDirection: 'column',
  140. }}
  141. >
  142. <Content
  143. style={{
  144. flex: '1 0 auto',
  145. overflowY: isMobile ? 'visible' : 'hidden',
  146. WebkitOverflowScrolling: 'touch',
  147. padding: shouldInnerPadding ? (isMobile ? '5px' : '24px') : '0',
  148. position: 'relative',
  149. }}
  150. >
  151. <App />
  152. </Content>
  153. {!shouldHideFooter && (
  154. <Layout.Footer
  155. style={{
  156. flex: '0 0 auto',
  157. width: '100%',
  158. }}
  159. >
  160. <FooterBar />
  161. </Layout.Footer>
  162. )}
  163. </Layout>
  164. </Layout>
  165. <ToastContainer />
  166. </Layout>
  167. );
  168. };
  169. export default PageLayout;