fix: giải quyết các lỗi về xuất track, tải cấu hình AI, tràn dropdown, click hủy chọn và làm mới bảng DSP Tools

This commit is contained in:
2026-07-22 18:08:23 +07:00
parent 6b7872c636
commit a8b484bd15
3 changed files with 191 additions and 20 deletions
+97 -10
View File
@@ -549,6 +549,17 @@ const WaveformLane = ({
speed: track.speed || 1.0
}] : [];
// Check if Ctrl+Click to exit selection
if (e.ctrlKey && selectionMode) {
e.preventDefault();
e.stopPropagation();
if (onClearLocalSelection) onClearLocalSelection();
if (onSetSelectionMode) onSetSelectionMode(null);
if (onSetSelectionStart) onSetSelectionStart(null);
if (onSetSelectionEnd) onSetSelectionEnd(null);
return;
}
// 1. Shift+Click (TOP PRIORITY): Range selection on track waveform
if (e.shiftKey) {
e.preventDefault();
@@ -3192,6 +3203,45 @@ const App = () => {
const [subTabs, setSubTabs] = useState([]); // [{id, label, trackId, startTime, endTime, buffer, effects, currentTime, selectionStart, selectionEnd, isPlaying}, ...]
const [selectionCleared, setSelectionCleared] = useState(false); // LOOP_EDITOR_2.md §4.2
const dspSelectionStats = useMemo(() => {
const track = tracks.find(t => t.id === selectedTrackId);
if (!track) return null;
const numChannels = track.buffer ? (track.buffer.numberOfChannels || 1) : 0;
if (selLeft === null || selRight === null || selRight <= selLeft || !track.buffer) {
return {
trackName: track.name,
channels: numChannels,
timeRange: 'Chưa chọn vùng',
peakVolume: 'N/A'
};
}
const buffer = track.buffer;
const sampleRate = buffer.sampleRate;
const startSample = Math.max(0, Math.min(buffer.length - 1, Math.floor(selLeft * sampleRate)));
const endSample = Math.max(0, Math.min(buffer.length, Math.floor(selRight * sampleRate)));
let maxVal = 0;
for (let c = 0; c < numChannels; c++) {
const data = buffer.getChannelData(c);
for (let i = startSample; i < endSample; i++) {
const val = Math.abs(data[i]);
if (val > maxVal) maxVal = val;
}
}
let peakDb = 'N/A';
if (maxVal > 0) {
const db = 20 * Math.log10(maxVal);
peakDb = db.toFixed(2) + ' dB';
} else {
peakDb = '-∞ dB';
}
return {
trackName: track.name,
channels: numChannels,
timeRange: `${selLeft.toFixed(2)}s - ${selRight.toFixed(2)}s (${(selRight - selLeft).toFixed(2)}s)`,
peakVolume: peakDb
};
}, [tracks, selectedTrackId, selLeft, selRight]);
// Temp Edit Tab (LOOP_EDITOR_2.md §1.2 Sub Tab)
const [tempTabActive, setTempTabActive] = useState(false);
const [tempTabBuffer, setTempTabBuffer] = useState(null);
@@ -3306,6 +3356,14 @@ const App = () => {
if (data && data.preferences) loadPrefs(data.preferences);
else { const cached = localStorage.getItem('sonic_preferences'); if (cached) loadPrefs(JSON.parse(cached)); }
} catch (e) { const cached = localStorage.getItem('sonic_preferences'); if (cached) loadPrefs(JSON.parse(cached)); }
try {
const data = await window.SonicAPI.getAIConfigs();
if (data && data.providers) {
setAiProviders(data.providers);
const active = data.providers.find(p => p.is_active) || data.providers[0];
if (active) setSelectedProviderId(active.id);
}
} catch (e) {}
})();
}
};
@@ -7139,6 +7197,8 @@ const App = () => {
if (window.DAWCommandDispatcher) {
window.DAWCommandDispatcher.currentSelectedTrackId = selectedTrackId;
window.DAWCommandDispatcher.currentTracks = tracks;
window.DAWCommandDispatcher.lastCutSourceTrackId = null;
window.DAWCommandDispatcher.lastCutNewTrackId = null;
}
setAiProcessing(true);
setAiActionLog(prev => [...prev, { type: 'status', text: ` ⏳ Đang gửi prompt đến AI...`, time: Date.now() }]);
@@ -7570,6 +7630,8 @@ const App = () => {
if (window.DAWCommandDispatcher) {
window.DAWCommandDispatcher.currentTracks = nextTracks;
window.DAWCommandDispatcher.currentSelectedTrackId = newId;
window.DAWCommandDispatcher.lastCutSourceTrackId = tid;
window.DAWCommandDispatcher.lastCutNewTrackId = newId;
}
setTracks(nextTracks);
setSelectedTrackId(newId);
@@ -7643,8 +7705,16 @@ const App = () => {
return { success: true, time: parseFloat(time.toFixed(3)) };
},
exportAudio: async (args) => {
const tid = args.track_id || selectedTrackId;
const track = tid && tracks.find(t => t.id === tid);
const currentTracks = window.DAWCommandDispatcher?.currentTracks || tracks;
const currentSelTrackId = window.DAWCommandDispatcher?.currentSelectedTrackId || selectedTrackId;
let tid = args.track_id;
if (tid && window.DAWCommandDispatcher?.lastCutSourceTrackId &&
(String(tid) === String(window.DAWCommandDispatcher.lastCutSourceTrackId) ||
'track_' + tid === String(window.DAWCommandDispatcher.lastCutSourceTrackId))) {
tid = window.DAWCommandDispatcher.lastCutNewTrackId;
}
if (!tid) tid = currentSelTrackId;
const track = tid && currentTracks.find(t => t.id === String(tid) || t.id === 'track_' + tid);
if (!track) return { success: false, error: 'No track found' };
const clips = track.clips && track.clips.length > 0 ? track.clips : (track.buffer ? [{ id: 'default_' + track.id, buffer: track.buffer, startTime: track.startTime || 0, name: track.name }] : []);
if (clips.length === 0) return { success: false, error: 'Track has no audio clips' };
@@ -7773,7 +7843,13 @@ const App = () => {
fadeIn: (args) => {
const currentTracks = window.DAWCommandDispatcher?.currentTracks || tracks;
const currentSelTrackId = window.DAWCommandDispatcher?.currentSelectedTrackId || selectedTrackId;
const trackIdRaw = args.track_id || currentSelTrackId;
let trackIdRaw = args.track_id;
if (trackIdRaw && window.DAWCommandDispatcher?.lastCutSourceTrackId &&
(String(trackIdRaw) === String(window.DAWCommandDispatcher.lastCutSourceTrackId) ||
'track_' + trackIdRaw === String(window.DAWCommandDispatcher.lastCutSourceTrackId))) {
trackIdRaw = window.DAWCommandDispatcher.lastCutNewTrackId;
}
if (!trackIdRaw) trackIdRaw = currentSelTrackId;
const track = currentTracks.find(t => t.id === String(trackIdRaw) || t.id === 'track_' + trackIdRaw);
if (!track) return { success: false, error: `Track ${trackIdRaw} not found` };
const clips = track.clips && track.clips.length > 0 ? track.clips : (track.buffer ? [{ id: 'default_' + track.id, buffer: track.buffer, startTime: track.startTime || 0, name: track.name }] : []);
@@ -7833,7 +7909,13 @@ const App = () => {
fadeOut: (args) => {
const currentTracks = window.DAWCommandDispatcher?.currentTracks || tracks;
const currentSelTrackId = window.DAWCommandDispatcher?.currentSelectedTrackId || selectedTrackId;
const trackIdRaw = args.track_id || currentSelTrackId;
let trackIdRaw = args.track_id;
if (trackIdRaw && window.DAWCommandDispatcher?.lastCutSourceTrackId &&
(String(trackIdRaw) === String(window.DAWCommandDispatcher.lastCutSourceTrackId) ||
'track_' + trackIdRaw === String(window.DAWCommandDispatcher.lastCutSourceTrackId))) {
trackIdRaw = window.DAWCommandDispatcher.lastCutNewTrackId;
}
if (!trackIdRaw) trackIdRaw = currentSelTrackId;
const track = currentTracks.find(t => t.id === String(trackIdRaw) || t.id === 'track_' + trackIdRaw);
if (!track) return { success: false, error: `Track ${trackIdRaw} not found` };
const clips = track.clips && track.clips.length > 0 ? track.clips : (track.buffer ? [{ id: 'default_' + track.id, buffer: track.buffer, startTime: track.startTime || 0, name: track.name }] : []);
@@ -8131,7 +8213,7 @@ const App = () => {
icon: 'settings',
action: () => setAiConfigModalOpen(true)
}, {
label: 'Python DSP Tools Panel',
label: 'DSP Tools Panel',
icon: 'wrench',
action: () => openPanel('python_tools')
}]
@@ -8790,7 +8872,7 @@ const App = () => {
"data-lucide": "x",
className: "w-3 h-3"
}))))), /*#__PURE__*/React.createElement("div", {
className: "flex items-center gap-1.5 shrink-0"
className: "flex items-center gap-1.5 w-full min-w-0 pb-1"
}, /*#__PURE__*/React.createElement("span", {
className: "inline-flex items-center shrink-0"
}, /*#__PURE__*/React.createElement("i", {
@@ -8799,7 +8881,7 @@ const App = () => {
})), /*#__PURE__*/React.createElement("select", {
value: selectedProviderId,
onChange: e => setSelectedProviderId(e.target.value),
className: "flex-1 bg-[#141414] text-zinc-200 border border-zinc-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-purple-600"
className: "flex-1 min-w-0 max-w-full bg-[#141414] text-zinc-200 border border-zinc-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-purple-600 truncate"
}, aiProviders.length === 0 ? /*#__PURE__*/React.createElement("option", {
value: ""
}, "Chưa có provider") : aiProviders.map(p => /*#__PURE__*/React.createElement("option", {
@@ -8917,7 +8999,7 @@ const App = () => {
}, /*#__PURE__*/React.createElement("i", {
"data-lucide": "wrench",
className: "w-3.5 h-3.5 text-amber-400"
})), " Python DSP Tools"), /*#__PURE__*/React.createElement("button", {
})), " DSP Tools"), /*#__PURE__*/React.createElement("button", {
onClick: () => closePanel('python_tools'),
className: "text-zinc-600 hover:text-zinc-300"
}, /*#__PURE__*/React.createElement("span", {
@@ -8926,8 +9008,13 @@ const App = () => {
"data-lucide": "x",
className: "w-3 h-3"
})))), /*#__PURE__*/React.createElement("div", {
className: "p-1 bg-[#141414] rounded border border-zinc-800 text-xs font-mono text-zinc-400"
}, "// Non-AI Audio Processing Tools"), /*#__PURE__*/React.createElement("div", {
className: "p-1.5 bg-[#141414] rounded border border-zinc-800 text-[10px] font-mono text-zinc-400 space-y-0.5 leading-relaxed"
}, dspSelectionStats ? [
/*#__PURE__*/React.createElement("div", { key: "track" }, `Track: ${dspSelectionStats.trackName}`),
/*#__PURE__*/React.createElement("div", { key: "range" }, `Range: ${dspSelectionStats.timeRange}`),
/*#__PURE__*/React.createElement("div", { key: "ch" }, `Channels: ${dspSelectionStats.channels}`),
/*#__PURE__*/React.createElement("div", { key: "peak" }, `Peak Vol: ${dspSelectionStats.peakVolume}`)
] : "Chưa chọn track"), /*#__PURE__*/React.createElement("div", {
className: "grid grid-cols-2 gap-1 text-xs"
}, /*#__PURE__*/React.createElement("button", {
onClick: () => runPythonTool('normalize'),