fix: thêm và sửa các tools của AI

This commit is contained in:
2026-07-22 09:51:27 +07:00
parent 4791bb22d9
commit 108943ae81
7 changed files with 285 additions and 13 deletions
+131 -1
View File
@@ -3240,6 +3240,25 @@ const App = () => {
setIsMandatoryLogin(false);
setAuthModalOpen(false);
loadPendingSfsProject();
(async () => {
try {
const data = await window.SonicAPI.getPreferences();
if (data && data.preferences) {
const p = data.preferences;
if (p.showAIPanel !== undefined) setShowAIPanel(p.showAIPanel);
if (p.showExportPanel !== undefined) setShowExportPanel(p.showExportPanel);
if (p.showSelectionPanel !== undefined) setShowSelectionPanel(p.showSelectionPanel);
if (p.showPythonToolsPanel !== undefined) setShowPythonToolsPanel(p.showPythonToolsPanel);
if (p.showMediaExplorer !== undefined) setShowMediaExplorer(p.showMediaExplorer);
if (p.showFxRack !== undefined) setShowFxRack(p.showFxRack);
if (p.showMidiEvents !== undefined) setShowMidiEvents(p.showMidiEvents);
if (p.panelPositions) setPanelPositions(p.panelPositions);
if (p.rightSidebarWidth) setRightSidebarWidth(p.rightSidebarWidth);
if (p.mediaExplorerHeight) setMediaExplorerHeight(p.mediaExplorerHeight);
if (p.selectedProviderId) setSelectedProviderId(p.selectedProviderId);
}
} catch (e) {/* preferences not available */}
})();
}
};
const handleLogout = () => {
@@ -7460,6 +7479,92 @@ const App = () => {
length: parseFloat((end - start).toFixed(3))
};
},
cutAudio: args => {
const tid = args.track_id || selectedTrackId;
const track = tracks.find(t => t.id === tid);
if (!track) return {
success: false,
error: 'Track not found'
};
if (!track.buffer) return {
success: false,
error: 'Track has no audio buffer'
};
const barDur = 60 / parseInt(bpm || 120) * 4;
let rawStart, rawEnd;
if (args.start_time !== undefined) rawStart = args.start_time;else if (args.start_bar !== undefined) rawStart = args.start_bar * barDur;else if (selectionStart !== null) rawStart = selectionStart;else return {
success: false,
error: 'No selection or start position provided. Use set_selection first or provide start_time/start_bar.'
};
if (args.end_time !== undefined) rawEnd = args.end_time;else if (args.length_bars !== undefined) rawEnd = rawStart + args.length_bars * barDur;else if (selectionEnd !== null && selectionEnd > rawStart) rawEnd = selectionEnd;else return {
success: false,
error: 'No end position provided. Use set_selection first or provide end_time/length_bars.'
};
const buffer = track.buffer;
const ctx = getAudioContext();
const snap = args.snap_silence !== false;
const loopStart = snap ? findZeroCrossing(buffer, rawStart) : rawStart;
const loopEnd = snap ? findZeroCrossing(buffer, rawEnd) : rawEnd;
const sampleRate = buffer.sampleRate;
const startSample = Math.max(0, Math.min(buffer.length - 1, Math.floor(loopStart * sampleRate)));
const endSample = Math.max(0, Math.min(buffer.length, Math.floor(loopEnd * sampleRate)));
const sliceLength = endSample - startSample;
if (sliceLength <= 100) return {
success: false,
error: 'Selection too short or invalid'
};
const numChannels = buffer.numberOfChannels || 1;
const slicedBuffer = ctx.createBuffer(numChannels, sliceLength, sampleRate);
for (let c = 0; c < numChannels; c++) {
const src = buffer.getChannelData(c);
const dst = slicedBuffer.getChannelData(c);
dst.set(src.subarray(startSample, endSample));
}
const newId = 'track_cut_' + Date.now();
const colors = ['#0f766e', '#1d4ed8', '#701a75', '#a21caf', '#b45309'];
const color = colors[tracks.length % colors.length];
const cutName = args.new_track_name || `Cut_${track.name}`;
const newTrack = {
id: newId,
name: cutName,
buffer: slicedBuffer,
startTime: 0,
height: 128,
volumeDb: 0,
pan: 0,
muted: false,
solo: false,
color,
markers: [],
clips: [{
id: 'clip_' + newId,
buffer: slicedBuffer,
startTime: 0,
name: cutName
}],
serverFileId: null
};
setTracks(prev => {
const idx = prev.findIndex(t => t.id === tid);
const updated = [...prev];
updated.splice(idx + 1, 0, newTrack);
return updated;
});
setSelectedTrackId(newId);
clearLocalSelection();
setSelectionMode('global');
setSelectionStart(0);
setSelectionEnd(slicedBuffer.duration);
setTimeout(() => lucide.createIcons(), 200);
return {
success: true,
trackId: newId,
trackName: cutName,
cutStart: parseFloat(loopStart.toFixed(3)),
cutEnd: parseFloat(loopEnd.toFixed(3)),
duration: parseFloat(slicedBuffer.duration.toFixed(3))
};
},
scanTrack: args => {
const tid = args.track_id || selectedTrackId;
const track = tracks.find(t => t.id === tid);
@@ -7565,6 +7670,31 @@ const App = () => {
localStorage.setItem('ai_api_key', aiConfig.apiKey);
localStorage.setItem('ai_model', aiConfig.model);
}, [aiConfig]);
// ── Auto-save user preferences (panel state, provider) ──
const prefsRef = useRef({});
prefsRef.current = {
showAIPanel,
showExportPanel,
showSelectionPanel,
showPythonToolsPanel,
showMediaExplorer,
showFxRack,
showMidiEvents,
panelPositions,
rightSidebarWidth,
mediaExplorerHeight,
selectedProviderId
};
useEffect(() => {
if (!currentUser) return;
const timer = setTimeout(async () => {
try {
await window.SonicAPI.savePreferences(prefsRef.current);
} catch (e) {}
}, 2000);
return () => clearTimeout(timer);
}, [showAIPanel, showExportPanel, showSelectionPanel, showPythonToolsPanel, showMediaExplorer, showFxRack, showMidiEvents, panelPositions, rightSidebarWidth, mediaExplorerHeight, selectedProviderId, currentUser]);
return /*#__PURE__*/React.createElement("div", {
className: "h-full w-full flex flex-col bg-[#1e1e1e]"
}, /*#__PURE__*/React.createElement("header", {
@@ -8444,7 +8574,7 @@ const App = () => {
key: p.id,
value: p.id
}, p.name, p.is_active ? '' : ' (inactive)')))), /*#__PURE__*/React.createElement("div", {
className: "border-t border-zinc-800 pt-1.5 mt-1"
className: "border-t border-zinc-800 pt-1.5 mt-1 shrink-0"
}, /*#__PURE__*/React.createElement("div", {
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center gap-1"
}, /*#__PURE__*/React.createElement("span", {