feat(ai): 打通 SEO 闭环与投稿写作辅助
对齐 Zibll 的 AI 生成 SEO 流程:统一 WP AI Client/主题 Provider 双后端、文章/分类 SEO 框与生成回填、投稿摘要/标签建议,并收敛扩展页为连接配置。
This commit is contained in:
1 parent
a4cd5276e2
commit
b36186da65
14 files changed
+1243
-330
No files matched your search
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* AKSRT Admin AI SEO — generate button for post/term SEO fields
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (!window.aksrtAiSeo) return;
|
||||
|
||||
var CFG = window.aksrtAiSeo;
|
||||
var REST = (CFG.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, '');
|
||||
var NONCE = CFG.nonce || '';
|
||||
var I18N = CFG.i18n || {};
|
||||
|
||||
function notify($btn, msg, type) {
|
||||
var wrap = $btn.closest('.aksrt-seo-meta-box, td, .form-field, p') || $btn.parentNode;
|
||||
if (!wrap) return;
|
||||
var old = wrap.querySelector('.aksrt-ai-seo-msg');
|
||||
if (old) old.remove();
|
||||
var el = document.createElement('span');
|
||||
el.className = 'aksrt-ai-seo-msg ' + (type === 'error' ? 'is-error' : 'is-ok');
|
||||
el.textContent = msg;
|
||||
wrap.appendChild(el);
|
||||
setTimeout(function () {
|
||||
if (el.parentNode) el.parentNode.removeChild(el);
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
function fillPost(data) {
|
||||
var map = {
|
||||
title: 'aksrt_seo_title',
|
||||
keywords: 'aksrt_seo_keywords',
|
||||
description: 'aksrt_seo_description',
|
||||
};
|
||||
Object.keys(map).forEach(function (key) {
|
||||
var field = data[key] || {};
|
||||
if (!field.content) return;
|
||||
var input = document.getElementById(map[key]) || document.querySelector('[name="' + map[key] + '"]');
|
||||
if (input) input.value = field.content;
|
||||
});
|
||||
}
|
||||
|
||||
function fillTerm(data) {
|
||||
var map = {
|
||||
title: '.aksrt-term-seo-title, #aksrt_seo_title',
|
||||
keywords: '.aksrt-term-seo-keywords, #aksrt_seo_keywords',
|
||||
description: '.aksrt-term-seo-description, #aksrt_seo_description',
|
||||
};
|
||||
Object.keys(map).forEach(function (key) {
|
||||
var field = data[key] || {};
|
||||
if (!field.content) return;
|
||||
var input = document.querySelector(map[key]);
|
||||
if (input) input.value = field.content;
|
||||
});
|
||||
}
|
||||
|
||||
function collectErrors(data) {
|
||||
var errors = [];
|
||||
['title', 'keywords', 'description'].forEach(function (key) {
|
||||
if (data[key] && data[key].error) errors.push(data[key].error);
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('.aksrt-ai-seo-generate');
|
||||
if (!btn) return;
|
||||
e.preventDefault();
|
||||
|
||||
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');
|
||||
return;
|
||||
}
|
||||
|
||||
var original = btn.textContent;
|
||||
btn.classList.add('is-loading');
|
||||
btn.textContent = I18N.generating || '生成中…';
|
||||
|
||||
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) {
|
||||
btn.classList.remove('is-loading');
|
||||
btn.textContent = original;
|
||||
|
||||
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');
|
||||
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');
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
btn.classList.remove('is-loading');
|
||||
btn.textContent = original;
|
||||
notify(btn, I18N.failed || '生成失败', 'error');
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in new issue
Block a user