feat(ai): 后台摘要生成与 SEO 设置文案补齐

独立 SEO 框增加 AI 生成摘要写入 excerpt;全局 SEO schema 补充 AI 开关说明与提示词占位。
This commit is contained in:
2026-07-12 15:41:18 +08:00
parent b36186da65
commit 8fe708bf1e
4 changed files with 267 additions and 60 deletions
+165 -35
View File
@@ -1,5 +1,5 @@
/**
* AKSRT Admin AI SEO — generate button for post/term SEO fields
* AKSRT Admin AI SEO — SEO generate + summary → excerpt
*/
(function () {
'use strict';
@@ -12,7 +12,7 @@
var I18N = CFG.i18n || {};
function notify($btn, msg, type) {
var wrap = $btn.closest('.aksrt-seo-meta-box, td, .form-field, p') || $btn.parentNode;
var wrap = $btn.closest('.aksrt-seo-meta-box, td, .form-field, p, .aksrt-ai-summary-row') || $btn.parentNode;
if (!wrap) return;
var old = wrap.querySelector('.aksrt-ai-seo-msg');
if (old) old.remove();
@@ -61,36 +61,174 @@
return errors;
}
function getEditorTitle() {
var titleInput = document.getElementById('title');
if (titleInput && titleInput.value) return titleInput.value.trim();
// Block editor
try {
if (window.wp && wp.data && wp.data.select) {
var ed = wp.data.select('core/editor');
if (ed && ed.getEditedPostAttribute) {
return String(ed.getEditedPostAttribute('title') || '').trim();
}
}
} catch (e) { /* ignore */ }
return '';
}
function getEditorContent() {
var content = '';
var ta = document.getElementById('content');
if (ta && ta.value) content = ta.value;
try {
if (window.wp && wp.data && wp.data.select) {
var ed = wp.data.select('core/editor');
if (ed && ed.getEditedPostContent) {
var blockContent = ed.getEditedPostContent();
if (blockContent) content = blockContent;
}
}
} catch (e) { /* ignore */ }
// Strip tags for length check / API
var div = document.createElement('div');
div.innerHTML = content || '';
return (div.textContent || div.innerText || content || '').trim();
}
function setExcerptValue(summary) {
if (!summary) return false;
// Classic: #excerpt
var classic = document.getElementById('excerpt');
if (classic) {
classic.value = summary;
classic.dispatchEvent(new Event('input', { bubbles: true }));
classic.dispatchEvent(new Event('change', { bubbles: true }));
return true;
}
// Block editor excerpt attribute
try {
if (window.wp && wp.data && wp.data.dispatch) {
var d = wp.data.dispatch('core/editor');
if (d && d.editPost) {
d.editPost({ excerpt: summary });
return true;
}
}
} catch (e) { /* ignore */ }
// Fallback name selector
var named = document.querySelector('textarea[name="excerpt"]');
if (named) {
named.value = summary;
named.dispatchEvent(new Event('input', { bubbles: true }));
return true;
}
return false;
}
function setLoading(btn, on, original) {
if (on) {
btn.classList.add('is-loading');
btn.textContent = I18N.generating || '生成中…';
} else {
btn.classList.remove('is-loading');
btn.textContent = original;
}
}
document.addEventListener('click', function (e) {
var btn = e.target.closest('.aksrt-ai-seo-generate');
if (!btn) return;
// SEO generate
var seoBtn = e.target.closest('.aksrt-ai-seo-generate');
if (seoBtn) {
e.preventDefault();
if (seoBtn.classList.contains('is-loading') || seoBtn.disabled) return;
var type = seoBtn.getAttribute('data-type') || 'post';
var id = parseInt(seoBtn.getAttribute('data-id') || '0', 10);
if (!id) {
notify(seoBtn, I18N.needSave || '请先保存后再生成 SEO', 'error');
return;
}
if (CFG.ai && CFG.ai.available === false) {
notify(seoBtn, I18N.notAvailable || 'AI 未配置或未启用', 'error');
return;
}
var seoOriginal = seoBtn.textContent;
setLoading(seoBtn, true, seoOriginal);
fetch(REST + '/ai/seo-generate', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
},
body: JSON.stringify({ type: type, id: id }),
})
.then(function (r) {
return r.json().then(function (json) {
return { ok: r.ok, json: json };
});
})
.then(function (res) {
setLoading(seoBtn, false, seoOriginal);
if (!res.ok || !res.json || res.json.success === false) {
var msg =
(res.json && (res.json.message || (res.json.data && res.json.data.message))) ||
I18N.failed ||
'生成失败';
notify(seoBtn, msg, 'error');
return;
}
var data = res.json.data || {};
if (type === 'term') fillTerm(data);
else fillPost(data);
var errors = collectErrors(data);
if (errors.length) notify(seoBtn, errors.join(';'), 'error');
else notify(seoBtn, I18N.success || '已填入 SEO 字段,请检查后更新保存', 'ok');
})
.catch(function () {
setLoading(seoBtn, false, seoOriginal);
notify(seoBtn, I18N.failed || '生成失败', 'error');
});
return;
}
// Summary → excerpt
var sumBtn = e.target.closest('.aksrt-ai-summary-generate');
if (!sumBtn) return;
e.preventDefault();
if (sumBtn.classList.contains('is-loading') || sumBtn.disabled) return;
if (btn.classList.contains('is-loading') || btn.disabled) return;
var type = btn.getAttribute('data-type') || 'post';
var id = parseInt(btn.getAttribute('data-id') || '0', 10);
if (!id) {
notify(btn, I18N.needSave || '请先保存后再生成 SEO', 'error');
return;
}
if (CFG.ai && CFG.ai.available === false) {
notify(btn, I18N.notAvailable || 'AI 未配置或未启用', 'error');
notify(sumBtn, I18N.notAvailable || 'AI 未配置或未启用', 'error');
return;
}
var original = btn.textContent;
btn.classList.add('is-loading');
btn.textContent = I18N.generating || '生成中…';
var content = getEditorContent();
if (!content) {
notify(sumBtn, I18N.needContent || '请先填写正文内容', 'error');
return;
}
fetch(REST + '/ai/seo-generate', {
var sumOriginal = sumBtn.textContent;
setLoading(sumBtn, true, sumOriginal);
fetch(REST + '/ai/summary-suggest', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
},
body: JSON.stringify({ type: type, id: id }),
body: JSON.stringify({
title: getEditorTitle(),
content: content,
max_length: 200,
}),
})
.then(function (r) {
return r.json().then(function (json) {
@@ -98,33 +236,25 @@
});
})
.then(function (res) {
btn.classList.remove('is-loading');
btn.textContent = original;
setLoading(sumBtn, false, sumOriginal);
if (!res.ok || !res.json || res.json.success === false) {
var msg =
(res.json && (res.json.message || (res.json.data && res.json.data.message))) ||
I18N.failed ||
'生成失败';
notify(btn, msg, 'error');
notify(sumBtn, msg, 'error');
return;
}
var data = res.json.data || {};
if (type === 'term') fillTerm(data);
else fillPost(data);
var errors = collectErrors(data);
if (errors.length) {
notify(btn, errors.join(';'), 'error');
} else {
notify(btn, I18N.success || '已填入 SEO 字段,请检查后更新保存', 'ok');
var summary = (res.json.data && res.json.data.summary) || '';
if (!setExcerptValue(summary)) {
notify(sumBtn, I18N.noExcerptField || '未找到摘要字段,请展开「摘要」面板', 'error');
return;
}
notify(sumBtn, I18N.summarySuccess || '已填入摘要字段,请检查后更新保存', 'ok');
})
.catch(function () {
btn.classList.remove('is-loading');
btn.textContent = original;
notify(btn, I18N.failed || '生成失败', 'error');
setLoading(sumBtn, false, sumOriginal);
notify(sumBtn, I18N.failed || '生成失败', 'error');
});
});
})();