Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43ddbddd11 | |||
| 7b9f26e966 | |||
| 6f488c5c6d | |||
| 1c4ef5e16a | |||
| 1bb10f4b45 |
@@ -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');
|
||||||
@@ -47,11 +51,31 @@
|
|||||||
if (applyButton) {
|
if (applyButton) {
|
||||||
applyButton.addEventListener('click', () => {
|
applyButton.addEventListener('click', () => {
|
||||||
const descriptionField = document.getElementById('description');
|
const descriptionField = document.getElementById('description');
|
||||||
if (!descriptionField || !outputField) {
|
const bottomDescriptionField = document.getElementById('groq-ai-term-bottom-description');
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
descriptionField.value = outputField.value || '';
|
|
||||||
setStatus('Tekst ingevuld in het beschrijving-veld. Vergeet niet op "Opslaan" te klikken.', 'success');
|
if (descriptionField) {
|
||||||
|
descriptionField.value = outputTopField.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');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +93,12 @@
|
|||||||
rawField.textContent = '';
|
rawField.textContent = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outputTopField) outputTopField.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',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -83,8 +113,25 @@
|
|||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputField) {
|
if (outputTopField) {
|
||||||
outputField.value = (json.data && json.data.description ? json.data.description : '').trim();
|
const top = json.data && (json.data.top_description || json.data.description) ? (json.data.top_description || json.data.description) : '';
|
||||||
|
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();
|
||||||
|
|||||||
@@ -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.0
|
* Version: 1.4.5
|
||||||
* Author: SitiAI
|
* Author: SitiAI
|
||||||
* Text Domain: siti-ai-product-content-generator
|
* Text Domain: siti-ai-product-content-generator
|
||||||
* Domain Path: /languages
|
* Domain Path: /languages
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,8 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
[
|
[
|
||||||
'taxonomy' => 'product_cat',
|
'taxonomy' => 'product_cat',
|
||||||
'hide_empty' => false,
|
'hide_empty' => false,
|
||||||
|
'orderby' => 'name',
|
||||||
|
'order' => 'ASC',
|
||||||
'number' => 0,
|
'number' => 0,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -230,6 +232,8 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
[
|
[
|
||||||
'taxonomy' => $taxonomy,
|
'taxonomy' => $taxonomy,
|
||||||
'hide_empty' => false,
|
'hide_empty' => false,
|
||||||
|
'orderby' => 'name',
|
||||||
|
'order' => 'ASC',
|
||||||
'number' => 0,
|
'number' => 0,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -315,6 +319,21 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
$term_label = ( 'product_cat' === $taxonomy ) ? __( 'Categorie', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Term', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
$term_label = ( 'product_cat' === $taxonomy ) ? __( 'Categorie', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Term', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
$word_count = $this->count_words( $term->description );
|
$word_count = $this->count_words( $term->description );
|
||||||
$meta_prompt = get_term_meta( $term_id, 'groq_ai_term_custom_prompt', true );
|
$meta_prompt = get_term_meta( $term_id, 'groq_ai_term_custom_prompt', true );
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( $term, $settings );
|
||||||
|
$effective_bottom_meta_key = '' !== $bottom_meta_key ? $bottom_meta_key : 'groq_ai_term_bottom_description';
|
||||||
|
$bottom_description = (string) get_term_meta( $term_id, $effective_bottom_meta_key, true );
|
||||||
|
$rankmath_module_enabled = $this->plugin->is_module_enabled( 'rankmath', $settings );
|
||||||
|
$rankmath_active = $this->plugin->is_rankmath_active();
|
||||||
|
$rankmath_title = '';
|
||||||
|
$rankmath_description = '';
|
||||||
|
$rankmath_focus_keywords = '';
|
||||||
|
if ( $rankmath_module_enabled ) {
|
||||||
|
$rankmath_keys = $this->resolve_rankmath_term_meta_keys( $term, $settings );
|
||||||
|
$rankmath_title = (string) get_term_meta( $term_id, $rankmath_keys['title'], true );
|
||||||
|
$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;
|
$default_prompt = (string) $meta_prompt;
|
||||||
if ( '' === trim( $default_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 = __( 'Schrijf een SEO-vriendelijke categorieomschrijving in het Nederlands. Gebruik duidelijke tussenkoppen en <p>-tags. Voeg geen prijsinformatie toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
@@ -350,6 +369,24 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
<p class="description"><?php esc_html_e( 'Dit is de standaard WordPress term-omschrijving (wordt o.a. gebruikt op categorie/merk pagina’s).', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
<p class="description"><?php esc_html_e( 'Dit is de standaard WordPress term-omschrijving (wordt o.a. gebruikt op categorie/merk pagina’s).', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label for="groq-ai-term-bottom-description"><?php esc_html_e( 'Omschrijving (onderaan)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<textarea name="groq_ai_term_bottom_description" id="groq-ai-term-bottom-description" rows="8" class="large-text"><?php echo esc_textarea( (string) $bottom_description ); ?></textarea>
|
||||||
|
<p class="description">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
/* translators: %s: meta key */
|
||||||
|
esc_html__( 'Deze tekst wordt opgeslagen in term meta (%s) en is bedoeld voor helemaal onderaan (LiveBetter customfields).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
esc_html( $effective_bottom_meta_key )
|
||||||
|
);
|
||||||
|
if ( '' === $bottom_meta_key ) {
|
||||||
|
echo ' ' . esc_html__( 'Let op: stel de juiste LiveBetter meta key in via de plugin-instelling of via de filter groq_ai_term_bottom_description_meta_key.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row"><label for="groq-ai-term-custom-prompt"><?php esc_html_e( 'Prompt (optioneel, per term)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
|
<th scope="row"><label for="groq-ai-term-custom-prompt"><?php esc_html_e( 'Prompt (optioneel, per term)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
|
||||||
<td>
|
<td>
|
||||||
@@ -357,6 +394,34 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
<p class="description"><?php esc_html_e( 'Laat leeg om de standaard prompt te gebruiken. Deze prompt wordt gebruikt wanneer je op de knop "Genereer" klikt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
<p class="description"><?php esc_html_e( 'Laat leeg om de standaard prompt te gebruiken. Deze prompt wordt gebruikt wanneer je op de knop "Genereer" klikt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<?php if ( $rankmath_module_enabled ) : ?>
|
||||||
|
<?php if ( ! $rankmath_active ) : ?>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?php esc_html_e( 'Rank Math', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></th>
|
||||||
|
<td>
|
||||||
|
<p class="description"><?php esc_html_e( 'Rank Math plugin lijkt niet actief. Velden zijn wel invulbaar en worden opgeslagen in term meta, maar Rank Math gebruikt ze pas na activatie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label for="groq-ai-rankmath-title"><?php esc_html_e( 'Rank Math meta title', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<textarea name="groq_ai_rankmath_meta_title" id="groq-ai-rankmath-title" rows="2" class="large-text"><?php echo esc_textarea( (string) $rankmath_title ); ?></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label for="groq-ai-rankmath-description"><?php esc_html_e( 'Rank Math meta description', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<textarea name="groq_ai_rankmath_meta_description" id="groq-ai-rankmath-description" rows="3" class="large-text"><?php echo esc_textarea( (string) $rankmath_description ); ?></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><label for="groq-ai-rankmath-keywords"><?php esc_html_e( 'Rank Math focus keywords', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<textarea name="groq_ai_rankmath_focus_keywords" id="groq-ai-rankmath-keywords" rows="2" class="large-text" placeholder="<?php esc_attr_e( 'bijv. luxe massage apparaat, wellness cadeau', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>"><?php echo esc_textarea( (string) $rankmath_focus_keywords ); ?></textarea>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
</table>
|
</table>
|
||||||
<?php submit_button( __( 'Opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); ?>
|
<?php submit_button( __( 'Opslaan', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); ?>
|
||||||
</form>
|
</form>
|
||||||
@@ -380,11 +445,21 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
<textarea id="groq-ai-term-prompt" class="large-text" rows="5"><?php echo esc_textarea( $default_prompt ); ?></textarea>
|
<textarea id="groq-ai-term-prompt" class="large-text" rows="5"><?php echo esc_textarea( $default_prompt ); ?></textarea>
|
||||||
<p>
|
<p>
|
||||||
<button type="submit" class="button button-primary"><?php esc_html_e( 'Genereer', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
|
<button type="submit" class="button button-primary"><?php esc_html_e( 'Genereer', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
|
||||||
<button type="button" class="button" id="groq-ai-term-apply"><?php esc_html_e( 'Zet in omschrijving', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
|
<button type="button" class="button" id="groq-ai-term-apply"><?php esc_html_e( 'Zet in velden', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></button>
|
||||||
</p>
|
</p>
|
||||||
<div id="groq-ai-term-status" class="description" aria-live="polite"></div>
|
<div id="groq-ai-term-status" class="description" aria-live="polite"></div>
|
||||||
<h3><?php esc_html_e( 'Gegenereerde tekst', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
<h3><?php esc_html_e( 'Gegenereerde tekst (omschrijving, 1 alinea)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
<textarea id="groq-ai-term-generated" class="large-text" rows="10"></textarea>
|
<textarea id="groq-ai-term-generated-top" class="large-text" rows="6"></textarea>
|
||||||
|
<h3><?php esc_html_e( 'Gegenereerde tekst (onderaan)', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
|
<textarea id="groq-ai-term-generated-bottom" class="large-text" rows="10"></textarea>
|
||||||
|
<?php if ( $rankmath_module_enabled ) : ?>
|
||||||
|
<h3><?php esc_html_e( 'Gegenereerde Rank Math meta title', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
|
<textarea id="groq-ai-term-generated-meta-title" class="large-text" rows="2"></textarea>
|
||||||
|
<h3><?php esc_html_e( 'Gegenereerde Rank Math meta description', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
|
<textarea id="groq-ai-term-generated-meta-description" class="large-text" rows="3"></textarea>
|
||||||
|
<h3><?php esc_html_e( 'Gegenereerde Rank Math focus keywords', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
|
<textarea id="groq-ai-term-generated-focus-keywords" class="large-text" rows="2"></textarea>
|
||||||
|
<?php endif; ?>
|
||||||
<h3><?php esc_html_e( 'Ruwe JSON-output', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
<h3><?php esc_html_e( 'Ruwe JSON-output', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h3>
|
||||||
<pre id="groq-ai-term-raw" style="background:#fff;border:1px solid #ddd;padding:12px;max-height:240px;overflow:auto;"></pre>
|
<pre id="groq-ai-term-raw" style="background:#fff;border:1px solid #ddd;padding:12px;max-height:240px;overflow:auto;"></pre>
|
||||||
</form>
|
</form>
|
||||||
@@ -392,6 +467,35 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolve_term_bottom_description_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolve_rankmath_term_meta_keys( $term, $settings ) {
|
||||||
|
$keys = [
|
||||||
|
'title' => 'rank_math_title',
|
||||||
|
'description' => 'rank_math_description',
|
||||||
|
'focus_keyword' => 'rank_math_focus_keyword',
|
||||||
|
];
|
||||||
|
$keys = apply_filters( 'groq_ai_rankmath_term_meta_keys', $keys, $term, $settings );
|
||||||
|
if ( ! is_array( $keys ) ) {
|
||||||
|
$keys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function handle_save_term_content() {
|
public function handle_save_term_content() {
|
||||||
if ( ! current_user_can( 'manage_options' ) ) {
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
wp_die( esc_html__( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
wp_die( esc_html__( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
@@ -402,7 +506,11 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
$taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : '';
|
$taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : '';
|
||||||
$term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0;
|
$term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0;
|
||||||
$description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
|
$description = isset( $_POST['description'] ) ? wp_kses_post( wp_unslash( $_POST['description'] ) ) : '';
|
||||||
|
$bottom_description = isset( $_POST['groq_ai_term_bottom_description'] ) ? wp_kses_post( wp_unslash( $_POST['groq_ai_term_bottom_description'] ) ) : '';
|
||||||
$custom_prompt = isset( $_POST['groq_ai_term_custom_prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['groq_ai_term_custom_prompt'] ) ) : '';
|
$custom_prompt = isset( $_POST['groq_ai_term_custom_prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['groq_ai_term_custom_prompt'] ) ) : '';
|
||||||
|
$rankmath_meta_title = isset( $_POST['groq_ai_rankmath_meta_title'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_meta_title'] ) ) : '';
|
||||||
|
$rankmath_meta_description = isset( $_POST['groq_ai_rankmath_meta_description'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_meta_description'] ) ) : '';
|
||||||
|
$rankmath_focus_keywords = isset( $_POST['groq_ai_rankmath_focus_keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['groq_ai_rankmath_focus_keywords'] ) ) : '';
|
||||||
|
|
||||||
if ( '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) {
|
if ( '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) {
|
||||||
wp_safe_redirect( $this->get_settings_page_url() );
|
wp_safe_redirect( $this->get_settings_page_url() );
|
||||||
@@ -419,6 +527,21 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
|
|
||||||
if ( ! is_wp_error( $result ) ) {
|
if ( ! is_wp_error( $result ) ) {
|
||||||
update_term_meta( $term_id, 'groq_ai_term_custom_prompt', $custom_prompt );
|
update_term_meta( $term_id, 'groq_ai_term_custom_prompt', $custom_prompt );
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$term = get_term( $term_id, $taxonomy );
|
||||||
|
if ( $term && ! is_wp_error( $term ) ) {
|
||||||
|
$bottom_meta_key = $this->resolve_term_bottom_description_meta_key( $term, $settings );
|
||||||
|
$effective_bottom_meta_key = '' !== $bottom_meta_key ? $bottom_meta_key : 'groq_ai_term_bottom_description';
|
||||||
|
update_term_meta( $term_id, $effective_bottom_meta_key, $bottom_description );
|
||||||
|
|
||||||
|
$rankmath_module_enabled = $this->plugin->is_module_enabled( 'rankmath', $settings );
|
||||||
|
if ( $rankmath_module_enabled ) {
|
||||||
|
$rankmath_keys = $this->resolve_rankmath_term_meta_keys( $term, $settings );
|
||||||
|
update_term_meta( $term_id, $rankmath_keys['title'], $rankmath_meta_title );
|
||||||
|
update_term_meta( $term_id, $rankmath_keys['description'], $rankmath_meta_description );
|
||||||
|
update_term_meta( $term_id, $rankmath_keys['focus_keyword'], $rankmath_focus_keywords );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_safe_redirect( $this->get_term_page_url( $taxonomy, $term_id ) );
|
wp_safe_redirect( $this->get_term_page_url( $taxonomy, $term_id ) );
|
||||||
@@ -542,6 +665,22 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
'groq_ai_product_text_prompts'
|
'groq_ai_product_text_prompts'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'groq_ai_max_output_tokens',
|
||||||
|
__( 'Max output tokens', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
[ $this, 'render_max_output_tokens_field' ],
|
||||||
|
'groq-ai-product-text-prompts',
|
||||||
|
'groq_ai_product_text_prompts'
|
||||||
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'groq_ai_term_bottom_description_meta_key',
|
||||||
|
__( 'Term-veld (onderaan) meta key', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
[ $this, 'render_term_bottom_description_meta_key_field' ],
|
||||||
|
'groq-ai-product-text-prompts',
|
||||||
|
'groq_ai_product_text_prompts'
|
||||||
|
);
|
||||||
|
|
||||||
add_settings_field(
|
add_settings_field(
|
||||||
'groq_ai_context_fields',
|
'groq_ai_context_fields',
|
||||||
__( 'Standaard productcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
__( 'Standaard productcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
@@ -550,6 +689,14 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
'groq_ai_product_text_prompts'
|
'groq_ai_product_text_prompts'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_settings_field(
|
||||||
|
'groq_ai_product_attribute_includes',
|
||||||
|
__( 'Productattributen meesturen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
[ $this, 'render_product_attribute_includes_field' ],
|
||||||
|
'groq-ai-product-text-prompts',
|
||||||
|
'groq_ai_product_text_prompts'
|
||||||
|
);
|
||||||
|
|
||||||
add_settings_field(
|
add_settings_field(
|
||||||
'groq_ai_response_format_compat',
|
'groq_ai_response_format_compat',
|
||||||
__( 'Response-format compatibiliteit', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
__( 'Response-format compatibiliteit', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
@@ -1455,6 +1602,41 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function render_max_output_tokens_field() {
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$value = isset( $settings['max_output_tokens'] ) ? absint( $settings['max_output_tokens'] ) : 2048;
|
||||||
|
$value = max( 128, min( 8192, $value ) );
|
||||||
|
?>
|
||||||
|
<input type="number"
|
||||||
|
name="<?php echo esc_attr( $this->plugin->get_option_key() ); ?>[max_output_tokens]"
|
||||||
|
min="128"
|
||||||
|
max="8192"
|
||||||
|
step="128"
|
||||||
|
value="<?php echo esc_attr( (string) $value ); ?>"
|
||||||
|
class="small-text"
|
||||||
|
/>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Limiet voor lengte van het AI-antwoord. Als teksten afgekapt worden, zet dit hoger (kost vaak wel meer tokens).', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
|
||||||
|
</p>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render_term_bottom_description_meta_key_field() {
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$value = isset( $settings['term_bottom_description_meta_key'] ) ? (string) $settings['term_bottom_description_meta_key'] : '';
|
||||||
|
?>
|
||||||
|
<input type="text"
|
||||||
|
name="<?php echo esc_attr( $this->plugin->get_option_key() ); ?>[term_bottom_description_meta_key]"
|
||||||
|
value="<?php echo esc_attr( $value ); ?>"
|
||||||
|
class="regular-text"
|
||||||
|
placeholder="bijv. bottom_description"
|
||||||
|
/>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Dit is de term meta key van het extra customfields-veld dat onderaan de categorie/merk pagina wordt getoond (LiveBetter customfields). Laat leeg om alleen de standaard term-omschrijving te gebruiken.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
|
||||||
|
</p>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
public function render_context_fields_field() {
|
public function render_context_fields_field() {
|
||||||
$settings = $this->plugin->get_settings();
|
$settings = $this->plugin->get_settings();
|
||||||
$values = isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields();
|
$values = isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields();
|
||||||
@@ -1462,6 +1644,9 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
?>
|
?>
|
||||||
<div class="groq-ai-context-defaults">
|
<div class="groq-ai-context-defaults">
|
||||||
<?php foreach ( $definitions as $key => $definition ) :
|
<?php foreach ( $definitions as $key => $definition ) :
|
||||||
|
if ( 'attributes' === $key ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$checked = ! empty( $values[ $key ] );
|
$checked = ! empty( $values[ $key ] );
|
||||||
?>
|
?>
|
||||||
<label>
|
<label>
|
||||||
@@ -1478,6 +1663,74 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function render_product_attribute_includes_field() {
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$values = isset( $settings['product_attribute_includes'] ) && is_array( $settings['product_attribute_includes'] )
|
||||||
|
? $settings['product_attribute_includes']
|
||||||
|
: [];
|
||||||
|
$values = array_values( array_unique( array_map( 'sanitize_key', $values ) ) );
|
||||||
|
|
||||||
|
$options = $this->get_product_attribute_include_options();
|
||||||
|
?>
|
||||||
|
<div class="groq-ai-attribute-includes">
|
||||||
|
<p class="description" style="margin-top:0;">
|
||||||
|
<?php esc_html_e( 'Selecteer welke productattributen je als context mee wilt sturen naar de AI. Als je niets selecteert, worden attributen niet meegestuurd (tenzij je dit eerder al had ingeschakeld via de oude instelling).', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
|
||||||
|
</p>
|
||||||
|
<?php if ( empty( $options ) ) : ?>
|
||||||
|
<p class="description">
|
||||||
|
<?php esc_html_e( 'Geen WooCommerce-attributen gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?>
|
||||||
|
</p>
|
||||||
|
<?php else : ?>
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:8px;">
|
||||||
|
<?php foreach ( $options as $key => $label ) :
|
||||||
|
$checked = in_array( $key, $values, true );
|
||||||
|
?>
|
||||||
|
<label style="display:flex;gap:8px;align-items:flex-start;">
|
||||||
|
<input type="checkbox" name="<?php echo esc_attr( $this->plugin->get_option_key() ); ?>[product_attribute_includes][]" value="<?php echo esc_attr( $key ); ?>" <?php checked( $checked ); ?> />
|
||||||
|
<span><?php echo esc_html( $label ); ?></span>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
|
||||||
public function render_response_format_compat_field() {
|
public function render_response_format_compat_field() {
|
||||||
$settings = $this->plugin->get_settings();
|
$settings = $this->plugin->get_settings();
|
||||||
$is_enabled = ! empty( $settings['response_format_compat'] );
|
$is_enabled = ! empty( $settings['response_format_compat'] );
|
||||||
@@ -1529,7 +1782,7 @@ class Groq_AI_Product_Text_Settings_Page {
|
|||||||
type="number"
|
type="number"
|
||||||
id="groq-ai-rankmath-keywords"
|
id="groq-ai-rankmath-keywords"
|
||||||
min="1"
|
min="1"
|
||||||
max="99"
|
max="100"
|
||||||
name="<?php echo esc_attr( $this->plugin->get_option_key() ); ?>[modules][rankmath][focus_keyword_limit]"
|
name="<?php echo esc_attr( $this->plugin->get_option_key() ); ?>[modules][rankmath][focus_keyword_limit]"
|
||||||
value="<?php echo esc_attr( $keyword_limit ); ?>"
|
value="<?php echo esc_attr( $keyword_limit ); ?>"
|
||||||
style="width: 80px;"
|
style="width: 80px;"
|
||||||
|
|||||||
@@ -107,7 +107,12 @@ class Groq_AI_Ajax_Controller {
|
|||||||
|
|
||||||
wp_send_json_success(
|
wp_send_json_success(
|
||||||
[
|
[
|
||||||
'description' => isset( $parsed['description'] ) ? $parsed['description'] : '',
|
'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,
|
'raw' => $response_text,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -138,6 +143,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 );
|
||||||
|
|
||||||
@@ -163,7 +185,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 );
|
||||||
|
|||||||
@@ -80,11 +80,20 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$max_tokens = isset( $args['max_tokens'] ) ? absint( $args['max_tokens'] ) : 0;
|
||||||
|
if ( $max_tokens <= 0 ) {
|
||||||
|
$max_tokens = isset( $settings['max_output_tokens'] ) ? absint( $settings['max_output_tokens'] ) : 0;
|
||||||
|
}
|
||||||
|
if ( $max_tokens <= 0 ) {
|
||||||
|
$max_tokens = 2048;
|
||||||
|
}
|
||||||
|
$max_tokens = max( 128, min( 8192, $max_tokens ) );
|
||||||
|
|
||||||
$request_body = [
|
$request_body = [
|
||||||
'model' => $model,
|
'model' => $model,
|
||||||
'messages' => $messages,
|
'messages' => $messages,
|
||||||
'temperature' => isset( $args['temperature'] ) ? (float) $args['temperature'] : 0.7,
|
'temperature' => isset( $args['temperature'] ) ? (float) $args['temperature'] : 0.7,
|
||||||
'max_tokens' => 1024,
|
'max_tokens' => $max_tokens,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ( ! empty( $args['response_format'] ) ) {
|
if ( ! empty( $args['response_format'] ) ) {
|
||||||
@@ -122,6 +131,10 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
|
|||||||
|
|
||||||
$content = trim( $body['choices'][0]['message']['content'] );
|
$content = trim( $body['choices'][0]['message']['content'] );
|
||||||
$usage = isset( $body['usage'] ) && is_array( $body['usage'] ) ? $body['usage'] : [];
|
$usage = isset( $body['usage'] ) && is_array( $body['usage'] ) ? $body['usage'] : [];
|
||||||
|
$finish_reason = isset( $body['choices'][0]['finish_reason'] ) ? sanitize_text_field( (string) $body['choices'][0]['finish_reason'] ) : '';
|
||||||
|
if ( '' !== $finish_reason ) {
|
||||||
|
$usage['finish_reason'] = $finish_reason;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'content' => $content,
|
'content' => $content,
|
||||||
|
|||||||
@@ -144,6 +144,15 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$max_tokens = isset( $args['max_tokens'] ) ? absint( $args['max_tokens'] ) : 0;
|
||||||
|
if ( $max_tokens <= 0 ) {
|
||||||
|
$max_tokens = isset( $settings['max_output_tokens'] ) ? absint( $settings['max_output_tokens'] ) : 0;
|
||||||
|
}
|
||||||
|
if ( $max_tokens <= 0 ) {
|
||||||
|
$max_tokens = 2048;
|
||||||
|
}
|
||||||
|
$max_tokens = max( 128, min( 8192, $max_tokens ) );
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'contents' => [
|
'contents' => [
|
||||||
[
|
[
|
||||||
@@ -153,7 +162,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
|
|||||||
],
|
],
|
||||||
'generationConfig' => [
|
'generationConfig' => [
|
||||||
'temperature' => isset( $args['temperature'] ) ? (float) $args['temperature'] : 0.7,
|
'temperature' => isset( $args['temperature'] ) ? (float) $args['temperature'] : 0.7,
|
||||||
'maxOutputTokens' => 1024,
|
'maxOutputTokens' => $max_tokens,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -196,6 +205,10 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
|
|||||||
|
|
||||||
$content = trim( implode( "\n\n", array_filter( $texts ) ) );
|
$content = trim( implode( "\n\n", array_filter( $texts ) ) );
|
||||||
$usage = isset( $body['usageMetadata'] ) && is_array( $body['usageMetadata'] ) ? $body['usageMetadata'] : [];
|
$usage = isset( $body['usageMetadata'] ) && is_array( $body['usageMetadata'] ) ? $body['usageMetadata'] : [];
|
||||||
|
$finish_reason = isset( $body['candidates'][0]['finishReason'] ) ? sanitize_text_field( (string) $body['candidates'][0]['finishReason'] ) : '';
|
||||||
|
if ( '' !== $finish_reason ) {
|
||||||
|
$usage['finish_reason'] = $finish_reason;
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'content' => $content,
|
'content' => $content,
|
||||||
|
|||||||
@@ -55,6 +55,145 @@ class Groq_AI_Prompt_Builder {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function detect_brand_taxonomy() {
|
||||||
|
$candidates = [
|
||||||
|
'product_brand',
|
||||||
|
'pwb-brand',
|
||||||
|
'yith_product_brand',
|
||||||
|
'berocket_brand',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( taxonomy_exists( 'pa_brand' ) ) {
|
||||||
|
array_unshift( $candidates, 'pa_brand' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidates = apply_filters( 'groq_ai_brand_taxonomy_candidates', $candidates );
|
||||||
|
$found = '';
|
||||||
|
foreach ( $candidates as $tax ) {
|
||||||
|
$tax = sanitize_key( (string) $tax );
|
||||||
|
if ( $tax && taxonomy_exists( $tax ) ) {
|
||||||
|
$found = $tax;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$found = apply_filters( 'groq_ai_brand_taxonomy', $found );
|
||||||
|
return sanitize_key( (string) $found );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_internal_link_suggestions( $taxonomy, $current_term_id, $limit = 10 ) {
|
||||||
|
$taxonomy = sanitize_key( (string) $taxonomy );
|
||||||
|
$current_term_id = absint( $current_term_id );
|
||||||
|
$limit = max( 0, min( 50, absint( $limit ) ) );
|
||||||
|
if ( '' === $taxonomy || $limit <= 0 || ! taxonomy_exists( $taxonomy ) ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache_key = 'groq_ai_internal_links_' . $taxonomy;
|
||||||
|
$cached = get_transient( $cache_key );
|
||||||
|
if ( is_array( $cached ) ) {
|
||||||
|
$all = $cached;
|
||||||
|
} else {
|
||||||
|
$terms = get_terms(
|
||||||
|
[
|
||||||
|
'taxonomy' => $taxonomy,
|
||||||
|
'hide_empty' => false,
|
||||||
|
'orderby' => 'name',
|
||||||
|
'order' => 'ASC',
|
||||||
|
'number' => 0,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if ( is_wp_error( $terms ) ) {
|
||||||
|
$terms = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$all = [];
|
||||||
|
foreach ( (array) $terms as $t ) {
|
||||||
|
if ( ! $t || ! is_object( $t ) || empty( $t->term_id ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$link = get_term_link( $t );
|
||||||
|
if ( is_wp_error( $link ) || ! is_string( $link ) || '' === $link ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = isset( $t->name ) ? trim( wp_strip_all_tags( (string) $t->name ) ) : '';
|
||||||
|
if ( '' === $name ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$all[] = [
|
||||||
|
'term_id' => absint( $t->term_id ),
|
||||||
|
'name' => $name,
|
||||||
|
'url' => esc_url_raw( $link ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
set_transient( $cache_key, $all, HOUR_IN_SECONDS );
|
||||||
|
}
|
||||||
|
|
||||||
|
$suggestions = [];
|
||||||
|
foreach ( $all as $row ) {
|
||||||
|
if ( ! is_array( $row ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$tid = isset( $row['term_id'] ) ? absint( $row['term_id'] ) : 0;
|
||||||
|
if ( $current_term_id && $tid === $current_term_id ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = isset( $row['name'] ) ? (string) $row['name'] : '';
|
||||||
|
$url = isset( $row['url'] ) ? (string) $row['url'] : '';
|
||||||
|
if ( '' === $name || '' === $url ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$suggestions[] = [
|
||||||
|
'name' => $name,
|
||||||
|
'url' => $url,
|
||||||
|
];
|
||||||
|
if ( count( $suggestions ) >= $limit ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $suggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function build_internal_links_context( $term ) {
|
||||||
|
if ( ! $term || ! is_object( $term ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$current_tax = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : '';
|
||||||
|
$current_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0;
|
||||||
|
|
||||||
|
$links = [];
|
||||||
|
|
||||||
|
// Categories.
|
||||||
|
if ( taxonomy_exists( 'product_cat' ) ) {
|
||||||
|
$links = array_merge( $links, $this->get_internal_link_suggestions( 'product_cat', 'product_cat' === $current_tax ? $current_id : 0, 10 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brands.
|
||||||
|
$brand_tax = $this->detect_brand_taxonomy();
|
||||||
|
if ( '' !== $brand_tax ) {
|
||||||
|
$links = array_merge( $links, $this->get_internal_link_suggestions( $brand_tax, $brand_tax === $current_tax ? $current_id : 0, 10 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $links ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = __( 'Interne links (gebruik 2–5 relevante links in de tekst, als HTML: <a href="URL">Anker</a>):', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
foreach ( $links as $link ) {
|
||||||
|
$name = isset( $link['name'] ) ? (string) $link['name'] : '';
|
||||||
|
$url = isset( $link['url'] ) ? (string) $link['url'] : '';
|
||||||
|
if ( '' === $name || '' === $url ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$lines[] = sprintf( '- %s → %s', $name, $url );
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode( "\n", $lines );
|
||||||
|
}
|
||||||
|
|
||||||
public function append_response_instructions( $prompt, $settings ) {
|
public function append_response_instructions( $prompt, $settings ) {
|
||||||
$instructions = (string) ( $this->get_structured_response_instructions( $settings ) ?? '' );
|
$instructions = (string) ( $this->get_structured_response_instructions( $settings ) ?? '' );
|
||||||
$prompt = trim( (string) $prompt );
|
$prompt = trim( (string) $prompt );
|
||||||
@@ -199,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 ) {
|
||||||
@@ -229,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 );
|
||||||
}
|
}
|
||||||
@@ -285,6 +430,16 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$parts[] = sprintf( __( 'Huidige omschrijving: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( (string) $term->description ) );
|
$parts[] = sprintf( __( 'Huidige omschrijving: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( (string) $term->description ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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 ) {
|
||||||
|
$bottom = (string) get_term_meta( $term_id, $bottom_meta_key, true );
|
||||||
|
$bottom = trim( wp_strip_all_tags( $bottom ) );
|
||||||
|
if ( '' !== $bottom ) {
|
||||||
|
$parts[] = sprintf( __( 'Huidige omschrijving (onderaan): %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $bottom );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( $include_top_products ) {
|
if ( $include_top_products ) {
|
||||||
$top_products = $this->get_top_products_for_term( $taxonomy, $term_id, $top_products_limit );
|
$top_products = $this->get_top_products_for_term( $taxonomy, $term_id, $top_products_limit );
|
||||||
if ( ! empty( $top_products ) ) {
|
if ( ! empty( $top_products ) ) {
|
||||||
@@ -296,6 +451,12 @@ class Groq_AI_Prompt_Builder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$internal_links = $this->build_internal_links_context( $term );
|
||||||
|
$internal_links = trim( (string) $internal_links );
|
||||||
|
if ( '' !== $internal_links ) {
|
||||||
|
$parts[] = $internal_links;
|
||||||
|
}
|
||||||
|
|
||||||
$google_context = apply_filters( 'groq_ai_term_google_context', '', $term, $settings );
|
$google_context = apply_filters( 'groq_ai_term_google_context', '', $term, $settings );
|
||||||
$google_context = trim( (string) $google_context );
|
$google_context = trim( (string) $google_context );
|
||||||
if ( '' !== $google_context ) {
|
if ( '' !== $google_context ) {
|
||||||
@@ -321,9 +482,14 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
||||||
|
|
||||||
$properties = [
|
$properties = [
|
||||||
'description' => [
|
'top_description' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => __( 'HTML-omschrijving voor de categorie/term 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,
|
||||||
|
],
|
||||||
|
'bottom_description' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => __( 'Uitgebreide HTML-omschrijving (helemaal onderaan), 2–4 alinea’s, met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
'minLength' => 20,
|
'minLength' => 20,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -361,7 +527,7 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$schema = [
|
$schema = [
|
||||||
'type' => 'object',
|
'type' => 'object',
|
||||||
'properties' => $properties,
|
'properties' => $properties,
|
||||||
'required' => [ 'description' ],
|
'required' => [ 'top_description', 'bottom_description' ],
|
||||||
'additionalProperties' => false,
|
'additionalProperties' => false,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -404,14 +570,25 @@ class Groq_AI_Prompt_Builder {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$description = isset( $decoded['description'] ) ? trim( (string) $decoded['description'] ) : '';
|
$top = isset( $decoded['top_description'] ) ? trim( (string) $decoded['top_description'] ) : '';
|
||||||
if ( '' === $description ) {
|
$bottom = isset( $decoded['bottom_description'] ) ? trim( (string) $decoded['bottom_description'] ) : '';
|
||||||
return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen description veld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
// Backward compatibility: older prompts only returned `description`.
|
||||||
|
if ( '' === $top && isset( $decoded['description'] ) ) {
|
||||||
|
$top = trim( (string) $decoded['description'] );
|
||||||
|
}
|
||||||
|
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 = [];
|
||||||
'description' => $description,
|
if ( '' !== $top ) {
|
||||||
];
|
$result['top_description'] = $top;
|
||||||
|
// For backwards compatibility with existing UI, keep `description` alias.
|
||||||
|
$result['description'] = $top;
|
||||||
|
}
|
||||||
|
if ( '' !== $bottom ) {
|
||||||
|
$result['bottom_description'] = $bottom;
|
||||||
|
}
|
||||||
|
|
||||||
if ( isset( $decoded['meta_title'] ) ) {
|
if ( isset( $decoded['meta_title'] ) ) {
|
||||||
$result['meta_title'] = $this->truncate_meta_field( (string) $decoded['meta_title'], 60 );
|
$result['meta_title'] = $this->truncate_meta_field( (string) $decoded['meta_title'], 60 );
|
||||||
@@ -438,7 +615,8 @@ class Groq_AI_Prompt_Builder {
|
|||||||
|
|
||||||
private function get_term_structured_response_instructions( $settings = null ) {
|
private function get_term_structured_response_instructions( $settings = null ) {
|
||||||
$schema_parts = [
|
$schema_parts = [
|
||||||
'"description":"..."',
|
'"top_description":"..."',
|
||||||
|
'"bottom_description":"..."',
|
||||||
];
|
];
|
||||||
|
|
||||||
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||||
@@ -455,10 +633,21 @@ class Groq_AI_Prompt_Builder {
|
|||||||
$json_structure
|
$json_structure
|
||||||
);
|
);
|
||||||
|
|
||||||
$instruction .= ' ' . __( 'Zorg dat description geldige HTML bevat (gebruik minimaal <p>-tags en waar relevant lijstjes of benadrukking). Voeg geen extra tekst buiten het JSON-object toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
$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 .= ' ' . __( '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 bottom_description als HTML-links (<a href="URL">Anker</a>).', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
return $instruction;
|
return $instruction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolve_term_bottom_description_meta_key( $term = null, $settings = null ) {
|
||||||
|
$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 );
|
||||||
|
return sanitize_key( (string) $key );
|
||||||
|
}
|
||||||
|
|
||||||
private function get_top_products_for_term( $taxonomy, $term_id, $limit = 10 ) {
|
private function get_top_products_for_term( $taxonomy, $term_id, $limit = 10 ) {
|
||||||
$taxonomy = sanitize_key( (string) $taxonomy );
|
$taxonomy = sanitize_key( (string) $taxonomy );
|
||||||
$term_id = absint( $term_id );
|
$term_id = absint( $term_id );
|
||||||
@@ -650,7 +839,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 '';
|
||||||
}
|
}
|
||||||
@@ -667,14 +856,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() );
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class Groq_AI_Settings_Manager {
|
|||||||
'model' => '',
|
'model' => '',
|
||||||
'store_context' => '',
|
'store_context' => '',
|
||||||
'default_prompt' => '',
|
'default_prompt' => '',
|
||||||
|
'max_output_tokens' => 2048,
|
||||||
|
'product_attribute_includes' => [],
|
||||||
|
'term_bottom_description_meta_key' => '',
|
||||||
'groq_api_key' => '',
|
'groq_api_key' => '',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
'google_api_key' => '',
|
'google_api_key' => '',
|
||||||
@@ -72,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +94,9 @@ class Groq_AI_Settings_Manager {
|
|||||||
'model' => '',
|
'model' => '',
|
||||||
'store_context' => '',
|
'store_context' => '',
|
||||||
'default_prompt' => '',
|
'default_prompt' => '',
|
||||||
|
'max_output_tokens' => 2048,
|
||||||
|
'product_attribute_includes' => [],
|
||||||
|
'term_bottom_description_meta_key' => '',
|
||||||
'groq_api_key' => '',
|
'groq_api_key' => '',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
'google_api_key' => '',
|
'google_api_key' => '',
|
||||||
@@ -129,6 +139,10 @@ class Groq_AI_Settings_Manager {
|
|||||||
|
|
||||||
$image_limit = isset( $input['image_context_limit'] ) ? $this->sanitize_image_context_limit_value( $input['image_context_limit'] ) : $defaults['image_context_limit'];
|
$image_limit = isset( $input['image_context_limit'] ) ? $this->sanitize_image_context_limit_value( $input['image_context_limit'] ) : $defaults['image_context_limit'];
|
||||||
|
|
||||||
|
$max_output_tokens = isset( $input['max_output_tokens'] ) ? absint( $input['max_output_tokens'] ) : absint( $defaults['max_output_tokens'] );
|
||||||
|
// Keep within sane bounds across providers.
|
||||||
|
$max_output_tokens = max( 128, min( 8192, $max_output_tokens ) );
|
||||||
|
|
||||||
$context_fields = $this->normalize_context_fields( $context_posted ? $raw_input['context_fields'] : $defaults['context_fields'] );
|
$context_fields = $this->normalize_context_fields( $context_posted ? $raw_input['context_fields'] : $defaults['context_fields'] );
|
||||||
|
|
||||||
if ( 'none' === $image_mode ) {
|
if ( 'none' === $image_mode ) {
|
||||||
@@ -142,6 +156,9 @@ class Groq_AI_Settings_Manager {
|
|||||||
'model' => $model,
|
'model' => $model,
|
||||||
'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,
|
||||||
|
'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'] ),
|
||||||
'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'] ),
|
||||||
'google_api_key' => sanitize_text_field( $input['google_api_key'] ),
|
'google_api_key' => sanitize_text_field( $input['google_api_key'] ),
|
||||||
@@ -167,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 = [
|
||||||
@@ -275,7 +319,7 @@ class Groq_AI_Settings_Manager {
|
|||||||
$config = $this->get_module_config( 'rankmath', $settings );
|
$config = $this->get_module_config( 'rankmath', $settings );
|
||||||
$limit = isset( $config['focus_keyword_limit'] ) ? absint( $config['focus_keyword_limit'] ) : 3;
|
$limit = isset( $config['focus_keyword_limit'] ) ? absint( $config['focus_keyword_limit'] ) : 3;
|
||||||
|
|
||||||
return max( 1, min( 10, $limit ) );
|
return max( 1, min( 100, $limit ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_rankmath_meta_title_pixel_limit( $settings = null ) {
|
public function get_rankmath_meta_title_pixel_limit( $settings = null ) {
|
||||||
@@ -368,7 +412,7 @@ class Groq_AI_Settings_Manager {
|
|||||||
if ( $limit <= 0 ) {
|
if ( $limit <= 0 ) {
|
||||||
$limit = $module_default_config['focus_keyword_limit'];
|
$limit = $module_default_config['focus_keyword_limit'];
|
||||||
}
|
}
|
||||||
$result[ $module_key ]['focus_keyword_limit'] = max( 1, min( 10, $limit ) );
|
$result[ $module_key ]['focus_keyword_limit'] = max( 1, min( 100, $limit ) );
|
||||||
|
|
||||||
$title_pixel_limit = isset( $raw['meta_title_pixel_limit'] ) ? absint( $raw['meta_title_pixel_limit'] ) : ( isset( $current_config['meta_title_pixel_limit'] ) ? absint( $current_config['meta_title_pixel_limit'] ) : $module_default_config['meta_title_pixel_limit'] );
|
$title_pixel_limit = isset( $raw['meta_title_pixel_limit'] ) ? absint( $raw['meta_title_pixel_limit'] ) : ( isset( $current_config['meta_title_pixel_limit'] ) ? absint( $current_config['meta_title_pixel_limit'] ) : $module_default_config['meta_title_pixel_limit'] );
|
||||||
if ( $title_pixel_limit <= 0 ) {
|
if ( $title_pixel_limit <= 0 ) {
|
||||||
|
|||||||
Reference in New Issue
Block a user