feat: add authenticated settings page.

This commit is contained in:
liqupan
2026-02-02 20:12:19 +08:00
parent cb3e16cd16
commit 6c32d845a7
259 changed files with 24685 additions and 0 deletions

View File

@@ -0,0 +1,331 @@
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Checkbox } from '@/components/ui/checkbox';
import { Textarea } from '@/components/ui/textarea';
import { useCharacterDialog } from './character-dialog-context';
import { Character, characterSchema } from '../data/schema';
import { createCharacter, updateCharacter } from '../data/api';
import { useQueryClient } from '@tanstack/react-query';
export function CharacterDialog() {
const { open, setOpen, character } = useCharacterDialog();
const queryClient = useQueryClient();
const isEdit = !!character;
const form = useForm<any>({
resolver: zodResolver(characterSchema),
defaultValues: {
name: '',
status: 'online',
compatibility: 0,
is_active: true,
is_locked: false,
sort_order: 0,
tagline: '',
description: '',
ai_system_prompt: '',
ai_greeting: '',
ai_personality: {},
ai_voice_config: {},
},
});
useEffect(() => {
if (character) {
form.reset(character);
} else {
form.reset({
name: '',
status: 'online',
compatibility: 0,
is_active: true,
is_locked: false,
sort_order: 0,
tagline: '',
description: '',
ai_system_prompt: '',
ai_greeting: '',
});
}
}, [character, form, open]);
const onSubmit = async (values: Character) => {
console.log('🚀 表单提交触发');
console.log('📝 表单值:', values);
console.log('✅ 表单验证状态:', form.formState.isValid);
console.log('❌ 表单错误:', form.formState.errors);
try {
// 确保 JSONB 字段有默认值
const sanitizedValues = {
...values,
ai_personality: values.ai_personality || {},
ai_voice_config: values.ai_voice_config || {},
};
console.log('🔧 处理后的数据:', sanitizedValues);
if (isEdit) {
console.log('🔄 更新角色ID:', character.id);
await updateCharacter({ ...sanitizedValues, id: character.id });
toast.success('角色更新成功');
} else {
console.log(' 创建新角色');
await createCharacter(sanitizedValues);
toast.success('角色创建成功');
}
setOpen(false);
queryClient.invalidateQueries({ queryKey: ['characters'] });
} catch (error) {
console.error('❌ 保存角色失败,详细错误:', error);
// 改进错误信息显示
const errorMessage = error instanceof Error
? error.message
: JSON.stringify(error);
toast.error(`保存角色失败: ${errorMessage}`);
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='max-h-[85vh] overflow-y-auto sm:max-w-2xl'>
<DialogHeader>
<DialogTitle>{isEdit ? '编辑角色' : '创建角色'}</DialogTitle>
<DialogDescription>
{isEdit
? '在下方修改角色详情。'
: '填写详情以创建新角色。'}
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form
onSubmit={(e) => {
console.log('📋 Form onSubmit 事件触发', e);
console.log('📊 当前表单状态:', form.formState);
console.log('❌ 表单错误:', form.formState.errors);
console.log('📝 当前表单值:', form.getValues());
form.handleSubmit(
onSubmit,
(errors) => {
console.error('🚫 表单验证失败:', errors);
toast.error('请检查表单输入');
}
)(e);
}}
className='space-y-4'
>
<div className='grid grid-cols-2 gap-4'>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder='角色名称' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='status'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder='选择状态' />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value='online'>线</SelectItem>
<SelectItem value='busy'></SelectItem>
<SelectItem value='offline'>线</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name='tagline'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder='简短描述' {...field} value={field.value || ''} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='description'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Textarea placeholder='完整角色描述' {...field} value={field.value || ''} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='grid grid-cols-2 gap-4'>
<FormField
control={form.control}
name='compatibility'
render={({ field }) => (
<FormItem>
<FormLabel> (%)</FormLabel>
<FormControl>
<Input
type='number'
min={0}
max={100}
{...field}
onChange={e => field.onChange(parseInt(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='sort_order'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input
type='number'
{...field}
onChange={e => field.onChange(parseInt(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className='flex gap-4'>
<FormField
control={form.control}
name='is_active'
render={({ field }) => (
<FormItem className='flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4'>
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className='space-y-1 leading-none'>
<FormLabel></FormLabel>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name='is_locked'
render={({ field }) => (
<FormItem className='flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4'>
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className='space-y-1 leading-none'>
<FormLabel> ()</FormLabel>
</div>
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name='ai_system_prompt'
render={({ field }) => (
<FormItem>
<FormLabel> (System Prompt)</FormLabel>
<FormControl>
<Textarea
placeholder='你是一个乐于助人的助手...'
className='min-h-[100px]'
{...field}
value={field.value || ''}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='ai_greeting'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder='你好!' {...field} value={field.value || ''} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='flex justify-end gap-2'>
<Button type='button' variant='outline' onClick={() => setOpen(false)}>
</Button>
<Button
type='submit'
disabled={form.formState.isSubmitting}
onClick={() => console.log('💾 保存按钮被点击')}
>
{form.formState.isSubmitting ? '保存中...' : '保存'}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
}