fix: sửa các tools của AI
This commit is contained in:
+71
-7
@@ -3190,6 +3190,7 @@ const App = () => {
|
|||||||
try {
|
try {
|
||||||
const profile = await window.SonicAPI.getProfile();
|
const profile = await window.SonicAPI.getProfile();
|
||||||
setCurrentUser(profile);
|
setCurrentUser(profile);
|
||||||
|
localStorage.setItem('sonic_user', JSON.stringify(profile));
|
||||||
if (profile.must_change_password) {
|
if (profile.must_change_password) {
|
||||||
setIsMandatoryLogin(true);
|
setIsMandatoryLogin(true);
|
||||||
setAuthMode('force_change');
|
setAuthMode('force_change');
|
||||||
@@ -3199,12 +3200,19 @@ const App = () => {
|
|||||||
setAuthModalOpen(false);
|
setAuthModalOpen(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.removeItem('sonic_token');
|
const cached = localStorage.getItem('sonic_user');
|
||||||
localStorage.removeItem('sonic_user');
|
if (cached) {
|
||||||
setCurrentUser(null);
|
try { setCurrentUser(JSON.parse(cached)); } catch (_) {}
|
||||||
setIsMandatoryLogin(true);
|
setIsMandatoryLogin(false);
|
||||||
setAuthMode('login');
|
setAuthModalOpen(false);
|
||||||
setAuthModalOpen(true);
|
} else {
|
||||||
|
localStorage.removeItem('sonic_token');
|
||||||
|
localStorage.removeItem('sonic_user');
|
||||||
|
setCurrentUser(null);
|
||||||
|
setIsMandatoryLogin(true);
|
||||||
|
setAuthMode('login');
|
||||||
|
setAuthModalOpen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkAuthStatus();
|
checkAuthStatus();
|
||||||
@@ -7470,6 +7478,45 @@ const App = () => {
|
|||||||
handlePlayheadSet(time);
|
handlePlayheadSet(time);
|
||||||
return { success: true, time: parseFloat(time.toFixed(3)) };
|
return { success: true, time: parseFloat(time.toFixed(3)) };
|
||||||
},
|
},
|
||||||
|
selectItem: (args) => {
|
||||||
|
if (args.select_all) {
|
||||||
|
setSelectedTrackId(null);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
setSelectionStart(0);
|
||||||
|
const maxDur = tracks.reduce((max, t) => {
|
||||||
|
const dur = t.buffer ? t.buffer.duration : 0;
|
||||||
|
const clips = t.clips || [];
|
||||||
|
const clipMax = clips.reduce((m, c) => Math.max(m, (c.startTime || 0) + (c.buffer ? c.buffer.duration : 0)), 0);
|
||||||
|
return Math.max(max, dur, clipMax);
|
||||||
|
}, 0);
|
||||||
|
setSelectionEnd(Math.max(maxDur, currentTime + 10));
|
||||||
|
return { success: true, selection: 'all', duration: parseFloat(Math.max(maxDur, currentTime + 10).toFixed(3)) };
|
||||||
|
}
|
||||||
|
const tid = args.track_id || selectedTrackId;
|
||||||
|
const track = tracks.find(t => t.id === tid);
|
||||||
|
if (!track) return { success: false, error: `Track ${tid} not found` };
|
||||||
|
const clips = track.clips && track.clips.length > 0 ? track.clips : (track.buffer ? [{ id: 'default_' + track.id, name: track.name, buffer: track.buffer, startTime: track.startTime || 0 }] : []);
|
||||||
|
if (args.item_name) {
|
||||||
|
const match = clips.find(c => c.name && c.name.toLowerCase().includes(args.item_name.toLowerCase()));
|
||||||
|
if (!match) return { success: false, error: `No clip matching "${args.item_name}" on track ${track.name}` };
|
||||||
|
setSelectedTrackId(tid);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
const start = match.startTime || 0;
|
||||||
|
const end = start + (match.buffer ? match.buffer.duration : 2);
|
||||||
|
setSelectionStart(start);
|
||||||
|
setSelectionEnd(end);
|
||||||
|
return { success: true, trackId: tid, trackName: track.name, clipId: match.id, clipName: match.name, start: parseFloat(start.toFixed(3)), end: parseFloat(end.toFixed(3)) };
|
||||||
|
}
|
||||||
|
setSelectedTrackId(tid);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
const trackEnd = track.buffer ? track.buffer.duration : (clips.length > 0 ? Math.max(...clips.map(c => (c.startTime || 0) + (c.buffer ? c.buffer.duration : 0))) : 4);
|
||||||
|
setSelectionStart(0);
|
||||||
|
setSelectionEnd(trackEnd);
|
||||||
|
return { success: true, trackId: tid, trackName: track.name, duration: parseFloat(trackEnd.toFixed(3)) };
|
||||||
|
},
|
||||||
addMarker: (args) => {
|
addMarker: (args) => {
|
||||||
const trackId = args.track_id || selectedTrackId;
|
const trackId = args.track_id || selectedTrackId;
|
||||||
const time = args.time ?? currentTime;
|
const time = args.time ?? currentTime;
|
||||||
@@ -7741,7 +7788,7 @@ const App = () => {
|
|||||||
onClick: () => setMenuOpen(menuOpen === menu.label ? null : menu.label),
|
onClick: () => setMenuOpen(menuOpen === menu.label ? null : menu.label),
|
||||||
className: `px-3 py-1 text-xs font-medium transition rounded ${menuOpen === menu.label ? 'bg-zinc-700 text-zinc-100' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`
|
className: `px-3 py-1 text-xs font-medium transition rounded ${menuOpen === menu.label ? 'bg-zinc-700 text-zinc-100' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`
|
||||||
}, menu.label), menuOpen === menu.label && /*#__PURE__*/React.createElement("div", {
|
}, menu.label), menuOpen === menu.label && /*#__PURE__*/React.createElement("div", {
|
||||||
className: "absolute top-full left-0 mt-0.5 bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-52 z-50",
|
className: "absolute top-full left-0 mt-0.5 bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64 z-50",
|
||||||
onClick: () => setMenuOpen(null)
|
onClick: () => setMenuOpen(null)
|
||||||
}, menu.items.map((item, i) => item.sep ? /*#__PURE__*/React.createElement("div", {
|
}, menu.items.map((item, i) => item.sep ? /*#__PURE__*/React.createElement("div", {
|
||||||
key: i,
|
key: i,
|
||||||
@@ -8892,6 +8939,23 @@ const App = () => {
|
|||||||
setEditNameInput(track.name);
|
setEditNameInput(track.name);
|
||||||
}
|
}
|
||||||
}, track.name)), /*#__PURE__*/React.createElement("div", {
|
}, track.name)), /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "flex flex-wrap gap-0.5 max-w-[100px] mb-0.5"
|
||||||
|
}, (track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{ id: 'default', name: track.name, startTime: track.startTime }] : []).slice(0, 3).map(c => /*#__PURE__*/React.createElement("span", {
|
||||||
|
key: c.id,
|
||||||
|
className: "text-[9px] font-mono text-zinc-500 bg-zinc-800/60 rounded px-0.5 truncate max-w-[90px] cursor-pointer hover:text-cyan-400 hover:bg-zinc-700",
|
||||||
|
title: c.name || track.name,
|
||||||
|
onClick: e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setSelectedTrackId(track.id);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
const start = c.startTime || 0;
|
||||||
|
const end = start + (c.buffer ? c.buffer.duration : 2);
|
||||||
|
setSelectionStart(start);
|
||||||
|
setSelectionEnd(end);
|
||||||
|
showToast(`Selected: ${c.name || track.name}`, 'info');
|
||||||
|
}
|
||||||
|
}, c.name || track.name))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex items-center gap-1"
|
className: "flex items-center gap-1"
|
||||||
}, /*#__PURE__*/React.createElement("button", {
|
}, /*#__PURE__*/React.createElement("button", {
|
||||||
onClick: e => {
|
onClick: e => {
|
||||||
|
|||||||
@@ -3190,6 +3190,7 @@ const App = () => {
|
|||||||
try {
|
try {
|
||||||
const profile = await window.SonicAPI.getProfile();
|
const profile = await window.SonicAPI.getProfile();
|
||||||
setCurrentUser(profile);
|
setCurrentUser(profile);
|
||||||
|
localStorage.setItem('sonic_user', JSON.stringify(profile));
|
||||||
if (profile.must_change_password) {
|
if (profile.must_change_password) {
|
||||||
setIsMandatoryLogin(true);
|
setIsMandatoryLogin(true);
|
||||||
setAuthMode('force_change');
|
setAuthMode('force_change');
|
||||||
@@ -3199,12 +3200,21 @@ const App = () => {
|
|||||||
setAuthModalOpen(false);
|
setAuthModalOpen(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
localStorage.removeItem('sonic_token');
|
const cached = localStorage.getItem('sonic_user');
|
||||||
localStorage.removeItem('sonic_user');
|
if (cached) {
|
||||||
setCurrentUser(null);
|
try {
|
||||||
setIsMandatoryLogin(true);
|
setCurrentUser(JSON.parse(cached));
|
||||||
setAuthMode('login');
|
} catch (_) {}
|
||||||
setAuthModalOpen(true);
|
setIsMandatoryLogin(false);
|
||||||
|
setAuthModalOpen(false);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('sonic_token');
|
||||||
|
localStorage.removeItem('sonic_user');
|
||||||
|
setCurrentUser(null);
|
||||||
|
setIsMandatoryLogin(true);
|
||||||
|
setAuthMode('login');
|
||||||
|
setAuthModalOpen(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkAuthStatus();
|
checkAuthStatus();
|
||||||
@@ -7635,6 +7645,73 @@ const App = () => {
|
|||||||
time: parseFloat(time.toFixed(3))
|
time: parseFloat(time.toFixed(3))
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
selectItem: args => {
|
||||||
|
if (args.select_all) {
|
||||||
|
setSelectedTrackId(null);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
setSelectionStart(0);
|
||||||
|
const maxDur = tracks.reduce((max, t) => {
|
||||||
|
const dur = t.buffer ? t.buffer.duration : 0;
|
||||||
|
const clips = t.clips || [];
|
||||||
|
const clipMax = clips.reduce((m, c) => Math.max(m, (c.startTime || 0) + (c.buffer ? c.buffer.duration : 0)), 0);
|
||||||
|
return Math.max(max, dur, clipMax);
|
||||||
|
}, 0);
|
||||||
|
setSelectionEnd(Math.max(maxDur, currentTime + 10));
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
selection: 'all',
|
||||||
|
duration: parseFloat(Math.max(maxDur, currentTime + 10).toFixed(3))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const tid = args.track_id || selectedTrackId;
|
||||||
|
const track = tracks.find(t => t.id === tid);
|
||||||
|
if (!track) return {
|
||||||
|
success: false,
|
||||||
|
error: `Track ${tid} not found`
|
||||||
|
};
|
||||||
|
const clips = track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{
|
||||||
|
id: 'default_' + track.id,
|
||||||
|
name: track.name,
|
||||||
|
buffer: track.buffer,
|
||||||
|
startTime: track.startTime || 0
|
||||||
|
}] : [];
|
||||||
|
if (args.item_name) {
|
||||||
|
const match = clips.find(c => c.name && c.name.toLowerCase().includes(args.item_name.toLowerCase()));
|
||||||
|
if (!match) return {
|
||||||
|
success: false,
|
||||||
|
error: `No clip matching "${args.item_name}" on track ${track.name}`
|
||||||
|
};
|
||||||
|
setSelectedTrackId(tid);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
const start = match.startTime || 0;
|
||||||
|
const end = start + (match.buffer ? match.buffer.duration : 2);
|
||||||
|
setSelectionStart(start);
|
||||||
|
setSelectionEnd(end);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
trackId: tid,
|
||||||
|
trackName: track.name,
|
||||||
|
clipId: match.id,
|
||||||
|
clipName: match.name,
|
||||||
|
start: parseFloat(start.toFixed(3)),
|
||||||
|
end: parseFloat(end.toFixed(3))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
setSelectedTrackId(tid);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
const trackEnd = track.buffer ? track.buffer.duration : clips.length > 0 ? Math.max(...clips.map(c => (c.startTime || 0) + (c.buffer ? c.buffer.duration : 0))) : 4;
|
||||||
|
setSelectionStart(0);
|
||||||
|
setSelectionEnd(trackEnd);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
trackId: tid,
|
||||||
|
trackName: track.name,
|
||||||
|
duration: parseFloat(trackEnd.toFixed(3))
|
||||||
|
};
|
||||||
|
},
|
||||||
addMarker: args => {
|
addMarker: args => {
|
||||||
const trackId = args.track_id || selectedTrackId;
|
const trackId = args.track_id || selectedTrackId;
|
||||||
const time = args.time ?? currentTime;
|
const time = args.time ?? currentTime;
|
||||||
@@ -7928,7 +8005,7 @@ const App = () => {
|
|||||||
onClick: () => setMenuOpen(menuOpen === menu.label ? null : menu.label),
|
onClick: () => setMenuOpen(menuOpen === menu.label ? null : menu.label),
|
||||||
className: `px-3 py-1 text-xs font-medium transition rounded ${menuOpen === menu.label ? 'bg-zinc-700 text-zinc-100' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`
|
className: `px-3 py-1 text-xs font-medium transition rounded ${menuOpen === menu.label ? 'bg-zinc-700 text-zinc-100' : 'text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800'}`
|
||||||
}, menu.label), menuOpen === menu.label && /*#__PURE__*/React.createElement("div", {
|
}, menu.label), menuOpen === menu.label && /*#__PURE__*/React.createElement("div", {
|
||||||
className: "absolute top-full left-0 mt-0.5 bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-52 z-50",
|
className: "absolute top-full left-0 mt-0.5 bg-[#2a2a2a] border border-zinc-700 rounded-lg shadow-2xl py-1 w-64 z-50",
|
||||||
onClick: () => setMenuOpen(null)
|
onClick: () => setMenuOpen(null)
|
||||||
}, menu.items.map((item, i) => item.sep ? /*#__PURE__*/React.createElement("div", {
|
}, menu.items.map((item, i) => item.sep ? /*#__PURE__*/React.createElement("div", {
|
||||||
key: i,
|
key: i,
|
||||||
@@ -9081,6 +9158,27 @@ const App = () => {
|
|||||||
setEditNameInput(track.name);
|
setEditNameInput(track.name);
|
||||||
}
|
}
|
||||||
}, track.name)), /*#__PURE__*/React.createElement("div", {
|
}, track.name)), /*#__PURE__*/React.createElement("div", {
|
||||||
|
className: "flex flex-wrap gap-0.5 max-w-[100px] mb-0.5"
|
||||||
|
}, (track.clips && track.clips.length > 0 ? track.clips : track.buffer ? [{
|
||||||
|
id: 'default',
|
||||||
|
name: track.name,
|
||||||
|
startTime: track.startTime
|
||||||
|
}] : []).slice(0, 3).map(c => /*#__PURE__*/React.createElement("span", {
|
||||||
|
key: c.id,
|
||||||
|
className: "text-[9px] font-mono text-zinc-500 bg-zinc-800/60 rounded px-0.5 truncate max-w-[90px] cursor-pointer hover:text-cyan-400 hover:bg-zinc-700",
|
||||||
|
title: c.name || track.name,
|
||||||
|
onClick: e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setSelectedTrackId(track.id);
|
||||||
|
clearLocalSelection();
|
||||||
|
setSelectionMode('global');
|
||||||
|
const start = c.startTime || 0;
|
||||||
|
const end = start + (c.buffer ? c.buffer.duration : 2);
|
||||||
|
setSelectionStart(start);
|
||||||
|
setSelectionEnd(end);
|
||||||
|
showToast(`Selected: ${c.name || track.name}`, 'info');
|
||||||
|
}
|
||||||
|
}, c.name || track.name))), /*#__PURE__*/React.createElement("div", {
|
||||||
className: "flex items-center gap-1"
|
className: "flex items-center gap-1"
|
||||||
}, /*#__PURE__*/React.createElement("button", {
|
}, /*#__PURE__*/React.createElement("button", {
|
||||||
onClick: e => {
|
onClick: e => {
|
||||||
|
|||||||
@@ -166,6 +166,17 @@ const AIGateway = (function() {
|
|||||||
length_bars: { type: 'number', description: 'Độ dài vùng chọn (bar). Dùng cùng start_bar thay cho end_bar.' }
|
length_bars: { type: 'number', description: 'Độ dài vùng chọn (bar). Dùng cùng start_bar thay cho end_bar.' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
name: 'select_item',
|
||||||
|
description: 'Chọn một item/clip/sample cụ thể trên track. Nếu không có track_id và item_name thì chọn toàn bộ timeline.',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
track_id: { type: 'string', description: 'ID của track. Nếu không có thì dùng track đang chọn.' },
|
||||||
|
item_name: { type: 'string', description: 'Tên của clip/sample cần chọn. VD: "Cut_Cartoon Capers Loop.mp3". Nếu không có thì chọn toàn bộ track.' },
|
||||||
|
select_all: { type: 'boolean', description: 'Chọn toàn bộ timeline (bỏ qua track_id và item_name)' }
|
||||||
|
}
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
name: 'cut_audio',
|
name: 'cut_audio',
|
||||||
description: 'Cắt đoạn audio đang được chọn từ track nguồn, snap zero-crossing, tạo track mới chứa đoạn cắt',
|
description: 'Cắt đoạn audio đang được chọn từ track nguồn, snap zero-crossing, tạo track mới chứa đoạn cắt',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const DAWCommandDispatcher = (function() {
|
|||||||
register('SET_SELECTION', (args) => api.setSelection(args));
|
register('SET_SELECTION', (args) => api.setSelection(args));
|
||||||
register('SET_BPM', (args) => api.setBpm(args));
|
register('SET_BPM', (args) => api.setBpm(args));
|
||||||
register('SET_PLAYHEAD', (args) => api.setPlayhead(args));
|
register('SET_PLAYHEAD', (args) => api.setPlayhead(args));
|
||||||
|
register('SELECT_ITEM', (args) => api.selectItem(args));
|
||||||
register('ADD_MARKER', (args) => api.addMarker(args));
|
register('ADD_MARKER', (args) => api.addMarker(args));
|
||||||
register('CREATE_MIDI_ITEM', (args) => AIGateway.createMidiItem(args));
|
register('CREATE_MIDI_ITEM', (args) => AIGateway.createMidiItem(args));
|
||||||
register('MODIFY_MIDI_NOTES', (args) => AIGateway.modifyMidiNotes(args));
|
register('MODIFY_MIDI_NOTES', (args) => AIGateway.modifyMidiNotes(args));
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user