Commit 3cc2b1be by CaIon

feat(channel-test-dialog): add functionality to delete failed models and copy selected models

parent 122a730a
......@@ -23,7 +23,15 @@ import {
type RowSelectionState,
type Table as TanStackTable,
} from '@tanstack/react-table'
import { Check, Copy, Info, Loader2, Settings } from 'lucide-react'
import {
Check,
CheckCircle2,
Copy,
Info,
Loader2,
Settings,
Trash2,
} from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
......@@ -61,6 +69,7 @@ import {
DataTableView,
useDataTable,
} from '@/components/data-table'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { Dialog } from '@/components/dialog'
import {
sideDrawerContentClassName,
......@@ -69,6 +78,7 @@ import {
sideDrawerHeaderClassName,
} from '@/components/drawer-layout'
import { StatusBadge } from '@/components/status-badge'
import { updateChannel } from '../../api'
import {
channelsQueryKeys,
formatResponseTime,
......@@ -315,11 +325,17 @@ function ChannelTestDialogContent({
const [isBatchTesting, setIsBatchTesting] = useState(false)
const [isBatchStopRequested, setIsBatchStopRequested] = useState(false)
const [batchProgress, setBatchProgress] = useState<BatchProgress | null>(null)
const [removedModels, setRemovedModels] = useState<Set<string>>(
() => new Set()
)
const [isDeleteFailedDialogOpen, setIsDeleteFailedDialogOpen] =
useState(false)
const [isDeletingFailed, setIsDeletingFailed] = useState(false)
const [failureDetails, setFailureDetails] =
useState<FailureDetailsState | null>(null)
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 10,
pageSize: 30,
})
const endpointSelectItems = useMemo(
() =>
......@@ -341,8 +357,11 @@ function ChannelTestDialogContent({
setIsBatchTesting(false)
setIsBatchStopRequested(false)
setBatchProgress(null)
setRemovedModels(() => new Set())
setIsDeleteFailedDialogOpen(false)
setIsDeletingFailed(false)
setFailureDetails(null)
setPagination({ pageIndex: 0, pageSize: 10 })
setPagination({ pageIndex: 0, pageSize: 30 })
}, [])
const streamDisabled = STREAM_INCOMPATIBLE_ENDPOINTS.has(endpointType)
......@@ -370,7 +389,7 @@ function ChannelTestDialogContent({
const modelsValue = currentRow.models
const defaultTestModel = currentRow.test_model?.trim()
const models = useMemo(() => {
const baseModels = useMemo(() => {
if (!modelsValue) return []
return modelsValue
.split(',')
......@@ -378,6 +397,21 @@ function ChannelTestDialogContent({
.filter(Boolean)
}, [modelsValue])
const models = useMemo(
() => baseModels.filter((model) => !removedModels.has(model)),
[baseModels, removedModels]
)
const successModels = useMemo(
() => models.filter((model) => testResults[model]?.status === 'success'),
[models, testResults]
)
const failedModels = useMemo(
() => models.filter((model) => testResults[model]?.status === 'error'),
[models, testResults]
)
const filteredModels = useMemo(() => {
if (!searchTerm) return models
const keyword = searchTerm.toLowerCase()
......@@ -661,6 +695,68 @@ function ChannelTestDialogContent({
[refreshChannelLists, t, testSingleModel, updateTestResult]
)
const handleSelectSuccessfulModels = useCallback(() => {
setRowSelection(() => {
const next: RowSelectionState = {}
for (const model of successModels) {
next[model] = true
}
return next
})
}, [successModels])
const handleDeleteFailedModels = useCallback(async () => {
const failed = models.filter(
(model) => testResults[model]?.status === 'error'
)
if (!failed.length) {
setIsDeleteFailedDialogOpen(false)
return
}
const failedSet = new Set(failed)
const remaining = models.filter((model) => !failedSet.has(model))
setIsDeletingFailed(true)
try {
const response = await updateChannel(currentRow.id, {
models: remaining.join(','),
})
if (response.success) {
setRemovedModels((prev) => {
const next = new Set(prev)
for (const model of failed) next.add(model)
return next
})
setTestResults((prev) => {
const next = { ...prev }
for (const model of failed) delete next[model]
return next
})
setRowSelection((prev) => {
const next = { ...prev }
for (const model of failed) delete next[model]
return next
})
toast.success(
t('Deleted {{count}} failed models', { count: failed.length })
)
refreshChannelLists()
setIsDeleteFailedDialogOpen(false)
} else {
toast.error(response.message || t('Failed to delete failed models'))
}
} catch (error: unknown) {
toast.error(
error instanceof Error
? error.message
: t('Failed to delete failed models')
)
} finally {
setIsDeletingFailed(false)
}
}, [currentRow.id, models, refreshChannelLists, t, testResults])
const handleClose = useCallback(() => {
resetState()
onOpenChange(false)
......@@ -911,6 +1007,36 @@ function ChannelTestDialogContent({
/>
)}
{!isAnyTesting &&
(successModels.length > 0 || failedModels.length > 0) && (
<div className='flex flex-wrap items-center gap-2'>
{successModels.length > 0 && (
<Button
variant='outline'
size='sm'
onClick={handleSelectSuccessfulModels}
>
<CheckCircle2 data-icon='inline-start' />
{t('Select successful models ({{count}})', {
count: successModels.length,
})}
</Button>
)}
{failedModels.length > 0 && (
<Button
variant='outline'
size='sm'
onClick={() => setIsDeleteFailedDialogOpen(true)}
>
<Trash2 data-icon='inline-start' />
{t('Delete failed models ({{count}})', {
count: failedModels.length,
})}
</Button>
)}
</div>
)}
<div className='space-y-3'>
<DataTableView
table={table}
......@@ -951,14 +1077,23 @@ function ChannelTestDialogContent({
<DataTablePagination table={table} />
</div>
<TestModelsBulkActions
table={table}
disabled={isAnyTesting}
onTestSelected={handleBatchTest}
/>
<TestModelsBulkActions table={table} />
</div>
</div>
</Dialog>
<ConfirmDialog
open={isDeleteFailedDialogOpen}
onOpenChange={setIsDeleteFailedDialogOpen}
title={t('Delete failed models')}
desc={t(
'This removes {{count}} failed models from this channel. This action cannot be undone.',
{ count: failedModels.length }
)}
destructive
isLoading={isDeletingFailed}
confirmText={t('Delete')}
handleConfirm={handleDeleteFailedModels}
/>
<FailureDetailsSheet
details={failureDetails}
onOpenChange={(sheetOpen) => {
......@@ -1193,21 +1328,18 @@ function FailureDetailsSheet({
function TestModelsBulkActions({
table,
disabled,
onTestSelected,
}: {
table: TanStackTable<ModelRow>
disabled?: boolean
onTestSelected: (models: string[]) => void
}) {
const { t } = useTranslation()
const { copyToClipboard } = useCopyToClipboard()
const selectedRows = table.getFilteredSelectedRowModel().rows
const selectedModels = selectedRows.map((row) => row.original.model)
const buttonLabel =
selectedModels.length > 0
? t('Test {{count}} selected', { count: selectedModels.length })
: t('Test selected models')
const handleCopySelected = useCallback(() => {
if (selectedModels.length === 0) return
void copyToClipboard(selectedModels.join(','))
}, [copyToClipboard, selectedModels])
return (
<BulkActionsToolbar table={table} entityName='model'>
......@@ -1216,22 +1348,16 @@ function TestModelsBulkActions({
render={
<Button
size='sm'
onClick={() => onTestSelected(selectedModels)}
disabled={disabled || selectedModels.length === 0}
onClick={handleCopySelected}
disabled={selectedModels.length === 0}
/>
}
>
{disabled ? (
<>
<Loader2 className='animate-spin' data-icon='inline-start' />
{t('Testing...')}
</>
) : (
buttonLabel
)}
<Copy data-icon='inline-start' />
{t('Copy selected models')}
</TooltipTrigger>
<TooltipContent>
<p>{t('Run tests for the selected models')}</p>
<p>{t('Copy selected models separated by commas (e.g. a,b)')}</p>
</TooltipContent>
</Tooltip>
</BulkActionsToolbar>
......
......@@ -980,6 +980,8 @@
"Copy secret key": "Copy secret key",
"Copy selected codes": "Copy selected codes",
"Copy selected keys": "Copy selected keys",
"Copy selected models": "Copy selected models",
"Copy selected models separated by commas (e.g. a,b)": "Copy selected models separated by commas (e.g. a,b)",
"Copy source field to target field": "Copy source field to target field",
"Copy the key and paste it here": "Copy the key and paste it here",
"Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.": "Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.",
......@@ -1158,6 +1160,8 @@
"Delete condition": "Delete condition",
"Delete Condition": "Delete Condition",
"Delete failed": "Delete failed",
"Delete failed models": "Delete failed models",
"Delete failed models ({{count}})": "Delete failed models ({{count}})",
"Delete Field": "Delete Field",
"Delete group": "Delete group",
"Delete Header": "Delete Header",
......@@ -1176,6 +1180,7 @@
"Delete selected channels": "Delete selected channels",
"Delete selected models": "Delete selected models",
"Deleted": "Deleted",
"Deleted {{count}} failed models": "Deleted {{count}} failed models",
"Deleted a custom OAuth provider": "Deleted a custom OAuth provider",
"Deleted a deployment": "Deleted a deployment",
"Deleted a model": "Deleted a model",
......@@ -1641,6 +1646,7 @@
"Failed to delete API keys": "Failed to delete API keys",
"Failed to delete channel": "Failed to delete channel",
"Failed to delete disabled channels": "Failed to delete disabled channels",
"Failed to delete failed models": "Failed to delete failed models",
"Failed to delete invalid redemption codes": "Failed to delete invalid redemption codes",
"Failed to delete model": "Failed to delete model",
"Failed to delete provider": "Failed to delete provider",
......@@ -3652,6 +3658,7 @@
"Select site direction": "Select site direction",
"Select start time": "Select start time",
"Select subscription plan": "Select subscription plan",
"Select successful models ({{count}})": "Select successful models ({{count}})",
"Select Sync Channels": "Select Sync Channels",
"Select sync channels to compare prices": "Select sync channels to compare prices",
"Select sync channels to compare ratios": "Select sync channels to compare ratios",
......@@ -4053,6 +4060,7 @@
"This plan does not allow balance redemption": "This plan does not allow balance redemption",
"This project must be used in compliance with the": "This project must be used in compliance with the",
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "This removes {{count}} failed models from this channel. This action cannot be undone.",
"This site currently has {{count}} models enabled": "This site currently has {{count}} models enabled",
"This tier catches any request that did not match earlier tiers.": "This tier catches any request that did not match earlier tiers.",
"this token group": "this token group",
......
......@@ -980,6 +980,8 @@
"Copy secret key": "Copier la clé secrète",
"Copy selected codes": "Copier les codes sélectionnés",
"Copy selected keys": "Copier les clés sélectionnées",
"Copy selected models": "Copier les modèles sélectionnés",
"Copy selected models separated by commas (e.g. a,b)": "Copier les modèles sélectionnés séparés par des virgules (par ex. a,b)",
"Copy source field to target field": "Copier le champ source vers le champ cible",
"Copy the key and paste it here": "Copiez la clé et collez-la ici",
"Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.": "Copiez ce prompt et envoyez-le à un LLM (ex. ChatGPT / Claude) pour vous aider à concevoir votre expression de facturation.",
......@@ -1158,6 +1160,8 @@
"Delete condition": "Supprimer la condition",
"Delete Condition": "Supprimer la condition",
"Delete failed": "Échec de la suppression",
"Delete failed models": "Supprimer les modèles en échec",
"Delete failed models ({{count}})": "Supprimer les modèles en échec ({{count}})",
"Delete Field": "Supprimer le champ",
"Delete group": "Supprimer le groupe",
"Delete Header": "Supprimer l'en-tête",
......@@ -1176,6 +1180,7 @@
"Delete selected channels": "Supprimer les canaux sélectionnés",
"Delete selected models": "Supprimer les modèles sélectionnés",
"Deleted": "Supprimé",
"Deleted {{count}} failed models": "{{count}} modèles en échec supprimés",
"Deleted a custom OAuth provider": "Fournisseur OAuth personnalisé supprimé",
"Deleted a deployment": "Déploiement supprimé",
"Deleted a model": "Modèle supprimé",
......@@ -1641,6 +1646,7 @@
"Failed to delete API keys": "Échec de la suppression des Clés API",
"Failed to delete channel": "Échec de la suppression du canal",
"Failed to delete disabled channels": "Échec de la suppression des canaux désactivés",
"Failed to delete failed models": "Échec de la suppression des modèles en échec",
"Failed to delete invalid redemption codes": "Échec de la suppression des codes d'échange invalides",
"Failed to delete model": "Échec de la suppression du modèle",
"Failed to delete provider": "Échec de la suppression du fournisseur",
......@@ -3652,6 +3658,7 @@
"Select site direction": "Sélectionner la direction du site",
"Select start time": "Sélectionner l'heure de début",
"Select subscription plan": "Sélectionner un plan d'abonnement",
"Select successful models ({{count}})": "Sélectionner les modèles réussis ({{count}})",
"Select Sync Channels": "Sélectionner les canaux de synchronisation",
"Select sync channels to compare prices": "Sélectionner les canaux de synchronisation pour comparer les prix",
"Select sync channels to compare ratios": "Sélectionner les canaux de synchronisation pour comparer les ratios",
......@@ -4053,6 +4060,7 @@
"This plan does not allow balance redemption": "Ce forfait ne permet pas le paiement avec le solde",
"This project must be used in compliance with the": "Ce projet doit être utilisé conformément aux",
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Cet enregistrement provient d’une instance avant la mise à niveau et n’inclut pas d’audits. Mettez à jour l’instance pour enregistrer l’IP du serveur, l’IP de callback, le moyen de paiement et la version du système.",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "Cela supprime {{count}} modèles en échec de ce canal. Cette action est irréversible.",
"This site currently has {{count}} models enabled": "Ce site compte actuellement {{count}} modèles activés",
"This tier catches any request that did not match earlier tiers.": "Ce palier récupère toute requête qui ne correspond à aucun palier précédent.",
"this token group": "ce groupe de jetons",
......
......@@ -980,6 +980,8 @@
"Copy secret key": "シークレットキーをコピー",
"Copy selected codes": "選択したコードをコピー",
"Copy selected keys": "選択したキーをコピー",
"Copy selected models": "選択したモデルをコピー",
"Copy selected models separated by commas (e.g. a,b)": "選択したモデルをカンマ区切りでコピー(例: a,b)",
"Copy source field to target field": "ソースフィールドをターゲットフィールドにコピー",
"Copy the key and paste it here": "キーをコピーしてここに貼り付けてください",
"Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.": "このプロンプトをコピーしてLLM(ChatGPT / Claudeなど)に送り、課金式の設計を依頼してください。",
......@@ -1158,6 +1160,8 @@
"Delete condition": "条件を削除",
"Delete Condition": "条件を削除",
"Delete failed": "削除に失敗しました",
"Delete failed models": "失敗したモデルを削除",
"Delete failed models ({{count}})": "失敗したモデルを削除({{count}})",
"Delete Field": "フィールドを削除",
"Delete group": "グループを削除",
"Delete Header": "ヘッダーを削除",
......@@ -1176,6 +1180,7 @@
"Delete selected channels": "選択したチャネルを削除",
"Delete selected models": "選択したモデルを削除",
"Deleted": "削除済み",
"Deleted {{count}} failed models": "失敗したモデルを {{count}} 個削除しました",
"Deleted a custom OAuth provider": "カスタム OAuth プロバイダーを削除しました",
"Deleted a deployment": "デプロイメントを削除しました",
"Deleted a model": "モデルを削除しました",
......@@ -1641,6 +1646,7 @@
"Failed to delete API keys": "APIキーの削除に失敗しました",
"Failed to delete channel": "チャネルの削除に失敗しました",
"Failed to delete disabled channels": "無効化されたチャネルの削除に失敗しました",
"Failed to delete failed models": "失敗したモデルの削除に失敗しました",
"Failed to delete invalid redemption codes": "無効な引き換えコードの削除に失敗しました",
"Failed to delete model": "モデルの削除に失敗しました",
"Failed to delete provider": "プロバイダーの削除に失敗しました",
......@@ -3652,6 +3658,7 @@
"Select site direction": "サイトの方向を選択",
"Select start time": "開始時間を選択",
"Select subscription plan": "サブスクリプションプランを選択",
"Select successful models ({{count}})": "成功したモデルを選択({{count}})",
"Select Sync Channels": "同期チャネルを選択",
"Select sync channels to compare prices": "価格比較のために同期チャネルを選択してください",
"Select sync channels to compare ratios": "比率を比較するために同期チャネルを選択",
......@@ -4053,6 +4060,7 @@
"This plan does not allow balance redemption": "このプランでは残高での交換は許可されていません",
"This project must be used in compliance with the": "このプロジェクトは、以下を遵守して使用する必要があります",
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "古いバージョンのインスタンスがこの記録を書き込み、監査情報がありません。最新に更新し、サーバーIP・コールバックIP・支払方法・OSバージョンの記録を有効にしてください。",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "この操作はこのチャンネルから失敗した {{count}} 個のモデルを削除します。元に戻せません。",
"This site currently has {{count}} models enabled": "このサイトでは現在 {{count}} 個のモデルが有効です",
"This tier catches any request that did not match earlier tiers.": "この段階は、前の段階に一致しなかったすべてのリクエストを受け取ります。",
"this token group": "このトークングループ",
......
......@@ -980,6 +980,8 @@
"Copy secret key": "Скопировать секретный ключ",
"Copy selected codes": "Копировать выбранные коды",
"Copy selected keys": "Копировать выбранные ключи",
"Copy selected models": "Копировать выбранные модели",
"Copy selected models separated by commas (e.g. a,b)": "Копировать выбранные модели через запятую (например, a,b)",
"Copy source field to target field": "Копировать исходное поле в целевое",
"Copy the key and paste it here": "Скопируйте ключ и вставьте его сюда",
"Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.": "Скопируйте этот промпт и отправьте его LLM (например, ChatGPT / Claude), чтобы помочь с разработкой выражения тарификации.",
......@@ -1158,6 +1160,8 @@
"Delete condition": "Удалить условие",
"Delete Condition": "Удалить условие",
"Delete failed": "Не удалось удалить",
"Delete failed models": "Удалить неуспешные модели",
"Delete failed models ({{count}})": "Удалить неуспешные модели ({{count}})",
"Delete Field": "Удалить поле",
"Delete group": "Удалить группу",
"Delete Header": "Удалить заголовок",
......@@ -1176,6 +1180,7 @@
"Delete selected channels": "Удалить выбранные каналы",
"Delete selected models": "Удалить выбранные модели",
"Deleted": "Удалён",
"Deleted {{count}} failed models": "Удалено неуспешных моделей: {{count}}",
"Deleted a custom OAuth provider": "Удалён пользовательский провайдер OAuth",
"Deleted a deployment": "Удалено развёртывание",
"Deleted a model": "Удалена модель",
......@@ -1641,6 +1646,7 @@
"Failed to delete API keys": "Не удалось удалить API ключи",
"Failed to delete channel": "Не удалось удалить канал",
"Failed to delete disabled channels": "Не удалось удалить отключённые каналы",
"Failed to delete failed models": "Не удалось удалить неуспешные модели",
"Failed to delete invalid redemption codes": "Не удалось удалить недействительные коды активации",
"Failed to delete model": "Не удалось удалить модель",
"Failed to delete provider": "Не удалось удалить поставщика",
......@@ -3652,6 +3658,7 @@
"Select site direction": "Выбрать направление сайта",
"Select start time": "Выбрать время начала",
"Select subscription plan": "Выбрать план подписки",
"Select successful models ({{count}})": "Выбрать успешные модели ({{count}})",
"Select Sync Channels": "Выбрать каналы синхронизации",
"Select sync channels to compare prices": "Выберите каналы синхронизации для сравнения цен",
"Select sync channels to compare ratios": "Выбрать каналы синхронизации для сравнения соотношений",
......@@ -4053,6 +4060,7 @@
"This plan does not allow balance redemption": "Этот план не разрешает оплату балансом",
"This project must be used in compliance with the": "Этот проект должен использоваться в соответствии с",
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Запись создана экземпляром до обновления и не содержит сведений аудита. Обновите экземпляр, чтобы фиксировать IP сервера, IP callback, способ оплаты и версию ОС.",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "Это удалит {{count}} неуспешных моделей из этого канала. Действие необратимо.",
"This site currently has {{count}} models enabled": "На этом сайте сейчас включено моделей: {{count}}",
"This tier catches any request that did not match earlier tiers.": "Этот уровень обрабатывает все запросы, которые не совпали с предыдущими уровнями.",
"this token group": "эта группа токенов",
......
......@@ -980,6 +980,8 @@
"Copy secret key": "Sao chép khóa bí mật",
"Copy selected codes": "Sao chép các mã đã chọn",
"Copy selected keys": "Sao chép các khóa đã chọn",
"Copy selected models": "Sao chép các mô hình đã chọn",
"Copy selected models separated by commas (e.g. a,b)": "Sao chép các mô hình đã chọn, phân tách bằng dấu phẩy (ví dụ: a,b)",
"Copy source field to target field": "Sao chép trường nguồn sang trường đích",
"Copy the key and paste it here": "Sao chép khóa và dán vào đây",
"Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.": "Sao chép prompt này và gửi cho LLM (ví dụ: ChatGPT / Claude) để được hỗ trợ thiết kế biểu thức tính phí.",
......@@ -1158,6 +1160,8 @@
"Delete condition": "Xóa điều kiện",
"Delete Condition": "Xóa điều kiện",
"Delete failed": "Xóa thất bại",
"Delete failed models": "Xóa các mô hình thất bại",
"Delete failed models ({{count}})": "Xóa các mô hình thất bại ({{count}})",
"Delete Field": "Xóa trường",
"Delete group": "Xóa nhóm",
"Delete Header": "Xóa tiêu đề",
......@@ -1176,6 +1180,7 @@
"Delete selected channels": "Xóa các kênh đã chọn",
"Delete selected models": "Xóa các mô hình đã chọn",
"Deleted": "Đã xóa",
"Deleted {{count}} failed models": "Đã xóa {{count}} mô hình thất bại",
"Deleted a custom OAuth provider": "Đã xóa nhà cung cấp OAuth tùy chỉnh",
"Deleted a deployment": "Đã xóa một triển khai",
"Deleted a model": "Đã xóa một mô hình",
......@@ -1641,6 +1646,7 @@
"Failed to delete API keys": "Không thể xóa khóa API",
"Failed to delete channel": "Không thể xóa kênh",
"Failed to delete disabled channels": "Không thể xóa các kênh đã vô hiệu hóa",
"Failed to delete failed models": "Xóa các mô hình thất bại không thành công",
"Failed to delete invalid redemption codes": "Không thể xóa các mã đổi thưởng không hợp lệ",
"Failed to delete model": "Không thể xóa mô hình",
"Failed to delete provider": "Xóa nhà cung cấp thất bại",
......@@ -3652,6 +3658,7 @@
"Select site direction": "Chọn hướng trang",
"Select start time": "Chọn thời gian bắt đầu",
"Select subscription plan": "Chọn gói đăng ký",
"Select successful models ({{count}})": "Chọn các mô hình thành công ({{count}})",
"Select Sync Channels": "Chọn Kênh đồng bộ",
"Select sync channels to compare prices": "Chọn kênh đồng bộ để so sánh giá",
"Select sync channels to compare ratios": "Chọn kênh đồng bộ để so sánh tỷ lệ",
......@@ -4053,6 +4060,7 @@
"This plan does not allow balance redemption": "Gói này không cho phép thanh toán bằng số dư",
"This project must be used in compliance with the": "Dự án này phải được sử dụng tuân thủ theo",
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Bản ghi này do bản cũ tạo và thiếu thông tin audit. Nâng cấp bản cài để lưu IP máy chủ, IP callback, hình thức thanh toán và phiên bản hệ thống.",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "Thao tác này sẽ xóa {{count}} mô hình thất bại khỏi kênh này. Không thể hoàn tác.",
"This site currently has {{count}} models enabled": "Trang này hiện đã bật {{count}} mô hình",
"This tier catches any request that did not match earlier tiers.": "Tầng này bắt mọi yêu cầu không khớp với các tầng trước.",
"this token group": "nhóm token này",
......
......@@ -980,6 +980,8 @@
"Copy secret key": "复制密钥",
"Copy selected codes": "复制选定的代码",
"Copy selected keys": "复制选定的密钥",
"Copy selected models": "复制已选模型",
"Copy selected models separated by commas (e.g. a,b)": "复制已选模型,使用英文逗号隔开(例如 a,b)",
"Copy source field to target field": "把来源字段复制到目标字段",
"Copy the key and paste it here": "复制密钥并粘贴到这里",
"Copy this prompt and send it to an LLM (e.g. ChatGPT / Claude) to help design your billing expression.": "复制以下提示词发送给 LLM(如 ChatGPT / Claude),让它帮你设计计费表达式",
......@@ -1158,6 +1160,8 @@
"Delete condition": "删除条件",
"Delete Condition": "删除条件",
"Delete failed": "删除失败",
"Delete failed models": "删除失败模型",
"Delete failed models ({{count}})": "删除失败模型({{count}})",
"Delete Field": "删除字段",
"Delete group": "删除分组",
"Delete Header": "删请求头",
......@@ -1176,6 +1180,7 @@
"Delete selected channels": "删除所选渠道",
"Delete selected models": "删除选定的模型",
"Deleted": "已注销",
"Deleted {{count}} failed models": "已删除 {{count}} 个失败模型",
"Deleted a custom OAuth provider": "删除了一个自定义 OAuth 提供方",
"Deleted a deployment": "删除了一个部署",
"Deleted a model": "删除了一个模型",
......@@ -1641,6 +1646,7 @@
"Failed to delete API keys": "删除API密钥失败",
"Failed to delete channel": "删除渠道失败",
"Failed to delete disabled channels": "删除已禁用渠道失败",
"Failed to delete failed models": "删除失败模型失败",
"Failed to delete invalid redemption codes": "删除无效兑换码失败",
"Failed to delete model": "删除模型失败",
"Failed to delete provider": "删除提供商失败",
......@@ -3652,6 +3658,7 @@
"Select site direction": "选择站点方向",
"Select start time": "选择开始时间",
"Select subscription plan": "选择订阅套餐",
"Select successful models ({{count}})": "勾选成功模型({{count}})",
"Select Sync Channels": "选择同步渠道",
"Select sync channels to compare prices": "选择同步渠道以对比价格",
"Select sync channels to compare ratios": "选择同步渠道以比较比率",
......@@ -4053,6 +4060,7 @@
"This plan does not allow balance redemption": "该套餐不允许使用余额兑换",
"This project must be used in compliance with the": "此项目的使用必须遵守",
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "该记录由旧版本实例写入,缺少审计信息,建议将实例升级至最新版本以便记录服务器 IP、回调 IP、支付方式与系统版本等审计字段。",
"This removes {{count}} failed models from this channel. This action cannot be undone.": "此操作将从该渠道移除 {{count}} 个测试失败的模型,且无法撤销。",
"This site currently has {{count}} models enabled": "本站当前已启用模型,总计 {{count}} 个",
"This tier catches any request that did not match earlier tiers.": "此阶梯会兜底处理未匹配前面阶梯的请求。",
"this token group": "此令牌分组",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment