fix(default): resolve v1 frontend issue regressions

Fix v1 frontend regressions across channel forms, dashboard charts, wallet history, payment callbacks, invite links, API key groups, rate-limit errors, and usage-log scrolling.

Fixes #4715
Fixes #4618
Fixes #4699
Fixes #4651
Fixes #4637
Fixes #4682
Fixes #4691
Fixes #4565
Fixes #4334
This commit is contained in:
CaIon
2026-05-11 11:25:05 +08:00
parent 5fa103fa5b
commit ba474393fb
27 changed files with 267 additions and 41 deletions
+6
View File
@@ -484,6 +484,12 @@ export function transformFormDataToUpdatePayload(
}
})
// Send explicit empty strings for nullable JSON/text fields so GORM updates can clear them.
payload.model_mapping = formData.model_mapping || ''
payload.status_code_mapping = formData.status_code_mapping || ''
payload.param_override = formData.param_override || ''
payload.header_override = formData.header_override || ''
return payload
}
+7 -1
View File
@@ -143,6 +143,12 @@ function replaceToken(source: string, token: string, value: string) {
return source.split(token).join(value)
}
function normalizeApiKey(apiKey: string): string {
const trimmed = apiKey.trim()
if (!trimmed) return ''
return trimmed.startsWith('sk-') ? trimmed : `sk-${trimmed}`
}
export function resolveChatUrl({
template,
apiKey,
@@ -151,7 +157,7 @@ export function resolveChatUrl({
let url = template
const safeServerAddress = serverAddress || ''
const safeApiKey = apiKey || ''
const safeApiKey = normalizeApiKey(apiKey || '')
if (url.includes('{cherryConfig}')) {
const payload = {
@@ -115,6 +115,15 @@ export function ConsumptionDistributionChart(
]
)
const spec = chartType === 'bar' ? chartData.spec_line : chartData.spec_area
const specType = typeof spec?.type === 'string' ? spec.type : chartType
const chartKey = [
chartType,
specType,
props.loading ? 'loading' : 'ready',
props.data.length,
resolvedTheme,
customization.preset,
].join('-')
return (
<div className='overflow-hidden rounded-lg border'>
@@ -152,7 +161,7 @@ export function ConsumptionDistributionChart(
<div className='h-[300px] p-1.5 sm:h-96 sm:p-2'>
{themeReady && spec && (
<VChart
key={`${chartType}-${resolvedTheme}-${customization.preset}`}
key={chartKey}
spec={{
...spec,
theme: resolvedTheme === 'dark' ? 'dark' : 'light',
@@ -114,6 +114,15 @@ export function ModelCharts(props: ModelChartsProps) {
)
const spec = chartData[CHART_SPEC_KEYS[activeTab]]
const specType = typeof spec?.type === 'string' ? spec.type : activeTab
const chartKey = [
activeTab,
specType,
props.loading ? 'loading' : 'ready',
props.data.length,
resolvedTheme,
customization.preset,
].join('-')
return (
<div className='overflow-hidden rounded-lg border'>
@@ -149,7 +158,7 @@ export function ModelCharts(props: ModelChartsProps) {
<div className='h-[300px] p-1.5 sm:h-96 sm:p-2'>
{themeReady && spec && (
<VChart
key={`${activeTab}-${resolvedTheme}-${customization.preset}`}
key={chartKey}
spec={{
...spec,
theme: resolvedTheme === 'dark' ? 'dark' : 'light',
+1 -1
View File
@@ -212,7 +212,7 @@ export function processChartData(
legends: { visible: true, selectMode: 'single' },
},
spec_model_line: {
type: 'line',
type: 'area',
data: [{ id: 'lineData', values: [] }],
xField: 'Time',
yField: 'Count',
+24 -6
View File
@@ -25,27 +25,45 @@ const FEEDBACK_URL = 'https://github.com/QuantumNous/new-api/issues'
type GeneralErrorProps = React.HTMLAttributes<HTMLDivElement> & {
minimal?: boolean
error?: unknown
}
function getHttpStatus(error: unknown): number | undefined {
if (typeof error !== 'object' || error === null) return undefined
const response = (error as Record<string, unknown>).response
if (typeof response !== 'object' || response === null) return undefined
const status = (response as Record<string, unknown>).status
return typeof status === 'number' ? status : undefined
}
export function GeneralError({
className,
minimal = false,
error,
}: GeneralErrorProps) {
const { t } = useTranslation()
const navigate = useNavigate()
const { history } = useRouter()
const status = getHttpStatus(error)
const isRateLimited = status === 429
const title = isRateLimited
? t('Too many requests')
: `${t('Oops! Something went wrong')} ${`:')`}`
const description = isRateLimited
? t('Please wait a moment before trying again.')
: t('Please try again later.')
return (
<div className={cn('h-svh w-full', className)}>
<div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
{!minimal && (
<h1 className='text-[7rem] leading-tight font-bold'>500</h1>
<h1 className='text-[7rem] leading-tight font-bold'>
{status ?? 500}
</h1>
)}
<span className='font-medium'>
{t('Oops! Something went wrong')} {`:')`}
</span>
<span className='font-medium'>{title}</span>
<p className='text-muted-foreground text-center'>
{t('We apologize for the inconvenience.')} <br />{' '}
{t('Please try again later.')}
{t('We apologize for the inconvenience.')} <br /> {description}
</p>
{!minimal && (
<p className='text-muted-foreground text-center text-sm'>
@@ -145,7 +145,7 @@ export function ApiKeyGroupCombobox({
<span className='flex min-w-0 flex-1 items-center justify-between gap-2 sm:gap-3'>
<span className='min-w-0'>
<span className='block truncate font-medium'>
{selectedOption?.value || placeholder || t('Select a group')}
{selectedOption?.label || placeholder || t('Select a group')}
</span>
{selectedOption?.desc && (
<span className='text-muted-foreground block truncate text-[11px] sm:text-xs'>
@@ -178,7 +178,7 @@ export function ApiKeyGroupCombobox({
<CommandItem
key={option.value}
value={option.value}
onSelect={handleSelect}
onSelect={() => handleSelect(option.value)}
className='data-[selected=true]:bg-muted items-start gap-3 rounded-lg px-3 py-3 transition-colors'
>
<Check
@@ -189,7 +189,7 @@ export function ApiKeyGroupCombobox({
/>
<span className='min-w-0 flex-1'>
<span className='block truncate font-medium'>
{option.value}
{option.label}
</span>
{option.desc && (
<span className='text-muted-foreground block truncate text-xs'>
@@ -126,7 +126,7 @@ export function RateLimitSection({ defaultValues }: RateLimitSectionProps) {
</FormLabel>
<FormDescription>
{t(
'Restrict user model request frequency (may impact high concurrency performance)'
'This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.'
)}
</FormDescription>
</div>
@@ -171,6 +171,7 @@ export function UsageLogsTable({ logCategory }: UsageLogsTableProps) {
'No usage logs available. Logs will appear here once API calls are made.'
)}
skeletonKeyPrefix='usage-log-skeleton'
tableClassName='max-h-[calc(100dvh-13rem)] overflow-auto sm:max-h-[calc(100dvh-14rem)]'
tableHeaderClassName='bg-muted/30 sticky top-0 z-10'
toolbar={
isCommon ? (
@@ -176,8 +176,8 @@ export function BillingHistoryDialog({
</p>
<p className='mt-1 text-xs'>
{keyword
? 'Try adjusting your search'
: 'Your transaction history will appear here'}
? t('Try adjusting your search')
: t('Your transaction history will appear here')}
</p>
</div>
) : (
@@ -233,15 +233,15 @@ export function BillingHistoryDialog({
<div className='mt-3 grid grid-cols-2 gap-3 sm:mt-4 sm:grid-cols-3 sm:gap-4'>
<div className='space-y-1'>
<Label className='text-muted-foreground text-xs'>
Payment Method
{t('Payment Method')}
</Label>
<div className='text-sm font-medium'>
{getPaymentMethodName(record.payment_method)}
{getPaymentMethodName(record.payment_method, t)}
</div>
</div>
<div className='space-y-1'>
<Label className='text-muted-foreground text-xs'>
Amount
{t('Amount')}
</Label>
<div className='text-sm font-semibold'>
{formatCurrencyFromUSD(record.amount, {
@@ -253,7 +253,7 @@ export function BillingHistoryDialog({
</div>
<div className='space-y-1'>
<Label className='text-muted-foreground text-xs'>
Payment
{t('Payment')}
</Label>
<div className='text-sm font-semibold text-red-600'>
{formatNumber(record.money)}
@@ -270,7 +270,7 @@ export function BillingHistoryDialog({
onClick={() => setConfirmTradeNo(record.trade_no)}
disabled={completing}
>
Complete Order
{t('Complete Order')}
</Button>
</div>
)}
@@ -341,7 +341,7 @@ export function BillingHistoryDialog({
onClick={handleConfirmComplete}
disabled={completing}
>
{completing ? 'Processing...' : 'Confirm'}
{completing ? t('Processing...') : t('Confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
+6 -2
View File
@@ -67,8 +67,12 @@ export const PAYMENT_METHOD_NAMES: Record<string, string> = {
/**
* Get payment method display name
*/
export function getPaymentMethodName(method: string): string {
return PAYMENT_METHOD_NAMES[method] || method
export function getPaymentMethodName(
method: string,
t?: (key: string) => string
): string {
const name = PAYMENT_METHOD_NAMES[method] || method
return t ? t(name) : name
}
/**