fix: copilot Multi-tool Calling)
This commit is contained in:
@@ -7729,6 +7729,115 @@ const App = () => {
|
||||
return { ...t, markers: [...(t.markers || []), { id: 'ai_marker_' + Date.now(), time, label: args.label || 'AI Marker' }] };
|
||||
}));
|
||||
return { success: true, trackId, time };
|
||||
},
|
||||
fadeIn: (args) => {
|
||||
const trackIdRaw = args.track_id || selectedTrackId;
|
||||
const track = tracks.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 }] : []);
|
||||
if (clips.length === 0) return { success: false, error: 'No clips on track' };
|
||||
let clip = null;
|
||||
if (args.clip_id) {
|
||||
clip = clips.find(c => c.id === args.clip_id);
|
||||
} else if (args.clip_index !== undefined) {
|
||||
const idx = parseInt(args.clip_index);
|
||||
const realIdx = idx > 0 ? idx - 1 : 0;
|
||||
clip = clips[realIdx] || clips[0];
|
||||
} else {
|
||||
clip = clips[0];
|
||||
}
|
||||
if (!clip || !clip.buffer) return { success: false, error: 'Clip has no audio buffer' };
|
||||
const duration = parseFloat(args.duration_seconds || 3);
|
||||
const buffer = clip.buffer;
|
||||
const sr = buffer.sampleRate;
|
||||
const numChannels = buffer.numberOfChannels;
|
||||
const length = buffer.length;
|
||||
const ctx = getAudioContext();
|
||||
const newBuffer = ctx.createBuffer(numChannels, length, sr);
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
newBuffer.getChannelData(c).set(buffer.getChannelData(c));
|
||||
}
|
||||
const fadeSamples = Math.min(length, Math.floor(duration * sr));
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
const data = newBuffer.getChannelData(c);
|
||||
for (let i = 0; i < fadeSamples; i++) {
|
||||
const factor = (1 - Math.cos(Math.PI * i / fadeSamples)) / 2;
|
||||
data[i] *= factor;
|
||||
}
|
||||
}
|
||||
const beforeSnap = captureTrackSnapshot(track.id);
|
||||
setTracks(prev => prev.map(t => {
|
||||
if (t.id !== track.id) return t;
|
||||
const existingClips = t.clips && t.clips.length > 0 ? t.clips : (t.buffer ? [{ id: 'default_' + t.id, buffer: t.buffer, startTime: t.startTime || 0, name: t.name }] : []);
|
||||
const updatedClips = existingClips.map(c => {
|
||||
if (c.id === clip.id || (clip.id.startsWith('default_') && c.id === 'default')) {
|
||||
return { ...c, buffer: newBuffer };
|
||||
}
|
||||
return c;
|
||||
});
|
||||
const mainBuffer = updatedClips[0]?.buffer || t.buffer;
|
||||
return { ...t, clips: updatedClips, buffer: mainBuffer };
|
||||
}));
|
||||
setTimeout(() => {
|
||||
const afterSnap = captureTrackSnapshot(track.id);
|
||||
pushAction('AI_FADE_IN', track.id, beforeSnap, afterSnap);
|
||||
}, 50);
|
||||
return { success: true, trackId: track.id, clipId: clip.id, duration_seconds: duration };
|
||||
},
|
||||
fadeOut: (args) => {
|
||||
const trackIdRaw = args.track_id || selectedTrackId;
|
||||
const track = tracks.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 }] : []);
|
||||
if (clips.length === 0) return { success: false, error: 'No clips on track' };
|
||||
let clip = null;
|
||||
if (args.clip_id) {
|
||||
clip = clips.find(c => c.id === args.clip_id);
|
||||
} else if (args.clip_index !== undefined) {
|
||||
const idx = parseInt(args.clip_index);
|
||||
const realIdx = idx > 0 ? idx - 1 : 0;
|
||||
clip = clips[realIdx] || clips[0];
|
||||
} else {
|
||||
clip = clips[0];
|
||||
}
|
||||
if (!clip || !clip.buffer) return { success: false, error: 'Clip has no audio buffer' };
|
||||
const duration = parseFloat(args.duration_seconds || 3);
|
||||
const buffer = clip.buffer;
|
||||
const sr = buffer.sampleRate;
|
||||
const numChannels = buffer.numberOfChannels;
|
||||
const length = buffer.length;
|
||||
const ctx = getAudioContext();
|
||||
const newBuffer = ctx.createBuffer(numChannels, length, sr);
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
newBuffer.getChannelData(c).set(buffer.getChannelData(c));
|
||||
}
|
||||
const fadeSamples = Math.min(length, Math.floor(duration * sr));
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
const data = newBuffer.getChannelData(c);
|
||||
for (let i = 0; i < fadeSamples; i++) {
|
||||
const idx = length - fadeSamples + i;
|
||||
const factor = (1 + Math.cos(Math.PI * i / fadeSamples)) / 2;
|
||||
data[idx] *= factor;
|
||||
}
|
||||
}
|
||||
const beforeSnap = captureTrackSnapshot(track.id);
|
||||
setTracks(prev => prev.map(t => {
|
||||
if (t.id !== track.id) return t;
|
||||
const existingClips = t.clips && t.clips.length > 0 ? t.clips : (t.buffer ? [{ id: 'default_' + t.id, buffer: t.buffer, startTime: t.startTime || 0, name: t.name }] : []);
|
||||
const updatedClips = existingClips.map(c => {
|
||||
if (c.id === clip.id || (clip.id.startsWith('default_') && c.id === 'default')) {
|
||||
return { ...c, buffer: newBuffer };
|
||||
}
|
||||
return c;
|
||||
});
|
||||
const mainBuffer = updatedClips[0]?.buffer || t.buffer;
|
||||
return { ...t, clips: updatedClips, buffer: mainBuffer };
|
||||
}));
|
||||
setTimeout(() => {
|
||||
const afterSnap = captureTrackSnapshot(track.id);
|
||||
pushAction('AI_FADE_OUT', track.id, beforeSnap, afterSnap);
|
||||
}, 50);
|
||||
return { success: true, trackId: track.id, clipId: clip.id, duration_seconds: duration };
|
||||
}
|
||||
};
|
||||
window.DAWCommandDispatcher.registerDAWCommands(api);
|
||||
|
||||
@@ -8099,6 +8099,177 @@ const App = () => {
|
||||
trackId,
|
||||
time
|
||||
};
|
||||
},
|
||||
fadeIn: args => {
|
||||
const trackIdRaw = args.track_id || selectedTrackId;
|
||||
const track = tracks.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
|
||||
}] : [];
|
||||
if (clips.length === 0) return {
|
||||
success: false,
|
||||
error: 'No clips on track'
|
||||
};
|
||||
let clip = null;
|
||||
if (args.clip_id) {
|
||||
clip = clips.find(c => c.id === args.clip_id);
|
||||
} else if (args.clip_index !== undefined) {
|
||||
const idx = parseInt(args.clip_index);
|
||||
const realIdx = idx > 0 ? idx - 1 : 0;
|
||||
clip = clips[realIdx] || clips[0];
|
||||
} else {
|
||||
clip = clips[0];
|
||||
}
|
||||
if (!clip || !clip.buffer) return {
|
||||
success: false,
|
||||
error: 'Clip has no audio buffer'
|
||||
};
|
||||
const duration = parseFloat(args.duration_seconds || 3);
|
||||
const buffer = clip.buffer;
|
||||
const sr = buffer.sampleRate;
|
||||
const numChannels = buffer.numberOfChannels;
|
||||
const length = buffer.length;
|
||||
const ctx = getAudioContext();
|
||||
const newBuffer = ctx.createBuffer(numChannels, length, sr);
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
newBuffer.getChannelData(c).set(buffer.getChannelData(c));
|
||||
}
|
||||
const fadeSamples = Math.min(length, Math.floor(duration * sr));
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
const data = newBuffer.getChannelData(c);
|
||||
for (let i = 0; i < fadeSamples; i++) {
|
||||
const factor = (1 - Math.cos(Math.PI * i / fadeSamples)) / 2;
|
||||
data[i] *= factor;
|
||||
}
|
||||
}
|
||||
const beforeSnap = captureTrackSnapshot(track.id);
|
||||
setTracks(prev => prev.map(t => {
|
||||
if (t.id !== track.id) return t;
|
||||
const existingClips = t.clips && t.clips.length > 0 ? t.clips : t.buffer ? [{
|
||||
id: 'default_' + t.id,
|
||||
buffer: t.buffer,
|
||||
startTime: t.startTime || 0,
|
||||
name: t.name
|
||||
}] : [];
|
||||
const updatedClips = existingClips.map(c => {
|
||||
if (c.id === clip.id || clip.id.startsWith('default_') && c.id === 'default') {
|
||||
return {
|
||||
...c,
|
||||
buffer: newBuffer
|
||||
};
|
||||
}
|
||||
return c;
|
||||
});
|
||||
const mainBuffer = updatedClips[0]?.buffer || t.buffer;
|
||||
return {
|
||||
...t,
|
||||
clips: updatedClips,
|
||||
buffer: mainBuffer
|
||||
};
|
||||
}));
|
||||
setTimeout(() => {
|
||||
const afterSnap = captureTrackSnapshot(track.id);
|
||||
pushAction('AI_FADE_IN', track.id, beforeSnap, afterSnap);
|
||||
}, 50);
|
||||
return {
|
||||
success: true,
|
||||
trackId: track.id,
|
||||
clipId: clip.id,
|
||||
duration_seconds: duration
|
||||
};
|
||||
},
|
||||
fadeOut: args => {
|
||||
const trackIdRaw = args.track_id || selectedTrackId;
|
||||
const track = tracks.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
|
||||
}] : [];
|
||||
if (clips.length === 0) return {
|
||||
success: false,
|
||||
error: 'No clips on track'
|
||||
};
|
||||
let clip = null;
|
||||
if (args.clip_id) {
|
||||
clip = clips.find(c => c.id === args.clip_id);
|
||||
} else if (args.clip_index !== undefined) {
|
||||
const idx = parseInt(args.clip_index);
|
||||
const realIdx = idx > 0 ? idx - 1 : 0;
|
||||
clip = clips[realIdx] || clips[0];
|
||||
} else {
|
||||
clip = clips[0];
|
||||
}
|
||||
if (!clip || !clip.buffer) return {
|
||||
success: false,
|
||||
error: 'Clip has no audio buffer'
|
||||
};
|
||||
const duration = parseFloat(args.duration_seconds || 3);
|
||||
const buffer = clip.buffer;
|
||||
const sr = buffer.sampleRate;
|
||||
const numChannels = buffer.numberOfChannels;
|
||||
const length = buffer.length;
|
||||
const ctx = getAudioContext();
|
||||
const newBuffer = ctx.createBuffer(numChannels, length, sr);
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
newBuffer.getChannelData(c).set(buffer.getChannelData(c));
|
||||
}
|
||||
const fadeSamples = Math.min(length, Math.floor(duration * sr));
|
||||
for (let c = 0; c < numChannels; c++) {
|
||||
const data = newBuffer.getChannelData(c);
|
||||
for (let i = 0; i < fadeSamples; i++) {
|
||||
const idx = length - fadeSamples + i;
|
||||
const factor = (1 + Math.cos(Math.PI * i / fadeSamples)) / 2;
|
||||
data[idx] *= factor;
|
||||
}
|
||||
}
|
||||
const beforeSnap = captureTrackSnapshot(track.id);
|
||||
setTracks(prev => prev.map(t => {
|
||||
if (t.id !== track.id) return t;
|
||||
const existingClips = t.clips && t.clips.length > 0 ? t.clips : t.buffer ? [{
|
||||
id: 'default_' + t.id,
|
||||
buffer: t.buffer,
|
||||
startTime: t.startTime || 0,
|
||||
name: t.name
|
||||
}] : [];
|
||||
const updatedClips = existingClips.map(c => {
|
||||
if (c.id === clip.id || clip.id.startsWith('default_') && c.id === 'default') {
|
||||
return {
|
||||
...c,
|
||||
buffer: newBuffer
|
||||
};
|
||||
}
|
||||
return c;
|
||||
});
|
||||
const mainBuffer = updatedClips[0]?.buffer || t.buffer;
|
||||
return {
|
||||
...t,
|
||||
clips: updatedClips,
|
||||
buffer: mainBuffer
|
||||
};
|
||||
}));
|
||||
setTimeout(() => {
|
||||
const afterSnap = captureTrackSnapshot(track.id);
|
||||
pushAction('AI_FADE_OUT', track.id, beforeSnap, afterSnap);
|
||||
}, 50);
|
||||
return {
|
||||
success: true,
|
||||
trackId: track.id,
|
||||
clipId: clip.id,
|
||||
duration_seconds: duration
|
||||
};
|
||||
}
|
||||
};
|
||||
window.DAWCommandDispatcher.registerDAWCommands(api);
|
||||
|
||||
@@ -143,7 +143,13 @@ const AIGateway = (function() {
|
||||
const contextStr = JSON.stringify(context, null, 2);
|
||||
const toolNames = DEFAULT_TOOLS.map(t => ` - ${t.name}: ${t.description}`).join('\n');
|
||||
return [
|
||||
{ role: 'system', content: `Bạn là trợ lý DAW. Dùng function calls. Bar 0 = bar đầu. Có thể gọi nhiều function cùng lúc.` },
|
||||
{ role: 'system', content: `Bạn là trợ lý điều khiển DAW chuyên nghiệp.
|
||||
Nhiệm vụ của bạn là phân tích yêu cầu của người dùng và chuyển đổi thành danh sách các function calls tương ứng.
|
||||
QUAN TRỌNG:
|
||||
1. Bạn đang hoạt động ở chế độ một lượt (one-shot). Hãy trả về TẤT CẢ các function calls cần thiết để thực hiện toàn bộ các bước trong yêu cầu của người dùng trong một phản hồi duy nhất. Đừng thực hiện từng bước qua nhiều lượt chat.
|
||||
2. Có thể gọi nhiều function cùng một lúc (gọi song song/nối tiếp). Chúng sẽ được thực thi theo thứ tự bạn trả về.
|
||||
3. Khi một lệnh trước đó (như 'cut_audio') tạo ra một track mới, track đó sẽ tự động được chọn làm active track (selectedTrackId). Do đó, đối với các lệnh tiếp theo áp dụng lên track mới này (như 'fade_in', 'set_track_volume'), bạn hãy để trống tham số 'track_id' (hoặc truyền null) để hệ thống tự động áp dụng lên track vừa được tạo và chọn.
|
||||
4. Bar 0 đại diện cho bar đầu tiên trên timeline.` },
|
||||
{ role: 'user', content: `Ngữ cảnh DAW hiện tại:\n${contextStr}\n\nYêu cầu người dùng: ${prompt}` }
|
||||
];
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user