perf(json-editor): unify admin JSON editing experience (#6421)
* perf(json-editor): improve JSON editing experience - integrate Yace for syntax highlighting, history, indentation, auto-closing, and smart line breaks. - add copy support, cursor location feedback, and synchronized content and line-number scrolling. - extract JSON editor utilities and cover key interactions with unit tests. * perf(system-settings): improve JSON configuration editing - replace raw JSON textareas with the shared editor for highlighting, validation, copy, and formatting. - preserve field-specific examples and make placeholders visible through the transparent editor layer. - remove duplicate formatting controls while keeping existing form validation and save behavior. * perf(json-editor): standardize JSON inputs across admin settings - replace pure JSON textareas with the shared editor across system settings and channel workflows. - preserve form focus, validation, placeholders, and visual or JSON editing modes. - add happy-dom component coverage for form bindings, controlled updates, and formatting. * fix(json-code-editor): address accessibility review findings - Drop the unconditional aria-label that overrode every field's label-derived accessible name; add an optional ariaLabel prop and set it at call sites without an associated label - Associate standalone Labels via htmlFor/id in channel-affinity views - Hide the highlight mirror and line-number layers from the accessibility tree (aria-hidden) - Give the line-number gutter an opaque background so horizontally scrolled code no longer slides under it - Degrade to no scroll sync instead of destroying the editor when the line-number layer is not found
This commit is contained in:
@@ -33,6 +33,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -54,7 +55,6 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -461,12 +461,6 @@ export function AdvancedCustomEditorDialog({
|
||||
if (jsonError) setJsonError('')
|
||||
}
|
||||
|
||||
const formatJson = () => {
|
||||
const parsed = parseJsonEditorConfig()
|
||||
if (!parsed) return
|
||||
setJsonText(stringifyAdvancedCustomConfig(parsed))
|
||||
}
|
||||
|
||||
const applyTemplate = (mode: 'fill' | 'append') => {
|
||||
const templateConfig = getAdvancedCustomTemplateConfig(templateKey)
|
||||
let nextConfig = templateConfig
|
||||
@@ -713,26 +707,19 @@ export function AdvancedCustomEditorDialog({
|
||||
) : (
|
||||
<div className='p-4'>
|
||||
<div className='mb-2 flex items-center gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={formatJson}
|
||||
>
|
||||
{t('Format')}
|
||||
</Button>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
{t('Advanced text editing')}
|
||||
</span>
|
||||
</div>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={jsonText}
|
||||
onChange={(event) => handleJsonChange(event.target.value)}
|
||||
onChange={handleJsonChange}
|
||||
placeholder={stringifyAdvancedCustomConfig(
|
||||
getAdvancedCustomTemplateConfig(templateKey)
|
||||
)}
|
||||
rows={22}
|
||||
className='min-h-[420px] font-mono text-xs'
|
||||
heightClassName='h-[420px] min-h-[420px] max-h-[420px]'
|
||||
aria-invalid={Boolean(jsonError)}
|
||||
ariaLabel={t('Advanced text editing')}
|
||||
/>
|
||||
<p className='text-muted-foreground mt-2 text-xs'>
|
||||
{t('Edit JSON text directly. Format will be validated on save.')}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { toast } from 'sonner'
|
||||
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { GroupBadge } from '@/components/group-badge'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -38,7 +39,6 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
editTagChannels,
|
||||
@@ -370,13 +370,12 @@ export function EditTagDialog({ open, onOpenChange }: EditTagDialogProps) {
|
||||
{t('(Optional: redirect model names)')}
|
||||
</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
id='model-mapping'
|
||||
value={modelMapping}
|
||||
onChange={(e) => setModelMapping(e.target.value)}
|
||||
onChange={setModelMapping}
|
||||
placeholder={'{\n "gpt-3.5-turbo": "gpt-3.5-turbo-0125"\n}'}
|
||||
rows={4}
|
||||
className='font-mono text-sm'
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
|
||||
@@ -37,6 +37,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -1656,17 +1657,6 @@ export function ParamOverrideEditorDialog(
|
||||
[t]
|
||||
)
|
||||
|
||||
const formatJson = useCallback(() => {
|
||||
const trimmed = jsonText.trim()
|
||||
if (!trimmed) return
|
||||
if (!verifyJSON(trimmed)) {
|
||||
toast.error(t('Parameter override must be valid JSON format'))
|
||||
return
|
||||
}
|
||||
setJsonText(JSON.stringify(JSON.parse(trimmed), null, 2))
|
||||
setJsonError('')
|
||||
}, [jsonText, t])
|
||||
|
||||
const visualValidationError = useMemo(() => {
|
||||
if (editMode !== 'visual') return ''
|
||||
try {
|
||||
@@ -1832,12 +1822,12 @@ export function ParamOverrideEditorDialog(
|
||||
<p className='text-muted-foreground mb-2 text-sm'>
|
||||
{t('Legacy Format (JSON Object)')}
|
||||
</p>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={legacyValue}
|
||||
onChange={(e) => setLegacyValue(e.target.value)}
|
||||
onChange={setLegacyValue}
|
||||
placeholder={JSON.stringify(LEGACY_TEMPLATE, null, 2)}
|
||||
rows={14}
|
||||
className='font-mono text-xs'
|
||||
heightClassName='h-72 min-h-72 max-h-72'
|
||||
ariaLabel={t('Legacy Format (JSON Object)')}
|
||||
/>
|
||||
<p className='text-muted-foreground mt-2 text-xs'>
|
||||
{t(
|
||||
@@ -2044,24 +2034,17 @@ export function ParamOverrideEditorDialog(
|
||||
/* JSON mode */
|
||||
<div className='p-4'>
|
||||
<div className='mb-2 flex items-center gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={formatJson}
|
||||
>
|
||||
{t('Format')}
|
||||
</Button>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
{t('Advanced text editing')}
|
||||
</span>
|
||||
</div>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={jsonText}
|
||||
onChange={(e) => handleJsonChange(e.target.value)}
|
||||
onChange={handleJsonChange}
|
||||
placeholder={JSON.stringify(OPERATION_TEMPLATE, null, 2)}
|
||||
rows={20}
|
||||
className='font-mono text-xs'
|
||||
heightClassName='h-[420px] min-h-[420px] max-h-[420px]'
|
||||
aria-invalid={Boolean(jsonError)}
|
||||
ariaLabel={t('Advanced text editing')}
|
||||
/>
|
||||
<p className='text-muted-foreground mt-2 text-xs'>
|
||||
{t('Edit JSON text directly. Format will be validated on save.')}
|
||||
|
||||
@@ -63,6 +63,7 @@ import {
|
||||
sideDrawerSectionClassName,
|
||||
sideDrawerSwitchItemClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { JsonEditor } from '@/components/json-editor'
|
||||
import { MultiSelect } from '@/components/multi-select'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
@@ -3912,17 +3913,18 @@ export function ChannelMutateDrawer({
|
||||
</div>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
disabled={
|
||||
sensitiveLocked || isSubmitting
|
||||
}
|
||||
rows={8}
|
||||
placeholder={t(
|
||||
'Override request parameters. Cannot override stream parameter.'
|
||||
)}
|
||||
className='max-h-72 min-h-40 resize-y overflow-auto font-mono text-xs'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -3984,25 +3986,6 @@ export function ChannelMutateDrawer({
|
||||
>
|
||||
{t('Passthrough Template')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
try {
|
||||
const parsed = JSON.parse(
|
||||
field.value || '{}'
|
||||
)
|
||||
field.onChange(
|
||||
JSON.stringify(parsed, null, 2)
|
||||
)
|
||||
} catch {
|
||||
/* ignore invalid JSON */
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('Format')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
@@ -4014,17 +3997,19 @@ export function ChannelMutateDrawer({
|
||||
</div>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className='font-mono text-sm'
|
||||
rows={6}
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
disabled={
|
||||
sensitiveLocked || isSubmitting
|
||||
}
|
||||
placeholder={t(
|
||||
'Enter JSON to override request headers'
|
||||
)}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className='text-xs'>
|
||||
|
||||
@@ -20,12 +20,11 @@ import { Code, Plus, Table, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useId, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type ModelMappingEditorProps = {
|
||||
value: string
|
||||
@@ -327,17 +326,14 @@ export function ModelMappingEditor(props: ModelMappingEditorProps) {
|
||||
</Button>
|
||||
</TabsContent>
|
||||
<TabsContent value='json'>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={jsonValue}
|
||||
onChange={(e) => handleJsonChange(e.target.value)}
|
||||
onChange={handleJsonChange}
|
||||
placeholder={t('{"original-model": "replacement-model"}')}
|
||||
disabled={props.disabled}
|
||||
rows={8}
|
||||
className={cn(
|
||||
'font-mono text-sm',
|
||||
jsonError && 'border-destructive'
|
||||
)}
|
||||
className={jsonError ? 'border-destructive' : undefined}
|
||||
aria-invalid={Boolean(jsonError)}
|
||||
ariaLabel={t('Model Mapping')}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
sideDrawerFormClassName,
|
||||
sideDrawerHeaderClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { MultiSelect } from '@/components/multi-select'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -59,7 +60,6 @@ import {
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
checkClusterNameAvailability,
|
||||
@@ -702,10 +702,14 @@ export function CreateDeploymentDrawer({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Environment variables (JSON)')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className='min-h-24 font-mono text-xs'
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='{"KEY":"VALUE"}'
|
||||
{...field}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -722,10 +726,14 @@ export function CreateDeploymentDrawer({
|
||||
{t('Secret environment variables (JSON)')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className='min-h-24 font-mono text-xs'
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='{"SECRET":"VALUE"}'
|
||||
{...field}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
@@ -26,6 +26,7 @@ import { toast } from 'sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -41,7 +42,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { getDeployment, updateDeployment } from '../../api'
|
||||
import { deploymentsQueryKeys } from '../../lib'
|
||||
@@ -398,10 +398,14 @@ export function UpdateConfigDialog({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Env (JSON object)')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className='min-h-40 font-mono text-xs'
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='{"KEY":"VALUE"}'
|
||||
{...field}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -415,10 +419,14 @@ export function UpdateConfigDialog({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Secret env (JSON object)')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className='min-h-40 font-mono text-xs'
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='{"SECRET":"VALUE"}'
|
||||
{...field}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
||||
+8
-4
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { CopyButton } from '@/components/copy-button'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -45,7 +46,6 @@ import {
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -604,12 +604,16 @@ export function ProviderFormDialog(props: ProviderFormDialogProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Access Policy (JSON)')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={t(
|
||||
'Optional JSON policy to restrict access based on user info fields'
|
||||
)}
|
||||
className='min-h-[80px] font-mono text-xs'
|
||||
{...field}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -32,7 +33,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { SettingsForm } from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
@@ -172,12 +172,17 @@ export function ChatSettingsSection({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Chat configuration JSON')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={12}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={t(
|
||||
'[{"ChatGPT":"https://chat.openai.com"},{"Lobe Chat":"https://chat-preview.lobehub.com/?settings={...}"}]'
|
||||
)}
|
||||
{...field}
|
||||
heightClassName='h-72 min-h-72 max-h-72'
|
||||
aria-invalid={Boolean(form.formState.errors.Chats)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -32,7 +33,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { SettingsAccordion } from '../components/settings-accordion'
|
||||
import {
|
||||
@@ -199,7 +199,16 @@ export function JsonToggleSection({
|
||||
<FormItem>
|
||||
<FormLabel>{textareaLabel}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={12} placeholder={placeholder} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={placeholder}
|
||||
heightClassName='h-72 min-h-72 max-h-72'
|
||||
aria-invalid={Boolean(form.formState.errors.json)}
|
||||
/>
|
||||
</FormControl>
|
||||
{textareaDescription && (
|
||||
<FormDescription>{t(textareaDescription)}</FormDescription>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { toast } from 'sonner'
|
||||
|
||||
import { StaticDataTable } from '@/components/data-table'
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { StatusBadge, StatusBadgeList } from '@/components/status-badge'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { SettingsSwitchField } from '../../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../../components/settings-page-context'
|
||||
@@ -661,11 +661,14 @@ export function ChannelAffinitySection(props: Props) {
|
||||
/>
|
||||
) : (
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Rules JSON')}</Label>
|
||||
<Textarea
|
||||
className='min-h-[300px] font-mono text-xs'
|
||||
<Label htmlFor='channel-affinity-rules-json'>
|
||||
{t('Rules JSON')}
|
||||
</Label>
|
||||
<JsonCodeEditor
|
||||
id='channel-affinity-rules-json'
|
||||
value={jsonText}
|
||||
onChange={(e) => setJsonText(e.target.value)}
|
||||
onChange={setJsonText}
|
||||
heightClassName='h-[300px] min-h-[300px] max-h-[300px]'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Dialog } from '@/components/dialog'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Collapsible,
|
||||
@@ -134,6 +135,9 @@ export function RuleEditorDialog(props: Props) {
|
||||
param_override_template_json: '',
|
||||
},
|
||||
})
|
||||
const paramOverrideTemplateField = form.register(
|
||||
'param_override_template_json'
|
||||
)
|
||||
|
||||
const resetFromRule = (r: Partial<AffinityRule>) => {
|
||||
form.reset({
|
||||
@@ -434,12 +438,30 @@ export function RuleEditorDialog(props: Props) {
|
||||
</div>
|
||||
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Parameter Override Template (JSON)')}</Label>
|
||||
<Textarea
|
||||
rows={5}
|
||||
<Label htmlFor='channel-affinity-param-override-template'>
|
||||
{t('Parameter Override Template (JSON)')}
|
||||
</Label>
|
||||
<JsonCodeEditor
|
||||
id='channel-affinity-param-override-template'
|
||||
value={form.watch('param_override_template_json') || ''}
|
||||
onChange={(value) =>
|
||||
form.setValue('param_override_template_json', value, {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
name={paramOverrideTemplateField.name}
|
||||
onBlur={() => {
|
||||
void paramOverrideTemplateField.onBlur({
|
||||
target: {
|
||||
name: paramOverrideTemplateField.name,
|
||||
value: form.getValues('param_override_template_json'),
|
||||
},
|
||||
type: 'blur',
|
||||
})
|
||||
}}
|
||||
textareaRef={paramOverrideTemplateField.ref}
|
||||
placeholder='{"operations": [...]}'
|
||||
{...form.register('param_override_template_json')}
|
||||
className='font-mono text-xs'
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { RiskAcknowledgementDialog } from '@/components/risk-acknowledgement-dialog'
|
||||
import {
|
||||
Alert,
|
||||
@@ -45,7 +46,6 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import { confirmPaymentCompliance } from '../api'
|
||||
@@ -984,15 +984,19 @@ export function PaymentSettingsSection({
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<Textarea
|
||||
rows={4}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={t(
|
||||
'[{"name":"支付宝","type":"alipay","icon":"SiAlipay"}]'
|
||||
)}
|
||||
{...field}
|
||||
onChange={(event) =>
|
||||
field.onChange(event.target.value)
|
||||
}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.PayMethods
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
@@ -1045,13 +1049,17 @@ export function PaymentSettingsSection({
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<Textarea
|
||||
rows={4}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='[10, 20, 50, 100]'
|
||||
{...field}
|
||||
onChange={(event) =>
|
||||
field.onChange(event.target.value)
|
||||
}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.AmountOptions
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
@@ -1101,13 +1109,17 @@ export function PaymentSettingsSection({
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<Textarea
|
||||
rows={4}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='{"100":0.95,"200":0.9}'
|
||||
{...field}
|
||||
onChange={(event) =>
|
||||
field.onChange(event.target.value)
|
||||
}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.AmountDiscount
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
@@ -1568,13 +1580,17 @@ export function PaymentSettingsSection({
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<Textarea
|
||||
rows={4}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder='[{"name":"Basic","productId":"prod_xxx","price":10,"quota":500000,"currency":"USD"}]'
|
||||
{...field}
|
||||
onChange={(event) =>
|
||||
field.onChange(event.target.value)
|
||||
}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.CreemProducts
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -34,7 +35,6 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -196,7 +196,16 @@ export function ClaudeSettingsCard({ defaultValues }: ClaudeSettingsCardProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Request Header Overrides')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.claude?.model_headers_settings
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -215,7 +224,16 @@ export function ClaudeSettingsCard({ defaultValues }: ClaudeSettingsCardProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Default Max Tokens')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.claude?.default_max_tokens
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Example')}{' '}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -34,7 +35,6 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -248,7 +248,16 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Safety Settings')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.gemini?.safety_settings
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -267,7 +276,16 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Version Overrides')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.gemini?.version_settings
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -286,10 +304,17 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Supported Imagine Models')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={6}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={imaginePlaceholder}
|
||||
{...field}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.gemini?.supported_imagine_models
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -38,7 +39,6 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -157,21 +157,6 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
|
||||
const pingEnabled = form.watch('general_setting.ping_interval_enabled')
|
||||
|
||||
const formatJsonField = (
|
||||
field:
|
||||
| 'global.thinking_model_blacklist'
|
||||
| 'global.chat_completions_to_responses_policy'
|
||||
) => {
|
||||
const raw = form.getValues(field)
|
||||
if (!raw || !raw.trim()) return
|
||||
try {
|
||||
const formatted = JSON.stringify(JSON.parse(raw), null, 2)
|
||||
form.setValue(field, formatted, { shouldDirty: true })
|
||||
} catch {
|
||||
toast.error(t('Invalid JSON format'))
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async (values: GlobalModelSettingsFormValues) => {
|
||||
const flattenedDefaults = flattenGlobalValues(defaultValues)
|
||||
const flattenedValues = flattenGlobalValues(values)
|
||||
@@ -233,11 +218,14 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
{t('Models that skip thinking suffix processing')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={4}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={`${t('Example:')}\n${thinkingBlacklistExample}`}
|
||||
{...field}
|
||||
onChange={(event) => field.onChange(event.target.value)}
|
||||
heightClassName='h-32 min-h-32 max-h-32'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
@@ -245,18 +233,6 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
'Models listed here will not automatically append or remove -thinking / -nothinking suffixes.'
|
||||
)}
|
||||
</FormDescription>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() =>
|
||||
formatJsonField('global.thinking_model_blacklist')
|
||||
}
|
||||
>
|
||||
{t('Format JSON')}
|
||||
</Button>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -292,11 +268,13 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('Policy JSON')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={8}
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={`${t('Example (specific channels):')}\n${chatToResponsesPolicyExample}\n\n${t('Example (all channels):')}\n${chatToResponsesPolicyAllChannelsExample}`}
|
||||
{...field}
|
||||
onChange={(event) => field.onChange(event.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
@@ -331,18 +309,6 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
>
|
||||
{t('Fill example (all channels)')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='sm'
|
||||
onClick={() =>
|
||||
formatJsonField(
|
||||
'global.chat_completions_to_responses_policy'
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('Format JSON')}
|
||||
</Button>
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
sideDrawerFormClassName,
|
||||
sideDrawerHeaderClassName,
|
||||
} from '@/components/drawer-layout'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@@ -50,7 +51,6 @@ import {
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -215,7 +215,13 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Group ratios')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -234,7 +240,14 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Top-up group ratios')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={6} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -254,7 +267,14 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Selectable groups')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={6} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -273,7 +293,13 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Inter-group overrides')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Nested JSON: source group →')}{' '}
|
||||
@@ -294,7 +320,14 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Auto assignment order')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={6} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
heightClassName='h-40 min-h-40 max-h-40'
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@@ -313,7 +346,13 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('Special usable group rules')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea rows={8} {...field} />
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
|
||||
@@ -149,6 +149,9 @@ function ModelJsonTextareaField(props: {
|
||||
<JsonCodeEditor
|
||||
value={field.value}
|
||||
onChange={(value) => field.onChange(value)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription className='text-xs leading-5'>
|
||||
|
||||
@@ -22,10 +22,10 @@ import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { StaticDataTable } from '@/components/data-table'
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@@ -308,12 +308,11 @@ export const ToolPriceSettings = memo(function ToolPriceSettings({
|
||||
/>
|
||||
) : (
|
||||
<div className='space-y-2'>
|
||||
<Textarea
|
||||
<JsonCodeEditor
|
||||
value={jsonText}
|
||||
onChange={(e) => handleJsonChange(e.target.value)}
|
||||
className='font-mono text-sm'
|
||||
rows={12}
|
||||
spellCheck={false}
|
||||
onChange={handleJsonChange}
|
||||
heightClassName='h-72 min-h-72 max-h-72'
|
||||
aria-invalid={Boolean(jsonError)}
|
||||
/>
|
||||
{jsonError && <p className='text-destructive text-sm'>{jsonError}</p>}
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { JsonCodeEditor } from '@/components/json-code-editor'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -273,11 +273,16 @@ export function RateLimitSection({ defaultValues }: RateLimitSectionProps) {
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
) : (
|
||||
<Textarea
|
||||
rows={8}
|
||||
<JsonCodeEditor
|
||||
value={field.value || ''}
|
||||
onChange={field.onChange}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
textareaRef={field.ref}
|
||||
placeholder={`{\n "default": [200, 100],\n "vip": [0, 1000]\n}`}
|
||||
className='font-mono text-sm'
|
||||
{...field}
|
||||
aria-invalid={Boolean(
|
||||
form.formState.errors.ModelRequestRateLimitGroup
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
Reference in New Issue
Block a user