feat: improve model price error UX with role-aware messages and cleaner UI

- Backend: differentiate error messages for admin vs regular users in price.go
- Backend: include error_code in channel test response for structured error handling
- Frontend: render model_price_error as a styled card in Playground with admin nav button
- Frontend: show inline error details and settings link in channel test modal
- Frontend: parse error codes from both SSE and non-streaming API responses
- i18n: remove redundant "Settings" suffix from setting tab translations (en/fr/ru/ja/vi)
- i18n: update "Group & Model Pricing" translations across all locales
This commit is contained in:
CaIon
2026-04-11 17:19:38 +08:00
parent 040e8c1da8
commit ed7f839911
16 changed files with 224 additions and 84 deletions
@@ -21,8 +21,9 @@ import React, { useRef, useEffect } from 'react';
import { Typography, TextArea, Button } from '@douyinfe/semi-ui';
import MarkdownRenderer from '../common/markdown/MarkdownRenderer';
import ThinkingContent from './ThinkingContent';
import { Loader2, Check, X } from 'lucide-react';
import { Loader2, Check, X, Settings, AlertTriangle } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { isAdmin } from '../../helpers/utils';
const MessageContent = ({
message,
@@ -64,6 +65,44 @@ const MessageContent = ({
errorText = t('请求发生错误');
}
if (message.errorCode === 'model_price_error') {
return (
<div className={`${className}`}>
<div
className='rounded-lg p-3 space-y-2'
style={{
background: 'var(--semi-color-bg-0)',
border: '1px solid var(--semi-color-border)',
}}
>
<div className='flex items-center gap-2'>
<AlertTriangle size={16} className='text-orange-500 shrink-0' />
<Typography.Text strong className='!text-[var(--semi-color-text-0)]'>
{t('模型价格未配置')}
</Typography.Text>
</div>
<Typography.Paragraph
className='!text-[var(--semi-color-text-1)] !text-sm !mb-0'
style={{ wordBreak: 'break-word' }}
>
{errorText}
</Typography.Paragraph>
{isAdmin() && (
<Button
size='small'
theme='light'
type='warning'
icon={<Settings size={14} />}
onClick={() => window.open('/console/setting?tab=ratio', '_blank')}
>
{t('前往设置')}
</Button>
)}
</div>
</div>
);
}
return (
<div className={`${className}`}>
<Typography.Text className='text-white'>{errorText}</Typography.Text>
@@ -30,6 +30,7 @@ import {
Banner,
} from '@douyinfe/semi-ui';
import { IconSearch, IconInfoCircle } from '@douyinfe/semi-icons';
import { Settings } from 'lucide-react';
import { copy, showError, showInfo, showSuccess } from '../../../../helpers';
import { MODEL_TABLE_PAGE_SIZE } from '../../../../constants';
@@ -168,17 +169,43 @@ const ModelTestModal = ({
}
return (
<div className='flex items-center gap-2'>
<Tag color={testResult.success ? 'green' : 'red'} shape='circle'>
{testResult.success ? t('成功') : t('失败')}
</Tag>
{testResult.success && (
<Typography.Text type='tertiary'>
{t('请求时长: ${time}s').replace(
'${time}',
testResult.time.toFixed(2),
<div className='flex flex-col gap-1'>
<div className='flex items-center gap-2'>
<Tag color={testResult.success ? 'green' : 'red'} shape='circle'>
{testResult.success ? t('成功') : t('失败')}
</Tag>
{testResult.success && (
<Typography.Text type='tertiary'>
{t('请求时长: ${time}s').replace(
'${time}',
testResult.time.toFixed(2),
)}
</Typography.Text>
)}
</div>
{!testResult.success && testResult.message && (
<div className='flex flex-col gap-1'>
<Typography.Text
type='danger'
size='small'
className='break-all'
style={{ maxWidth: '400px', fontSize: '12px' }}
>
{testResult.message}
</Typography.Text>
{testResult.errorCode === 'model_price_error' && (
<Button
size='small'
theme='light'
type='warning'
icon={<Settings size={12} />}
onClick={() => window.open('/console/setting?tab=ratio', '_blank')}
style={{ width: 'fit-content' }}
>
{t('前往设置')}
</Button>
)}
</Typography.Text>
</div>
)}
</div>
);