mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-03 04:43:01 +00:00
Merge remote-tracking branch 'origin/main' into refactor/ui
This commit is contained in:
@@ -18,9 +18,10 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Code2, Eye, RotateCcw, Save } from 'lucide-react'
|
||||
import { memo, useCallback, useRef, useState } from 'react'
|
||||
import { type UseFormReturn } from 'react-hook-form'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { UseFormReturn } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Button } from '@/components/design-system/button'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
@@ -180,6 +181,18 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
||||
enabled: isUnsetVariant,
|
||||
})
|
||||
|
||||
const enabledModelsError = isUnsetVariant
|
||||
? enabledModelsQuery.isError ||
|
||||
(enabledModelsQuery.data !== undefined &&
|
||||
!enabledModelsQuery.data.success)
|
||||
: false
|
||||
const enabledModelsErrorMessage = enabledModelsQuery.data?.message
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabledModelsError) return
|
||||
toast.error(enabledModelsErrorMessage || t('Failed to load enabled models'))
|
||||
}, [enabledModelsError, enabledModelsErrorMessage, t])
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(field: keyof ModelFormValues, value: string) => {
|
||||
form.setValue(field, value, {
|
||||
@@ -266,6 +279,9 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
||||
candidateModelNames={
|
||||
isUnsetVariant ? enabledModelsQuery.data?.data : undefined
|
||||
}
|
||||
candidateModelsLoading={
|
||||
isUnsetVariant && enabledModelsQuery.isLoading
|
||||
}
|
||||
filterMode={isUnsetVariant ? 'unset' : 'all'}
|
||||
onSave={handleSave}
|
||||
isSaving={isSaving}
|
||||
|
||||
+38
-16
@@ -16,13 +16,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import {
|
||||
type ColumnFiltersState,
|
||||
type OnChangeFn,
|
||||
type PaginationState,
|
||||
type RowSelectionState,
|
||||
type VisibilityState,
|
||||
type SortingState,
|
||||
import type {
|
||||
ColumnFiltersState,
|
||||
OnChangeFn,
|
||||
PaginationState,
|
||||
RowSelectionState,
|
||||
VisibilityState,
|
||||
SortingState,
|
||||
} from '@tanstack/react-table'
|
||||
import { Copy, Plus } from 'lucide-react'
|
||||
import {
|
||||
@@ -51,6 +51,7 @@ import { combineBillingExpr } from '@/features/pricing/lib/billing-expr'
|
||||
import { useMediaQuery } from '@/hooks'
|
||||
|
||||
import { safeJsonParse } from '../utils/json-parser'
|
||||
import type { PricingMode } from './model-pricing-core'
|
||||
import {
|
||||
ModelPricingEditorPanel,
|
||||
type ModelPricingEditorPanelHandle,
|
||||
@@ -87,6 +88,7 @@ type ModelRatioVisualEditorProps = {
|
||||
billingMode: string
|
||||
billingExpr: string
|
||||
candidateModelNames?: string[]
|
||||
candidateModelsLoading?: boolean
|
||||
filterMode?: 'all' | 'unset'
|
||||
onChange: (field: string, value: string) => void
|
||||
onSave: () => void | Promise<void>
|
||||
@@ -125,6 +127,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
||||
billingMode,
|
||||
billingExpr,
|
||||
candidateModelNames,
|
||||
candidateModelsLoading,
|
||||
filterMode = 'all',
|
||||
onChange,
|
||||
onSave,
|
||||
@@ -219,7 +222,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
||||
...draftByName.keys(),
|
||||
])
|
||||
|
||||
return Array.from(modelNames)
|
||||
return [...modelNames]
|
||||
.map((name) => {
|
||||
const saved = savedByName.get(name)
|
||||
const draft = draftByName.get(name)
|
||||
@@ -289,6 +292,12 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
||||
const handleEdit = useCallback(
|
||||
(model: ModelRow) => {
|
||||
const editableModel = model.draft ?? model.saved ?? model
|
||||
let editBillingMode: PricingMode = 'per-token'
|
||||
if (editableModel.billingMode === 'tiered_expr') {
|
||||
editBillingMode = 'tiered_expr'
|
||||
} else if (editableModel.price && editableModel.price !== '') {
|
||||
editBillingMode = 'per-request'
|
||||
}
|
||||
setEditData({
|
||||
name: editableModel.name,
|
||||
price: editableModel.price,
|
||||
@@ -299,12 +308,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
||||
imageRatio: editableModel.imageRatio,
|
||||
audioRatio: editableModel.audioRatio,
|
||||
audioCompletionRatio: editableModel.audioCompletionRatio,
|
||||
billingMode:
|
||||
editableModel.billingMode === 'tiered_expr'
|
||||
? 'tiered_expr'
|
||||
: editableModel.price && editableModel.price !== ''
|
||||
? 'per-request'
|
||||
: 'per-token',
|
||||
billingMode: editBillingMode,
|
||||
billingExpr: editableModel.billingExpr,
|
||||
requestRuleExpr: editableModel.requestRuleExpr,
|
||||
})
|
||||
@@ -632,7 +636,11 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
||||
return
|
||||
}
|
||||
|
||||
persistPricingData(sourceData, targetNames)
|
||||
// Persist to the source model too, so targets never carry pricing the
|
||||
// source itself would lose if the editor draft were abandoned.
|
||||
persistPricingData(sourceData, [
|
||||
...new Set([sourceData.name, ...targetNames]),
|
||||
])
|
||||
table.resetRowSelection()
|
||||
toast.success(
|
||||
t('Applied {{name}} pricing to {{count}} models', {
|
||||
@@ -663,7 +671,9 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
||||
if (table.getState().globalFilter) {
|
||||
emptyStateText = t('No models match your search')
|
||||
} else if (filterMode === 'unset') {
|
||||
emptyStateText = t('No models with unset prices')
|
||||
emptyStateText = candidateModelsLoading
|
||||
? t('Loading...')
|
||||
: t('No models with unset prices')
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -821,6 +831,17 @@ export const ModelRatioVisualEditor = memo(
|
||||
// Custom equality check - only re-render if JSON props actually changed
|
||||
(prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.savedModelPrice === nextProps.savedModelPrice &&
|
||||
prevProps.savedModelRatio === nextProps.savedModelRatio &&
|
||||
prevProps.savedCacheRatio === nextProps.savedCacheRatio &&
|
||||
prevProps.savedCreateCacheRatio === nextProps.savedCreateCacheRatio &&
|
||||
prevProps.savedCompletionRatio === nextProps.savedCompletionRatio &&
|
||||
prevProps.savedImageRatio === nextProps.savedImageRatio &&
|
||||
prevProps.savedAudioRatio === nextProps.savedAudioRatio &&
|
||||
prevProps.savedAudioCompletionRatio ===
|
||||
nextProps.savedAudioCompletionRatio &&
|
||||
prevProps.savedBillingMode === nextProps.savedBillingMode &&
|
||||
prevProps.savedBillingExpr === nextProps.savedBillingExpr &&
|
||||
prevProps.modelPrice === nextProps.modelPrice &&
|
||||
prevProps.modelRatio === nextProps.modelRatio &&
|
||||
prevProps.cacheRatio === nextProps.cacheRatio &&
|
||||
@@ -832,6 +853,7 @@ export const ModelRatioVisualEditor = memo(
|
||||
prevProps.billingMode === nextProps.billingMode &&
|
||||
prevProps.billingExpr === nextProps.billingExpr &&
|
||||
prevProps.candidateModelNames === nextProps.candidateModelNames &&
|
||||
prevProps.candidateModelsLoading === nextProps.candidateModelsLoading &&
|
||||
prevProps.filterMode === nextProps.filterMode &&
|
||||
prevProps.onChange === nextProps.onChange &&
|
||||
prevProps.onSave === nextProps.onSave &&
|
||||
|
||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "Failed to load",
|
||||
"Failed to load API keys": "Failed to load API keys",
|
||||
"Failed to load billing history": "Failed to load billing history",
|
||||
"Failed to load enabled models": "Failed to load enabled models",
|
||||
"Failed to load home page content": "Failed to load home page content",
|
||||
"Failed to load image": "Failed to load image",
|
||||
"Failed to load key status": "Failed to load key status",
|
||||
|
||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "Échec du chargement",
|
||||
"Failed to load API keys": "Échec du chargement des Clés API",
|
||||
"Failed to load billing history": "Échec du chargement de l'historique de facturation",
|
||||
"Failed to load enabled models": "Échec du chargement des modèles activés",
|
||||
"Failed to load home page content": "Échec du chargement du contenu de la page d'accueil",
|
||||
"Failed to load image": "Échec du chargement de l'image",
|
||||
"Failed to load key status": "Échec du chargement du statut des clés",
|
||||
|
||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "読み込みに失敗しました",
|
||||
"Failed to load API keys": "APIキーの読み込みに失敗しました",
|
||||
"Failed to load billing history": "請求履歴の読み込みに失敗しました",
|
||||
"Failed to load enabled models": "有効なモデルの取得に失敗しました",
|
||||
"Failed to load home page content": "ホームページの内容の読み込みに失敗しました",
|
||||
"Failed to load image": "画像の読み込みに失敗しました",
|
||||
"Failed to load key status": "キー状態の読み込みに失敗しました",
|
||||
|
||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "Не удалось загрузить",
|
||||
"Failed to load API keys": "Не удалось загрузить API ключи",
|
||||
"Failed to load billing history": "Не удалось загрузить историю платежей",
|
||||
"Failed to load enabled models": "Не удалось загрузить включённые модели",
|
||||
"Failed to load home page content": "Не удалось загрузить содержимое главной страницы",
|
||||
"Failed to load image": "Не удалось загрузить изображение",
|
||||
"Failed to load key status": "Не удалось загрузить статус ключей",
|
||||
|
||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "Tải thất bại",
|
||||
"Failed to load API keys": "Không thể tải khóa API",
|
||||
"Failed to load billing history": "Không thể tải lịch sử thanh toán",
|
||||
"Failed to load enabled models": "Không thể tải các mô hình đã bật",
|
||||
"Failed to load home page content": "Không thể tải nội dung trang chủ",
|
||||
"Failed to load image": "Không thể tải ảnh",
|
||||
"Failed to load key status": "Không thể tải trạng thái khóa",
|
||||
|
||||
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "載入失敗",
|
||||
"Failed to load API keys": "載入 API 金鑰失敗",
|
||||
"Failed to load billing history": "載入收費歷史失敗",
|
||||
"Failed to load enabled models": "獲取啟用模型失敗",
|
||||
"Failed to load home page content": "載入首頁內容失敗",
|
||||
"Failed to load image": "無法載入圖像",
|
||||
"Failed to load key status": "載入金鑰狀態失敗",
|
||||
|
||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
||||
"Failed to load": "加载失败",
|
||||
"Failed to load API keys": "加载 API 密钥失败",
|
||||
"Failed to load billing history": "加载计费历史失败",
|
||||
"Failed to load enabled models": "获取启用模型失败",
|
||||
"Failed to load home page content": "加载首页内容失败",
|
||||
"Failed to load image": "无法加载图像",
|
||||
"Failed to load key status": "加载密钥状态失败",
|
||||
|
||||
Reference in New Issue
Block a user