Przeglądaj źródła

✨ feat(homepage): enhance banner visuals & UX

• Added read-only Base URL input that shows `status.server_address` (fallback `window.location.origin`) and copies value on click.
• Embedded `ScrollList` as input `suffix`; auto-cycles common endpoints every 3 s and allows manual selection.
• Introduced `API_ENDPOINTS` array in `web/src/constants/common.constant.js` for centralized endpoint management.
• Implemented custom CSS to hide ScrollList wheel indicators / scrollbars for a cleaner look.
• Created two blurred colour spheres behind the banner (`blur-ball-indigo`, `blur-ball-teal`) with light-/dark-mode opacity tweaks and lower vertical placement.
• Increased letter-spacing for Chinese heading via conditional `tracking-wide` / `md:tracking-wider` classes to improve readability.
• Misc: updated imports, helper functions, and responsive sizes to keep UI consistent across devices.
t0ng7u 6 miesięcy temu
rodzic
commit
29a44eb7ae

+ 1 - 3
web/src/components/layout/PageLayout.js

@@ -11,7 +11,7 @@ import { API, getLogo, getSystemName, showError, setStatusData } from '../../hel
 import { UserContext } from '../../context/User/index.js';
 import { StatusContext } from '../../context/Status/index.js';
 import { useLocation } from 'react-router-dom';
