diff --git a/src/components/ApiEndpoints.jsx b/src/components/ApiEndpoints.jsx new file mode 100644 index 0000000..7e4bae8 --- /dev/null +++ b/src/components/ApiEndpoints.jsx @@ -0,0 +1,118 @@ +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import toast from 'react-hot-toast'; +import { useSite } from '../context/SiteContext'; + +const SHARED_API_ENDPOINTS = [ + { + id: 'world', + labelKey: 'home.apiEndpointWorld', + url: 'https://world.claudecoder.me', + }, + { + id: 'china', + labelKey: 'home.apiEndpointChina', + url: 'https://china.claudecoder.me', + }, +]; + +const normalizeEndpoint = (value = '') => { + const raw = String(value || '').trim(); + if (!raw) return ''; + if (/^https?:\/\//i.test(raw)) { + return raw.replace(/\/+$/, ''); + } + return `https://${raw.replace(/^\/+/, '').replace(/\/+$/, '')}`; +}; + +const copyToClipboard = async (text) => { + try { + await navigator.clipboard.writeText(text); + } catch (e) { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } +}; + +export default function ApiEndpoints() { + const { t } = useTranslation(); + const { site } = useSite(); + + const siteEndpoint = useMemo(() => { + const currentOrigin = + typeof window !== 'undefined' ? window.location.origin : ''; + return normalizeEndpoint(site?.domain || currentOrigin); + }, [site?.domain]); + + const endpoints = useMemo( + () => [ + { + id: 'site', + label: t('home.apiEndpointSite'), + url: siteEndpoint, + }, + ...SHARED_API_ENDPOINTS.map((endpoint) => ({ + ...endpoint, + label: t(endpoint.labelKey), + apiOnly: true, + })), + ].filter((endpoint) => endpoint.url), + [siteEndpoint, t], + ); + + const handleCopy = async (url) => { + await copyToClipboard(url); + toast.success(t('config.apiUrlCopied')); + }; + + return ( +
+
+
+
+

+ {t('home.apiEndpointsTitle')} +

+

+ {t('home.apiEndpointsDesc')} +

+
+ + {t('home.apiEndpointClickToCopy')} + +
+ +
+ {endpoints.map((endpoint) => ( + + ))} +
+
+
+ ); +} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 33c2b84..785dc1f 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -38,6 +38,13 @@ "home.createFreeAccount": "Create Free Account", "home.signIn": "Sign In", "home.defaultHeroTitle": "AI API Platform", + "home.apiEndpointsTitle": "API Endpoints", + "home.apiEndpointsDesc": "After creating an API key, use any of these URLs. Pick the endpoint closest to your network.", + "home.apiEndpointClickToCopy": "Click an address to copy it", + "home.apiEndpointSite": "Sub-site Domain", + "home.apiEndpointWorld": "Overseas calls", + "home.apiEndpointChina": "China calls", + "home.apiEndpointApiOnly": "API only", "login.welcomeBack": "Welcome Back", "login.signInTo": "Sign in to {{name}}", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index c8c79da..10048a5 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -38,6 +38,13 @@ "home.createFreeAccount": "免费创建账户", "home.signIn": "登录", "home.defaultHeroTitle": "AI API 平台", + "home.apiEndpointsTitle": "API 调用地址", + "home.apiEndpointsDesc": "创建 API Key 后,可使用以下任一地址调用。优先选择最适合当前网络的位置。", + "home.apiEndpointClickToCopy": "点击地址即可复制", + "home.apiEndpointSite": "分站域名", + "home.apiEndpointWorld": "海外调用", + "home.apiEndpointChina": "中国调用", + "home.apiEndpointApiOnly": "仅支持 API", "login.welcomeBack": "欢迎回来", "login.signInTo": "登录到 {{name}}", diff --git a/src/themes/claude/Home.jsx b/src/themes/claude/Home.jsx index 6a05bb7..b539e5a 100644 --- a/src/themes/claude/Home.jsx +++ b/src/themes/claude/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function ClaudeHome() { const { t } = useTranslation(); @@ -81,6 +82,8 @@ export default function ClaudeHome() { + + {/* Features */}
diff --git a/src/themes/clean/Home.jsx b/src/themes/clean/Home.jsx index b3a28fd..c953ee1 100644 --- a/src/themes/clean/Home.jsx +++ b/src/themes/clean/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function CleanHome() { const { t } = useTranslation(); @@ -82,6 +83,8 @@ export default function CleanHome() {
+ + {/* Features */}
diff --git a/src/themes/corporate/Home.jsx b/src/themes/corporate/Home.jsx index ccbb065..4f02d7e 100644 --- a/src/themes/corporate/Home.jsx +++ b/src/themes/corporate/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function CorporateHome() { const { t } = useTranslation(); @@ -81,6 +82,8 @@ export default function CorporateHome() {
+ + {/* Features */}
diff --git a/src/themes/dark/Home.jsx b/src/themes/dark/Home.jsx index 1a579a5..439a260 100644 --- a/src/themes/dark/Home.jsx +++ b/src/themes/dark/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function DarkHome() { const { t } = useTranslation(); @@ -86,6 +87,8 @@ export default function DarkHome() {
+ + {/* Features */}
diff --git a/src/themes/default/Home.jsx b/src/themes/default/Home.jsx index 8f8885c..46dbed0 100644 --- a/src/themes/default/Home.jsx +++ b/src/themes/default/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function DefaultHome() { const { t } = useTranslation(); @@ -81,6 +82,8 @@ export default function DefaultHome() {
+ + {/* Features */}
diff --git a/src/themes/minimal/Home.jsx b/src/themes/minimal/Home.jsx index faf08be..9e7d67b 100644 --- a/src/themes/minimal/Home.jsx +++ b/src/themes/minimal/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function MinimalHome() { const { t } = useTranslation(); @@ -81,6 +82,8 @@ export default function MinimalHome() {
+ + {/* Features — two-column layout */}
diff --git a/src/themes/starter/Home.jsx b/src/themes/starter/Home.jsx index e267f31..f070fb0 100644 --- a/src/themes/starter/Home.jsx +++ b/src/themes/starter/Home.jsx @@ -8,6 +8,7 @@ import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; import CountUp from '../../components/bits/CountUp'; import FadeContent from '../../components/bits/FadeContent'; +import ApiEndpoints from '../../components/ApiEndpoints'; export default function StarterHome() { const { t } = useTranslation(); @@ -87,6 +88,8 @@ export default function StarterHome() {
+ + {/* Features */}