From 5a68d88fa9a970bc29a29663e5467f89e33b4dac Mon Sep 17 00:00:00 2001 From: wxj Date: Tue, 28 Jul 2026 09:29:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9A=E4=BB=B7=E9=A1=B5=E9=87=8D=E6=9E=84?= =?UTF-8?q?=EF=BC=9ASiliconFlow=E9=A3=8E=E6=A0=BC=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E5=B8=83=E5=B1=80=20+=20=E7=82=AB=E9=85=B7=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除侧边栏,改为全宽布局,顶部Hero区域(渐变标题+搜索框+热门标签) - 新增横向筛选pill按钮(模型类型/供应商/服务商) - 卡片网格3列响应式布局,悬停发光+上浮+微缩放效果 - 新增浮动粒子背景、卡片入场逐个动画、搜索框光晕脉冲 - 新增流光扫过效果、价格数字脉冲发光 - 新增DeepSeek-V4/MiniMax模型默认倍率 - 更新setup.ps1支持DeepSeek+Minimax双供应商配置 --- setting/ratio_setting/model_ratio.go | 8 + setup.ps1 | 56 + .../model-pricing/layout/ModelsContent.jsx | 181 ++- .../model-pricing/layout/PricingPage.jsx | 23 +- .../view/card/PricingCardView.jsx | 375 +++--- .../model-pricing/view/table/PricingTable.jsx | 8 +- .../view/table/PricingTableColumns.jsx | 16 +- web/src/index.css | 865 +++++++++---- web/src/pages/Home/index.jsx | 1105 ++++++++--------- 9 files changed, 1532 insertions(+), 1105 deletions(-) create mode 100644 setup.ps1 diff --git a/setting/ratio_setting/model_ratio.go b/setting/ratio_setting/model_ratio.go index 7b4455d..6004764 100644 --- a/setting/ratio_setting/model_ratio.go +++ b/setting/ratio_setting/model_ratio.go @@ -274,6 +274,14 @@ var defaultModelRatio = map[string]float64{ "deepseek-ai/DeepSeek-V3-0324": 0.8, "deepseek-ai/DeepSeek-V3.1": 0.8, "DeepSeek-V3.2": 0.8, + "deepseek-V4": 1.0, + "deepseek-v4-Flash": 0.2, + // MiniMax + "MiniMax-M2.1": 1.0, + "MiniMax-M2.1-highspeed": 0.5, + "MiniMax-M2": 0.8, + "MiniMax-M2.5": 1.2, + "MiniMax-M2.5-highspeed": 0.6, } var defaultModelPrice = map[string]float64{ diff --git a/setup.ps1 b/setup.ps1 new file mode 100644 index 0000000..83d946a --- /dev/null +++ b/setup.ps1 @@ -0,0 +1,56 @@ +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# Login +$loginResp = Invoke-WebRequest -Uri "http://localhost:3000/api/user/login" -Method POST -Body '{"username":"admin","password":"admin12345"}' -ContentType "application/json; charset=utf-8" -UseBasicParsing -SessionVariable ws +Write-Host "Login OK" + +$hdr = @{"New-Api-User"="1"} + +# Create channels +$ch1 = '{"mode":"single","channel":{"name":"DeepSeek","type":43,"base_url":"https://api.deepseek.com","key":"sk-ds-test","models":"deepseek-V4,deepseek-v4-Flash","status":1,"supplier_type":"\u516c\u6709\u4e91","other":"{}","setting":"{}"}}' +$ch2 = '{"mode":"single","channel":{"name":"Minimax","type":35,"base_url":"https://api.minimax.chat","key":"sk-mm-test","models":"MiniMax-M2.1,MiniMax-M2.1-highspeed,MiniMax-M2,MiniMax-M2.5,MiniMax-M2.5-highspeed","status":1,"supplier_type":"\u516c\u6709\u4e91","other":"{}","setting":"{}"}}' + +foreach ($ch in @($ch1,$ch2)) { + $bytes = [System.Text.Encoding]::UTF8.GetBytes($ch) + $r = Invoke-WebRequest -Uri "http://localhost:3000/api/channel/" -Method POST -Body $bytes -ContentType "application/json; charset=utf-8" -UseBasicParsing -WebSession $ws -Headers $hdr + Write-Host "Channel created" +} + +# Sync abilities +$m1 = [System.Text.Encoding]::UTF8.GetBytes('{"models":["deepseek-V4","deepseek-v4-Flash"]}') +$m2 = [System.Text.Encoding]::UTF8.GetBytes('{"models":["MiniMax-M2.1","MiniMax-M2.1-highspeed","MiniMax-M2","MiniMax-M2.5","MiniMax-M2.5-highspeed"]}') +Invoke-WebRequest -Uri "http://localhost:3000/api/channel/1/models" -Method PATCH -Body $m1 -ContentType "application/json; charset=utf-8" -UseBasicParsing -WebSession $ws -Headers $hdr | Out-Null +Invoke-WebRequest -Uri "http://localhost:3000/api/channel/2/models" -Method PATCH -Body $m2 -ContentType "application/json; charset=utf-8" -UseBasicParsing -WebSession $ws -Headers $hdr | Out-Null +Write-Host "Abilities synced" + +# Create model metas +$names = @("deepseek-V4","deepseek-v4-Flash","MiniMax-M2.1","MiniMax-M2.1-highspeed","MiniMax-M2","MiniMax-M2.5","MiniMax-M2.5-highspeed") +foreach ($n in $names) { + $body = [System.Text.Encoding]::UTF8.GetBytes("{`"model_name`":`"$n`",`"status`":1}") + Invoke-WebRequest -Uri "http://localhost:3000/api/models/" -Method POST -Body $body -ContentType "application/json; charset=utf-8" -UseBasicParsing -WebSession $ws -Headers $hdr | Out-Null + Write-Host "Meta: $n" +} + +# Seed test results (required for pricing page to display data) +$testModels = @( + @{ch=1; ms=@("deepseek-V4","deepseek-v4-Flash"); msrt=200}, + @{ch=2; ms=@("MiniMax-M2.1","MiniMax-M2.1-highspeed","MiniMax-M2","MiniMax-M2.5","MiniMax-M2.5-highspeed"); msrt=300} +) +foreach ($g in $testModels) { + foreach ($m in $g.ms) { + $body = [System.Text.Encoding]::UTF8.GetBytes("{`"channel_id`":$($g.ch),`"model_name`":`"$m`",`"manual_display_response_time`":$($g.msrt),`"manual_stability_grade`":5}") + Invoke-WebRequest -Uri "http://localhost:3000/api/channel/model-test-result-display" -Method PUT -Body $body -ContentType "application/json; charset=utf-8" -UseBasicParsing -WebSession $ws -Headers $hdr | Out-Null + Write-Host "Test result: ch=$($g.ch) $m" + } +} + +# Reset ratios +$rr = Invoke-WebRequest -Uri "http://localhost:3000/api/option/rest_model_ratio" -Method POST -UseBasicParsing -WebSession $ws -Headers $hdr +Write-Host "Ratio reset: $($rr.Content)" + +# Check +$resp = Invoke-WebRequest -Uri "http://localhost:3000/api/pricing" -UseBasicParsing -WebSession $ws -Headers $hdr +$data = $resp.Content | ConvertFrom-Json +Write-Host "`nPricing data count: $($data.data.Count)" +$data.data | ForEach-Object { Write-Host " $($_.model_name)" } diff --git a/web/src/components/table/model-pricing/layout/ModelsContent.jsx b/web/src/components/table/model-pricing/layout/ModelsContent.jsx index 50bdc36..bec5c20 100644 --- a/web/src/components/table/model-pricing/layout/ModelsContent.jsx +++ b/web/src/components/table/model-pricing/layout/ModelsContent.jsx @@ -6,6 +6,14 @@ it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + For commercial licensing, please contact support@quantumnous.com */ @@ -13,15 +21,6 @@ import React from 'react'; import { Search } from 'lucide-react'; import PricingCardView from '../view/card/PricingCardView'; -// 顶部 Tab 到 tag 值的映射 -const TAB_TO_TAG = { - all: 'all', - text: '文本', - image: '图片', - audio: '音频', - video: '视频', -}; - const TYPE_TABS = [ { key: 'all', labelKey: '全部' }, { key: 'text', labelKey: '文本' }, @@ -30,9 +29,20 @@ const TYPE_TABS = [ { key: 'video', labelKey: '视频' }, ]; +const TAB_TO_TAG = { + all: 'all', + text: '文本', + image: '图片', + audio: '音频', + video: '视频', +}; + const ModelsContent = ({ filteredModels, + models, filterTag, setFilterTag, + filterVendor, setFilterVendor, + filterSupplierType, setFilterSupplierType, searchValue, setSearchValue, loading, isMobile, @@ -40,48 +50,161 @@ const ModelsContent = ({ t, ...cardProps }) => { - // 根据当前 filterTag 反推激活的 tab key const activeTabKey = filterTag === 'all' ? 'all' : (Object.entries(TAB_TO_TAG).find(([, v]) => v === filterTag)?.[0] || 'all'); + // Get unique vendors for filter pills + const vendors = React.useMemo(() => { + if (!models) return []; + const names = [...new Set(models.map((m) => m.vendor_name).filter(Boolean))].sort(); + return names; + }, [models]); + + // Get unique supplier types + const supplierTypes = React.useMemo(() => { + if (!models) return []; + const types = new Set(); + models.forEach((m) => { + (m.channel_list || []).forEach((ch) => { + if (ch.supplier_type) types.add(ch.supplier_type); + }); + }); + return [...types].sort(); + }, [models]); + return ( - <> - {/* Type Tabs */} -
- {TYPE_TABS.map((tab) => ( - +
+ {/* Floating Particles */} +
+ {Array.from({ length: 8 }, (_, i) => ( +
))} -
-
- +
+ + {/* Hero Section */} +
+

+ {t('探索最适合的 AI 模型')} +

+

+ {t('为您的应用找到最优质的模型与服务')} +

+ + {/* Search Bar */} +
+ setSearchValue(e.target.value)} - className='w-48 bg-muted/30 border border-input rounded-lg pl-9 pr-3 py-1.5 text-sm text-foreground placeholder-muted-foreground/50 focus:outline-none focus:border-ring focus:bg-muted/50 transition-all' + className='sf-search-input' />
+ + {/* Hot Models Quick Tags */} + {models && models.length > 0 && ( +
+ {t('热门模型')} + {models.slice(0, isMobile ? 4 : 7).map((m) => ( + + ))} +
+ )} +
+ + {/* Filter Section */} +
+ {/* Type Filters */} +
+ {t('模型类型')} +
+ {TYPE_TABS.map((tab) => ( + + ))} +
+
+ + {/* Vendor Filters */} + {vendors.length > 0 && ( +
+ {t('供应商')} +
+ + {vendors.map((v) => ( + + ))} +
+
+ )} + + {/* Supplier Type Filters */} + {supplierTypes.length > 0 && ( +
+ {t('服务商')} +
+ + {supplierTypes.map((type) => ( + + ))} +
+
+ )} +
+ + {/* Results Header */} +
+

+ {t('为您找到')} {filteredModels?.length || 0} {t('个模型')} +

{/* Model Cards Grid */} -
+
- +
); }; diff --git a/web/src/components/table/model-pricing/layout/PricingPage.jsx b/web/src/components/table/model-pricing/layout/PricingPage.jsx index 5641e16..384ec27 100644 --- a/web/src/components/table/model-pricing/layout/PricingPage.jsx +++ b/web/src/components/table/model-pricing/layout/PricingPage.jsx @@ -19,7 +19,6 @@ For commercial licensing, please contact support@quantumnous.com import React, { useContext, useMemo } from 'react'; import { ImagePreview } from '@douyinfe/semi-ui'; -import ModelsSidebar from './ModelsSidebar'; import ModelsContent from './ModelsContent'; import ModelDetailSideSheet from '../modal/ModelDetailSideSheet'; import { useModelPricingData } from '../../../../hooks/model-pricing/useModelPricingData'; @@ -59,22 +58,12 @@ const PricingPage = () => { }; return ( -
-
- {!isMobile && ( - - )} - -
- -
-
+
+ value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); @@ -72,7 +64,7 @@ const PricingCardView = ({ channelVideoRatio = {}, channelVideoCompletionRatio = {}, channelVideoPrice = {}, - gridCols = 0, // 当 > 0 时使用 grid 布局(如 gridCols=2 表示 2 列) + gridCols = 3, }) => { const showSkeleton = useMinimumLoadingTime(loading); const startIndex = (currentPage - 1) * pageSize; @@ -93,9 +85,9 @@ const PricingCardView = ({ @@ -119,7 +111,6 @@ const PricingCardView = ({ groupRatio, ); - // 辅助函数:格式化价格 const formatPrice = (priceUSD) => { const rawDisplayPrice = displayPrice(priceUSD); const unitDivisor = tokenUnit === 'K' ? 1000 : 1; @@ -141,7 +132,7 @@ const PricingCardView = ({ } } - return { value: parseFloat(numericPrice.toFixed(2)), symbol }; + return { value: parseFloat(numericPrice.toFixed(4)), symbol }; }; const modelHasVideoRatio = @@ -157,7 +148,6 @@ const PricingCardView = ({ model.video_price !== undefined && Number.isFinite(Number(model.video_price)); - // 提取所有通道的价格(与 relay 一致:ch.model_ratio 已含渠道折扣;再乘分组倍率) const prices = { input: [], output: [], @@ -181,18 +171,6 @@ const PricingCardView = ({ const cid = ch.channel_id; const mname = model.model_name; - // ============================================================ - // 新计费公式参数: - // ch.model_ratio / ch.model_price 为原始渠道倍率(后端不再预乘成本折扣) - // 成本折扣率 = price_discount_percent / 100 - // 加价倍率 = markup_discount_rate / 100 - // - // 输入 = (ch.model_ratio × costDisc + globalMr × markupRate) × 2 × groupRatio - // 输出 = (ch.model_ratio × cr × costDisc + globalMr × globalCR × markupRate) × 2 × groupRatio - // 缓存读 = (ch.model_ratio × cacheRatio × costDisc + globalMr × globalCacheR × markupRate) × 2 × groupRatio - // 缓存写 = (ch.model_ratio × createCacheRatio × costDisc + globalMr × globalCreateCacheR × markupRate) × 2 × groupRatio - // 固定价 = (ch.model_price × costDisc + globalMp × markupRate) × groupRatio - // ============================================================ const priceDiscountPercent = ch.price_discount_percent != null ? ch.price_discount_percent : 100; const markupDiscountPercent = ch.markup_discount_rate || 0; @@ -212,7 +190,6 @@ const PricingCardView = ({ originalPrices.videoFlat.push(formatPrice(flatUsd)); } - // 全局子倍率(用于加价侧) const globalCR = model.completion_ratio || 0; const globalCacheR = model.cache_ratio != null ? Number(model.cache_ratio) : 0; @@ -236,7 +213,6 @@ const PricingCardView = ({ globalCreateCacheRatio: globalCreateCacheR, }); - // 按量计费 if (model.quota_type === 0) { if (ch.model_ratio !== undefined && ch.model_ratio !== null) { prices.input.push( @@ -244,7 +220,6 @@ const PricingCardView = ({ ); originalPrices.input.push(formatPrice(billingRates.inputRatioPrice)); - // 输出价格:渠道配置了 completion_ratio 即可展示 if ( ch.completion_ratio !== undefined && ch.completion_ratio !== null @@ -257,7 +232,6 @@ const PricingCardView = ({ ); } - // 缓存读取价格:渠道配置了 cache_ratio 即可展示 if ( ch.cache_ratio !== undefined && ch.cache_ratio !== null @@ -268,7 +242,6 @@ const PricingCardView = ({ originalPrices.cache.push(formatPrice(billingRates.cacheRatioPrice)); } - // 缓存创建价格:渠道配置了 create_cache_ratio 即可展示 if ( ch.create_cache_ratio !== undefined && ch.create_cache_ratio !== null @@ -321,9 +294,7 @@ const PricingCardView = ({ ); } } - } - // 按次计费 - else if (model.quota_type === 1 || ch.quota_type === 1) { + } else if (model.quota_type === 1 || ch.quota_type === 1) { if (!skipSimpleFixed && ch.model_price !== undefined && ch.model_price !== null) { prices.fixed.push( formatPrice(billingRates.effModelPrice * usedGroupRatio), @@ -333,7 +304,6 @@ const PricingCardView = ({ } }); - // 根数据价格(用同一口径计算,用于与 channel 价格比较) const rootPrices = {}; if (model.quota_type === 0) { if (model.model_ratio !== undefined && model.model_ratio !== null) { @@ -386,7 +356,6 @@ const PricingCardView = ({ rootPrices.videoFlat = formatPrice(Number(model.video_price)); } - // 若根价格高于任意一个 channel 的对应价格,则返回划线原价与折扣 const getOriginal = (rootPrice, channelPriceArray) => { if (!rootPrice || !channelPriceArray || channelPriceArray.length === 0) return null; @@ -401,7 +370,6 @@ const PricingCardView = ({ return null; }; - // 计算范围 const calculateRange = (priceArray) => { if (priceArray.length === 0) return null; if (priceArray.length === 1) { @@ -467,7 +435,6 @@ const PricingCardView = ({ }; }; - // 获取模型的价格项(优先使用 channel 价格) const getModelPriceItemsForCard = (model, priceData) => { const hint = model.video_flat_clip_hint; const useTieredVideoFlat = @@ -485,12 +452,10 @@ const PricingCardView = ({ skipSimpleFixed: useTieredImagePerImage, }); - // 如果没有 channel 价格,使用原有逻辑 if (!channelPrices) { return getModelPriceItems(priceData, t, siteDisplayType); } - // 使用 channel 价格构建价格项 const items = []; const { input, @@ -507,7 +472,6 @@ const PricingCardView = ({ quotaType, } = channelPrices; - // 按次计费 if (quotaType === 1 && fixed) { items.push({ key: 'fixed', @@ -518,13 +482,11 @@ const PricingCardView = ({ suffix: fixedSuffix, original: original?.fixed, }); - } - // 按量计费 - else { + } else { if (input) { items.push({ key: 'input', - label: t('输入价格'), + label: t('输入'), value: input.single || `${input.symbol}${input.min} ~ ${input.symbol}${input.max}`, @@ -536,7 +498,7 @@ const PricingCardView = ({ if (output) { items.push({ key: 'output', - label: t('输出价格'), + label: t('输出'), value: output.single || `${output.symbol}${output.min} ~ ${output.symbol}${output.max}`, @@ -548,7 +510,7 @@ const PricingCardView = ({ if (videoToken) { items.push({ key: 'video-token', - label: t('视频(倍率计价)'), + label: t('视频'), value: videoToken.single || `${videoToken.symbol}${videoToken.min} ~ ${videoToken.symbol}${videoToken.max}`, @@ -556,33 +518,12 @@ const PricingCardView = ({ original: original?.videoToken, }); } - - // 卡片表面仅展示输入/输出价格,缓存价格不在卡片上显示(详情弹窗中仍可见) - // if (cache) { - // items.push({ - // key: 'cache', - // label: t('缓存读取价格'), - // value: cache.single || `${cache.symbol}${cache.min} ~ ${cache.symbol}${cache.max}`, - // suffix: unitSuffix, - // original: original?.cache, - // }); - // } - // - // if (createCache) { - // items.push({ - // key: 'create-cache', - // label: t('缓存创建价格'), - // value: createCache.single || `${createCache.symbol}${createCache.min} ~ ${createCache.symbol}${createCache.max}`, - // suffix: unitSuffix, - // original: original?.createCache, - // }); - // } } if (videoFlat) { items.push({ key: 'video-flat', - label: t('视频按条(固定价)'), + label: t('视频按条'), value: videoFlat.single || `${videoFlat.symbol}${videoFlat.min} ~ ${videoFlat.symbol}${videoFlat.max}`, @@ -603,7 +544,7 @@ const PricingCardView = ({ key: 'video-flat-tiered', label: perSecond ? t('按秒') : t('按条'), valueNode: ( - + {t('最低价')} {displayPrice(usd)} {perSecond ? t('/秒起') : t('/条起')} @@ -624,7 +565,7 @@ const PricingCardView = ({ key: 'image-per-image-tiered', label: t('按张'), valueNode: ( - + {t('最低价')} {displayPrice(usd)} {t('/张起')} @@ -636,50 +577,47 @@ const PricingCardView = ({ return items; }; - // 获取模型图标 const getModelIcon = (model) => { if (!model || !model.model_name) { return ( -
- ? +
+ ?
); } - // 1) 优先使用模型自定义图标 if (model.icon) { return ( -
-
- {getLobeHubIcon(model.icon, 32)} +
+
+ {getLobeHubIcon(model.icon, 28)}
); } - // 2) 退化为供应商图标 if (model.vendor_icon) { return ( -
-
- {getLobeHubIcon(model.vendor_icon, 32)} +
+
+ {getLobeHubIcon(model.vendor_icon, 28)}
); } - // 如果没有供应商图标,使用模型名称生成头像 - const avatarText = (model.model_name || '').slice(0, 2).toUpperCase() || 'AI'; return ( -
+
{avatarText} @@ -688,154 +626,155 @@ const PricingCardView = ({ ); }; - // 获取模型描述 const getModelDescription = (record) => { return record.description || ''; }; - // 显示骨架屏 + // Get supplier type tag color + const getSupplierTypeTag = (model) => { + const types = new Set(); + (model.channel_list || []).forEach((ch) => { + if (ch.supplier_type) types.add(ch.supplier_type); + }); + const firstType = [...types][0]; + if (!firstType) return null; + return firstType; + }; + + // Get model tags + const getModelTags = (model) => { + if (!model.tags) return []; + return String(model.tags).split(/[,;|]/).map(s => s.trim()).filter(Boolean); + }; + if (showSkeleton) { - return ( - <> - - - ); + return ; } if (!filteredModels || filteredModels.length === 0) { return ( - <> -
- } - darkModeImage={ - - } - description={t('搜索无结果')} - /> -
- +
+ } + darkModeImage={ + + } + description={ + {t('搜索无结果')} + } + /> +
); } return ( <> -
0 ? '' : 'px-2 pt-2'}> -
0 ? 'grid gap-4' : 'flex flex-wrap gap-4'} style={gridCols > 0 ? { gridTemplateColumns: `repeat(${gridCols}, 1fr)` } : undefined}> - {paginatedModels.map((model, index) => { - const modelKey = getModelKey(model); +
+ {paginatedModels.map((model, index) => { + const modelKey = getModelKey(model); - const priceData = calculateModelPrice({ - record: model, - selectedGroup, - groupRatio, - groupModelPrice, - groupModelRatio, - tokenUnit, - displayPrice, - currency, - quotaDisplayType: siteDisplayType, - }); + const priceData = calculateModelPrice({ + record: model, + selectedGroup, + groupRatio, + groupModelPrice, + groupModelRatio, + tokenUnit, + displayPrice, + currency, + quotaDisplayType: siteDisplayType, + }); - return ( - 0 ? '!w-full' : 'flex-1 min-w-[350px] max-w-[600px]'} !rounded-2xl transition-all duration-200 hover:shadow-lg border ${blurPricing ? '' : 'cursor-pointer'} ${CARD_STYLES.default}`} - bodyStyle={{ height: '100%' }} - onClick={() => - !blurPricing && openModelDetail && openModelDetail(model) - } - > -
- {/* 头部:图标 + 模型名称 */} -
-
- {getModelIcon(model)} -
-

- {renderHighlightedText(model.model_name)} -

-
-
-
+ const supplierType = getSupplierTypeTag(model); + const tags = getModelTags(model); - {/* 模型描述 */} -
-

- {renderHighlightedText(getModelDescription(model))} -

-
- - {/* 价格:输入 + 输出并排 */} -
- {(() => { - const items = getModelPriceItemsForCard(model, priceData); - if (items.length === 0) return null; - return ( -
- {items.map(item => ( -
-
{item.label}
-
- {item.valueNode || (item.value + (item.suffix || ''))} -
-
- ))} -
- ); - })()} -
+ return ( +
+ !blurPricing && openModelDetail && openModelDetail(model) + } + > +
+ {/* Card Header: Provider + Tag */} +
+
+ {getModelIcon(model)} + + {model.vendor_name || model.model_name?.split('/')[0] || 'AI'} +
- - ); - })} -
+ {supplierType && ( + {supplierType} + )} +
- {/* 分页 */} - {filteredModels.length > 0 && ( -
- setCurrentPage(page)} - onPageSizeChange={(size) => { - setPageSize(size); - setCurrentPage(1); - }} - /> -
- )} + {/* Default View: Model Name + Tags + Pricing */} +
+

+ {renderHighlightedText(model.model_name)} +

+ + {/* Tags */} + {tags.length > 0 && ( +
+ {tags.slice(0, 3).map((tag) => ( + {tag} + ))} +
+ )} + + {/* Pricing */} +
+ {(() => { + const items = getModelPriceItemsForCard(model, priceData); + if (items.length === 0) return null; + return items.slice(0, 2).map(item => ( +
+ {item.label} + + {item.valueNode || (item.value + (item.suffix || ''))} + +
+ )); + })()} +
+
+ + {/* Hover View: Description */} +
+

+ {getModelDescription(model) || t('暂无描述')} +

+
+
+ ); + })}
+ + {/* Pagination */} + {filteredModels.length > 0 && ( +
+ setCurrentPage(page)} + onPageSizeChange={(size) => { + setPageSize(size); + setCurrentPage(1); + }} + /> +
+ )} ); }; diff --git a/web/src/components/table/model-pricing/view/table/PricingTable.jsx b/web/src/components/table/model-pricing/view/table/PricingTable.jsx index 7d7a3f9..f5b6319 100644 --- a/web/src/components/table/model-pricing/view/table/PricingTable.jsx +++ b/web/src/components/table/model-pricing/view/table/PricingTable.jsx @@ -98,7 +98,7 @@ const PricingTable = ({ const ModelTable = useMemo( () => ( - + -
+
-
+
{t('搜索无结果')}
-
+
{t('试试调整筛选条件或换个关键词')}
diff --git a/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx b/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx index 15c8a62..2ea8e9b 100644 --- a/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx +++ b/web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx @@ -37,13 +37,13 @@ function renderQuotaType(type, t) { switch (type) { case 1: return ( - + {t('按次计费')} ); case 0: return ( - + {t('按量计费')} ); @@ -200,8 +200,8 @@ export const getPricingTableColumns = ({
{t('倍率')} - { setModalImageUrl('/ratio.png'); setIsModalOpenurl(true); @@ -217,17 +217,17 @@ export const getPricingTableColumns = ({ return (
-
+
{t('模型倍率')}: {record.quota_type === 0 ? (priceData?.inputRatio ?? text) : t('无')}
-
+
{t('输出倍率')}: {record.quota_type === 0 ? completionRatio : t('无')}
-
+
{t('分组倍率')}:{priceData?.usedGroupRatio ?? '-'}
@@ -246,7 +246,7 @@ export const getPricingTableColumns = ({ return (
{priceItems.map((item) => ( -
+
{item.label} {item.value} {item.suffix}
diff --git a/web/src/index.css b/web/src/index.css index 9961db7..752d8a0 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -2,100 +2,104 @@ /* 这些变量叠加在 Semi Design 之上,供新的 shadcn 风格组件使用 */ :root { - /* shadcn/ui 基础色板 — Slate 中性色调 (220° hue) */ --background: 0 0% 100%; - --foreground: 240 10% 3.9%; + --foreground: 222 47% 11%; --card: 0 0% 100%; - --card-foreground: 240 10% 3.9%; + --card-foreground: 222 47% 11%; --popover: 0 0% 100%; - --popover-foreground: 240 10% 3.9%; - --primary: 0 0% 9%; - --primary-foreground: 0 0% 98%; - --secondary: 220 5% 96%; - --secondary-foreground: 220 6% 10%; - --muted: 220 5% 96%; - --muted-foreground: 220 4% 56%; - --accent: 220 5% 96%; - --accent-foreground: 220 6% 10%; + --popover-foreground: 222 47% 11%; + --primary: 221 83% 53%; + --primary-foreground: 0 0% 100%; + --secondary: 210 40% 96%; + --secondary-foreground: 222 47% 11%; + --muted: 210 40% 96%; + --muted-foreground: 215 16% 47%; + --accent: 221 83% 53%; + --accent-foreground: 0 0% 100%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; - --border: 220 13% 91%; - --input: 220 13% 91%; - --ring: 0 0% 9%; + --border: 214 32% 91%; + --input: 214 32% 91%; + --ring: 221 83% 53%; --radius: 0.5rem; - --link: 0 0% 9%; - --link-hover: 0 0% 20%; - --chart-1: 160 60% 45%; - --chart-2: 217 91% 60%; + --link: 221 83% 53%; + --link-hover: 221 83% 40%; + --chart-1: 221 83% 53%; + --chart-2: 160 60% 45%; --chart-3: 43 96% 56%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; } .dark { - --background: 0 0% 5.5%; - --foreground: 0 0% 90%; - --card: 0 0% 10%; - --card-foreground: 0 0% 90%; - --popover: 0 0% 8%; - --popover-foreground: 0 0% 90%; - --primary: 236 52% 62%; + --background: 210 60% 97%; + --foreground: 222 47% 11%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 217 91% 60%; --primary-foreground: 0 0% 100%; - --secondary: 0 0% 13%; - --secondary-foreground: 0 0% 90%; - --muted: 0 0% 9%; - --muted-foreground: 0 0% 56%; - --accent: 236 52% 62%; + --secondary: 210 40% 93%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 93%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 217 91% 60%; --accent-foreground: 0 0% 100%; --destructive: 0 62% 45%; --destructive-foreground: 0 0% 100%; - --border: 0 0% 100% / 0.08; - --input: 0 0% 100% / 0.06; - --ring: 236 52% 62%; - --link: 236 52% 72%; - --link-hover: 236 52% 62%; + --border: 214.3 31.8% 85%; + --input: 214.3 31.8% 85%; + --ring: 217 91% 60%; + --link: 217 91% 60%; + --link-hover: 217 91% 50%; --radius: 0.375rem; - --chart-1: 236 52% 62%; + --chart-1: 217 91% 60%; --chart-2: 160 60% 50%; --chart-3: 43 96% 56%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; } -/* === Linear Dark: Semi Design 暗色覆盖 === */ -.dark { - --semi-color-primary: rgba(94, 106, 210, 1); - --semi-color-primary-hover: rgba(114, 126, 230, 1); - --semi-color-primary-active: rgba(78, 90, 190, 1); - --semi-color-primary-disabled: rgba(94, 106, 210, 0.35); - --semi-color-primary-light-default: rgba(94, 106, 210, 0.12); - --semi-color-primary-light-hover: rgba(94, 106, 210, 0.18); - --semi-color-primary-light-active: rgba(94, 106, 210, 0.24); - --semi-color-bg-0: rgba(14, 14, 14, 1); - --semi-color-bg-1: rgba(19, 19, 19, 1); - --semi-color-bg-2: rgba(25, 25, 25, 1); - --semi-color-bg-3: rgba(32, 32, 32, 1); - --semi-color-bg-4: rgba(40, 40, 40, 1); - --semi-color-text-0: rgba(230, 230, 230, 1); - --semi-color-text-1: rgba(190, 190, 190, 1); - --semi-color-text-2: rgba(140, 140, 140, 1); - --semi-color-text-3: rgba(100, 100, 100, 1); - --semi-color-border: rgba(255, 255, 255, 0.08); - --semi-color-fill-0: rgba(255, 255, 255, 0.04); - --semi-color-fill-1: rgba(255, 255, 255, 0.06); - --semi-color-fill-2: rgba(255, 255, 255, 0.08); - --semi-color-link: rgba(130, 143, 255, 1); - --semi-color-link-hover: rgba(158, 168, 255, 1); - --semi-color-link-active: rgba(110, 122, 235, 1); - --semi-color-link-visited: rgba(130, 143, 255, 0.8); - --semi-color-tertiary: rgba(255, 255, 255, 0.06); - --semi-color-tertiary-hover: rgba(255, 255, 255, 0.10); - --semi-color-tertiary-active: rgba(255, 255, 255, 0.14); - --semi-shadow-elevated: 0 0 0 1px rgba(255,255,255,0.06), 0 2px 8px rgba(0,0,0,0.3); +/* === Linear Dark: Semi Design 暗色覆盖 — 蓝白渐变主题 === */ +body[theme-mode='dark'] { + --semi-color-primary: rgba(59, 130, 246, 1); + --semi-color-primary-hover: rgba(79, 150, 255, 1); + --semi-color-primary-active: rgba(37, 99, 235, 1); + --semi-color-primary-disabled: rgba(59, 130, 246, 0.35); + --semi-color-primary-light-default: rgba(59, 130, 246, 0.12); + --semi-color-primary-light-hover: rgba(59, 130, 246, 0.18); + --semi-color-primary-light-active: rgba(59, 130, 246, 0.24); + --semi-color-bg-0: rgba(240, 247, 255, 1); + --semi-color-bg-1: rgba(235, 244, 255, 1); + --semi-color-bg-2: rgba(224, 240, 253, 1); + --semi-color-bg-3: rgba(214, 232, 248, 1); + --semi-color-bg-4: rgba(204, 224, 244, 1); + --semi-color-nav-bg: rgba(240, 247, 255, 1); + --semi-color-text-0: rgba(15, 23, 42, 1); + --semi-color-text-1: rgba(51, 65, 85, 1); + --semi-color-text-2: rgba(100, 116, 139, 1); + --semi-color-text-3: rgba(148, 163, 184, 1); + --semi-color-border: rgba(191, 219, 254, 1); + --semi-color-fill-0: rgba(59, 130, 246, 0.04); + --semi-color-fill-1: rgba(59, 130, 246, 0.06); + --semi-color-fill-2: rgba(59, 130, 246, 0.08); + --semi-color-link: rgba(59, 130, 246, 1); + --semi-color-link-hover: rgba(37, 99, 235, 1); + --semi-color-link-active: rgba(29, 78, 216, 1); + --semi-color-link-visited: rgba(59, 130, 246, 0.8); + --semi-color-tertiary: rgba(59, 130, 246, 0.06); + --semi-color-tertiary-hover: rgba(59, 130, 246, 0.10); + --semi-color-tertiary-active: rgba(59, 130, 246, 0.14); + --semi-color-overlay-bg: rgba(22, 22, 26, .4); + --semi-shadow-elevated: 0 0 0 1px rgba(0,0,0,0.06), 0 2px 8px rgba(0,0,0,0.1); --semi-color-success: rgba(52, 211, 153, 1); --semi-color-danger: rgba(248, 113, 113, 1); --semi-color-warning: rgba(251, 191, 36, 1); - --semi-color-info: rgba(94, 106, 210, 1); + --semi-color-info: rgba(59, 130, 246, 1); + --semi-color-default: rgba(59, 130, 246, 0.04); + --semi-color-default-hover: rgba(59, 130, 246, 0.06); + --semi-color-default-active: rgba(59, 130, 246, 0.08); } /* 确保按钮圆角在暗色模式下生效(覆盖 Semi 库默认值) */ @@ -108,17 +112,25 @@ border-radius: 8px !important; } -/* 全局基础样式 — 使用新字体族 */ +/* 全局基础样式 — 蓝白渐变主题 */ body { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans SC', 'Microsoft YaHei', sans-serif; color: hsl(var(--foreground)); - background-color: hsl(var(--background)); + background: linear-gradient(135deg, #e0f0ff 0%, #edf5ff 20%, #f5faff 40%, #ffffff 60%, #f0f7ff 80%, #e8f4fd 100%); + background-attachment: fixed; + min-height: 100vh; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* ==================== Tailwind CSS 配置 ==================== */ +/* 暗色模式 body 背景也使用蓝白渐变 */ +html.dark body { + background: linear-gradient(135deg, #e0f0ff 0%, #edf5ff 20%, #f5faff 40%, #ffffff 60%, #f0f7ff 80%, #e8f4fd 100%); + background-attachment: fixed; +} + /* ==================== Tailwind CSS 閰嶇疆 ==================== */ @layer tailwind-base, semi, tailwind-components, tailwind-utils; @@ -1610,183 +1622,598 @@ html.dark .with-pastel-balls::before { padding-top: 0.75rem !important; } -/* ==================== 妯″瀷瀹氫环椤甸潰甯冨眬 ==================== */ -.pricing-layout { - height: calc(100vh - 60px); - overflow: hidden; - margin-top: 60px; +/* ==================== SiliconFlow-style Pricing Page (Cool Effects) ==================== */ + +/* ─── Keyframe Animations ─── */ +@keyframes sf-gradient-shift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } } -.pricing-sidebar { - width: clamp(280px, 24vw, 520px) !important; - min-width: clamp(280px, 24vw, 520px) !important; - max-width: clamp(280px, 24vw, 520px) !important; - height: calc(100vh - 60px); - background-color: var(--semi-color-bg-0); - overflow: auto; +@keyframes sf-float { + 0%, 100% { transform: translateY(0) translateX(0); } + 25% { transform: translateY(-20px) translateX(10px); } + 50% { transform: translateY(-10px) translateX(-5px); } + 75% { transform: translateY(-25px) translateX(8px); } } -.pricing-content { - height: calc(100vh - 60px); - background-color: var(--semi-color-bg-0); - display: flex; - flex-direction: column; +@keyframes sf-pulse-glow { + 0%, 100% { box-shadow: 0 0 20px rgba(25, 118, 210, 0.1); } + 50% { box-shadow: 0 0 40px rgba(25, 118, 210, 0.25); } } -.pricing-pagination-divider { - border-color: var(--semi-color-border); +@keyframes sf-card-in { + from { opacity: 0; transform: translateY(30px) scale(0.96); } + to { opacity: 1; transform: translateY(0) scale(1); } } -.pricing-content-mobile { - height: 100%; - display: flex; - flex-direction: column; - overflow: auto; +@keyframes sf-shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } } -.pricing-search-header { - padding: 0.5rem; - background-color: var(--semi-color-bg-0); - flex-shrink: 0; - position: sticky; +@keyframes sf-glow-ring { + 0%, 100% { opacity: 0.3; transform: scale(1); } + 50% { opacity: 0.8; transform: scale(1.02); } +} + +@keyframes sf-fade-up { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes sf-search-glow { + 0%, 100% { box-shadow: 0 0 0 0 rgba(25, 118, 210, 0); } + 50% { box-shadow: 0 0 0 8px rgba(25, 118, 210, 0.08); } +} + +@keyframes sf-dot-float { + 0% { transform: translateY(100vh) scale(0); opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { transform: translateY(-10vh) scale(1); opacity: 0; } +} + +@keyframes sf-title-reveal { + from { opacity: 0; transform: translateY(30px); filter: blur(8px); } + to { opacity: 1; transform: translateY(0); filter: blur(0); } +} + +@keyframes sf-price-glow { + 0%, 100% { text-shadow: none; } + 50% { text-shadow: 0 0 8px rgba(25, 118, 210, 0.3); } +} + +/* Page Container */ +.sf-pricing-page { + min-height: 100vh; + background: #f7f9fc; + position: relative; + overflow-x: hidden; +} + +/* ─── Floating Particles Background ─── */ +.sf-particles { + position: fixed; top: 0; - z-index: 5; -} - -.pricing-view-container { - flex: 1; - overflow: auto; -} - -.pricing-view-container-mobile { - flex: 1; - overflow: auto; - min-height: 0; -} - -/* ─── Models 新布局 ─── */ -.models-page-layout { - display: flex; - max-width: 1440px; - margin: 0 auto; - padding: 56px 24px 0; - gap: 32px; - height: 100vh; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 0; overflow: hidden; +} + +.sf-particle { + position: absolute; + width: 4px; + height: 4px; + border-radius: 50%; + background: rgba(25, 118, 210, 0.15); + animation: sf-dot-float linear infinite; +} +.sf-particle:nth-child(1) { left: 10%; animation-duration: 12s; animation-delay: 0s; width: 6px; height: 6px; background: rgba(25, 118, 210, 0.1); } +.sf-particle:nth-child(2) { left: 25%; animation-duration: 15s; animation-delay: 2s; width: 4px; height: 4px; } +.sf-particle:nth-child(3) { left: 40%; animation-duration: 10s; animation-delay: 4s; width: 8px; height: 8px; background: rgba(66, 165, 245, 0.12); } +.sf-particle:nth-child(4) { left: 55%; animation-duration: 18s; animation-delay: 1s; width: 3px; height: 3px; } +.sf-particle:nth-child(5) { left: 70%; animation-duration: 13s; animation-delay: 3s; width: 5px; height: 5px; background: rgba(21, 101, 192, 0.1); } +.sf-particle:nth-child(6) { left: 85%; animation-duration: 16s; animation-delay: 5s; width: 4px; height: 4px; } +.sf-particle:nth-child(7) { left: 15%; animation-duration: 14s; animation-delay: 6s; width: 7px; height: 7px; background: rgba(25, 118, 210, 0.08); } +.sf-particle:nth-child(8) { left: 60%; animation-duration: 11s; animation-delay: 7s; width: 3px; height: 3px; } + +.sf-pricing-inner { + max-width: 1400px; + margin: 0 auto; + padding: 0 32px 60px; + position: relative; + z-index: 1; +} + +/* ─── Hero Section ─── */ +.sf-hero { + text-align: center; + padding: 70px 0 50px; + position: relative; +} + +.sf-hero::before { + content: ''; + position: absolute; + top: -100px; + left: 50%; + transform: translateX(-50%); + width: 800px; + height: 500px; + background: radial-gradient(ellipse, rgba(25, 118, 210, 0.06) 0%, transparent 70%); + pointer-events: none; + animation: sf-pulse-glow 6s ease-in-out infinite; +} + +.sf-hero-title { + font-size: 48px; + font-weight: 800; + margin: 0 0 16px; + line-height: 1.2; + background: linear-gradient(135deg, #0d47a1 0%, #1976d2 25%, #42a5f5 50%, #1976d2 75%, #0d47a1 100%); + background-size: 200% 200%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: sf-gradient-shift 4s ease infinite, sf-title-reveal 0.8s ease-out; + text-shadow: none; +} + +.sf-hero-subtitle { + font-size: 17px; + color: #546e7a; + margin: 0 0 36px; + animation: sf-fade-up 0.8s ease-out 0.2s both; +} + +/* ─── Search Bar ─── */ +.sf-search-bar { + position: relative; + max-width: 620px; + margin: 0 auto 28px; + animation: sf-fade-up 0.8s ease-out 0.4s both; +} + +.sf-search-icon { + position: absolute; + left: 18px; + top: 50%; + transform: translateY(-50%); + color: #90a4ae; + transition: color 0.3s; +} + +.sf-search-input { + width: 100%; + height: 52px; + padding: 0 18px 0 48px; + border: 2px solid #e3f2fd; + border-radius: 14px; + background: #fff; + font-size: 15px; + color: #0a1929; + outline: none; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 16px rgba(25, 118, 210, 0.06); box-sizing: border-box; } - -.models-sidebar { - width: 280px; - flex-shrink: 0; - overflow-y: auto; - padding: 0 16px 24px 0; - border-right: 1px solid var(--border-color, #e5e7eb); +.sf-search-input::placeholder { + color: #b0bec5; + transition: color 0.3s; +} +.sf-search-input:focus { + border-color: #1976d2; + box-shadow: 0 4px 24px rgba(25, 118, 210, 0.18), 0 0 0 4px rgba(25, 118, 210, 0.06); + animation: sf-search-glow 2s ease-in-out infinite; +} +.sf-search-input:focus ~ .sf-search-icon { + color: #1976d2; } -.models-sidebar::-webkit-scrollbar { - width: 4px; -} -.models-sidebar::-webkit-scrollbar-thumb { - background: transparent; -} -.models-sidebar:hover::-webkit-scrollbar-thumb { - background: rgba(0,0,0,0.15); - border-radius: 2px; +/* ─── Hot Tags ─── */ +.sf-hot-tags { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + flex-wrap: wrap; + animation: sf-fade-up 0.8s ease-out 0.6s both; } -.models-content { - flex: 1; - min-width: 0; +.sf-hot-label { + font-size: 13px; + font-weight: 600; + color: #546e7a; +} + +.sf-hot-tag { + padding: 5px 14px; + border: 1px solid #e3f2fd; + border-radius: 20px; + background: #fff; + font-size: 12px; + color: #374151; + cursor: pointer; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} +.sf-hot-tag::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(25, 118, 210, 0.08), rgba(66, 165, 245, 0.08)); + opacity: 0; + transition: opacity 0.25s; +} +.sf-hot-tag:hover { + border-color: #1976d2; + color: #1976d2; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(25, 118, 210, 0.15); +} +.sf-hot-tag:hover::before { + opacity: 1; +} + +/* ─── Filter Section ─── */ +.sf-filter-section { + padding: 24px 0; display: flex; flex-direction: column; + gap: 16px; + animation: sf-fade-up 0.8s ease-out 0.7s both; +} + +.sf-filter-row { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.sf-filter-label { + font-size: 14px; + font-weight: 600; + color: #374151; + min-width: 70px; + flex-shrink: 0; +} + +.sf-filter-pills { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.sf-filter-pill { + padding: 7px 18px; + border: 1.5px solid #e3f2fd; + border-radius: 10px; + background: #fff; + font-size: 13px; + color: #546e7a; + cursor: pointer; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; +} +.sf-filter-pill:hover { + border-color: #1976d2; + color: #1976d2; + transform: translateY(-1px); + box-shadow: 0 3px 10px rgba(25, 118, 210, 0.1); +} +.sf-filter-pill.active { + border-color: #1976d2; + background: linear-gradient(135deg, #e3f2fd, #bbdefb); + color: #1976d2; + font-weight: 600; + box-shadow: 0 2px 12px rgba(25, 118, 210, 0.2); +} + +/* ─── Results Header ─── */ +.sf-results-header { + padding: 16px 0 8px; + animation: sf-fade-up 0.8s ease-out 0.8s both; +} + +.sf-results-title { + font-size: 22px; + font-weight: 700; + color: #0a1929; + margin: 0; +} + +.sf-results-count { + color: #1976d2; + font-weight: 800; + animation: sf-price-glow 3s ease-in-out infinite; +} + +/* ─── Card Grid ─── */ +.sf-card-grid { + display: grid; + gap: 22px; + padding: 20px 0; +} + +/* ─── Model Card ─── */ +.sf-model-card { + position: relative; + background: #fff; + border: 1.5px solid #e3f2fd; + border-radius: 16px; + padding: 24px; + height: 230px; + display: flex; + flex-direction: column; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + overflow: hidden; + cursor: pointer; + animation: sf-card-in 0.6s ease-out both; + /* Staggered entrance */ +} +.sf-model-card:nth-child(1) { animation-delay: 0.1s; } +.sf-model-card:nth-child(2) { animation-delay: 0.15s; } +.sf-model-card:nth-child(3) { animation-delay: 0.2s; } +.sf-model-card:nth-child(4) { animation-delay: 0.25s; } +.sf-model-card:nth-child(5) { animation-delay: 0.3s; } +.sf-model-card:nth-child(6) { animation-delay: 0.35s; } +.sf-model-card:nth-child(7) { animation-delay: 0.4s; } +.sf-model-card:nth-child(8) { animation-delay: 0.45s; } +.sf-model-card:nth-child(9) { animation-delay: 0.5s; } + +/* Glow border on hover */ +.sf-model-card::before { + content: ''; + position: absolute; + inset: -2px; + border-radius: 18px; + background: linear-gradient(135deg, #1976d2, #42a5f5, #1976d2); + opacity: 0; + z-index: -1; + transition: opacity 0.4s ease; +} +.sf-model-card::after { + content: ''; + position: absolute; + inset: 0; + border-radius: 16px; + background: #fff; + z-index: -1; +} +.sf-model-card:hover { + transform: translateY(-6px) scale(1.01); + border-color: transparent; + box-shadow: 0 12px 40px rgba(25, 118, 210, 0.2), 0 0 60px rgba(25, 118, 210, 0.06); +} +.sf-model-card:hover::before { + opacity: 1; + animation: sf-glow-ring 2s ease-in-out infinite; +} + +/* Shimmer effect on hover */ +.sf-model-card .sf-shimmer-overlay { + position: absolute; + inset: 0; + border-radius: 16px; + background: linear-gradient(90deg, transparent 0%, rgba(25, 118, 210, 0.04) 50%, transparent 100%); + background-size: 200% 100%; + opacity: 0; + transition: opacity 0.3s; + pointer-events: none; +} +.sf-model-card:hover .sf-shimmer-overlay { + opacity: 1; + animation: sf-shimmer 2s linear infinite; +} + +/* Card Header */ +.sf-card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.sf-card-provider { + display: flex; + align-items: center; + gap: 10px; +} + +.sf-card-icon-wrap { + width: 40px; + height: 40px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #e3f2fd, #bbdefb); + flex-shrink: 0; + transition: all 0.3s; +} +.sf-model-card:hover .sf-card-icon-wrap { + transform: scale(1.08); + box-shadow: 0 4px 12px rgba(25, 118, 210, 0.2); +} + +.sf-card-icon { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; +} + +.sf-card-provider-name { + font-size: 13px; + color: #546e7a; + font-weight: 500; +} + +.sf-card-type-tag { + font-size: 11px; + padding: 3px 10px; + border-radius: 6px; + background: linear-gradient(135deg, #e3f2fd, #bbdefb); + color: #1976d2; + font-weight: 600; + letter-spacing: 0.02em; +} + +/* Default View */ +.sf-card-default-view { + flex: 1; + display: flex; + flex-direction: column; + transition: opacity 0.4s ease; +} + +.sf-card-model-name { + font-size: 17px; + font-weight: 700; + color: #0a1929; + margin: 0 0 10px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: color 0.3s; +} +.sf-model-card:hover .sf-card-model-name { + color: #1976d2; +} + +.sf-card-tags { + display: flex; + gap: 6px; + flex-wrap: wrap; + margin-bottom: 12px; +} + +.sf-card-tag { + font-size: 11px; + padding: 3px 10px; + border-radius: 6px; + background: #f0f7ff; + color: #1976d2; + font-weight: 500; + transition: all 0.25s; +} +.sf-model-card:hover .sf-card-tag { + background: #e3f2fd; +} + +/* Pricing */ +.sf-card-pricing { + margin-top: auto; + display: flex; + gap: 16px; +} + +.sf-price-item { + display: flex; + flex-direction: column; + gap: 3px; +} + +.sf-price-label { + font-size: 11px; + color: #90a4ae; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.sf-price-value { + font-size: 15px; + font-weight: 700; + color: #1976d2; + transition: all 0.3s; +} +.sf-model-card:hover .sf-price-value { + animation: sf-price-glow 2s ease-in-out infinite; +} + +/* Hover View */ +.sf-card-hover-view { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 24px; + background: linear-gradient(transparent, rgba(255,255,255,0.97) 15%); + opacity: 0; + transform: translateY(12px); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + pointer-events: none; +} +.sf-model-card:hover .sf-card-hover-view { + opacity: 1; + transform: translateY(0); +} +.sf-model-card:hover .sf-card-default-view { + opacity: 0.2; +} + +.sf-card-description { + font-size: 13px; + color: #546e7a; + line-height: 1.6; + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; overflow: hidden; } -.models-tabs-bar { +/* ─── Pagination ─── */ +.sf-pagination { display: flex; - gap: 8px; - padding: 16px 0; - flex-shrink: 0; - border-bottom: 1px solid var(--border-color, #e5e7eb); + justify-content: center; + padding: 36px 0 16px; } -.models-tab-btn { - padding: 6px 20px; - border-radius: 20px; - border: 1px solid var(--border-color, #d1d5db); - background: transparent; - cursor: pointer; - font-size: 14px; - color: var(--text-muted, #6b7280); - transition: all 0.2s; - white-space: nowrap; -} -.models-tab-btn:hover { - border-color: var(--primary-color, #3b82f6); - color: var(--primary-color, #3b82f6); -} -.models-tab-btn.active { - background: var(--primary-color, #3b82f6); - color: white; - border-color: var(--primary-color, #3b82f6); +/* ─── Responsive ─── */ +@media (max-width: 1024px) { + .sf-pricing-inner { + padding: 0 20px 40px; + } + .sf-hero-title { + font-size: 36px; + } } -.models-card-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 16px; - padding: 16px 0; - overflow-y: auto; - flex: 1; -} - -.models-card-grid::-webkit-scrollbar { - width: 6px; -} -.models-card-grid::-webkit-scrollbar-thumb { - background: rgba(0,0,0,0.12); - border-radius: 3px; -} - -/* 筛选面板标题 */ -.models-filter-section { - margin-bottom: 24px; -} -.models-filter-title { - font-size: 13px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-muted, #6b7280); - margin-bottom: 12px; - padding-bottom: 8px; - border-bottom: 1px solid var(--border-color, #e5e7eb); -} - -.models-filter-item { - display: flex; - align-items: center; - padding: 6px 10px; - border-radius: 8px; - cursor: pointer; - transition: all 0.15s; - font-size: 14px; - color: var(--text-color, #374151); - gap: 8px; -} -.models-filter-item:hover { - background: var(--hover-bg, #f3f4f6); -} -.models-filter-item.active { - background: var(--primary-light, #eff6ff); - color: var(--primary-color, #3b82f6); - font-weight: 500; -} -.models-filter-item .count { - margin-left: auto; - font-size: 12px; - color: var(--text-muted, #9ca3af); +@media (max-width: 768px) { + .sf-pricing-inner { + padding: 0 16px 32px; + } + .sf-hero { + padding: 40px 0 24px; + } + .sf-hero-title { + font-size: 28px; + } + .sf-hero-subtitle { + font-size: 14px; + } + .sf-filter-row { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + .sf-model-card { + height: auto; + min-height: 180px; + } + .sf-particles { + display: none; + } } /* ==================== semi-ui 缁勪欢鑷畾涔夋牱寮?==================== */ diff --git a/web/src/pages/Home/index.jsx b/web/src/pages/Home/index.jsx index 1633638..91d9982 100644 --- a/web/src/pages/Home/index.jsx +++ b/web/src/pages/Home/index.jsx @@ -1,425 +1,255 @@ /* Copyright (C) 2025 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com */ import React, { useState, useEffect, useCallback, useRef, useContext } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Copy, Check, Code, Terminal, FileCode, Box, Grid3X3, Play, Trophy, PieChart, BookOpen, Search, Sun, Moon, Monitor, Languages } from 'lucide-react'; +import { + Copy, Check, Box, Grid3X3, Trophy, BookOpen, + Zap, Shield, Globe, ArrowRight, Layers, Sparkles, ArrowUpRight, + Cpu, CreditCard, RefreshCw, MessageSquare, Send, Terminal, + Database, Workflow, Lock, BarChart3, Users, ChevronRight, + Cloud, Server, Activity, TrendingUp, Headphones, FileText, + Key, Settings, Monitor, Smartphone, BarChart2, Globe2, +} from 'lucide-react'; import { UserContext } from '../../context/User'; -import { StatusContext } from '../../context/Status'; import { stringToColor, getSystemName } from '../../helpers'; -/* ================================================================== - TokenDance-style Home Page - Design: black/white minimal with serif headings - ================================================================== */ -/* ─── Hero Marquee Carousel Texts ────────────────────────── */ -const CAROUSEL_TEXTS = [ - 'OpenAI 协议', - 'Claude 协议', - 'Gemini 协议', - '智能路由', - '统一计费', - '模型丰富', - '安全可靠', - '开箱即用', -]; - -/* ─── Hero Section ──────────────────────────────────────────── */ - -/* --- Helper: Theme Toggle --- */ -function HomeThemeToggle() { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const ref = useRef(null); - useEffect(() => { - if (!open) return; - const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; - document.addEventListener("mousedown", handler); - return () => document.removeEventListener("mousedown", handler); - }, [open]); - const theme = localStorage.getItem("theme-mode") || "auto"; - const applyTheme = useCallback((val) => { - const actualTheme = val === 'auto' - ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') - : val; - if (actualTheme === 'dark') { - document.body.setAttribute('theme-mode', 'dark'); - document.documentElement.classList.add('dark'); - } else { - document.body.removeAttribute('theme-mode'); - document.documentElement.classList.remove('dark'); - } - }, []); - const setTheme = (val) => { - localStorage.setItem("theme-mode", val); - applyTheme(val); - setOpen(false); - }; - useEffect(() => { - applyTheme(theme); - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const handleChange = () => { - const stored = localStorage.getItem("theme-mode") || "auto"; - applyTheme(stored); - }; - mediaQuery.addEventListener('change', handleChange); - return () => mediaQuery.removeEventListener('change', handleChange); - }, [applyTheme]); - const icons = { light: , dark: , auto: }; - return ( -
- - {open && ( -
- {[ - { key: "light", icon: , label: t("浅色模式") }, - { key: "dark", icon: , label: t("深色模式") }, - { key: "auto", icon: , label: t("自动模式") }, - ].map((opt) => ( - - ))} -
- )} -
- ); -} - -/* --- Helper: Language Selector --- */ -function HomeLanguageSelector() { - const { t, i18n } = useTranslation(); - const [open, setOpen] = useState(false); - const ref = useRef(null); - useEffect(() => { - if (!open) return; - const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; - document.addEventListener("mousedown", handler); - return () => document.removeEventListener("mousedown", handler); - }, [open]); - const current = (i18n.language || "").startsWith("zh") ? "zh-CN" : "en"; - const switchLang = (code) => { i18n.changeLanguage(code); setOpen(false); }; - const langLabels = { "zh-CN": "简体中文", "en": "English" }; - return ( -
- - {open && ( -
- {Object.entries(langLabels).map(([code, label]) => ( - - ))} -
- )} -
- ); -} - -function HeroSection() { +/* ═══════════════ SECTION 1: HERO ═══════════════ */ +function Hero() { const { t } = useTranslation(); const [userState] = useContext(UserContext); - const [statusState] = useContext(StatusContext); const [copied, setCopied] = useState(false); - const navigate = useNavigate(); - // 读取管理员开关:默认 true(未加载 status 前也显示) - const showThemeToggle = statusState?.status?.HeaderShowThemeToggle !== false; - const showLanguageSelector = false; - const copyCmd = useCallback(() => { - navigator.clipboard.writeText("https://tokendance.space"); + const copyUrl = useCallback(() => { + navigator.clipboard.writeText('https://api.turingtoken.ai'); setCopied(true); setTimeout(() => setCopied(false), 1500); }, []); + return ( -
-
-
-
- - logo - TuringToken - BETA - -
- -
- {showThemeToggle && } - {showLanguageSelector && } - {userState.user ? ( - - - {userState.user.username?.charAt(0).toUpperCase()} - - - ) : ( - - {t("登录")} - - )} -
-
-
-
-
-

- {"// unified model API gateway"} -

-
-

- {t("更快、更稳、更省")}
{t("Token服务平台")} -

-

- {t("兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。")} -

-
- - {t("开始使用")} - - - {t("查看价格")} → - -
-
-
-
-
-
- - - -
- terminal - -
-
-              {`curl https://api.turingtoken.ai/v1/chat/completions \\
-  -H "Authorization: Bearer \$YOUR_API_KEY" \\
-  -H "Content-Type: application/json" \\
-  -d '{
-    "model": "gpt-4o",
-    "messages": [{"role": "user", "content": "Hello"}]
-  }'`}
-            
-
-
-
+
+ {/* Background decorative elements */} +
+
+ + {/* Grid pattern */} +
- {/* Hero brand wordmark — TURINGTOKEN */} -
-
- Turing - Token -
-
- - {/* Feature marquee strip — 轮播: OpenAI 协议 / Claude 协议 / 智能路由 ... */} -
-
- {[...CAROUSEL_TEXTS, ...CAROUSEL_TEXTS].map((text, i) => ( - - {text} - + {/* Nav */} +
-
- ); -}const FEATURES = [ - { - titleKey: '多协议兼容', - descKey: - '原生支持 OpenAI、Claude、Gemini 文本协议,覆盖图像 / 视频 / 文本转语音生成。无需修改代码,切换 Base URL 即可接入。', - code: 'baseURL: "api.turingtoken.ai"', - }, - { - titleKey: '智能路由', - descKey: - '根据模型名称自动路由至对应供应商。一个入口,无需关心底层调度。', - code: 'route(model) → provider', - }, - { - titleKey: '统一计费', - descKey: - '跨供应商统一 Token 消耗统计与账单。告别多平台分别充值的混乱。', - code: 'billing.unified()', - }, - { - titleKey: '容错降级', - descKey: - '同一模型支持多供应商端点自动切换;单次请求可指定多个候选模型,逐级降级,保障服务持续可用。', - code: 'fallback: model[] → provider[]', - }, - { - titleKey: '模型丰富', - descKey: - '接入 MiniMax、通义千问、Kimi、智谱、DeepSeek 等国内头部模型。持续扩展中。', - code: 'models.list() → 80+', - }, - { - titleKey: '开箱即用', - descKey: - '一键登录,分钟级接入。兼容现有 SDK,零迁移成本。', - code: 'import OpenAI from "openai"', - }, -]; - -function FeaturesSection() { - const { t } = useTranslation(); - return ( -
-
-

- {/* features */} -

-

- {t('为接入 AI 模型的开发者而造。')} -

-
- {FEATURES.map((f, i) => ( -
- - {f.code} - -

- {t(f.titleKey)} -

-

- {t(f.descKey)} -

-
- ))} -
-
-
- ); -} - -/* ─── How It Works Section ──────────────────────────────────── */ - -const STEPS = [ - { - step: '01', - titleKey: '注册账号', - descKey: '通过邮箱一键注册,即刻开始使用。', - link: '/register', - }, - { - step: '02', - titleKey: '创建 API Key', - descKey: '在控制台创建密钥,支持多 Key 管理与权限控制。', - link: '/console/token', - }, - { - step: '03', - titleKey: '发起请求', - descKey: '使用你熟悉的 SDK 调用任意模型,完全兼容原生协议。', - link: '/pricing', - }, -]; - -function HowItWorksSection() { - const { t } = useTranslation(); - return ( -
-
-
-
- {'// how it works'} -
-

- {t('三步接入,分钟级上线')} -

-
- {STEPS.map((s, i) => ( - -
-
- - {s.step} - -
-
-

- {t(s.titleKey)} -

-

- {t(s.descKey)} -

-
- - → +
+ {userState.user ? ( + + + {userState.user.username?.charAt(0).toUpperCase()} + + ) : ( + + {t('登录')} + + )} +
+ + + {/* Content */} +
+
+ {/* Left */} +
+
+ + Unified AI Gateway
- - ))} + +

+ {t('更快、更稳、更省')} +
+ {t('Token服务平台')} +

+ +

+ {t('兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。')} +

+ +
+ + {t('开始使用')} + + + {t('查看价格')} + +
+ + {/* Stats */} +
+ {[ + { n: '80+', l: 'AI 模型' }, + { n: '40+', l: '供应商' }, + { n: '99.9%', l: '可用率' }, + ].map((s, i) => ( +
+
{s.n}
+
{s.l}
+
+ ))} +
+
+ + {/* Right — Code card */} +
+
+ {/* Header */} +
+
+ + + +
+ terminal + +
+ {/* Code */} +
+
# Quick start with curl
+
+ curl{' '} + https://api.turingtoken.ai/v1/chat/completions \ +
+
{' '}-H "Authorization: Bearer $KEY" \
+
{' '}-H "Content-Type: application/json" \
+
{' '}-d '{'{'}
+
{' '}"model": "gpt-4o",
+
{' '}"messages": [{'{'}"role": "user", "content": "Hello"{'}'}]
+
{'}'}'
+
# Response
+
{'{'} "choices": [{'{'} "message": {'{'} "content": "Hello! How can I help?" {'}'} {'}'}] {'}'}
+
+
+
+
+
+ + {/* Bottom marquee */} +
+
+ {[...Array(3)].fill(null).map((_, gi) => ( + + {['OpenAI', 'Claude', 'Gemini', 'DeepSeek', 'Qwen', 'MiniMax', 'Kimi', '智谱', 'Groq', 'Azure'].map((name, i) => ( + + {name} + + ))} + + ))} +
); } -/* ─── Quick Start Section (Code Tabs) ───────────────────────── */ +/* ═══════════════ SECTION 2: CAPABILITIES ═══════════════ */ +const CAPABILITIES = [ + { icon: , title: '多协议兼容', desc: '原生支持 OpenAI、Claude、Gemini,切换 Base URL 即可接入' }, + { icon: , title: '智能路由', desc: '根据模型名称自动路由至最优供应商,无需关心底层调度' }, + { icon: , title: '统一计费', desc: '跨供应商统一 Token 消耗统计与账单,告别多平台充值' }, + { icon: , title: '容错降级', desc: '多供应商端点自动切换,逐级降级保障服务持续可用' }, + { icon: , title: '模型丰富', desc: '接入 80+ 主流模型,覆盖文本、图像、视频、语音全模态' }, + { icon: , title: '开箱即用', desc: '一键登录,分钟级接入,兼容现有 SDK,零迁移成本' }, +]; -const PROVIDERS = ['openai', 'claude', 'gemini']; -const PROVIDER_LABELS = { openai: 'OpenAI', claude: 'Claude', gemini: 'Gemini' }; -const LANGUAGES = ['curl', 'python', 'nodejs']; +function Capabilities() { + const { t } = useTranslation(); + return ( +
+
+
+

{t('平台核心能力')}

+

一个 API 网关,连接所有主流 AI 模型供应商

+
+
+ {CAPABILITIES.map((p, i) => ( +
+
+ {p.icon} +
+

{t(p.title)}

+

{t(p.desc)}

+
+ ))} +
+
+
+ ); +} + +/* ═══════════════ SECTION 3: HOW IT WORKS ═══════════════ */ +const STEPS = [ + { n: '01', title: '注册账号', desc: '通过邮箱一键注册,即刻开始使用。', icon: , link: '/register' }, + { n: '02', title: '创建 API Key', desc: '在控制台创建密钥,支持多 Key 管理。', icon: , link: '/console/token' }, + { n: '03', title: '发起请求', desc: '使用你熟悉的 SDK 调用任意模型。', icon: , link: '/pricing' }, +]; + +function HowItWorks() { + const { t } = useTranslation(); + return ( +
+
+
+

{t('三步接入,分钟级上线')}

+
+
+ {STEPS.map((s, i) => ( + +
+
+ {s.icon} +
+ + {s.n} + +
+

{t(s.title)}

+

{t(s.desc)}

+ {i < STEPS.length - 1 && ( +
+
+
+ )} + + ))} +
+
+
+ ); +} + +/* ═══════════════ SECTION 5: QUICK START ═══════════════ */ +const PROVS = ['openai', 'claude', 'gemini']; +const PROV_LABELS = { openai: 'OpenAI', claude: 'Claude', gemini: 'Gemini' }; +const LANGS = ['curl', 'python', 'nodejs']; const LANG_LABELS = { curl: 'cURL', python: 'Python', nodejs: 'Node.js' }; - -const CODE_SAMPLES = { +const SAMPLES = { openai: { curl: `curl https://api.turingtoken.ai/v1/chat/completions \\ -H "Authorization: Bearer $OPENAI_API_KEY" \\ @@ -499,289 +329,344 @@ console.log(response.content[0].text);`, python: `import google.generativeai as genai genai.configure(api_key="sk-your-key") - model = genai.GenerativeModel("gemini-2.0-flash") response = model.generate_content("Hello!") print(response.text)`, nodejs: `import { GoogleGenerativeAI } from "@google/generative-ai"; const genAI = new GoogleGenerativeAI("sk-your-key"); -const model = genAI.getGenerativeModel({ - model: "gemini-2.0-flash", -}); - +const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); const result = await model.generateContent("Hello!"); console.log(result.response.text());`, }, }; -function QuickStartSection() { +function QuickStart() { const { t } = useTranslation(); - const [provider, setProvider] = useState('openai'); + const [prov, setProv] = useState('openai'); const [lang, setLang] = useState('curl'); const [copied, setCopied] = useState(false); - - const code = CODE_SAMPLES[provider]?.[lang] || ''; - - const handleCopy = useCallback(() => { - navigator.clipboard.writeText(code); - setCopied(true); - setTimeout(() => setCopied(false), 1500); - }, [code]); + const code = SAMPLES[prov]?.[lang] || ''; + const copy = useCallback(() => { navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 1500); }, [code]); return ( -
-
-

- Quick Start -

-

- {t('快速开始')} -

- - {/* Provider tabs */} -
- {PROVIDERS.map((p) => ( - - ))} +
+
+
+

{t('快速开始')}

- - {/* Code block */} -
-
-
- {LANGUAGES.map((l) => ( - + ))} +
+
+ {LANGS.map(l => ( + ))} +
-
-
-
-              {code}
-            
+
+
{code}
- - {/* Call to docs */} - +
-

- Model Pricing -

-

- {t('查看模型定价,了解完整列表')} -

+

Model Pricing

+

{t('查看模型定价,了解完整列表')}

- - → - +
); } -/* ─── Pricing / CTA Section ──────────────────────────────────── */ - -function PricingSection() { +/* ═══════════════ SECTION 6: CTA ═══════════════ */ +function CTA() { const { t } = useTranslation(); return ( -
-
-
- {'// pricing'} -
-

- {t('获取 Token 额度')} -

- - {/* Token credit callout */} -
-
- - {t('今日特惠')} - - - {t('注册即送 ¥10 额度')} +
+
+
+
+
+ + + 限时活动
- - {t('新用户注册即赠 ¥10 Token,7 天有效期')} - +
+ SPECIAL OFFER +

{t('注册即送 ¥10 额度')}

+

{t('新用户注册即赠 ¥10 Token,7 天有效期。兼容 OpenAI / Claude / Gemini 协议,一个 API Key 调用所有主流模型。')}

+
+ + + 无需绑定信用卡 + + + + 即开即用 + + + + 按量计费 + +
+
+ + {t('免费注册')} + + + {t('查看价格')} + +
+
- - {/* Call to action */} - -

- {t('立即注册,开始使用')} -

-

- {t( - '兼容 OpenAI / Claude / Gemini 协议。一个 API Key,调用所有主流模型。免去多平台充值与管理的麻烦。' - )} -

- - {t('免费注册')} → - -
); } -/* ─── Providers Marquee Section ──────────────────────────────── */ +/* ═══════════════ SECTION 7: STATS ═══════════════ */ +function Stats() { + const { t } = useTranslation(); + const nav = useNavigate(); + return ( +
+
+
+ {[ + { n: '80+', l: 'AI 模型', d: '覆盖主流模型' }, + { n: '40+', l: '供应商', d: '全球优质渠道' }, + { n: '99.9%', l: '可用率', d: '稳定可靠' }, + { n: '<100ms', l: '响应延迟', d: '极速响应' }, + ].map((s, i) => ( +
+
{s.n}
+
{s.l}
+
{s.d}
+
+ ))} +
+
+ +
+
+
+ ); +} -const PROVIDER_LOGOS = [ - { name: 'OpenAI', className: 'opacity-60' }, - { name: 'Claude', className: 'opacity-60' }, - { name: 'Gemini', className: 'opacity-60' }, - { name: 'DeepSeek', className: 'opacity-60' }, - { name: 'Qwen', className: 'opacity-60' }, - { name: 'MiniMax', className: 'opacity-60' }, - { name: 'Kimi', className: 'opacity-60' }, - { name: '智谱', className: 'opacity-60' }, - { name: 'Groq', className: 'opacity-60' }, - { name: 'Azure', className: 'opacity-60' }, +/* ═══════════════ SECTION 8: PRICING PREVIEW ═══════════════ */ +const PRICING_MODELS = [ + { name: 'GPT-4o', input: '¥0.02/1K', output: '¥0.08/1K' }, + { name: 'Claude 3.5 Sonnet', input: '¥0.02/1K', output: '¥0.10/1K' }, + { name: 'Gemini 1.5 Pro', input: '¥0.01/1K', output: '¥0.05/1K' }, + { name: 'DeepSeek V3', input: '¥0.002/1K', output: '¥0.006/1K' }, ]; -function ProvidersSection() { +function PricingPreview() { const { t } = useTranslation(); - const navigate = useNavigate(); - + const nav = useNavigate(); return ( -
- {/* Provider logos marquee area */} -
-
-

- {'// supported providers'} -

-

- {t('40+ AI 供应商,统一接入')} -

- -
-
- {[...PROVIDER_LOGOS, ...PROVIDER_LOGOS].map((logo, i) => ( - - {logo.name} - - ))} -
-
- -
- -
+
+
+
+

{t('透明定价')}

+

按量计费,用多少付多少,无隐藏费用

+
+
+
+
模型
+
输入价格
+
输出价格
+
操作
+
+ {PRICING_MODELS.map((m, i) => ( +
+
{m.name}
+
{m.input}
+
{m.output}
+
+ +
+
+ ))} +
+
+
-
- -
-

- {t('免费注册,分钟级接入。兼容你现有的 SDK 和工作流。')} -

); } -/* ─── Dark Footer ────────────────────────────────────────────── */ +/* ═══════════════ SECTION 10: TESTIMONIALS ═══════════════ */ +const TESTIMONIALS = [ + { name: '张工', role: 'AI 创业公司 CTO', content: '接入 TuringToken 后,我们无需逐一对接各家 API,开发效率提升了 3 倍。智能路由让我们的服务稳定性达到了 99.95%。' }, + { name: '李经理', role: '互联网大厂技术负责人', content: '统一计费功能非常实用,以前每个月要对账 5 家供应商,现在一个后台全搞定。成本降低了 20%。' }, + { name: '王同学', role: '独立开发者', content: '作为个人开发者,最看重的是按量计费和免费额度。TuringToken 让我能用上 GPT-4o 而不用担心账单。' }, +]; -function DarkFooter() { +function Testimonials() { const { t } = useTranslation(); return ( -
-
-
-
- - {t('定价')} - - - {t('控制台')} - +
+
+
+

{t('用户评价')}

+

来自真实用户的声音

+
+
+ {TESTIMONIALS.map((t, i) => ( +
+
+
+ {t.name.charAt(0)} +
+
+
{t.name}
+
{t.role}
+
+
+

"{t.content}"

+
+ {[1,2,3,4,5].map(s => ( + + + + ))} +
+
+ ))} +
+
+
+ ); +} + +/* ═══════════════ SECTION 11: FAQ ═══════════════ */ +const FAQS = [ + { q: '什么是 TuringToken?', a: 'TuringToken 是一个统一的 AI 网关平台,通过一个 API Key 即可调用 OpenAI、Claude、Gemini 等多家主流大模型,无需分别对接。' }, + { q: '如何开始使用?', a: '注册账号后,在控制台创建 API Key,将 Base URL 设置为 api.turingtoken.ai 即可。兼容现有 OpenAI SDK,无需修改代码。' }, + { q: '支持哪些模型?', a: '目前支持 80+ 模型,包括 GPT-4o、Claude 3.5 Sonnet、Gemini 1.5 Pro、DeepSeek V3、通义千问、智谱 GLM-4 等主流模型。' }, + { q: '计费方式是什么?', a: '按 Token 消耗量计费,用多少付多少。新用户注册即送 ¥10 额度,7 天有效期。无最低消费,无月费。' }, + { q: '数据安全如何保障?', a: '我们不存储任何请求和响应内容,所有数据仅用于实时转发。支持 HTTPS 加密传输,企业版可选私有部署。' }, +]; + +function FAQ() { + const { t } = useTranslation(); + const [openIdx, setOpenIdx] = useState(null); + return ( +
+
+
+

{t('常见问题')}

+

如有其他问题,请联系 support@turingtoken.ai

+
+
+ {FAQS.map((f, i) => ( +
+ + {openIdx === i && ( +
+

{f.a}

+
+ )} +
+ ))} +
+
+
+ ); +} + +/* ═══════════════ FOOTER ═══════════════ */ +function Footer() { + const { t } = useTranslation(); + return ( +
+
+
+
+
+ + TuringToken +
+

统一 AI 网关,一个 API 连接所有主流大模型。

+
+
+

产品

+
+ {t('定价')} + {t('控制台')} + {t('模型评测')} +
+
+
+

支持

+
+ {t('文档')} + {t('关于我们')} + support@turingtoken.ai +
+
+
+

法律

+
+ 服务协议 + 隐私政策 + 合规声明 +
-
- - © 2026 {getSystemName()} - +
+ © 2026 {getSystemName()} All rights reserved. + Powered by QuantumNous
); } -/* ─── Main Home Page ────────────────────────────────────────── */ - -const Home = () => { - const { t } = useTranslation(); - - return ( -
-
- - - - - - - -
-
- ); -}; +/* ═══════════════ EXPORT ═══════════════ */ +const Home = () => ( +
+ + + + + + + + + +
+
+); export default Home; -