Spaces:
Build error
Build error
| /* | |
| 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 <https://www.gnu.org/licenses/>. | |
| For commercial licensing, please contact support@quantumnous.com | |
| */ | |
| import { Toast, Pagination } from '@douyinfe/semi-ui'; | |
| import { toastConstants } from '../constants'; | |
| import React from 'react'; | |
| import { toast } from 'react-toastify'; | |
| import { | |
| THINK_TAG_REGEX, | |
| MESSAGE_ROLES, | |
| } from '../constants/playground.constants'; | |
| import { TABLE_COMPACT_MODES_KEY } from '../constants'; | |
| import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile'; | |
| const HTMLToastContent = ({ htmlContent }) => { | |
| return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />; | |
| }; | |
| export default HTMLToastContent; | |
| export function isAdmin() { | |
| let user = localStorage.getItem('user'); | |
| if (!user) return false; | |
| user = JSON.parse(user); | |
| return user.role >= 10; | |
| } | |
| export function isRoot() { | |
| let user = localStorage.getItem('user'); | |
| if (!user) return false; | |
| user = JSON.parse(user); | |
| return user.role >= 100; | |
| } | |
| export function getSystemName() { | |
| let system_name = localStorage.getItem('system_name'); | |
| if (!system_name) return 'New API'; | |
| return system_name; | |
| } | |
| export function getLogo() { | |
| let logo = localStorage.getItem('logo'); | |
| if (!logo) return '/logo.png'; | |
| return logo; | |
| } | |
| export function getUserIdFromLocalStorage() { | |
| let user = localStorage.getItem('user'); | |
| if (!user) return -1; | |
| user = JSON.parse(user); | |
| return user.id; | |
| } | |
| export function getFooterHTML() { | |
| return localStorage.getItem('footer_html'); | |
| } | |
| export async function copy(text) { | |
| let okay = true; | |
| try { | |
| await navigator.clipboard.writeText(text); | |
| } catch (e) { | |
| try { | |
| // 构建 textarea 执行复制命令,保留多行文本格式 | |
| const textarea = window.document.createElement('textarea'); | |
| textarea.value = text; | |
| textarea.setAttribute('readonly', ''); | |
| textarea.style.position = 'fixed'; | |
| textarea.style.left = '-9999px'; | |
| textarea.style.top = '-9999px'; | |
| window.document.body.appendChild(textarea); | |
| textarea.select(); | |
| window.document.execCommand('copy'); | |
| window.document.body.removeChild(textarea); | |
| } catch (e) { | |
| okay = false; | |
| console.error(e); | |
| } | |
| } | |
| return okay; | |
| } | |
| // isMobile 函数已移除,请改用 useIsMobile Hook | |
| let showErrorOptions = { autoClose: toastConstants.ERROR_TIMEOUT }; | |
| let showWarningOptions = { autoClose: toastConstants.WARNING_TIMEOUT }; | |
| let showSuccessOptions = { autoClose: toastConstants.SUCCESS_TIMEOUT }; | |
| let showInfoOptions = { autoClose: toastConstants.INFO_TIMEOUT }; | |
| let showNoticeOptions = { autoClose: false }; | |
| const isMobileScreen = window.matchMedia( | |
| `(max-width: ${MOBILE_BREAKPOINT - 1}px)`, | |
| ).matches; | |
| if (isMobileScreen) { | |
| showErrorOptions.position = 'top-center'; | |
| // showErrorOptions.transition = 'flip'; | |
| showSuccessOptions.position = 'top-center'; | |
| // showSuccessOptions.transition = 'flip'; | |
| showInfoOptions.position = 'top-center'; | |
| // showInfoOptions.transition = 'flip'; | |
| showNoticeOptions.position = 'top-center'; | |
| // showNoticeOptions.transition = 'flip'; | |
| } | |
| export function showError(error) { | |
| console.error(error); | |
| if (error.message) { | |
| if (error.name === 'AxiosError') { | |
| switch (error.response.status) { | |
| case 401: | |
| // 清除用户状态 | |
| localStorage.removeItem('user'); | |
| // toast.error('错误:未登录或登录已过期,请重新登录!', showErrorOptions); | |
| window.location.href = '/login?expired=true'; | |
| break; | |
| case 429: | |
| Toast.error('错误:请求次数过多,请稍后再试!'); | |
| break; | |
| case 500: | |
| Toast.error('错误:服务器内部错误,请联系管理员!'); | |
| break; | |
| case 405: | |
| Toast.info('本站仅作演示之用,无服务端!'); | |
| break; | |
| default: | |
| Toast.error('错误:' + error.message); | |
| } | |
| return; | |
| } | |
| Toast.error('错误:' + error.message); | |
| } else { | |
| Toast.error('错误:' + error); | |
| } | |
| } | |
| export function showWarning(message) { | |
| Toast.warning(message); | |
| } | |
| export function showSuccess(message) { | |
| Toast.success(message); | |
| } | |
| export function showInfo(message) { | |
| Toast.info(message); | |
| } | |
| export function showNotice(message, isHTML = false) { | |
| if (isHTML) { | |
| toast(<HTMLToastContent htmlContent={message} />, showNoticeOptions); | |
| } else { | |
| Toast.info(message); | |
| } | |
| } | |
| export function openPage(url) { | |
| window.open(url); | |
| } | |
| export function removeTrailingSlash(url) { | |
| if (!url) return ''; | |
| if (url.endsWith('/')) { | |
| return url.slice(0, -1); | |
| } else { | |
| return url; | |
| } | |
| } | |
| export function getTodayStartTimestamp() { | |
| var now = new Date(); | |
| now.setHours(0, 0, 0, 0); | |
| return Math.floor(now.getTime() / 1000); | |
| } | |
| export function timestamp2string(timestamp) { | |
| let date = new Date(timestamp * 1000); | |
| let year = date.getFullYear().toString(); | |
| let month = (date.getMonth() + 1).toString(); | |
| let day = date.getDate().toString(); | |
| let hour = date.getHours().toString(); | |
| let minute = date.getMinutes().toString(); | |
| let second = date.getSeconds().toString(); | |
| if (month.length === 1) { | |
| month = '0' + month; | |
| } | |
| if (day.length === 1) { | |
| day = '0' + day; | |
| } | |
| if (hour.length === 1) { | |
| hour = '0' + hour; | |
| } | |
| if (minute.length === 1) { | |
| minute = '0' + minute; | |
| } | |
| if (second.length === 1) { | |
| second = '0' + second; | |
| } | |
| return ( | |
| year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second | |
| ); | |
| } | |
| export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') { | |
| let date = new Date(timestamp * 1000); | |
| // let year = date.getFullYear().toString(); | |
| let month = (date.getMonth() + 1).toString(); | |
| let day = date.getDate().toString(); | |
| let hour = date.getHours().toString(); | |
| if (day === '24') { | |
| console.log('timestamp', timestamp); | |
| } | |
| if (month.length === 1) { | |
| month = '0' + month; | |
| } | |
| if (day.length === 1) { | |
| day = '0' + day; | |
| } | |
| if (hour.length === 1) { | |
| hour = '0' + hour; | |
| } | |
| let str = month + '-' + day; | |
| if (dataExportDefaultTime === 'hour') { | |
| str += ' ' + hour + ':00'; | |
| } else if (dataExportDefaultTime === 'week') { | |
| let nextWeek = new Date(timestamp * 1000 + 6 * 24 * 60 * 60 * 1000); | |
| let nextMonth = (nextWeek.getMonth() + 1).toString(); | |
| let nextDay = nextWeek.getDate().toString(); | |
| if (nextMonth.length === 1) { | |
| nextMonth = '0' + nextMonth; | |
| } | |
| if (nextDay.length === 1) { | |
| nextDay = '0' + nextDay; | |
| } | |
| str += ' - ' + nextMonth + '-' + nextDay; | |
| } | |
| return str; | |
| } | |
| export function downloadTextAsFile(text, filename) { | |
| let blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); | |
| let url = URL.createObjectURL(blob); | |
| let a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename; | |
| a.click(); | |
| } | |
| export const verifyJSON = (str) => { | |
| try { | |
| JSON.parse(str); | |
| } catch (e) { | |
| return false; | |
| } | |
| return true; | |
| }; | |
| export function verifyJSONPromise(value) { | |
| try { | |
| JSON.parse(value); | |
| return Promise.resolve(); | |
| } catch (e) { | |
| return Promise.reject('不是合法的 JSON 字符串'); | |
| } | |
| } | |
| export function shouldShowPrompt(id) { | |
| let prompt = localStorage.getItem(`prompt-${id}`); | |
| return !prompt; | |
| } | |
| export function setPromptShown(id) { | |
| localStorage.setItem(`prompt-${id}`, 'true'); | |
| } | |
| /** | |
| * 比较两个对象的属性,找出有变化的属性,并返回包含变化属性信息的数组 | |
| * @param {Object} oldObject - 旧对象 | |
| * @param {Object} newObject - 新对象 | |
| * @return {Array} 包含变化属性信息的数组,每个元素是一个对象,包含 key, oldValue 和 newValue | |
| */ | |
| export function compareObjects(oldObject, newObject) { | |
| const changedProperties = []; | |
| // 比较两个对象的属性 | |
| for (const key in oldObject) { | |
| if (oldObject.hasOwnProperty(key) && newObject.hasOwnProperty(key)) { | |
| if (oldObject[key] !== newObject[key]) { | |
| changedProperties.push({ | |
| key: key, | |
| oldValue: oldObject[key], | |
| newValue: newObject[key], | |
| }); | |
| } | |
| } | |
| } | |
| return changedProperties; | |
| } | |
| // playground message | |
| // 生成唯一ID | |
| let messageId = 4; | |
| export const generateMessageId = () => `${messageId++}`; | |
| // 提取消息中的文本内容 | |
| export const getTextContent = (message) => { | |
| if (!message || !message.content) return ''; | |
| if (Array.isArray(message.content)) { | |
| const textContent = message.content.find((item) => item.type === 'text'); | |
| return textContent?.text || ''; | |
| } | |
| return typeof message.content === 'string' ? message.content : ''; | |
| }; | |
| // 处理 think 标签 | |
| export const processThinkTags = (content, reasoningContent = '') => { | |
| if (!content || !content.includes('<think>')) { | |
| return { content, reasoningContent }; | |
| } | |
| const thoughts = []; | |
| const replyParts = []; | |
| let lastIndex = 0; | |
| let match; | |
| THINK_TAG_REGEX.lastIndex = 0; | |
| while ((match = THINK_TAG_REGEX.exec(content)) !== null) { | |
| replyParts.push(content.substring(lastIndex, match.index)); | |
| thoughts.push(match[1]); | |
| lastIndex = match.index + match[0].length; | |
| } | |
| replyParts.push(content.substring(lastIndex)); | |
| const processedContent = replyParts | |
| .join('') | |
| .replace(/<\/?think>/g, '') | |
| .trim(); | |
| const thoughtsStr = thoughts.join('\n\n---\n\n'); | |
| const processedReasoningContent = | |
| reasoningContent && thoughtsStr | |
| ? `${reasoningContent}\n\n---\n\n${thoughtsStr}` | |
| : reasoningContent || thoughtsStr; | |
| return { | |
| content: processedContent, | |
| reasoningContent: processedReasoningContent, | |
| }; | |
| }; | |
| // 处理未完成的 think 标签 | |
| export const processIncompleteThinkTags = (content, reasoningContent = '') => { | |
| if (!content) return { content: '', reasoningContent }; | |
| const lastOpenThinkIndex = content.lastIndexOf('<think>'); | |
| if (lastOpenThinkIndex === -1) { | |
| return processThinkTags(content, reasoningContent); | |
| } | |
| const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex); | |
| if (!fragmentAfterLastOpen.includes('</think>')) { | |
| const unclosedThought = fragmentAfterLastOpen | |
| .substring('<think>'.length) | |
| .trim(); | |
| const cleanContent = content.substring(0, lastOpenThinkIndex); | |
| const processedReasoningContent = unclosedThought | |
| ? reasoningContent | |
| ? `${reasoningContent}\n\n---\n\n${unclosedThought}` | |
| : unclosedThought | |
| : reasoningContent; | |
| return processThinkTags(cleanContent, processedReasoningContent); | |
| } | |
| return processThinkTags(content, reasoningContent); | |
| }; | |
| // 构建消息内容(包含图片) | |
| export const buildMessageContent = ( | |
| textContent, | |
| imageUrls = [], | |
| imageEnabled = false, | |
| ) => { | |
| if (!textContent && (!imageUrls || imageUrls.length === 0)) { | |
| return ''; | |
| } | |
| const validImageUrls = imageUrls.filter((url) => url && url.trim() !== ''); | |
| if (imageEnabled && validImageUrls.length > 0) { | |
| return [ | |
| { type: 'text', text: textContent || '' }, | |
| ...validImageUrls.map((url) => ({ | |
| type: 'image_url', | |
| image_url: { url: url.trim() }, | |
| })), | |
| ]; | |
| } | |
| return textContent || ''; | |
| }; | |
| // 创建新消息 | |
| export const createMessage = (role, content, options = {}) => ({ | |
| role, | |
| content, | |
| createAt: Date.now(), | |
| id: generateMessageId(), | |
| ...options, | |
| }); | |
| // 创建加载中的助手消息 | |
| export const createLoadingAssistantMessage = () => | |
| createMessage(MESSAGE_ROLES.ASSISTANT, '', { | |
| reasoningContent: '', | |
| isReasoningExpanded: true, | |
| isThinkingComplete: false, | |
| hasAutoCollapsed: false, | |
| status: 'loading', | |
| }); | |
| // 检查消息是否包含图片 | |
| export const hasImageContent = (message) => { | |
| return ( | |
| message && | |
| Array.isArray(message.content) && | |
| message.content.some((item) => item.type === 'image_url') | |
| ); | |
| }; | |
| // 格式化消息用于API请求 | |
| export const formatMessageForAPI = (message) => { | |
| if (!message) return null; | |
| return { | |
| role: message.role, | |
| content: message.content, | |
| }; | |
| }; | |
| // 验证消息是否有效 | |
| export const isValidMessage = (message) => { | |
| return message && message.role && (message.content || message.content === ''); | |
| }; | |
| // 获取最后一条用户消息 | |
| export const getLastUserMessage = (messages) => { | |
| if (!Array.isArray(messages)) return null; | |
| for (let i = messages.length - 1; i >= 0; i--) { | |
| if (messages[i].role === MESSAGE_ROLES.USER) { | |
| return messages[i]; | |
| } | |
| } | |
| return null; | |
| }; | |
| // 获取最后一条助手消息 | |
| export const getLastAssistantMessage = (messages) => { | |
| if (!Array.isArray(messages)) return null; | |
| for (let i = messages.length - 1; i >= 0; i--) { | |
| if (messages[i].role === MESSAGE_ROLES.ASSISTANT) { | |
| return messages[i]; | |
| } | |
| } | |
| return null; | |
| }; | |
| // 计算相对时间(几天前、几小时前等) | |
| export const getRelativeTime = (publishDate) => { | |
| if (!publishDate) return ''; | |
| const now = new Date(); | |
| const pubDate = new Date(publishDate); | |
| // 如果日期无效,返回原始字符串 | |
| if (isNaN(pubDate.getTime())) return publishDate; | |
| const diffMs = now.getTime() - pubDate.getTime(); | |
| const diffSeconds = Math.floor(diffMs / 1000); | |
| const diffMinutes = Math.floor(diffSeconds / 60); | |
| const diffHours = Math.floor(diffMinutes / 60); | |
| const diffDays = Math.floor(diffHours / 24); | |
| const diffWeeks = Math.floor(diffDays / 7); | |
| const diffMonths = Math.floor(diffDays / 30); | |
| const diffYears = Math.floor(diffDays / 365); | |
| // 如果是未来时间,显示具体日期 | |
| if (diffMs < 0) { | |
| return formatDateString(pubDate); | |
| } | |
| // 根据时间差返回相应的描述 | |
| if (diffSeconds < 60) { | |
| return '刚刚'; | |
| } else if (diffMinutes < 60) { | |
| return `${diffMinutes} 分钟前`; | |
| } else if (diffHours < 24) { | |
| return `${diffHours} 小时前`; | |
| } else if (diffDays < 7) { | |
| return `${diffDays} 天前`; | |
| } else if (diffWeeks < 4) { | |
| return `${diffWeeks} 周前`; | |
| } else if (diffMonths < 12) { | |
| return `${diffMonths} 个月前`; | |
| } else if (diffYears < 2) { | |
| return '1 年前'; | |
| } else { | |
| // 超过2年显示具体日期 | |
| return formatDateString(pubDate); | |
| } | |
| }; | |
| // 格式化日期字符串 | |
| export const formatDateString = (date) => { | |
| const year = date.getFullYear(); | |
| const month = String(date.getMonth() + 1).padStart(2, '0'); | |
| const day = String(date.getDate()).padStart(2, '0'); | |
| return `${year}-${month}-${day}`; | |
| }; | |
| // 格式化日期时间字符串(包含时间) | |
| export const formatDateTimeString = (date) => { | |
| const year = date.getFullYear(); | |
| const month = String(date.getMonth() + 1).padStart(2, '0'); | |
| const day = String(date.getDate()).padStart(2, '0'); | |
| const hours = String(date.getHours()).padStart(2, '0'); | |
| const minutes = String(date.getMinutes()).padStart(2, '0'); | |
| return `${year}-${month}-${day} ${hours}:${minutes}`; | |
| }; | |
| function readTableCompactModes() { | |
| try { | |
| const json = localStorage.getItem(TABLE_COMPACT_MODES_KEY); | |
| return json ? JSON.parse(json) : {}; | |
| } catch { | |
| return {}; | |
| } | |
| } | |
| function writeTableCompactModes(modes) { | |
| try { | |
| localStorage.setItem(TABLE_COMPACT_MODES_KEY, JSON.stringify(modes)); | |
| } catch { | |
| // ignore | |
| } | |
| } | |
| export function getTableCompactMode(tableKey = 'global') { | |
| const modes = readTableCompactModes(); | |
| return !!modes[tableKey]; | |
| } | |
| export function setTableCompactMode(compact, tableKey = 'global') { | |
| const modes = readTableCompactModes(); | |
| modes[tableKey] = compact; | |
| writeTableCompactModes(modes); | |
| } | |
| // ------------------------------- | |
| // Select 组件统一过滤逻辑 | |
| // 使用方式: <Select filter={selectFilter} ... /> | |
| // 统一的 Select 搜索过滤逻辑 -- 支持同时匹配 option.value 与 option.label | |
| export const selectFilter = (input, option) => { | |
| if (!input) return true; | |
| const keyword = input.trim().toLowerCase(); | |
| const valueText = (option?.value ?? '').toString().toLowerCase(); | |
| const labelText = (option?.label ?? '').toString().toLowerCase(); | |
| return valueText.includes(keyword) || labelText.includes(keyword); | |
| }; | |
| // ------------------------------- | |
| // 模型定价计算工具函数 | |
| export const calculateModelPrice = ({ | |
| record, | |
| selectedGroup, | |
| groupRatio, | |
| tokenUnit, | |
| displayPrice, | |
| currency, | |
| precision = 4, | |
| }) => { | |
| // 1. 选择实际使用的分组 | |
| let usedGroup = selectedGroup; | |
| let usedGroupRatio = groupRatio[selectedGroup]; | |
| if (selectedGroup === 'all' || usedGroupRatio === undefined) { | |
| // 在模型可用分组中选择倍率最小的分组,若无则使用 1 | |
| let minRatio = Number.POSITIVE_INFINITY; | |
| if ( | |
| Array.isArray(record.enable_groups) && | |
| record.enable_groups.length > 0 | |
| ) { | |
| record.enable_groups.forEach((g) => { | |
| const r = groupRatio[g]; | |
| if (r !== undefined && r < minRatio) { | |
| minRatio = r; | |
| usedGroup = g; | |
| usedGroupRatio = r; | |
| } | |
| }); | |
| } | |
| // 如果找不到合适分组倍率,回退为 1 | |
| if (usedGroupRatio === undefined) { | |
| usedGroupRatio = 1; | |
| } | |
| } | |
| // 2. 根据计费类型计算价格 | |
| if (record.quota_type === 0) { | |
| // 按量计费 | |
| const inputRatioPriceUSD = record.model_ratio * 2 * usedGroupRatio; | |
| const completionRatioPriceUSD = | |
| record.model_ratio * record.completion_ratio * 2 * usedGroupRatio; | |
| const unitDivisor = tokenUnit === 'K' ? 1000 : 1; | |
| const unitLabel = tokenUnit === 'K' ? 'K' : 'M'; | |
| const rawDisplayInput = displayPrice(inputRatioPriceUSD); | |
| const rawDisplayCompletion = displayPrice(completionRatioPriceUSD); | |
| const numInput = | |
| parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor; | |
| const numCompletion = | |
| parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor; | |
| return { | |
| inputPrice: `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(precision)}`, | |
| completionPrice: `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(precision)}`, | |
| unitLabel, | |
| isPerToken: true, | |
| usedGroup, | |
| usedGroupRatio, | |
| }; | |
| } | |
| if (record.quota_type === 1) { | |
| // 按次计费 | |
| const priceUSD = parseFloat(record.model_price) * usedGroupRatio; | |
| const displayVal = displayPrice(priceUSD); | |
| return { | |
| price: displayVal, | |
| isPerToken: false, | |
| usedGroup, | |
| usedGroupRatio, | |
| }; | |
| } | |
| // 未知计费类型,返回占位信息 | |
| return { | |
| price: '-', | |
| isPerToken: false, | |
| usedGroup, | |
| usedGroupRatio, | |
| }; | |
| }; | |
| // 格式化价格信息(用于卡片视图) | |
| export const formatPriceInfo = (priceData, t) => { | |
| if (priceData.isPerToken) { | |
| return ( | |
| <> | |
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |
| {t('输入')} {priceData.inputPrice}/{priceData.unitLabel} | |
| </span> | |
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |
| {t('输出')} {priceData.completionPrice}/{priceData.unitLabel} | |
| </span> | |
| </> | |
| ); | |
| } | |
| return ( | |
| <> | |
| <span style={{ color: 'var(--semi-color-text-1)' }}> | |
| {t('模型价格')} {priceData.price} | |
| </span> | |
| </> | |
| ); | |
| }; | |
| // ------------------------------- | |
| // CardPro 分页配置函数 | |
| // 用于创建 CardPro 的 paginationArea 配置 | |
| export const createCardProPagination = ({ | |
| currentPage, | |
| pageSize, | |
| total, | |
| onPageChange, | |
| onPageSizeChange, | |
| isMobile = false, | |
| pageSizeOpts = [10, 20, 50, 100], | |
| showSizeChanger = true, | |
| t = (key) => key, | |
| }) => { | |
| if (!total || total <= 0) return null; | |
| const start = (currentPage - 1) * pageSize + 1; | |
| const end = Math.min(currentPage * pageSize, total); | |
| const totalText = `${t('显示第')} ${start} ${t('条 - 第')} ${end} ${t('条,共')} ${total} ${t('条')}`; | |
| return ( | |
| <> | |
| {/* 桌面端左侧总数信息 */} | |
| {!isMobile && ( | |
| <span | |
| className='text-sm select-none' | |
| style={{ color: 'var(--semi-color-text-2)' }} | |
| > | |
| {totalText} | |
| </span> | |
| )} | |
| {/* 右侧分页控件 */} | |
| <Pagination | |
| currentPage={currentPage} | |
| pageSize={pageSize} | |
| total={total} | |
| pageSizeOpts={pageSizeOpts} | |
| showSizeChanger={showSizeChanger} | |
| onPageSizeChange={onPageSizeChange} | |
| onPageChange={onPageChange} | |
| size={isMobile ? 'small' : 'default'} | |
| showQuickJumper={isMobile} | |
| showTotal | |
| /> | |
| </> | |
| ); | |
| }; | |
| // 模型定价筛选条件默认值 | |
| const DEFAULT_PRICING_FILTERS = { | |
| search: '', | |
| showWithRecharge: false, | |
| currency: 'USD', | |
| showRatio: false, | |
| viewMode: 'card', | |
| tokenUnit: 'M', | |
| filterGroup: 'all', | |
| filterQuotaType: 'all', | |
| filterEndpointType: 'all', | |
| filterVendor: 'all', | |
| filterTag: 'all', | |
| currentPage: 1, | |
| }; | |
| // 重置模型定价筛选条件 | |
| export const resetPricingFilters = ({ | |
| handleChange, | |
| setShowWithRecharge, | |
| setCurrency, | |
| setShowRatio, | |
| setViewMode, | |
| setFilterGroup, | |
| setFilterQuotaType, | |
| setFilterEndpointType, | |
| setFilterVendor, | |
| setFilterTag, | |
| setCurrentPage, | |
| setTokenUnit, | |
| }) => { | |
| handleChange?.(DEFAULT_PRICING_FILTERS.search); | |
| setShowWithRecharge?.(DEFAULT_PRICING_FILTERS.showWithRecharge); | |
| setCurrency?.(DEFAULT_PRICING_FILTERS.currency); | |
| setShowRatio?.(DEFAULT_PRICING_FILTERS.showRatio); | |
| setViewMode?.(DEFAULT_PRICING_FILTERS.viewMode); | |
| setTokenUnit?.(DEFAULT_PRICING_FILTERS.tokenUnit); | |
| setFilterGroup?.(DEFAULT_PRICING_FILTERS.filterGroup); | |
| setFilterQuotaType?.(DEFAULT_PRICING_FILTERS.filterQuotaType); | |
| setFilterEndpointType?.(DEFAULT_PRICING_FILTERS.filterEndpointType); | |
| setFilterVendor?.(DEFAULT_PRICING_FILTERS.filterVendor); | |
| setFilterTag?.(DEFAULT_PRICING_FILTERS.filterTag); | |
| setCurrentPage?.(DEFAULT_PRICING_FILTERS.currentPage); | |
| }; | |