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:
@@ -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
@@ -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 = {
|
||||
|
||||
+10
-1
@@ -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
@@ -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
@@ -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'>
|
||||
|
||||
+1
-1
@@ -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 ? (
|
||||
|
||||
+8
-8
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user