fix: xử lý phân quyền tải file từ lệnh AI và hỗ trợ nén MP3/OGG qua API máy chủ
This commit is contained in:
+159
-28
@@ -5089,13 +5089,15 @@ const App = () => {
|
||||
}, [tracks, selectedTrackId, selLeft, selRight]);
|
||||
|
||||
// ── Toast helper ──
|
||||
const showToast = (text, type = 'info') => {
|
||||
const showToast = (text, type = 'info', actionText = null, onActionClick = null) => {
|
||||
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current);
|
||||
setToastMessage({
|
||||
text,
|
||||
type
|
||||
type,
|
||||
actionText,
|
||||
onActionClick
|
||||
});
|
||||
toastTimeoutRef.current = setTimeout(() => setToastMessage(null), 3500);
|
||||
toastTimeoutRef.current = setTimeout(() => setToastMessage(null), actionText ? 8000 : 3500);
|
||||
};
|
||||
|
||||
// ── Server-side upload ──
|
||||
@@ -6605,12 +6607,63 @@ const App = () => {
|
||||
type: 'audio/wav'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.wav`;
|
||||
a.click();
|
||||
if ((exportSettings.format === 'mp3' || exportSettings.format === 'ogg') && serverStatus === 'connected') {
|
||||
setIsExporting(true);
|
||||
showToast("Đang gửi yêu cầu nén định dạng lên máy chủ...", "info");
|
||||
try {
|
||||
const file = new File([blob], `session_export.wav`, { type: 'audio/wav' });
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const uploadResp = await fetch(`${API_AUDIO}/upload`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
if (!uploadResp.ok) throw new Error("Upload to transcode server failed");
|
||||
const uploadData = await uploadResp.json();
|
||||
const fileId = uploadData.file_id;
|
||||
|
||||
const exportResp = await fetch(`${API_AUDIO}/export`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
format: exportSettings.format,
|
||||
sample_rate: targetRate,
|
||||
bit_depth: bitDepth
|
||||
})
|
||||
});
|
||||
if (!exportResp.ok) throw new Error("Transcode request failed");
|
||||
const exportData = await exportResp.json();
|
||||
const result = await pollTaskResult(exportData.task_id, 20);
|
||||
if (result.success) {
|
||||
const outId = result.output_file_id;
|
||||
const downloadUrl = `${API_AUDIO}/download/${outId}`;
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.${exportSettings.format}`;
|
||||
a.click();
|
||||
showToast(`Xuất bản âm thanh định dạng ${exportSettings.format.toUpperCase()} thành công!`, "success");
|
||||
} else {
|
||||
throw new Error(result.error || 'Server encoding failed');
|
||||
}
|
||||
} catch (transcodeErr) {
|
||||
showToast(`Lỗi chuyển đổi: ${transcodeErr.message}. Tải dạng WAV thay thế.`, "warning");
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.wav`;
|
||||
a.click();
|
||||
}
|
||||
} else {
|
||||
if (exportSettings.format !== 'wav') {
|
||||
showToast("Đang ngoại tuyến. Tải về định dạng WAV thay thế.", "warning");
|
||||
}
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.wav`;
|
||||
a.click();
|
||||
showToast("Xuất bản âm thanh hoàn tất!", "success");
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
showToast("Xuất bản âm thanh hoàn tất!", "success");
|
||||
} catch (err) {
|
||||
showToast("Lỗi xuất âm thanh: " + err.message, "error");
|
||||
} finally {
|
||||
@@ -7239,19 +7292,28 @@ const App = () => {
|
||||
}
|
||||
if (hasCalls) {
|
||||
setAiActionLog(prev => [...prev, { type: 'info', text: ` Gọi ${result.functionCalls.length} lệnh...`, time: Date.now() }]);
|
||||
for (const fc of result.functionCalls) {
|
||||
setAiActionLog(prev => [...prev, { type: 'info', text: ` Gọi lệnh: ${fc.name}(${JSON.stringify(fc.arguments)})`, time: Date.now() }]);
|
||||
const cmdName = fc.name.toUpperCase();
|
||||
if (window.DAWCommandDispatcher) {
|
||||
try {
|
||||
let cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
|
||||
if (cmdResult && typeof cmdResult.then === 'function') cmdResult = await cmdResult;
|
||||
setAiActionLog(prev => [...prev, { type: 'status', text: ` ✅ ${fc.name}: ${cmdResult && cmdResult.success ? 'thành công' : 'thất bại: ' + ((cmdResult && cmdResult.error) || 'unknown')}`, time: Date.now() }]);
|
||||
} catch (cmdErr) {
|
||||
setAiActionLog(prev => [...prev, { type: 'error', text: ` ❌ ${fc.name}: ${cmdErr.message}`, time: Date.now() }]);
|
||||
if (window.DAWCommandDispatcher) {
|
||||
window.DAWCommandDispatcher.isExecutingAI = true;
|
||||
}
|
||||
try {
|
||||
for (const fc of result.functionCalls) {
|
||||
setAiActionLog(prev => [...prev, { type: 'info', text: ` Gọi lệnh: ${fc.name}(${JSON.stringify(fc.arguments)})`, time: Date.now() }]);
|
||||
const cmdName = fc.name.toUpperCase();
|
||||
if (window.DAWCommandDispatcher) {
|
||||
try {
|
||||
let cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
|
||||
if (cmdResult && typeof cmdResult.then === 'function') cmdResult = await cmdResult;
|
||||
setAiActionLog(prev => [...prev, { type: 'status', text: ` ✅ ${fc.name}: ${cmdResult && cmdResult.success ? 'thành công' : 'thất bại: ' + ((cmdResult && cmdResult.error) || 'unknown')}`, time: Date.now() }]);
|
||||
} catch (cmdErr) {
|
||||
setAiActionLog(prev => [...prev, { type: 'error', text: ` ❌ ${fc.name}: ${cmdErr.message}`, time: Date.now() }]);
|
||||
}
|
||||
} else {
|
||||
setAiActionLog(prev => [...prev, { type: 'error', text: ` ❌ ${fc.name}: DAWCommandDispatcher not available`, time: Date.now() }]);
|
||||
}
|
||||
} else {
|
||||
setAiActionLog(prev => [...prev, { type: 'error', text: ` ❌ ${fc.name}: DAWCommandDispatcher not available`, time: Date.now() }]);
|
||||
}
|
||||
} finally {
|
||||
if (window.DAWCommandDispatcher) {
|
||||
window.DAWCommandDispatcher.isExecutingAI = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7782,13 +7844,76 @@ const App = () => {
|
||||
ofs += bps;
|
||||
}
|
||||
}
|
||||
const blob = new Blob([fileBuf], { type: 'audio/' + fmt });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `export_${Date.now()}.${fmt}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
const blob = new Blob([fileBuf], { type: 'audio/wav' });
|
||||
const localUrl = URL.createObjectURL(blob);
|
||||
const targetFilename = `export_${Date.now()}.${fmt}`;
|
||||
|
||||
const triggerDownload = (downloadUrl, finalFilename) => {
|
||||
if (window.DAWCommandDispatcher?.isExecutingAI) {
|
||||
showToast(`Xuất nhạc thành công (${fmt.toUpperCase()})!`, "success", "Tải về", () => {
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = finalFilename;
|
||||
a.click();
|
||||
if (downloadUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = finalFilename;
|
||||
a.click();
|
||||
showToast("Xuất bản âm thanh hoàn tất!", "success");
|
||||
if (downloadUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ((fmt === 'mp3' || fmt === 'ogg') && serverStatus === 'connected') {
|
||||
try {
|
||||
const file = new File([blob], `export_ai.wav`, { type: 'audio/wav' });
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const uploadResp = await fetch(`${API_AUDIO}/upload`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
if (!uploadResp.ok) throw new Error("Upload failed");
|
||||
const uploadData = await uploadResp.json();
|
||||
const uploadId = uploadData.file_id;
|
||||
|
||||
const exportResp = await fetch(`${API_AUDIO}/export`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
file_id: uploadId,
|
||||
format: fmt,
|
||||
sample_rate: sr,
|
||||
bit_depth: bd
|
||||
})
|
||||
});
|
||||
if (!exportResp.ok) throw new Error("Export failed");
|
||||
const exportData = await exportResp.json();
|
||||
const result = await pollTaskResult(exportData.task_id, 20);
|
||||
if (result.success) {
|
||||
const downloadUrl = `${API_AUDIO}/download/${result.output_file_id}`;
|
||||
triggerDownload(downloadUrl, targetFilename);
|
||||
} else {
|
||||
throw new Error(result.error || 'Server encoding failed');
|
||||
}
|
||||
} catch (transcodeErr) {
|
||||
showToast(`Lỗi chuyển đổi: ${transcodeErr.message}. Tải về dạng WAV thay thế.`, "warning");
|
||||
triggerDownload(localUrl, `export_${Date.now()}.wav`);
|
||||
}
|
||||
} else {
|
||||
const finalFilename = fmt === 'wav' ? `export_${Date.now()}.wav` : `export_${Date.now()}.wav`;
|
||||
if (fmt !== 'wav') {
|
||||
showToast("Đang ngoại tuyến. Tải về định dạng WAV thay thế.", "warning");
|
||||
}
|
||||
triggerDownload(localUrl, finalFilename);
|
||||
}
|
||||
return { success: true, trackId: tid, range: parseFloat((rawEnd - rawStart).toFixed(3)) + 's', format: fmt, channels: numCh === 1 ? 'mono' : 'stereo' };
|
||||
},
|
||||
selectItem: (args) => {
|
||||
@@ -10461,7 +10586,13 @@ const App = () => {
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "bell",
|
||||
className: `w-4 h-4 ${toastMessage.type === 'success' ? 'text-emerald-400' : toastMessage.type === 'error' ? 'text-rose-400' : toastMessage.type === 'warning' ? 'text-amber-400' : 'text-cyan-400'}`
|
||||
})), toastMessage.text), /*#__PURE__*/React.createElement(AuthModal, {
|
||||
})), toastMessage.text, toastMessage.onActionClick && /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => {
|
||||
toastMessage.onActionClick();
|
||||
setToastMessage(null);
|
||||
},
|
||||
className: "ml-2 px-2 py-0.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-[10px] uppercase font-bold"
|
||||
}, toastMessage.actionText || 'Tải về')), /*#__PURE__*/React.createElement(AuthModal, {
|
||||
isOpen: authModalOpen,
|
||||
mode: authMode,
|
||||
forceMandatory: isMandatoryLogin,
|
||||
|
||||
@@ -5144,13 +5144,15 @@ const App = () => {
|
||||
}, [tracks, selectedTrackId, selLeft, selRight]);
|
||||
|
||||
// ── Toast helper ──
|
||||
const showToast = (text, type = 'info') => {
|
||||
const showToast = (text, type = 'info', actionText = null, onActionClick = null) => {
|
||||
if (toastTimeoutRef.current) clearTimeout(toastTimeoutRef.current);
|
||||
setToastMessage({
|
||||
text,
|
||||
type
|
||||
type,
|
||||
actionText,
|
||||
onActionClick
|
||||
});
|
||||
toastTimeoutRef.current = setTimeout(() => setToastMessage(null), 3500);
|
||||
toastTimeoutRef.current = setTimeout(() => setToastMessage(null), actionText ? 8000 : 3500);
|
||||
};
|
||||
|
||||
// ── Server-side upload ──
|
||||
@@ -6738,12 +6740,66 @@ const App = () => {
|
||||
type: 'audio/wav'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.wav`;
|
||||
a.click();
|
||||
if ((exportSettings.format === 'mp3' || exportSettings.format === 'ogg') && serverStatus === 'connected') {
|
||||
setIsExporting(true);
|
||||
showToast("Đang gửi yêu cầu nén định dạng lên máy chủ...", "info");
|
||||
try {
|
||||
const file = new File([blob], `session_export.wav`, {
|
||||
type: 'audio/wav'
|
||||
});
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const uploadResp = await fetch(`${API_AUDIO}/upload`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
if (!uploadResp.ok) throw new Error("Upload to transcode server failed");
|
||||
const uploadData = await uploadResp.json();
|
||||
const fileId = uploadData.file_id;
|
||||
const exportResp = await fetch(`${API_AUDIO}/export`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
file_id: fileId,
|
||||
format: exportSettings.format,
|
||||
sample_rate: targetRate,
|
||||
bit_depth: bitDepth
|
||||
})
|
||||
});
|
||||
if (!exportResp.ok) throw new Error("Transcode request failed");
|
||||
const exportData = await exportResp.json();
|
||||
const result = await pollTaskResult(exportData.task_id, 20);
|
||||
if (result.success) {
|
||||
const outId = result.output_file_id;
|
||||
const downloadUrl = `${API_AUDIO}/download/${outId}`;
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.${exportSettings.format}`;
|
||||
a.click();
|
||||
showToast(`Xuất bản âm thanh định dạng ${exportSettings.format.toUpperCase()} thành công!`, "success");
|
||||
} else {
|
||||
throw new Error(result.error || 'Server encoding failed');
|
||||
}
|
||||
} catch (transcodeErr) {
|
||||
showToast(`Lỗi chuyển đổi: ${transcodeErr.message}. Tải dạng WAV thay thế.`, "warning");
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.wav`;
|
||||
a.click();
|
||||
}
|
||||
} else {
|
||||
if (exportSettings.format !== 'wav') {
|
||||
showToast("Đang ngoại tuyến. Tải về định dạng WAV thay thế.", "warning");
|
||||
}
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `sonicforge-session-${targetRate}hz-${bitDepth}bit.wav`;
|
||||
a.click();
|
||||
showToast("Xuất bản âm thanh hoàn tất!", "success");
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
showToast("Xuất bản âm thanh hoàn tất!", "success");
|
||||
} catch (err) {
|
||||
showToast("Lỗi xuất âm thanh: " + err.message, "error");
|
||||
} finally {
|
||||
@@ -7396,35 +7452,44 @@ const App = () => {
|
||||
text: ` Gọi ${result.functionCalls.length} lệnh...`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
for (const fc of result.functionCalls) {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'info',
|
||||
text: ` Gọi lệnh: ${fc.name}(${JSON.stringify(fc.arguments)})`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
const cmdName = fc.name.toUpperCase();
|
||||
if (window.DAWCommandDispatcher) {
|
||||
try {
|
||||
let cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
|
||||
if (cmdResult && typeof cmdResult.then === 'function') cmdResult = await cmdResult;
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'status',
|
||||
text: ` ✅ ${fc.name}: ${cmdResult && cmdResult.success ? 'thành công' : 'thất bại: ' + (cmdResult && cmdResult.error || 'unknown')}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
} catch (cmdErr) {
|
||||
if (window.DAWCommandDispatcher) {
|
||||
window.DAWCommandDispatcher.isExecutingAI = true;
|
||||
}
|
||||
try {
|
||||
for (const fc of result.functionCalls) {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'info',
|
||||
text: ` Gọi lệnh: ${fc.name}(${JSON.stringify(fc.arguments)})`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
const cmdName = fc.name.toUpperCase();
|
||||
if (window.DAWCommandDispatcher) {
|
||||
try {
|
||||
let cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
|
||||
if (cmdResult && typeof cmdResult.then === 'function') cmdResult = await cmdResult;
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'status',
|
||||
text: ` ✅ ${fc.name}: ${cmdResult && cmdResult.success ? 'thành công' : 'thất bại: ' + (cmdResult && cmdResult.error || 'unknown')}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
} catch (cmdErr) {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'error',
|
||||
text: ` ❌ ${fc.name}: ${cmdErr.message}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
}
|
||||
} else {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'error',
|
||||
text: ` ❌ ${fc.name}: ${cmdErr.message}`,
|
||||
text: ` ❌ ${fc.name}: DAWCommandDispatcher not available`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
}
|
||||
} else {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'error',
|
||||
text: ` ❌ ${fc.name}: DAWCommandDispatcher not available`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
}
|
||||
} finally {
|
||||
if (window.DAWCommandDispatcher) {
|
||||
window.DAWCommandDispatcher.isExecutingAI = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8110,14 +8175,78 @@ const App = () => {
|
||||
}
|
||||
}
|
||||
const blob = new Blob([fileBuf], {
|
||||
type: 'audio/' + fmt
|
||||
type: 'audio/wav'
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `export_${Date.now()}.${fmt}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
const localUrl = URL.createObjectURL(blob);
|
||||
const targetFilename = `export_${Date.now()}.${fmt}`;
|
||||
const triggerDownload = (downloadUrl, finalFilename) => {
|
||||
if (window.DAWCommandDispatcher?.isExecutingAI) {
|
||||
showToast(`Xuất nhạc thành công (${fmt.toUpperCase()})!`, "success", "Tải về", () => {
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = finalFilename;
|
||||
a.click();
|
||||
if (downloadUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = finalFilename;
|
||||
a.click();
|
||||
showToast("Xuất bản âm thanh hoàn tất!", "success");
|
||||
if (downloadUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(downloadUrl);
|
||||
}
|
||||
}
|
||||
};
|
||||
if ((fmt === 'mp3' || fmt === 'ogg') && serverStatus === 'connected') {
|
||||
try {
|
||||
const file = new File([blob], `export_ai.wav`, {
|
||||
type: 'audio/wav'
|
||||
});
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const uploadResp = await fetch(`${API_AUDIO}/upload`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
if (!uploadResp.ok) throw new Error("Upload failed");
|
||||
const uploadData = await uploadResp.json();
|
||||
const uploadId = uploadData.file_id;
|
||||
const exportResp = await fetch(`${API_AUDIO}/export`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
file_id: uploadId,
|
||||
format: fmt,
|
||||
sample_rate: sr,
|
||||
bit_depth: bd
|
||||
})
|
||||
});
|
||||
if (!exportResp.ok) throw new Error("Export failed");
|
||||
const exportData = await exportResp.json();
|
||||
const result = await pollTaskResult(exportData.task_id, 20);
|
||||
if (result.success) {
|
||||
const downloadUrl = `${API_AUDIO}/download/${result.output_file_id}`;
|
||||
triggerDownload(downloadUrl, targetFilename);
|
||||
} else {
|
||||
throw new Error(result.error || 'Server encoding failed');
|
||||
}
|
||||
} catch (transcodeErr) {
|
||||
showToast(`Lỗi chuyển đổi: ${transcodeErr.message}. Tải về dạng WAV thay thế.`, "warning");
|
||||
triggerDownload(localUrl, `export_${Date.now()}.wav`);
|
||||
}
|
||||
} else {
|
||||
const finalFilename = fmt === 'wav' ? `export_${Date.now()}.wav` : `export_${Date.now()}.wav`;
|
||||
if (fmt !== 'wav') {
|
||||
showToast("Đang ngoại tuyến. Tải về định dạng WAV thay thế.", "warning");
|
||||
}
|
||||
triggerDownload(localUrl, finalFilename);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
trackId: tid,
|
||||
@@ -10972,7 +11101,13 @@ const App = () => {
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "bell",
|
||||
className: `w-4 h-4 ${toastMessage.type === 'success' ? 'text-emerald-400' : toastMessage.type === 'error' ? 'text-rose-400' : toastMessage.type === 'warning' ? 'text-amber-400' : 'text-cyan-400'}`
|
||||
})), toastMessage.text), /*#__PURE__*/React.createElement(AuthModal, {
|
||||
})), toastMessage.text, toastMessage.onActionClick && /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => {
|
||||
toastMessage.onActionClick();
|
||||
setToastMessage(null);
|
||||
},
|
||||
className: "ml-2 px-2 py-0.5 bg-purple-700 hover:bg-purple-600 text-white rounded text-[10px] uppercase font-bold"
|
||||
}, toastMessage.actionText || 'Tải về')), /*#__PURE__*/React.createElement(AuthModal, {
|
||||
isOpen: authModalOpen,
|
||||
mode: authMode,
|
||||
forceMandatory: isMandatoryLogin,
|
||||
|
||||
Reference in New Issue
Block a user