/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import { zodResolver } from '@hookform/resolvers/zod'
import { useEffect, useRef, useState } from 'react'
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,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { SettingsForm } from '../components/settings-form-layout'
import { SettingsPageFormActions } from '../components/settings-page-context'
import { SettingsSection } from '../components/settings-section'
import { useUpdateOption } from '../hooks/use-update-option'
import { ChatSettingsVisualEditor } from './chat-settings-visual-editor'
import { formatJsonForEditor, normalizeJsonString } from './utils'
const createChatSchema = (t: (key: string) => string) =>
z.object({
Chats: z.string().superRefine((value, ctx) => {
try {
const parsed = JSON.parse(value || '[]')
if (!Array.isArray(parsed)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t('Expected a JSON array.'),
})
return
}
for (const item of parsed) {
if (
item === null ||
typeof item !== 'object' ||
Array.isArray(item)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t(
'Each item must be an object with a single key-value pair.'
),
})
return
}
const entries = Object.entries(item)
if (entries.length !== 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t('Each item must have exactly one key-value pair.'),
})
return
}
}
} catch {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t('Invalid JSON string.'),
})
}
}),
})
type ChatSettingsFormValues = z.infer>
type ChatSettingsSectionProps = {
defaultValue: string
}
export function ChatSettingsSection({
defaultValue,
}: ChatSettingsSectionProps) {
const { t } = useTranslation()
const updateOption = useUpdateOption()
const [editMode, setEditMode] = useState<'visual' | 'json'>('visual')
const chatSchema = createChatSchema(t)
const formatted = formatJsonForEditor(defaultValue, '[]')
const form = useForm({
resolver: zodResolver(chatSchema),
mode: 'onChange', // Enable real-time validation
defaultValues: {
Chats: formatted,
},
})
const initialNormalizedRef = useRef(normalizeJsonString(defaultValue, '[]'))
useEffect(() => {
form.reset({ Chats: formatJsonForEditor(defaultValue, '[]') })
initialNormalizedRef.current = normalizeJsonString(defaultValue, '[]')
}, [defaultValue, form])
const onSubmit = async (values: ChatSettingsFormValues) => {
const normalized = normalizeJsonString(values.Chats, '[]')
if (normalized === initialNormalizedRef.current) {
return
}
await updateOption.mutateAsync({
key: 'Chats',
value: normalized,
})
}
return (
)
}