Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9df41ca85c | |||
| 934cbf0f73 | |||
| 5cc6e869bf | |||
| 79d411f35a | |||
| 58a9b37ccf | |||
| 5b256f1374 | |||
| d878bb7805 | |||
| 43ddbddd11 | |||
| 7b9f26e966 |
@@ -33,7 +33,7 @@ class SitiWebUpdater {
|
|||||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->plugin = get_plugin_data( $this->file );
|
$this->plugin = get_plugin_data( $this->file, false, false );
|
||||||
$this->basename = plugin_basename( $this->file );
|
$this->basename = plugin_basename( $this->file );
|
||||||
$this->active = is_plugin_active( $this->basename );
|
$this->active = is_plugin_active( $this->basename );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,3 +29,80 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-panel {
|
||||||
|
margin: 16px 0;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dcdcde;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-panel .description {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#groq-ai-bulk-status {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#groq-ai-bulk-status[data-status='error'] {
|
||||||
|
color: #b32d2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#groq-ai-bulk-status[data-status='success'] {
|
||||||
|
color: #008a20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-log {
|
||||||
|
margin: 12px 0 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-log li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-log li[data-status='error'] {
|
||||||
|
color: #b32d2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-bulk-log li[data-status='success'] {
|
||||||
|
color: #008a20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-term-row.groq-ai-term-missing td {
|
||||||
|
background: #fff8e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-term-row.groq-ai-term-updated td {
|
||||||
|
animation: groqAiTermPulse 1.8s ease-out 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes groqAiTermPulse {
|
||||||
|
from {
|
||||||
|
background-color: #e3f8eb;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-term-actions {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.groq-ai-regenerate-term.is-busy {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
const resultField = document.getElementById('groq-ai-output');
|
const resultField = document.getElementById('groq-ai-output');
|
||||||
const jsonCopyButton = modal.querySelector('.groq-ai-copy-json');
|
const jsonCopyButton = modal.querySelector('.groq-ai-copy-json');
|
||||||
const contextToggles = modal.querySelectorAll('.groq-ai-context-toggle');
|
const contextToggles = modal.querySelectorAll('.groq-ai-context-toggle');
|
||||||
|
const attributeToggles = modal.querySelectorAll('.groq-ai-attribute-toggle');
|
||||||
const resultFields = {};
|
const resultFields = {};
|
||||||
modal.querySelectorAll('.groq-ai-result-field').forEach((field) => {
|
modal.querySelectorAll('.groq-ai-result-field').forEach((field) => {
|
||||||
const key = field.getAttribute('data-field');
|
const key = field.getAttribute('data-field');
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
promptField.value = GroqAIGenerator.defaultPrompt;
|
promptField.value = GroqAIGenerator.defaultPrompt;
|
||||||
}
|
}
|
||||||
resetContextToggles();
|
resetContextToggles();
|
||||||
|
resetAttributeToggles();
|
||||||
setTimeout(() => promptField.focus(), 50);
|
setTimeout(() => promptField.focus(), 50);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +117,7 @@
|
|||||||
payload.append('prompt', prompt);
|
payload.append('prompt', prompt);
|
||||||
payload.append('post_id', GroqAIGenerator.postId || 0);
|
payload.append('post_id', GroqAIGenerator.postId || 0);
|
||||||
payload.append('context_fields', JSON.stringify(collectContextSelection()));
|
payload.append('context_fields', JSON.stringify(collectContextSelection()));
|
||||||
|
payload.append('attribute_includes', JSON.stringify(collectAttributeSelection()));
|
||||||
|
|
||||||
toggleLoading(true);
|
toggleLoading(true);
|
||||||
resultWrapper.hidden = true;
|
resultWrapper.hidden = true;
|
||||||
@@ -458,6 +461,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetAttributeToggles() {
|
||||||
|
const defaults = Array.isArray(GroqAIGenerator.attributeIncludesDefaults)
|
||||||
|
? GroqAIGenerator.attributeIncludesDefaults
|
||||||
|
: [];
|
||||||
|
|
||||||
|
attributeToggles.forEach((toggle) => {
|
||||||
|
const key = toggle.getAttribute('data-attribute');
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toggle.checked = defaults.includes(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function collectContextSelection() {
|
function collectContextSelection() {
|
||||||
const selected = [];
|
const selected = [];
|
||||||
contextToggles.forEach((toggle) => {
|
contextToggles.forEach((toggle) => {
|
||||||
@@ -467,4 +484,18 @@
|
|||||||
});
|
});
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collectAttributeSelection() {
|
||||||
|
const selected = [];
|
||||||
|
attributeToggles.forEach((toggle) => {
|
||||||
|
if (!toggle.checked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = toggle.getAttribute('data-attribute');
|
||||||
|
if (key) {
|
||||||
|
selected.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
})(jQuery);
|
})(jQuery);
|
||||||
|
|||||||
@@ -9,7 +9,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const promptField = document.getElementById('groq-ai-term-prompt');
|
const promptField = document.getElementById('groq-ai-term-prompt');
|
||||||
const outputField = document.getElementById('groq-ai-term-generated');
|
const outputTopField = document.getElementById('groq-ai-term-generated-top');
|
||||||
|
const outputBottomField = document.getElementById('groq-ai-term-generated-bottom');
|
||||||
|
const outputMetaTitleField = document.getElementById('groq-ai-term-generated-meta-title');
|
||||||
|
const outputMetaDescriptionField = document.getElementById('groq-ai-term-generated-meta-description');
|
||||||
|
const outputFocusKeywordsField = document.getElementById('groq-ai-term-generated-focus-keywords');
|
||||||
const rawField = document.getElementById('groq-ai-term-raw');
|
const rawField = document.getElementById('groq-ai-term-raw');
|
||||||
const statusField = document.getElementById('groq-ai-term-status');
|
const statusField = document.getElementById('groq-ai-term-status');
|
||||||
const applyButton = document.getElementById('groq-ai-term-apply');
|
const applyButton = document.getElementById('groq-ai-term-apply');
|
||||||
@@ -48,14 +52,27 @@
|
|||||||
applyButton.addEventListener('click', () => {
|
applyButton.addEventListener('click', () => {
|
||||||
const descriptionField = document.getElementById('description');
|
const descriptionField = document.getElementById('description');
|
||||||
const bottomDescriptionField = document.getElementById('groq-ai-term-bottom-description');
|
const bottomDescriptionField = document.getElementById('groq-ai-term-bottom-description');
|
||||||
if (!outputField) {
|
const rankmathTitleField = document.getElementById('groq-ai-rankmath-title');
|
||||||
|
const rankmathDescriptionField = document.getElementById('groq-ai-rankmath-description');
|
||||||
|
const rankmathKeywordsField = document.getElementById('groq-ai-rankmath-keywords');
|
||||||
|
if (!outputTopField) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bottomDescriptionField) {
|
if (descriptionField) {
|
||||||
bottomDescriptionField.value = outputField.value || '';
|
descriptionField.value = outputTopField.value || '';
|
||||||
} else if (descriptionField) {
|
}
|
||||||
descriptionField.value = outputField.value || '';
|
if (bottomDescriptionField && outputBottomField) {
|
||||||
|
bottomDescriptionField.value = outputBottomField.value || '';
|
||||||
|
}
|
||||||
|
if (rankmathTitleField && outputMetaTitleField) {
|
||||||
|
rankmathTitleField.value = outputMetaTitleField.value || '';
|
||||||
|
}
|
||||||
|
if (rankmathDescriptionField && outputMetaDescriptionField) {
|
||||||
|
rankmathDescriptionField.value = outputMetaDescriptionField.value || '';
|
||||||
|
}
|
||||||
|
if (rankmathKeywordsField && outputFocusKeywordsField) {
|
||||||
|
rankmathKeywordsField.value = outputFocusKeywordsField.value || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus('Tekst ingevuld. Vergeet niet op "Opslaan" te klikken.', 'success');
|
setStatus('Tekst ingevuld. Vergeet niet op "Opslaan" te klikken.', 'success');
|
||||||
@@ -76,9 +93,11 @@
|
|||||||
rawField.textContent = '';
|
rawField.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputField) {
|
if (outputTopField) outputTopField.value = '';
|
||||||
outputField.value = '';
|
if (outputBottomField) outputBottomField.value = '';
|
||||||
}
|
if (outputMetaTitleField) outputMetaTitleField.value = '';
|
||||||
|
if (outputMetaDescriptionField) outputMetaDescriptionField.value = '';
|
||||||
|
if (outputFocusKeywordsField) outputFocusKeywordsField.value = '';
|
||||||
|
|
||||||
fetch(GroqAITermGenerator.ajaxUrl, {
|
fetch(GroqAITermGenerator.ajaxUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -94,9 +113,25 @@
|
|||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputField) {
|
if (outputTopField) {
|
||||||
const text = json.data && json.data.description ? json.data.description : '';
|
const top = json.data && (json.data.top_description || json.data.description) ? (json.data.top_description || json.data.description) : '';
|
||||||
outputField.value = String(text).trim();
|
outputTopField.value = String(top).trim();
|
||||||
|
}
|
||||||
|
if (outputBottomField) {
|
||||||
|
const bottom = json.data && json.data.bottom_description ? json.data.bottom_description : '';
|
||||||
|
outputBottomField.value = String(bottom).trim();
|
||||||
|
}
|
||||||
|
if (outputMetaTitleField) {
|
||||||
|
const metaTitle = json.data && json.data.meta_title ? json.data.meta_title : '';
|
||||||
|
outputMetaTitleField.value = String(metaTitle).trim();
|
||||||
|
}
|
||||||
|
if (outputMetaDescriptionField) {
|
||||||
|
const metaDescription = json.data && json.data.meta_description ? json.data.meta_description : '';
|
||||||
|
outputMetaDescriptionField.value = String(metaDescription).trim();
|
||||||
|
}
|
||||||
|
if (outputFocusKeywordsField) {
|
||||||
|
const keywords = json.data && json.data.focus_keywords ? json.data.focus_keywords : '';
|
||||||
|
outputFocusKeywordsField.value = String(keywords).trim();
|
||||||
}
|
}
|
||||||
if (rawField) {
|
if (rawField) {
|
||||||
rawField.textContent = (json.data && json.data.raw ? String(json.data.raw) : '').trim();
|
rawField.textContent = (json.data && json.data.raw ? String(json.data.raw) : '').trim();
|
||||||
|
|||||||
288
assets/js/term-bulk.js
Normal file
288
assets/js/term-bulk.js
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
(function () {
|
||||||
|
const data = window.GroqAITermBulk || {};
|
||||||
|
const startButton = document.getElementById('groq-ai-bulk-generate');
|
||||||
|
const stopButton = document.getElementById('groq-ai-bulk-cancel');
|
||||||
|
const statusField = document.getElementById('groq-ai-bulk-status');
|
||||||
|
const logList = document.getElementById('groq-ai-bulk-log');
|
||||||
|
|
||||||
|
if (!data.ajaxUrl || !startButton || !statusField || !logList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strings = data.strings || {};
|
||||||
|
const allowRegenerate = !!data.allowRegenerate;
|
||||||
|
const terms = (Array.isArray(data.terms) ? data.terms : [])
|
||||||
|
.map((term) => {
|
||||||
|
const id = parseInt(term.id, 10);
|
||||||
|
if (!Number.isFinite(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const words = typeof term.words === 'number' ? term.words : parseInt(term.words, 10) || 0;
|
||||||
|
const hasDescription = !!term.hasDescription;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: term.name || '',
|
||||||
|
slug: term.slug || '',
|
||||||
|
count: typeof term.count === 'number' ? term.count : parseInt(term.count, 10) || 0,
|
||||||
|
words,
|
||||||
|
hasDescription,
|
||||||
|
needsGeneration: !hasDescription,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const termMap = new Map();
|
||||||
|
terms.forEach((term) => termMap.set(term.id, term));
|
||||||
|
|
||||||
|
let queue = [];
|
||||||
|
let totalCount = 0;
|
||||||
|
let processed = 0;
|
||||||
|
let successes = 0;
|
||||||
|
let isRunning = false;
|
||||||
|
let abortRequested = false;
|
||||||
|
|
||||||
|
function formatString(template, values) {
|
||||||
|
if (!template) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let autoIndex = 0;
|
||||||
|
return template.replace(/%(\d+\$)?[sd]/g, (match, position) => {
|
||||||
|
let valueIndex;
|
||||||
|
if (position) {
|
||||||
|
valueIndex = parseInt(position, 10) - 1;
|
||||||
|
} else {
|
||||||
|
valueIndex = autoIndex;
|
||||||
|
autoIndex += 1;
|
||||||
|
}
|
||||||
|
const replacement = values[valueIndex];
|
||||||
|
return typeof replacement === 'undefined' ? '' : String(replacement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(message, type) {
|
||||||
|
statusField.textContent = message || '';
|
||||||
|
statusField.dataset.status = type || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendLog(message, type) {
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = document.createElement('li');
|
||||||
|
item.textContent = message;
|
||||||
|
item.dataset.status = type || '';
|
||||||
|
logList.appendChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetLog() {
|
||||||
|
logList.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleButtons(running) {
|
||||||
|
isRunning = running;
|
||||||
|
startButton.disabled = running;
|
||||||
|
if (stopButton) {
|
||||||
|
stopButton.hidden = !running;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPendingTerms() {
|
||||||
|
return terms.filter((term) => term.needsGeneration);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRow(term) {
|
||||||
|
const row = document.querySelector('[data-groq-ai-term-id="' + term.id + '"]');
|
||||||
|
if (!row) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
row.classList.remove('groq-ai-term-missing');
|
||||||
|
row.classList.add('groq-ai-term-updated');
|
||||||
|
const wordCell = row.querySelector('.groq-ai-word-count');
|
||||||
|
if (wordCell) {
|
||||||
|
wordCell.textContent = String(term.words);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function markTermCompleted(term, words) {
|
||||||
|
term.hasDescription = true;
|
||||||
|
term.needsGeneration = false;
|
||||||
|
if (Number.isFinite(words)) {
|
||||||
|
term.words = words;
|
||||||
|
}
|
||||||
|
updateRow(term);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish(state) {
|
||||||
|
const summaryTemplate = state === 'done' ? strings.statusDone : state === 'stopped' ? strings.statusStopped : '';
|
||||||
|
const summary = summaryTemplate ? formatString(summaryTemplate, [successes]) : '';
|
||||||
|
const statusType = state === 'done' ? 'success' : state === 'stopped' ? 'info' : '';
|
||||||
|
setStatus(summary, statusType);
|
||||||
|
toggleButtons(false);
|
||||||
|
queue = [];
|
||||||
|
totalCount = 0;
|
||||||
|
processed = 0;
|
||||||
|
successes = 0;
|
||||||
|
abortRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendRequest(term, options = {}) {
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('action', 'groq_ai_bulk_generate_terms');
|
||||||
|
payload.append('nonce', data.nonce || '');
|
||||||
|
payload.append('taxonomy', data.taxonomy || '');
|
||||||
|
payload.append('term_id', term.id);
|
||||||
|
if (options.force) {
|
||||||
|
payload.append('force', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(data.ajaxUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
},
|
||||||
|
body: payload.toString(),
|
||||||
|
}).then((response) => response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResponse(term, json, context) {
|
||||||
|
if (!json || !json.success) {
|
||||||
|
const errorMessage = (json && json.data && json.data.message) || 'Onbekende fout';
|
||||||
|
appendLog(formatString(strings.logError || '%1$s: %2$s', [term.name || term.id, errorMessage]), 'error');
|
||||||
|
if (context === 'single') {
|
||||||
|
setStatus(formatString(strings.regenerateError || '%1$s mislukt: %2$s', [term.name || term.id, errorMessage]), 'error');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = json.data && typeof json.data.words !== 'undefined' ? parseInt(json.data.words, 10) : term.words;
|
||||||
|
markTermCompleted(term, Number.isFinite(words) ? words : term.words);
|
||||||
|
appendLog(formatString(strings.logSuccess || '%1$s gevuld.', [term.name || term.id, term.words]), 'success');
|
||||||
|
if (context === 'single') {
|
||||||
|
setStatus(formatString(strings.regenerateDone || '%s is bijgewerkt.', [term.name || term.id]), 'success');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processNext() {
|
||||||
|
if (abortRequested) {
|
||||||
|
finish('stopped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queue.length) {
|
||||||
|
finish('done');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const term = queue.shift();
|
||||||
|
const progressTemplate = strings.statusProgress;
|
||||||
|
if (progressTemplate) {
|
||||||
|
setStatus(formatString(progressTemplate, [processed + 1, totalCount, term.name || '']), 'loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest(term)
|
||||||
|
.then((json) => {
|
||||||
|
if (handleResponse(term, json, 'bulk')) {
|
||||||
|
successes += 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
appendLog(
|
||||||
|
formatString(strings.logError || '%1$s: %2$s', [term.name || term.id, error && error.message ? error.message : 'Onbekende fout']),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
processed += 1;
|
||||||
|
if (abortRequested) {
|
||||||
|
finish('stopped');
|
||||||
|
} else {
|
||||||
|
processNext();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startBulk() {
|
||||||
|
if (isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pending = getPendingTerms();
|
||||||
|
if (!pending.length) {
|
||||||
|
setStatus(strings.statusEmpty || '', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue = pending.slice();
|
||||||
|
totalCount = queue.length;
|
||||||
|
processed = 0;
|
||||||
|
successes = 0;
|
||||||
|
abortRequested = false;
|
||||||
|
resetLog();
|
||||||
|
toggleButtons(true);
|
||||||
|
if (strings.statusIdle) {
|
||||||
|
setStatus(strings.statusIdle, 'info');
|
||||||
|
}
|
||||||
|
processNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
startButton.addEventListener('click', startBulk);
|
||||||
|
|
||||||
|
if (stopButton) {
|
||||||
|
stopButton.addEventListener('click', () => {
|
||||||
|
if (!isRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const confirmation = strings.confirmStop ? window.confirm(strings.confirmStop) : window.confirm('Stoppen?');
|
||||||
|
if (confirmation) {
|
||||||
|
abortRequested = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowRegenerate) {
|
||||||
|
const buttons = document.querySelectorAll('.groq-ai-regenerate-term');
|
||||||
|
buttons.forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if (isRunning) {
|
||||||
|
setStatus(strings.regenerateBlocked || '', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const termId = parseInt(button.getAttribute('data-term-id'), 10);
|
||||||
|
const term = termMap.get(termId);
|
||||||
|
if (!term) {
|
||||||
|
setStatus('Onbekende term.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strings.confirmRegenerate) {
|
||||||
|
const confirmed = window.confirm(formatString(strings.confirmRegenerate, [term.name || term.id]));
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button.classList.add('is-busy');
|
||||||
|
button.disabled = true;
|
||||||
|
if (strings.regenerateProgress) {
|
||||||
|
setStatus(formatString(strings.regenerateProgress, [term.name || term.id]), 'loading');
|
||||||
|
}
|
||||||
|
sendRequest(term, { force: true })
|
||||||
|
.then((json) => {
|
||||||
|
handleResponse(term, json, 'single');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const message = error && error.message ? error.message : 'Onbekende fout';
|
||||||
|
appendLog(formatString(strings.logError || '%1$s: %2$s', [term.name || term.id, message]), 'error');
|
||||||
|
setStatus(formatString(strings.regenerateError || '%1$s mislukt: %2$s', [term.name || term.id, message]), 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
button.disabled = false;
|
||||||
|
button.classList.remove('is-busy');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getPendingTerms().length === 0) {
|
||||||
|
setStatus(strings.statusEmpty || '', 'info');
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* Plugin Name: SitiAI Product Teksten
|
* Plugin Name: SitiAI Product Teksten
|
||||||
* Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce.
|
* Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce.
|
||||||
* Version: 1.4.3
|
* Version: 1.6.4
|
||||||
* Author: SitiAI
|
* Author: SitiAI
|
||||||
* Text Domain: siti-ai-product-content-generator
|
* Text Domain: siti-ai-product-content-generator
|
||||||
* Domain Path: /languages
|
* Domain Path: /languages
|
||||||
@@ -108,7 +108,6 @@ final class Groq_AI_Product_Text_Plugin {
|
|||||||
$this->settings_page = new Groq_AI_Product_Text_Settings_Page( $this, $this->get_provider_manager() );
|
$this->settings_page = new Groq_AI_Product_Text_Settings_Page( $this, $this->get_provider_manager() );
|
||||||
$this->product_ui = new Groq_AI_Product_Text_Product_UI( $this );
|
$this->product_ui = new Groq_AI_Product_Text_Product_UI( $this );
|
||||||
|
|
||||||
add_action( 'plugins_loaded', [ $this, 'maybe_load_textdomain_early' ], 0 );
|
|
||||||
add_action( 'init', [ $this, 'load_textdomain' ] );
|
add_action( 'init', [ $this, 'load_textdomain' ] );
|
||||||
add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] );
|
add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] );
|
||||||
add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] );
|
add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] );
|
||||||
@@ -131,14 +130,6 @@ final class Groq_AI_Product_Text_Plugin {
|
|||||||
$this->textdomain_loaded = true;
|
$this->textdomain_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function maybe_load_textdomain_early() {
|
|
||||||
if ( did_action( 'init' ) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->load_textdomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function register_services() {
|
private function register_services() {
|
||||||
$this->container = new Groq_AI_Service_Container();
|
$this->container = new Groq_AI_Service_Container();
|
||||||
|
|
||||||
|
|||||||
@@ -136,28 +136,15 @@ class Groq_AI_Logs_Table extends WP_List_Table {
|
|||||||
|
|
||||||
protected function column_created_at( $item ) {
|
protected function column_created_at( $item ) {
|
||||||
$date = esc_html( mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $item['created_at'] ) );
|
$date = esc_html( mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $item['created_at'] ) );
|
||||||
$usage = $this->get_usage_meta( $item );
|
$url = add_query_arg(
|
||||||
$payload = [
|
[
|
||||||
'created_at' => $item['created_at'],
|
'page' => 'groq-ai-product-text-log',
|
||||||
'user' => $this->column_default( $item, 'user_id' ),
|
'log_id' => isset( $item['id'] ) ? (int) $item['id'] : 0,
|
||||||
'post_title' => $item['post_title'],
|
],
|
||||||
'provider' => $item['provider'],
|
admin_url( 'options-general.php' )
|
||||||
'model' => $item['model'],
|
|
||||||
'status' => $item['status'],
|
|
||||||
'tokens_prompt' => isset( $item['tokens_prompt'] ) ? (int) $item['tokens_prompt'] : null,
|
|
||||||
'tokens_completion' => isset( $item['tokens_completion'] ) ? (int) $item['tokens_completion'] : null,
|
|
||||||
'tokens_total' => isset( $item['tokens_total'] ) ? (int) $item['tokens_total'] : null,
|
|
||||||
'prompt' => $item['prompt'],
|
|
||||||
'response' => $item['response'],
|
|
||||||
'error_message' => $item['error_message'],
|
|
||||||
'image_context' => isset( $usage['image_context'] ) ? $usage['image_context'] : null,
|
|
||||||
];
|
|
||||||
$encoded = esc_attr( wp_json_encode( $payload ) );
|
|
||||||
return sprintf(
|
|
||||||
'<a href="#" class="groq-ai-log-row" data-groq-log="%s">%s</a>',
|
|
||||||
$encoded,
|
|
||||||
$date
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return sprintf( '<a href="%s">%s</a>', esc_url( $url ), $date );
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_usage_meta( $item ) {
|
private function get_usage_meta( $item ) {
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ class Groq_AI_Product_Text_Product_UI {
|
|||||||
$post_id = ( $post && isset( $post->ID ) ) ? (int) $post->ID : 0;
|
$post_id = ( $post && isset( $post->ID ) ) ? (int) $post->ID : 0;
|
||||||
|
|
||||||
$settings = $this->plugin->get_settings();
|
$settings = $this->plugin->get_settings();
|
||||||
|
$attribute_defaults = isset( $settings['product_attribute_includes'] ) && is_array( $settings['product_attribute_includes'] )
|
||||||
|
? array_values( array_unique( array_map( 'sanitize_key', $settings['product_attribute_includes'] ) ) )
|
||||||
|
: [];
|
||||||
|
|
||||||
wp_localize_script(
|
wp_localize_script(
|
||||||
'groq-ai-admin',
|
'groq-ai-admin',
|
||||||
@@ -69,6 +72,7 @@ class Groq_AI_Product_Text_Product_UI {
|
|||||||
'defaultPrompt' => $settings['default_prompt'],
|
'defaultPrompt' => $settings['default_prompt'],
|
||||||
'postId' => $post_id,
|
'postId' => $post_id,
|
||||||
'contextDefaults' => isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields(),
|
'contextDefaults' => isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields(),
|
||||||
|
'attributeIncludesDefaults' => $attribute_defaults,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -82,6 +86,7 @@ class Groq_AI_Product_Text_Product_UI {
|
|||||||
|
|
||||||
$settings = $this->plugin->get_settings();
|
$settings = $this->plugin->get_settings();
|
||||||
$rankmath_enabled = $this->plugin->is_rankmath_active() && $this->plugin->is_module_enabled( 'rankmath', $settings );
|
$rankmath_enabled = $this->plugin->is_rankmath_active() && $this->plugin->is_module_enabled( 'rankmath', $settings );
|
||||||
|
$attribute_options = $this->get_product_attribute_include_options();
|
||||||
?>
|
?>
|
||||||
<div id="groq-ai-modal" class="groq-ai-modal" aria-hidden="true">
|
<div id="groq-ai-modal" class="groq-ai-modal" aria-hidden="true">
|
||||||
<div class="groq-ai-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="groq-ai-modal-title">
|
<div class="groq-ai-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="groq-ai-modal-title">
|
||||||
@@ -109,6 +114,9 @@ class Groq_AI_Product_Text_Product_UI {
|
|||||||
$context_definitions = $this->plugin->get_context_field_definitions();
|
$context_definitions = $this->plugin->get_context_field_definitions();
|
||||||
$context_defaults = isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields();
|
$context_defaults = isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields();
|
||||||
foreach ( $context_definitions as $context_key => $context_info ) :
|
foreach ( $context_definitions as $context_key => $context_info ) :
|
||||||
|
if ( 'attributes' === $context_key ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$checked = ! empty( $context_defaults[ $context_key ] );
|
$checked = ! empty( $context_defaults[ $context_key ] );
|
||||||
?>
|
?>
|
||||||
<label class="groq-ai-context-option">
|
<label class="groq-ai-context-option">
|
||||||
@@ -122,6 +130,23 @@ class Groq_AI_Product_Text_Product_UI {
|
|||||||
</label>
|
</label>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3 style="margin-top:16px;"><?php esc_html_e( 'Attributen meesturen', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
|
<p class="description"><?php esc_html_e( 'Selecteer welke productattributen je mee wilt geven aan de AI. Dit vervangt de oude alles-of-niets optie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||||
|
<?php if ( empty( $attribute_options ) ) : ?>
|
||||||
|
<p class="description"><?php esc_html_e( 'Geen WooCommerce-attributen gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="groq-ai-context-options__grid">
|
||||||
|
<?php foreach ( $attribute_options as $attr_key => $attr_label ) : ?>
|
||||||
|
<label class="groq-ai-context-option">
|
||||||
|
<input type="checkbox" class="groq-ai-attribute-toggle" data-attribute="<?php echo esc_attr( $attr_key ); ?>" />
|
||||||
|
<div>
|
||||||
|
<strong><?php echo esc_html( $attr_label ); ?></strong>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -225,4 +250,39 @@ class Groq_AI_Product_Text_Product_UI {
|
|||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function get_product_attribute_include_options() {
|
||||||
|
$options = [
|
||||||
|
'__custom__' => __( 'Custom attributen (niet-taxonomie)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( function_exists( 'wc_get_attribute_taxonomies' ) ) {
|
||||||
|
$taxonomies = wc_get_attribute_taxonomies();
|
||||||
|
if ( is_array( $taxonomies ) ) {
|
||||||
|
foreach ( $taxonomies as $attr ) {
|
||||||
|
$name = isset( $attr->attribute_name ) ? sanitize_key( (string) $attr->attribute_name ) : '';
|
||||||
|
$label = isset( $attr->attribute_label ) ? sanitize_text_field( (string) $attr->attribute_label ) : '';
|
||||||
|
if ( '' === $name ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$taxonomy = 'pa_' . $name;
|
||||||
|
if ( '' === $label ) {
|
||||||
|
$label = function_exists( 'wc_attribute_label' ) ? wc_attribute_label( $taxonomy ) : $taxonomy;
|
||||||
|
}
|
||||||
|
$options[ $taxonomy ] = $label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( count( $options ) > 1 ) {
|
||||||
|
$fixed = [
|
||||||
|
'__custom__' => $options['__custom__'],
|
||||||
|
];
|
||||||
|
unset( $options['__custom__'] );
|
||||||
|
asort( $options, SORT_NATURAL | SORT_FLAG_CASE );
|
||||||
|
$options = $fixed + $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ class Groq_AI_Ajax_Controller {
|
|||||||
add_action( 'wp_ajax_groq_ai_generate_text', [ $this, 'handle_generate_text' ] );
|
add_action( 'wp_ajax_groq_ai_generate_text', [ $this, 'handle_generate_text' ] );
|
||||||
add_action( 'wp_ajax_groq_ai_refresh_models', [ $this, 'handle_refresh_models' ] );
|
add_action( 'wp_ajax_groq_ai_refresh_models', [ $this, 'handle_refresh_models' ] );
|
||||||
add_action( 'wp_ajax_groq_ai_generate_term_text', [ $this, 'handle_generate_term_text' ] );
|
add_action( 'wp_ajax_groq_ai_generate_term_text', [ $this, 'handle_generate_term_text' ] );
|
||||||
|
add_action( 'wp_ajax_groq_ai_bulk_generate_terms', [ $this, 'handle_bulk_generate_terms_request' ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle_generate_term_text() {
|
public function handle_generate_term_text() {
|
||||||
@@ -35,6 +36,113 @@ class Groq_AI_Ajax_Controller {
|
|||||||
wp_send_json_error( [ 'message' => __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 );
|
wp_send_json_error( [ 'message' => __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$result = $this->run_term_generation(
|
||||||
|
$term,
|
||||||
|
$prompt,
|
||||||
|
[
|
||||||
|
'include_top_products' => $include_top_products,
|
||||||
|
'top_products_limit' => $top_products_limit,
|
||||||
|
'origin' => 'term_manual',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $result ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success( $result );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle_bulk_generate_terms_request() {
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
|
||||||
|
}
|
||||||
|
|
||||||
|
check_ajax_referer( 'groq_ai_bulk_generate_terms', 'nonce' );
|
||||||
|
|
||||||
|
$taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : '';
|
||||||
|
$term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0;
|
||||||
|
$force = ! empty( $_POST['force'] );
|
||||||
|
|
||||||
|
if ( '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) {
|
||||||
|
wp_send_json_error( [ 'message' => __( 'Taxonomie en term_id zijn verplicht.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 400 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$term = get_term( $term_id, $taxonomy );
|
||||||
|
if ( ! $term || is_wp_error( $term ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_description = isset( $term->description ) ? trim( wp_strip_all_tags( (string) $term->description ) ) : '';
|
||||||
|
if ( '' !== $current_description && ! $force ) {
|
||||||
|
wp_send_json_error(
|
||||||
|
[
|
||||||
|
'message' => __( 'Categorie heeft al een omschrijving.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
'code' => 'groq_ai_term_has_description',
|
||||||
|
],
|
||||||
|
400
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = apply_filters(
|
||||||
|
'groq_ai_bulk_term_generation_options',
|
||||||
|
[
|
||||||
|
'include_top_products' => true,
|
||||||
|
'top_products_limit' => 10,
|
||||||
|
],
|
||||||
|
$term
|
||||||
|
);
|
||||||
|
|
||||||
|
$options['origin'] = $force ? 'term_force_regenerate' : 'term_bulk_auto';
|
||||||
|
$options['force'] = $force;
|
||||||
|
|
||||||
|
$result = $this->run_term_generation( $term, $this->get_term_prompt_text( $term ), $options );
|
||||||
|
if ( is_wp_error( $result ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$saved = $this->save_term_generation_result( $term, $result, $settings );
|
||||||
|
|
||||||
|
if ( is_wp_error( $saved ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => $saved->get_error_message() ], 500 );
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(
|
||||||
|
[
|
||||||
|
'term_id' => $term_id,
|
||||||
|
'name' => isset( $term->name ) ? (string) $term->name : '',
|
||||||
|
'words' => isset( $saved['words'] ) ? absint( $saved['words'] ) : 0,
|
||||||
|
'count' => isset( $term->count ) ? absint( $term->count ) : 0,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function run_term_generation( $term, $prompt, $options = [] ) {
|
||||||
|
if ( ! $term || ! is_object( $term ) ) {
|
||||||
|
return new WP_Error( 'groq_ai_invalid_term', __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$taxonomy = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : '';
|
||||||
|
$term_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0;
|
||||||
|
|
||||||
|
$options = wp_parse_args(
|
||||||
|
$options,
|
||||||
|
[
|
||||||
|
'include_top_products' => true,
|
||||||
|
'top_products_limit' => 10,
|
||||||
|
'origin' => 'term_manual',
|
||||||
|
'force' => false,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$origin = isset( $options['origin'] ) ? sanitize_key( (string) $options['origin'] ) : 'term_manual';
|
||||||
|
$force_run = ! empty( $options['force'] );
|
||||||
|
$include_top_products = ! empty( $options['include_top_products'] );
|
||||||
|
$top_products_limit = isset( $options['top_products_limit'] ) ? absint( $options['top_products_limit'] ) : 10;
|
||||||
|
$top_products_limit = max( 1, min( 25, $top_products_limit ) );
|
||||||
|
|
||||||
|
$logger = $this->plugin->get_generation_logger();
|
||||||
$settings = $this->plugin->get_settings();
|
$settings = $this->plugin->get_settings();
|
||||||
$provider_manager = $this->plugin->get_provider_manager();
|
$provider_manager = $this->plugin->get_provider_manager();
|
||||||
$provider_key = $settings['provider'];
|
$provider_key = $settings['provider'];
|
||||||
@@ -51,7 +159,6 @@ class Groq_AI_Ajax_Controller {
|
|||||||
? $prompt_builder->build_term_system_prompt( $settings, $conversation_id, $term )
|
? $prompt_builder->build_term_system_prompt( $settings, $conversation_id, $term )
|
||||||
: $prompt_builder->build_system_prompt( $settings, $conversation_id );
|
: $prompt_builder->build_system_prompt( $settings, $conversation_id );
|
||||||
|
|
||||||
$model = $this->plugin->get_selected_model( $provider, $settings );
|
|
||||||
$context_block = '';
|
$context_block = '';
|
||||||
if ( method_exists( $prompt_builder, 'build_term_context_block' ) ) {
|
if ( method_exists( $prompt_builder, 'build_term_context_block' ) ) {
|
||||||
$context_block = $prompt_builder->build_term_context_block(
|
$context_block = $prompt_builder->build_term_context_block(
|
||||||
@@ -67,6 +174,19 @@ class Groq_AI_Ajax_Controller {
|
|||||||
? $prompt_builder->prepend_term_context_to_prompt( $prompt, $context_block )
|
? $prompt_builder->prepend_term_context_to_prompt( $prompt, $context_block )
|
||||||
: $prompt_builder->prepend_context_to_prompt( $prompt, $context_block );
|
: $prompt_builder->prepend_context_to_prompt( $prompt, $context_block );
|
||||||
|
|
||||||
|
$usage_meta = [
|
||||||
|
'term_context' => [
|
||||||
|
'taxonomy' => $taxonomy,
|
||||||
|
'term_id' => $term_id,
|
||||||
|
'origin' => $origin,
|
||||||
|
],
|
||||||
|
'term_options' => [
|
||||||
|
'include_top_products' => $include_top_products,
|
||||||
|
'top_products_limit' => $top_products_limit,
|
||||||
|
'force' => $force_run,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
$response_format = null;
|
$response_format = null;
|
||||||
$use_response_format = $this->plugin->should_use_response_format( $provider, $settings );
|
$use_response_format = $this->plugin->should_use_response_format( $provider, $settings );
|
||||||
if ( $use_response_format && method_exists( $prompt_builder, 'get_term_response_format_definition' ) ) {
|
if ( $use_response_format && method_exists( $prompt_builder, 'get_term_response_format_definition' ) ) {
|
||||||
@@ -78,6 +198,7 @@ class Groq_AI_Ajax_Controller {
|
|||||||
$final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings );
|
$final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$model = $this->plugin->get_selected_model( $provider, $settings );
|
||||||
$result = $provider->generate_content(
|
$result = $provider->generate_content(
|
||||||
[
|
[
|
||||||
'prompt' => $final_prompt,
|
'prompt' => $final_prompt,
|
||||||
@@ -91,39 +212,194 @@ class Groq_AI_Ajax_Controller {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if ( is_wp_error( $result ) ) {
|
if ( is_wp_error( $result ) ) {
|
||||||
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
|
if ( $logger ) {
|
||||||
|
$logger->log_generation_event(
|
||||||
|
[
|
||||||
|
'provider' => $provider_key,
|
||||||
|
'model' => $model,
|
||||||
|
'prompt' => $final_prompt,
|
||||||
|
'response' => '',
|
||||||
|
'usage' => $usage_meta,
|
||||||
|
'status' => 'error',
|
||||||
|
'error_message' => $result->get_error_message(),
|
||||||
|
'post_id' => 0,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response_text = $this->extract_content_text( $result );
|
$response_text = $this->extract_content_text( $result );
|
||||||
|
$response_usage = is_array( $result ) && isset( $result['usage'] ) ? $result['usage'] : [];
|
||||||
|
if ( ! is_array( $response_usage ) ) {
|
||||||
|
$response_usage = [];
|
||||||
|
}
|
||||||
|
$response_usage['term_context'] = $usage_meta['term_context'];
|
||||||
|
$response_usage['term_options'] = $usage_meta['term_options'];
|
||||||
$parsed = null;
|
$parsed = null;
|
||||||
if ( method_exists( $prompt_builder, 'parse_term_structured_response' ) ) {
|
if ( method_exists( $prompt_builder, 'parse_term_structured_response' ) ) {
|
||||||
$parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings );
|
$parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings );
|
||||||
}
|
}
|
||||||
|
if ( is_wp_error( $parsed ) ) {
|
||||||
|
if ( $logger ) {
|
||||||
|
$logger->log_generation_event(
|
||||||
|
[
|
||||||
|
'provider' => $provider_key,
|
||||||
|
'model' => $model,
|
||||||
|
'prompt' => $final_prompt,
|
||||||
|
'response' => $response_text,
|
||||||
|
'usage' => $response_usage,
|
||||||
|
'status' => 'error',
|
||||||
|
'error_message' => $parsed->get_error_message(),
|
||||||
|
'post_id' => 0,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $parsed;
|
||||||
|
}
|
||||||
if ( ! is_array( $parsed ) ) {
|
if ( ! is_array( $parsed ) ) {
|
||||||
$parsed = [
|
$parsed = [
|
||||||
'description' => trim( (string) $response_text ),
|
'description' => trim( (string) $response_text ),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$default_bottom_key = isset( $settings['term_bottom_description_meta_key'] ) ? sanitize_key( (string) $settings['term_bottom_description_meta_key'] ) : '';
|
if ( $logger ) {
|
||||||
$bottom_meta_key = apply_filters( 'groq_ai_term_bottom_description_meta_key', $default_bottom_key, $term, $settings );
|
$logger->log_generation_event(
|
||||||
$bottom_meta_key = sanitize_key( (string) $bottom_meta_key );
|
|
||||||
$has_bottom_field = ( '' !== $bottom_meta_key );
|
|
||||||
|
|
||||||
$top_description = isset( $parsed['description'] ) ? (string) $parsed['description'] : '';
|
|
||||||
$bottom_description = isset( $parsed['bottom_description'] ) ? (string) $parsed['bottom_description'] : '';
|
|
||||||
$apply_text = $has_bottom_field ? ( '' !== $bottom_description ? $bottom_description : $top_description ) : $top_description;
|
|
||||||
|
|
||||||
wp_send_json_success(
|
|
||||||
[
|
[
|
||||||
'top_description' => $top_description,
|
'provider' => $provider_key,
|
||||||
'bottom_description' => $has_bottom_field ? $apply_text : $bottom_description,
|
'model' => $model,
|
||||||
'description' => $apply_text,
|
'prompt' => $final_prompt,
|
||||||
'raw' => $response_text,
|
'response' => $response_text,
|
||||||
|
'usage' => $response_usage,
|
||||||
|
'status' => 'success',
|
||||||
|
'post_id' => 0,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'top_description' => isset( $parsed['top_description'] ) ? $parsed['top_description'] : ( isset( $parsed['description'] ) ? $parsed['description'] : '' ),
|
||||||
|
'bottom_description' => isset( $parsed['bottom_description'] ) ? $parsed['bottom_description'] : '',
|
||||||
|
'meta_title' => isset( $parsed['meta_title'] ) ? $parsed['meta_title'] : '',
|
||||||
|
'meta_description' => isset( $parsed['meta_description'] ) ? $parsed['meta_description'] : '',
|
||||||
|
'focus_keywords' => isset( $parsed['focus_keywords'] ) ? $parsed['focus_keywords'] : '',
|
||||||
|
'description' => isset( $parsed['description'] ) ? $parsed['description'] : ( isset( $parsed['top_description'] ) ? $parsed['top_description'] : '' ),
|
||||||
|
'raw' => $response_text,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function save_term_generation_result( $term, $result, $settings ) {
|
||||||
|
$term_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0;
|
||||||
|
$taxonomy = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : '';
|
||||||
|
|
||||||
|
if ( ! $term_id || '' === $taxonomy ) {
|
||||||
|
return new WP_Error( 'groq_ai_invalid_term', __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$top_description = '';
|
||||||
|
if ( isset( $result['top_description'] ) && '' !== trim( (string) $result['top_description'] ) ) {
|
||||||
|
$top_description = (string) $result['top_description'];
|
||||||
|
} elseif ( isset( $result['description'] ) ) {
|
||||||
|
$top_description = (string) $result['description'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( '' === trim( wp_strip_all_tags( $top_description ) ) ) {
|
||||||
|
return new WP_Error( 'groq_ai_missing_description', __( 'De AI gaf geen omschrijving terug.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$update = wp_update_term(
|
||||||
|
$term_id,
|
||||||
|
$taxonomy,
|
||||||
|
[
|
||||||
|
'description' => wp_kses_post( $top_description ),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $update ) ) {
|
||||||
|
return $update;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bottom_key = $this->get_bottom_meta_key( $term, $settings );
|
||||||
|
if ( '' !== $bottom_key ) {
|
||||||
|
$bottom_description = isset( $result['bottom_description'] ) ? (string) $result['bottom_description'] : '';
|
||||||
|
update_term_meta( $term_id, $bottom_key, wp_kses_post( $bottom_description ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->plugin->is_module_enabled( 'rankmath', $settings ) ) {
|
||||||
|
$rankmath_keys = $this->get_rankmath_term_meta_keys( $term, $settings );
|
||||||
|
update_term_meta( $term_id, $rankmath_keys['title'], sanitize_text_field( isset( $result['meta_title'] ) ? $result['meta_title'] : '' ) );
|
||||||
|
update_term_meta( $term_id, $rankmath_keys['description'], sanitize_text_field( isset( $result['meta_description'] ) ? $result['meta_description'] : '' ) );
|
||||||
|
update_term_meta( $term_id, $rankmath_keys['focus_keyword'], sanitize_text_field( isset( $result['focus_keywords'] ) ? $result['focus_keywords'] : '' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'words' => $this->count_words( $top_description ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_bottom_meta_key( $term, $settings ) {
|
||||||
|
$default_key = '';
|
||||||
|
if ( is_array( $settings ) && isset( $settings['term_bottom_description_meta_key'] ) ) {
|
||||||
|
$default_key = sanitize_key( (string) $settings['term_bottom_description_meta_key'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = apply_filters( 'groq_ai_term_bottom_description_meta_key', $default_key, $term, $settings );
|
||||||
|
$key = sanitize_key( (string) $key );
|
||||||
|
|
||||||
|
return '' !== $key ? $key : 'groq_ai_term_bottom_description';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_rankmath_term_meta_keys( $term, $settings ) {
|
||||||
|
$defaults = [
|
||||||
|
'title' => 'rank_math_title',
|
||||||
|
'description' => 'rank_math_description',
|
||||||
|
'focus_keyword' => 'rank_math_focus_keyword',
|
||||||
|
];
|
||||||
|
|
||||||
|
$keys = apply_filters( 'groq_ai_rankmath_term_meta_keys', $defaults, $term, $settings );
|
||||||
|
if ( ! is_array( $keys ) ) {
|
||||||
|
$keys = $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => isset( $keys['title'] ) ? sanitize_key( (string) $keys['title'] ) : 'rank_math_title',
|
||||||
|
'description' => isset( $keys['description'] ) ? sanitize_key( (string) $keys['description'] ) : 'rank_math_description',
|
||||||
|
'focus_keyword' => isset( $keys['focus_keyword'] ) ? sanitize_key( (string) $keys['focus_keyword'] ) : 'rank_math_focus_keyword',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_term_prompt_text( $term ) {
|
||||||
|
$prompt = '';
|
||||||
|
|
||||||
|
if ( $term && isset( $term->term_id ) ) {
|
||||||
|
$prompt = (string) get_term_meta( $term->term_id, 'groq_ai_term_custom_prompt', true );
|
||||||
|
}
|
||||||
|
|
||||||
|
$prompt = trim( $prompt );
|
||||||
|
if ( '' !== $prompt ) {
|
||||||
|
return $prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
$default_prompt = __( 'Schrijf een SEO-vriendelijke categorieomschrijving in het Nederlands. Gebruik duidelijke tussenkoppen en <p>-tags. Voeg geen prijsinformatie toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
|
||||||
|
return apply_filters( 'groq_ai_default_term_prompt', $default_prompt, $term );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function count_words( $text ) {
|
||||||
|
$text = wp_strip_all_tags( (string) $text );
|
||||||
|
$text = trim( preg_replace( '/\s+/u', ' ', $text ) );
|
||||||
|
|
||||||
|
if ( '' === $text ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( preg_match_all( '/\pL[\pL\pN\']*/u', $text, $matches ) ) {
|
||||||
|
return count( $matches[0] );
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_word_count( $text );
|
||||||
|
}
|
||||||
|
|
||||||
public function handle_generate_text() {
|
public function handle_generate_text() {
|
||||||
if ( ! current_user_can( 'edit_products' ) ) {
|
if ( ! current_user_can( 'edit_products' ) ) {
|
||||||
wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
|
wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
|
||||||
@@ -149,6 +425,23 @@ class Groq_AI_Ajax_Controller {
|
|||||||
$system_prompt = $prompt_builder->build_system_prompt( $settings, $conversation_id );
|
$system_prompt = $prompt_builder->build_system_prompt( $settings, $conversation_id );
|
||||||
$model = $this->plugin->get_selected_model( $provider, $settings );
|
$model = $this->plugin->get_selected_model( $provider, $settings );
|
||||||
$context_fields = $prompt_builder->parse_context_fields_from_request( isset( $_POST['context_fields'] ) ? $_POST['context_fields'] : '', $settings );
|
$context_fields = $prompt_builder->parse_context_fields_from_request( isset( $_POST['context_fields'] ) ? $_POST['context_fields'] : '', $settings );
|
||||||
|
if ( array_key_exists( 'attribute_includes', $_POST ) ) {
|
||||||
|
$attribute_includes = [];
|
||||||
|
$attribute_raw = (string) wp_unslash( $_POST['attribute_includes'] );
|
||||||
|
$decoded = json_decode( $attribute_raw, true );
|
||||||
|
if ( is_array( $decoded ) ) {
|
||||||
|
foreach ( $decoded as $value ) {
|
||||||
|
$key = sanitize_key( (string) $value );
|
||||||
|
if ( '' === $key ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( in_array( $key, [ '__custom__', '__all__' ], true ) || 0 === strpos( $key, 'pa_' ) ) {
|
||||||
|
$attribute_includes[] = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$settings['product_attribute_includes'] = array_values( array_unique( $attribute_includes ) );
|
||||||
|
}
|
||||||
$image_context_mode = $this->plugin->get_image_context_mode( $settings );
|
$image_context_mode = $this->plugin->get_image_context_mode( $settings );
|
||||||
$image_context_limit = $this->plugin->get_image_context_limit( $settings );
|
$image_context_limit = $this->plugin->get_image_context_limit( $settings );
|
||||||
|
|
||||||
@@ -174,7 +467,7 @@ class Groq_AI_Ajax_Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$product_context_text = $prompt_builder->build_product_context_block( $post_id, $context_fields, $prompt_image_mode, $image_context_limit );
|
$product_context_text = $prompt_builder->build_product_context_block( $post_id, $context_fields, $prompt_image_mode, $image_context_limit, $settings );
|
||||||
$image_context_payloads = [];
|
$image_context_payloads = [];
|
||||||
if ( $use_base64_payloads ) {
|
if ( $use_base64_payloads ) {
|
||||||
$image_context_payloads = $prompt_builder->get_product_image_payloads( $post_id, $image_context_limit );
|
$image_context_payloads = $prompt_builder->get_product_image_payloads( $post_id, $image_context_limit );
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ class Groq_AI_Prompt_Builder {
|
|||||||
return $normalized;
|
return $normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function build_product_context_block( $post_id, $fields, $image_mode = 'url', $image_limit = 3 ) {
|
public function build_product_context_block( $post_id, $fields, $image_mode = 'url', $image_limit = 3, $settings = null ) {
|
||||||
$post_id = absint( $post_id );
|
$post_id = absint( $post_id );
|
||||||
|
|
||||||
if ( ! $post_id ) {
|
if ( ! $post_id ) {
|
||||||
@@ -368,8 +368,14 @@ class Groq_AI_Prompt_Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! empty( $fields['attributes'] ) ) {
|
$attribute_includes = [];
|
||||||
$attributes = $this->get_product_attributes_text( $post_id );
|
if ( is_array( $settings ) && isset( $settings['product_attribute_includes'] ) && is_array( $settings['product_attribute_includes'] ) ) {
|
||||||
|
$attribute_includes = array_values( array_unique( array_map( 'sanitize_key', $settings['product_attribute_includes'] ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$include_attributes = ! empty( $attribute_includes ) || ! empty( $fields['attributes'] );
|
||||||
|
if ( $include_attributes ) {
|
||||||
|
$attributes = $this->get_product_attributes_text( $post_id, $attribute_includes );
|
||||||
if ( $attributes ) {
|
if ( $attributes ) {
|
||||||
$parts[] = sprintf( __( 'Attributen: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $attributes );
|
$parts[] = sprintf( __( 'Attributen: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $attributes );
|
||||||
}
|
}
|
||||||
@@ -382,9 +388,66 @@ class Groq_AI_Prompt_Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $fields['brands'] ) ) {
|
||||||
|
$brands_context = $this->get_product_brand_context_text( $post_id );
|
||||||
|
if ( '' !== $brands_context ) {
|
||||||
|
$parts[] = sprintf( __( 'Merken: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $brands_context );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return implode( "\n\n", array_filter( $parts ) );
|
return implode( "\n\n", array_filter( $parts ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function get_product_brand_context_text( $post_id ) {
|
||||||
|
$post_id = absint( $post_id );
|
||||||
|
$taxonomy = $this->detect_brand_taxonomy();
|
||||||
|
|
||||||
|
if ( ! $post_id || '' === $taxonomy || ! taxonomy_exists( $taxonomy ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$terms = get_the_terms( $post_id, $taxonomy );
|
||||||
|
if ( empty( $terms ) || is_wp_error( $terms ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = [];
|
||||||
|
foreach ( $terms as $term ) {
|
||||||
|
if ( ! $term || ! is_object( $term ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = isset( $term->name ) ? trim( wp_strip_all_tags( (string) $term->name ) ) : '';
|
||||||
|
if ( '' === $name ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = isset( $term->description ) ? trim( wp_strip_all_tags( (string) $term->description ) ) : '';
|
||||||
|
if ( '' !== $description ) {
|
||||||
|
$entries[] = sprintf( '%s - %s', $name, $description );
|
||||||
|
} else {
|
||||||
|
$entries[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = array_values( array_unique( array_filter( $entries ) ) );
|
||||||
|
if ( empty( $entries ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = implode( '; ', $entries );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the product brand context string added to prompts.
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param int $post_id
|
||||||
|
* @param array $terms
|
||||||
|
* @param string $taxonomy
|
||||||
|
*/
|
||||||
|
return (string) apply_filters( 'groq_ai_product_brand_context', $context, $post_id, $terms, $taxonomy );
|
||||||
|
}
|
||||||
|
|
||||||
public function prepend_context_to_prompt( $prompt, $context ) {
|
public function prepend_context_to_prompt( $prompt, $context ) {
|
||||||
$context = trim( (string) $context );
|
$context = trim( (string) $context );
|
||||||
|
|
||||||
@@ -425,6 +488,7 @@ class Groq_AI_Prompt_Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( $term, $settings );
|
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( $term, $settings );
|
||||||
|
$bottom_meta_key = '' !== $bottom_meta_key ? $bottom_meta_key : 'groq_ai_term_bottom_description';
|
||||||
if ( '' !== $bottom_meta_key && $term_id ) {
|
if ( '' !== $bottom_meta_key && $term_id ) {
|
||||||
$bottom = (string) get_term_meta( $term_id, $bottom_meta_key, true );
|
$bottom = (string) get_term_meta( $term_id, $bottom_meta_key, true );
|
||||||
$bottom = trim( wp_strip_all_tags( $bottom ) );
|
$bottom = trim( wp_strip_all_tags( $bottom ) );
|
||||||
@@ -474,27 +538,18 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$title_pixels = $this->settings_manager->get_rankmath_meta_title_pixel_limit( $settings );
|
$title_pixels = $this->settings_manager->get_rankmath_meta_title_pixel_limit( $settings );
|
||||||
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
||||||
|
|
||||||
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( null, $settings );
|
$properties = [
|
||||||
$use_bottom_field = ( '' !== $bottom_meta_key );
|
'top_description' => [
|
||||||
|
|
||||||
$properties = [];
|
|
||||||
$required = [];
|
|
||||||
|
|
||||||
if ( $use_bottom_field ) {
|
|
||||||
$properties['bottom_description'] = [
|
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => __( 'Uitgebreide HTML-omschrijving (helemaal onderaan) met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
'description' => __( 'Korte HTML-omschrijving (1 alinea) voor de standaard WordPress term description. Exact één alinea in <p>-tags.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
'minLength' => 20,
|
'minLength' => 20,
|
||||||
];
|
],
|
||||||
$required[] = 'bottom_description';
|
'bottom_description' => [
|
||||||
} else {
|
|
||||||
$properties['description'] = [
|
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => __( 'HTML-omschrijving (WordPress term description) met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
'description' => __( 'Uitgebreide HTML-omschrijving (helemaal onderaan), 2–4 alinea’s, met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
'minLength' => 20,
|
'minLength' => 20,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
$required[] = 'description';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $rankmath_enabled ) {
|
if ( $rankmath_enabled ) {
|
||||||
$properties['meta_title'] = [
|
$properties['meta_title'] = [
|
||||||
@@ -529,7 +584,7 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$schema = [
|
$schema = [
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => $properties,
|
'properties' => $properties,
|
||||||
'required' => $required,
|
'required' => [ 'top_description', 'bottom_description' ],
|
||||||
'additionalProperties' => false,
|
'additionalProperties' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -572,36 +627,24 @@ class Groq_AI_Prompt_Builder {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( null, $settings );
|
|
||||||
$use_bottom_field = ( '' !== $bottom_meta_key );
|
|
||||||
|
|
||||||
$description = isset( $decoded['description'] ) ? trim( (string) $decoded['description'] ) : '';
|
|
||||||
$top = isset( $decoded['top_description'] ) ? trim( (string) $decoded['top_description'] ) : '';
|
$top = isset( $decoded['top_description'] ) ? trim( (string) $decoded['top_description'] ) : '';
|
||||||
$bottom = isset( $decoded['bottom_description'] ) ? trim( (string) $decoded['bottom_description'] ) : '';
|
$bottom = isset( $decoded['bottom_description'] ) ? trim( (string) $decoded['bottom_description'] ) : '';
|
||||||
|
// Backward compatibility: older prompts only returned `description`.
|
||||||
if ( $use_bottom_field ) {
|
if ( '' === $top && isset( $decoded['description'] ) ) {
|
||||||
if ( '' === $bottom ) {
|
$top = trim( (string) $decoded['description'] );
|
||||||
$bottom = '' !== $description ? $description : $top;
|
|
||||||
}
|
|
||||||
if ( '' === $bottom ) {
|
|
||||||
return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen bottom_description veld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ( '' === $description ) {
|
|
||||||
$description = '' !== $top ? $top : $bottom;
|
|
||||||
}
|
|
||||||
if ( '' === $description ) {
|
|
||||||
return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen description veld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
|
||||||
}
|
}
|
||||||
|
if ( '' === $top && '' === $bottom ) {
|
||||||
|
return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen top_description/bottom_description velden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = [];
|
$result = [];
|
||||||
if ( $use_bottom_field ) {
|
if ( '' !== $top ) {
|
||||||
$result['bottom_description'] = $bottom;
|
$result['top_description'] = $top;
|
||||||
// For backwards compatibility with existing UI, keep `description` alias.
|
// For backwards compatibility with existing UI, keep `description` alias.
|
||||||
$result['description'] = $bottom;
|
$result['description'] = $top;
|
||||||
} else {
|
}
|
||||||
$result['description'] = $description;
|
if ( '' !== $bottom ) {
|
||||||
|
$result['bottom_description'] = $bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isset( $decoded['meta_title'] ) ) {
|
if ( isset( $decoded['meta_title'] ) ) {
|
||||||
@@ -628,15 +671,10 @@ class Groq_AI_Prompt_Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function get_term_structured_response_instructions( $settings = null ) {
|
private function get_term_structured_response_instructions( $settings = null ) {
|
||||||
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( null, $settings );
|
$schema_parts = [
|
||||||
$use_bottom_field = ( '' !== $bottom_meta_key );
|
'"top_description":"..."',
|
||||||
|
'"bottom_description":"..."',
|
||||||
$schema_parts = [];
|
];
|
||||||
if ( $use_bottom_field ) {
|
|
||||||
$schema_parts[] = '"bottom_description":"..."';
|
|
||||||
} else {
|
|
||||||
$schema_parts[] = '"description":"..."';
|
|
||||||
}
|
|
||||||
|
|
||||||
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||||
if ( $rankmath_enabled ) {
|
if ( $rankmath_enabled ) {
|
||||||
@@ -652,13 +690,9 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$json_structure
|
$json_structure
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $use_bottom_field ) {
|
$instruction .= ' ' . __( 'Zorg dat top_description en bottom_description geldige HTML bevatten. top_description moet exact één alinea zijn in <p>-tags. bottom_description moet 2–4 alinea’s bevatten.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
$instruction .= ' ' . __( 'Zorg dat bottom_description geldige HTML bevat. Dit is de tekst die helemaal onderaan de pagina komt.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
|
||||||
} else {
|
|
||||||
$instruction .= ' ' . __( 'Zorg dat description geldige HTML bevat.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
|
||||||
}
|
|
||||||
$instruction .= ' ' . __( 'Voeg geen extra tekst buiten het JSON-object toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
$instruction .= ' ' . __( 'Voeg geen extra tekst buiten het JSON-object toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
$instruction .= ' ' . __( 'Als in de context een sectie "Interne links" staat, verwerk dan 2–5 van deze links natuurlijk in de hoofdtekst als HTML-links (<a href="URL">Anker</a>).', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
$instruction .= ' ' . __( 'Als in de context een sectie "Interne links" staat, verwerk dan 2–5 van deze links natuurlijk in bottom_description als HTML-links (<a href="URL">Anker</a>).', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
return $instruction;
|
return $instruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,7 +896,7 @@ class Groq_AI_Prompt_Builder {
|
|||||||
return substr( $text, 0, $limit );
|
return substr( $text, 0, $limit );
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_product_attributes_text( $post_id ) {
|
private function get_product_attributes_text( $post_id, $attribute_includes = [] ) {
|
||||||
if ( ! function_exists( 'wc_get_product' ) ) {
|
if ( ! function_exists( 'wc_get_product' ) ) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -879,14 +913,27 @@ class Groq_AI_Prompt_Builder {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$attribute_includes = is_array( $attribute_includes ) ? array_values( array_unique( array_map( 'sanitize_key', $attribute_includes ) ) ) : [];
|
||||||
|
$include_all = empty( $attribute_includes ) || in_array( '__all__', $attribute_includes, true );
|
||||||
|
$include_custom = $include_all || in_array( '__custom__', $attribute_includes, true );
|
||||||
|
|
||||||
$lines = [];
|
$lines = [];
|
||||||
|
|
||||||
foreach ( $attributes as $attribute ) {
|
foreach ( $attributes as $attribute ) {
|
||||||
if ( $attribute->is_taxonomy() ) {
|
if ( $attribute->is_taxonomy() ) {
|
||||||
$terms = wc_get_product_terms( $post_id, $attribute->get_name(), [ 'fields' => 'names' ] );
|
$taxonomy_name = sanitize_key( (string) $attribute->get_name() );
|
||||||
|
if ( ! $include_all && ! in_array( $taxonomy_name, $attribute_includes, true ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$terms = wc_get_product_terms( $post_id, $taxonomy_name, [ 'fields' => 'names' ] );
|
||||||
$value = implode( ', ', array_map( 'sanitize_text_field', (array) $terms ) );
|
$value = implode( ', ', array_map( 'sanitize_text_field', (array) $terms ) );
|
||||||
$label = wc_attribute_label( $attribute->get_name() );
|
$label = wc_attribute_label( $taxonomy_name );
|
||||||
} else {
|
} else {
|
||||||
|
if ( ! $include_custom ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$options = $attribute->get_options();
|
$options = $attribute->get_options();
|
||||||
$value = implode( ', ', array_map( 'sanitize_text_field', (array) $options ) );
|
$value = implode( ', ', array_map( 'sanitize_text_field', (array) $options ) );
|
||||||
$label = sanitize_text_field( $attribute->get_name() );
|
$label = sanitize_text_field( $attribute->get_name() );
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class Groq_AI_Settings_Manager {
|
|||||||
'store_context' => '',
|
'store_context' => '',
|
||||||
'default_prompt' => '',
|
'default_prompt' => '',
|
||||||
'max_output_tokens' => 2048,
|
'max_output_tokens' => 2048,
|
||||||
|
'product_attribute_includes' => [],
|
||||||
'term_bottom_description_meta_key' => '',
|
'term_bottom_description_meta_key' => '',
|
||||||
'groq_api_key' => '',
|
'groq_api_key' => '',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
@@ -74,6 +75,10 @@ class Groq_AI_Settings_Manager {
|
|||||||
$limit = isset( $settings['image_context_limit'] ) ? $this->sanitize_image_context_limit_value( $settings['image_context_limit'] ) : 3;
|
$limit = isset( $settings['image_context_limit'] ) ? $this->sanitize_image_context_limit_value( $settings['image_context_limit'] ) : 3;
|
||||||
$settings['image_context_limit'] = $limit;
|
$settings['image_context_limit'] = $limit;
|
||||||
|
|
||||||
|
$settings['product_attribute_includes'] = $this->sanitize_product_attribute_includes(
|
||||||
|
isset( $settings['product_attribute_includes'] ) ? $settings['product_attribute_includes'] : []
|
||||||
|
);
|
||||||
|
|
||||||
return $settings;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +95,7 @@ class Groq_AI_Settings_Manager {
|
|||||||
'store_context' => '',
|
'store_context' => '',
|
||||||
'default_prompt' => '',
|
'default_prompt' => '',
|
||||||
'max_output_tokens' => 2048,
|
'max_output_tokens' => 2048,
|
||||||
|
'product_attribute_includes' => [],
|
||||||
'term_bottom_description_meta_key' => '',
|
'term_bottom_description_meta_key' => '',
|
||||||
'groq_api_key' => '',
|
'groq_api_key' => '',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
@@ -151,6 +157,7 @@ class Groq_AI_Settings_Manager {
|
|||||||
'store_context' => sanitize_textarea_field( $input['store_context'] ),
|
'store_context' => sanitize_textarea_field( $input['store_context'] ),
|
||||||
'default_prompt' => sanitize_textarea_field( $input['default_prompt'] ),
|
'default_prompt' => sanitize_textarea_field( $input['default_prompt'] ),
|
||||||
'max_output_tokens' => $max_output_tokens,
|
'max_output_tokens' => $max_output_tokens,
|
||||||
|
'product_attribute_includes' => $this->sanitize_product_attribute_includes( isset( $raw_input['product_attribute_includes'] ) ? $raw_input['product_attribute_includes'] : [] ),
|
||||||
'term_bottom_description_meta_key' => sanitize_key( (string) $input['term_bottom_description_meta_key'] ),
|
'term_bottom_description_meta_key' => sanitize_key( (string) $input['term_bottom_description_meta_key'] ),
|
||||||
'groq_api_key' => sanitize_text_field( $input['groq_api_key'] ),
|
'groq_api_key' => sanitize_text_field( $input['groq_api_key'] ),
|
||||||
'openai_api_key' => sanitize_text_field( $input['openai_api_key'] ),
|
'openai_api_key' => sanitize_text_field( $input['openai_api_key'] ),
|
||||||
@@ -177,6 +184,33 @@ class Groq_AI_Settings_Manager {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function sanitize_product_attribute_includes( $value ) {
|
||||||
|
if ( ! is_array( $value ) ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$clean = [];
|
||||||
|
foreach ( $value as $item ) {
|
||||||
|
$item = sanitize_key( (string) $item );
|
||||||
|
if ( '' === $item ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow special tokens and attribute taxonomies.
|
||||||
|
if ( in_array( $item, [ '__all__', '__custom__' ], true ) || 0 === strpos( $item, 'pa_' ) ) {
|
||||||
|
$clean[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$clean = array_values( array_unique( $clean ) );
|
||||||
|
// Hard cap to avoid overly large option payloads.
|
||||||
|
if ( count( $clean ) > 200 ) {
|
||||||
|
$clean = array_slice( $clean, 0, 200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clean;
|
||||||
|
}
|
||||||
|
|
||||||
public function get_context_field_definitions() {
|
public function get_context_field_definitions() {
|
||||||
if ( null === $this->context_field_definitions ) {
|
if ( null === $this->context_field_definitions ) {
|
||||||
$this->context_field_definitions = [
|
$this->context_field_definitions = [
|
||||||
@@ -200,6 +234,11 @@ class Groq_AI_Settings_Manager {
|
|||||||
'description' => __( 'Voeg gestructureerde productattributen toe (zoals kleur, maat, materiaal).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
'description' => __( 'Voeg gestructureerde productattributen toe (zoals kleur, maat, materiaal).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
'default' => false,
|
'default' => false,
|
||||||
],
|
],
|
||||||
|
'brands' => [
|
||||||
|
'label' => __( 'Merken', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
'description' => __( 'Voegt gekoppelde productmerken toe (detecteert WooCommerce merk-taxonomieën).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
'default' => true,
|
||||||
|
],
|
||||||
'images' => [
|
'images' => [
|
||||||
'label' => __( 'Afbeeldingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
'label' => __( 'Afbeeldingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
'description' => __( 'Voeg een korte lijst toe met productafbeeldingen (beschrijving + URL).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
'description' => __( 'Voeg een korte lijst toe met productafbeeldingen (beschrijving + URL).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
@@ -227,7 +266,7 @@ class Groq_AI_Settings_Manager {
|
|||||||
$normalized = [];
|
$normalized = [];
|
||||||
|
|
||||||
foreach ( $definitions as $key => $data ) {
|
foreach ( $definitions as $key => $data ) {
|
||||||
$normalized[ $key ] = false;
|
$normalized[ $key ] = ! empty( $data['default'] );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! is_array( $fields ) ) {
|
if ( ! is_array( $fields ) ) {
|
||||||
|
|||||||
Reference in New Issue
Block a user