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 { useQuery } from '@tanstack/react-query'
|
||||||
import { Code2, Eye, RotateCcw, Save } from 'lucide-react'
|
import { Code2, Eye, RotateCcw, Save } from 'lucide-react'
|
||||||
import { memo, useCallback, useRef, useState } from 'react'
|
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { type UseFormReturn } from 'react-hook-form'
|
import type { UseFormReturn } from 'react-hook-form'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import { Button } from '@/components/design-system/button'
|
import { Button } from '@/components/design-system/button'
|
||||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||||
@@ -180,6 +181,18 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
|||||||
enabled: isUnsetVariant,
|
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(
|
const handleFieldChange = useCallback(
|
||||||
(field: keyof ModelFormValues, value: string) => {
|
(field: keyof ModelFormValues, value: string) => {
|
||||||
form.setValue(field, value, {
|
form.setValue(field, value, {
|
||||||
@@ -266,6 +279,9 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
|||||||
candidateModelNames={
|
candidateModelNames={
|
||||||
isUnsetVariant ? enabledModelsQuery.data?.data : undefined
|
isUnsetVariant ? enabledModelsQuery.data?.data : undefined
|
||||||
}
|
}
|
||||||
|
candidateModelsLoading={
|
||||||
|
isUnsetVariant && enabledModelsQuery.isLoading
|
||||||
|
}
|
||||||
filterMode={isUnsetVariant ? 'unset' : 'all'}
|
filterMode={isUnsetVariant ? 'unset' : 'all'}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
isSaving={isSaving}
|
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
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
import {
|
import type {
|
||||||
type ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
type OnChangeFn,
|
OnChangeFn,
|
||||||
type PaginationState,
|
PaginationState,
|
||||||
type RowSelectionState,
|
RowSelectionState,
|
||||||
type VisibilityState,
|
VisibilityState,
|
||||||
type SortingState,
|
SortingState,
|
||||||
} from '@tanstack/react-table'
|
} from '@tanstack/react-table'
|
||||||
import { Copy, Plus } from 'lucide-react'
|
import { Copy, Plus } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
@@ -51,6 +51,7 @@ import { combineBillingExpr } from '@/features/pricing/lib/billing-expr'
|
|||||||
import { useMediaQuery } from '@/hooks'
|
import { useMediaQuery } from '@/hooks'
|
||||||
|
|
||||||
import { safeJsonParse } from '../utils/json-parser'
|
import { safeJsonParse } from '../utils/json-parser'
|
||||||
|
import type { PricingMode } from './model-pricing-core'
|
||||||
import {
|
import {
|
||||||
ModelPricingEditorPanel,
|
ModelPricingEditorPanel,
|
||||||
type ModelPricingEditorPanelHandle,
|
type ModelPricingEditorPanelHandle,
|
||||||
@@ -87,6 +88,7 @@ type ModelRatioVisualEditorProps = {
|
|||||||
billingMode: string
|
billingMode: string
|
||||||
billingExpr: string
|
billingExpr: string
|
||||||
candidateModelNames?: string[]
|
candidateModelNames?: string[]
|
||||||
|
candidateModelsLoading?: boolean
|
||||||
filterMode?: 'all' | 'unset'
|
filterMode?: 'all' | 'unset'
|
||||||
onChange: (field: string, value: string) => void
|
onChange: (field: string, value: string) => void
|
||||||
onSave: () => void | Promise<void>
|
onSave: () => void | Promise<void>
|
||||||
@@ -125,6 +127,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
|||||||
billingMode,
|
billingMode,
|
||||||
billingExpr,
|
billingExpr,
|
||||||
candidateModelNames,
|
candidateModelNames,
|
||||||
|
candidateModelsLoading,
|
||||||
filterMode = 'all',
|
filterMode = 'all',
|
||||||
onChange,
|
onChange,
|
||||||
onSave,
|
onSave,
|
||||||
@@ -219,7 +222,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
|||||||
...draftByName.keys(),
|
...draftByName.keys(),
|
||||||
])
|
])
|
||||||
|
|
||||||
return Array.from(modelNames)
|
return [...modelNames]
|
||||||
.map((name) => {
|
.map((name) => {
|
||||||
const saved = savedByName.get(name)
|
const saved = savedByName.get(name)
|
||||||
const draft = draftByName.get(name)
|
const draft = draftByName.get(name)
|
||||||
@@ -289,6 +292,12 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
|||||||
const handleEdit = useCallback(
|
const handleEdit = useCallback(
|
||||||
(model: ModelRow) => {
|
(model: ModelRow) => {
|
||||||
const editableModel = model.draft ?? model.saved ?? model
|
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({
|
setEditData({
|
||||||
name: editableModel.name,
|
name: editableModel.name,
|
||||||
price: editableModel.price,
|
price: editableModel.price,
|
||||||
@@ -299,12 +308,7 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
|||||||
imageRatio: editableModel.imageRatio,
|
imageRatio: editableModel.imageRatio,
|
||||||
audioRatio: editableModel.audioRatio,
|
audioRatio: editableModel.audioRatio,
|
||||||
audioCompletionRatio: editableModel.audioCompletionRatio,
|
audioCompletionRatio: editableModel.audioCompletionRatio,
|
||||||
billingMode:
|
billingMode: editBillingMode,
|
||||||
editableModel.billingMode === 'tiered_expr'
|
|
||||||
? 'tiered_expr'
|
|
||||||
: editableModel.price && editableModel.price !== ''
|
|
||||||
? 'per-request'
|
|
||||||
: 'per-token',
|
|
||||||
billingExpr: editableModel.billingExpr,
|
billingExpr: editableModel.billingExpr,
|
||||||
requestRuleExpr: editableModel.requestRuleExpr,
|
requestRuleExpr: editableModel.requestRuleExpr,
|
||||||
})
|
})
|
||||||
@@ -632,7 +636,11 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
|||||||
return
|
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()
|
table.resetRowSelection()
|
||||||
toast.success(
|
toast.success(
|
||||||
t('Applied {{name}} pricing to {{count}} models', {
|
t('Applied {{name}} pricing to {{count}} models', {
|
||||||
@@ -663,7 +671,9 @@ const ModelRatioVisualEditorComponent = forwardRef<
|
|||||||
if (table.getState().globalFilter) {
|
if (table.getState().globalFilter) {
|
||||||
emptyStateText = t('No models match your search')
|
emptyStateText = t('No models match your search')
|
||||||
} else if (filterMode === 'unset') {
|
} else if (filterMode === 'unset') {
|
||||||
emptyStateText = t('No models with unset prices')
|
emptyStateText = candidateModelsLoading
|
||||||
|
? t('Loading...')
|
||||||
|
: t('No models with unset prices')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -821,6 +831,17 @@ export const ModelRatioVisualEditor = memo(
|
|||||||
// Custom equality check - only re-render if JSON props actually changed
|
// Custom equality check - only re-render if JSON props actually changed
|
||||||
(prevProps, nextProps) => {
|
(prevProps, nextProps) => {
|
||||||
return (
|
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.modelPrice === nextProps.modelPrice &&
|
||||||
prevProps.modelRatio === nextProps.modelRatio &&
|
prevProps.modelRatio === nextProps.modelRatio &&
|
||||||
prevProps.cacheRatio === nextProps.cacheRatio &&
|
prevProps.cacheRatio === nextProps.cacheRatio &&
|
||||||
@@ -832,6 +853,7 @@ export const ModelRatioVisualEditor = memo(
|
|||||||
prevProps.billingMode === nextProps.billingMode &&
|
prevProps.billingMode === nextProps.billingMode &&
|
||||||
prevProps.billingExpr === nextProps.billingExpr &&
|
prevProps.billingExpr === nextProps.billingExpr &&
|
||||||
prevProps.candidateModelNames === nextProps.candidateModelNames &&
|
prevProps.candidateModelNames === nextProps.candidateModelNames &&
|
||||||
|
prevProps.candidateModelsLoading === nextProps.candidateModelsLoading &&
|
||||||
prevProps.filterMode === nextProps.filterMode &&
|
prevProps.filterMode === nextProps.filterMode &&
|
||||||
prevProps.onChange === nextProps.onChange &&
|
prevProps.onChange === nextProps.onChange &&
|
||||||
prevProps.onSave === nextProps.onSave &&
|
prevProps.onSave === nextProps.onSave &&
|
||||||
|
|||||||
Vendored
+1
@@ -1842,6 +1842,7 @@
|
|||||||
"Failed to load": "Failed to load",
|
"Failed to load": "Failed to load",
|
||||||
"Failed to load API keys": "Failed to load API keys",
|
"Failed to load API keys": "Failed to load API keys",
|
||||||
"Failed to load billing history": "Failed to load billing history",
|
"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 home page content": "Failed to load home page content",
|
||||||
"Failed to load image": "Failed to load image",
|
"Failed to load image": "Failed to load image",
|
||||||
"Failed to load key status": "Failed to load key status",
|
"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": "Échec du chargement",
|
||||||
"Failed to load API keys": "Échec du chargement des Clés API",
|
"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 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 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 image": "Échec du chargement de l'image",
|
||||||
"Failed to load key status": "Échec du chargement du statut des clés",
|
"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": "読み込みに失敗しました",
|
||||||
"Failed to load API keys": "APIキーの読み込みに失敗しました",
|
"Failed to load API keys": "APIキーの読み込みに失敗しました",
|
||||||
"Failed to load billing history": "請求履歴の読み込みに失敗しました",
|
"Failed to load billing history": "請求履歴の読み込みに失敗しました",
|
||||||
|
"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": "Не удалось загрузить",
|
"Failed to load": "Не удалось загрузить",
|
||||||
"Failed to load API keys": "Не удалось загрузить API ключи",
|
"Failed to load API keys": "Не удалось загрузить API ключи",
|
||||||
"Failed to load billing history": "Не удалось загрузить историю платежей",
|
"Failed to load billing history": "Не удалось загрузить историю платежей",
|
||||||
|
"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": "Tải thất bại",
|
"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 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 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 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 image": "Không thể tải ảnh",
|
||||||
"Failed to load key status": "Không thể tải trạng thái khóa",
|
"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": "載入失敗",
|
||||||
"Failed to load API keys": "載入 API 金鑰失敗",
|
"Failed to load API keys": "載入 API 金鑰失敗",
|
||||||
"Failed to load billing history": "載入收費歷史失敗",
|
"Failed to load billing history": "載入收費歷史失敗",
|
||||||
|
"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": "加载失败",
|
"Failed to load": "加载失败",
|
||||||
"Failed to load API keys": "加载 API 密钥失败",
|
"Failed to load API keys": "加载 API 密钥失败",
|
||||||
"Failed to load billing history": "加载计费历史失败",
|
"Failed to load billing history": "加载计费历史失败",
|
||||||
|
"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": "加载密钥状态失败",
|
||||||
|
|||||||
Reference in New Issue
Block a user