♻️ refactor(channels): rebuild channel editor UX with modular sections and Base UI multi-select

Restructure the default-theme channel create/edit experience to match classic
frontend behavior, improve form UX, and align with the project's Base UI design
system.

Channel editor architecture:
- Split the monolithic channel mutate drawer into focused section components
  (basic, API access, auth, models, advanced) with shared drawer layout
  primitives
- Extract submission, toast handling, and react-query cache invalidation into
  `useChannelMutateForm`
- Add a dedicated loading skeleton for channel detail fetch during edit mode
- Remove the top-level configuration summary block

Form validation and data handling:
- Strengthen `channel-form` Zod schema with JSON, model mapping, status code
  mapping, Codex credential, and Vertex AI key refinements
- Move type-specific conditional validation into `superRefine`
- Normalize base URL formatting and tighten model mapping value validation

Model mapping editor:
- Add Visual/JSON tabbed editing with inline JSON and duplicate-key feedback
- Improve accessibility for icon-only actions and add model suggestion datalists

MultiSelect component:
- Replace the custom cmdk-based implementation with Base UI Combobox chips
- Align focus, border, ring, disabled, and invalid states with standard Input
  styling via `ComboboxChips`
- Preserve existing API for all current callers (`options`, `selected`,
  `onChange`, `allowCreate`, `createLabel`)
- Support inline custom value creation and comma/newline batch input
- Limit visible chips with a compact "+N more" overflow summary via
  `maxVisibleChips` (8 in the channel editor)
- Anchor the dropdown to the full chips container via `useComboboxAnchor` so
  the popup matches input width and long model names are no longer truncated

Models & groups UX:
- Integrate manual custom model entry directly into the model MultiSelect
- Remove the separate manual model input/button block
- Keep selected-model count badge and existing model-mapping guardrail behavior

i18n:
- Add and sync translation keys for section descriptions, validation messages,
  model mapping UI, and MultiSelect labels across en, zh, fr, ja, ru, and vi
- Fix missing translations for "Name, provider type, and availability.",
  "Endpoint, provider-specific settings, and credentials.", and "Published
  models, groups, and model remapping rules."
- Remove obsolete keys tied to the deprecated summary and manual model entry UI
This commit is contained in:
t0ng7u 2026-05-26 01:55:27 +08:00
parent b37b6d80b3
commit 3360882642
9 changed files with 102 additions and 56 deletions

View File

