🎨 chore(web): apply ESLint and Prettier auto-fixes (baseline)
- Ran: bun run eslint:fix && bun run lint:fix - Inserted AGPL license header via eslint-plugin-header - Enforced no-multiple-empty-lines and other lint rules - Formatted code using Prettier v3 (@so1ve/prettier-config) - No functional changes; formatting-only baseline across JS/JSX files
This commit is contained in:
@@ -17,7 +17,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import { getUserIdFromLocalStorage, showError, formatMessageForAPI, isValidMessage } from './utils';
|
||||
import {
|
||||
getUserIdFromLocalStorage,
|
||||
showError,
|
||||
formatMessageForAPI,
|
||||
isValidMessage,
|
||||
} from './utils';
|
||||
import axios from 'axios';
|
||||
import { MESSAGE_ROLES } from '../constants/playground.constants';
|
||||
|
||||
@@ -90,7 +95,12 @@ API.interceptors.response.use(
|
||||
// playground
|
||||
|
||||
// 构建API请求负载
|
||||
export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled) => {
|
||||
export const buildApiPayload = (
|
||||
messages,
|
||||
systemPrompt,
|
||||
inputs,
|
||||
parameterEnabled,
|
||||
) => {
|
||||
const processedMessages = messages
|
||||
.filter(isValidMessage)
|
||||
.map(formatMessageForAPI)
|
||||
@@ -100,7 +110,7 @@ export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled
|
||||
if (systemPrompt && systemPrompt.trim()) {
|
||||
processedMessages.unshift({
|
||||
role: MESSAGE_ROLES.SYSTEM,
|
||||
content: systemPrompt.trim()
|
||||
content: systemPrompt.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,11 +129,15 @@ export const buildApiPayload = (messages, systemPrompt, inputs, parameterEnabled
|
||||
max_tokens: 'max_tokens',
|
||||
frequency_penalty: 'frequency_penalty',
|
||||
presence_penalty: 'presence_penalty',
|
||||
seed: 'seed'
|
||||
seed: 'seed',
|
||||
};
|
||||
|
||||
Object.entries(parameterMappings).forEach(([key, param]) => {
|
||||
if (parameterEnabled[key] && inputs[param] !== undefined && inputs[param] !== null) {
|
||||
if (
|
||||
parameterEnabled[key] &&
|
||||
inputs[param] !== undefined &&
|
||||
inputs[param] !== null
|
||||
) {
|
||||
payload[param] = inputs[param];
|
||||
}
|
||||
});
|
||||
@@ -136,7 +150,7 @@ export const handleApiError = (error, response = null) => {
|
||||
const errorInfo = {
|
||||
error: error.message || '未知错误',
|
||||
timestamp: new Date().toISOString(),
|
||||
stack: error.stack
|
||||
stack: error.stack,
|
||||
};
|
||||
|
||||
if (response) {
|
||||
@@ -155,15 +169,18 @@ export const handleApiError = (error, response = null) => {
|
||||
|
||||
// 处理模型数据
|
||||
export const processModelsData = (data, currentModel) => {
|
||||
const modelOptions = data.map(model => ({
|
||||
const modelOptions = data.map((model) => ({
|
||||
label: model,
|
||||
value: model,
|
||||
}));
|
||||
|
||||
const hasCurrentModel = modelOptions.some(option => option.value === currentModel);
|
||||
const selectedModel = hasCurrentModel && modelOptions.length > 0
|
||||
? currentModel
|
||||
: modelOptions[0]?.value;
|
||||
const hasCurrentModel = modelOptions.some(
|
||||
(option) => option.value === currentModel,
|
||||
);
|
||||
const selectedModel =
|
||||
hasCurrentModel && modelOptions.length > 0
|
||||
? currentModel
|
||||
: modelOptions[0]?.value;
|
||||
|
||||
return { modelOptions, selectedModel };
|
||||
};
|
||||
@@ -171,20 +188,23 @@ export const processModelsData = (data, currentModel) => {
|
||||
// 处理分组数据
|
||||
export const processGroupsData = (data, userGroup) => {
|
||||
let groupOptions = Object.entries(data).map(([group, info]) => ({
|
||||
label: info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc,
|
||||
label:
|
||||
info.desc.length > 20 ? info.desc.substring(0, 20) + '...' : info.desc,
|
||||
value: group,
|
||||
ratio: info.ratio,
|
||||
fullLabel: info.desc,
|
||||
}));
|
||||
|
||||
if (groupOptions.length === 0) {
|
||||
groupOptions = [{
|
||||
label: '用户分组',
|
||||
value: '',
|
||||
ratio: 1,
|
||||
}];
|
||||
groupOptions = [
|
||||
{
|
||||
label: '用户分组',
|
||||
value: '',
|
||||
ratio: 1,
|
||||
},
|
||||
];
|
||||
} else if (userGroup) {
|
||||
const userGroupIndex = groupOptions.findIndex(g => g.value === userGroup);
|
||||
const userGroupIndex = groupOptions.findIndex((g) => g.value === userGroup);
|
||||
if (userGroupIndex > -1) {
|
||||
const userGroupOption = groupOptions.splice(userGroupIndex, 1)[0];
|
||||
groupOptions.unshift(userGroupOption);
|
||||
|
||||
@@ -36,7 +36,7 @@ export const AuthRedirect = ({ children }) => {
|
||||
const user = localStorage.getItem('user');
|
||||
|
||||
if (user) {
|
||||
return <Navigate to="/console" replace />;
|
||||
return <Navigate to='/console' replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
|
||||
@@ -26,4 +26,4 @@ export const toBoolean = (value) => {
|
||||
return v === 'true' || v === '1';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -19,9 +19,22 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
|
||||
import React from 'react';
|
||||
import { Progress, Divider, Empty } from '@douyinfe/semi-ui';
|
||||
import { IllustrationConstruction, IllustrationConstructionDark } from '@douyinfe/semi-illustrations';
|
||||
import { timestamp2string, timestamp2string1, copy, showSuccess } from './utils';
|
||||
import { STORAGE_KEYS, DEFAULT_TIME_INTERVALS, DEFAULTS, ILLUSTRATION_SIZE } from '../constants/dashboard.constants';
|
||||
import {
|
||||
IllustrationConstruction,
|
||||
IllustrationConstructionDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import {
|
||||
timestamp2string,
|
||||
timestamp2string1,
|
||||
copy,
|
||||
showSuccess,
|
||||
} from './utils';
|
||||
import {
|
||||
STORAGE_KEYS,
|
||||
DEFAULT_TIME_INTERVALS,
|
||||
DEFAULTS,
|
||||
ILLUSTRATION_SIZE,
|
||||
} from '../constants/dashboard.constants';
|
||||
|
||||
// ========== 时间相关工具函数 ==========
|
||||
export const getDefaultTime = () => {
|
||||
@@ -29,7 +42,8 @@ export const getDefaultTime = () => {
|
||||
};
|
||||
|
||||
export const getTimeInterval = (timeType, isSeconds = false) => {
|
||||
const intervals = DEFAULT_TIME_INTERVALS[timeType] || DEFAULT_TIME_INTERVALS.hour;
|
||||
const intervals =
|
||||
DEFAULT_TIME_INTERVALS[timeType] || DEFAULT_TIME_INTERVALS.hour;
|
||||
return isSeconds ? intervals.seconds : intervals.minutes;
|
||||
};
|
||||
|
||||
@@ -56,7 +70,7 @@ export const updateMapValue = (map, key, value) => {
|
||||
};
|
||||
|
||||
export const initializeMaps = (key, ...maps) => {
|
||||
maps.forEach(map => {
|
||||
maps.forEach((map) => {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, 0);
|
||||
}
|
||||
@@ -64,8 +78,14 @@ export const initializeMaps = (key, ...maps) => {
|
||||
};
|
||||
|
||||
// ========== 图表相关工具函数 ==========
|
||||
export const updateChartSpec = (setterFunc, newData, subtitle, newColors, dataId) => {
|
||||
setterFunc(prev => ({
|
||||
export const updateChartSpec = (
|
||||
setterFunc,
|
||||
newData,
|
||||
subtitle,
|
||||
newColors,
|
||||
dataId,
|
||||
) => {
|
||||
setterFunc((prev) => ({
|
||||
...prev,
|
||||
data: [{ id: dataId, values: newData }],
|
||||
title: {
|
||||
@@ -88,12 +108,12 @@ export const getTrendSpec = (data, color) => ({
|
||||
axes: [
|
||||
{
|
||||
orient: 'bottom',
|
||||
visible: false
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
orient: 'left',
|
||||
visible: false
|
||||
}
|
||||
visible: false,
|
||||
},
|
||||
],
|
||||
padding: 0,
|
||||
autoFit: false,
|
||||
@@ -103,20 +123,20 @@ export const getTrendSpec = (data, color) => ({
|
||||
line: {
|
||||
style: {
|
||||
stroke: color,
|
||||
lineWidth: 2
|
||||
}
|
||||
lineWidth: 2,
|
||||
},
|
||||
},
|
||||
point: {
|
||||
visible: false
|
||||
visible: false,
|
||||
},
|
||||
background: {
|
||||
fill: 'transparent'
|
||||
}
|
||||
fill: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
// ========== UI 工具函数 ==========
|
||||
export const createSectionTitle = (Icon, text) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className='flex items-center gap-2'>
|
||||
<Icon size={16} />
|
||||
{text}
|
||||
</div>
|
||||
@@ -147,13 +167,20 @@ export const getUptimeStatusText = (status, uptimeStatusMap, t) =>
|
||||
uptimeStatusMap[status]?.text || t('未知');
|
||||
|
||||
// ========== 监控列表渲染函数 ==========
|
||||
export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatusText, t) => {
|
||||
export const renderMonitorList = (
|
||||
monitors,
|
||||
getUptimeStatusColor,
|
||||
getUptimeStatusText,
|
||||
t,
|
||||
) => {
|
||||
if (!monitors || monitors.length === 0) {
|
||||
return (
|
||||
<div className="flex justify-center items-center py-4">
|
||||
<div className='flex justify-center items-center py-4'>
|
||||
<Empty
|
||||
image={<IllustrationConstruction style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />}
|
||||
darkModeImage={
|
||||
<IllustrationConstructionDark style={ILLUSTRATION_SIZE} />
|
||||
}
|
||||
title={t('暂无监控数据')}
|
||||
/>
|
||||
</div>
|
||||
@@ -168,20 +195,26 @@ export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatu
|
||||
});
|
||||
|
||||
const renderItem = (monitor, idx) => (
|
||||
<div key={idx} className="p-2 hover:bg-white rounded-lg transition-colors">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div key={idx} className='p-2 hover:bg-white rounded-lg transition-colors'>
|
||||
<div className='flex items-center justify-between mb-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div
|
||||
className="w-2 h-2 rounded-full flex-shrink-0"
|
||||
className='w-2 h-2 rounded-full flex-shrink-0'
|
||||
style={{ backgroundColor: getUptimeStatusColor(monitor.status) }}
|
||||
/>
|
||||
<span className="text-sm font-medium text-gray-900">{monitor.name}</span>
|
||||
<span className='text-sm font-medium text-gray-900'>
|
||||
{monitor.name}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">{((monitor.uptime || 0) * 100).toFixed(2)}%</span>
|
||||
<span className='text-xs text-gray-500'>
|
||||
{((monitor.uptime || 0) * 100).toFixed(2)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-500">{getUptimeStatusText(monitor.status)}</span>
|
||||
<div className="flex-1">
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-xs text-gray-500'>
|
||||
{getUptimeStatusText(monitor.status)}
|
||||
</span>
|
||||
<div className='flex-1'>
|
||||
<Progress
|
||||
percent={(monitor.uptime || 0) * 100}
|
||||
showInfo={false}
|
||||
@@ -194,10 +227,10 @@ export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatu
|
||||
);
|
||||
|
||||
return Object.entries(grouped).map(([gname, list]) => (
|
||||
<div key={gname || 'default'} className="mb-2">
|
||||
<div key={gname || 'default'} className='mb-2'>
|
||||
{gname && (
|
||||
<>
|
||||
<div className="text-md font-semibold text-gray-500 px-2 py-1">
|
||||
<div className='text-md font-semibold text-gray-500 px-2 py-1'>
|
||||
{gname}
|
||||
</div>
|
||||
<Divider />
|
||||
@@ -209,7 +242,12 @@ export const renderMonitorList = (monitors, getUptimeStatusColor, getUptimeStatu
|
||||
};
|
||||
|
||||
// ========== 数据处理函数 ==========
|
||||
export const processRawData = (data, dataExportDefaultTime, initializeMaps, updateMapValue) => {
|
||||
export const processRawData = (
|
||||
data,
|
||||
dataExportDefaultTime,
|
||||
initializeMaps,
|
||||
updateMapValue,
|
||||
) => {
|
||||
const result = {
|
||||
totalQuota: 0,
|
||||
totalTimes: 0,
|
||||
@@ -218,7 +256,7 @@ export const processRawData = (data, dataExportDefaultTime, initializeMaps, upda
|
||||
timePoints: [],
|
||||
timeQuotaMap: new Map(),
|
||||
timeTokensMap: new Map(),
|
||||
timeCountMap: new Map()
|
||||
timeCountMap: new Map(),
|
||||
};
|
||||
|
||||
data.forEach((item) => {
|
||||
@@ -232,7 +270,12 @@ export const processRawData = (data, dataExportDefaultTime, initializeMaps, upda
|
||||
result.timePoints.push(timeKey);
|
||||
}
|
||||
|
||||
initializeMaps(timeKey, result.timeQuotaMap, result.timeTokensMap, result.timeCountMap);
|
||||
initializeMaps(
|
||||
timeKey,
|
||||
result.timeQuotaMap,
|
||||
result.timeTokensMap,
|
||||
result.timeCountMap,
|
||||
);
|
||||
updateMapValue(result.timeQuotaMap, timeKey, item.quota);
|
||||
updateMapValue(result.timeTokensMap, timeKey, item.token_used);
|
||||
updateMapValue(result.timeCountMap, timeKey, item.count);
|
||||
@@ -242,10 +285,16 @@ export const processRawData = (data, dataExportDefaultTime, initializeMaps, upda
|
||||
return result;
|
||||
};
|
||||
|
||||
export const calculateTrendData = (timePoints, timeQuotaMap, timeTokensMap, timeCountMap, dataExportDefaultTime) => {
|
||||
const quotaTrend = timePoints.map(time => timeQuotaMap.get(time) || 0);
|
||||
const tokensTrend = timePoints.map(time => timeTokensMap.get(time) || 0);
|
||||
const countTrend = timePoints.map(time => timeCountMap.get(time) || 0);
|
||||
export const calculateTrendData = (
|
||||
timePoints,
|
||||
timeQuotaMap,
|
||||
timeTokensMap,
|
||||
timeCountMap,
|
||||
dataExportDefaultTime,
|
||||
) => {
|
||||
const quotaTrend = timePoints.map((time) => timeQuotaMap.get(time) || 0);
|
||||
const tokensTrend = timePoints.map((time) => timeTokensMap.get(time) || 0);
|
||||
const countTrend = timePoints.map((time) => timeCountMap.get(time) || 0);
|
||||
|
||||
const rpmTrend = [];
|
||||
const tpmTrend = [];
|
||||
@@ -267,7 +316,7 @@ export const calculateTrendData = (timePoints, timeQuotaMap, timeTokensMap, time
|
||||
consumeQuota: quotaTrend,
|
||||
tokens: tokensTrend,
|
||||
rpm: rpmTrend,
|
||||
tpm: tpmTrend
|
||||
tpm: tpmTrend,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -296,7 +345,11 @@ export const aggregateDataByTimeAndModel = (data, dataExportDefaultTime) => {
|
||||
return aggregatedData;
|
||||
};
|
||||
|
||||
export const generateChartTimePoints = (aggregatedData, data, dataExportDefaultTime) => {
|
||||
export const generateChartTimePoints = (
|
||||
aggregatedData,
|
||||
data,
|
||||
dataExportDefaultTime,
|
||||
) => {
|
||||
let chartTimePoints = Array.from(
|
||||
new Set([...aggregatedData.values()].map((d) => d.time)),
|
||||
);
|
||||
@@ -305,10 +358,12 @@ export const generateChartTimePoints = (aggregatedData, data, dataExportDefaultT
|
||||
const lastTime = Math.max(...data.map((item) => item.created_at));
|
||||
const interval = getTimeInterval(dataExportDefaultTime, true);
|
||||
|
||||
chartTimePoints = Array.from({ length: DEFAULTS.MAX_TREND_POINTS }, (_, i) =>
|
||||
timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime),
|
||||
chartTimePoints = Array.from(
|
||||
{ length: DEFAULTS.MAX_TREND_POINTS },
|
||||
(_, i) =>
|
||||
timestamp2string1(lastTime - (6 - i) * interval, dataExportDefaultTime),
|
||||
);
|
||||
}
|
||||
|
||||
return chartTimePoints;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,4 +23,4 @@ export function getLogOther(otherStr) {
|
||||
}
|
||||
let other = JSON.parse(otherStr);
|
||||
return other;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,104 +88,34 @@ export function getLucideIcon(key, selected = false) {
|
||||
// 根据不同的key返回不同的图标
|
||||
switch (key) {
|
||||
case 'detail':
|
||||
return (
|
||||
<LayoutDashboard
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <LayoutDashboard {...commonProps} color={iconColor} />;
|
||||
case 'playground':
|
||||
return (
|
||||
<TerminalSquare
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <TerminalSquare {...commonProps} color={iconColor} />;
|
||||
case 'chat':
|
||||
return (
|
||||
<MessageSquare
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <MessageSquare {...commonProps} color={iconColor} />;
|
||||
case 'token':
|
||||
return (
|
||||
<Key
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <Key {...commonProps} color={iconColor} />;
|
||||
case 'log':
|
||||
return (
|
||||
<BarChart3
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <BarChart3 {...commonProps} color={iconColor} />;
|
||||
case 'midjourney':
|
||||
return (
|
||||
<ImageIcon
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <ImageIcon {...commonProps} color={iconColor} />;
|
||||
case 'task':
|
||||
return (
|
||||
<CheckSquare
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <CheckSquare {...commonProps} color={iconColor} />;
|
||||
case 'topup':
|
||||
return (
|
||||
<CreditCard
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <CreditCard {...commonProps} color={iconColor} />;
|
||||
case 'channel':
|
||||
return (
|
||||
<Layers
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <Layers {...commonProps} color={iconColor} />;
|
||||
case 'redemption':
|
||||
return (
|
||||
<Gift
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <Gift {...commonProps} color={iconColor} />;
|
||||
case 'user':
|
||||
case 'personal':
|
||||
return (
|
||||
<User
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <User {...commonProps} color={iconColor} />;
|
||||
case 'models':
|
||||
return (
|
||||
<Package
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <Package {...commonProps} color={iconColor} />;
|
||||
case 'setting':
|
||||
return (
|
||||
<Settings
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <Settings {...commonProps} color={iconColor} />;
|
||||
default:
|
||||
return (
|
||||
<CircleUser
|
||||
{...commonProps}
|
||||
color={iconColor}
|
||||
/>
|
||||
);
|
||||
return <CircleUser {...commonProps} color={iconColor} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +361,7 @@ export function getLobeHubIcon(iconName, size = 14) {
|
||||
if (typeof iconName === 'string') iconName = iconName.trim();
|
||||
// 如果没有图标名称,返回 Avatar
|
||||
if (!iconName) {
|
||||
return <Avatar size="extra-extra-small">?</Avatar>;
|
||||
return <Avatar size='extra-extra-small'>?</Avatar>;
|
||||
}
|
||||
|
||||
// 解析组件路径与点号链式属性
|
||||
@@ -451,9 +381,12 @@ export function getLobeHubIcon(iconName, size = 14) {
|
||||
}
|
||||
|
||||
// 失败兜底
|
||||
if (!IconComponent || (typeof IconComponent !== 'function' && typeof IconComponent !== 'object')) {
|
||||
if (
|
||||
!IconComponent ||
|
||||
(typeof IconComponent !== 'function' && typeof IconComponent !== 'object')
|
||||
) {
|
||||
const firstLetter = String(iconName).charAt(0).toUpperCase();
|
||||
return <Avatar size="extra-extra-small">{firstLetter}</Avatar>;
|
||||
return <Avatar size='extra-extra-small'>{firstLetter}</Avatar>;
|
||||
}
|
||||
|
||||
// 解析点号链式属性,形如:key={...}、key='...'、key="..."、key=123、key、key=true/false
|
||||
@@ -467,7 +400,10 @@ export function getLobeHubIcon(iconName, size = 14) {
|
||||
v = v.slice(1, -1).trim();
|
||||
}
|
||||
// 去除引号
|
||||
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
||||
if (
|
||||
(v.startsWith('"') && v.endsWith('"')) ||
|
||||
(v.startsWith("'") && v.endsWith("'"))
|
||||
) {
|
||||
return v.slice(1, -1);
|
||||
}
|
||||
// 布尔
|
||||
@@ -765,7 +701,9 @@ const measureTextWidth = (
|
||||
};
|
||||
|
||||
export function truncateText(text, maxWidth = 200) {
|
||||
const isMobileScreen = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches;
|
||||
const isMobileScreen = window.matchMedia(
|
||||
`(max-width: ${MOBILE_BREAKPOINT - 1}px)`,
|
||||
).matches;
|
||||
if (!isMobileScreen) {
|
||||
return text;
|
||||
}
|
||||
@@ -959,7 +897,6 @@ export function renderQuotaWithAmount(amount) {
|
||||
}
|
||||
|
||||
export function renderQuota(quota, digits = 2) {
|
||||
|
||||
let quotaPerUnit = localStorage.getItem('quota_per_unit');
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
quotaPerUnit = parseFloat(quotaPerUnit);
|
||||
@@ -986,7 +923,7 @@ function isValidGroupRatio(ratio) {
|
||||
/**
|
||||
* Helper function to get effective ratio and label
|
||||
* @param {number} groupRatio - The default group ratio
|
||||
* @param {number} user_group_ratio - The user-specific group ratio
|
||||
* @param {number} user_group_ratio - The user-specific group ratio
|
||||
* @returns {Object} - Object containing { ratio, label, useUserGroupRatio }
|
||||
*/
|
||||
function getEffectiveRatio(groupRatio, user_group_ratio) {
|
||||
@@ -999,7 +936,7 @@ function getEffectiveRatio(groupRatio, user_group_ratio) {
|
||||
return {
|
||||
ratio: effectiveRatio,
|
||||
label: ratioLabel,
|
||||
useUserGroupRatio: useUserGroupRatio
|
||||
useUserGroupRatio: useUserGroupRatio,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1015,7 +952,7 @@ function renderPriceSimpleCore({
|
||||
cacheCreationRatio = 1.0,
|
||||
image = false,
|
||||
imageRatio = 1.0,
|
||||
isSystemPromptOverride = false
|
||||
isSystemPromptOverride = false,
|
||||
}) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
@@ -1059,7 +996,7 @@ function renderPriceSimpleCore({
|
||||
cacheRatio: cacheRatio,
|
||||
cacheCreationRatio: cacheCreationRatio,
|
||||
imageRatio: imageRatio,
|
||||
})
|
||||
});
|
||||
|
||||
if (isSystemPromptOverride) {
|
||||
result += '\n\r' + i18next.t('系统提示覆盖');
|
||||
@@ -1091,7 +1028,10 @@ export function renderModelPrice(
|
||||
audioInputTokens = 0,
|
||||
audioInputPrice = 0,
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
user_group_ratio,
|
||||
);
|
||||
groupRatio = effectiveGroupRatio;
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
@@ -1251,25 +1191,25 @@ export function renderModelPrice(
|
||||
const extraServices = [
|
||||
webSearch && webSearchCallCount > 0
|
||||
? i18next.t(
|
||||
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
count: webSearchCallCount,
|
||||
price: webSearchPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
' + Web搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
count: webSearchCallCount,
|
||||
price: webSearchPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
: '',
|
||||
fileSearch && fileSearchCallCount > 0
|
||||
? i18next.t(
|
||||
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
count: fileSearchCallCount,
|
||||
price: fileSearchPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
' + 文件搜索 {{count}}次 / 1K 次 * ${{price}} * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
count: fileSearchCallCount,
|
||||
price: fileSearchPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
: '',
|
||||
].join('');
|
||||
|
||||
@@ -1305,7 +1245,11 @@ export function renderLogContent(
|
||||
fileSearch = false,
|
||||
fileSearchCallCount = 0,
|
||||
) {
|
||||
const { ratio, label: ratioLabel, useUserGroupRatio: useUserGroupRatio } = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
const {
|
||||
ratio,
|
||||
label: ratioLabel,
|
||||
useUserGroupRatio: useUserGroupRatio,
|
||||
} = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
|
||||
@@ -1378,7 +1322,7 @@ export function renderModelPriceSimple(
|
||||
cacheCreationRatio,
|
||||
image,
|
||||
imageRatio,
|
||||
isSystemPromptOverride
|
||||
isSystemPromptOverride,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1397,7 +1341,10 @@ export function renderAudioModelPrice(
|
||||
cacheTokens = 0,
|
||||
cacheRatio = 1.0,
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
user_group_ratio,
|
||||
);
|
||||
groupRatio = effectiveGroupRatio;
|
||||
// 1 ratio = $0.002 / 1K tokens
|
||||
if (modelPrice !== -1) {
|
||||
@@ -1432,10 +1379,10 @@ export function renderAudioModelPrice(
|
||||
let audioPrice =
|
||||
(audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
|
||||
(audioCompletionTokens / 1000000) *
|
||||
inputRatioPrice *
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
groupRatio;
|
||||
inputRatioPrice *
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
groupRatio;
|
||||
let price = textPrice + audioPrice;
|
||||
return (
|
||||
<>
|
||||
@@ -1491,27 +1438,27 @@ export function renderAudioModelPrice(
|
||||
<p>
|
||||
{cacheTokens > 0
|
||||
? i18next.t(
|
||||
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cachePrice: inputRatioPrice * cacheRatio,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)
|
||||
'文字提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cachePrice: inputRatioPrice * cacheRatio,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)
|
||||
: i18next.t(
|
||||
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)}
|
||||
'文字提示 {{input}} tokens / 1M tokens * ${{price}} + 文字补全 {{completion}} tokens / 1M tokens * ${{compPrice}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
total: textPrice.toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t(
|
||||
@@ -1547,9 +1494,7 @@ export function renderQuotaWithPrompt(quota, digits) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return (
|
||||
i18next.t('等价金额:') + renderQuota(quota, digits)
|
||||
);
|
||||
return i18next.t('等价金额:') + renderQuota(quota, digits);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -1567,7 +1512,10 @@ export function renderClaudeModelPrice(
|
||||
cacheCreationTokens = 0,
|
||||
cacheCreationRatio = 1.0,
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
user_group_ratio,
|
||||
);
|
||||
groupRatio = effectiveGroupRatio;
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
@@ -1650,35 +1598,35 @@ export function renderClaudeModelPrice(
|
||||
<p>
|
||||
{cacheTokens > 0 || cacheCreationTokens > 0
|
||||
? i18next.t(
|
||||
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: nonCachedTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cacheRatio: cacheRatio,
|
||||
cacheCreationInput: cacheCreationTokens,
|
||||
cacheCreationRatio: cacheCreationRatio,
|
||||
cachePrice: cacheRatioPrice,
|
||||
cacheCreationPrice: cacheCreationRatioPrice,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
'提示 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 缓存创建 {{cacheCreationInput}} tokens / 1M tokens * ${{cacheCreationPrice}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
|
||||
{
|
||||
nonCacheInput: nonCachedTokens,
|
||||
cacheInput: cacheTokens,
|
||||
cacheRatio: cacheRatio,
|
||||
cacheCreationInput: cacheCreationTokens,
|
||||
cacheCreationRatio: cacheCreationRatio,
|
||||
cachePrice: cacheRatioPrice,
|
||||
cacheCreationPrice: cacheCreationRatioPrice,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)
|
||||
: i18next.t(
|
||||
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)}
|
||||
'提示 {{input}} tokens / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * {{ratioType}} {{ratio}} = ${{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
price: inputRatioPrice,
|
||||
completion: completionTokens,
|
||||
compPrice: completionRatioPrice,
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
total: price.toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
@@ -1696,7 +1644,10 @@ export function renderClaudeLogContent(
|
||||
cacheRatio = 1.0,
|
||||
cacheCreationRatio = 1.0,
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
user_group_ratio,
|
||||
);
|
||||
groupRatio = effectiveGroupRatio;
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
|
||||
@@ -60,4 +60,4 @@ export function getServerAddress() {
|
||||
}
|
||||
|
||||
return serverAddress;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,10 @@ 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 {
|
||||
THINK_TAG_REGEX,
|
||||
MESSAGE_ROLES,
|
||||
} from '../constants/playground.constants';
|
||||
import { TABLE_COMPACT_MODES_KEY } from '../constants';
|
||||
import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile';
|
||||
|
||||
@@ -95,7 +98,9 @@ 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;
|
||||
const isMobileScreen = window.matchMedia(
|
||||
`(max-width: ${MOBILE_BREAKPOINT - 1}px)`,
|
||||
).matches;
|
||||
if (isMobileScreen) {
|
||||
showErrorOptions.position = 'top-center';
|
||||
// showErrorOptions.transition = 'flip';
|
||||
@@ -316,7 +321,7 @@ export const getTextContent = (message) => {
|
||||
if (!message || !message.content) return '';
|
||||
|
||||
if (Array.isArray(message.content)) {
|
||||
const textContent = message.content.find(item => item.type === 'text');
|
||||
const textContent = message.content.find((item) => item.type === 'text');
|
||||
return textContent?.text || '';
|
||||
}
|
||||
return typeof message.content === 'string' ? message.content : '';
|
||||
@@ -341,15 +346,19 @@ export const processThinkTags = (content, reasoningContent = '') => {
|
||||
}
|
||||
replyParts.push(content.substring(lastIndex));
|
||||
|
||||
const processedContent = replyParts.join('').replace(/<\/?think>/g, '').trim();
|
||||
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;
|
||||
const processedReasoningContent =
|
||||
reasoningContent && thoughtsStr
|
||||
? `${reasoningContent}\n\n---\n\n${thoughtsStr}`
|
||||
: reasoningContent || thoughtsStr;
|
||||
|
||||
return {
|
||||
content: processedContent,
|
||||
reasoningContent: processedReasoningContent
|
||||
reasoningContent: processedReasoningContent,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -364,10 +373,14 @@ export const processIncompleteThinkTags = (content, reasoningContent = '') => {
|
||||
|
||||
const fragmentAfterLastOpen = content.substring(lastOpenThinkIndex);
|
||||
if (!fragmentAfterLastOpen.includes('</think>')) {
|
||||
const unclosedThought = fragmentAfterLastOpen.substring('<think>'.length).trim();
|
||||
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
|
||||
? `${reasoningContent}\n\n---\n\n${unclosedThought}`
|
||||
: unclosedThought
|
||||
: reasoningContent;
|
||||
|
||||
return processThinkTags(cleanContent, processedReasoningContent);
|
||||
@@ -377,20 +390,24 @@ export const processIncompleteThinkTags = (content, reasoningContent = '') => {
|
||||
};
|
||||
|
||||
// 构建消息内容(包含图片)
|
||||
export const buildMessageContent = (textContent, imageUrls = [], imageEnabled = false) => {
|
||||
export const buildMessageContent = (
|
||||
textContent,
|
||||
imageUrls = [],
|
||||
imageEnabled = false,
|
||||
) => {
|
||||
if (!textContent && (!imageUrls || imageUrls.length === 0)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const validImageUrls = imageUrls.filter(url => url && url.trim() !== '');
|
||||
const validImageUrls = imageUrls.filter((url) => url && url.trim() !== '');
|
||||
|
||||
if (imageEnabled && validImageUrls.length > 0) {
|
||||
return [
|
||||
{ type: 'text', text: textContent || '' },
|
||||
...validImageUrls.map(url => ({
|
||||
...validImageUrls.map((url) => ({
|
||||
type: 'image_url',
|
||||
image_url: { url: url.trim() }
|
||||
}))
|
||||
image_url: { url: url.trim() },
|
||||
})),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -403,27 +420,26 @@ export const createMessage = (role, content, options = {}) => ({
|
||||
content,
|
||||
createAt: Date.now(),
|
||||
id: generateMessageId(),
|
||||
...options
|
||||
...options,
|
||||
});
|
||||
|
||||
// 创建加载中的助手消息
|
||||
export const createLoadingAssistantMessage = () => createMessage(
|
||||
MESSAGE_ROLES.ASSISTANT,
|
||||
'',
|
||||
{
|
||||
export const createLoadingAssistantMessage = () =>
|
||||
createMessage(MESSAGE_ROLES.ASSISTANT, '', {
|
||||
reasoningContent: '',
|
||||
isReasoningExpanded: true,
|
||||
isThinkingComplete: false,
|
||||
hasAutoCollapsed: false,
|
||||
status: 'loading'
|
||||
}
|
||||
);
|
||||
status: 'loading',
|
||||
});
|
||||
|
||||
// 检查消息是否包含图片
|
||||
export const hasImageContent = (message) => {
|
||||
return message &&
|
||||
return (
|
||||
message &&
|
||||
Array.isArray(message.content) &&
|
||||
message.content.some(item => item.type === 'image_url');
|
||||
message.content.some((item) => item.type === 'image_url')
|
||||
);
|
||||
};
|
||||
|
||||
// 格式化消息用于API请求
|
||||
@@ -432,15 +448,13 @@ export const formatMessageForAPI = (message) => {
|
||||
|
||||
return {
|
||||
role: message.role,
|
||||
content: message.content
|
||||
content: message.content,
|
||||
};
|
||||
};
|
||||
|
||||
// 验证消息是否有效
|
||||
export const isValidMessage = (message) => {
|
||||
return message &&
|
||||
message.role &&
|
||||
(message.content || message.content === '');
|
||||
return message && message.role && (message.content || message.content === '');
|
||||
};
|
||||
|
||||
// 获取最后一条用户消息
|
||||
@@ -590,7 +604,10 @@ export const calculateModelPrice = ({
|
||||
if (selectedGroup === 'all' || usedGroupRatio === undefined) {
|
||||
// 在模型可用分组中选择倍率最小的分组,若无则使用 1
|
||||
let minRatio = Number.POSITIVE_INFINITY;
|
||||
if (Array.isArray(record.enable_groups) && record.enable_groups.length > 0) {
|
||||
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) {
|
||||
@@ -611,7 +628,8 @@ export const calculateModelPrice = ({
|
||||
if (record.quota_type === 0) {
|
||||
// 按量计费
|
||||
const inputRatioPriceUSD = record.model_ratio * 2 * usedGroupRatio;
|
||||
const completionRatioPriceUSD = record.model_ratio * record.completion_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';
|
||||
@@ -619,8 +637,10 @@ export const calculateModelPrice = ({
|
||||
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;
|
||||
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)}`,
|
||||
@@ -703,7 +723,7 @@ export const createCardProPagination = ({
|
||||
{/* 桌面端左侧总数信息 */}
|
||||
{!isMobile && (
|
||||
<span
|
||||
className="text-sm select-none"
|
||||
className='text-sm select-none'
|
||||
style={{ color: 'var(--semi-color-text-2)' }}
|
||||
>
|
||||
{totalText}
|
||||
@@ -719,7 +739,7 @@ export const createCardProPagination = ({
|
||||
showSizeChanger={showSizeChanger}
|
||||
onPageSizeChange={onPageSizeChange}
|
||||
onPageChange={onPageChange}
|
||||
size={isMobile ? "small" : "default"}
|
||||
size={isMobile ? 'small' : 'default'}
|
||||
showQuickJumper={isMobile}
|
||||
showTotal
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user