Fix AI tab save: dedicated AJAX endpoint /save-ai
Instead of relying on the native HTML form (which couldn't reliably isolate AI fields), the IA tab now uses an AJAX call identical to save-per-type. The new /admin/bot-config/save-ai endpoint only reads and merges AI fields into config_json, preserving menus/flows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
5afa846b4e
commit
bb4b5dab10
@@ -3747,7 +3747,7 @@ HTML;
|
||||
<strong>Requiere bot_type = hybrid y proveedor de IA configurado.</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:14px"><button type="submit" class="btn-primary">💾 Guardar IA</button></div>
|
||||
<div style="margin-top:14px"><button type="button" id="saveAiBtn" onclick="saveAiConfig()" class="btn-primary">💾 Guardar IA</button></div>
|
||||
</div>
|
||||
|
||||
<!-- ─── GENERAL ──────────────────────────────────────────────────────── -->
|
||||
@@ -3842,6 +3842,41 @@ function savePerType() {
|
||||
});
|
||||
}
|
||||
|
||||
function saveAiConfig() {
|
||||
const cid = document.querySelector('input[name="company_id"]').value;
|
||||
const fd = new FormData();
|
||||
fd.set('company_id', cid);
|
||||
const qs = sel => document.querySelector(sel);
|
||||
fd.set('ai_provider', qs('select[name="ai_provider"]')?.value || '');
|
||||
fd.set('ai_model', qs('select[name="ai_model"]')?.value || 'gpt-4o-mini');
|
||||
fd.set('ai_temperature', qs('input[name="ai_temperature"]')?.value || '0.7');
|
||||
fd.set('ai_prompt', qs('textarea[name="ai_prompt"]')?.value || '');
|
||||
fd.set('gemini_model', qs('select[name="gemini_model"]')?.value || '');
|
||||
fd.set('claude_model', qs('select[name="claude_model"]')?.value || '');
|
||||
const oKey = qs('input[name="openai_api_key"]')?.value || '';
|
||||
const gKey = qs('input[name="gemini_api_key"]')?.value || '';
|
||||
const cKey = qs('input[name="claude_api_key"]')?.value || '';
|
||||
if (oKey) fd.set('openai_api_key', oKey);
|
||||
if (gKey) fd.set('gemini_api_key', gKey);
|
||||
if (cKey) fd.set('claude_api_key', cKey);
|
||||
if (qs('input[name="ai_for_media"]')?.checked) fd.set('ai_for_media', '1');
|
||||
if (qs('input[name="nlu"]')?.checked) fd.set('nlu', '1');
|
||||
|
||||
const btn = document.getElementById('saveAiBtn');
|
||||
if (btn) { btn.textContent = 'Guardando...'; btn.disabled = true; }
|
||||
fetch('/admin/bot-config/save-ai', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
if (btn) { btn.textContent = '💾 Guardar IA'; btn.disabled = false; }
|
||||
if (d.ok) { btn.textContent = '✅ Guardado'; setTimeout(() => { btn.textContent = '💾 Guardar IA'; }, 2000); }
|
||||
else alert('❌ Error al guardar: ' + JSON.stringify(d));
|
||||
})
|
||||
.catch(e => {
|
||||
if (btn) { btn.textContent = '💾 Guardar IA'; btn.disabled = false; }
|
||||
alert('❌ Error de red: ' + e.message);
|
||||
});
|
||||
}
|
||||
|
||||
function switchTab(tab, btn) {
|
||||
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-btn').forEach(t => t.classList.remove('active'));
|
||||
|
||||
@@ -587,6 +587,53 @@ $routes = [
|
||||
exit;
|
||||
})()],
|
||||
|
||||
// ─── Admin: guardar solo campos IA (endpoint separado) ───────────────────
|
||||
['POST', '/admin/bot-config/save-ai', fn() => (function () {
|
||||
SessionAuth::require();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
$companyId = (int)($_POST['company_id'] ?? 0);
|
||||
if ($companyId === 0) { echo json_encode(['ok' => false, 'error' => 'missing company_id']); exit; }
|
||||
|
||||
$company = CompanyRepository::findById($companyId);
|
||||
if (!$company) { echo json_encode(['ok' => false, 'error' => 'not found']); exit; }
|
||||
|
||||
$config = json_decode($company['config_json'] ?? '{}', true) ?: [];
|
||||
|
||||
$aiProvider = trim($_POST['ai_provider'] ?? '');
|
||||
if ($aiProvider !== '') $config['ai_provider'] = $aiProvider; else unset($config['ai_provider']);
|
||||
|
||||
$aiModel = trim($_POST['ai_model'] ?? '');
|
||||
if ($aiModel !== '') $config['ai_model'] = $aiModel;
|
||||
|
||||
$aiTemp = trim($_POST['ai_temperature'] ?? '');
|
||||
if ($aiTemp !== '') $config['ai_temperature'] = (float)$aiTemp;
|
||||
|
||||
$aiPrompt = trim($_POST['ai_prompt'] ?? '');
|
||||
$config['ai_prompt'] = $aiPrompt;
|
||||
|
||||
$config['ai_for_media'] = isset($_POST['ai_for_media']);
|
||||
$config['nlu'] = isset($_POST['nlu']);
|
||||
|
||||
$openaiKey = trim($_POST['openai_api_key'] ?? '');
|
||||
if ($openaiKey !== '') $config['openai_api_key'] = $openaiKey;
|
||||
|
||||
$geminiKey = trim($_POST['gemini_api_key'] ?? '');
|
||||
if ($geminiKey !== '') $config['gemini_api_key'] = $geminiKey;
|
||||
|
||||
$geminiModel = trim($_POST['gemini_model'] ?? '');
|
||||
if ($geminiModel !== '') $config['gemini_model'] = $geminiModel;
|
||||
|
||||
$claudeKey = trim($_POST['claude_api_key'] ?? '');
|
||||
if ($claudeKey !== '') $config['claude_api_key'] = $claudeKey;
|
||||
|
||||
$claudeModel = trim($_POST['claude_model'] ?? '');
|
||||
if ($claudeModel !== '') $config['claude_model'] = $claudeModel;
|
||||
|
||||
CompanyRepository::save(['id' => $companyId, 'config_json' => json_encode($config, JSON_UNESCAPED_UNICODE)]);
|
||||
echo json_encode(['ok' => true]);
|
||||
exit;
|
||||
})()],
|
||||
|
||||
// ─── Admin: guardar per_type por categoría (endpoint separado) ───────────
|
||||
['POST', '/admin/bot-config/save-per-type', fn() => (function () {
|
||||
SessionAuth::require();
|
||||
|
||||
Reference in New Issue
Block a user