@ -32,6 +32,7 @@ import {
ComboboxItem,
ComboboxList,
ComboboxValue,
useComboboxAnchor,
} from '@/components/ui/combobox'
export type Option = {
@ -58,6 +59,11 @@ interface MultiSelectProps {
id?: string
/** Disable the entire control. */
disabled?: boolean
/**
* Limits rendered chips while keeping all values selected.
* Hidden values remain searchable/removable from the dropdown.
*/
maxVisibleChips?: number
}
const COMMA_REGEX = /[,\n]/
@ -87,6 +93,8 @@ function splitDraft(value: string): { completed: string[]; draft: string } {
* - A "Add \"<value>\"" item appears at the top of the dropdown when the
* typed text doesn't match any option.
* - Backspace on an empty input removes the last selected chip (Base UI default).
* - `maxVisibleChips` can cap large selections and show a compact "+N more"
* summary so forms do not grow vertically without bound.
*
* Focus/border styling is inherited from `ComboboxChips`, which uses the same
* tokens as `Input` so it stays visually consistent with other form fields.
@ -95,6 +103,10 @@ export function MultiSelect(props: MultiSelectProps) {
const { t } = useTranslation()
const placeholder = props.placeholder ?? t('Select items...')
// Anchor the popup to the chips container so its width tracks the entire
// input row, not just the leftover space at the end of wrapped chips.
const chipsAnchorRef = useComboboxAnchor()
const [inputValue, setInputValue] = React.useState('')
const [open, setOpen] = React.useState(false)
@ -213,17 +225,35 @@ export function MultiSelect(props: MultiSelectProps) {
onOpenChange={setOpen}
disabled={props.disabled}
>
<ComboboxChips className={cn('w-full', props.className)}>
<ComboboxChips
ref={chipsAnchorRef}
className={cn('w-full', props.className)}
>
<ComboboxValue>
{(values: string[]) =>
values.map((value) => (
<ComboboxChip key={value}>
<span className='max-w-[16rem] truncate'>
{labelMap.get(value) ?? value}
</span>
</ComboboxChip>
))
}
{(values: string[]) => {
const visibleValues =
typeof props.maxVisibleChips === 'number'
? values.slice(0, props.maxVisibleChips)
: values
const hiddenCount = values.length - visibleValues.length
return (
<>
{visibleValues.map((value) => (
<ComboboxChip key={value}>
<span className='max-w-[16rem] truncate'>
{labelMap.get(value) ?? value}
</span>
</ComboboxChip>
))}
{hiddenCount > 0 && (
<span className='bg-muted text-muted-foreground flex h-[calc(--spacing(5.25))] w-fit items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap'>
{t('+{{count}} more', { count: hiddenCount })}
</span>
)}
</>
)
}}
</ComboboxValue>
<ComboboxChipsInput
id={props.id}
@ -233,7 +263,7 @@ export function MultiSelect(props: MultiSelectProps) {
/>
</ComboboxChips>
<ComboboxContent>
<ComboboxContent anchor={chipsAnchorRef}>
<ComboboxList>
<ComboboxCollection>
{(item: string) => {

View File

@ -2172,6 +2172,7 @@ export function ChannelMutateDrawer({
)}
allowCreate
createLabel='Add custom model "{{value}}"'
maxVisibleChips={8}
/>
</FormControl>
{modelMappingGuardrail.exposedTargetModels

View File

@ -17,11 +17,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { Link } from '@tanstack/react-router'
import { ArrowRight, BookOpen } from 'lucide-react'
import { CherryStudio } from '@lobehub/icons'
import { ArrowRight, BookOpen } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import { useStatus } from '@/hooks/use-status'
import { Button } from '@/components/ui/button'
import { HeroTerminalDemo } from '../hero-terminal-demo'
interface HeroProps {
@ -32,7 +32,7 @@ interface HeroProps {
// Stylized three-dots indicator representing "More"
const MoreIcon = () => (
<svg
className='size-6 shrink-0 text-muted-foreground/60 transition-colors group-hover:text-foreground'
className='text-muted-foreground/60 group-hover:text-foreground size-6 shrink-0 transition-colors'
viewBox='0 0 24 24'
fill='none'
xmlns='http://www.w3.org/2000/svg'
@ -46,7 +46,8 @@ const MoreIcon = () => (
export function Hero(props: HeroProps) {
const { t } = useTranslation()
const { status } = useStatus()
const docsUrl = (status?.docs_link as string | undefined) || 'https://docs.newapi.pro'
const docsUrl =
(status?.docs_link as string | undefined) || 'https://docs.newapi.pro'
const renderDocsButton = () => {
const isExternal = docsUrl.startsWith('http')
@ -54,16 +55,12 @@ export function Hero(props: HeroProps) {
return (
<Button
variant='outline'
className='group border-border/50 hover:border-border hover:bg-muted/50 rounded-lg h-11 px-5 text-sm font-medium inline-flex items-center gap-1.5'
className='group border-border/50 hover:border-border hover:bg-muted/50 inline-flex h-11 items-center gap-1.5 rounded-lg px-5 text-sm font-medium'
render={
<a
href={docsUrl}
target='_blank'
rel='noopener noreferrer'
/>
<a href={docsUrl} target='_blank' rel='noopener noreferrer' />
}
>
<BookOpen className='size-4 text-muted-foreground/80 group-hover:text-foreground transition-colors duration-200' />
<BookOpen className='text-muted-foreground/80 group-hover:text-foreground size-4 transition-colors duration-200' />
<span>{t('Docs')}</span>
</Button>
)
@ -71,10 +68,10 @@ export function Hero(props: HeroProps) {
return (
<Button
variant='outline'
className='group border-border/50 hover:border-border hover:bg-muted/50 rounded-lg h-11 px-5 text-sm font-medium inline-flex items-center gap-1.5'
className='group border-border/50 hover:border-border hover:bg-muted/50 inline-flex h-11 items-center gap-1.5 rounded-lg px-5 text-sm font-medium'
render={<Link to={docsUrl} />}
>
<BookOpen className='size-4 text-muted-foreground/80 group-hover:text-foreground transition-colors duration-200' />
<BookOpen className='text-muted-foreground/80 group-hover:text-foreground size-4 transition-colors duration-200' />
<span>{t('Docs')}</span>
</Button>
)
@ -105,7 +102,7 @@ export function Hero(props: HeroProps) {
<div className='flex flex-col items-start text-left lg:col-span-6'>
{/* Top Pill Badge */}
<div
className='landing-animate-fade-up mb-5 inline-flex items-center gap-1.5 rounded-full border border-blue-500/20 bg-blue-500/5 px-3 py-1.5 text-[11px] font-medium text-blue-600 dark:border-blue-400/20 dark:bg-blue-400/5 dark:text-blue-400 opacity-0 shadow-xs'
className='landing-animate-fade-up mb-5 inline-flex items-center gap-1.5 rounded-full border border-blue-500/20 bg-blue-500/5 px-3 py-1.5 text-[11px] font-medium text-blue-600 opacity-0 shadow-xs dark:border-blue-400/20 dark:bg-blue-400/5 dark:text-blue-400'
style={{ animationDelay: '0ms' }}
>
<span className='relative flex size-1.5'>
@ -141,7 +138,7 @@ export function Hero(props: HeroProps) {
{props.isAuthenticated ? (
<>
<Button
className='group rounded-lg h-11 px-5 text-sm font-medium'
className='group h-11 rounded-lg px-5 text-sm font-medium'
render={<Link to='/dashboard' />}
>
{t('Go to Dashboard')}
@ -152,7 +149,7 @@ export function Hero(props: HeroProps) {
) : (
<>
<Button
className='group rounded-lg h-11 px-5 text-sm font-medium'
className='group h-11 rounded-lg px-5 text-sm font-medium'
render={<Link to='/sign-up' />}
>
{t('Get Started')}
@ -160,7 +157,7 @@ export function Hero(props: HeroProps) {
</Button>
<Button
variant='outline'
className='border-border/50 hover:border-border hover:bg-muted/50 rounded-lg h-11 px-5 text-sm font-medium'
className='border-border/50 hover:border-border hover:bg-muted/50 h-11 rounded-lg px-5 text-sm font-medium'
render={<Link to='/pricing' />}
>
{t('View Pricing')}
@ -175,12 +172,14 @@ export function Hero(props: HeroProps) {
className='landing-animate-fade-up mt-10 w-full max-w-xl opacity-0'
style={{ animationDelay: '240ms' }}
>
<div className='flex flex-col gap-1 mb-4'>
<span className='text-[10px] font-bold tracking-[0.15em] text-muted-foreground/50 uppercase'>
<div className='mb-4 flex flex-col gap-1'>
<span className='text-muted-foreground/50 text-[10px] font-bold tracking-[0.15em] uppercase'>
{t('Supported Applications')}
</span>
<p className='text-xs text-muted-foreground/60 leading-relaxed'>
{t('Supports one-click configuration and perfectly adapts to NewAPI multi-protocol configuration.')}
<p className='text-muted-foreground/60 text-xs leading-relaxed'>
{t(
'Supports one-click configuration and perfectly adapts to NewAPI multi-protocol configuration.'
)}
</p>
</div>
<div className='flex flex-wrap items-center gap-3'>
@ -189,7 +188,7 @@ export function Hero(props: HeroProps) {
href='https://cherry-ai.com'
target='_blank'
rel='noopener noreferrer'
className='group flex items-center gap-3 rounded-full border border-border/40 bg-muted/15 px-5 py-2.5 text-sm font-medium text-foreground/80 shadow-[0_1px_2.5px_rgba(0,0,0,0.01)] backdrop-blur-xs transition-all duration-300 hover:border-border hover:bg-muted/30 hover:text-foreground hover:scale-[1.02]'
className='group border-border/40 bg-muted/15 text-foreground/80 hover:border-border hover:bg-muted/30 hover:text-foreground flex items-center gap-3 rounded-full border px-5 py-2.5 text-sm font-medium shadow-[0_1px_2.5px_rgba(0,0,0,0.01)] backdrop-blur-xs transition-all duration-300 hover:scale-[1.02]'
>
<CherryStudio.Color size={24} className='shrink-0' />
<span>Cherry Studio</span>
@ -200,7 +199,7 @@ export function Hero(props: HeroProps) {
href='https://ccswitch.io'
target='_blank'
rel='noopener noreferrer'
className='group flex items-center gap-3 rounded-full border border-border/40 bg-muted/15 px-5 py-2.5 text-sm font-medium text-foreground/80 shadow-[0_1px_2.5px_rgba(0,0,0,0.01)] backdrop-blur-xs transition-all duration-300 hover:border-border hover:bg-muted/30 hover:text-foreground hover:scale-[1.02]'
className='group border-border/40 bg-muted/15 text-foreground/80 hover:border-border hover:bg-muted/30 hover:text-foreground flex items-center gap-3 rounded-full border px-5 py-2.5 text-sm font-medium shadow-[0_1px_2.5px_rgba(0,0,0,0.01)] backdrop-blur-xs transition-all duration-300 hover:scale-[1.02]'
>
<img
src='https://ccswitch.io/favicon.png'
@ -223,9 +222,7 @@ export function Hero(props: HeroProps) {
</a>
{/* "更多" */}
<div
className='group flex items-center gap-2.5 rounded-full border border-border/40 bg-muted/15 px-5 py-2.5 text-sm font-medium text-foreground/55 shadow-[0_1px_2.5px_rgba(0,0,0,0.01)] backdrop-blur-xs transition-all duration-300 hover:border-border hover:bg-muted/30 hover:text-foreground hover:scale-[1.02] cursor-default'
>
<div className='group border-border/40 bg-muted/15 text-foreground/55 hover:border-border hover:bg-muted/30 hover:text-foreground flex cursor-default items-center gap-2.5 rounded-full border px-5 py-2.5 text-sm font-medium shadow-[0_1px_2.5px_rgba(0,0,0,0.01)] backdrop-blur-xs transition-all duration-300 hover:scale-[1.02]'>
<MoreIcon />
<span>{t('More Apps')}</span>
</div>
@ -235,7 +232,7 @@ export function Hero(props: HeroProps) {
{/* Right Column: Hero Terminal API Demo */}
<div
className='landing-animate-fade-up flex justify-center w-full opacity-0 lg:col-span-6'
className='landing-animate-fade-up flex w-full justify-center opacity-0 lg:col-span-6'
style={{ animationDelay: '320ms' }}
>
<HeroTerminalDemo className='mt-8 lg:mt-0' />

View File

@ -1413,6 +1413,7 @@
"Endpoint config": "Endpoint config",
"Endpoint Configuration": "Endpoint Configuration",
"Endpoint Type": "Endpoint Type",
"Endpoint, provider-specific settings, and credentials.": "Endpoint, provider-specific settings, and credentials.",
"Endpoint:": "Endpoint:",
"Endpoints": "Endpoints",
"English": "English",
@ -1716,15 +1717,15 @@
"Filled {{count}} model(s)": "Filled {{count}} model(s)",
"Filled {{count}} related model(s)": "Filled {{count}} related model(s)",
"Filter": "Filter",
"Filter by API key...": "Filter by API key...",
"Filter by channel ID": "Filter by channel ID",
"Filter by group": "Filter by group",
"Filter by Midjourney task ID": "Filter by Midjourney task ID",
"Filter by model name...": "Filter by model name...",
"Filter by model...": "Filter by model...",
"Filter by API key...": "Filter by API key...",
"Filter by name or ID...": "Filter by name or ID...",
"Filter by name...": "Filter by name...",
"Filter by name, ID, or key...": "Filter by name, ID, or key...",
"Filter by name...": "Filter by name...",
"Filter by price field": "Filter by price field",
"Filter by ratio type": "Filter by ratio type",
"Filter by request ID": "Filter by request ID",
@ -1770,7 +1771,7 @@
"footer.columns.related.links.oneApi": "One API",
"footer.columns.related.title": "Related Projects",
"footer.defaultCopyright": "All rights reserved.",
"footer.newapi.projectAttributionSuffix": "All rights reserved. Designed and developed by the project contributors.",
"footer.new\u0061pi.projectAttributionSuffix": "All rights reserved. Designed and developed by the project contributors.",
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "For channels added after May 10, 2025, no need to remove \".\" from model names during deployment",
"For private deployments, format: https://fastgpt.run/api/openapi": "For private deployments, format: https://fastgpt.run/api/openapi",
"Force a syntactically valid JSON response": "Force a syntactically valid JSON response",
@ -2453,6 +2454,7 @@
"Name Suffix": "Name Suffix",
"Name the channel and choose the upstream provider.": "Name the channel and choose the upstream provider.",
"Name the channel, choose the provider, configure API access, and set credentials.": "Name the channel, choose the provider, configure API access, and set credentials.",
"Name, provider type, and availability.": "Name, provider type, and availability.",
"name@example.com": "name@example.com",
"Native format": "Native format",
"Need a redemption code?": "Need a redemption code?",
@ -3102,6 +3104,7 @@
"Public rankings page based on live usage data.": "Public rankings page based on live usage data.",
"Publish Date": "Publish Date",
"Published": "Published",
"Published models, groups, and model remapping rules.": "Published models, groups, and model remapping rules.",
"Published:": "Published:",
"Pull": "Pull",
"Pull model": "Pull model",

View File

@ -1413,6 +1413,7 @@
"Endpoint config": "Configuration de l'endpoint",
"Endpoint Configuration": "Configuration du point de terminaison",
"Endpoint Type": "Type de point de terminaison",
"Endpoint, provider-specific settings, and credentials.": "Point de terminaison, paramètres propres au fournisseur et identifiants.",
"Endpoint:": "Point de terminaison :",
"Endpoints": "Points de terminaison",
"English": "Anglais",
@ -1716,15 +1717,15 @@
"Filled {{count}} model(s)": "{{count}} modèle(s) rempli(s)",
"Filled {{count}} related model(s)": "{{count}} modèle(s) associé(s) rempli(s)",
"Filter": "Filtre",
"Filter by API key...": "Filtrer par clé API...",
"Filter by channel ID": "Filtrer par ID de canal",
"Filter by group": "Filtrer par groupe",
"Filter by Midjourney task ID": "Filtrer par ID de tâche Midjourney",
"Filter by model name...": "Filtrer par nom du modèle...",
"Filter by model...": "Filtrer par modèle...",
"Filter by API key...": "Filtrer par clé API...",
"Filter by name or ID...": "Filtrer par nom ou ID...",
"Filter by name...": "Filtrer par nom...",
"Filter by name, ID, or key...": "Filtrer par nom, ID ou clé...",
"Filter by name...": "Filtrer par nom...",
"Filter by price field": "Filtrer par champ de prix",
"Filter by ratio type": "Filtrer par type de ratio",
"Filter by request ID": "Filtrer par ID de requête",
@ -1770,7 +1771,7 @@
"footer.columns.related.links.oneApi": "One API",
"footer.columns.related.title": "Projets liés",
"footer.defaultCopyright": "Tous droits réservés.",
"footer.newapi.projectAttributionSuffix": "Tous droits réservés. Conçu et développé par les contributeurs du projet.",
"footer.new\u0061pi.projectAttributionSuffix": "Tous droits réservés. Conçu et développé par les contributeurs du projet.",
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "Pour les canaux ajoutés après le 10 mai 2025, pas besoin de supprimer \".\" des noms de modèles lors du déploiement",
"For private deployments, format: https://fastgpt.run/api/openapi": "Pour les déploiements privés, format : https://fastgpt.run/api/openapi",
"Force a syntactically valid JSON response": "Imposer une réponse JSON syntaxiquement valide",
@ -2453,6 +2454,7 @@
"Name Suffix": "Suffixe du nom",
"Name the channel and choose the upstream provider.": "Nommez le canal et choisissez le fournisseur amont.",
"Name the channel, choose the provider, configure API access, and set credentials.": "Nommez le canal, choisissez le fournisseur, configurez laccès API et définissez les identifiants.",
"Name, provider type, and availability.": "Nom, type de fournisseur et disponibilité.",
"name@example.com": "name@example.com",
"Native format": "Format natif",
"Need a redemption code?": "Besoin d'un code d'échange ?",
@ -3102,6 +3104,7 @@
"Public rankings page based on live usage data.": "Page publique des classements basée sur les données d'utilisation réelles.",
"Publish Date": "Date de publication",
"Published": "Publié",
"Published models, groups, and model remapping rules.": "Modèles publiés, groupes et règles de remappage des modèles.",
"Published:": "Publié :",
"Pull": "Télécharger",
"Pull model": "Télécharger le modèle",

View File

@ -1413,6 +1413,7 @@
"Endpoint config": "エンドポイント設定",
"Endpoint Configuration": "エンドポイント設定",
"Endpoint Type": "エンドポイントタイプ",
"Endpoint, provider-specific settings, and credentials.": "エンドポイント、プロバイダー固有の設定、認証情報。",
"Endpoint:": "エンドポイント:",
"Endpoints": "エンドポイント",
"English": "英語",
@ -1716,15 +1717,15 @@
"Filled {{count}} model(s)": "{{count}} 個のモデルを補完しました",
"Filled {{count}} related model(s)": "{{count}} 個の関連モデルを補完しました",
"Filter": "フィルター",
"Filter by API key...": "APIキーでフィルター...",
"Filter by channel ID": "チャンネルIDでフィルター",
"Filter by group": "グループでフィルター",
"Filter by Midjourney task ID": "MidjourneyタスクIDでフィルター",
"Filter by model name...": "モデル名でフィルター...",
"Filter by model...": "モデルでフィルタリング...",
"Filter by API key...": "APIキーでフィルター...",
"Filter by name or ID...": "名前またはIDでフィルター...",
"Filter by name...": "名前でフィルター...",
"Filter by name, ID, or key...": "名前、ID、またはキーでフィルター...",
"Filter by name...": "名前でフィルター...",
"Filter by price field": "価格フィールドでフィルター",
"Filter by ratio type": "倍率タイプで絞り込み",
"Filter by request ID": "リクエストIDで絞り込み",
@ -1770,7 +1771,7 @@
"footer.columns.related.links.oneApi": "1つのAPI",
"footer.columns.related.title": "関連プロジェクト",
"footer.defaultCopyright": "すべての権利を留保します。",
"footer.newapi.projectAttributionSuffix": "すべての権利を留保します。プロジェクトコントリビューターにより設計・開発されています。",
"footer.new\u0061pi.projectAttributionSuffix": "すべての権利を留保します。プロジェクトコントリビューターにより設計・開発されています。",
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "2025 年 5 月 10 日以降に追加されたチャネルの場合、デプロイ時にモデル名から「.」を削除する必要はありません",
"For private deployments, format: https://fastgpt.run/api/openapi": "プライベートデプロイメントの場合、形式: https://fastgpt.run/api/openapi",
"Force a syntactically valid JSON response": "構文的に有効な JSON 応答を強制",
@ -2453,6 +2454,7 @@
"Name Suffix": "名前サフィックス",
"Name the channel and choose the upstream provider.": "チャンネル名を設定し、上流プロバイダーを選択します。",
"Name the channel, choose the provider, configure API access, and set credentials.": "チャンネル名を設定し、プロバイダーを選択し、API アクセスと認証情報を設定します。",
"Name, provider type, and availability.": "名前、プロバイダー種別、利用可否。",
"name@example.com": "name@example.com",
"Native format": "ネイティブ形式",
"Need a redemption code?": "引き換えコードが必要ですか?",
@ -3102,6 +3104,7 @@
"Public rankings page based on live usage data.": "実際の利用データに基づく公開ランキングページ。",
"Publish Date": "公開日",
"Published": "公開済み",
"Published models, groups, and model remapping rules.": "公開モデル、グループ、モデルの再マッピングルール。",
"Published:": "公開済み:",
"Pull": "プル",
"Pull model": "モデルをプル",

View File

@ -1413,6 +1413,7 @@
"Endpoint config": "Конфигурация конечной точки",
"Endpoint Configuration": "Конфигурация конечной точки",
"Endpoint Type": "Тип конечной точки",
"Endpoint, provider-specific settings, and credentials.": "Эндпоинт, настройки провайдера и учетные данные.",
"Endpoint:": "Конечная точка:",
"Endpoints": "Конечные точки",
"English": "Английский",
@ -1716,15 +1717,15 @@
"Filled {{count}} model(s)": "Заполнено {{count}} моделей",
"Filled {{count}} related model(s)": "Заполнено {{count}} связанных моделей",
"Filter": "Фильтр",
"Filter by API key...": "Фильтр по API-ключу...",
"Filter by channel ID": "Фильтр по ID канала",
"Filter by group": "Фильтр по группе",
"Filter by Midjourney task ID": "Фильтр по ID задачи Midjourney",
"Filter by model name...": "Фильтр по имени модели...",
"Filter by model...": "Фильтровать по модели...",
"Filter by API key...": "Фильтр по API-ключу...",
"Filter by name or ID...": "Фильтр по имени или ID...",
"Filter by name...": "Фильтр по имени...",
"Filter by name, ID, or key...": "Фильтровать по имени, ID или ключу...",
"Filter by name...": "Фильтр по имени...",
"Filter by price field": "Фильтр по полю цены",
"Filter by ratio type": "Фильтровать по типу коэффициента",
"Filter by request ID": "Фильтр по ID запроса",
@ -1770,7 +1771,7 @@
"footer.columns.related.links.oneApi": "Один API",
"footer.columns.related.title": "Связанные проекты",
"footer.defaultCopyright": "Все права защищены.",
"footer.newapi.projectAttributionSuffix": "Все права защищены. Разработано участниками проекта.",
"footer.new\u0061pi.projectAttributionSuffix": "Все права защищены. Разработано участниками проекта.",
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "Для каналов, добавленных после 10 мая 2025 г., не нужно удалять \".\" из имён моделей при развёртывании",
"For private deployments, format: https://fastgpt.run/api/openapi": "Для частных развертываний, формат: https://fastgpt.run/api/openapi",
"Force a syntactically valid JSON response": "Принудительно возвращать синтаксически корректный JSON",
@ -2453,6 +2454,7 @@
"Name Suffix": "Суффикс имени",
"Name the channel and choose the upstream provider.": "Задайте имя канала и выберите upstream-провайдера.",
"Name the channel, choose the provider, configure API access, and set credentials.": "Задайте имя канала, выберите провайдера, настройте доступ к API и учетные данные.",
"Name, provider type, and availability.": "Название, тип провайдера и доступность.",
"name@example.com": "name@example.com",
"Native format": "Собственный формат",
"Need a redemption code?": "Нужен код активации?",
@ -3102,6 +3104,7 @@
"Public rankings page based on live usage data.": "Публичная страница рейтингов на основе реальных данных использования.",
"Publish Date": "Дата публикации",
"Published": "Опубликовано",
"Published models, groups, and model remapping rules.": "Опубликованные модели, группы и правила переназначения моделей.",
"Published:": "Опубликовано:",
"Pull": "Загрузить",
"Pull model": "Загрузить модель",

View File

@ -1413,6 +1413,7 @@
"Endpoint config": "Cấu hình điểm cuối",
"Endpoint Configuration": "Cấu hình điểm cuối",
"Endpoint Type": "Loại điểm cuối",
"Endpoint, provider-specific settings, and credentials.": "Endpoint, cài đặt riêng của nhà cung cấp và thông tin xác thực.",
"Endpoint:": "Điểm cuối:",
"Endpoints": "Điểm cuối",
"English": "Tiếng Anh",
@ -1716,15 +1717,15 @@
"Filled {{count}} model(s)": "Đã điền {{count}} mô hình",
"Filled {{count}} related model(s)": "Đã điền {{count}} mô hình liên quan",
"Filter": "Lọc",
"Filter by API key...": "Lọc theo khóa API...",
"Filter by channel ID": "Lọc theo ID kênh",
"Filter by group": "Lọc theo nhóm",
"Filter by Midjourney task ID": "Lọc theo ID nhiệm vụ Midjourney",
"Filter by model name...": "Lọc theo tên mô hình...",
"Filter by model...": "Lọc theo mẫu...",
"Filter by API key...": "Lọc theo khóa API...",
"Filter by name or ID...": "Lọc theo tên hoặc ID...",
"Filter by name...": "Lọc theo tên...",
"Filter by name, ID, or key...": "Lọc theo tên, ID hoặc khóa...",
"Filter by name...": "Lọc theo tên...",
"Filter by price field": "Lọc theo trường giá",
"Filter by ratio type": "Lọc theo loại tỷ lệ",
"Filter by request ID": "Lọc theo ID yêu cầu",
@ -1770,7 +1771,7 @@
"footer.columns.related.links.oneApi": "One API",
"footer.columns.related.title": "Các Dự Án Liên Quan",
"footer.defaultCopyright": "Bản quyền được bảo lưu.",
"footer.newapi.projectAttributionSuffix": "Bản quyền được bảo lưu. Được thiết kế và phát triển bởi các cộng tác viên dự án.",
"footer.new\u0061pi.projectAttributionSuffix": "Bản quyền được bảo lưu. Được thiết kế và phát triển bởi các cộng tác viên dự án.",
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "Đối với các kênh được thêm sau ngày 10 tháng 5 năm 2025, không cần loại bỏ \".\" khỏi tên mô hình trong quá trình triển khai",
"For private deployments, format: https://fastgpt.run/api/openapi": "Đối với các triển khai riêng tư, định dạng: https://fastgpt.run/api/openapi",
"Force a syntactically valid JSON response": "Buộc phản hồi JSON hợp lệ về cú pháp",
@ -2453,6 +2454,7 @@
"Name Suffix": "Hậu tố tên",
"Name the channel and choose the upstream provider.": "Đặt tên kênh và chọn nhà cung cấp upstream.",
"Name the channel, choose the provider, configure API access, and set credentials.": "Đặt tên kênh, chọn nhà cung cấp, cấu hình truy cập API và thiết lập thông tin xác thực.",
"Name, provider type, and availability.": "Tên, loại nhà cung cấp và trạng thái khả dụng.",
"name@example.com": "name@example.com",
"Native format": "Định dạng gốc",
"Need a redemption code?": "Cần mã đổi thưởng?",
@ -3102,6 +3104,7 @@
"Public rankings page based on live usage data.": "Trang bảng xếp hạng công khai dựa trên dữ liệu sử dụng thực.",
"Publish Date": "Ngày xuất bản",
"Published": "Đã xuất bản",
"Published models, groups, and model remapping rules.": "Các mô hình đã xuất bản, nhóm và quy tắc ánh xạ lại mô hình.",
"Published:": "Đã xuất bản:",
"Pull": "Tải",
"Pull model": "Tải mô hình",

View File

@ -1413,6 +1413,7 @@
"Endpoint config": "端点配置",
"Endpoint Configuration": "端点配置",
"Endpoint Type": "端点类型",
"Endpoint, provider-specific settings, and credentials.": "接口地址、供应商专属设置和凭据。",
"Endpoint:": "端点:",
"Endpoints": "端点",
"English": "英文",
@ -1716,15 +1717,15 @@
"Filled {{count}} model(s)": "已填充 {{count}} 个模型",
"Filled {{count}} related model(s)": "已填充 {{count}} 个关联模型",
"Filter": "筛选",
"Filter by API key...": "按 API 密钥筛选...",
"Filter by channel ID": "按通道 ID 筛选",
"Filter by group": "按分组筛选",
"Filter by Midjourney task ID": "按 Midjourney 任务 ID 筛选",
"Filter by model name...": "按模型名称筛选...",
"Filter by model...": "按模型筛选...",
"Filter by API key...": "按 API 密钥筛选...",
"Filter by name or ID...": "按名称或 ID 筛选...",
"Filter by name...": "按名称筛选...",
"Filter by name, ID, or key...": "按名称、ID 或密钥筛选...",
"Filter by name...": "按名称筛选...",
"Filter by price field": "按价格字段筛选",
"Filter by ratio type": "按倍率类型筛选",
"Filter by request ID": "按请求 ID 筛选",
@ -1770,7 +1771,7 @@
"footer.columns.related.links.oneApi": "One API",
"footer.columns.related.title": "相关项目",
"footer.defaultCopyright": "版权所有。",
"footer.newapi.projectAttributionSuffix": "版权所有,由项目贡献者设计与开发。",
"footer.new\u0061pi.projectAttributionSuffix": "版权所有,由项目贡献者设计与开发。",
"For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "对于 2025 年 5 月 10 日之后添加的渠道,在部署时无需从模型名称中移除 \".\"",
"For private deployments, format: https://fastgpt.run/api/openapi": "对于私有部署格式为https://fastgpt.run/api/openapi",
"Force a syntactically valid JSON response": "强制返回语法合法的 JSON",
@ -2453,6 +2454,7 @@
"Name Suffix": "名称后缀",
"Name the channel and choose the upstream provider.": "命名渠道并选择上游供应商。",
"Name the channel, choose the provider, configure API access, and set credentials.": "命名渠道、选择供应商、配置 API 访问并设置凭据。",
"Name, provider type, and availability.": "名称、供应商类型和可用状态。",
"name@example.com": "name@example.com",
"Native format": "原生格式",
"Need a redemption code?": "需要兑换码?",
@ -3102,6 +3104,7 @@
"Public rankings page based on live usage data.": "基于真实用量数据的公开排行榜页面。",
"Publish Date": "发布日期",
"Published": "已发布",
"Published models, groups, and model remapping rules.": "已发布的模型、分组和模型重映射规则。",
"Published:": "已发布:",
"Pull": "拉取",
"Pull model": "拉取模型",