feat & fix : intégration IA éditeur + onglet admin IA + corrections CSP (v1.6.24-25)
- #96 : boutons IA sidebar éditeur (analyse critique / réécriture) via Anthropic API - #97 : onglet admin /admin/ia — provider anthropic/claude_code, modèle, procédure CLI - #95 : extraction scripts inline vers fichiers JS (comments.js, post_confirm.js, admin.js) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,16 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// Boutons data-confirm-discard (évite onclick inline bloqué par CSP)
|
||||
document.querySelectorAll('[data-confirm-discard]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var msg = btn.getAttribute('data-confirm-msg') || 'Confirmer ?';
|
||||
if (window.confirm(msg)) {
|
||||
window.location = btn.getAttribute('data-discard-url');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sélection globale articles
|
||||
var checkAll = document.getElementById('check-all');
|
||||
if (checkAll) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// ai-editor.js — boutons IA dans la sidebar éditeur
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var btnCritique = document.getElementById('btn-ai-critique');
|
||||
var btnRewrite = document.getElementById('btn-ai-rewrite');
|
||||
if (!btnCritique || !btnRewrite) return;
|
||||
|
||||
var panel = document.getElementById('ai-result-panel');
|
||||
var labelEl = document.getElementById('ai-result-label');
|
||||
var resultEl = document.getElementById('ai-result-content');
|
||||
var btnApply = document.getElementById('btn-ai-apply');
|
||||
var btnClose = document.getElementById('btn-ai-close');
|
||||
var ta = document.getElementById('content');
|
||||
var titleEl = document.getElementById('title');
|
||||
|
||||
var lastRewrite = '';
|
||||
|
||||
function setLoading(btn, loading) {
|
||||
btn.disabled = loading;
|
||||
if (loading) {
|
||||
btn._origText = btn.textContent;
|
||||
btn.textContent = 'En cours…';
|
||||
} else {
|
||||
btn.textContent = btn._origText || btn.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
async function callAi(action) {
|
||||
var btn = (action === 'critique') ? btnCritique : btnRewrite;
|
||||
|
||||
setLoading(btn, true);
|
||||
panel.style.display = 'none';
|
||||
btnApply.style.display = 'none';
|
||||
lastRewrite = '';
|
||||
|
||||
try {
|
||||
var res = await fetch('/?action=ai_query', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
action: action,
|
||||
title: titleEl ? titleEl.value : '',
|
||||
content: ta ? ta.value : '',
|
||||
}),
|
||||
});
|
||||
|
||||
var data = await res.json();
|
||||
|
||||
if (!data.ok) {
|
||||
labelEl.textContent = 'Erreur';
|
||||
resultEl.textContent = data.error || 'Erreur inconnue.';
|
||||
} else {
|
||||
labelEl.textContent = (action === 'critique') ? 'Analyse critique' : 'Réécriture';
|
||||
resultEl.textContent = data.text;
|
||||
if (action === 'rewrite') {
|
||||
lastRewrite = data.text;
|
||||
btnApply.style.display = '';
|
||||
}
|
||||
}
|
||||
panel.style.display = '';
|
||||
} catch (e) {
|
||||
labelEl.textContent = 'Erreur';
|
||||
resultEl.textContent = 'Erreur de connexion.';
|
||||
panel.style.display = '';
|
||||
} finally {
|
||||
setLoading(btn, false);
|
||||
}
|
||||
}
|
||||
|
||||
btnCritique.addEventListener('click', function () { callAi('critique'); });
|
||||
btnRewrite.addEventListener('click', function () { callAi('rewrite'); });
|
||||
|
||||
btnApply.addEventListener('click', function () {
|
||||
if (!lastRewrite) return;
|
||||
if (!confirm("Remplacer le contenu de l’éditeur par la réécriture IA ?")) return;
|
||||
if (ta) {
|
||||
ta.value = lastRewrite;
|
||||
ta.dispatchEvent(new Event('input'));
|
||||
}
|
||||
panel.style.display = 'none';
|
||||
btnApply.style.display = 'none';
|
||||
lastRewrite = '';
|
||||
});
|
||||
|
||||
btnClose.addEventListener('click', function () {
|
||||
panel.style.display = 'none';
|
||||
btnApply.style.display = 'none';
|
||||
lastRewrite = '';
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var maxAge = 365 * 24 * 3600;
|
||||
function getCookie(name) {
|
||||
var m = document.cookie.match('(?:^|; )' + name + '=([^;]*)');
|
||||
return m ? decodeURIComponent(m[1]) : '';
|
||||
}
|
||||
function setCookie(name, value) {
|
||||
document.cookie = name + '=' + encodeURIComponent(value) + ';max-age=' + maxAge + ';path=/;SameSite=Lax';
|
||||
}
|
||||
var nameEl = document.getElementById('comment-name');
|
||||
var emailEl = document.getElementById('comment-email');
|
||||
if (!nameEl || !emailEl) { return; }
|
||||
var savedName = getCookie('cmt_name');
|
||||
var savedEmail = getCookie('cmt_email');
|
||||
if (savedName) { nameEl.value = savedName; }
|
||||
if (savedEmail) { emailEl.value = savedEmail; }
|
||||
var form = document.getElementById('comment-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function () {
|
||||
if (nameEl.value.trim()) { setCookie('cmt_name', nameEl.value.trim()); }
|
||||
if (emailEl.value.trim()) { setCookie('cmt_email', emailEl.value.trim()); }
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -40,6 +40,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
var slugInput = document.getElementById('confirm-slug');
|
||||
var slugDisplay = document.getElementById('slug-display');
|
||||
|
||||
if (slugInput && slugDisplay) {
|
||||
slugInput.addEventListener('input', function () {
|
||||
slugDisplay.textContent = slugInput.value;
|
||||
});
|
||||
}
|
||||
|
||||
var btnSuggest = document.getElementById('slug-btn-suggest');
|
||||
if (btnSuggest && slugInput && slugDisplay) {
|
||||
btnSuggest.addEventListener('click', function () {
|
||||
|
||||
+47
-1
@@ -45,7 +45,7 @@ $action = $_GET['action'] ?? 'list';
|
||||
$uuid = $_GET['uuid'] ?? '';
|
||||
$slug = $_GET['slug'] ?? '';
|
||||
|
||||
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links', 'react', 'comment', 'verify_comment', 'comment_moderate', 'comment_delete', 'comment_resend', 'create_tag_type', 'delete_tag_type', 'edit_tags', 'book_save', 'book_delete', 'admin_save_as_groups', 'admin_save_folio_config', 'run_engine_update', 'run_content_migrations', 'admin_delete_feed', 'rate'];
|
||||
$_noindexActions = ['create', 'edit', 'admin', 'categories', 'diff', 'add_files', 'import_image', 'import_image_step2', 'sources', 'profile', 'delete_file', 'delete_external_link', 'rename_category', 'delete_category', 'toggle_private_category', 'admin_save_site', 'not_found', 'add_feed', 'delete_feed', 'add_link', 'delete_link', 'reorder_links', 'react', 'comment', 'verify_comment', 'comment_moderate', 'comment_delete', 'comment_resend', 'create_tag_type', 'delete_tag_type', 'edit_tags', 'book_save', 'book_delete', 'admin_save_as_groups', 'admin_save_folio_config', 'run_engine_update', 'run_content_migrations', 'admin_delete_feed', 'rate', 'admin_save_ai_config'];
|
||||
$metaRobots = in_array($action, $_noindexActions, true) ? 'noindex, nofollow' : null;
|
||||
unset($_noindexActions);
|
||||
|
||||
@@ -1688,6 +1688,24 @@ switch ($action) {
|
||||
echo json_encode(fetchUrlMeta(trim($_GET['url'] ?? '')));
|
||||
exit;
|
||||
|
||||
case 'ai_query':
|
||||
requireAuth();
|
||||
header('Content-Type: application/json');
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
echo json_encode(['ok' => false, 'error' => 'Méthode invalide']);
|
||||
exit;
|
||||
}
|
||||
$_aiAction = trim($_POST['action'] ?? '');
|
||||
$_aiTitle = trim($_POST['title'] ?? '');
|
||||
$_aiContent = str_replace("\r\n", "\n", trim($_POST['content'] ?? ''));
|
||||
if (!in_array($_aiAction, ['critique', 'rewrite'], true) || $_aiContent === '') {
|
||||
echo json_encode(['ok' => false, 'error' => 'Paramètres invalides']);
|
||||
exit;
|
||||
}
|
||||
require_once BASE_PATH . '/src/Service/AiService.php';
|
||||
echo json_encode((new AiService())->query($_aiAction, $_aiTitle, $_aiContent));
|
||||
exit;
|
||||
|
||||
case 'import_image_step2':
|
||||
requireAuth();
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
@@ -2765,9 +2783,37 @@ switch ($action) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($tab === 'ia') {
|
||||
if (!isAdmin()) { http_response_code(403); exit; }
|
||||
require_once BASE_PATH . '/src/SiteSettings.php';
|
||||
require_once BASE_PATH . '/src/Service/AiService.php';
|
||||
$adminData['ai_provider'] = aiProvider();
|
||||
$adminData['ai_model'] = aiModel();
|
||||
$adminData['anthropic_key_set'] = (($_ENV['ANTHROPIC_API_KEY'] ?? getenv('ANTHROPIC_API_KEY') ?: '') !== '');
|
||||
$adminData['claude_cli_found'] = is_executable('/usr/local/bin/claude');
|
||||
$adminData['ai_notice'] = $_GET['notice'] ?? '';
|
||||
}
|
||||
|
||||
include BASE_PATH . '/templates/admin.php';
|
||||
break;
|
||||
|
||||
case 'admin_save_ai_config':
|
||||
requireAuth();
|
||||
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(403); exit;
|
||||
}
|
||||
require_once BASE_PATH . '/src/SiteSettings.php';
|
||||
$allowedProviders = ['anthropic', 'claude_code'];
|
||||
$aiProvider = in_array($_POST['ai_provider'] ?? '', $allowedProviders, true)
|
||||
? $_POST['ai_provider']
|
||||
: 'anthropic';
|
||||
$ok = saveSiteSettings([
|
||||
'ai_provider' => $aiProvider,
|
||||
'ai_model' => trim($_POST['ai_model'] ?? ''),
|
||||
]);
|
||||
header('Location: /admin/ia?notice=' . ($ok ? 'saved' : 'error'));
|
||||
exit;
|
||||
|
||||
case 'admin_smtp_save':
|
||||
requireAuth();
|
||||
if (!isAdmin()) {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.6.23
|
||||
1.6.25
|
||||
|
||||
Reference in New Issue
Block a user