diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index cbc9a44..9e850a7 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -4544,7 +4544,7 @@ const AIPresetModal = ({ isOpen, onClose }) => { }; const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNotes, onSaveNotes, setSubTabs, onPlayPause, onStop, isPlaying, playPreviewNote, showToast }) => { - const [activeRollTool, setActiveRollTool] = React.useState('select'); + const [activeRollTool, setActiveRollTool] = React.useState('pen'); const [snapVal, setSnapVal] = React.useState('1/16'); const [ccMode, setCcMode] = React.useState('velocity'); const [rollZoom, setRollZoom] = React.useState(60); // local horizontal zoom factor diff --git a/app/static/js/app.precompiled.js b/app/static/js/app.precompiled.js index a1153ad..066093b 100644 --- a/app/static/js/app.precompiled.js +++ b/app/static/js/app.precompiled.js @@ -127,7 +127,7 @@ const[projectsList,setProjectsList]=useState([]);const[loadingProjects,setLoadin const[filesList,setFilesList]=useState([]);const[loadingFiles,setLoadingFiles]=useState(false);// Confirmation modal state const[confirmModal,setConfirmModal]=useState(null);const handleDragStart=e=>{const r=dragRef.current;r.active=true;r.startX=e.clientX;r.startY=e.clientY;r.ofsX=dragOfs.x;r.ofsY=dragOfs.y;const onMove=ev=>{if(!r.active)return;setDragOfs({x:r.ofsX+ev.clientX-r.startX,y:r.ofsY+ev.clientY-r.startY});};const onUp=()=>{r.active=false;document.removeEventListener('mousemove',onMove);document.removeEventListener('mouseup',onUp);};document.addEventListener('mousemove',onMove);document.addEventListener('mouseup',onUp);};useEffect(()=>{if(isOpen){fetchProfile();if(activeTab==='projects')fetchProjects();if(activeTab==='files')fetchFiles();}},[isOpen,activeTab]);const fetchProfile=async()=>{try{const data=await window.SonicAPI.getProfile();setProfile(data);}catch(e){setError(e.message||'Không thể tải thông tin profile');}};const fetchProjects=async()=>{setLoadingProjects(true);try{const data=await window.SonicAPI.listCloudProjects();setProjectsList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách dự án','error');}finally{setLoadingProjects(false);}};const fetchFiles=async()=>{setLoadingFiles(true);try{const activeFileIds=tracks.map(t=>t.serverFileId).filter(Boolean);const data=await window.SonicAPI.listMyFiles(activeFileIds);setFilesList(data||[]);}catch(e){showToast(e.message||'Không thể tải danh sách tệp tin','error');}finally{setLoadingFiles(false);}};const handleOpenProject=async projectId=>{setAppWarningModal({title:"Mở dự án",message:"Bạn có muốn mở dự án này? Các thay đổi chưa lưu trên workspace hiện tại sẽ bị mất.",isAlert:false,onConfirm:async()=>{try{const proj=await window.SonicAPI.getCloudProject(projectId);const parsed=JSON.parse(proj.data_json);let restoredTracks=[];let restoredBpm=bpm;let restoredSessionTabs=[];let restoredSubTabs=[];if(parsed.main_session){const result=deserializeProjectFromSchema(parsed);restoredTracks=result.tracks;restoredBpm=result.bpm;restoredSessionTabs=result.sessionTabs;restoredSubTabs=result.subTabs;}else{restoredTracks=(parsed.tracks||[]).map(t=>{const{height:_h,...rest}=t;return{...rest,buffer:null,channelInfo:t.channelInfo||null,clips:t.clips||[],serverFileId:t.serverFileId||null};});}setTracks(restoredTracks);setBpm(restoredBpm.toString());setSelectedTrackId(restoredTracks[0]?.id||'1');setProjectName(proj.name);setCurrentProjectId(proj.id);if(restoredSessionTabs.length>0){setSessionTabs(restoredSessionTabs);}if(restoredSubTabs.length>0){setSubTabs(restoredSubTabs);}localStorage.setItem('sonic_project_name',proj.name);localStorage.setItem('sonic_project_id',proj.id);showToast(`Đã nạp dự án "${proj.name}" thành công!`,"success");}catch(e){showToast(e.message||"Lỗi khi nạp dự án","error");}}});};const handleDeleteProject=async(projectId,e)=>{e.stopPropagation();setConfirmModal({title:"Xóa dự án Cloud",message:"Bạn có chắc chắn muốn xóa dự án này khỏi Cloud? Hành động này không thể hoàn tác.",onConfirm:async()=>{try{await window.SonicAPI.deleteCloudProject(projectId);showToast("Đã xóa dự án thành công!","success");fetchProjects();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa dự án","error");}}});};const handleDeleteFile=async fileId=>{if(!confirm(`Bạn có chắc chắn muốn xóa tệp tin ${fileId}?`))return;try{await window.SonicAPI.deleteMyFile(fileId);showToast("Đã xóa tệp tin thành công!","success");fetchFiles();fetchProfile();}catch(err){showToast(err.message||"Lỗi khi xóa tệp tin","error");}};const handleCleanUnusedFiles=async()=>{const unusedFiles=filesList.filter(f=>!f.is_in_use);if(unusedFiles.length===0){showToast("Không có tập tin rác nào để dọn dẹp.","info");return;}if(!confirm(`Bạn có chắc chắn muốn xóa tất cả ${unusedFiles.length} tập tin rác để giải phóng dung lượng?`))return;let successCount=0;for(const file of unusedFiles){try{await window.SonicAPI.deleteMyFile(file.file_id);successCount++;}catch(e){console.error("Lỗi xóa file rác: ",file.file_id,e);}}showToast(`Đã dọn dẹp thành công ${successCount}/${unusedFiles.length} tập tin rác!`,"success");fetchFiles();fetchProfile();};const handleChangePassword=async e=>{e.preventDefault();setMsg('');setError('');setLoading(true);try{const res=await window.SonicAPI.changePassword(oldPassword,newPassword);setMsg(res.message||'Đổi mật khẩu thành công!');setOldPassword('');setNewPassword('');}catch(err){setError(err.message||'Lỗi khi đổi mật khẩu');}finally{setLoading(false);}};const modalStyle={left:`calc(50% + ${dragOfs.x}px)`,top:`calc(50% + ${dragOfs.y}px)`,transform:'translate(-50%, -50%)'};return/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{key:"backdrop",className:"fixed inset-0 z-40 bg-black/70 backdrop-blur-sm",onClick:onClose}),confirmModal&&/*#__PURE__*/React.createElement("div",{key:"confirm-overlay",className:"fixed inset-0 z-[60] flex items-center justify-center bg-black/50"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-lg shadow-2xl p-5 max-w-sm w-full text-slate-200"},/*#__PURE__*/React.createElement("h4",{className:"text-sm font-bold text-rose-400 mb-2"},confirmModal.title),/*#__PURE__*/React.createElement("p",{className:"text-xs text-slate-300 mb-4"},confirmModal.message),/*#__PURE__*/React.createElement("div",{className:"flex justify-end gap-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>setConfirmModal(null),className:"px-3 py-1.5 bg-zinc-700 hover:bg-zinc-600 text-slate-300 rounded text-xs font-semibold"},"Hủy"),/*#__PURE__*/React.createElement("button",{onClick:()=>{const fn=confirmModal.onConfirm;setConfirmModal(null);fn();},className:"px-3 py-1.5 bg-rose-700 hover:bg-rose-600 text-white rounded text-xs font-semibold"},"Xác nhận xóa")))),/*#__PURE__*/React.createElement("div",{key:"dialog",className:"fixed z-50 bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200 flex flex-col max-h-[85vh]",style:modalStyle},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-3 border-b border-[#383838] shrink-0 cursor-grab active:cursor-grabbing select-none",onMouseDown:handleDragStart},/*#__PURE__*/React.createElement("h3",{className:"text-md font-bold text-teal-400 flex items-center gap-1.5"},"👤 Hồ Sơ Cá Nhân & Quản Lý Dự Án"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),/*#__PURE__*/React.createElement("div",{className:"flex gap-2 border-b border-[#383838] py-2 shrink-0 text-xs font-semibold"},[/*#__PURE__*/React.createElement("button",{key:"tab-acc",onClick:()=>setActiveTab('account'),className:`px-3 py-1.5 rounded transition ${activeTab==='account'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tài Khoản"),/*#__PURE__*/React.createElement("button",{key:"tab-proj",onClick:()=>setActiveTab('projects'),className:`px-3 py-1.5 rounded transition ${activeTab==='projects'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Dự Án Cloud"),/*#__PURE__*/React.createElement("button",{key:"tab-files",onClick:()=>setActiveTab('files'),className:`px-3 py-1.5 rounded transition ${activeTab==='files'?'bg-teal-950/60 text-teal-300 border border-teal-800':'text-slate-400 hover:text-slate-200'}`},"Tập Tin Của Tôi")]),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto pt-4 space-y-4 pr-1 min-h-[300px]"},activeTab==='account'&&profile?[/*#__PURE__*/React.createElement("div",{key:"quota-info",className:"bg-[#1e1e1e] p-4 rounded-lg border border-[#333] grid grid-cols-2 gap-4 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"username"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Tên người dùng"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-300 text-sm"},profile.username)]),/*#__PURE__*/React.createElement("div",{key:"role"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Vai trò"),/*#__PURE__*/React.createElement("span",{className:"uppercase font-semibold text-amber-400"},profile.role)]),/*#__PURE__*/React.createElement("div",{key:"email"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Email"),/*#__PURE__*/React.createElement("span",null,profile.email)]),/*#__PURE__*/React.createElement("div",{key:"quota"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-500 block"},"Dung lượng Quota"),/*#__PURE__*/React.createElement("span",{className:"font-semibold text-slate-200"},`${profile.quota.used_mb} MB / ${profile.quota.storage_limit_mb} MB`)])]),/*#__PURE__*/React.createElement("div",{key:"progress"},[/*#__PURE__*/React.createElement("div",{className:"flex justify-between text-xs mb-1"},[/*#__PURE__*/React.createElement("span",{className:"text-slate-400"},"Tiến trình sử dụng bộ nhớ Server"),/*#__PURE__*/React.createElement("span",{className:"font-bold text-teal-400"},`${(profile.quota.used_mb/profile.quota.storage_limit_mb*100).toFixed(1)}%`)]),/*#__PURE__*/React.createElement("div",{className:"w-full h-2 bg-slate-800 rounded-full overflow-hidden"},[/*#__PURE__*/React.createElement("div",{className:"h-full bg-teal-500 rounded-full transition-all duration-300",style:{width:`${Math.min(100,profile.quota.used_mb/profile.quota.storage_limit_mb*100)}%`}})])]),/*#__PURE__*/React.createElement("form",{key:"pwd-form",onSubmit:handleChangePassword,className:"pt-4 border-t border-[#383838] space-y-3"},[/*#__PURE__*/React.createElement("h4",{className:"text-xs font-bold text-slate-300 uppercase"},"Thay Đổi Mật Khẩu"),msg&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{key:"old"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu cũ"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"current-password",required:true,value:oldPassword,onChange:e=>setOldPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("div",{key:"new"},[/*#__PURE__*/React.createElement("label",{className:"block text-xs text-slate-400 mb-1"},"Mật khẩu mới"),/*#__PURE__*/React.createElement("input",{type:"password",autoComplete:"new-password",required:true,value:newPassword,onChange:e=>setNewPassword(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs focus:outline-none focus:border-teal-500"})]),/*#__PURE__*/React.createElement("button",{type:"submit",disabled:loading,className:"w-full py-1.5 bg-teal-600 hover:bg-teal-500 text-white font-semibold text-xs rounded transition"},loading?'Đang cập nhật...':'Cập Nhật Mật Khẩu')])]:activeTab==='projects'?[loadingProjects?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách dự án..."):projectsList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Bạn chưa có dự án nào lưu trên Cloud."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[projectsList.map(proj=>/*#__PURE__*/React.createElement("div",{key:proj.id,onClick:()=>handleOpenProject(proj.id),className:"flex items-center justify-between p-3 bg-[#1e1e1e] hover:bg-zinc-800 rounded border border-zinc-800 transition cursor-pointer text-xs group"},[/*#__PURE__*/React.createElement("div",{key:"meta"},[/*#__PURE__*/React.createElement("div",{className:"font-bold text-slate-200 group-hover:text-teal-400"},proj.name),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Dung lượng: ${proj.size_mb} MB | Cập nhật: ${new Date(proj.updated_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-1.5"},[/*#__PURE__*/React.createElement("button",{onClick:e=>{e.stopPropagation();handleOpenProject(proj.id);},className:"px-2.5 py-1 bg-teal-800/80 hover:bg-teal-700 text-teal-200 rounded font-semibold text-[10px]"},"MỞ"),/*#__PURE__*/React.createElement("button",{onClick:e=>handleDeleteProject(proj.id,e),className:"px-2 py-1 bg-rose-950/60 hover:bg-rose-900/80 text-rose-300 rounded font-semibold text-[10px]"},"XÓA")])]))])]:activeTab==='files'?[/*#__PURE__*/React.createElement("div",{key:"cleanup-header",className:"flex items-center justify-between bg-zinc-900/60 p-2.5 rounded border border-zinc-800 text-xs shrink-0 mb-3"},[/*#__PURE__*/React.createElement("span",{className:"text-zinc-400 text-[10px]"},"💡 Các tập tin không dùng trong dự án nào sẽ được đánh dấu 'Không dùng' và có thể xóa để giải phóng dung lượng."),/*#__PURE__*/React.createElement("button",{onClick:handleCleanUnusedFiles,className:"px-2.5 py-1 bg-rose-700 hover:bg-rose-600 text-white rounded font-bold text-[10px] uppercase flex items-center gap-1"},"🧹 Dọn dẹp tệp rác")]),loadingFiles?/*#__PURE__*/React.createElement("div",{key:"loading",className:"text-center py-8 text-xs text-zinc-500"},"Đang tải danh sách tập tin..."):filesList.length===0?/*#__PURE__*/React.createElement("div",{key:"empty",className:"text-center py-8 text-xs text-zinc-500"},"Chưa có tập tin nào tải lên hoặc tạo ra."):/*#__PURE__*/React.createElement("div",{key:"list",className:"space-y-1.5"},[filesList.map(file=>/*#__PURE__*/React.createElement("div",{key:file.file_id,className:"flex items-center justify-between p-3 bg-[#1e1e1e] rounded border border-zinc-800 text-xs"},[/*#__PURE__*/React.createElement("div",{key:"meta",className:"max-w-[70%]"},[/*#__PURE__*/React.createElement("div",{className:"font-semibold text-slate-300 truncate"},file.original_name||file.file_id),/*#__PURE__*/React.createElement("div",{className:"text-[10px] text-zinc-500 mt-0.5"},`Loại: ${file.type} | Dung lượng: ${file.size_mb} MB | Tạo lúc: ${new Date(file.created_at*1000).toLocaleString()}`)]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex items-center gap-2"},[file.is_in_use?/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-emerald-950 text-emerald-400 border border-emerald-900 rounded text-[9px] uppercase font-bold font-mono"},"Đang dùng"):/*#__PURE__*/React.createElement("span",{className:"px-2 py-0.5 bg-zinc-850 text-zinc-400 border border-zinc-700 rounded text-[9px] uppercase font-bold font-mono"},"Không dùng"),!file.is_in_use&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteFile(file.file_id),className:"px-2 py-0.5 bg-rose-950 hover:bg-rose-900 text-rose-300 rounded font-semibold text-[10px] border border-rose-900"},"Xóa")])]))])]:null)));};const SaveProjectModal=({isOpen,onClose,onSave})=>{if(!isOpen)return null;const[name,setName]=useState('');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;onSave(name.trim());onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Đặt tên dự án"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("input",{key:"name-input",type:"text",placeholder:"Nhập tên dự án...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true}),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Lưu")])])));};const SaveAsModal=({isOpen,onClose,projectName,onSaveCloud,onSaveLocal})=>{if(!isOpen)return null;const[name,setName]=useState(projectName||'');const[saveType,setSaveType]=useState('cloud');const handleSubmit=e=>{e.preventDefault();if(!name.trim())return;if(saveType==='cloud'){onSaveCloud(name.trim());}else{onSaveLocal(name.trim());}onClose();};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-md p-6 text-slate-200"},/*#__PURE__*/React.createElement("h3",{className:"text-sm font-bold text-teal-400 mb-4 uppercase"},"Lưu dưới tên khác (Save As...)"),/*#__PURE__*/React.createElement("form",{onSubmit:handleSubmit,className:"space-y-4"},[/*#__PURE__*/React.createElement("div",{key:"name-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1"},"Tên dự án mới"),/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Nhập tên mới...",required:true,value:name,onChange:e=>setName(e.target.value),className:"w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-teal-500 font-bold",autoFocus:true})]),/*#__PURE__*/React.createElement("div",{key:"type-block"},[/*#__PURE__*/React.createElement("label",{className:"block text-[10px] text-zinc-400 uppercase font-bold mb-1.5"},"Phương thức lưu trữ"),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-2 text-xs"},[/*#__PURE__*/React.createElement("button",{key:"btn-cloud",type:"button",onClick:()=>setSaveType('cloud'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='cloud'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"☁️ Lưu Cloud"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Lưu lên server cá nhân")]),/*#__PURE__*/React.createElement("button",{key:"btn-local",type:"button",onClick:()=>setSaveType('local'),className:`py-2 rounded border flex flex-col items-center gap-1 font-semibold transition ${saveType==='local'?'bg-teal-950/60 border-teal-500 text-teal-300':'bg-zinc-800 border-zinc-700 hover:bg-zinc-750 text-zinc-400'}`},[/*#__PURE__*/React.createElement("span",{key:"title"},"💾 Tải về máy (.sfs)"),/*#__PURE__*/React.createElement("span",{key:"desc",className:"text-[9px] font-normal text-zinc-500"},"Tải tệp JSON dự án về máy")])])]),/*#__PURE__*/React.createElement("div",{key:"actions",className:"flex justify-end gap-2 text-xs pt-2"},[/*#__PURE__*/React.createElement("button",{key:"cancel",type:"button",onClick:onClose,className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded"},"Hủy"),/*#__PURE__*/React.createElement("button",{key:"save",type:"submit",className:"px-3 py-1.5 bg-teal-600 hover:bg-teal-500 text-white rounded font-semibold"},"Thực hiện lưu")])])));};const SystemManagerModal=({isOpen,onClose})=>{if(!isOpen)return null;const[users,setUsers]=useState([]);const[loading,setLoading]=useState(true);const[msg,setMsg]=useState('');const[error,setError]=useState('');const[editingQuotaUser,setEditingQuotaUser]=useState(null);const[newQuotaMb,setNewQuotaMb]=useState(500);useEffect(()=>{if(isOpen)loadUsers();},[isOpen]);const loadUsers=async()=>{setLoading(true);setError('');try{const data=await window.SonicAPI.listUsers();setUsers(data);}catch(err){setError(err.message||'Không thể tải danh sách người dùng hệ thống');}finally{setLoading(false);}};const handleSaveQuota=async userId=>{try{await window.SonicAPI.updateUserQuota(userId,parseInt(newQuotaMb));setMsg('Đã cập nhật hạn mức Quota thành công!');setEditingQuotaUser(null);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật Quota');}};const handleToggleRole=async user=>{const nextRole=user.role==='admin'?'standard':'admin';try{await window.SonicAPI.updateUserRole(user.id,nextRole,user.is_active);setMsg(`Đã đổi vai trò người dùng ${user.username} thành ${nextRole}`);loadUsers();}catch(err){setError(err.message||'Lỗi cập nhật vai trò');}};const handleDeleteUser=async userId=>{if(!confirm('Bạn có chắc chắn muốn xóa người dùng này khỏi hệ thống?'))return;try{await window.SonicAPI.deleteUser(userId);setMsg('Đã xóa người dùng thành công');loadUsers();}catch(err){setError(err.message||'Lỗi khi xóa người dùng');}};return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"},/*#__PURE__*/React.createElement("div",{className:"bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-4xl p-6 text-slate-200"},/*#__PURE__*/React.createElement("div",{className:"flex justify-between items-center pb-4 border-b border-[#383838]"},/*#__PURE__*/React.createElement("h3",{className:"text-lg font-bold text-amber-400"},"⚙️ Quản Lý Hệ Thống & Phân Quyền Admin"),/*#__PURE__*/React.createElement("button",{onClick:onClose,className:"text-slate-400 hover:text-slate-200"},"✕")),msg&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs"},msg),error&&/*#__PURE__*/React.createElement("div",{className:"mt-3 p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs"},error),/*#__PURE__*/React.createElement("div",{className:"mt-4 overflow-x-auto max-h-96 no-scrollbar"},loading?/*#__PURE__*/React.createElement("div",{className:"py-8 text-center text-slate-400 text-xs"},"Đang tải thông tin hệ thống..."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",null,/*#__PURE__*/React.createElement("tr",{className:"border-b border-[#383838] text-slate-400 bg-[#1e1e1e]"},/*#__PURE__*/React.createElement("th",{className:"p-3"},"Tên Người Dùng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Email"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Vai Trò"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Dung Lượng Sử Dụng"),/*#__PURE__*/React.createElement("th",{className:"p-3"},"Hạn Mức Quota"),/*#__PURE__*/React.createElement("th",{className:"p-3 text-right"},"Thao Tác"))),/*#__PURE__*/React.createElement("tbody",{className:"divide-y divide-[#333]"},users.map(u=>/*#__PURE__*/React.createElement("tr",{key:u.id,className:"hover:bg-[#2e2e2e]"},/*#__PURE__*/React.createElement("td",{className:"p-3 font-semibold text-teal-300"},u.username,u.must_change_password&&/*#__PURE__*/React.createElement("span",{className:"ml-2 text-xs bg-amber-900/60 text-amber-300 px-1.5 py-0.5 rounded"},"Mật khẩu gốc")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-slate-300"},u.email),/*#__PURE__*/React.createElement("td",{className:"p-3 uppercase font-bold text-amber-400"},u.role),/*#__PURE__*/React.createElement("td",{className:"p-3"},u.used_mb," MB"),/*#__PURE__*/React.createElement("td",{className:"p-3"},editingQuotaUser===u.id?/*#__PURE__*/React.createElement("div",{className:"flex items-center space-x-1"},/*#__PURE__*/React.createElement("input",{type:"number",value:newQuotaMb,onChange:e=>setNewQuotaMb(e.target.value),className:"w-16 bg-[#1e1e1e] border border-[#444] rounded px-1 py-0.5 text-xs text-slate-200"}),/*#__PURE__*/React.createElement("span",null,"MB"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleSaveQuota(u.id),className:"px-2 py-0.5 bg-teal-600 rounded text-xs"},"Lưu")):/*#__PURE__*/React.createElement("span",{className:"font-semibold"},u.quota_mb," MB")),/*#__PURE__*/React.createElement("td",{className:"p-3 text-right space-x-2"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingQuotaUser(u.id);setNewQuotaMb(u.quota_mb);},className:"px-2 py-1 bg-slate-700 hover:bg-slate-600 rounded text-xs"},"Sửa Quota"),/*#__PURE__*/React.createElement("button",{onClick:()=>handleToggleRole(u),className:"px-2 py-1 bg-amber-700/60 hover:bg-amber-600 rounded text-xs"},"Đổi Role"),u.role!=='admin'&&/*#__PURE__*/React.createElement("button",{onClick:()=>handleDeleteUser(u.id),className:"px-2 py-1 bg-red-700/60 hover:bg-red-600 rounded text-xs"},"Xóa")))))))));};const DEFAULT_PRESETS=[{id:"preset_epic_orchestra_intro",name:"Epic Orchestra Intro (8 Bars)",keywords:["epic orchestra","epic orchestral","hoành tráng","nhạc phim epic"],category:"Orchestral / Film Score",default_bars:8,default_bpm:130,default_scale:"C Minor",system_instruction_template:"You are a professional film composer. Create a powerful, dramatic 8-bar orchestral intro. Keep the note density low (e.g. use mostly whole notes, half notes, or quarter notes) and do NOT generate dense 16th notes or complex drum rolls. This is critical to avoid output token limit timeouts. The required structure to return via the `generate_multitrack_midi` tool consists of 3 tracks: 1. Strings: plays smooth legato chord changes (one chord per 1 or 2 bars). 2. Brass Theme: plays a swelling simple melodic line in the C3-C5 range. 3. Epic Percussion: hits heavily on beats 1 and 3. Ensure the duration is precisely 8 bars (32 beats).",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"},{id:"preset_pop_piano_chords",name:"Pop Piano Chords (4 Bars)",keywords:["pop piano","piano chords","ballad piano","hợp âm piano"],category:"Pop / Ballad",default_bars:4,default_bpm:90,default_scale:"C Major",system_instruction_template:"You are a professional Pop Piano player. Generate a beautiful 4-bar piano chord progression (e.g. C - G - Am - F) with pleasant chord voicing and simple accompaniment. Return the MIDI notes via `generate_multitrack_midi` function on a track named 'Pop Piano'. Keep notes simple, using mostly whole/half/quarter notes. Ensure the duration of the track is precisely 4 bars (16 beats).",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"},{id:"preset_cyberpunk_synth",name:"Cyberpunk Synthwave (8 Bars)",keywords:["cyberpunk synth","synthwave","cyberpunk","futuristic synth"],category:"Electronic / Synthwave",default_bars:8,default_bpm:120,default_scale:"A Minor",system_instruction_template:"You are a Synthwave producer. Generate a driving 8-bar cyberpunk synth theme. Return MIDI notes via `generate_multitrack_midi` containing: 1. Synth Bass: eighth notes on pitch A1, C2, G1. 2. Synth Lead: simple melodic line in high register C4-E5. Keep notes clean and concise to ensure fast generation.",is_user_defined:false,created_at:"2026-07-23T16:00:00Z"}];const AIPresetModal=({isOpen,onClose})=>{if(!isOpen)return null;const[presets,setPresets]=React.useState(()=>{const local=localStorage.getItem('daw_ai_prompt_presets');if(!local)return DEFAULT_PRESETS;try{const parsed=JSON.parse(local);const userPresets=parsed.filter(p=>p.is_user_defined);return[...DEFAULT_PRESETS,...userPresets];}catch(_){return DEFAULT_PRESETS;}});const[search,setSearch]=React.useState('');const[filterCategory,setFilterCategory]=React.useState('ALL');const[editingPreset,setEditingPreset]=React.useState(null);// preset object or 'new' // Form states -const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeywords]=React.useState('');const[formCategory,setFormCategory]=React.useState('Orchestral / Film Score');const[formBars,setFormBars]=React.useState(8);const[formBpm,setFormBpm]=React.useState(120);const[formScale,setFormScale]=React.useState('C Minor');const[formTemplate,setFormTemplate]=React.useState('');const savePresets=newPresets=>{setPresets(newPresets);localStorage.setItem('daw_ai_prompt_presets',JSON.stringify(newPresets));};const handleEdit=p=>{setEditingPreset(p);setFormName(p.name);setFormKeywords(p.keywords.join(', '));setFormCategory(p.category);setFormBars(p.default_bars);setFormBpm(p.default_bpm);setFormScale(p.default_scale);setFormTemplate(p.system_instruction_template);};const handleNew=()=>{setEditingPreset('new');setFormName('');setFormKeywords('');setFormCategory('Orchestral / Film Score');setFormBars(8);setFormBpm(120);setFormScale('C Minor');setFormTemplate('');};const handleDelete=id=>{const updated=presets.filter(p=>p.id!==id);savePresets(updated);showToast('Đã xóa preset.','info');};const handleSaveForm=e=>{e.preventDefault();if(!formName.trim()||!formTemplate.trim()){showToast('Vui lòng điền đầy đủ tên và mẫu gợi ý.','warning');return;}const keywordsArray=formKeywords.split(',').map(k=>k.trim()).filter(Boolean);const presetObj={id:editingPreset==='new'?'preset_'+Date.now():editingPreset.id,name:formName.trim(),keywords:keywordsArray,category:formCategory,default_bars:parseInt(formBars)||8,default_bpm:parseInt(formBpm)||120,default_scale:formScale,system_instruction_template:formTemplate.trim(),is_user_defined:true,created_at:editingPreset==='new'?new Date().toISOString():editingPreset.created_at};let updated;if(editingPreset==='new'){updated=[...presets,presetObj];}else{updated=presets.map(p=>p.id===presetObj.id?presetObj:p);}savePresets(updated);setEditingPreset(null);showToast('Đã lưu preset thành công!','success');};const categories=['ALL',...new Set(presets.map(p=>p.category))];const filtered=presets.filter(p=>{const matchesSearch=p.name.toLowerCase().includes(search.toLowerCase())||p.keywords.some(k=>k.toLowerCase().includes(search.toLowerCase()));const matchesCategory=filterCategory==='ALL'||p.category===filterCategory;return matchesSearch&&matchesCategory;});return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4 animate-fade-in"},/*#__PURE__*/React.createElement("div",{className:"bg-[#18181b] border border-zinc-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden text-zinc-100"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-zinc-800 flex items-center justify-between shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("h2",{className:"text-sm font-bold tracking-wider uppercase text-purple-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-4 h-4"}),"AI Prompt Preset Manager"),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"text-zinc-400 hover:text-zinc-200 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto p-4 flex gap-4 min-h-0"},!editingPreset?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 shrink-0"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Tìm kiếm preset hoặc từ khóa...",value:search,onChange:e=>setSearch(e.target.value),className:"flex-1 bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"}),/*#__PURE__*/React.createElement("select",{value:filterCategory,onChange:e=>setFilterCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"},categories.map(c=>/*#__PURE__*/React.createElement("option",{key:c,value:c},c==='ALL'?'Tất cả danh mục':c))),/*#__PURE__*/React.createElement("button",{onClick:handleNew,className:"px-3 py-1 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold flex items-center gap-1.5 shadow transition shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}),"Tạo mới")),/*#__PURE__*/React.createElement("div",{className:"flex-1 border border-zinc-800 rounded bg-[#0f0f12] overflow-y-auto"},filtered.length===0?/*#__PURE__*/React.createElement("div",{className:"p-8 text-center text-zinc-500 text-xs italic"},"Không tìm thấy preset nào."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",{className:"bg-[#1f1f23] text-zinc-400 font-bold border-b border-zinc-800"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Tên Preset"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Từ khóa kích hoạt"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"Số Bar"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"BPM"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6 text-right"},"Hành động"))),/*#__PURE__*/React.createElement("tbody",null,filtered.map(p=>/*#__PURE__*/React.createElement("tr",{key:p.id,className:"border-b border-zinc-800/50 hover:bg-zinc-850"},/*#__PURE__*/React.createElement("td",{className:"p-2.5 font-semibold text-purple-300"},p.name),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400 font-mono text-[11px] truncate max-w-[150px]"},p.keywords.join(', ')),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bars," Bars"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bpm," BPM"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-right flex items-center justify-end gap-1.5 h-full"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleEdit(p),className:"px-2 py-0.5 bg-zinc-850 hover:bg-zinc-700 text-zinc-300 rounded border border-zinc-700 text-[10px]"},"Sửa"),/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleDelete(p.id),className:"px-2 py-0.5 bg-red-950/40 hover:bg-red-800 text-red-400 rounded border border-red-900 text-[10px]"},"Xóa"))))))))):/*#__PURE__*/React.createElement("form",{onSubmit:handleSaveForm,className:"flex-1 flex flex-col gap-3 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 shrink-0 border-b border-zinc-800 pb-1"},editingPreset==='new'?"TẠO PRESET MỚI":`SỬA PRESET: ${editingPreset.name}`),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Tên Preset"),/*#__PURE__*/React.createElement("input",{type:"text",value:formName,onChange:e=>setFormName(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Danh mục"),/*#__PURE__*/React.createElement("input",{type:"text",value:formCategory,onChange:e=>setFormCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Từ khóa kích hoạt (ngăn cách bằng dấu phẩy)"),/*#__PURE__*/React.createElement("input",{type:"text",value:formKeywords,onChange:e=>setFormKeywords(e.target.value),placeholder:"Ví dụ: epic orchestra, hoành tráng, nhạc phim epic",className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Số Bars mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBars,onChange:e=>setFormBars(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"BPM mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBpm,onChange:e=>setFormBpm(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Âm giai (Scale) mặc định"),/*#__PURE__*/React.createElement("input",{type:"text",value:formScale,onChange:e=>setFormScale(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"System Prompt Template / Luật soạn nhạc"),/*#__PURE__*/React.createElement("textarea",{value:formTemplate,onChange:e=>setFormTemplate(e.target.value),rows:6,className:"flex-1 bg-zinc-900 border border-zinc-700 rounded p-2.5 text-xs outline-none focus:border-purple-600 text-zinc-200 font-mono resize-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-2 shrink-0 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>setEditingPreset(null),className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 border border-zinc-700 rounded text-xs transition"},"Quay lại"),/*#__PURE__*/React.createElement("button",{type:"submit",className:"px-3 py-1.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold shadow transition"},"Lưu Preset")))),/*#__PURE__*/React.createElement("div",{className:"p-4 border-t border-zinc-800 flex justify-end shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs font-bold shadow transition"},"Đóng"))));};const PianoRollTabEditor=({st,zoom,bpm,viewportWidth,onClose,onUpdateNotes,onSaveNotes,setSubTabs,onPlayPause,onStop,isPlaying,playPreviewNote,showToast})=>{const[activeRollTool,setActiveRollTool]=React.useState('select');const[snapVal,setSnapVal]=React.useState('1/16');const[ccMode,setCcMode]=React.useState('velocity');const[rollZoom,setRollZoom]=React.useState(60);// local horizontal zoom factor +const[formName,setFormName]=React.useState('');const[formKeywords,setFormKeywords]=React.useState('');const[formCategory,setFormCategory]=React.useState('Orchestral / Film Score');const[formBars,setFormBars]=React.useState(8);const[formBpm,setFormBpm]=React.useState(120);const[formScale,setFormScale]=React.useState('C Minor');const[formTemplate,setFormTemplate]=React.useState('');const savePresets=newPresets=>{setPresets(newPresets);localStorage.setItem('daw_ai_prompt_presets',JSON.stringify(newPresets));};const handleEdit=p=>{setEditingPreset(p);setFormName(p.name);setFormKeywords(p.keywords.join(', '));setFormCategory(p.category);setFormBars(p.default_bars);setFormBpm(p.default_bpm);setFormScale(p.default_scale);setFormTemplate(p.system_instruction_template);};const handleNew=()=>{setEditingPreset('new');setFormName('');setFormKeywords('');setFormCategory('Orchestral / Film Score');setFormBars(8);setFormBpm(120);setFormScale('C Minor');setFormTemplate('');};const handleDelete=id=>{const updated=presets.filter(p=>p.id!==id);savePresets(updated);showToast('Đã xóa preset.','info');};const handleSaveForm=e=>{e.preventDefault();if(!formName.trim()||!formTemplate.trim()){showToast('Vui lòng điền đầy đủ tên và mẫu gợi ý.','warning');return;}const keywordsArray=formKeywords.split(',').map(k=>k.trim()).filter(Boolean);const presetObj={id:editingPreset==='new'?'preset_'+Date.now():editingPreset.id,name:formName.trim(),keywords:keywordsArray,category:formCategory,default_bars:parseInt(formBars)||8,default_bpm:parseInt(formBpm)||120,default_scale:formScale,system_instruction_template:formTemplate.trim(),is_user_defined:true,created_at:editingPreset==='new'?new Date().toISOString():editingPreset.created_at};let updated;if(editingPreset==='new'){updated=[...presets,presetObj];}else{updated=presets.map(p=>p.id===presetObj.id?presetObj:p);}savePresets(updated);setEditingPreset(null);showToast('Đã lưu preset thành công!','success');};const categories=['ALL',...new Set(presets.map(p=>p.category))];const filtered=presets.filter(p=>{const matchesSearch=p.name.toLowerCase().includes(search.toLowerCase())||p.keywords.some(k=>k.toLowerCase().includes(search.toLowerCase()));const matchesCategory=filterCategory==='ALL'||p.category===filterCategory;return matchesSearch&&matchesCategory;});return/*#__PURE__*/React.createElement("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4 animate-fade-in"},/*#__PURE__*/React.createElement("div",{className:"bg-[#18181b] border border-zinc-800 rounded-xl shadow-2xl w-full max-w-4xl max-h-[85vh] flex flex-col overflow-hidden text-zinc-100"},/*#__PURE__*/React.createElement("div",{className:"p-4 border-b border-zinc-800 flex items-center justify-between shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("h2",{className:"text-sm font-bold tracking-wider uppercase text-purple-400 flex items-center gap-1.5"},/*#__PURE__*/React.createElement("i",{"data-lucide":"sliders",className:"w-4 h-4"}),"AI Prompt Preset Manager"),/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"text-zinc-400 hover:text-zinc-200 transition"},/*#__PURE__*/React.createElement("i",{"data-lucide":"x",className:"w-4 h-4"}))),/*#__PURE__*/React.createElement("div",{className:"flex-1 overflow-y-auto p-4 flex gap-4 min-h-0"},!editingPreset?/*#__PURE__*/React.createElement(React.Fragment,null,/*#__PURE__*/React.createElement("div",{className:"flex-1 flex flex-col min-w-0"},/*#__PURE__*/React.createElement("div",{className:"flex items-center gap-2 mb-3 shrink-0"},/*#__PURE__*/React.createElement("input",{type:"text",placeholder:"Tìm kiếm preset hoặc từ khóa...",value:search,onChange:e=>setSearch(e.target.value),className:"flex-1 bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"}),/*#__PURE__*/React.createElement("select",{value:filterCategory,onChange:e=>setFilterCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600"},categories.map(c=>/*#__PURE__*/React.createElement("option",{key:c,value:c},c==='ALL'?'Tất cả danh mục':c))),/*#__PURE__*/React.createElement("button",{onClick:handleNew,className:"px-3 py-1 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold flex items-center gap-1.5 shadow transition shrink-0"},/*#__PURE__*/React.createElement("i",{"data-lucide":"plus",className:"w-3.5 h-3.5"}),"Tạo mới")),/*#__PURE__*/React.createElement("div",{className:"flex-1 border border-zinc-800 rounded bg-[#0f0f12] overflow-y-auto"},filtered.length===0?/*#__PURE__*/React.createElement("div",{className:"p-8 text-center text-zinc-500 text-xs italic"},"Không tìm thấy preset nào."):/*#__PURE__*/React.createElement("table",{className:"w-full text-left text-xs border-collapse"},/*#__PURE__*/React.createElement("thead",{className:"bg-[#1f1f23] text-zinc-400 font-bold border-b border-zinc-800"},/*#__PURE__*/React.createElement("tr",null,/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Tên Preset"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/4"},"Từ khóa kích hoạt"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"Số Bar"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6"},"BPM"),/*#__PURE__*/React.createElement("th",{className:"p-2.5 w-1/6 text-right"},"Hành động"))),/*#__PURE__*/React.createElement("tbody",null,filtered.map(p=>/*#__PURE__*/React.createElement("tr",{key:p.id,className:"border-b border-zinc-800/50 hover:bg-zinc-850"},/*#__PURE__*/React.createElement("td",{className:"p-2.5 font-semibold text-purple-300"},p.name),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400 font-mono text-[11px] truncate max-w-[150px]"},p.keywords.join(', ')),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bars," Bars"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-zinc-400"},p.default_bpm," BPM"),/*#__PURE__*/React.createElement("td",{className:"p-2.5 text-right flex items-center justify-end gap-1.5 h-full"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleEdit(p),className:"px-2 py-0.5 bg-zinc-850 hover:bg-zinc-700 text-zinc-300 rounded border border-zinc-700 text-[10px]"},"Sửa"),/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>handleDelete(p.id),className:"px-2 py-0.5 bg-red-950/40 hover:bg-red-800 text-red-400 rounded border border-red-900 text-[10px]"},"Xóa"))))))))):/*#__PURE__*/React.createElement("form",{onSubmit:handleSaveForm,className:"flex-1 flex flex-col gap-3 min-w-0"},/*#__PURE__*/React.createElement("div",{className:"text-xs font-bold text-zinc-400 shrink-0 border-b border-zinc-800 pb-1"},editingPreset==='new'?"TẠO PRESET MỚI":`SỬA PRESET: ${editingPreset.name}`),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-2 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Tên Preset"),/*#__PURE__*/React.createElement("input",{type:"text",value:formName,onChange:e=>setFormName(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Danh mục"),/*#__PURE__*/React.createElement("input",{type:"text",value:formCategory,onChange:e=>setFormCategory(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Từ khóa kích hoạt (ngăn cách bằng dấu phẩy)"),/*#__PURE__*/React.createElement("input",{type:"text",value:formKeywords,onChange:e=>setFormKeywords(e.target.value),placeholder:"Ví dụ: epic orchestra, hoành tráng, nhạc phim epic",className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"grid grid-cols-3 gap-3"},/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Số Bars mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBars,onChange:e=>setFormBars(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"BPM mặc định"),/*#__PURE__*/React.createElement("input",{type:"number",value:formBpm,onChange:e=>setFormBpm(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"})),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"Âm giai (Scale) mặc định"),/*#__PURE__*/React.createElement("input",{type:"text",value:formScale,onChange:e=>setFormScale(e.target.value),className:"bg-zinc-900 border border-zinc-700 rounded px-2.5 py-1 text-xs outline-none focus:border-purple-600 text-zinc-200"}))),/*#__PURE__*/React.createElement("div",{className:"flex flex-col gap-1 flex-1 min-h-0"},/*#__PURE__*/React.createElement("label",{className:"text-[10px] uppercase font-bold text-zinc-500"},"System Prompt Template / Luật soạn nhạc"),/*#__PURE__*/React.createElement("textarea",{value:formTemplate,onChange:e=>setFormTemplate(e.target.value),rows:6,className:"flex-1 bg-zinc-900 border border-zinc-700 rounded p-2.5 text-xs outline-none focus:border-purple-600 text-zinc-200 font-mono resize-none"})),/*#__PURE__*/React.createElement("div",{className:"flex items-center justify-end gap-2 shrink-0 pt-2 border-t border-zinc-800"},/*#__PURE__*/React.createElement("button",{type:"button",onClick:()=>setEditingPreset(null),className:"px-3 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 border border-zinc-700 rounded text-xs transition"},"Quay lại"),/*#__PURE__*/React.createElement("button",{type:"submit",className:"px-3 py-1.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-xs font-bold shadow transition"},"Lưu Preset")))),/*#__PURE__*/React.createElement("div",{className:"p-4 border-t border-zinc-800 flex justify-end shrink-0 bg-[#202024]"},/*#__PURE__*/React.createElement("button",{onClick:()=>{setEditingPreset(null);onClose();},className:"px-4 py-1.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 border border-zinc-700 rounded text-xs font-bold shadow transition"},"Đóng"))));};const PianoRollTabEditor=({st,zoom,bpm,viewportWidth,onClose,onUpdateNotes,onSaveNotes,setSubTabs,onPlayPause,onStop,isPlaying,playPreviewNote,showToast})=>{const[activeRollTool,setActiveRollTool]=React.useState('pen');const[snapVal,setSnapVal]=React.useState('1/16');const[ccMode,setCcMode]=React.useState('velocity');const[rollZoom,setRollZoom]=React.useState(60);// local horizontal zoom factor const canvasRef=React.useRef(null);const ccCanvasRef=React.useRef(null);const ccWrapperRef=React.useRef(null);const gridScrollRef=React.useRef(null);const keybedRef=React.useRef(null);const keybedMouseDownRef=React.useRef(false);const rulerScrollRef=React.useRef(null);React.useEffect(()=>{const up=()=>{keybedMouseDownRef.current=false;};window.addEventListener('mouseup',up);return()=>window.removeEventListener('mouseup',up);},[]);const NoteHeight=18;const PITCH_START=0;// C0 (render all 128 keys) const KeybedPixelHeight=(128-PITCH_START)*NoteHeight;const pixelsPerBeat=rollZoom;const timeSigNum=4;const noteMaxBeat=(st.notes||[]).reduce((max,n)=>Math.max(max,(n.start_beat||0)+(n.duration_beats||1)),0);const[selectionMarquee,setSelectionMarquee]=React.useState(null);// { startBeat, startPitch, currentBeat, currentPitch } const[draggedNote,setDraggedNote]=React.useState(null);// { mode: 'move'|'resize', idx, startOffsetBeat, originalStart }