- 首页定价区域添加响应式分页(移动端6/平板8/桌面9) - 渠道列表显示 ID,支持在名称中查看 - 渠道定价卡片添加 ID 标签和使用提示 Banner - 代码示例中展示 API Key 指定渠道的格式 Co-Authored-By: Claude <noreply@anthropic.com>feat/alipay-payment
| @@ -304,6 +304,7 @@ export const getChannelsColumns = ({ | |||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| render: (text, record, index) => { | render: (text, record, index) => { | ||||
| const passThroughEnabled = isRequestPassThroughEnabled(record); | const passThroughEnabled = isRequestPassThroughEnabled(record); | ||||
| const nameWithId = `${record.id} - ${text}`; | |||||
| const nameNode = | const nameNode = | ||||
| record.remark && record.remark.trim() !== '' ? ( | record.remark && record.remark.trim() !== '' ? ( | ||||
| <Tooltip | <Tooltip | ||||
| @@ -333,10 +334,10 @@ export const getChannelsColumns = ({ | |||||
| trigger='hover' | trigger='hover' | ||||
| position='topLeft' | position='topLeft' | ||||
| > | > | ||||
| <span>{text}</span> | |||||
| <span>{nameWithId}</span> | |||||
| </Tooltip> | </Tooltip> | ||||
| ) : ( | ) : ( | ||||
| <span>{text}</span> | |||||
| <span>{nameWithId}</span> | |||||
| ); | ); | ||||
| if (!passThroughEnabled) { | if (!passThroughEnabled) { | ||||
| @@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com | |||||
| */ | */ | ||||
| import React, { useState, useEffect, useMemo } from 'react'; | import React, { useState, useEffect, useMemo } from 'react'; | ||||
| import { Card, Avatar, Typography, Table, Tag, Spin } from '@douyinfe/semi-ui'; | |||||
| import { Card, Avatar, Typography, Table, Tag, Spin, Banner } from '@douyinfe/semi-ui'; | |||||
| import { IconServer } from '@douyinfe/semi-icons'; | import { IconServer } from '@douyinfe/semi-icons'; | ||||
| import { API } from '../../../../../helpers'; | import { API } from '../../../../../helpers'; | ||||
| @@ -103,6 +103,7 @@ const ChannelPricingCard = ({ | |||||
| // 准备表格数据 | // 准备表格数据 | ||||
| const tableData = channelPricingData.map((item, index) => ({ | const tableData = channelPricingData.map((item, index) => ({ | ||||
| key: item.channel_id || index, | key: item.channel_id || index, | ||||
| channelId: item.channel_id, | |||||
| channelName: `渠道${['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'][index] || ` ${index + 1}`}`, | channelName: `渠道${['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'][index] || ` ${index + 1}`}`, | ||||
| channelTags: item.tags || [], // 渠道定价的标签列表 | channelTags: item.tags || [], // 渠道定价的标签列表 | ||||
| channelType: item.channel_type, | channelType: item.channel_type, | ||||
| @@ -125,6 +126,9 @@ const ChannelPricingCard = ({ | |||||
| dataIndex: 'channelName', | dataIndex: 'channelName', | ||||
| render: (text, record) => ( | render: (text, record) => ( | ||||
| <div className='flex items-center gap-2 flex-wrap'> | <div className='flex items-center gap-2 flex-wrap'> | ||||
| <Tag color='grey' size='small' shape='circle'> | |||||
| ID: {record.channelId} | |||||
| </Tag> | |||||
| <Tag color='cyan' size='small' shape='circle'> | <Tag color='cyan' size='small' shape='circle'> | ||||
| {text} | {text} | ||||
| </Tag> | </Tag> | ||||
| @@ -237,6 +241,11 @@ const ChannelPricingCard = ({ | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <Banner | |||||
| type='info' | |||||
| description={t('在 API Key 后添加 ":渠道ID" 可指定使用特定渠道,如:sk-xxxx:1')} | |||||
| className='mb-4' | |||||
| /> | |||||
| <Table | <Table | ||||
| dataSource={tableData} | dataSource={tableData} | ||||
| columns={columns} | columns={columns} | ||||
| @@ -46,7 +46,7 @@ const getDefaultCodeByLang = () => ({ | |||||
| from openai import OpenAI | from openai import OpenAI | ||||
| client = OpenAI( | client = OpenAI( | ||||
| api_key="<Your API Key>", | |||||
| api_key="<Your API Key>-<Channel ID>", | |||||
| base_url="__BASE_URL__" | base_url="__BASE_URL__" | ||||
| ) | ) | ||||
| @@ -66,7 +66,7 @@ print(response.choices[0].message.content) | |||||
| import com.openai.OpenAI; | import com.openai.OpenAI; | ||||
| import com.openai.models.*; | import com.openai.models.*; | ||||
| OpenAI client = new OpenAI("<Your API Key>", "__BASE_URL__"); | |||||
| OpenAI client = new OpenAI("<Your API Key>-<Channel ID>", "__BASE_URL__"); | |||||
| ChatCompletionRequest request = ChatCompletionRequest.builder() | ChatCompletionRequest request = ChatCompletionRequest.builder() | ||||
| .model("__MODEL_NAME__") | .model("__MODEL_NAME__") | ||||
| @@ -91,37 +91,37 @@ import ( | |||||
| ) | ) | ||||
| func main() { | func main() { | ||||
| client := openai.NewClient("<Your API Key>", "__BASE_URL__") | |||||
| client := openai.NewClient("<Your API Key>-<Channel ID>", "__BASE_URL__") | |||||
| messages := []openai.ChatMessage{ | messages := []openai.ChatMessage{ | ||||
| {Role: "system", Content: "You are a helpful assistant."}, | {Role: "system", Content: "You are a helpful assistant."}, | ||||
| {Role: "user", Content: "Hello, how are you?"}, | {Role: "user", Content: "Hello, how are you?"}, | ||||
| } | } | ||||
| response, err := client.ChatCompletions.Create(context.Background(), openai.ChatCompletionRequest{ | response, err := client.ChatCompletions.Create(context.Background(), openai.ChatCompletionRequest{ | ||||
| Model: "__MODEL_NAME__", | Model: "__MODEL_NAME__", | ||||
| Messages: messages, | Messages: messages, | ||||
| MaxTokens: 128000, | MaxTokens: 128000, | ||||
| Temperature: 0.7, | Temperature: 0.7, | ||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| panic(err) | panic(err) | ||||
| } | } | ||||
| fmt.Println(response.Choices[0].Message.Content) | fmt.Println(response.Choices[0].Message.Content) | ||||
| } | } | ||||
| `, | `, | ||||
| shell: ` | shell: ` | ||||
| #!/bin/bash | #!/bin/bash | ||||
| API_KEY="<Your API Key>" | |||||
| API_KEY="<Your API Key>-<Channel ID>" | |||||
| MODEL_ID="__MODEL_NAME__" | MODEL_ID="__MODEL_NAME__" | ||||
| BASE_URL="__BASE_URL__" | BASE_URL="__BASE_URL__" | ||||
| curl -X POST "$BASE_URL/v1/chat/completions" \ | |||||
| -H "Content-Type: application/json" \ | |||||
| -H "Authorization: Bearer $API_KEY" \ | |||||
| curl -X POST "$BASE_URL/v1/chat/completions" \\ | |||||
| -H "Content-Type: application/json" \\ | |||||
| -H "Authorization: Bearer $API_KEY" \\ | |||||
| -d '{ | -d '{ | ||||
| "model": "'$MODEL_ID'", | "model": "'$MODEL_ID'", | ||||
| "messages": [ | "messages": [ | ||||
| @@ -24,6 +24,7 @@ import PricingCardView from '../../components/table/model-pricing/view/card/Pric | |||||
| import ModelDetailSideSheet from '../../components/table/model-pricing/modal/ModelDetailSideSheet'; | import ModelDetailSideSheet from '../../components/table/model-pricing/modal/ModelDetailSideSheet'; | ||||
| import { useModelPricingData } from '../../hooks/model-pricing/useModelPricingData'; | import { useModelPricingData } from '../../hooks/model-pricing/useModelPricingData'; | ||||
| import { usePricingFilterCounts } from '../../hooks/model-pricing/usePricingFilterCounts'; | import { usePricingFilterCounts } from '../../hooks/model-pricing/usePricingFilterCounts'; | ||||
| import { useIsMobile } from '../../hooks/common/useIsMobile'; | |||||
| import { getLobeHubIcon } from '../../helpers'; | import { getLobeHubIcon } from '../../helpers'; | ||||
| // 与 model_meta.go ModelType 常量一致:1=Chat, 2=Image, 3=Audio, 4=Video, 5=Embedding, 6=Rerank, 7=Vision, 8=Other | // 与 model_meta.go ModelType 常量一致:1=Chat, 2=Image, 3=Audio, 4=Video, 5=Embedding, 6=Rerank, 7=Vision, 8=Other | ||||
| @@ -38,9 +39,28 @@ const MODEL_TYPE_LIST = [ | |||||
| { value: 8, labelKey: '其他' }, | { value: 8, labelKey: '其他' }, | ||||
| ]; | ]; | ||||
| // 首页定价区域分页大小配置 | |||||
| // 与 gridColsClass 网格布局配合:移动端1列、平板2列、桌面3列 | |||||
| const HOME_PAGE_SIZE = { | |||||
| mobile: 6, // 1列 × 6行 | |||||
| tablet: 8, // 2列 × 4行 | |||||
| desktop: 9, // 3列 × 3行 | |||||
| }; | |||||
| const HomePricingFilters = ({ t }) => { | const HomePricingFilters = ({ t }) => { | ||||
| const [showRatio, setShowRatio] = useState(false); | const [showRatio, setShowRatio] = useState(false); | ||||
| const isMobile = useIsMobile(); | |||||
| const pricingData = useModelPricingData(); | const pricingData = useModelPricingData(); | ||||
| // 根据屏幕尺寸动态计算分页大小 | |||||
| const pageSize = useMemo(() => { | |||||
| if (isMobile) return HOME_PAGE_SIZE.mobile; | |||||
| // 通过窗口宽度判断平板(768px-1024px 为 md 范围) | |||||
| if (typeof window !== 'undefined' && window.innerWidth < 1024) { | |||||
| return HOME_PAGE_SIZE.tablet; | |||||
| } | |||||
| return HOME_PAGE_SIZE.desktop; | |||||
| }, [isMobile]); | |||||
| const { | const { | ||||
| vendorModels, | vendorModels, | ||||
| typeModels, | typeModels, | ||||
| @@ -154,7 +174,7 @@ const HomePricingFilters = ({ t }) => { | |||||
| filteredModels={pricingData.filteredModels} | filteredModels={pricingData.filteredModels} | ||||
| loading={pricingData.loading} | loading={pricingData.loading} | ||||
| rowSelection={null} | rowSelection={null} | ||||
| pageSize={pricingData.pageSize} | |||||
| pageSize={pageSize} | |||||
| setPageSize={pricingData.setPageSize} | setPageSize={pricingData.setPageSize} | ||||
| currentPage={pricingData.currentPage} | currentPage={pricingData.currentPage} | ||||
| setCurrentPage={pricingData.setCurrentPage} | setCurrentPage={pricingData.setCurrentPage} | ||||
| @@ -171,7 +191,7 @@ const HomePricingFilters = ({ t }) => { | |||||
| selectedRowKeys={[]} | selectedRowKeys={[]} | ||||
| openModelDetail={pricingData.openModelDetail} | openModelDetail={pricingData.openModelDetail} | ||||
| gridColsClass='grid-cols-1 md:grid-cols-2 lg:grid-cols-3' | gridColsClass='grid-cols-1 md:grid-cols-2 lg:grid-cols-3' | ||||
| showPagination={false} | |||||
| showPagination={true} | |||||
| /> | /> | ||||
| </div> | </div> | ||||