fix: thêm và sửa các tools của AI
This commit is contained in:
+98
-1
@@ -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 = () => {
|
||||
@@ -7333,6 +7352,67 @@ const App = () => {
|
||||
setSelectionEnd(end);
|
||||
return { success: true, start: parseFloat(start.toFixed(3)), end: parseFloat(end.toFixed(3)), 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);
|
||||
@@ -7411,6 +7491,23 @@ 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", {
|
||||
@@ -8296,7 +8393,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", {
|
||||
|
||||
Reference in New Issue
Block a user