diff --git a/assets/js/term-admin.js b/assets/js/term-admin.js index 89efeac..fa0715c 100644 --- a/assets/js/term-admin.js +++ b/assets/js/term-admin.js @@ -47,11 +47,18 @@ if (applyButton) { applyButton.addEventListener('click', () => { const descriptionField = document.getElementById('description'); - if (!descriptionField || !outputField) { + const bottomDescriptionField = document.getElementById('groq-ai-term-bottom-description'); + if (!outputField) { return; } - descriptionField.value = outputField.value || ''; - setStatus('Tekst ingevuld in het beschrijving-veld. Vergeet niet op "Opslaan" te klikken.', 'success'); + + if (bottomDescriptionField) { + bottomDescriptionField.value = outputField.value || ''; + } else if (descriptionField) { + descriptionField.value = outputField.value || ''; + } + + setStatus('Tekst ingevuld. Vergeet niet op "Opslaan" te klikken.', 'success'); }); } @@ -69,6 +76,10 @@ rawField.textContent = ''; } + if (outputField) { + outputField.value = ''; + } + fetch(GroqAITermGenerator.ajaxUrl, { method: 'POST', headers: { @@ -84,7 +95,8 @@ } if (outputField) { - outputField.value = (json.data && json.data.description ? json.data.description : '').trim(); + const text = json.data && json.data.description ? json.data.description : ''; + outputField.value = String(text).trim(); } if (rawField) { rawField.textContent = (json.data && json.data.raw ? String(json.data.raw) : '').trim(); diff --git a/groq-ai-product-text.php b/groq-ai-product-text.php index c8c9351..6a43767 100644 --- a/groq-ai-product-text.php +++ b/groq-ai-product-text.php @@ -2,7 +2,7 @@ /** * Plugin Name: SitiAI Product Teksten * Description: Genereer productteksten met diverse AI-aanbieders rechtstreeks vanuit WooCommerce. - * Version: 1.4.2 + * Version: 1.4.3 * Author: SitiAI * Text Domain: siti-ai-product-content-generator * Domain Path: /languages diff --git a/includes/Admin/class-groq-ai-settings-page.php b/includes/Admin/class-groq-ai-settings-page.php index 3b230f0..48bf588 100644 --- a/includes/Admin/class-groq-ai-settings-page.php +++ b/includes/Admin/class-groq-ai-settings-page.php @@ -319,6 +319,12 @@ class Groq_AI_Product_Text_Settings_Page { $term_label = ( 'product_cat' === $taxonomy ) ? __( 'Categorie', GROQ_AI_PRODUCT_TEXT_DOMAIN ) : __( 'Term', GROQ_AI_PRODUCT_TEXT_DOMAIN ); $word_count = $this->count_words( $term->description ); $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 ); + $bottom_description = ''; + if ( '' !== $bottom_meta_key ) { + $bottom_description = (string) get_term_meta( $term_id, $bottom_meta_key, true ); + } $default_prompt = (string) $meta_prompt; if ( '' === trim( $default_prompt ) ) { $default_prompt = __( 'Schrijf een SEO-vriendelijke categorieomschrijving in het Nederlands. Gebruik duidelijke tussenkoppen en
-tags. Voeg geen prijsinformatie toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); @@ -354,6 +360,23 @@ class Groq_AI_Product_Text_Settings_Page {
+ ++ +
+- +
- + @@ -396,6 +427,17 @@ class Groq_AI_Product_Text_Settings_Page { 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 ); + if ( '' !== $bottom_meta_key ) { + update_term_meta( $term_id, $bottom_meta_key, $bottom_description ); + } + } } wp_safe_redirect( $this->get_term_page_url( $taxonomy, $term_id ) ); @@ -554,6 +605,14 @@ class Groq_AI_Product_Text_Settings_Page { '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( 'groq_ai_context_fields', __( 'Standaard productcontext', GROQ_AI_PRODUCT_TEXT_DOMAIN ), @@ -1486,6 +1545,22 @@ class Groq_AI_Product_Text_Settings_Page { plugin->get_settings(); + $value = isset( $settings['term_bottom_description_meta_key'] ) ? (string) $settings['term_bottom_description_meta_key'] : ''; + ?> + ++ +
+ plugin->get_settings(); $values = isset( $settings['context_fields'] ) ? $settings['context_fields'] : $this->plugin->get_default_context_fields(); @@ -1560,7 +1635,7 @@ class Groq_AI_Product_Text_Settings_Page { type="number" id="groq-ai-rankmath-keywords" min="1" - max="99" + max="100" name="plugin->get_option_key() ); ?>[modules][rankmath][focus_keyword_limit]" value="" style="width: 80px;" diff --git a/includes/Core/class-groq-ai-ajax-controller.php b/includes/Core/class-groq-ai-ajax-controller.php index 40ff885..e559a30 100644 --- a/includes/Core/class-groq-ai-ajax-controller.php +++ b/includes/Core/class-groq-ai-ajax-controller.php @@ -105,9 +105,20 @@ class Groq_AI_Ajax_Controller { ]; } + $default_bottom_key = isset( $settings['term_bottom_description_meta_key'] ) ? sanitize_key( (string) $settings['term_bottom_description_meta_key'] ) : ''; + $bottom_meta_key = apply_filters( 'groq_ai_term_bottom_description_meta_key', $default_bottom_key, $term, $settings ); + $bottom_meta_key = sanitize_key( (string) $bottom_meta_key ); + $has_bottom_field = ( '' !== $bottom_meta_key ); + + $top_description = isset( $parsed['description'] ) ? (string) $parsed['description'] : ''; + $bottom_description = isset( $parsed['bottom_description'] ) ? (string) $parsed['bottom_description'] : ''; + $apply_text = $has_bottom_field ? ( '' !== $bottom_description ? $bottom_description : $top_description ) : $top_description; + wp_send_json_success( [ - 'description' => isset( $parsed['description'] ) ? $parsed['description'] : '', + 'top_description' => $top_description, + 'bottom_description' => $has_bottom_field ? $apply_text : $bottom_description, + 'description' => $apply_text, 'raw' => $response_text, ] ); diff --git a/includes/Services/Prompt/class-groq-ai-prompt-builder.php b/includes/Services/Prompt/class-groq-ai-prompt-builder.php index d34ab35..7263af4 100644 --- a/includes/Services/Prompt/class-groq-ai-prompt-builder.php +++ b/includes/Services/Prompt/class-groq-ai-prompt-builder.php @@ -424,6 +424,15 @@ class Groq_AI_Prompt_Builder { $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 ); + 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 ) { $top_products = $this->get_top_products_for_term( $taxonomy, $term_id, $top_products_limit ); if ( ! empty( $top_products ) ) { @@ -465,13 +474,27 @@ class Groq_AI_Prompt_Builder { $title_pixels = $this->settings_manager->get_rankmath_meta_title_pixel_limit( $settings ); $desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings ); - $properties = [ - 'description' => [ + $bottom_meta_key = $this->resolve_term_bottom_description_meta_key( null, $settings ); + $use_bottom_field = ( '' !== $bottom_meta_key ); + + $properties = []; + $required = []; + + if ( $use_bottom_field ) { + $properties['bottom_description'] = [ 'type' => 'string', - 'description' => __( 'HTML-omschrijving voor de categorie/term met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), + 'description' => __( 'Uitgebreide HTML-omschrijving (helemaal onderaan) met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), 'minLength' => 20, - ], - ]; + ]; + $required[] = 'bottom_description'; + } else { + $properties['description'] = [ + 'type' => 'string', + 'description' => __( 'HTML-omschrijving (WordPress term description) met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), + 'minLength' => 20, + ]; + $required[] = 'description'; + } if ( $rankmath_enabled ) { $properties['meta_title'] = [ @@ -506,7 +529,7 @@ class Groq_AI_Prompt_Builder { $schema = [ 'type' => 'object', 'properties' => $properties, - 'required' => [ 'description' ], + 'required' => $required, 'additionalProperties' => false, ]; @@ -549,14 +572,37 @@ class Groq_AI_Prompt_Builder { ]; } + $bottom_meta_key = $this->resolve_term_bottom_description_meta_key( null, $settings ); + $use_bottom_field = ( '' !== $bottom_meta_key ); + $description = isset( $decoded['description'] ) ? trim( (string) $decoded['description'] ) : ''; - if ( '' === $description ) { - return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen description veld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); + $top = isset( $decoded['top_description'] ) ? trim( (string) $decoded['top_description'] ) : ''; + $bottom = isset( $decoded['bottom_description'] ) ? trim( (string) $decoded['bottom_description'] ) : ''; + + if ( $use_bottom_field ) { + if ( '' === $bottom ) { + $bottom = '' !== $description ? $description : $top; + } + if ( '' === $bottom ) { + return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen bottom_description veld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); + } + } else { + if ( '' === $description ) { + $description = '' !== $top ? $top : $bottom; + } + if ( '' === $description ) { + return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen description veld.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ); + } } - $result = [ - 'description' => $description, - ]; + $result = []; + if ( $use_bottom_field ) { + $result['bottom_description'] = $bottom; + // For backwards compatibility with existing UI, keep `description` alias. + $result['description'] = $bottom; + } else { + $result['description'] = $description; + } if ( isset( $decoded['meta_title'] ) ) { $result['meta_title'] = $this->truncate_meta_field( (string) $decoded['meta_title'], 60 ); @@ -582,9 +628,15 @@ class Groq_AI_Prompt_Builder { } private function get_term_structured_response_instructions( $settings = null ) { - $schema_parts = [ - '"description":"..."', - ]; + $bottom_meta_key = $this->resolve_term_bottom_description_meta_key( null, $settings ); + $use_bottom_field = ( '' !== $bottom_meta_key ); + + $schema_parts = []; + if ( $use_bottom_field ) { + $schema_parts[] = '"bottom_description":"..."'; + } else { + $schema_parts[] = '"description":"..."'; + } $rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings ); if ( $rankmath_enabled ) { @@ -600,11 +652,25 @@ class Groq_AI_Prompt_Builder { $json_structure ); - $instruction .= ' ' . __( 'Zorg dat description geldige HTML bevat (gebruik minimaal-tags en waar relevant lijstjes of benadrukking). Voeg geen extra tekst buiten het JSON-object toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); - $instruction .= ' ' . __( 'Als in de context een sectie "Interne links" staat, verwerk dan 2–5 van deze links natuurlijk in de description als HTML-links (Anker).', GROQ_AI_PRODUCT_TEXT_DOMAIN ); + if ( $use_bottom_field ) { + $instruction .= ' ' . __( 'Zorg dat bottom_description geldige HTML bevat. Dit is de tekst die helemaal onderaan de pagina komt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); + } else { + $instruction .= ' ' . __( 'Zorg dat description geldige HTML bevat.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); + } + $instruction .= ' ' . __( 'Voeg geen extra tekst buiten het JSON-object toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); + $instruction .= ' ' . __( 'Als in de context een sectie "Interne links" staat, verwerk dan 2–5 van deze links natuurlijk in de hoofdtekst als HTML-links (Anker).', GROQ_AI_PRODUCT_TEXT_DOMAIN ); 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 ) { $taxonomy = sanitize_key( (string) $taxonomy ); $term_id = absint( $term_id ); diff --git a/includes/Services/Settings/class-groq-ai-settings-manager.php b/includes/Services/Settings/class-groq-ai-settings-manager.php index 0675a7b..66c10cb 100644 --- a/includes/Services/Settings/class-groq-ai-settings-manager.php +++ b/includes/Services/Settings/class-groq-ai-settings-manager.php @@ -33,6 +33,7 @@ class Groq_AI_Settings_Manager { 'store_context' => '', 'default_prompt' => '', 'max_output_tokens' => 2048, + 'term_bottom_description_meta_key' => '', 'groq_api_key' => '', 'openai_api_key' => '', 'google_api_key' => '', @@ -89,6 +90,7 @@ class Groq_AI_Settings_Manager { 'store_context' => '', 'default_prompt' => '', 'max_output_tokens' => 2048, + 'term_bottom_description_meta_key' => '', 'groq_api_key' => '', 'openai_api_key' => '', 'google_api_key' => '', @@ -149,6 +151,7 @@ class Groq_AI_Settings_Manager { 'store_context' => sanitize_textarea_field( $input['store_context'] ), 'default_prompt' => sanitize_textarea_field( $input['default_prompt'] ), 'max_output_tokens' => $max_output_tokens, + 'term_bottom_description_meta_key' => sanitize_key( (string) $input['term_bottom_description_meta_key'] ), 'groq_api_key' => sanitize_text_field( $input['groq_api_key'] ), 'openai_api_key' => sanitize_text_field( $input['openai_api_key'] ), 'google_api_key' => sanitize_text_field( $input['google_api_key'] ), @@ -282,7 +285,7 @@ class Groq_AI_Settings_Manager { $config = $this->get_module_config( 'rankmath', $settings ); $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 ) { @@ -375,7 +378,7 @@ class Groq_AI_Settings_Manager { if ( $limit <= 0 ) { $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'] ); if ( $title_pixel_limit <= 0 ) {