Spaces:
Running
Running
File size: 8,360 Bytes
a8df197 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
import React, { useState, useRef, useEffect } from 'react';
import { Model } from '../types';
interface CreateModelModalProps {
isOpen: boolean;
onClose: () => void;
onSave: (model: Model) => void;
onDelete?: (id: string) => void;
editingModel?: Model | null;
}
const CreateModelModal: React.FC<CreateModelModalProps> = ({ isOpen, onClose, onSave, onDelete, editingModel }) => {
const [name, setName] = useState('');
const [image, setImage] = useState<File | string | null>(null);
const [audio, setAudio] = useState<File | null>(null);
const [gender, setGender] = useState<'male' | 'female'>('male');
const imgInputRef = useRef<HTMLInputElement>(null);
const audioInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (editingModel) {
setName(editingModel.name);
setImage(editingModel.image);
setAudio(null);
setGender(editingModel.targetGender || 'male');
} else {
setName('');
setImage(null);
setAudio(null);
setGender('male');
}
}, [editingModel, isOpen]);
if (!isOpen) return null;
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) setImage(e.target.files[0]);
};
const handleAudioChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) setAudio(e.target.files[0]);
};
const handleSave = () => {
if (!name || !image || (!audio && !editingModel)) return;
const model: Model = {
id: editingModel?.id || `user_${Date.now()}`,
name,
image: image as any,
refFile: audio || (editingModel?.refFile as any),
category: 'user_custom',
targetGender: gender,
isCustom: true,
type: 'standard'
};
onSave(model);
};
const getImgPreview = () => {
if (!image) return null;
if (typeof image === 'string') return image;
return URL.createObjectURL(image as Blob);
};
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/70 backdrop-blur-md" onClick={onClose}></div>
<div className="relative bg-white rounded-[2.5rem] w-full max-w-[380px] p-6 shadow-2xl animate-[scaleIn_0.3s_ease-out] overflow-hidden">
<div className="text-center mb-6">
<h3 className="text-xl font-black text-gray-800">{editingModel ? 'ویرایش مدل' : 'ساخت مدل صوتی جدید'}</h3>
<p className="text-xs text-gray-400 mt-1">مدل شخصی خود را با هوش مصنوعی آلفا بسازید</p>
</div>
<div className="space-y-5">
{/* Image Upload */}
<div className="flex flex-col items-center">
<div
onClick={() => imgInputRef.current?.click()}
className="w-24 h-24 rounded-3xl border-2 border-dashed border-gray-200 overflow-hidden cursor-pointer hover:border-primary transition-all relative group"
>
{image ? (
<img src={getImgPreview()!} className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex flex-col items-center justify-center bg-gray-50">
<i className="fas fa-image text-gray-300 text-2xl"></i>
<span className="text-[10px] text-gray-400 mt-1">عکس</span>
</div>
)}
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 flex items-center justify-center transition-opacity">
<i className="fas fa-camera text-white"></i>
</div>
</div>
<input type="file" ref={imgInputRef} hidden accept="image/*" onChange={handleImageChange} />
</div>
{/* Name Input */}
<div>
<label className="block text-[10px] font-bold text-gray-400 mb-1 mr-2">نام مدل</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="مثلا: صدای رادیویی من"
className="w-full px-4 py-3 rounded-xl border border-gray-100 bg-gray-50 focus:bg-white focus:border-primary outline-none text-sm transition-all"
/>
</div>
{/* Audio Upload */}
<div
onClick={() => audioInputRef.current?.click()}
className={`p-4 rounded-xl border-2 border-dashed transition-all cursor-pointer flex flex-col gap-2 text-right
${audio ? 'border-green-400 bg-green-50' : 'border-gray-100 bg-gray-50 hover:border-primary'}`}
>
<input type="file" ref={audioInputRef} hidden accept="audio/*" onChange={handleAudioChange} />
<div className="flex items-center gap-3">
<div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${audio ? 'bg-green-500 text-white' : 'bg-white text-gray-400 shadow-sm'}`}>
<i className={`fas ${audio ? 'fa-check' : 'fa-microphone'}`}></i>
</div>
<div className="flex-1">
<p className="text-xs font-bold text-gray-700">{audio ? 'فایل صوتی انتخاب شد' : 'آپلود فایل صوتی مرجع'}</p>
<p className="text-[10px] text-gray-400 truncate">{audio ? audio.name : 'انتخاب فایل مدل...'}</p>
</div>
</div>
{!audio && (
<p className="text-[9px] text-gray-400 leading-relaxed mt-1">
(بهترین حالت ۳ تا ۹ ثانیه)دقت کنید کیفیت صدای خروجی به کیفیت این فایل صوتی مدل که اضافه میکنید بستگی داره اگر فایل صوتی این مدل شما با کیفیت بالا بدون نویز باشه در حد استودیو خروجی های این مدل شما نیز با کیفیت خواهد بود
</p>
)}
</div>
{/* Gender Selection */}
<div className="flex gap-2">
<button
onClick={() => setGender('male')}
className={`flex-1 py-2 rounded-xl text-xs font-bold transition-all border
${gender === 'male' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-400 border-gray-100'}`}
>مرد</button>
<button
onClick={() => setGender('female')}
className={`flex-1 py-2 rounded-xl text-xs font-bold transition-all border
${gender === 'female' ? 'bg-pink-600 text-white border-pink-600' : 'bg-white text-gray-400 border-gray-100'}`}
>زن</button>
</div>
<div className="flex gap-2 pt-2">
<button
onClick={handleSave}
disabled={!name || !image || (!audio && !editingModel)}
className="flex-[2] py-4 bg-gradient-to-r from-primary to-secondary text-white rounded-2xl font-black text-sm shadow-xl shadow-primary/20 hover:scale-[1.02] active:scale-95 transition-all disabled:grayscale disabled:opacity-50"
>
{editingModel ? 'بروزرسانی مدل' : 'ساخت و ذخیره مدل'}
</button>
{editingModel && onDelete && (
<button
onClick={() => onDelete(editingModel.id)}
className="flex-1 bg-red-50 text-red-500 rounded-2xl flex items-center justify-center hover:bg-red-100 transition-colors"
>
<i className="fas fa-trash-alt"></i>
</button>
)}
</div>
<button onClick={onClose} className="w-full py-2 text-xs text-gray-400 font-bold">انصراف</button>
</div>
</div>
</div>
);
};
export default CreateModelModal;
|