-const { Sider, Content, Header, Footer } = Layout;
+const { Sider, Content, Header } = Layout;
 
 const PageLayout = () => {
   const [userState, userDispatch] = useContext(UserContext);
@@ -94,8 +94,6 @@ const PageLayout = () => {
       </Header>
       <Layout
         style={{
-          marginTop: '64px',
-          height: 'calc(100vh - 64px)',
           overflow: styleState.isMobile ? 'visible' : 'auto',
           display: 'flex',
           flexDirection: 'column',

Plik diff jest za duży
+ 713 - 716
web/src/components/settings/PersonalSetting.js


+ 2 - 2
web/src/components/table/ModelPricing.js

@@ -523,10 +523,10 @@ const ModelPricing = () => {
     <div className="bg-gray-50">
       <Layout>
         <Layout.Content>
-          <div className="flex justify-center p-4 sm:p-6 md:p-8">
+          <div className="flex justify-center">
             <div className="w-full">
               {/* 主卡片容器 */}
-              <Card className="!rounded-2xl shadow-lg border-0">
+              <Card bordered={false} className="!rounded-2xl shadow-lg border-0">
                 {/* 顶部状态卡片 */}
                 <Card
                   className="!rounded-2xl !border-0 !shadow-md overflow-hidden mb-6"

+ 16 - 1
web/src/constants/common.constant.js

@@ -2,4 +2,19 @@ export const ITEMS_PER_PAGE = 10; // this value must keep same as the one define
 
 export const DEFAULT_ENDPOINT = '/api/ratio_config';
 
-export const TABLE_COMPACT_MODES_KEY = 'table_compact_modes';
+export const TABLE_COMPACT_MODES_KEY = 'table_compact_modes';
+
+export const API_ENDPOINTS = [
+  '/v1/chat/completions',
+  '/v1/responses',
+  '/v1/messages',
+  '/v1beta/models',
+  '/v1/embeddings',
+  '/v1/rerank',
+  '/v1/images/generations',
+  '/v1/images/edits',
+  '/v1/images/variations',
+  '/v1/audio/speech',
+  '/v1/audio/transcriptions',
+  '/v1/audio/translations'
+];

+ 2 - 2
web/src/i18n/locales/en.json

@@ -1422,8 +1422,8 @@
   "初始化系统": "Initialize system",
   "支持众多的大模型供应商": "Supporting various LLM providers",
   "统一的大模型接口网关": "The Unified LLMs API Gateway",
-  "更好的价格,更好的稳定性,无需订阅": "Better price, better stability, no subscription required",
-  "开始使用": "Get Started",
+  "更好的价格,更好的稳定性,只需要将模型基址替换为:": "Better price, better stability, no subscription required, just replace the model BASE URL with: ",
+  "获取密钥": "Get Key",
   "关于我们": "About Us",
   "关于项目": "About Project",
   "联系我们": "Contact Us",

+ 62 - 0
web/src/index.css

@@ -530,4 +530,66 @@ code {
   -webkit-background-clip: text;
   background-clip: text;
   -webkit-text-fill-color: transparent;
+}
+
+/* ==================== ScrollList 定制样式 ==================== */
+.semi-scrolllist,
+.semi-scrolllist * {
+  -ms-overflow-style: none;
+  /* IE, Edge */
+  scrollbar-width: none;
+  /* Firefox */
+  background: transparent !important;
+}
+
+.semi-scrolllist::-webkit-scrollbar,
+.semi-scrolllist *::-webkit-scrollbar {
+  width: 0 !important;
+  height: 0 !important;
+  display: none !important;
+}
+
+.semi-scrolllist-body {
+  padding: 1px !important;
+}
+
+.semi-scrolllist-list-outer {
+  padding-right: 0 !important;
+}
+
+/* ==================== Banner 背景模糊球 ==================== */
+.blur-ball {
+  position: absolute;
+  width: 360px;
+  height: 360px;
+  border-radius: 50%;
+  filter: blur(120px);
+  pointer-events: none;
+  z-index: -1;
+}
+
+.blur-ball-indigo {
+  background: #6366f1;
+  /* indigo-500 */
+  top: 40px;
+  left: 50%;
+  transform: translateX(-50%);
+  opacity: 0.5;
+}
+
+.blur-ball-teal {
+  background: #14b8a6;
+  /* teal-400 */
+  top: 200px;
+  left: 30%;
+  opacity: 0.4;
+}
+
+/* 浅色主题下让模糊球更柔和 */
+html:not(.dark) .blur-ball-indigo {
+  opacity: 0.25;
+}
+
+html:not(.dark) .blur-ball-teal {
+  opacity: 0.2;
 }

+ 0 - 2
web/src/index.js

@@ -5,7 +5,6 @@ import '@douyinfe/semi-ui/dist/css/semi.css';
 import { UserProvider } from './context/User';
 import 'react-toastify/dist/ReactToastify.css';
 import { StatusProvider } from './context/Status';
-import { Layout } from '@douyinfe/semi-ui';
 import { ThemeProvider } from './context/Theme';
 import { StyleProvider } from './context/Style/index.js';
 import PageLayout from './components/layout/PageLayout.js';
@@ -15,7 +14,6 @@ import './index.css';
 // initialization
 
 const root = ReactDOM.createRoot(document.getElementById('root'));
-const { Sider, Content, Header, Footer } = Layout;
 root.render(
   <React.StrictMode>
     <StatusProvider>

+ 2 - 2
web/src/pages/About/index.js

@@ -105,7 +105,7 @@ const About = () => {
   );
 
   return (
-    <>
+    <div className="mt-[64px]">
       {aboutLoaded && about === '' ? (
         <div className="flex justify-center items-center h-screen p-8">
           <Empty
@@ -132,7 +132,7 @@ const About = () => {
           )}
         </>
       )}
-    </>
+    </div>
   );
 };
 

+ 2 - 2
web/src/pages/Channel/index.js

@@ -3,9 +3,9 @@ import ChannelsTable from '../../components/table/ChannelsTable';
 
 const File = () => {
   return (
-    <>
+    <div className="mt-[64px]">
       <ChannelsTable />
-    </>
+    </div>
   );
 };
 

+ 2 - 2
web/src/pages/Chat/index.js

@@ -37,12 +37,12 @@ const ChatPage = () => {
   return !isLoading && iframeSrc ? (
     <iframe
       src={iframeSrc}
-      style={{ width: '100%', height: '100%', border: 'none' }}
+      style={{ width: '100%', height: 'calc(100vh - 64px)', border: 'none', marginTop: '64px' }}
       title='Token Frame'
       allow='camera;microphone'
     />
   ) : (
-    <div className="fixed inset-0 w-screen h-screen flex items-center justify-center bg-white/80 z-[1000]">
+    <div className="fixed inset-0 w-screen h-screen flex items-center justify-center bg-white/80 z-[1000] mt-[64px]">
       <div className="flex flex-col items-center">
         <Spin
           size="large"

+ 1 - 1
web/src/pages/Chat2Link/index.js

@@ -17,7 +17,7 @@ const chat2page = () => {
   }
 
   return (
-    <div>
+    <div className="mt-[64px]">
       <h3>正在加载,请稍候...</h3>
     </div>
   );

+ 1 - 1
web/src/pages/Detail/index.js

@@ -984,7 +984,7 @@ const Detail = (props) => {
   }, []);
 
   return (
-    <div className="bg-gray-50 h-full">
+    <div className="bg-gray-50 h-full mt-[64px]">
       <div className="flex items-center justify-between mb-4">
         <h2 className="text-2xl font-semibold text-gray-800">{getGreeting}</h2>
         <div className="flex gap-3">

+ 82 - 31
web/src/pages/Home/index.js

@@ -1,10 +1,11 @@
 import React, { useContext, useEffect, useState } from 'react';
-import { Button, Typography, Tag } from '@douyinfe/semi-ui';
-import { API, showError, isMobile } from '../../helpers';
+import { Button, Typography, Tag, Input, ScrollList, ScrollItem } from '@douyinfe/semi-ui';
+import { API, showError, isMobile, copy, showSuccess } from '../../helpers';
+import { API_ENDPOINTS } from '../../constants/common.constant';
 import { StatusContext } from '../../context/Status';
 import { marked } from 'marked';
 import { useTranslation } from 'react-i18next';
-import { IconGithubLogo, IconPlay, IconFile } from '@douyinfe/semi-icons';
+import { IconGithubLogo, IconPlay, IconFile, IconCopy } from '@douyinfe/semi-icons';
 import { Link } from 'react-router-dom';
 import NoticeModal from '../../components/layout/NoticeModal';
 import { Moonshot, OpenAI, XAI, Zhipu, Volcengine, Cohere, Claude, Gemini, Suno, Minimax, Wenxin, Spark, Qingyan, DeepSeek, Qwen, Midjourney, Grok, AzureAI, Hunyuan, Xinference } from '@lobehub/icons';
@@ -17,29 +18,12 @@ const Home = () => {
   const [homePageContentLoaded, setHomePageContentLoaded] = useState(false);
   const [homePageContent, setHomePageContent] = useState('');
   const [noticeVisible, setNoticeVisible] = useState(false);
-
   const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
   const docsLink = statusState?.status?.docs_link || '';
-
-  useEffect(() => {
-    const checkNoticeAndShow = async () => {
-      const lastCloseDate = localStorage.getItem('notice_close_date');
-      const today = new Date().toDateString();
-      if (lastCloseDate !== today) {
-        try {
-          const res = await API.get('/api/notice');
-          const { success, data } = res.data;
-          if (success && data && data.trim() !== '') {
-            setNoticeVisible(true);
-          }
-        } catch (error) {
-          console.error('获取公告失败:', error);
-        }
-      }
-    };
-
-    checkNoticeAndShow();
-  }, []);
+  const serverAddress = statusState?.status?.server_address || window.location.origin;
+  const endpointItems = API_ENDPOINTS.map((e) => ({ value: e }));
+  const [endpointIndex, setEndpointIndex] = useState(0);
+  const isChinese = i18n.language.startsWith('zh');
 
   const displayHomePageContent = async () => {
     setHomePageContent(localStorage.getItem('home_page_content') || '');
@@ -71,10 +55,44 @@ const Home = () => {
     setHomePageContentLoaded(true);
   };
 
+  const handleCopyBaseURL = async () => {
+    const ok = await copy(serverAddress);
+    if (ok) {
+      showSuccess(t('已复制到剪切板'));
+    }
+  };
+
+  useEffect(() => {
+    const checkNoticeAndShow = async () => {
+      const lastCloseDate = localStorage.getItem('notice_close_date');
+      const today = new Date().toDateString();
+      if (lastCloseDate !== today) {
+        try {
+          const res = await API.get('/api/notice');
+          const { success, data } = res.data;
+          if (success && data && data.trim() !== '') {
+            setNoticeVisible(true);
+          }
+        } catch (error) {
+          console.error('获取公告失败:', error);
+        }
+      }
+    };
+
+    checkNoticeAndShow();
+  }, []);
+
   useEffect(() => {
     displayHomePageContent().then();
   }, []);
 
+  useEffect(() => {
+    const timer = setInterval(() => {
+      setEndpointIndex((prev) => (prev + 1) % endpointItems.length);
+    }, 3000);
+    return () => clearInterval(timer);
+  }, [endpointItems.length]);
+
   return (
     <div className="w-full overflow-x-hidden">
       <NoticeModal
@@ -86,30 +104,63 @@ const Home = () => {
         <div className="w-full overflow-x-hidden">
           {/* Banner 部分 */}
           <div className="w-full border-b border-semi-color-border min-h-[500px] md:min-h-[600px] lg:min-h-[700px] relative overflow-x-hidden">
-            <div className="flex items-center justify-center h-full px-4 py-20 md:py-24 lg:py-32">
+            {/* 背景模糊晕染球*/}
+            <div className="blur-ball blur-ball-indigo" />
+            <div className="blur-ball blur-ball-teal" />
+            <div className="flex items-center justify-center h-full px-4 py-20 md:py-24 lg:py-32 mt-10">
               {/* 居中内容区 */}
               <div className="flex flex-col items-center justify-center text-center max-w-4xl mx-auto">
                 <div className="flex flex-col items-center justify-center mb-6 md:mb-8">
-                  <h1 className="text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-semi-color-text-0 leading-tight">
+                  <h1 className={`text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-semi-color-text-0 leading-tight ${isChinese ? 'tracking-wide md:tracking-wider' : ''}`}>
                     {i18n.language === 'en' ? (
                       <>
                         The Unified<br />
-                        LLMs API Gateway
+                        <span className="shine-text">LLMs API Gateway</span>
                       </>
                     ) : (
-                      t('统一的大模型接口网关')
+                      <>
+                        统一的<br />
+                        <span className="shine-text">大模型接口网关</span>
+                      </>
                     )}
                   </h1>
-                  <p className="text-lg md:text-xl lg:text-2xl text-semi-color-text-1 mt-4 md:mt-6">
-                    {t('更好的价格,更好的稳定性,无需订阅')}
+                  <p className="text-base md:text-lg lg:text-xl text-semi-color-text-1 mt-4 md:mt-6 max-w-xl">
+                    {t('更好的价格,更好的稳定性,只需要将模型基址替换为:')}
                   </p>
+                  {/* BASE URL 与端点选择 */}
+                  <div className="flex flex-col md:flex-row items-center justify-center gap-4 w-full mt-4 md:mt-6 max-w-md">
+                    <Input
+                      readOnly
+                      value={serverAddress}
+                      className="flex-1 !rounded-full"
+                      size={isMobile() ? 'default' : 'large'}
+                      suffix={
+                        <div className="flex items-center gap-2">
+                          <ScrollList bodyHeight={32} style={{ border: 'unset', boxShadow: 'unset' }}>
+                            <ScrollItem
+                              mode="wheel"
+                              cycled={true}
+                              list={endpointItems}
+                              selectedIndex={endpointIndex}
+                              onSelect={({ index }) => setEndpointIndex(index)}
+                            />
+                          </ScrollList>
+                          <Button
+                            type="primary"
+                            onClick={handleCopyBaseURL}
+                            icon={<IconCopy />}
+                          />
+                        </div>
+                      }
+                    />
+                  </div>
                 </div>
 
                 {/* 操作按钮 */}
                 <div className="flex flex-row gap-4 justify-center items-center">
                   <Link to="/console">
                     <Button theme="solid" type="primary" size={isMobile() ? "default" : "large"} className="!rounded-3xl px-8 py-2" icon={<IconPlay />}>
-                      {t('开始使用')}
+                      {t('获取密钥')}
                     </Button>
                   </Link>
                   {isDemoSiteMode && statusState?.status?.version ? (

+ 2 - 2
web/src/pages/Log/index.js

@@ -2,9 +2,9 @@ import React from 'react';
 import LogsTable from '../../components/table/LogsTable';
 
 const Token = () => (
-  <>
+  <div className="mt-[64px]">
     <LogsTable />
-  </>
+  </div>
 );
 
 export default Token;

+ 2 - 2
web/src/pages/Midjourney/index.js

@@ -2,9 +2,9 @@ import React from 'react';
 import MjLogsTable from '../../components/table/MjLogsTable';
 
 const Midjourney = () => (
-  <>
+  <div className="mt-[64px]">
     <MjLogsTable />
-  </>
+  </div>
 );
 
 export default Midjourney;

+ 1 - 1
web/src/pages/NotFound/index.js

@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
 const NotFound = () => {
   const { t } = useTranslation();
   return (
-    <div className="flex justify-center items-center h-screen p-8">
+    <div className="flex justify-center items-center h-screen p-8 mt-[64px]">
       <Empty
         image={<IllustrationNotFound style={{ width: 250, height: 250 }} />}
         darkModeImage={<IllustrationNotFoundDark style={{ width: 250, height: 250 }} />}

+ 1 - 1
web/src/pages/Playground/index.js

@@ -363,7 +363,7 @@ const Playground = () => {
   }, [setMessage, saveMessagesImmediately]);
 
   return (
-    <div className="h-full bg-gray-50">
+    <div className="h-full bg-gray-50 mt-[64px]">
       <Layout style={{ height: '100%', background: 'transparent' }} className="flex flex-col md:flex-row">
         {(showSettings || !styleState.isMobile) && (
           <Layout.Sider

+ 2 - 2
web/src/pages/Pricing/index.js

@@ -2,9 +2,9 @@ import React from 'react';
 import ModelPricing from '../../components/table/ModelPricing.js';
 
 const Pricing = () => (
-  <>
+  <div className="mt-[64px]">
     <ModelPricing />
-  </>
+  </div>
 );
 
 export default Pricing;

+ 2 - 2
web/src/pages/Redemption/index.js

@@ -3,9 +3,9 @@ import RedemptionsTable from '../../components/table/RedemptionsTable';
 
 const Redemption = () => {
   return (
-    <>
+    <div className="mt-[64px]">
       <RedemptionsTable />
-    </>
+    </div>
   );
 };
 

+ 1 - 1
web/src/pages/Setting/index.js

@@ -150,7 +150,7 @@ const Setting = () => {
     }
   }, [location.search]);
   return (
-    <div>
+    <div className="mt-[64px]">
       <Layout>
         <Layout.Content>
           <Tabs

+ 1 - 1
web/src/pages/Setup/index.js

@@ -133,7 +133,7 @@ const Setup = () => {
   };
 
   return (
-    <div className="bg-gray-50">
+    <div className="bg-gray-50 mt-[64px]">
       <Layout>
         <Layout.Content>
           <div className="flex justify-center px-4 py-8">

+ 2 - 2
web/src/pages/Task/index.js

@@ -2,9 +2,9 @@ import React from 'react';
 import TaskLogsTable from '../../components/table/TaskLogsTable.js';
 
 const Task = () => (
-  <>
+  <div className="mt-[64px]">
     <TaskLogsTable />
-  </>
+  </div>
 );
 
 export default Task;

+ 2 - 2
web/src/pages/Token/index.js

@@ -3,9 +3,9 @@ import TokensTable from '../../components/table/TokensTable';
 
 const Token = () => {
   return (
-    <>
+    <div className="mt-[64px]">
       <TokensTable />
-    </>
+    </div>
   );
 };
 

+ 1 - 1
web/src/pages/TopUp/index.js

@@ -382,7 +382,7 @@ const TopUp = () => {
   };
 
   return (
-    <div className='mx-auto relative min-h-screen lg:min-h-0'>
+    <div className='mx-auto relative min-h-screen lg:min-h-0 mt-[64px]'>
       {/* 划转模态框 */}
       <Modal
         title={

+ 2 - 2
web/src/pages/User/index.js

@@ -3,9 +3,9 @@ import UsersTable from '../../components/table/UsersTable';
 
 const User = () => {
   return (
-    <>
+    <div className="mt-[64px]">
       <UsersTable />
-    </>
+    </div>
   );
 };
 

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików