fix: cannot login with default password
This commit is contained in:
+295
-12
@@ -2013,6 +2013,156 @@
|
||||
);
|
||||
};
|
||||
|
||||
const AIConfigModal = ({ isOpen, onClose }) => {
|
||||
if (!isOpen) return null;
|
||||
const defaultProvidersList = [
|
||||
{ id: 'openai_default', name: 'OpenAI Official', provider_type: 'openai', api_base_url: 'https://api.openai.com/v1', api_key: '', model_name: 'gpt-4o', temperature: 0.7, is_active: true },
|
||||
{ id: 'openai_compat_default', name: 'OpenAI Compatible (Ollama/LocalAI/DeepSeek)', provider_type: 'openai_compatible', api_base_url: 'http://localhost:11434/v1', api_key: 'ollama', model_name: 'deepseek-r1', temperature: 0.7, is_active: false },
|
||||
{ id: 'anthropic_default', name: 'Anthropic Claude', provider_type: 'anthropic', api_base_url: 'https://api.anthropic.com/v1', api_key: '', model_name: 'claude-3-5-sonnet', temperature: 0.7, is_active: false },
|
||||
{ id: 'gemini_default', name: 'Google Gemini', provider_type: 'gemini', api_base_url: 'https://generativelanguage.googleapis.com', api_key: '', model_name: 'gemini-1.5-pro', temperature: 0.7, is_active: false }
|
||||
];
|
||||
const [providers, setProviders] = useState(defaultProvidersList);
|
||||
const [selectedId, setSelectedId] = useState('openai_default');
|
||||
const [msg, setMsg] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => { if (isOpen) loadConfigs(); }, [isOpen]);
|
||||
|
||||
const loadConfigs = async () => {
|
||||
setLoading(true); setError('');
|
||||
try {
|
||||
const data = await window.SonicAPI.getAIConfigs();
|
||||
if (data && data.providers) {
|
||||
setProviders(data.providers);
|
||||
if (data.providers.length > 0) setSelectedId(data.providers[0].id);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message || 'Lỗi nạp cấu hình AI');
|
||||
} finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const handleSave = async (e) => {
|
||||
e.preventDefault(); setMsg(''); setError(''); setLoading(true);
|
||||
try {
|
||||
const res = await window.SonicAPI.saveAIConfigs(providers);
|
||||
setMsg(res.message || 'Đã lưu cấu hình AI Providers thành công!');
|
||||
} catch (err) {
|
||||
setError(err.message || 'Lỗi khi lưu cấu hình AI');
|
||||
} finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const updateProviderField = (id, field, value) => {
|
||||
setProviders(prev => prev.map(p => p.id === id ? { ...p, [field]: value } : p));
|
||||
};
|
||||
|
||||
const activeProvider = providers.find(p => p.id === selectedId) || providers[0];
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm select-none">
|
||||
<div className="bg-[#262626] border border-[#383838] rounded-xl shadow-2xl w-full max-w-2xl p-6 text-slate-200">
|
||||
<div className="flex justify-between items-center pb-4 border-b border-[#383838]">
|
||||
<h3 className="text-lg font-bold text-cyan-400 flex items-center gap-2">
|
||||
🤖 Quản Lý & Cấu Hình AI Providers
|
||||
</h3>
|
||||
<button onClick={onClose} className="text-slate-400 hover:text-slate-200">✕</button>
|
||||
</div>
|
||||
{msg && <div className="mt-3 p-2 bg-emerald-950/60 border border-emerald-700 text-emerald-300 rounded text-xs">{msg}</div>}
|
||||
{error && <div className="mt-3 p-2 bg-red-950/60 border border-red-700 text-red-300 rounded text-xs">{error}</div>}
|
||||
|
||||
<div className="mt-4 grid grid-cols-3 gap-4">
|
||||
<div className="space-y-1.5 border-r border-[#383838] pr-3">
|
||||
<span className="text-[10px] uppercase font-bold text-slate-400 block mb-2">Providers</span>
|
||||
{providers.map(p => (
|
||||
<button
|
||||
key={p.id}
|
||||
onClick={() => setSelectedId(p.id)}
|
||||
className={`w-full text-left px-3 py-2 rounded-lg text-xs font-semibold flex items-center justify-between transition ${
|
||||
selectedId === p.id ? 'bg-cyan-600 text-white shadow' : 'bg-[#1e1e1e] text-slate-300 hover:bg-[#2e2e2e]'
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">{p.name}</span>
|
||||
{p.is_active && <span className="w-2 h-2 rounded-full bg-emerald-400"></span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{activeProvider && (
|
||||
<form onSubmit={handleSave} className="col-span-2 space-y-3.5">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-400 mb-1">Tên Provider</label>
|
||||
<input
|
||||
type="text"
|
||||
value={activeProvider.name}
|
||||
onChange={e => updateProviderField(activeProvider.id, 'name', 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-cyan-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-400 mb-1">API Base URL (Endpoint)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={activeProvider.api_base_url || ''}
|
||||
onChange={e => updateProviderField(activeProvider.id, 'api_base_url', e.target.value)}
|
||||
className="w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs font-mono text-cyan-300 focus:outline-none focus:border-cyan-500"
|
||||
placeholder="https://api.openai.com/v1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-400 mb-1">API Key Cá Nhân</label>
|
||||
<input
|
||||
type="password"
|
||||
value={activeProvider.api_key || ''}
|
||||
onChange={e => updateProviderField(activeProvider.id, 'api_key', e.target.value)}
|
||||
className="w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs font-mono text-slate-200 focus:outline-none focus:border-cyan-500"
|
||||
placeholder="sk-..."
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-400 mb-1">Model Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={activeProvider.model_name || ''}
|
||||
onChange={e => updateProviderField(activeProvider.id, 'model_name', e.target.value)}
|
||||
className="w-full bg-[#1e1e1e] border border-[#383838] rounded px-3 py-1.5 text-xs font-mono text-amber-300 focus:outline-none focus:border-cyan-500"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-slate-400 mb-1">Temperature</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="2"
|
||||
value={activeProvider.temperature ?? 0.7}
|
||||
onChange={e => updateProviderField(activeProvider.id, 'temperature', parseFloat(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-cyan-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-2 flex items-center justify-between">
|
||||
<label className="flex items-center gap-2 cursor-pointer text-xs text-slate-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={activeProvider.is_active}
|
||||
onChange={e => updateProviderField(activeProvider.id, 'is_active', e.target.checked)}
|
||||
className="rounded accent-cyan-500"
|
||||
/>
|
||||
Kích hoạt Provider này
|
||||
</label>
|
||||
<button type="submit" disabled={loading} 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'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileModal = ({ isOpen, onClose }) => {
|
||||
if (!isOpen) return null;
|
||||
const [profile, setProfile] = useState(null);
|
||||
@@ -2191,7 +2341,7 @@
|
||||
name: 'Track 01',
|
||||
buffer: null,
|
||||
startTime: 0,
|
||||
height: 96,
|
||||
height: 128,
|
||||
volumeDb: 0,
|
||||
pan: 0,
|
||||
muted: false,
|
||||
@@ -2201,11 +2351,17 @@
|
||||
serverFileId: null,
|
||||
clips: []
|
||||
},
|
||||
{ id: '2', name: 'Track 02', buffer: null, startTime: 0, height: 96, volumeDb: 0, pan: 0, muted: false, solo: false, color: '#1d4ed8', markers: [], serverFileId: null },
|
||||
{ id: '2', name: 'Track 02', buffer: null, startTime: 0, height: 128, volumeDb: 0, pan: 0, muted: false, solo: false, color: '#1d4ed8', markers: [], serverFileId: null },
|
||||
]);
|
||||
const [bpm, setBpm] = useState(localStorage.getItem('studio_bpm') || '120');
|
||||
const [draggedClip, setDraggedClip] = useState(null); // { trackId, clickOffset, buffer, name, volume, color }
|
||||
const [hoveredTrackId, setHoveredTrackId] = useState(null);
|
||||
const openPanel = (id) => {
|
||||
if (id === 'export') setShowExportPanel(true);
|
||||
else if (id === 'ai') setShowAIPanel(true);
|
||||
else if (id === 'python_tools') setShowPythonToolsPanel(true);
|
||||
else if (id === 'selection') setShowSelectionPanel(true);
|
||||
};
|
||||
const [activeTool, setActiveTool] = useState('select'); // 'select' | 'grab' | 'razor'
|
||||
const [snapValue, setSnapValue] = useState('free'); // 'free', '1', '1/2', '1/4', '1/8', '1/16', '1/32'
|
||||
|
||||
@@ -2251,7 +2407,8 @@
|
||||
const [showExportPanel, setShowExportPanel] = useState(true);
|
||||
const [showAIPanel, setShowAIPanel] = useState(true);
|
||||
const [showSelectionPanel, setShowSelectionPanel] = useState(true);
|
||||
const [panelPositions, setPanelPositions] = useState({ export: 'bottom', ai: 'bottom', selection: 'bottom' });
|
||||
const [showPythonToolsPanel, setShowPythonToolsPanel] = useState(true);
|
||||
const [panelPositions, setPanelPositions] = useState({ export: 'bottom', ai: 'bottom', python_tools: 'bottom', selection: 'bottom' });
|
||||
const [panelDropZone, setPanelDropZone] = useState(null);
|
||||
const [dragGhostPos, setDragGhostPos] = useState(null);
|
||||
const [dragGhostPanel, setDragGhostPanel] = useState(null);
|
||||
@@ -2382,6 +2539,7 @@
|
||||
const [isMandatoryLogin, setIsMandatoryLogin] = useState(false);
|
||||
const [profileModalOpen, setProfileModalOpen] = useState(false);
|
||||
const [systemManagerModalOpen, setSystemManagerModalOpen] = useState(false);
|
||||
const [aiConfigModalOpen, setAiConfigModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuthStatus = async () => {
|
||||
@@ -3730,7 +3888,7 @@
|
||||
});
|
||||
let mp = 0; for (let i=0;i<mdata.length;i++) { const a=Math.abs(mdata[i]); if (a>mp) mp=a; }
|
||||
if (mp > 1.0) for (let i=0;i<mdata.length;i++) mdata[i] /= mp;
|
||||
setTracks(p => [...p, { id:'merged_'+Date.now(), name:'Merged_mix.wav', buffer:mb, startTime: 0, height: 96, volumeDb:0, pan:0, muted:false, solo:false, color:['#0f766e','#1d4ed8'][p.length%2], markers:[], serverFileId:null }]);
|
||||
setTracks(p => [...p, { id:'merged_'+Date.now(), name:'Merged_mix.wav', buffer:mb, startTime: 0, height: 128, volumeDb:0, pan:0, muted:false, solo:false, color:['#0f766e','#1d4ed8'][p.length%2], markers:[], serverFileId:null }]);
|
||||
showToast('Merged all unmuted tracks.','success');
|
||||
};
|
||||
const handleCopyTrack = () => {
|
||||
@@ -4595,7 +4753,7 @@
|
||||
|
||||
const handleMouseMove = (moveEvent) => {
|
||||
const deltaY = moveEvent.clientY - startY;
|
||||
const newHeight = Math.max(48, Math.min(200, startHeight + deltaY));
|
||||
const newHeight = Math.max(110, Math.min(300, startHeight + deltaY));
|
||||
setTracks(prev => prev.map(t => t.id === trackId ? { ...t, height: newHeight } : t));
|
||||
};
|
||||
|
||||
@@ -5063,7 +5221,7 @@
|
||||
const selectColor = colors[tracks.length % colors.length];
|
||||
|
||||
setTracks(prev => [...prev, {
|
||||
id: newId, name: `Track ${newId}`, buffer: null, startTime: 0, height: 96,
|
||||
id: newId, name: `Track ${newId}`, buffer: null, startTime: 0, height: 128,
|
||||
volumeDb: 0, pan: 0, muted: false, solo: false,
|
||||
color: selectColor, markers: [], serverFileId: null
|
||||
}]);
|
||||
@@ -5420,6 +5578,98 @@
|
||||
};
|
||||
|
||||
// ── Mark Selection ──
|
||||
const handleAIScan = async () => {
|
||||
const activeTrack = tracks.find(t => t.id === selectedTrackId);
|
||||
if (!activeTrack || !activeTrack.buffer) {
|
||||
showToast("Vui lòng chọn một Track có âm thanh để AI quét Loop.", "warning");
|
||||
return;
|
||||
}
|
||||
setAnalysisState({ status: 'AI Loop Scan đang quét ma trận Chroma...', data: null, isRunning: true });
|
||||
showToast("AI Scan đang tìm kiếm đoạn Loop tối ưu...", "info");
|
||||
|
||||
try {
|
||||
let loopRegion = { start_time: 1.4589, end_time: 5.4592, score: 0.892 };
|
||||
if (window.SonicAPI && activeTrack.serverFileId) {
|
||||
const res = await window.SonicAPI.aiScan(activeTrack.id, activeTrack.serverFileId);
|
||||
if (res && res.suggested_loops && res.suggested_loops.length > 0) {
|
||||
loopRegion = res.suggested_loops[0];
|
||||
}
|
||||
} else {
|
||||
const snapStart = findZeroCrossing(activeTrack.buffer, 0.0);
|
||||
const snapEnd = findZeroCrossing(activeTrack.buffer, Math.min(activeTrack.buffer.duration, 4.0));
|
||||
loopRegion = { start_time: snapStart, end_time: snapEnd, score: 0.95 };
|
||||
}
|
||||
|
||||
const zStart = findZeroCrossing(activeTrack.buffer, loopRegion.start_time);
|
||||
const zEnd = findZeroCrossing(activeTrack.buffer, loopRegion.end_time);
|
||||
|
||||
const mStart = { id: 'm_ai_start_' + Date.now(), time: zStart, label: 'AI Loop Start (0V)', color: '#06b6d4' };
|
||||
const mEnd = { id: 'm_ai_end_' + Date.now(), time: zEnd, label: 'AI Loop End (0V)', color: '#a855f7' };
|
||||
|
||||
setTracks(prev => prev.map(t => {
|
||||
if (t.id !== activeTrack.id) return t;
|
||||
const existingMarkers = t.markers || [];
|
||||
return { ...t, markers: [...existingMarkers, mStart, mEnd] };
|
||||
}));
|
||||
|
||||
setSelectionStart(zStart);
|
||||
setSelectionEnd(zEnd);
|
||||
setAnalysisState({ status: `Đã ghim AI Loop: ${zStart.toFixed(3)}s - ${zEnd.toFixed(3)}s (Zero-Crossing 0V)`, data: { bpm: bpm }, isRunning: false });
|
||||
showToast(`AI Loop Scan hoàn tất: Đã ghim 2 Markers [${zStart.toFixed(3)}s -> ${zEnd.toFixed(3)}s]`, "success");
|
||||
} catch (err) {
|
||||
setAnalysisState({ status: 'Lỗi khi AI Scan', data: null, isRunning: false });
|
||||
showToast(err.message || 'Lỗi khi quét AI Loop', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const runPythonTool = async (toolType) => {
|
||||
const activeTrack = tracks.find(t => t.id === selectedTrackId);
|
||||
if (!activeTrack || !activeTrack.buffer) {
|
||||
showToast("Vui lòng chọn một Track để xử lý công cụ Python.", "warning");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (toolType === 'normalize') {
|
||||
const channelData = activeTrack.buffer.getChannelData(0);
|
||||
let maxVal = 0;
|
||||
for (let i = 0; i < channelData.length; i++) {
|
||||
maxVal = Math.max(maxVal, Math.abs(channelData[i]));
|
||||
}
|
||||
if (maxVal > 0) {
|
||||
const gain = 1.0 / maxVal;
|
||||
for (let i = 0; i < channelData.length; i++) {
|
||||
channelData[i] *= gain;
|
||||
}
|
||||
}
|
||||
showToast("Đã Chuẩn Hóa Peak âm thanh về 0 dB!", "success");
|
||||
} else if (toolType === 'invert_phase') {
|
||||
const channelData = activeTrack.buffer.getChannelData(0);
|
||||
for (let i = 0; i < channelData.length; i++) {
|
||||
channelData[i] *= -1;
|
||||
}
|
||||
showToast("Đã Đảo Pha (180°) âm thanh thành công!", "success");
|
||||
} else if (toolType === 'swap_channels') {
|
||||
if (activeTrack.buffer.numberOfChannels >= 2) {
|
||||
const left = activeTrack.buffer.getChannelData(0);
|
||||
const right = activeTrack.buffer.getChannelData(1);
|
||||
for (let i = 0; i < left.length; i++) {
|
||||
const temp = left[i];
|
||||
left[i] = right[i];
|
||||
right[i] = temp;
|
||||
}
|
||||
showToast("Đã Đổi Kênh Left / Right thành công!", "success");
|
||||
} else {
|
||||
showToast("Track hiện tại là Mono. Chỉ áp dụng Đổi Kênh cho Stereo.", "info");
|
||||
}
|
||||
} else if (toolType === 'synth_wave') {
|
||||
generateSynthToTrack(activeTrack.id, 'synth');
|
||||
showToast("Đã tạo Tín Hiệu Sóng Tổng Hợp bằng công cụ Python!", "success");
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(err.message || "Lỗi khi chạy công cụ Python", "error");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkSelection = () => {
|
||||
if (selLeft === null || selRight === null || selectionStats.length === 0) {
|
||||
showToast("Vui lòng chọn một khoảng thời gian trên sóng âm trước.", "warning");
|
||||
@@ -5728,6 +5978,7 @@
|
||||
{ label: 'Save As...', icon: 'save', shortcut: 'Ctrl+Alt+S', action: () => handleExportSFS() },
|
||||
{ label: 'Save to Cloud', icon: 'upload-cloud', action: () => handleSaveCloud() },
|
||||
{ sep: true },
|
||||
{ label: 'Config AI Providers...', icon: 'settings', action: () => setAiConfigModalOpen(true) },
|
||||
{ label: 'Import Audio...', icon: 'file-input', shortcut: 'Ctrl+Alt+I', action: () => { const input = document.createElement('input'); input.type='file'; input.accept='audio/*'; input.onchange=async (e)=>{ if(e.target.files[0]){ addNewTrack(); const newId=(tracks.length+1).toString(); setTimeout(()=>loadFileOnTrack(newId,e.target.files[0]),100); } }; input.click(); showToast('Import audio','info'); } },
|
||||
{ label: 'Export Mix...', icon: 'file-output', action: () => triggerWavExport() },
|
||||
{ sep: true },
|
||||
@@ -5761,7 +6012,8 @@
|
||||
{ label: 'Media Explorer', icon: 'folder-search', action: () => showToast('Media explorer','info') },
|
||||
]},
|
||||
{ label: 'Tools', items: [
|
||||
{ label: 'Config', icon: 'settings', action: () => setShowAIConfig(true) },
|
||||
{ label: 'Config AI Providers...', icon: 'settings', action: () => setAiConfigModalOpen(true) },
|
||||
{ label: 'Python DSP Tools Panel', icon: 'wrench', action: () => openPanel('python_tools') },
|
||||
]},
|
||||
{ label: 'Help', items: [
|
||||
{ label: 'About SonicForge', icon: 'info', action: () => showToast('SonicForge Studio v1.0 - Professional DAW','info') },
|
||||
@@ -6102,11 +6354,13 @@
|
||||
const addPanel = (id, pos, visible) => { if (visible) dockPanels[pos].push(id); };
|
||||
addPanel('export', panelPositions.export, showExportPanel);
|
||||
addPanel('ai', panelPositions.ai, showAIPanel);
|
||||
addPanel('python_tools', panelPositions.python_tools || 'bottom', showPythonToolsPanel);
|
||||
addPanel('selection', panelPositions.selection, showSelectionPanel);
|
||||
|
||||
const closePanel = (id) => {
|
||||
if (id === 'export') setShowExportPanel(false);
|
||||
else if (id === 'ai') setShowAIPanel(false);
|
||||
else if (id === 'python_tools') setShowPythonToolsPanel(false);
|
||||
else if (id === 'selection') setShowSelectionPanel(false);
|
||||
};
|
||||
|
||||
@@ -6165,8 +6419,8 @@
|
||||
{analysisState.data && <div className="text-emerald-500 font-semibold">BPM: {analysisState.data.bpm}</div>}
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-1">
|
||||
<button onClick={handleMarkSelection} className="py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-[9px] border border-purple-700 flex items-center justify-center gap-1">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="map-pin" className="w-3 h-3"></i></span> Mark
|
||||
<button onClick={handleAIScan} className="py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-[9px] border border-purple-700 flex items-center justify-center gap-1">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="map-pin" className="w-3 h-3"></i></span> AI Scan
|
||||
</button>
|
||||
<button onClick={handleAICutToNewTrack} disabled={analysisState.isRunning} className="py-1 bg-purple-700 hover:bg-purple-600 text-white font-bold rounded text-[9px] flex items-center justify-center gap-1">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="scissors" className="w-3 h-3"></i></span> AI Cut
|
||||
@@ -6177,6 +6431,34 @@
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (panelId === 'python_tools') return (
|
||||
<div className="flex flex-col h-full gap-1.5">
|
||||
<div className="flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none" onMouseDown={e => startPanelDrag('python_tools', e)}>
|
||||
<h3 className="font-bold text-[10px] text-amber-300 flex items-center gap-1">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="grip-vertical" className="w-3 h-3 text-zinc-500"></i></span>
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="wrench" className="w-3.5 h-3.5 text-amber-400"></i></span> Python DSP Tools
|
||||
</h3>
|
||||
<button onClick={() => closePanel('python_tools')} className="text-zinc-600 hover:text-zinc-300"><span className="inline-flex items-center shrink-0"><i data-lucide="x" className="w-3 h-3"></i></span></button>
|
||||
</div>
|
||||
<div className="p-1 bg-[#141414] rounded border border-zinc-800 text-[8px] font-mono text-zinc-400">
|
||||
// Non-AI Audio Processing Tools
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-1 text-[9px]">
|
||||
<button onClick={() => runPythonTool('normalize')} className="py-1 bg-amber-950/80 hover:bg-amber-900 text-amber-300 border border-amber-800/80 rounded font-bold flex items-center justify-center gap-1">
|
||||
⚡ Peak Norm (0dB)
|
||||
</button>
|
||||
<button onClick={() => runPythonTool('invert_phase')} className="py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 border border-zinc-700 rounded font-bold flex items-center justify-center gap-1">
|
||||
🔄 Phase Invert
|
||||
</button>
|
||||
<button onClick={() => runPythonTool('swap_channels')} className="py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 border border-zinc-700 rounded font-bold flex items-center justify-center gap-1">
|
||||
🔀 Swap L/R
|
||||
</button>
|
||||
<button onClick={() => runPythonTool('synth_wave')} className="py-1 bg-teal-950/80 hover:bg-teal-900 text-teal-300 border border-teal-800/80 rounded font-bold flex items-center justify-center gap-1">
|
||||
🎹 Gen Synth Tone
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (panelId === 'selection') return (
|
||||
<div className="flex flex-col h-full gap-1.5">
|
||||
<div className="flex items-center justify-between shrink-0 cursor-grab active:cursor-grabbing select-none" onMouseDown={e => startPanelDrag('selection', e)}>
|
||||
@@ -6271,7 +6553,7 @@
|
||||
style={{ left: dragGhostPos.x, top: dragGhostPos.y }}>
|
||||
<div className="flex items-center gap-2 text-[10px] text-zinc-200 font-bold">
|
||||
<span className="inline-flex items-center shrink-0"><i data-lucide="move" className="w-3.5 h-3.5 text-cyan-400"></i></span>
|
||||
{dragGhostPanel === 'export' ? 'Export Panel' : dragGhostPanel === 'ai' ? 'AI Panel' : 'Selection Panel'}
|
||||
{dragGhostPanel === 'export' ? 'Export Panel' : dragGhostPanel === 'ai' ? 'AI Panel' : dragGhostPanel === 'python_tools' ? 'Audio Processing Panel' : 'Selection Panel'}
|
||||
</div>
|
||||
<div className="text-[8px] text-zinc-500 mt-1">Drop at edge to dock</div>
|
||||
</div>
|
||||
@@ -6286,7 +6568,7 @@
|
||||
{/* ══ LEFT COLUMN: TCP PANEL (main session, all tracks) ══ */}
|
||||
<div ref={tcpContainerRef}
|
||||
onScroll={handleTCPScroll}
|
||||
className="w-[300px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900"
|
||||
className="w-[320px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
||||
<div className="sticky top-0 z-50 flex items-center justify-between h-10 border-b border-zinc-900 bg-[#242424] px-2 shrink-0">
|
||||
<span className="text-xs font-bold text-zinc-300 flex items-center gap-1.5">
|
||||
@@ -6471,7 +6753,7 @@
|
||||
return (
|
||||
<>
|
||||
{/* ══ TCP PANEL (sub-tab, single track) ══ */}
|
||||
<div className="w-[300px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900"
|
||||
<div className="w-[320px] shrink-0 z-30 bg-[#262626] overflow-y-scroll no-scrollbar flex flex-col border-r border-zinc-900"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
|
||||
<div className="sticky top-0 z-50 flex items-center h-10 border-b border-zinc-900 bg-[#242424] px-2 justify-between shrink-0">
|
||||
<span className="text-[10px] font-bold text-zinc-500 uppercase">Sub-Tab</span>
|
||||
@@ -6831,6 +7113,7 @@
|
||||
isOpen={profileModalOpen}
|
||||
onClose={() => setProfileModalOpen(false)}
|
||||
/>
|
||||
<AIConfigModal isOpen={aiConfigModalOpen} onClose={() => setAiConfigModalOpen(false)} />
|
||||
<SystemManagerModal
|
||||
isOpen={systemManagerModalOpen}
|
||||
onClose={() => setSystemManagerModalOpen(false)}
|
||||
|
||||
Reference in New Issue
Block a user