fix: đã sửa lỗi AI gửi prompt
This commit is contained in:
+129
-45
@@ -2481,7 +2481,8 @@ const AuthModal = ({
|
||||
};
|
||||
const AIConfigModal = ({
|
||||
isOpen,
|
||||
onClose
|
||||
onClose,
|
||||
onConfigSaved
|
||||
}) => {
|
||||
if (!isOpen) return null;
|
||||
const defaultProvidersList = [{
|
||||
@@ -2552,6 +2553,7 @@ const AIConfigModal = ({
|
||||
try {
|
||||
const res = await window.SonicAPI.saveAIConfigs(providers);
|
||||
setMsg(res.message || 'Đã lưu cấu hình AI Providers thành công!');
|
||||
if (onConfigSaved) onConfigSaved(providers);
|
||||
} catch (err) {
|
||||
setError(err.message || 'Lỗi khi lưu cấu hình AI');
|
||||
} finally {
|
||||
@@ -2594,7 +2596,38 @@ const AIConfigModal = ({
|
||||
className: "truncate"
|
||||
}, p.name), p.is_active && /*#__PURE__*/React.createElement("span", {
|
||||
className: "w-2 h-2 rounded-full bg-emerald-400"
|
||||
})))), activeProvider && /*#__PURE__*/React.createElement("form", {
|
||||
}))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex gap-1 mt-2"
|
||||
}, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
const newId = 'provider_' + Date.now();
|
||||
setProviders(prev => [...prev, {
|
||||
id: newId,
|
||||
name: 'New Provider',
|
||||
provider_type: 'openai_compatible',
|
||||
api_base_url: 'https://api.openai.com/v1',
|
||||
api_key: '',
|
||||
model_name: 'gpt-4o-mini',
|
||||
temperature: 0.7,
|
||||
is_active: false
|
||||
}]);
|
||||
setSelectedId(newId);
|
||||
},
|
||||
className: "flex-1 px-2 py-1 bg-emerald-700 hover:bg-emerald-600 text-white text-xs font-bold rounded"
|
||||
}, "+ Thêm"), providers.length > 1 && /*#__PURE__*/React.createElement("button", {
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
if (confirm(`Xóa provider "${providers.find(p => p.id === selectedId)?.name}"?`)) {
|
||||
setProviders(prev => {
|
||||
const filtered = prev.filter(p => p.id !== selectedId);
|
||||
if (filtered.length > 0) setSelectedId(filtered[0].id);
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
},
|
||||
className: "px-2 py-1 bg-red-800 hover:bg-red-700 text-white text-xs font-bold rounded"
|
||||
}, "Xóa"))), activeProvider && /*#__PURE__*/React.createElement("form", {
|
||||
onSubmit: handleSave,
|
||||
className: "col-span-2 space-y-3.5"
|
||||
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
||||
@@ -3004,6 +3037,19 @@ const App = () => {
|
||||
apiKey: localStorage.getItem('ai_api_key') || '',
|
||||
model: localStorage.getItem('ai_model') || 'deepseek-chat'
|
||||
});
|
||||
const [aiProviders, setAiProviders] = useState([]);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const data = await window.SonicAPI.getAIConfigs();
|
||||
if (data && data.providers) {
|
||||
setAiProviders(data.providers);
|
||||
const active = data.providers.find(p => p.is_active) || data.providers[0];
|
||||
if (active) setSelectedProviderId(active.id);
|
||||
}
|
||||
} catch (e) { /* server may not have config endpoint */ }
|
||||
})();
|
||||
}, []);
|
||||
const [analysisState, setAnalysisState] = useState({
|
||||
status: 'Sẵn sàng. Chạy AI để phân tích nhịp.',
|
||||
data: null,
|
||||
@@ -3014,6 +3060,7 @@ const App = () => {
|
||||
const [aiModel, setAiModel] = useState('GPT-4o');
|
||||
const [aiActionLog, setAiActionLog] = useState([]);
|
||||
const [aiProcessing, setAiProcessing] = useState(false);
|
||||
const [selectedProviderId, setSelectedProviderId] = useState('');
|
||||
const [exportSettings, setExportSettings] = useState({
|
||||
sampleRate: '44100',
|
||||
bitDepth: '16',
|
||||
@@ -3348,7 +3395,7 @@ const App = () => {
|
||||
const sidebarEl = document.getElementById('right-sidebar');
|
||||
const sidebarHeight = sidebarEl ? sidebarEl.getBoundingClientRect().height : 400;
|
||||
const onMove = ev => {
|
||||
const deltaY = ev.clientY - startY;
|
||||
const deltaY = startY - ev.clientY;
|
||||
const pct = ((startHeight / 100 * sidebarHeight + deltaY) / sidebarHeight) * 100;
|
||||
const newPct = Math.max(20, Math.min(80, pct));
|
||||
setMediaExplorerHeight(newPct);
|
||||
@@ -6922,6 +6969,58 @@ const App = () => {
|
||||
}, 800);
|
||||
};
|
||||
|
||||
// ── AI Prompt Send to Active Provider ──
|
||||
const handleAISend = async () => {
|
||||
const prompt = aiPrompt.trim();
|
||||
if (!prompt) { showToast('Vui lòng nhập nội dung prompt.', 'warning'); return; }
|
||||
setAiProcessing(true);
|
||||
setAiActionLog(prev => [...prev, { type: 'status', text: ` ⏳ Đang gửi prompt đến AI...`, time: Date.now() }]);
|
||||
try {
|
||||
const selectedProvider = aiProviders.find(p => p.id === selectedProviderId) || (aiProviders.length > 0 ? aiProviders[0] : null);
|
||||
const provider = selectedProvider || aiConfig;
|
||||
const baseUrl = provider.api_base_url || provider.baseUrl || `${API_BASE_URL}`;
|
||||
const apiKey = provider.api_key || provider.apiKey || '';
|
||||
const model = provider.model_name || provider.model || 'deepseek-chat';
|
||||
const dawContext = window.AIGateway.buildAIPromptContext({
|
||||
tracks, bpm, selectedTrackId, currentTime, selLeft, selRight
|
||||
});
|
||||
const result = await window.AIGateway.executeAIPrompt({
|
||||
prompt,
|
||||
provider: provider.name || 'default',
|
||||
model,
|
||||
apiKey,
|
||||
baseUrl: baseUrl.replace(/\/chat\/completions$/, '').replace(/\/$/, ''),
|
||||
dawContext,
|
||||
tools: window.AIGateway.DEFAULT_TOOLS
|
||||
});
|
||||
if (result.textResponse) {
|
||||
setAiActionLog(prev => [...prev, { type: 'status', text: ` AI: ${result.textResponse}`, time: Date.now() }]);
|
||||
}
|
||||
if (result.functionCalls && result.functionCalls.length > 0) {
|
||||
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 {
|
||||
const cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
|
||||
setAiActionLog(prev => [...prev, { type: 'status', text: ` ✅ ${fc.name}: ${cmdResult.success ? 'thành công' : 'thất bại'}`, time: Date.now() }]);
|
||||
} catch (cmdErr) {
|
||||
setAiActionLog(prev => [...prev, { type: 'error', text: ` ❌ ${fc.name}: ${cmdErr.message}`, time: Date.now() }]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setAiActionLog(prev => [...prev, { type: 'status', text: ` Hoàn tất.`, time: Date.now() }]);
|
||||
setAiPrompt('');
|
||||
setTimeout(() => lucide.createIcons(), 200);
|
||||
} catch (err) {
|
||||
setAiActionLog(prev => [...prev, { type: 'error', text: ` Lỗi: ${err.message}`, time: Date.now() }]);
|
||||
showToast(`AI Error: ${err.message}`, 'error');
|
||||
} finally {
|
||||
setAiProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Split Track at Playhead ──
|
||||
const handleSplitTrackAtTime = (trackId, clipId, time) => {
|
||||
const track = tracks.find(t => t.id === trackId);
|
||||
@@ -8087,42 +8186,22 @@ const App = () => {
|
||||
"data-lucide": "x",
|
||||
className: "w-3 h-3"
|
||||
}))))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "p-1.5 bg-[#141414] rounded border border-zinc-800 text-xs font-mono min-h-[20px]"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-zinc-500"
|
||||
}, "// ", /*#__PURE__*/React.createElement("span", {
|
||||
className: "text-zinc-300"
|
||||
}, analysisState.status)), analysisState.data && /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-emerald-500 font-semibold"
|
||||
}, "BPM: ", analysisState.data.bpm)), /*#__PURE__*/React.createElement("div", {
|
||||
className: "grid grid-cols-3 gap-1"
|
||||
}, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleAIScan,
|
||||
className: "py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-xs border border-purple-700 flex items-center justify-center gap-1"
|
||||
className: "flex items-center gap-1.5 shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "map-pin",
|
||||
className: "w-3 h-3"
|
||||
})), " Scan"), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleAICutToNewTrack,
|
||||
disabled: analysisState.isRunning,
|
||||
className: "py-1 bg-fuchsia-800 hover:bg-fuchsia-700 text-white font-bold rounded text-xs border border-fuchsia-600 flex items-center justify-center gap-1"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "scissors",
|
||||
className: "w-3 h-3"
|
||||
})), " Cut"), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleAIAnalysicLoop,
|
||||
disabled: analysisState.isRunning,
|
||||
className: "py-1 bg-violet-800 hover:bg-violet-700 text-violet-100 font-bold rounded text-xs border border-violet-600 flex items-center justify-center gap-1"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "sparkles",
|
||||
className: "w-3 h-3"
|
||||
})), " Loop")), /*#__PURE__*/React.createElement("div", {
|
||||
"data-lucide": "cpu",
|
||||
className: "w-3 h-3 text-purple-400"
|
||||
})), /*#__PURE__*/React.createElement("select", {
|
||||
value: selectedProviderId,
|
||||
onChange: e => setSelectedProviderId(e.target.value),
|
||||
className: "flex-1 bg-[#141414] text-zinc-200 border border-zinc-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-purple-600"
|
||||
}, aiProviders.length === 0 ? /*#__PURE__*/React.createElement("option", {
|
||||
value: ""
|
||||
}, "Chưa có provider") : aiProviders.map(p => /*#__PURE__*/React.createElement("option", {
|
||||
key: p.id,
|
||||
value: p.id
|
||||
}, p.name, p.is_active ? '' : ' (inactive)')))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "border-t border-zinc-800 pt-1.5 mt-1"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center gap-1"
|
||||
@@ -8138,15 +8217,15 @@ const App = () => {
|
||||
className: "w-full bg-[#1e1e1e] text-zinc-200 p-1.5 rounded border border-zinc-700 focus:outline-none focus:border-purple-600 font-mono text-xs resize-none",
|
||||
rows: 2,
|
||||
onKeyDown: e => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
window.executeAIPrompt();
|
||||
handleAISend();
|
||||
}
|
||||
}
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex items-center gap-1 mt-1"
|
||||
}, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => window.executeAIPrompt(),
|
||||
onClick: handleAISend,
|
||||
disabled: aiProcessing,
|
||||
className: "flex-1 py-1 bg-purple-700 hover:bg-purple-600 text-white font-bold rounded text-xs border border-purple-500 flex items-center justify-center gap-1"
|
||||
}, aiProcessing ? 'Đang suy luận...' : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
||||
@@ -8162,10 +8241,10 @@ const App = () => {
|
||||
className: "px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded text-xs border border-zinc-700"
|
||||
}, "Clear")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs text-zinc-600 mt-0.5"
|
||||
}, "Ctrl+Enter để gửi nhanh")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "border-t border-zinc-800 pt-1.5 mt-1"
|
||||
}, "Enter để gửi nhanh")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "border-t border-zinc-800 pt-1.5 mt-1 flex-1 min-h-0 flex flex-col"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center justify-between"
|
||||
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center justify-between shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center gap-1"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
@@ -8186,9 +8265,9 @@ const App = () => {
|
||||
},
|
||||
className: "text-xs text-amber-400 hover:text-amber-300 border border-amber-800 rounded px-1 py-0.5"
|
||||
}, "Undo"))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 p-1 max-h-[120px]"
|
||||
className: "flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 p-1 select-text"
|
||||
}, aiActionLog.length === 0 ? /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs text-zinc-600 italic"
|
||||
className: "text-xs text-zinc-600 italic select-text"
|
||||
}, "Chưa có hành động nào.") : aiActionLog.map((entry, i) => /*#__PURE__*/React.createElement("div", {
|
||||
key: i,
|
||||
className: `text-xs font-mono py-0.5 border-b border-zinc-900 last:border-0 ${entry.type === 'error' ? 'text-red-400' : entry.type === 'status' ? 'text-zinc-400 italic' : entry.type === 'undo' ? 'text-amber-400' : 'text-zinc-300'}`
|
||||
@@ -9637,7 +9716,12 @@ const App = () => {
|
||||
onClose: () => setProfileModalOpen(false)
|
||||
}), /*#__PURE__*/React.createElement(AIConfigModal, {
|
||||
isOpen: aiConfigModalOpen,
|
||||
onClose: () => setAiConfigModalOpen(false)
|
||||
onClose: () => setAiConfigModalOpen(false),
|
||||
onConfigSaved: (providers) => {
|
||||
setAiProviders(providers);
|
||||
const active = providers.find(p => p.is_active) || providers[0];
|
||||
if (active) setSelectedProviderId(active.id);
|
||||
}
|
||||
}), /*#__PURE__*/React.createElement(SystemManagerModal, {
|
||||
isOpen: systemManagerModalOpen,
|
||||
onClose: () => setSystemManagerModalOpen(false)
|
||||
|
||||
@@ -2481,7 +2481,8 @@ const AuthModal = ({
|
||||
};
|
||||
const AIConfigModal = ({
|
||||
isOpen,
|
||||
onClose
|
||||
onClose,
|
||||
onConfigSaved
|
||||
}) => {
|
||||
if (!isOpen) return null;
|
||||
const defaultProvidersList = [{
|
||||
@@ -2552,6 +2553,7 @@ const AIConfigModal = ({
|
||||
try {
|
||||
const res = await window.SonicAPI.saveAIConfigs(providers);
|
||||
setMsg(res.message || 'Đã lưu cấu hình AI Providers thành công!');
|
||||
if (onConfigSaved) onConfigSaved(providers);
|
||||
} catch (err) {
|
||||
setError(err.message || 'Lỗi khi lưu cấu hình AI');
|
||||
} finally {
|
||||
@@ -2594,7 +2596,38 @@ const AIConfigModal = ({
|
||||
className: "truncate"
|
||||
}, p.name), p.is_active && /*#__PURE__*/React.createElement("span", {
|
||||
className: "w-2 h-2 rounded-full bg-emerald-400"
|
||||
})))), activeProvider && /*#__PURE__*/React.createElement("form", {
|
||||
}))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex gap-1 mt-2"
|
||||
}, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
const newId = 'provider_' + Date.now();
|
||||
setProviders(prev => [...prev, {
|
||||
id: newId,
|
||||
name: 'New Provider',
|
||||
provider_type: 'openai_compatible',
|
||||
api_base_url: 'https://api.openai.com/v1',
|
||||
api_key: '',
|
||||
model_name: 'gpt-4o-mini',
|
||||
temperature: 0.7,
|
||||
is_active: false
|
||||
}]);
|
||||
setSelectedId(newId);
|
||||
},
|
||||
className: "flex-1 px-2 py-1 bg-emerald-700 hover:bg-emerald-600 text-white text-xs font-bold rounded"
|
||||
}, "+ Thêm"), providers.length > 1 && /*#__PURE__*/React.createElement("button", {
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
if (confirm(`Xóa provider "${providers.find(p => p.id === selectedId)?.name}"?`)) {
|
||||
setProviders(prev => {
|
||||
const filtered = prev.filter(p => p.id !== selectedId);
|
||||
if (filtered.length > 0) setSelectedId(filtered[0].id);
|
||||
return filtered;
|
||||
});
|
||||
}
|
||||
},
|
||||
className: "px-2 py-1 bg-red-800 hover:bg-red-700 text-white text-xs font-bold rounded"
|
||||
}, "Xóa"))), activeProvider && /*#__PURE__*/React.createElement("form", {
|
||||
onSubmit: handleSave,
|
||||
className: "col-span-2 space-y-3.5"
|
||||
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
||||
@@ -3004,6 +3037,19 @@ const App = () => {
|
||||
apiKey: localStorage.getItem('ai_api_key') || '',
|
||||
model: localStorage.getItem('ai_model') || 'deepseek-chat'
|
||||
});
|
||||
const [aiProviders, setAiProviders] = useState([]);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const data = await window.SonicAPI.getAIConfigs();
|
||||
if (data && data.providers) {
|
||||
setAiProviders(data.providers);
|
||||
const active = data.providers.find(p => p.is_active) || data.providers[0];
|
||||
if (active) setSelectedProviderId(active.id);
|
||||
}
|
||||
} catch (e) {/* server may not have config endpoint */}
|
||||
})();
|
||||
}, []);
|
||||
const [analysisState, setAnalysisState] = useState({
|
||||
status: 'Sẵn sàng. Chạy AI để phân tích nhịp.',
|
||||
data: null,
|
||||
@@ -3014,6 +3060,7 @@ const App = () => {
|
||||
const [aiModel, setAiModel] = useState('GPT-4o');
|
||||
const [aiActionLog, setAiActionLog] = useState([]);
|
||||
const [aiProcessing, setAiProcessing] = useState(false);
|
||||
const [selectedProviderId, setSelectedProviderId] = useState('');
|
||||
const [exportSettings, setExportSettings] = useState({
|
||||
sampleRate: '44100',
|
||||
bitDepth: '16',
|
||||
@@ -3348,7 +3395,7 @@ const App = () => {
|
||||
const sidebarEl = document.getElementById('right-sidebar');
|
||||
const sidebarHeight = sidebarEl ? sidebarEl.getBoundingClientRect().height : 400;
|
||||
const onMove = ev => {
|
||||
const deltaY = ev.clientY - startY;
|
||||
const deltaY = startY - ev.clientY;
|
||||
const pct = (startHeight / 100 * sidebarHeight + deltaY) / sidebarHeight * 100;
|
||||
const newPct = Math.max(20, Math.min(80, pct));
|
||||
setMediaExplorerHeight(newPct);
|
||||
@@ -6921,6 +6968,94 @@ const App = () => {
|
||||
}, 800);
|
||||
};
|
||||
|
||||
// ── AI Prompt Send to Active Provider ──
|
||||
const handleAISend = async () => {
|
||||
const prompt = aiPrompt.trim();
|
||||
if (!prompt) {
|
||||
showToast('Vui lòng nhập nội dung prompt.', 'warning');
|
||||
return;
|
||||
}
|
||||
setAiProcessing(true);
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'status',
|
||||
text: ` ⏳ Đang gửi prompt đến AI...`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
try {
|
||||
const selectedProvider = aiProviders.find(p => p.id === selectedProviderId) || (aiProviders.length > 0 ? aiProviders[0] : null);
|
||||
const provider = selectedProvider || aiConfig;
|
||||
const baseUrl = provider.api_base_url || provider.baseUrl || `${API_BASE_URL}`;
|
||||
const apiKey = provider.api_key || provider.apiKey || '';
|
||||
const model = provider.model_name || provider.model || 'deepseek-chat';
|
||||
const dawContext = window.AIGateway.buildAIPromptContext({
|
||||
tracks,
|
||||
bpm,
|
||||
selectedTrackId,
|
||||
currentTime,
|
||||
selLeft,
|
||||
selRight
|
||||
});
|
||||
const result = await window.AIGateway.executeAIPrompt({
|
||||
prompt,
|
||||
provider: provider.name || 'default',
|
||||
model,
|
||||
apiKey,
|
||||
baseUrl: baseUrl.replace(/\/chat\/completions$/, '').replace(/\/$/, ''),
|
||||
dawContext,
|
||||
tools: window.AIGateway.DEFAULT_TOOLS
|
||||
});
|
||||
if (result.textResponse) {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'status',
|
||||
text: ` AI: ${result.textResponse}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
}
|
||||
if (result.functionCalls && result.functionCalls.length > 0) {
|
||||
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 {
|
||||
const cmdResult = window.DAWCommandDispatcher.execute(cmdName, fc.arguments);
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'status',
|
||||
text: ` ✅ ${fc.name}: ${cmdResult.success ? 'thành công' : 'thất bại'}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
} catch (cmdErr) {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'error',
|
||||
text: ` ❌ ${fc.name}: ${cmdErr.message}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'status',
|
||||
text: ` Hoàn tất.`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
setAiPrompt('');
|
||||
setTimeout(() => lucide.createIcons(), 200);
|
||||
} catch (err) {
|
||||
setAiActionLog(prev => [...prev, {
|
||||
type: 'error',
|
||||
text: ` Lỗi: ${err.message}`,
|
||||
time: Date.now()
|
||||
}]);
|
||||
showToast(`AI Error: ${err.message}`, 'error');
|
||||
} finally {
|
||||
setAiProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Split Track at Playhead ──
|
||||
const handleSplitTrackAtTime = (trackId, clipId, time) => {
|
||||
const track = tracks.find(t => t.id === trackId);
|
||||
@@ -8174,42 +8309,22 @@ const App = () => {
|
||||
"data-lucide": "x",
|
||||
className: "w-3 h-3"
|
||||
}))))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "p-1.5 bg-[#141414] rounded border border-zinc-800 text-xs font-mono min-h-[20px]"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-zinc-500"
|
||||
}, "// ", /*#__PURE__*/React.createElement("span", {
|
||||
className: "text-zinc-300"
|
||||
}, analysisState.status)), analysisState.data && /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-emerald-500 font-semibold"
|
||||
}, "BPM: ", analysisState.data.bpm)), /*#__PURE__*/React.createElement("div", {
|
||||
className: "grid grid-cols-3 gap-1"
|
||||
}, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleAIScan,
|
||||
className: "py-1 bg-purple-900 hover:bg-purple-800 text-purple-100 font-bold rounded text-xs border border-purple-700 flex items-center justify-center gap-1"
|
||||
className: "flex items-center gap-1.5 shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "map-pin",
|
||||
className: "w-3 h-3"
|
||||
})), " Scan"), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleAICutToNewTrack,
|
||||
disabled: analysisState.isRunning,
|
||||
className: "py-1 bg-fuchsia-800 hover:bg-fuchsia-700 text-white font-bold rounded text-xs border border-fuchsia-600 flex items-center justify-center gap-1"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "scissors",
|
||||
className: "w-3 h-3"
|
||||
})), " Cut"), /*#__PURE__*/React.createElement("button", {
|
||||
onClick: handleAIAnalysicLoop,
|
||||
disabled: analysisState.isRunning,
|
||||
className: "py-1 bg-violet-800 hover:bg-violet-700 text-violet-100 font-bold rounded text-xs border border-violet-600 flex items-center justify-center gap-1"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
"data-lucide": "sparkles",
|
||||
className: "w-3 h-3"
|
||||
})), " Loop")), /*#__PURE__*/React.createElement("div", {
|
||||
"data-lucide": "cpu",
|
||||
className: "w-3 h-3 text-purple-400"
|
||||
})), /*#__PURE__*/React.createElement("select", {
|
||||
value: selectedProviderId,
|
||||
onChange: e => setSelectedProviderId(e.target.value),
|
||||
className: "flex-1 bg-[#141414] text-zinc-200 border border-zinc-700 rounded px-1 py-0.5 text-xs font-mono focus:outline-none focus:border-purple-600"
|
||||
}, aiProviders.length === 0 ? /*#__PURE__*/React.createElement("option", {
|
||||
value: ""
|
||||
}, "Chưa có provider") : aiProviders.map(p => /*#__PURE__*/React.createElement("option", {
|
||||
key: p.id,
|
||||
value: p.id
|
||||
}, p.name, p.is_active ? '' : ' (inactive)')))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "border-t border-zinc-800 pt-1.5 mt-1"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center gap-1"
|
||||
@@ -8225,15 +8340,15 @@ const App = () => {
|
||||
className: "w-full bg-[#1e1e1e] text-zinc-200 p-1.5 rounded border border-zinc-700 focus:outline-none focus:border-purple-600 font-mono text-xs resize-none",
|
||||
rows: 2,
|
||||
onKeyDown: e => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
window.executeAIPrompt();
|
||||
handleAISend();
|
||||
}
|
||||
}
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex items-center gap-1 mt-1"
|
||||
}, /*#__PURE__*/React.createElement("button", {
|
||||
onClick: () => window.executeAIPrompt(),
|
||||
onClick: handleAISend,
|
||||
disabled: aiProcessing,
|
||||
className: "flex-1 py-1 bg-purple-700 hover:bg-purple-600 text-white font-bold rounded text-xs border border-purple-500 flex items-center justify-center gap-1"
|
||||
}, aiProcessing ? 'Đang suy luận...' : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
|
||||
@@ -8249,10 +8364,10 @@ const App = () => {
|
||||
className: "px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 rounded text-xs border border-zinc-700"
|
||||
}, "Clear")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs text-zinc-600 mt-0.5"
|
||||
}, "Ctrl+Enter để gửi nhanh")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "border-t border-zinc-800 pt-1.5 mt-1"
|
||||
}, "Enter để gửi nhanh")), /*#__PURE__*/React.createElement("div", {
|
||||
className: "border-t border-zinc-800 pt-1.5 mt-1 flex-1 min-h-0 flex flex-col"
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center justify-between"
|
||||
className: "text-xs font-bold text-zinc-400 uppercase mb-1 flex items-center justify-between shrink-0"
|
||||
}, /*#__PURE__*/React.createElement("span", {
|
||||
className: "inline-flex items-center gap-1"
|
||||
}, /*#__PURE__*/React.createElement("i", {
|
||||
@@ -8281,9 +8396,9 @@ const App = () => {
|
||||
},
|
||||
className: "text-xs text-amber-400 hover:text-amber-300 border border-amber-800 rounded px-1 py-0.5"
|
||||
}, "Undo"))), /*#__PURE__*/React.createElement("div", {
|
||||
className: "flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 p-1 max-h-[120px]"
|
||||
className: "flex-1 overflow-y-auto no-scrollbar bg-[#0f0f0f] rounded border border-zinc-800 p-1 select-text"
|
||||
}, aiActionLog.length === 0 ? /*#__PURE__*/React.createElement("div", {
|
||||
className: "text-xs text-zinc-600 italic"
|
||||
className: "text-xs text-zinc-600 italic select-text"
|
||||
}, "Chưa có hành động nào.") : aiActionLog.map((entry, i) => /*#__PURE__*/React.createElement("div", {
|
||||
key: i,
|
||||
className: `text-xs font-mono py-0.5 border-b border-zinc-900 last:border-0 ${entry.type === 'error' ? 'text-red-400' : entry.type === 'status' ? 'text-zinc-400 italic' : entry.type === 'undo' ? 'text-amber-400' : 'text-zinc-300'}`
|
||||
@@ -9732,7 +9847,12 @@ const App = () => {
|
||||
onClose: () => setProfileModalOpen(false)
|
||||
}), /*#__PURE__*/React.createElement(AIConfigModal, {
|
||||
isOpen: aiConfigModalOpen,
|
||||
onClose: () => setAiConfigModalOpen(false)
|
||||
onClose: () => setAiConfigModalOpen(false),
|
||||
onConfigSaved: providers => {
|
||||
setAiProviders(providers);
|
||||
const active = providers.find(p => p.is_active) || providers[0];
|
||||
if (active) setSelectedProviderId(active.id);
|
||||
}
|
||||
}), /*#__PURE__*/React.createElement(SystemManagerModal, {
|
||||
isOpen: systemManagerModalOpen,
|
||||
onClose: () => setSystemManagerModalOpen(false)
|
||||
|
||||
@@ -63,8 +63,26 @@ const AIGateway = (function() {
|
||||
}
|
||||
}];
|
||||
|
||||
function parseOrigin(urlStr) {
|
||||
try { const u = new URL(urlStr); return `${u.protocol}//${u.hostname}${u.port ? ':'+u.port : ''}`; } catch (_) { return null; }
|
||||
}
|
||||
|
||||
function isLocalhost(urlStr) {
|
||||
try {
|
||||
const u = new URL(urlStr);
|
||||
return u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '0.0.0.0' || u.hostname === '::1';
|
||||
} catch (_) { return false; }
|
||||
}
|
||||
|
||||
async function callLLM({ provider, model, apiKey, baseUrl, messages, tools, toolChoice }) {
|
||||
const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
|
||||
const base = baseUrl.replace(/\/$/, '');
|
||||
const url = `${base}/chat/completions`;
|
||||
const origin = window.location.origin;
|
||||
const urlOrigin = parseOrigin(url);
|
||||
const appOrigin = parseOrigin(origin);
|
||||
const sameOrigin = urlOrigin === appOrigin;
|
||||
const targetIsLocal = isLocalhost(url);
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {})
|
||||
@@ -77,15 +95,29 @@ const AIGateway = (function() {
|
||||
...(toolChoice ? { tool_choice: toolChoice } : {})
|
||||
};
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
let response;
|
||||
|
||||
if (sameOrigin) {
|
||||
response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
} else if (targetIsLocal && !isLocalhost(origin)) {
|
||||
throw new Error(`AI provider local (${url}) không khả dụng từ domain từ xa (${origin}).\nHãy dùng provider từ xa (OpenAI, Anthropic...) hoặc dùng CORS plugin trình duyệt.`);
|
||||
} else {
|
||||
response = await fetch(`${origin}/api/v1/ai/proxy`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url, headers, body })
|
||||
});
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
throw new Error(`LLM API error ${response.status}: ${errText}`);
|
||||
let detail = errText;
|
||||
try { const j = JSON.parse(errText); if (j.detail) detail = j.detail; } catch (_) {}
|
||||
throw new Error(detail);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
|
||||
Reference in New Issue
Block a user