Spaces:
Running
Running
import React from 'react'; | |
import Modal from 'react-modal'; | |
import { GoX } from 'react-icons/go'; | |
import { LLMConfig } from '@/api/llmConfigs/types'; | |
import { useQuery } from '@tanstack/react-query'; | |
import { fetchLLMConfigById, fetchDefaultLLMConfig } from '@/api/llmConfigs/llmConfigApi'; | |
import './LlmConfigModal.scss'; | |
interface LLMConfigModalProps { | |
isOpen: boolean; | |
onRequestClose: () => void; | |
config: LLMConfig | null; | |
onSave: (config: LLMConfig | Omit<LLMConfig, 'id' | 'created_at'>) => Promise<void>; | |
isEditMode: boolean; | |
} | |
const customStyles = { | |
content: { | |
top: '40%', | |
left: '50%', | |
right: 'auto', | |
bottom: 'auto', | |
transform: 'translate(-50%, -50%)', | |
borderRadius: '15px', | |
maxWidth: '100%', | |
overflow: 'hidden', | |
}, | |
}; | |
const LLMConfigModal: React.FC<LLMConfigModalProps> = ({ | |
isOpen, | |
onRequestClose, | |
config, | |
onSave, | |
isEditMode, | |
}) => { | |
const [formData, setFormData] = React.useState<LLMConfig | Omit<LLMConfig, 'id' | 'created_at'>>(() => ({ | |
is_default: false, | |
model: '', | |
temperature: 0.7, | |
top_p: 0.95, | |
min_p: 0.05, | |
frequency_penalty: 0, | |
presence_penalty: 0, | |
seed: 42, | |
n_predict: 1000 | |
})); | |
// Запрос конфига для редактирования | |
const { data: serverConfig, isLoading: isConfigLoading } = useQuery({ | |
queryKey: ['llmConfig', config?.id], | |
queryFn: () => fetchLLMConfigById(config!.id), | |
enabled: isOpen && isEditMode && !!config?.id, | |
}); | |
// Запрос дефолтной конфигурации для создания | |
const { data: defaultConfig, isLoading: isDefaultLoading } = useQuery({ | |
queryKey: ['defaultLLMConfig'], | |
queryFn: fetchDefaultLLMConfig, | |
enabled: isOpen && !isEditMode, // Запрашиваем только при создании | |
}); | |
React.useEffect(() => { | |
if (isOpen) { | |
if (isEditMode && serverConfig) { | |
setFormData(serverConfig); // Данные с сервера для редактирования | |
} else if (!isEditMode && defaultConfig) { | |
setFormData(defaultConfig); // Дефолтная запись с сервера для создания | |
} else if (!isEditMode && !defaultConfig && !isDefaultLoading) { | |
setFormData({ // Если дефолтной записи нет и загрузка завершена | |
is_default: true, | |
model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo', | |
temperature: 0.14, | |
top_p: 0.95, | |
min_p: 0.05, | |
frequency_penalty: -0.001, | |
presence_penalty: 1.3, | |
n_predict: 1000, | |
seed: 42, | |
}); | |
} | |
} | |
}, [isOpen, isEditMode, serverConfig, defaultConfig, isDefaultLoading]); | |
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
const { name, value } = e.target; | |
setFormData(prev => ({ | |
...prev, | |
[name]: | |
name === 'is_default' ? e.target.checked : | |
name === 'model' ? value : | |
name === 'seed' ? parseInt(value, 10) || 0 : | |
parseFloat(value) || 0, | |
})); | |
}; | |
const handleSubmit = async (e: React.FormEvent) => { | |
e.preventDefault(); | |
try { | |
await onSave(formData); | |
onRequestClose(); | |
} catch (err) { | |
console.error('Save failed:', err); | |
} | |
}; | |
if (isConfigLoading && isEditMode) { | |
return <div>Loading config...</div>; | |
} | |
if (isDefaultLoading && !isEditMode) { | |
return <div>Loading default config...</div>; | |
} | |
return ( | |
<Modal | |
isOpen={isOpen} | |
onRequestClose={onRequestClose} | |
style={customStyles} | |
overlayClassName="modal-overlay" | |
> | |
<div className="modal-content"> | |
<div className="label"> | |
<h3 className="name">{isEditMode ? 'Редактирование настроек' : 'Новые настройки'}</h3> | |
<button className="close-button" onClick={onRequestClose}> | |
<GoX style={{ height: '25px', width: '25px' }} /> | |
</button> | |
</div> | |
<form onSubmit={handleSubmit}> | |
{isEditMode && 'id' in formData && ( | |
<label> | |
<span className='title'>ID:</span> | |
<input type="text" value={formData.id} disabled /> | |
</label> | |
)} | |
<label> | |
<span className='title'>Задать по-умолчанию:</span> | |
<input | |
type="checkbox" | |
name="is_default" | |
checked={formData.is_default} | |
onChange={handleChange} | |
/> | |
</label> | |
<label> | |
<span className='title'>Модель:</span> | |
<input | |
type="text" | |
name="model" | |
value={formData.model} | |
onChange={handleChange} | |
/> | |
</label> | |
<label> | |
<span className='title'>Temperature:</span> | |
<input | |
type="number" | |
name="temperature" | |
value={formData.temperature} | |
onChange={handleChange} | |
step="0.01" | |
/> | |
</label> | |
<label> | |
<span className='title'>Top P:</span> | |
<input | |
type="number" | |
name="top_p" | |
value={formData.top_p} | |
onChange={handleChange} | |
step="0.01" | |
/> | |
</label> | |
<label> | |
<span className='title'>Min P:</span> | |
<input | |
type="number" | |
name="min_p" | |
value={formData.min_p} | |
onChange={handleChange} | |
step="0.01" | |
/> | |
</label> | |
<label> | |
<span className='title'>Frequency Penalty:</span> | |
<input | |
type="number" | |
name="frequency_penalty" | |
value={formData.frequency_penalty} | |
onChange={handleChange} | |
step="0.01" | |
/> | |
</label> | |
<label> | |
<span className='title'>Presence Penalty:</span> | |
<input | |
type="number" | |
name="presence_penalty" | |
value={formData.presence_penalty} | |
onChange={handleChange} | |
step="0.01" | |
/> | |
</label> | |
<label> | |
<span className='title'>N predict:</span> | |
<input | |
type="number" | |
name="n_predict" | |
value={formData.n_predict} | |
onChange={handleChange} | |
step="1" | |
/> | |
</label> | |
<label> | |
<span className='title'>Seed:</span> | |
<input | |
type="number" | |
name="seed" | |
value={formData.seed} | |
onChange={handleChange} | |
step="1" | |
/> | |
</label> | |
<div className="button-group"> | |
<button type="submit">{isEditMode ? 'Сохранить' : 'Создать'}</button> | |
</div> | |
</form> | |
</div> | |
</Modal> | |
); | |
}; | |
export default LLMConfigModal; |