fix: tự động dịch chuyển thanh cuộn xuống dưới cùng
This commit is contained in:
@@ -3133,6 +3133,12 @@ const App = () => {
|
||||
const [aiProvider, setAiProvider] = useState('OpenAI');
|
||||
const [aiModel, setAiModel] = useState('GPT-4o');
|
||||
const [aiActionLog, setAiActionLog] = useState([]);
|
||||
const actionLogContainerRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (actionLogContainerRef.current) {
|
||||
actionLogContainerRef.current.scrollTop = actionLogContainerRef.current.scrollHeight;
|
||||
}
|
||||
}, [aiActionLog]);
|
||||
const [aiProcessing, setAiProcessing] = useState(false);
|
||||
const [selectedProviderId, setSelectedProviderId] = useState('');
|
||||
const [exportSettings, setExportSettings] = useState({
|
||||
@@ -7937,25 +7943,58 @@ const App = () => {
|
||||
exportAudio: async args => {
|
||||
const tid = args.track_id || selectedTrackId;
|
||||
const track = tid && tracks.find(t => t.id === tid);
|
||||
if (!track || !track.buffer) return {
|
||||
if (!track) return {
|
||||
success: false,
|
||||
error: 'No track or audio data'
|
||||
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'
|
||||
};
|
||||
const totalDuration = Math.max(...clips.map(c => (c.startTime || 0) + (c.buffer ? c.buffer.duration / (c.speed || 1.0) : 0)));
|
||||
const barDur = 60 / parseInt(bpm || 120) * 4;
|
||||
const sel = selectionRef.current;
|
||||
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 (sel.start !== null) rawStart = sel.start;else rawStart = 0;
|
||||
if (args.end_time !== undefined) rawEnd = args.end_time;else if (args.length_bars !== undefined) rawEnd = (rawStart || 0) + args.length_bars * barDur;else if (args.end_bar !== undefined) rawEnd = args.end_bar * barDur;else if (sel.end !== null && sel.end > rawStart) rawEnd = sel.end;else rawEnd = track.buffer.duration;
|
||||
if (args.end_time !== undefined) rawEnd = args.end_time;else if (args.length_bars !== undefined) rawEnd = (rawStart || 0) + args.length_bars * barDur;else if (args.end_bar !== undefined) rawEnd = args.end_bar * barDur;else if (sel.end !== null && sel.end > rawStart) rawEnd = sel.end;else rawEnd = totalDuration;
|
||||
if (rawEnd <= rawStart) return {
|
||||
success: false,
|
||||
error: 'Export range is empty or invalid.'
|
||||
};
|
||||
const ctx = getAudioContext();
|
||||
const sr = parseInt(args.sample_rate || '44100');
|
||||
const numCh = args.channels === 'mono' ? 1 : track.buffer.numberOfChannels || 2;
|
||||
const firstBuffer = clips.find(c => c.buffer)?.buffer;
|
||||
const numCh = args.channels === 'mono' ? 1 : firstBuffer ? firstBuffer.numberOfChannels : 2;
|
||||
const bd = parseInt(args.bit_depth || '16');
|
||||
const fmt = args.format || 'wav';
|
||||
const offlineCtx = new OfflineAudioContext(numCh, Math.ceil(sr * Math.min(rawEnd - rawStart, track.buffer.duration)), sr);
|
||||
const source = offlineCtx.createBufferSource();
|
||||
source.buffer = track.buffer;
|
||||
source.start(0, rawStart, rawEnd - rawStart);
|
||||
source.connect(offlineCtx.destination);
|
||||
const renderLength = rawEnd - rawStart;
|
||||
const offlineCtx = new OfflineAudioContext(numCh, Math.ceil(sr * renderLength), sr);
|
||||
clips.forEach(clip => {
|
||||
if (!clip.buffer) return;
|
||||
const clipStart = clip.startTime || 0;
|
||||
const clipDuration = clip.buffer.duration / (clip.speed || 1.0);
|
||||
const clipEnd = clipStart + clipDuration;
|
||||
if (clipEnd <= rawStart || clipStart >= rawEnd) return;
|
||||
const source = offlineCtx.createBufferSource();
|
||||
source.buffer = clip.buffer;
|
||||
source.playbackRate.value = clip.speed || 1.0;
|
||||
source.connect(offlineCtx.destination);
|
||||
if (rawStart < clipStart) {
|
||||
const delay = clipStart - rawStart;
|
||||
const playDur = Math.min(clipDuration, rawEnd - clipStart);
|
||||
source.start(delay, 0, playDur * (clip.speed || 1.0));
|
||||
} else {
|
||||
const offsetInClip = rawStart - clipStart;
|
||||
const playDur = Math.min(clipEnd - rawStart, renderLength);
|
||||
source.start(0, offsetInClip * (clip.speed || 1.0), playDur * (clip.speed || 1.0));
|
||||
}
|
||||
});
|
||||
const renderedBuffer = await offlineCtx.startRendering();
|
||||
const len = renderedBuffer.length;
|
||||
const bps = bd / 8;
|
||||
@@ -9279,6 +9318,7 @@ const App = () => {
|
||||
"data-lucide": "list",
|
||||
className: "w-3 h-3"
|
||||
}), " Action Log")), /*#__PURE__*/React.createElement("div", {
|
||||
ref: actionLogContainerRef,
|
||||
className: "flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 select-text"
|
||||
}, aiActionLog.length === 0 ? /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs text-zinc-600 italic select-text"
|
||||
|
||||
Reference in New Issue
Block a user