/*
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 { 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';
import i18n from '../i18n/i18n';
const HTMLToastContent = ({ htmlContent }) => {
return
;
};
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(i18n.t('错误:请求次数过多,请稍后再试!'));
break;
case 500:
Toast.error(i18n.t('错误:服务器内部错误,请联系管理员!'));
break;
case 405:
Toast.info(i18n.t('本站仅作演示之用,无服务端!'));
break;
default:
Toast.error(i18n.t('错误:') + error.message);
}
return;
}
Toast.error(i18n.t('错误:') + error.message);
} else {
Toast.error(i18n.t('错误:') + 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( , 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',
showYear = false,
) {
let date = new Date(timestamp * 1000);
let year = date.getFullYear();
let month = (date.getMonth() + 1).toString();
let day = date.getDate().toString();
let hour = date.getHours().toString();
if (month.length === 1) {
month = '0' + month;
}
if (day.length === 1) {
day = '0' + day;
}
if (hour.length === 1) {
hour = '0' + hour;
}
// 仅在跨年时显示年份
let str = showYear ? year + '-' + month + '-' + day : month + '-' + day;
if (dataExportDefaultTime === 'hour') {
str += ' ' + hour + ':00';
} else if (dataExportDefaultTime === 'week') {
let nextWeek = new Date(timestamp * 1000 + 6 * 24 * 60 * 60 * 1000);
let nextWeekYear = nextWeek.getFullYear();
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;
}
// 周视图结束日期也仅在跨年时显示年份
let nextStr = showYear
? nextWeekYear + '-' + nextMonth + '-' + nextDay
: nextMonth + '-' + nextDay;
str += ' - ' + nextStr;
}
return str;
}
// 检查时间戳数组是否跨年
export function isDataCrossYear(timestamps) {
if (!timestamps || timestamps.length === 0) return false;
const years = new Set(
timestamps.map((ts) => new Date(ts * 1000).getFullYear()),
);
return years.size > 1;
}
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(i18n.t('不是合法的 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('')) {
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('');
if (lastOpenThinkIndex === -1) {
return processThinkTags(content, reasoningContent);
}
const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex);
if (!fragmentAfterLastOpen.includes(' ')) {
const unclosedThought = fragmentAfterLastOpen
.substring(''.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 i18n.t('刚刚');
} else if (diffMinutes < 60) {
return `${diffMinutes} ${i18n.t('分钟前')}`;
} else if (diffHours < 24) {
return `${diffHours} ${i18n.t('小时前')}`;
} else if (diffDays < 7) {
return `${diffDays} ${i18n.t('天前')}`;
} else if (diffWeeks < 4) {
return `${diffWeeks} ${i18n.t('周前')}`;
} else if (diffMonths < 12) {
return `${diffMonths} ${i18n.t('个月前')}`;
} else if (diffYears < 2) {
return i18n.t('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 搜索过滤逻辑 -- 支持同时匹配 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 hasDisplayPricing = (record) =>
Array.isArray(record?.display_pricing) && record.display_pricing.length > 0;
export const normalizeDisplayPricingItems = (record) => {
if (!hasDisplayPricing(record)) return [];
return [...record.display_pricing]
.map((item) => {
const discountRate =
Number(item.discount_rate) > 0 && Number(item.discount_rate) <= 1
? Number(item.discount_rate)
: 1;
const price = Number(item.price || 0);
return {
...item,
price,
discount_rate: discountRate,
effectivePrice: price * discountRate,
unit: item.unit || 'call',
sort_order: Number(item.sort_order || 0),
};
})
.sort((a, b) => {
if (a.sort_order !== b.sort_order) return a.sort_order - b.sort_order;
return String(a.specification || '').localeCompare(
String(b.specification || ''),
);
});
};
export const formatDisplayPricingItem = ({
item,
displayPrice,
t = (key) => key,
}) => {
const unit = item.unit || 'call';
const unitText = formatDisplayPricingUnit(unit, t);
const effectivePrice = Number(item.effectivePrice ?? item.price ?? 0);
const hasDiscount = item.discount_rate > 0 && item.discount_rate < 1;
return {
...item,
formattedPrice: `${displayPrice(effectivePrice)} / ${unitText}`,
originalPrice: hasDiscount
? `${displayPrice(Number(item.price || 0))} / ${unitText}`
: '',
discountText: hasDiscount
? `${Math.round(item.discount_rate * 100) / 10}折`
: '-',
};
};
const DISPLAY_PRICING_UNIT_FALLBACKS = {
second: '秒',
call: '次',
image: '张图片',
video: '个视频',
};
export const formatDisplayPricingUnit = (unit, t = (key) => key) => {
const normalizedUnit = unit || 'call';
const translated = t(normalizedUnit);
if (translated && translated !== normalizedUnit) {
return translated;
}
return DISPLAY_PRICING_UNIT_FALLBACKS[normalizedUnit] || normalizedUnit;
};
export const summarizeDisplayPricing = ({
record,
displayPrice,
t = (key) => key,
}) => {
const items = normalizeDisplayPricingItems(record);
if (items.length === 0) return null;
const groups = new Map();
items.forEach((item) => {
const unit = item.unit || 'call';
const list = groups.get(unit) || [];
list.push(item);
groups.set(unit, list);
});
const lines = Array.from(groups.entries()).map(([unit, list]) => {
const prices = list.map((item) => Number(item.effectivePrice || 0));
const min = Math.min(...prices);
const max = Math.max(...prices);
const priceText =
min === max
? displayPrice(min)
: `${displayPrice(min)} - ${displayPrice(max)}`;
return `${priceText} / ${formatDisplayPricingUnit(unit, t)}`;
});
return {
isDisplayPricing: true,
price: lines.join('\n'),
unit: '',
details: items
.slice(0, 3)
.map((item) => formatDisplayPricingItem({ item, displayPrice, t })),
hiddenDetailCount: Math.max(0, items.length - 3),
};
};
export const hasMatrixPricing = (record) =>
!!(
record?.pricing_config &&
Array.isArray(record.pricing_config.table) &&
record.pricing_config.table.length > 0
);
export const getMatrixPricingUnit = (pricingConfig, tokenUnit = 'M') => {
if (pricingConfig?.billing_unit === 'per_1m_tokens') {
return `1${tokenUnit === 'K' ? 'K' : 'M'} tokens`;
}
return '次';
};
const getDisplayCurrencySymbol = (currency) => {
if (currency === 'CNY') return '¥';
if (currency === 'CUSTOM') {
try {
const statusStr = localStorage.getItem('status');
if (statusStr) {
const s = JSON.parse(statusStr);
return s?.custom_currency_symbol || '¤';
}
} catch {
// ignore
}
return '¤';
}
return '$';
};
export const formatMatrixPrice = ({
price,
pricingConfig,
tokenUnit,
displayPrice,
currency,
precision = 4,
}) => {
const unitDivisor =
pricingConfig?.billing_unit === 'per_1m_tokens' && tokenUnit === 'K'
? 1000
: 1;
const displayVal = displayPrice(Number(price || 0));
const numPrice = parseFloat(displayVal.replace(/[^0-9.]/g, '')) / unitDivisor;
return `${getDisplayCurrencySymbol(currency)}${numPrice.toFixed(precision)}`;
};
export const summarizeMatrixPricing = ({
record,
usedGroupRatio = 1,
tokenUnit,
displayPrice,
currency,
precision = 4,
}) => {
const cfg = record?.pricing_config;
const prices = (cfg?.table || [])
.map((row) => Number(row.price))
.filter((price) => Number.isFinite(price));
if (prices.length === 0) {
return { price: '-', unit: getMatrixPricingUnit(cfg, tokenUnit) };
}
const min = Math.min(...prices) * usedGroupRatio;
const max = Math.max(...prices) * usedGroupRatio;
const minText = formatMatrixPrice({
price: min,
pricingConfig: cfg,
tokenUnit,
displayPrice,
currency,
precision,
});
const maxText = formatMatrixPrice({
price: max,
pricingConfig: cfg,
tokenUnit,
displayPrice,
currency,
precision,
});
return {
price: min === max ? minText : `${minText} - ${maxText}`,
unit: getMatrixPricingUnit(cfg, tokenUnit),
details: (cfg.table || []).slice(0, 3).map((row) => {
const dimensionText = Object.entries(row)
.filter(([key]) => key !== 'price' && key !== 'source')
.map(
([key, value]) => `${key}=${value === '*' ? '任意' : String(value)}`,
)
.join(' · ');
return {
dimensionText,
formattedPrice: formatMatrixPrice({
price: Number(row.price || 0) * usedGroupRatio,
pricingConfig: cfg,
tokenUnit,
displayPrice,
currency,
precision,
}),
};
}),
hiddenDetailCount: Math.max(0, (cfg.table || []).length - 3),
};
};
export const calculateModelPrice = ({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
precision = 4,
t = (key) => key,
}) => {
// 1. 选择实际使用的分组
const ratios = groupRatio || {};
let usedGroup = selectedGroup;
let usedGroupRatio = ratios[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 = ratios[g];
if (r !== undefined && r < minRatio) {
minRatio = r;
usedGroup = g;
usedGroupRatio = r;
}
});
}
// 如果找不到合适分组倍率,回退为 1
if (usedGroupRatio === undefined) {
usedGroupRatio = 1;
}
}
// 2. 根据计费类型计算价格
if (hasDisplayPricing(record)) {
const summary = summarizeDisplayPricing({
record,
displayPrice,
t,
});
return {
...summary,
usedGroup,
usedGroupRatio,
};
}
if (hasMatrixPricing(record)) {
const summary = summarizeMatrixPricing({
record,
usedGroupRatio,
tokenUnit,
displayPrice,
currency,
precision,
});
return {
...summary,
isMatrix: true,
isPerToken: record.pricing_config?.billing_unit === 'per_1m_tokens',
usedGroup,
usedGroupRatio,
};
}
if (record.quota_type === 0) {
// 按量计费
const inputRatioPriceUSD = record.model_ratio * 2 * usedGroupRatio;
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * usedGroupRatio;
// 缓存读取价格 = 输入价格 × 缓存倍率(默认 1 表示与输入价格相同)
const cacheRatio = record.cache_ratio ?? 1;
const cacheRatioPriceUSD = inputRatioPriceUSD * cacheRatio;
// 缓存创建价格 = 输入价格 × 缓存创建倍率(默认 1.25)
const cacheCreationRatio = record.cache_creation_ratio ?? 1.25;
const cacheCreationRatioPriceUSD = inputRatioPriceUSD * cacheCreationRatio;
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
const rawDisplayCache = displayPrice(cacheRatioPriceUSD);
const rawDisplayCacheCreation = displayPrice(cacheCreationRatioPriceUSD);
const numInput =
parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion =
parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCache =
parseFloat(rawDisplayCache.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCacheCreation =
parseFloat(rawDisplayCacheCreation.replace(/[^0-9.]/g, '')) / unitDivisor;
let symbol = '$';
if (currency === 'CNY') {
symbol = '¥';
} else if (currency === 'CUSTOM') {
try {
const statusStr = localStorage.getItem('status');
if (statusStr) {
const s = JSON.parse(statusStr);
symbol = s?.custom_currency_symbol || '¤';
} else {
symbol = '¤';
}
} catch (e) {
symbol = '¤';
}
}
return {
inputPrice: `${symbol}${numInput.toFixed(precision)}`,
completionPrice: `${symbol}${numCompletion.toFixed(precision)}`,
cachePrice: `${symbol}${numCache.toFixed(precision)}`,
cacheCreationPrice: `${symbol}${numCacheCreation.toFixed(precision)}`,
cacheRatio,
cacheCreationRatio,
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 (
{t('输入')} {priceData.inputPrice} / {t('输出')}{' '}
{priceData.completionPrice} / {priceData.unitLabel} tokens
);
}
return (
{t('模型价格')} {priceData.price}
);
};
// -------------------------------
// 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 && (
{totalText}
)}
{/* 右侧分页控件 */}
>
);
};
// 模型定价筛选条件默认值
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);
};