|
|
@@ -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 ? (
|