feat: thêm soundfont và VSTi cho MIDI

This commit is contained in:
2026-07-23 17:47:46 +07:00
parent 0b2382573f
commit 225f23516f
16 changed files with 1232 additions and 60 deletions
+96
View File
@@ -3132,6 +3132,87 @@ const AIConfigModal = ({
className: "px-4 py-1.5 bg-cyan-600 hover:bg-cyan-500 text-white font-semibold text-xs rounded-lg shadow transition"
}, loading ? 'Đang lưu...' : 'Lưu Cấu Hình AI'))))));
};
const PluginManagerModal = ({ isOpen, onClose, pluginsData }) => {
if (!isOpen) return null;
const [localData, setLocalData] = React.useState(pluginsData);
const [sfUploadStatus, setSfUploadStatus] = React.useState('');
React.useEffect(() => {
if (isOpen && !localData) {
window.SonicAPI.listPlugins()
.then(data => setLocalData(data))
.catch(() => setLocalData({ vst_instruments: [], soundfonts: [] }));
}
}, [isOpen]);
const handleUploadSF = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
setSfUploadStatus('Uploading...');
try {
const result = await window.SonicAPI.uploadSoundFont(file);
setSfUploadStatus('Uploaded: ' + result.name);
// Refresh plugin list
const data = await window.SonicAPI.listPlugins();
setLocalData(data);
} catch (err) {
setSfUploadStatus('Error: ' + err.message);
}
};
const vsts = localData?.vst_instruments || [];
const sfs = localData?.soundfonts || [];
return React.createElement('div', {
className: 'fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm',
onClick: onClose
}, React.createElement('div', {
className: 'bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200',
onClick: e => e.stopPropagation()
}, React.createElement('div', {
className: 'flex justify-between items-center pb-4 border-b border-[#383838]'
}, React.createElement('h3', {
className: 'text-lg font-bold text-cyan-400'
}, 'Plugin Manager (SoundFont / VSTi)'), React.createElement('button', {
onClick: onClose,
className: 'text-slate-400 hover:text-slate-200'
}, '✕')), React.createElement('div', {
className: 'mt-4 space-y-4'
}, React.createElement('div', null, React.createElement('h4', {
className: 'text-sm font-bold text-purple-400 mb-2 uppercase'
}, 'VST Instruments'), vsts.length === 0 ? React.createElement('p', {
className: 'text-xs text-zinc-500'
}, 'No VST instruments available on server.') : React.createElement('div', {
className: 'grid grid-cols-1 gap-1'
}, vsts.map((v, i) => React.createElement('div', {
key: i,
className: 'flex items-center justify-between bg-[#1e1e1e] px-3 py-2 rounded border border-[#333]'
}, React.createElement('span', {
className: 'text-xs font-semibold text-slate-200'
}, v.id), React.createElement('span', {
className: 'text-[10px] text-cyan-400 bg-cyan-950/40 px-1.5 py-0.5 rounded'
}, v.type))))), React.createElement('div', null, React.createElement('h4', {
className: 'text-sm font-bold text-amber-400 mb-2 uppercase'
}, 'SoundFonts'), sfs.length === 0 ? React.createElement('p', {
className: 'text-xs text-zinc-500'
}, 'No SoundFonts available.') : React.createElement('div', {
className: 'space-y-1'
}, sfs.map((sf, i) => React.createElement('div', {
key: i,
className: 'flex items-center justify-between bg-[#1e1e1e] px-3 py-2 rounded border border-[#333]'
}, React.createElement('span', {
className: 'text-xs text-slate-200'
}, sf.name))))), React.createElement('div', {
className: 'pt-4 border-t border-[#383838]'
}, React.createElement('h4', {
className: 'text-sm font-bold text-emerald-400 mb-2 uppercase'
}, 'Upload SoundFont'), React.createElement('input', {
type: 'file',
accept: '.sf2,.sf3',
onChange: handleUploadSF,
className: 'w-full text-xs text-zinc-400 file:mr-2 file:py-1 file:px-3 file:rounded file:border-0 file:text-xs file:font-semibold file:bg-emerald-700 file:text-white hover:file:bg-emerald-600'
}), sfUploadStatus && React.createElement('p', {
className: 'text-xs mt-1 text-zinc-400'
}, sfUploadStatus))))));
};
const ProfileModal = ({
isOpen,
onClose,
@@ -4998,6 +5079,8 @@ const App = () => {
const [profileModalOpen, setProfileModalOpen] = useState(false);
const [systemManagerModalOpen, setSystemManagerModalOpen] = useState(false);
const [aiConfigModalOpen, setAiConfigModalOpen] = useState(false);
const [pluginManagerModalOpen, setPluginManagerModalOpen] = useState(false);
const [pluginsData, setPluginsData] = useState(null);
useEffect(() => {
const checkAuthStatus = async () => {
const savedToken = localStorage.getItem('sonic_token');
@@ -11216,6 +11299,15 @@ const App = () => {
label: 'DSP Tools Panel',
icon: 'wrench',
action: () => openPanel('python_tools')
}, {
sep: true
}, {
label: 'Plugin Manager (SoundFont/VSTi)',
icon: 'zap',
action: () => {
setPluginManagerModalOpen(true);
window.SonicAPI.listPlugins().then(data => setPluginsData(data)).catch(() => {});
}
}]
}, {
label: 'Help',
@@ -13676,6 +13768,10 @@ const App = () => {
}), /*#__PURE__*/React.createElement(SystemManagerModal, {
isOpen: systemManagerModalOpen,
onClose: () => setSystemManagerModalOpen(false)
}), /*#__PURE__*/React.createElement(PluginManagerModal, {
isOpen: pluginManagerModalOpen,
onClose: () => setPluginManagerModalOpen(false),
pluginsData: pluginsData
}));
};
const root = ReactDOM.createRoot(document.getElementById('root'));