fix: tự động dịch chuyển thanh cuộn xuống dưới cùng

This commit is contained in:
2026-07-22 17:49:08 +07:00
parent 610c384bca
commit b9c524230d
4 changed files with 93 additions and 20 deletions
+37 -8
View File
@@ -3090,6 +3090,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({
@@ -7627,7 +7633,10 @@ 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 { success: false, error: 'No track or audio data' };
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' };
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;
@@ -7639,17 +7648,36 @@ const App = () => {
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;
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;
@@ -8784,6 +8812,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"