feat: Implement bulk generation for category descriptions with AJAX handling
This commit is contained in:
@@ -29,3 +29,63 @@
|
||||
align-items: center;
|
||||
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-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;
|
||||
}
|
||||
}
|
||||
|
||||
212
assets/js/category-bulk.js
Normal file
212
assets/js/category-bulk.js
Normal file
@@ -0,0 +1,212 @@
|
||||
(function () {
|
||||
const data = window.GroqAICategoryBulk || {};
|
||||
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 (!startButton || !data.ajaxUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!statusField) {
|
||||
return;
|
||||
}
|
||||
statusField.textContent = message || '';
|
||||
statusField.dataset.status = type || '';
|
||||
}
|
||||
|
||||
function appendLog(message, type) {
|
||||
if (!logList || !message) {
|
||||
return;
|
||||
}
|
||||
const item = document.createElement('li');
|
||||
item.textContent = message;
|
||||
item.dataset.status = type || '';
|
||||
logList.appendChild(item);
|
||||
}
|
||||
|
||||
function resetLog() {
|
||||
if (!logList) {
|
||||
return;
|
||||
}
|
||||
logList.innerHTML = '';
|
||||
}
|
||||
|
||||
function toggleButtons(running) {
|
||||
isRunning = running;
|
||||
startButton.disabled = running;
|
||||
if (stopButton) {
|
||||
stopButton.hidden = !running;
|
||||
}
|
||||
}
|
||||
|
||||
function getPendingTerms() {
|
||||
if (!Array.isArray(data.terms)) {
|
||||
return [];
|
||||
}
|
||||
return data.terms.filter((term) => !term.processed);
|
||||
}
|
||||
|
||||
function updateRow(termId, words) {
|
||||
const row = document.querySelector('[data-groq-ai-term-id="' + termId + '"]');
|
||||
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(typeof words === 'number' ? words : wordCell.textContent);
|
||||
}
|
||||
}
|
||||
|
||||
function finish(state) {
|
||||
const summaryTemplate =
|
||||
state === 'done'
|
||||
? data.strings && data.strings.statusDone
|
||||
: state === 'stopped'
|
||||
? data.strings && data.strings.statusStopped
|
||||
: '';
|
||||
|
||||
const summary = summaryTemplate
|
||||
? formatString(summaryTemplate, [successes])
|
||||
: '';
|
||||
|
||||
const statusType = state === 'done' ? 'success' : state === 'stopped' ? 'info' : '';
|
||||
setStatus(summary, statusType);
|
||||
toggleButtons(false);
|
||||
queue = [];
|
||||
totalCount = 0;
|
||||
abortRequested = false;
|
||||
}
|
||||
|
||||
function processNext() {
|
||||
if (abortRequested) {
|
||||
finish('stopped');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!queue.length) {
|
||||
finish('done');
|
||||
return;
|
||||
}
|
||||
|
||||
const term = queue.shift();
|
||||
const position = processed + 1;
|
||||
const progressTemplate = data.strings && data.strings.statusProgress;
|
||||
if (progressTemplate) {
|
||||
setStatus(formatString(progressTemplate, [position, totalCount, term.name || '']), 'loading');
|
||||
}
|
||||
|
||||
const payload = new URLSearchParams();
|
||||
payload.append('action', 'groq_ai_bulk_generate_terms');
|
||||
payload.append('nonce', data.nonce || '');
|
||||
payload.append('taxonomy', data.taxonomy || 'product_cat');
|
||||
payload.append('term_id', term.id);
|
||||
|
||||
fetch(data.ajaxUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: payload.toString(),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
if (!json.success) {
|
||||
const errorMessage = (json.data && json.data.message) || 'Onbekende fout';
|
||||
appendLog(formatString((data.strings && data.strings.logError) || '%1$s: %2$s', [term.name || term.id, errorMessage]), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
term.processed = true;
|
||||
successes += 1;
|
||||
const words = json.data && typeof json.data.words !== 'undefined' ? json.data.words : 0;
|
||||
updateRow(term.id, words);
|
||||
appendLog(formatString((data.strings && data.strings.logSuccess) || '%1$s gevuld.', [term.name || term.id, words]), 'success');
|
||||
})
|
||||
.catch((error) => {
|
||||
appendLog(
|
||||
formatString((data.strings && data.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((data.strings && data.strings.statusEmpty) || '', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
queue = pending.slice();
|
||||
totalCount = queue.length;
|
||||
processed = 0;
|
||||
successes = 0;
|
||||
abortRequested = false;
|
||||
resetLog();
|
||||
toggleButtons(true);
|
||||
setStatus((data.strings && data.strings.statusIdle) || '', 'info');
|
||||
processNext();
|
||||
}
|
||||
|
||||
startButton.addEventListener('click', startBulk);
|
||||
|
||||
if (stopButton) {
|
||||
stopButton.addEventListener('click', () => {
|
||||
if (!isRunning) {
|
||||
return;
|
||||
}
|
||||
const confirmation = ! (data.strings && data.strings.confirmStop)
|
||||
? window.confirm('Stoppen?')
|
||||
: window.confirm(data.strings.confirmStop);
|
||||
if (confirmation) {
|
||||
abortRequested = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.terms) || !data.terms.length) {
|
||||
setStatus((data.strings && data.strings.statusEmpty) || '', 'info');
|
||||
}
|
||||
})();
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Plugin Name: SitiAI Product Teksten
|
||||
* Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce.
|
||||
* Version: 1.5.0
|
||||
* Version: 1.6.0
|
||||
* Author: SitiAI
|
||||
* Text Domain: siti-ai-product-content-generator
|
||||
* Domain Path: /languages
|
||||
|
||||
@@ -173,10 +173,44 @@ class Groq_AI_Product_Text_Settings_Page {
|
||||
if ( is_wp_error( $terms ) ) {
|
||||
$terms = [];
|
||||
}
|
||||
|
||||
$word_map = [];
|
||||
$empty_terms = [];
|
||||
foreach ( $terms as $term ) {
|
||||
if ( ! $term || ! is_object( $term ) ) {
|
||||
continue;
|
||||
}
|
||||
$word_count = $this->count_words( isset( $term->description ) ? $term->description : '' );
|
||||
$word_map[ $term->term_id ] = $word_count;
|
||||
if ( 0 === $word_count ) {
|
||||
$empty_terms[] = $term;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Categorie teksten', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h1>
|
||||
<p><?php esc_html_e( 'Klik op een categorie om teksten te genereren en instellingen te beheren. De tabel toont de huidige woordlengte van de categorie-omschrijving.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||
<div class="groq-ai-bulk-panel">
|
||||
<?php if ( ! empty( $empty_terms ) ) : ?>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: amount of categories without description */
|
||||
esc_html__( 'Er zijn %d categorieën zonder omschrijving. Klik op de knop hieronder om automatisch teksten te genereren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
count( $empty_terms )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" class="button button-primary" id="groq-ai-bulk-generate"><?php esc_html_e( 'Genereer teksten voor lege categorieën', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
|
||||
<button type="button" class="button" id="groq-ai-bulk-cancel" hidden><?php esc_html_e( 'Stop bulk generatie', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
|
||||
</p>
|
||||
<div id="groq-ai-bulk-status" class="description"></div>
|
||||
<ol id="groq-ai-bulk-log" class="groq-ai-bulk-log"></ol>
|
||||
<?php else : ?>
|
||||
<p class="description"><?php esc_html_e( 'Alle categorieën hebben al een omschrijving.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<table class="widefat striped">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -193,16 +227,24 @@ class Groq_AI_Product_Text_Settings_Page {
|
||||
<?php foreach ( $terms as $term ) : ?>
|
||||
<?php
|
||||
$link = $this->get_term_page_url( 'product_cat', $term->term_id );
|
||||
$words = $this->count_words( $term->description );
|
||||
$words = isset( $word_map[ $term->term_id ] ) ? $word_map[ $term->term_id ] : 0;
|
||||
$count = isset( $term->count ) ? absint( $term->count ) : 0;
|
||||
$row_classes = [ 'groq-ai-term-row' ];
|
||||
if ( 0 === $words ) {
|
||||
$row_classes[] = 'groq-ai-term-missing';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<tr class="<?php echo esc_attr( implode( ' ', $row_classes ) ); ?>" data-groq-ai-term-id="<?php echo esc_attr( (string) $term->term_id ); ?>">
|
||||
<td>
|
||||
<a href="<?php echo esc_url( $link ); ?>"><strong><?php echo esc_html( $term->name ); ?></strong></a>
|
||||
</td>
|
||||
<td><?php echo esc_html( $term->slug ); ?></td>
|
||||
<td><?php echo esc_html( (string) $count ); ?></td>
|
||||
<td><?php echo esc_html( (string) $words ); ?></td>
|
||||
<td class="groq-ai-word-cell">
|
||||
<span class="groq-ai-word-count" data-term-id="<?php echo esc_attr( (string) $term->term_id ); ?>">
|
||||
<?php echo esc_html( (string) $words ); ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
@@ -334,10 +376,7 @@ class Groq_AI_Product_Text_Settings_Page {
|
||||
$rankmath_description = (string) get_term_meta( $term_id, $rankmath_keys['description'], true );
|
||||
$rankmath_focus_keywords = (string) get_term_meta( $term_id, $rankmath_keys['focus_keyword'], true );
|
||||
}
|
||||
$default_prompt = (string) $meta_prompt;
|
||||
if ( '' === trim( $default_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 );
|
||||
}
|
||||
$default_prompt = $this->get_term_prompt_text( $term, $meta_prompt );
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>
|
||||
@@ -496,6 +535,57 @@ class Groq_AI_Product_Text_Settings_Page {
|
||||
];
|
||||
}
|
||||
|
||||
private function get_term_prompt_text( $term, $custom_prompt = null ) {
|
||||
$prompt = ( null !== $custom_prompt ) ? $custom_prompt : '';
|
||||
|
||||
if ( null === $custom_prompt && $term && isset( $term->term_id ) ) {
|
||||
$prompt = get_term_meta( $term->term_id, 'groq_ai_term_custom_prompt', true );
|
||||
}
|
||||
|
||||
$prompt = trim( (string) $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 get_terms_without_description_payload( $taxonomy ) {
|
||||
$terms = get_terms(
|
||||
[
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
'number' => 0,
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $terms ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$payloads = [];
|
||||
foreach ( $terms as $term ) {
|
||||
$description = isset( $term->description ) ? trim( wp_strip_all_tags( (string) $term->description ) ) : '';
|
||||
if ( '' !== $description ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$payloads[] = [
|
||||
'id' => isset( $term->term_id ) ? absint( $term->term_id ) : 0,
|
||||
'name' => isset( $term->name ) ? (string) $term->name : '',
|
||||
'slug' => isset( $term->slug ) ? (string) $term->slug : '',
|
||||
'count' => isset( $term->count ) ? absint( $term->count ) : 0,
|
||||
'url' => esc_url( $this->get_term_page_url( $taxonomy, isset( $term->term_id ) ? $term->term_id : 0 ) ),
|
||||
];
|
||||
}
|
||||
|
||||
return array_values( $payloads );
|
||||
}
|
||||
|
||||
public function handle_save_term_content() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||
@@ -1886,6 +1976,37 @@ class Groq_AI_Product_Text_Settings_Page {
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'settings_page_groq-ai-product-text-categories' === $hook ) {
|
||||
wp_enqueue_script(
|
||||
'groq-ai-category-bulk',
|
||||
plugins_url( 'assets/js/category-bulk.js', GROQ_AI_PRODUCT_TEXT_FILE ),
|
||||
[],
|
||||
GROQ_AI_PRODUCT_TEXT_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'groq-ai-category-bulk',
|
||||
'GroqAICategoryBulk',
|
||||
[
|
||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
||||
'nonce' => wp_create_nonce( 'groq_ai_bulk_generate_terms' ),
|
||||
'taxonomy' => 'product_cat',
|
||||
'terms' => $this->get_terms_without_description_payload( 'product_cat' ),
|
||||
'strings' => [
|
||||
'statusIdle' => __( 'Klik op de knop om voor alle lege categorieën automatisch teksten te genereren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'statusProgress' => __( 'Bezig met categorie %1$s van %2$s: %3$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'statusDone' => __( 'Klaar! %d categorieën bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'statusStopped' => __( 'Bulk generatie gestopt. %d categorieën bijgewerkt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'statusEmpty' => __( 'Geen categorieën zonder omschrijving gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'logSuccess' => __( '%1$s gevuld (%2$d woorden).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'logError' => __( '%1$s mislukt: %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
'confirmStop' => __( 'Weet je zeker dat je wilt stoppen? De huidige categorie kan onafgemaakt blijven.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$current_settings = $this->plugin->get_settings();
|
||||
$data = [
|
||||
'optionKey' => $this->plugin->get_option_key(),
|
||||
|
||||
@@ -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_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_bulk_generate_terms', [ $this, 'handle_bulk_generate_terms_request' ] );
|
||||
}
|
||||
|
||||
public function handle_generate_term_text() {
|
||||
@@ -35,6 +36,101 @@ class Groq_AI_Ajax_Controller {
|
||||
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,
|
||||
]
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
$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 ) );
|
||||
}
|
||||
|
||||
$options = wp_parse_args(
|
||||
$options,
|
||||
[
|
||||
'include_top_products' => true,
|
||||
'top_products_limit' => 10,
|
||||
]
|
||||
);
|
||||
|
||||
$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 ) );
|
||||
|
||||
$settings = $this->plugin->get_settings();
|
||||
$provider_manager = $this->plugin->get_provider_manager();
|
||||
$provider_key = $settings['provider'];
|
||||
@@ -51,7 +147,6 @@ class Groq_AI_Ajax_Controller {
|
||||
? $prompt_builder->build_term_system_prompt( $settings, $conversation_id, $term )
|
||||
: $prompt_builder->build_system_prompt( $settings, $conversation_id );
|
||||
|
||||
$model = $this->plugin->get_selected_model( $provider, $settings );
|
||||
$context_block = '';
|
||||
if ( method_exists( $prompt_builder, 'build_term_context_block' ) ) {
|
||||
$context_block = $prompt_builder->build_term_context_block(
|
||||
@@ -78,6 +173,7 @@ class Groq_AI_Ajax_Controller {
|
||||
$final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings );
|
||||
}
|
||||
|
||||
$model = $this->plugin->get_selected_model( $provider, $settings );
|
||||
$result = $provider->generate_content(
|
||||
[
|
||||
'prompt' => $final_prompt,
|
||||
@@ -91,7 +187,7 @@ class Groq_AI_Ajax_Controller {
|
||||
);
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
|
||||
return $result;
|
||||
}
|
||||
|
||||
$response_text = $this->extract_content_text( $result );
|
||||
@@ -105,8 +201,7 @@ class Groq_AI_Ajax_Controller {
|
||||
];
|
||||
}
|
||||
|
||||
wp_send_json_success(
|
||||
[
|
||||
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'] : '',
|
||||
@@ -114,8 +209,119 @@ class Groq_AI_Ajax_Controller {
|
||||
'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() {
|
||||
|
||||
Reference in New Issue
Block a user