import { useState, useEffect, useRef } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { IconPlus, IconTrash, IconChevronDown, IconMoodSmile, } from '@tabler/icons-react' import EmojiPicker, { EmojiClickData, Theme } from 'emoji-picker-react' import { Textarea } from '@/components/ui/textarea' import { paramsSettings } from '@/lib/predefinedParams' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { useTheme } from '@/hooks/useTheme' import { teamEmoji } from '@/utils/teamEmoji' import { AvatarEmoji } from '@/containers/AvatarEmoji' import { useTranslation } from '@/i18n/react-i18next-compat' import { cn, isDev } from '@/lib/utils' interface AddEditAssistantProps { open: boolean onOpenChange: (open: boolean) => void editingKey: string | null initialData?: Assistant onSave: (assistant: Assistant) => void } export default function AddEditAssistant({ open, onOpenChange, editingKey, initialData, onSave, }: AddEditAssistantProps) { const [avatar, setAvatar] = useState(initialData?.avatar) const [name, setName] = useState(initialData?.name || '') const [description, setDescription] = useState( initialData?.description ) const [instructions, setInstructions] = useState( initialData?.instructions || '' ) const { isDark } = useTheme() const [paramsKeys, setParamsKeys] = useState(['']) const [paramsValues, setParamsValues] = useState(['']) const [paramsTypes, setParamsTypes] = useState(['string']) const [showEmojiPicker, setShowEmojiPicker] = useState(false) const emojiPickerRef = useRef(null) const emojiPickerTriggerRef = useRef(null) const [nameError, setNameError] = useState(null) const [toolSteps, setToolSteps] = useState(20) // Handle click outside emoji picker or trigger useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( emojiPickerRef.current && emojiPickerTriggerRef.current && !emojiPickerRef.current.contains(event.target as Node) && !emojiPickerTriggerRef.current.contains(event.target as Node) ) { setShowEmojiPicker(false) } } if (showEmojiPicker) { document.addEventListener('mousedown', handleClickOutside) } return () => { document.removeEventListener('mousedown', handleClickOutside) } }, [showEmojiPicker]) // Reset form when modal opens/closes or editing key changes useEffect(() => { if (open && editingKey && initialData) { setAvatar(initialData.avatar) setName(initialData.name) setDescription(initialData.description) setInstructions(initialData.instructions) setShowEmojiPicker(false) setToolSteps(initialData.tool_steps ?? 20) // Convert parameters object to arrays of keys and values const keys = Object.keys(initialData.parameters || {}) const values = Object.values(initialData.parameters || {}) // Determine parameter types based on values const types = values.map((value) => { if (typeof value === 'boolean') return 'boolean' if (typeof value === 'number') return 'number' if (typeof value === 'object') return 'json' return 'string' }) setParamsKeys(keys.length > 0 ? keys : ['']) setParamsValues(values.length > 0 ? values : ['']) setParamsTypes(types.length > 0 ? types : ['string']) } else if (open) { // Add mode - reset form resetForm() } }, [open, editingKey, initialData]) const resetForm = () => { setAvatar(undefined) setName('') setDescription(undefined) setInstructions('') setParamsKeys(['']) setParamsValues(['']) setParamsTypes(['string']) setNameError(null) setShowEmojiPicker(false) setToolSteps(20) } const handleParameterChange = ( index: number, value: unknown, field: 'key' | 'value' | 'type' ) => { if (field === 'key') { const newKeys = [...paramsKeys] newKeys[index] = value as string setParamsKeys(newKeys) } else if (field === 'value') { const newValues = [...paramsValues] // Convert value based on parameter type if (paramsTypes[index] === 'number' && typeof value === 'string') { newValues[index] = value === '' ? '' : Number(value) } else if ( paramsTypes[index] === 'boolean' && typeof value === 'boolean' ) { newValues[index] = value } else if (paramsTypes[index] === 'json' && typeof value === 'string') { try { newValues[index] = value === '' ? {} : JSON.parse(value) } catch { // If JSON is invalid, keep as string newValues[index] = value } } else { newValues[index] = value } setParamsValues(newValues) } else { const newTypes = [...paramsTypes] newTypes[index] = value as string // Reset value based on the new type const newValues = [...paramsValues] if (value === 'string') { newValues[index] = '' } else if (value === 'number') { newValues[index] = '' } else if (value === 'boolean') { newValues[index] = false } else if (value === 'json') { newValues[index] = {} } setParamsValues(newValues) setParamsTypes(newTypes) } } const handleAddParameter = () => { setParamsKeys([...paramsKeys, '']) setParamsValues([...paramsValues, '']) setParamsTypes([...paramsTypes, 'string']) } const handleRemoveParameter = (index: number) => { const newKeys = [...paramsKeys] const newValues = [...paramsValues] const newTypes = [...paramsTypes] newKeys.splice(index, 1) newValues.splice(index, 1) newTypes.splice(index, 1) setParamsKeys(newKeys.length > 0 ? newKeys : ['']) setParamsValues(newValues.length > 0 ? newValues : ['']) setParamsTypes(newTypes.length > 0 ? newTypes : ['string']) } const handleSave = () => { if (!name.trim()) { setNameError(t('assistants:nameRequired')) return } setNameError(null) // Convert parameters arrays to object const parameters: Record = {} paramsKeys.forEach((key, index) => { if (key) { parameters[key] = paramsValues[index] } }) const assistant: Assistant = { avatar, id: initialData?.id || Math.random().toString(36).substring(7), name, created_at: initialData?.created_at || Date.now(), description, instructions, parameters: parameters || {}, tool_steps: toolSteps, } onSave(assistant) onOpenChange(false) resetForm() } const { t } = useTranslation() return ( { e.preventDefault() }} > {editingKey ? t('assistants:editAssistant') : t('assistants:addAssistant')}
setShowEmojiPicker(!showEmojiPicker)} ref={emojiPickerTriggerRef} > {avatar ? ( ) : ( )}
{ // For custom emojis, use the imageUrl instead of the emoji name if (emojiData.isCustom && emojiData.imageUrl) { setAvatar(emojiData.imageUrl) } else { setAvatar(emojiData.emoji) } setShowEmojiPicker(false) }} />
{ setName(e.target.value) if (e.target.value.trim()) setNameError(null) }} placeholder={t('assistants:enterName')} autoFocus />
{nameError && (
{nameError}
)}