-
+
-
+
@@ -436,7 +461,7 @@ class Groq_AI_Product_Text_Settings_Page {
-
+
plugin->get_option_key() ); ?>[model]"
data-current-model=""
>
-
+
-
+
@@ -476,7 +501,7 @@ class Groq_AI_Product_Text_Settings_Page {
get_label() )
);
?>
@@ -489,15 +514,15 @@ class Groq_AI_Product_Text_Settings_Page {
$settings = $this->plugin->get_settings();
?>
-
+
plugin->get_settings();
?>
-
-
+
+
-
+
plugin->get_option_key() ); ?>[modules][rankmath][enabled]" value="0" />
/>
-
+
/>
-
+
/>
-
+
wp_create_nonce( 'groq_ai_refresh_models' ),
'excludedModels' => Groq_AI_Model_Exclusions::get_all(),
'placeholders' => [
- 'selectModel' => __( 'Selecteer een model via "Live modellen ophalen"', 'groq-ai-product-text' ),
+ 'selectModel' => __( 'Selecteer een model via "Live modellen ophalen"', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
];
@@ -669,7 +694,7 @@ class Groq_AI_Product_Text_Settings_Page {
$cached_models = $this->plugin->get_cached_models_for_provider( $provider_key );
$cached_models = Groq_AI_Model_Exclusions::filter_models( $provider_key, $cached_models );
$data['providers'][ $provider->get_key() ] = [
- 'default_label' => sprintf( __( 'Gebruik standaardmodel (%s)', 'groq-ai-product-text' ), $provider->get_default_model() ),
+ 'default_label' => sprintf( __( 'Gebruik standaardmodel (%s)', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $provider->get_default_model() ),
'models' => $cached_models,
'supports_live' => $provider->supports_live_models(),
];
diff --git a/includes/Core/class-groq-ai-ajax-controller.php b/includes/Core/class-groq-ai-ajax-controller.php
index 3a0ad4a..26726a0 100644
--- a/includes/Core/class-groq-ai-ajax-controller.php
+++ b/includes/Core/class-groq-ai-ajax-controller.php
@@ -13,7 +13,7 @@ class Groq_AI_Ajax_Controller {
public function handle_generate_text() {
if ( ! current_user_can( 'edit_products' ) ) {
- wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', 'groq-ai-product-text' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
}
check_ajax_referer( 'groq_ai_generate', 'nonce' );
@@ -37,6 +37,7 @@ class Groq_AI_Ajax_Controller {
$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 );
$image_context_mode = $this->plugin->get_image_context_mode( $settings );
+ $image_context_limit = $this->plugin->get_image_context_limit( $settings );
if ( 'none' === $image_context_mode ) {
$context_fields['images'] = false;
@@ -44,7 +45,8 @@ class Groq_AI_Ajax_Controller {
$image_context_enabled = ! empty( $context_fields['images'] );
$use_base64_payloads = $image_context_enabled && 'base64' === $image_context_mode && $provider->supports_image_context();
- $image_context_count = $image_context_enabled ? $prompt_builder->get_product_image_count( $post_id ) : 0;
+ $total_image_count = $image_context_enabled ? $prompt_builder->get_product_image_count( $post_id ) : 0;
+ $image_context_count = $image_context_enabled ? min( $image_context_limit, $total_image_count ) : 0;
$prompt_image_mode = 'none';
if ( $image_context_enabled ) {
@@ -59,17 +61,19 @@ class Groq_AI_Ajax_Controller {
}
}
- $product_context_text = $prompt_builder->build_product_context_block( $post_id, $context_fields, $prompt_image_mode );
+ $product_context_text = $prompt_builder->build_product_context_block( $post_id, $context_fields, $prompt_image_mode, $image_context_limit );
$image_context_payloads = [];
if ( $use_base64_payloads ) {
- $image_context_payloads = $prompt_builder->get_product_image_payloads( $post_id );
+ $image_context_payloads = $prompt_builder->get_product_image_payloads( $post_id, $image_context_limit );
}
$prompt_with_context = $prompt_builder->prepend_context_to_prompt( $prompt, $product_context_text );
$image_context_meta = [
'requested_mode' => $image_context_mode,
'effective_mode' => $prompt_image_mode,
- 'available' => $image_context_count,
+ 'limit' => $image_context_limit,
+ 'available' => $total_image_count,
+ 'used' => $image_context_count,
'base64_sent' => $use_base64_payloads ? count( $image_context_payloads ) : 0,
];
@@ -160,7 +164,7 @@ class Groq_AI_Ajax_Controller {
public function handle_refresh_models() {
if ( ! current_user_can( 'manage_options' ) ) {
- wp_send_json_error( [ 'message' => __( 'Geen toestemming.', 'groq-ai-product-text' ) ], 403 );
+ wp_send_json_error( [ 'message' => __( 'Geen toestemming.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
}
check_ajax_referer( 'groq_ai_refresh_models', 'nonce' );
@@ -169,13 +173,13 @@ class Groq_AI_Ajax_Controller {
$api_key = isset( $_POST['apiKey'] ) ? sanitize_text_field( wp_unslash( $_POST['apiKey'] ) ) : '';
if ( empty( $provider_key ) || empty( $api_key ) ) {
- wp_send_json_error( [ 'message' => __( 'Provider en API-sleutel zijn verplicht.', 'groq-ai-product-text' ) ], 400 );
+ wp_send_json_error( [ 'message' => __( 'Provider en API-sleutel zijn verplicht.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 400 );
}
$provider = $this->plugin->get_provider_manager()->get_provider( $provider_key );
if ( ! $provider || ! $provider->supports_live_models() ) {
- wp_send_json_error( [ 'message' => __( 'Deze aanbieder ondersteunt het ophalen van modellen niet.', 'groq-ai-product-text' ) ], 400 );
+ wp_send_json_error( [ 'message' => __( 'Deze aanbieder ondersteunt het ophalen van modellen niet.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 400 );
}
$result = $provider->fetch_live_models( $api_key );
diff --git a/includes/Providers/class-groq-ai-abstract-openai-provider.php b/includes/Providers/class-groq-ai-abstract-openai-provider.php
index 85905ff..e8ca7fd 100644
--- a/includes/Providers/class-groq-ai-abstract-openai-provider.php
+++ b/includes/Providers/class-groq-ai-abstract-openai-provider.php
@@ -16,7 +16,7 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
public function fetch_live_models( $api_key ) {
$endpoint = $this->get_models_endpoint();
if ( empty( $endpoint ) ) {
- return new WP_Error( 'groq_ai_models_endpoint_missing', __( 'Geen model-endpoint beschikbaar voor deze aanbieder.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_models_endpoint_missing', __( 'Geen model-endpoint beschikbaar voor deze aanbieder.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
$response = wp_remote_get(
@@ -41,7 +41,7 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
}
if ( empty( $body['data'] ) || ! is_array( $body['data'] ) ) {
- return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
$models = [];
@@ -52,7 +52,7 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
}
if ( empty( $models ) ) {
- return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
return $models;
@@ -66,7 +66,7 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
$api_key = $this->get_api_key( $settings );
if ( empty( $api_key ) ) {
- return new WP_Error( 'groq_ai_missing_api_key', sprintf( __( 'Stel eerst de API-sleutel voor %s in.', 'groq-ai-product-text' ), $this->get_label() ) );
+ return new WP_Error( 'groq_ai_missing_api_key', sprintf( __( 'Stel eerst de API-sleutel voor %s in.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $this->get_label() ) );
}
$messages = [
@@ -116,7 +116,7 @@ abstract class Groq_AI_Abstract_OpenAI_Provider implements Groq_AI_Provider_Inte
if ( empty( $body['choices'][0]['message']['content'] ) ) {
return new WP_Error(
'groq_ai_empty_response',
- sprintf( __( 'Geen antwoord ontvangen van %s.', 'groq-ai-product-text' ), $this->get_label() )
+ sprintf( __( 'Geen antwoord ontvangen van %s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $this->get_label() )
);
}
diff --git a/includes/Providers/class-groq-ai-provider-google.php b/includes/Providers/class-groq-ai-provider-google.php
index bffb6f5..1f7ed21 100644
--- a/includes/Providers/class-groq-ai-provider-google.php
+++ b/includes/Providers/class-groq-ai-provider-google.php
@@ -6,7 +6,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
}
public function get_label() {
- return __( 'Google AI (Gemini)', 'groq-ai-product-text' );
+ return __( 'Google AI (Gemini)', GROQ_AI_PRODUCT_TEXT_DOMAIN );
}
public function get_default_model() {
@@ -61,7 +61,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
}
if ( empty( $body['models'] ) || ! is_array( $body['models'] ) ) {
- return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
$models = [];
@@ -73,7 +73,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
}
if ( empty( $models ) ) {
- return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_empty_response', __( 'Geen modeldata ontvangen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
return $models;
@@ -87,7 +87,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
$api_key = isset( $settings[ $this->get_option_key() ] ) ? $settings[ $this->get_option_key() ] : '';
if ( empty( $api_key ) ) {
- return new WP_Error( 'groq_ai_missing_api_key', sprintf( __( 'Stel eerst de API-sleutel voor %s in.', 'groq-ai-product-text' ), $this->get_label() ) );
+ return new WP_Error( 'groq_ai_missing_api_key', sprintf( __( 'Stel eerst de API-sleutel voor %s in.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $this->get_label() ) );
}
$endpoint = add_query_arg(
@@ -123,7 +123,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
$parts[] = [
'text' => sprintf(
/* translators: %s: image label */
- __( 'Contextafbeelding: %s', 'groq-ai-product-text' ),
+ __( 'Contextafbeelding: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$label
),
];
@@ -181,7 +181,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
if ( empty( $body['candidates'][0]['content']['parts'] ) ) {
return new WP_Error(
'groq_ai_empty_response',
- sprintf( __( 'Geen antwoord ontvangen van %s.', 'groq-ai-product-text' ), $this->get_label() )
+ sprintf( __( 'Geen antwoord ontvangen van %s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $this->get_label() )
);
}
diff --git a/includes/Providers/class-groq-ai-provider-groq.php b/includes/Providers/class-groq-ai-provider-groq.php
index 83ea36b..3933eb9 100644
--- a/includes/Providers/class-groq-ai-provider-groq.php
+++ b/includes/Providers/class-groq-ai-provider-groq.php
@@ -6,7 +6,7 @@ class Groq_AI_Provider_Groq extends Groq_AI_Abstract_OpenAI_Provider {
}
public function get_label() {
- return __( 'Groq', 'groq-ai-product-text' );
+ return __( 'Groq', GROQ_AI_PRODUCT_TEXT_DOMAIN );
}
public function get_default_model() {
diff --git a/includes/Providers/class-groq-ai-provider-openai.php b/includes/Providers/class-groq-ai-provider-openai.php
index a94dc93..13a3026 100644
--- a/includes/Providers/class-groq-ai-provider-openai.php
+++ b/includes/Providers/class-groq-ai-provider-openai.php
@@ -6,7 +6,7 @@ class Groq_AI_Provider_OpenAI extends Groq_AI_Abstract_OpenAI_Provider {
}
public function get_label() {
- return __( 'OpenAI', 'groq-ai-product-text' );
+ return __( 'OpenAI', GROQ_AI_PRODUCT_TEXT_DOMAIN );
}
public function get_default_model() {
diff --git a/includes/Services/Prompt/class-groq-ai-prompt-builder.php b/includes/Services/Prompt/class-groq-ai-prompt-builder.php
index d689158..7a511e7 100644
--- a/includes/Services/Prompt/class-groq-ai-prompt-builder.php
+++ b/includes/Services/Prompt/class-groq-ai-prompt-builder.php
@@ -13,17 +13,17 @@ class Groq_AI_Prompt_Builder {
public function build_system_prompt( $settings, $conversation_id ) {
$context = isset( $settings['store_context'] ) ? trim( $settings['store_context'] ) : '';
- $base_instruction = __( 'Je bent een copywriter voor een WooCommerce winkel en schrijft overtuigende productbeschrijvingen.', 'groq-ai-product-text' );
+ $base_instruction = __( 'Je bent een copywriter voor een WooCommerce winkel en schrijft overtuigende productbeschrijvingen.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
if ( $context ) {
$base_instruction = sprintf(
- __( 'Je bent een copywriter voor een WooCommerce winkel. Gebruik de volgende context indien beschikbaar: %s', 'groq-ai-product-text' ),
+ __( 'Je bent een copywriter voor een WooCommerce winkel. Gebruik de volgende context indien beschikbaar: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$context
);
}
return sprintf(
- __( 'Conversatie-ID: %1$s. %2$s', 'groq-ai-product-text' ),
+ __( 'Conversatie-ID: %1$s. %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$conversation_id,
$base_instruction
);
@@ -46,7 +46,7 @@ class Groq_AI_Prompt_Builder {
public function parse_structured_response( $raw, $settings = null ) {
if ( empty( $raw ) ) {
- return new WP_Error( 'groq_ai_empty_response', __( 'Geen data ontvangen van de AI.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_empty_response', __( 'Geen data ontvangen van de AI.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
$clean = trim( $raw );
@@ -58,7 +58,7 @@ class Groq_AI_Prompt_Builder {
$decoded = json_decode( $clean, true );
if ( ! is_array( $decoded ) ) {
- return new WP_Error( 'groq_ai_parse_error', __( 'Kon de AI-respons niet als JSON lezen. Probeer het opnieuw.', 'groq-ai-product-text' ) );
+ return new WP_Error( 'groq_ai_parse_error', __( 'Kon de AI-respons niet als JSON lezen. Probeer het opnieuw.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
$fields = [
@@ -67,6 +67,40 @@ class Groq_AI_Prompt_Builder {
'description' => trim( (string) ( $decoded['description'] ?? '' ) ),
];
+ $title_suggestions = [];
+ if ( isset( $decoded['title_suggestions'] ) && is_array( $decoded['title_suggestions'] ) ) {
+ foreach ( $decoded['title_suggestions'] as $suggestion ) {
+ $suggestion = sanitize_text_field( (string) $suggestion );
+ $suggestion = trim( preg_replace( '/\s+/', ' ', $suggestion ) );
+
+ if ( '' === $suggestion ) {
+ continue;
+ }
+
+ $title_suggestions[] = $suggestion;
+
+ if ( count( $title_suggestions ) >= 3 ) {
+ break;
+ }
+ }
+ }
+
+ if ( empty( $title_suggestions ) && '' !== $fields['title'] ) {
+ $title_suggestions[] = $fields['title'];
+ }
+
+ if ( '' === $fields['title'] && ! empty( $title_suggestions ) ) {
+ $fields['title'] = $title_suggestions[0];
+ }
+
+ $fields['title_suggestions'] = $title_suggestions;
+
+ $slug_value = isset( $decoded['slug'] ) ? sanitize_title( $decoded['slug'] ) : '';
+ if ( '' === $slug_value && '' !== $fields['title'] ) {
+ $slug_value = sanitize_title( $fields['title'] );
+ }
+ $fields['slug'] = $slug_value;
+
if ( $this->settings_manager->is_module_enabled( 'rankmath', $settings ) ) {
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
$focus_keywords = [];
@@ -98,8 +132,22 @@ class Groq_AI_Prompt_Builder {
$fields['focus_keywords'] = implode( ', ', $focus_keywords );
}
- if ( implode( '', $fields ) === '' ) {
- return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen bruikbare velden.', 'groq-ai-product-text' ) );
+ $primary_values = [
+ $fields['title'],
+ $fields['short_description'],
+ $fields['description'],
+ ];
+
+ $has_primary_content = false;
+ foreach ( $primary_values as $value ) {
+ if ( '' !== trim( (string) $value ) ) {
+ $has_primary_content = true;
+ break;
+ }
+ }
+
+ if ( ! $has_primary_content ) {
+ return new WP_Error( 'groq_ai_parse_error', __( 'De AI-respons bevatte geen bruikbare velden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
}
return $fields;
@@ -125,7 +173,7 @@ class Groq_AI_Prompt_Builder {
return $normalized;
}
- public function build_product_context_block( $post_id, $fields, $image_mode = 'url' ) {
+ public function build_product_context_block( $post_id, $fields, $image_mode = 'url', $image_limit = 3 ) {
$post_id = absint( $post_id );
if ( ! $post_id ) {
@@ -137,35 +185,35 @@ class Groq_AI_Prompt_Builder {
if ( ! empty( $fields['title'] ) ) {
$title = get_the_title( $post_id );
if ( $title ) {
- $parts[] = sprintf( __( 'Titel: %s', 'groq-ai-product-text' ), wp_strip_all_tags( $title ) );
+ $parts[] = sprintf( __( 'Titel: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( $title ) );
}
}
if ( ! empty( $fields['short_description'] ) ) {
$excerpt = get_post_field( 'post_excerpt', $post_id );
if ( $excerpt ) {
- $parts[] = sprintf( __( 'Korte beschrijving: %s', 'groq-ai-product-text' ), wp_strip_all_tags( $excerpt ) );
+ $parts[] = sprintf( __( 'Korte beschrijving: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( $excerpt ) );
}
}
if ( ! empty( $fields['description'] ) ) {
$content = get_post_field( 'post_content', $post_id );
if ( $content ) {
- $parts[] = sprintf( __( 'Beschrijving: %s', 'groq-ai-product-text' ), wp_strip_all_tags( $content ) );
+ $parts[] = sprintf( __( 'Beschrijving: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( $content ) );
}
}
if ( ! empty( $fields['attributes'] ) ) {
$attributes = $this->get_product_attributes_text( $post_id );
if ( $attributes ) {
- $parts[] = sprintf( __( 'Attributen: %s', 'groq-ai-product-text' ), $attributes );
+ $parts[] = sprintf( __( 'Attributen: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $attributes );
}
}
if ( ! empty( $fields['images'] ) && 'url' === $image_mode ) {
- $images = $this->get_product_images_text( $post_id );
+ $images = $this->get_product_images_text( $post_id, $image_limit );
if ( $images ) {
- $parts[] = sprintf( __( 'Afbeeldingen: %s', 'groq-ai-product-text' ), $images );
+ $parts[] = sprintf( __( 'Afbeeldingen: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $images );
}
}
@@ -179,7 +227,7 @@ class Groq_AI_Prompt_Builder {
return $prompt;
}
- $intro = __( 'Gebruik de volgende productcontext bij het schrijven:', 'groq-ai-product-text' );
+ $intro = __( 'Gebruik de volgende productcontext bij het schrijven:', GROQ_AI_PRODUCT_TEXT_DOMAIN );
return $intro . "\n" . $context . "\n\n" . $prompt;
}
@@ -191,19 +239,36 @@ class Groq_AI_Prompt_Builder {
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
$properties = [
+ 'title_suggestions' => [
+ 'type' => 'array',
+ 'description' => __( 'Exact drie korte producttitelvoorstellen in het Nederlands. Kies de beste ook als title.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'minItems' => 3,
+ 'maxItems' => 3,
+ 'items' => [
+ 'type' => 'string',
+ 'minLength' => 3,
+ 'maxLength' => 120,
+ ],
+ ],
'title' => [
'type' => 'string',
- 'description' => __( 'Korte, overtuigende producttitel in het Nederlands.', 'groq-ai-product-text' ),
+ 'description' => __( 'Korte, overtuigende producttitel in het Nederlands.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'minLength' => 3,
],
+ 'slug' => [
+ 'type' => 'string',
+ 'description' => __( 'Productslug voor de URL (alleen kleine letters, cijfers en koppeltekens).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'minLength' => 3,
+ 'pattern' => '^[a-z0-9\\-]+$',
+ ],
'short_description' => [
'type' => 'string',
- 'description' => __( "Korte HTML-beschrijving in
-tags (maximaal 2 alinea's).", 'groq-ai-product-text' ),
+ 'description' => __( "Korte HTML-beschrijving in
-tags (maximaal 2 alinea's).", GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'minLength' => 10,
],
'description' => [
'type' => 'string',
- 'description' => __( 'Uitgebreide HTML-productbeschrijving met paragrafen en eventueel lijsten.', 'groq-ai-product-text' ),
+ 'description' => __( 'Uitgebreide HTML-productbeschrijving met paragrafen en eventueel lijsten.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'minLength' => 20,
],
];
@@ -213,7 +278,7 @@ class Groq_AI_Prompt_Builder {
'type' => 'string',
'description' => sprintf(
/* translators: 1: maximum character count, 2: maximum pixels */
- __( 'SEO-meta title (max. %1$d tekens en %2$d pixels).', 'groq-ai-product-text' ),
+ __( 'SEO-meta title (max. %1$d tekens en %2$d pixels).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
60,
$title_pixels
),
@@ -223,7 +288,7 @@ class Groq_AI_Prompt_Builder {
'type' => 'string',
'description' => sprintf(
/* translators: 1: maximum character count, 2: maximum pixels */
- __( 'SEO-meta description (max. %1$d tekens en %2$d pixels).', 'groq-ai-product-text' ),
+ __( 'SEO-meta description (max. %1$d tekens en %2$d pixels).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
160,
$desc_pixels
),
@@ -231,7 +296,7 @@ class Groq_AI_Prompt_Builder {
];
$properties['focus_keywords'] = [
'type' => 'array',
- 'description' => __( 'Lijst met korte zoekwoorden zonder hashtags of extra tekst.', 'groq-ai-product-text' ),
+ 'description' => __( 'Lijst met korte zoekwoorden zonder hashtags of extra tekst.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'maxItems' => max( 1, $keyword_limit ),
'items' => [
'type' => 'string',
@@ -243,7 +308,7 @@ class Groq_AI_Prompt_Builder {
$schema = [
'type' => 'object',
'properties' => $properties,
- 'required' => [ 'title', 'short_description', 'description' ],
+ 'required' => [ 'title_suggestions', 'title', 'slug', 'short_description', 'description' ],
'additionalProperties' => false,
];
@@ -258,7 +323,9 @@ class Groq_AI_Prompt_Builder {
private function get_structured_response_instructions( $settings = null ) {
$schema_parts = [
+ '"title_suggestions":["...","...","..."]',
'"title":"..."',
+ '"slug":"..."',
'"short_description":"..."',
'"description":"..."',
];
@@ -274,7 +341,7 @@ class Groq_AI_Prompt_Builder {
$instruction = sprintf(
/* translators: %s: JSON structure example */
- __( 'Geef ALLEEN een geldig JSON-object terug met deze structuur: %s. Gebruik dubbele aanhalingstekens, geen Markdown of extra tekst. Gebruik \\n voor regeleinden. Zorg dat zowel short_description als description nooit leeg zijn.', 'groq-ai-product-text' ),
+ __( 'Geef ALLEEN een geldig JSON-object terug met deze structuur: %s. Gebruik dubbele aanhalingstekens, geen Markdown of extra tekst. Gebruik \\n voor regeleinden. Zorg dat zowel short_description als description nooit leeg zijn.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$json_structure
);
@@ -284,14 +351,16 @@ class Groq_AI_Prompt_Builder {
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
$instruction .= ' ' . sprintf(
/* translators: 1: focus keyword limit, 2: meta title pixel limit, 3: meta description pixel limit */
- __( 'Beperk meta_title tot maximaal 60 tekens en %2$d pixels en meta_description tot maximaal 160 tekens en %3$d pixels. Lever maximaal %1$d focuskeywords in het focus_keywords-array (korte termen zonder hashtag of extra tekst).', 'groq-ai-product-text' ),
+ __( 'Beperk meta_title tot maximaal 60 tekens en %2$d pixels en meta_description tot maximaal 160 tekens en %3$d pixels. Lever maximaal %1$d focuskeywords in het focus_keywords-array (korte termen zonder hashtag of extra tekst).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
$keyword_limit,
$title_pixels,
$desc_pixels
);
}
- $instruction .= ' ' . __( 'Zorg dat short_description en description geldige HTML bevatten (gebruik minimaal
-tags en waar relevant lijstjes of benadrukking). Voeg geen extra tekst buiten het JSON-object toe.', 'groq-ai-product-text' );
+ $instruction .= ' ' . __( 'Lever exact drie verschillende titelvoorstellen in title_suggestions en kopieer de beste keuze naar title.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
+ $instruction .= ' ' . __( 'Zorg dat short_description en description geldige HTML bevatten (gebruik minimaal
-tags en waar relevant lijstjes of benadrukking). Voeg geen extra tekst buiten het JSON-object toe.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
+ $instruction .= ' ' . __( 'Maak de slug URL-vriendelijk, gebruik alleen kleine letters, cijfers en koppeltekens en geen spaties.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
return $instruction;
}
@@ -358,9 +427,14 @@ class Groq_AI_Prompt_Builder {
return implode( '; ', $lines );
}
- private function get_product_images_text( $post_id ) {
+ private function get_product_images_text( $post_id, $limit = 3 ) {
+ $limit = max( 0, (int) $limit );
$image_ids = $this->get_product_image_ids( $post_id );
+ if ( $limit > 0 ) {
+ $image_ids = array_slice( $image_ids, 0, $limit );
+ }
+
if ( empty( $image_ids ) ) {
return '';
}
@@ -380,7 +454,13 @@ class Groq_AI_Prompt_Builder {
}
public function get_product_image_payloads( $post_id, $limit = 3, $max_filesize = 1572864 ) {
- $image_ids = array_slice( $this->get_product_image_ids( $post_id ), 0, max( 1, (int) $limit ) );
+ $limit = max( 0, (int) $limit );
+
+ if ( $limit <= 0 ) {
+ return [];
+ }
+
+ $image_ids = array_slice( $this->get_product_image_ids( $post_id ), 0, $limit );
if ( empty( $image_ids ) ) {
return [];
@@ -476,7 +556,7 @@ class Groq_AI_Prompt_Builder {
$label = trim( wp_strip_all_tags( (string) $label ) );
if ( '' === $label ) {
- $label = sprintf( __( 'Afbeelding %d', 'groq-ai-product-text' ), $position );
+ $label = sprintf( __( 'Afbeelding %d', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $position );
}
$path = get_attached_file( $attachment_id );
diff --git a/includes/Services/Settings/class-groq-ai-settings-manager.php b/includes/Services/Settings/class-groq-ai-settings-manager.php
index 02dd390..c1532c1 100644
--- a/includes/Services/Settings/class-groq-ai-settings-manager.php
+++ b/includes/Services/Settings/class-groq-ai-settings-manager.php
@@ -38,6 +38,7 @@ class Groq_AI_Settings_Manager {
'context_fields' => $this->get_default_context_fields(),
'modules' => $this->get_default_modules_settings(),
'image_context_mode' => 'url',
+ 'image_context_limit' => 3,
'response_format_compat' => false,
];
@@ -59,6 +60,9 @@ class Groq_AI_Settings_Manager {
$settings['image_context_mode'] = 'url';
}
+ $limit = isset( $settings['image_context_limit'] ) ? $this->sanitize_image_context_limit_value( $settings['image_context_limit'] ) : 3;
+ $settings['image_context_limit'] = $limit;
+
return $settings;
}
@@ -80,6 +84,7 @@ class Groq_AI_Settings_Manager {
'context_fields' => $this->get_default_context_fields(),
'modules' => $this->get_default_modules_settings(),
'image_context_mode' => 'url',
+ 'image_context_limit' => 3,
'response_format_compat' => false,
];
@@ -104,6 +109,8 @@ class Groq_AI_Settings_Manager {
$image_mode = 'url';
}
+ $image_limit = isset( $input['image_context_limit'] ) ? $this->sanitize_image_context_limit_value( $input['image_context_limit'] ) : $defaults['image_context_limit'];
+
$context_fields = $this->normalize_context_fields( $context_posted ? $raw_input['context_fields'] : $defaults['context_fields'] );
if ( 'none' === $image_mode ) {
@@ -122,6 +129,7 @@ class Groq_AI_Settings_Manager {
'google_api_key' => sanitize_text_field( $input['google_api_key'] ),
'response_format_compat' => ! empty( $raw_input['response_format_compat'] ),
'image_context_mode' => $image_mode,
+ 'image_context_limit' => $image_limit,
'context_fields' => $context_fields,
'modules' => $this->sanitize_modules_settings(
$modules_posted ? $raw_input['modules'] : [],
@@ -136,28 +144,28 @@ class Groq_AI_Settings_Manager {
if ( null === $this->context_field_definitions ) {
$this->context_field_definitions = [
'title' => [
- 'label' => __( 'Producttitel', 'groq-ai-product-text' ),
- 'description' => __( 'Voeg de huidige producttitel toe als context.', 'groq-ai-product-text' ),
+ 'label' => __( 'Producttitel', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'description' => __( 'Voeg de huidige producttitel toe als context.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'default' => true,
],
'short_description' => [
- 'label' => __( 'Korte beschrijving', 'groq-ai-product-text' ),
- 'description' => __( 'Gebruik de bestaande korte beschrijving (indien aanwezig).', 'groq-ai-product-text' ),
+ 'label' => __( 'Korte beschrijving', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'description' => __( 'Gebruik de bestaande korte beschrijving (indien aanwezig).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'default' => true,
],
'description' => [
- 'label' => __( 'Volledige beschrijving', 'groq-ai-product-text' ),
- 'description' => __( 'Stuurt de huidige productbeschrijving mee als bronmateriaal.', 'groq-ai-product-text' ),
+ 'label' => __( 'Volledige beschrijving', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'description' => __( 'Stuurt de huidige productbeschrijving mee als bronmateriaal.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'default' => true,
],
'attributes' => [
- 'label' => __( 'Attributen', 'groq-ai-product-text' ),
- 'description' => __( 'Voeg gestructureerde productattributen toe (zoals kleur, maat, materiaal).', 'groq-ai-product-text' ),
+ 'label' => __( 'Attributen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'description' => __( 'Voeg gestructureerde productattributen toe (zoals kleur, maat, materiaal).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'default' => false,
],
'images' => [
- 'label' => __( 'Afbeeldingen', 'groq-ai-product-text' ),
- 'description' => __( 'Voeg een korte lijst toe met productafbeeldingen (beschrijving + URL).', 'groq-ai-product-text' ),
+ 'label' => __( 'Afbeeldingen', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
+ 'description' => __( 'Voeg een korte lijst toe met productafbeeldingen (beschrijving + URL).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'default' => false,
],
];
@@ -268,6 +276,16 @@ class Groq_AI_Settings_Manager {
return in_array( $mode, $allowed_modes, true ) ? $mode : 'url';
}
+ public function get_image_context_limit( $settings = null ) {
+ if ( null === $settings ) {
+ $settings = $this->all();
+ }
+
+ $limit = isset( $settings['image_context_limit'] ) ? $settings['image_context_limit'] : 3;
+
+ return $this->sanitize_image_context_limit_value( $limit );
+ }
+
public function is_response_format_compat_enabled( $settings = null ) {
if ( null === $settings ) {
$settings = $this->all();
@@ -341,4 +359,14 @@ class Groq_AI_Settings_Manager {
return $result;
}
+
+ private function sanitize_image_context_limit_value( $value ) {
+ $limit = absint( $value );
+
+ if ( $limit <= 0 ) {
+ $limit = 1;
+ }
+
+ return min( 10, $limit );
+ }
}
diff --git a/languages/.gitkeep b/languages/.gitkeep
new file mode 100644
index 0000000..e69de29