Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c4ef5e16a | |||
| 1bb10f4b45 | |||
| 95f7983e70 |
102
assets/js/term-admin.js
Normal file
102
assets/js/term-admin.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
(function () {
|
||||||
|
if (!window.GroqAITermGenerator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = document.getElementById('groq-ai-term-form');
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promptField = document.getElementById('groq-ai-term-prompt');
|
||||||
|
const outputField = document.getElementById('groq-ai-term-generated');
|
||||||
|
const rawField = document.getElementById('groq-ai-term-raw');
|
||||||
|
const statusField = document.getElementById('groq-ai-term-status');
|
||||||
|
const applyButton = document.getElementById('groq-ai-term-apply');
|
||||||
|
const includeTopProducts = document.getElementById('groq-ai-term-include-top-products');
|
||||||
|
const topProductsLimit = document.getElementById('groq-ai-term-top-products-limit');
|
||||||
|
|
||||||
|
function setStatus(message, type) {
|
||||||
|
if (!statusField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
statusField.textContent = message || '';
|
||||||
|
statusField.setAttribute('data-status', type || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoading(isLoading) {
|
||||||
|
form.classList.toggle('is-loading', !!isLoading);
|
||||||
|
const buttons = form.querySelectorAll('button, input[type="submit"]');
|
||||||
|
buttons.forEach((btn) => {
|
||||||
|
btn.disabled = !!isLoading;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPayload(prompt) {
|
||||||
|
const payload = new URLSearchParams();
|
||||||
|
payload.append('action', 'groq_ai_generate_term_text');
|
||||||
|
payload.append('nonce', GroqAITermGenerator.nonce);
|
||||||
|
payload.append('taxonomy', GroqAITermGenerator.taxonomy);
|
||||||
|
payload.append('term_id', GroqAITermGenerator.termId);
|
||||||
|
payload.append('prompt', prompt);
|
||||||
|
payload.append('include_top_products', includeTopProducts && includeTopProducts.checked ? '1' : '0');
|
||||||
|
payload.append('top_products_limit', topProductsLimit ? String(topProductsLimit.value || '') : '10');
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applyButton) {
|
||||||
|
applyButton.addEventListener('click', () => {
|
||||||
|
const descriptionField = document.getElementById('description');
|
||||||
|
if (!descriptionField || !outputField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
descriptionField.value = outputField.value || '';
|
||||||
|
setStatus('Tekst ingevuld in het beschrijving-veld. Vergeet niet op "Opslaan" te klikken.', 'success');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const prompt = promptField ? (promptField.value || '').trim() : '';
|
||||||
|
if (!prompt) {
|
||||||
|
setStatus('Vul eerst een prompt in.', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setStatus('AI is bezig met schrijven...', 'loading');
|
||||||
|
if (rawField) {
|
||||||
|
rawField.textContent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(GroqAITermGenerator.ajaxUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
|
},
|
||||||
|
body: buildPayload(prompt).toString(),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((json) => {
|
||||||
|
if (!json.success) {
|
||||||
|
const errorMessage = json.data && json.data.message ? json.data.message : 'Onbekende fout';
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputField) {
|
||||||
|
outputField.value = (json.data && json.data.description ? json.data.description : '').trim();
|
||||||
|
}
|
||||||
|
if (rawField) {
|
||||||
|
rawField.textContent = (json.data && json.data.raw ? String(json.data.raw) : '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus('Tekst gegenereerd. Je kunt hem toepassen en opslaan.', 'success');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setStatus(error && error.message ? error.message : 'Er ging iets mis bij het genereren.', 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
@@ -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.3.0
|
* Version: 1.4.2
|
||||||
* Author: SitiAI
|
* Author: SitiAI
|
||||||
* Text Domain: siti-ai-product-content-generator
|
* Text Domain: siti-ai-product-content-generator
|
||||||
* Domain Path: /languages
|
* Domain Path: /languages
|
||||||
@@ -54,6 +54,10 @@ require_once __DIR__ . '/includes/Services/Settings/class-groq-ai-settings-manag
|
|||||||
require_once __DIR__ . '/includes/Services/Prompt/class-groq-ai-prompt-builder.php';
|
require_once __DIR__ . '/includes/Services/Prompt/class-groq-ai-prompt-builder.php';
|
||||||
require_once __DIR__ . '/includes/Services/Conversations/class-groq-ai-conversation-manager.php';
|
require_once __DIR__ . '/includes/Services/Conversations/class-groq-ai-conversation-manager.php';
|
||||||
require_once __DIR__ . '/includes/Services/Logging/class-groq-ai-generation-logger.php';
|
require_once __DIR__ . '/includes/Services/Logging/class-groq-ai-generation-logger.php';
|
||||||
|
require_once __DIR__ . '/includes/Services/Google/class-groq-ai-google-oauth-client.php';
|
||||||
|
require_once __DIR__ . '/includes/Services/Google/class-groq-ai-google-search-console-client.php';
|
||||||
|
require_once __DIR__ . '/includes/Services/Google/class-groq-ai-google-analytics-data-client.php';
|
||||||
|
require_once __DIR__ . '/includes/Services/Google/class-groq-ai-google-context-builder.php';
|
||||||
require_once __DIR__ . '/includes/Admin/class-groq-ai-settings-page.php';
|
require_once __DIR__ . '/includes/Admin/class-groq-ai-settings-page.php';
|
||||||
require_once __DIR__ . '/includes/Admin/class-groq-ai-logs-table.php';
|
require_once __DIR__ . '/includes/Admin/class-groq-ai-logs-table.php';
|
||||||
require_once __DIR__ . '/includes/Admin/class-groq-ai-product-ui.php';
|
require_once __DIR__ . '/includes/Admin/class-groq-ai-product-ui.php';
|
||||||
@@ -108,6 +112,7 @@ final class Groq_AI_Product_Text_Plugin {
|
|||||||
add_action( 'init', [ $this, 'load_textdomain' ] );
|
add_action( 'init', [ $this, 'load_textdomain' ] );
|
||||||
add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] );
|
add_action( 'plugins_loaded', [ $this, 'maybe_create_logs_table' ] );
|
||||||
add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] );
|
add_action( 'load-plugins.php', [ $this, 'maybe_deactivate_if_woocommerce_missing' ] );
|
||||||
|
add_filter( 'groq_ai_term_google_context', [ $this, 'inject_google_term_context' ], 10, 3 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public function load_textdomain() {
|
public function load_textdomain() {
|
||||||
@@ -179,10 +184,47 @@ final class Groq_AI_Product_Text_Plugin {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$this->container->set(
|
||||||
|
'google_oauth_client',
|
||||||
|
function () {
|
||||||
|
return new Groq_AI_Google_OAuth_Client();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->container->set(
|
||||||
|
'gsc_client',
|
||||||
|
function ( Groq_AI_Service_Container $container ) {
|
||||||
|
return new Groq_AI_Google_Search_Console_Client( $container->get( 'google_oauth_client' ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->container->set(
|
||||||
|
'ga_client',
|
||||||
|
function ( Groq_AI_Service_Container $container ) {
|
||||||
|
return new Groq_AI_Google_Analytics_Data_Client( $container->get( 'google_oauth_client' ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->container->set(
|
||||||
|
'google_context_builder',
|
||||||
|
function ( Groq_AI_Service_Container $container ) {
|
||||||
|
return new Groq_AI_Google_Context_Builder( $container->get( 'gsc_client' ), $container->get( 'ga_client' ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Instantiate controller immediately so hooks are registered.
|
// Instantiate controller immediately so hooks are registered.
|
||||||
$this->container->get( 'ajax_controller' );
|
$this->container->get( 'ajax_controller' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function inject_google_term_context( $existing, $term, $settings ) {
|
||||||
|
$builder = $this->container->get( 'google_context_builder' );
|
||||||
|
if ( ! $builder ) {
|
||||||
|
return (string) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $builder->build_term_google_context( $existing, $term, $settings );
|
||||||
|
}
|
||||||
|
|
||||||
public function get_option_key() {
|
public function get_option_key() {
|
||||||
return self::OPTION_KEY;
|
return self::OPTION_KEY;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,108 @@ class Groq_AI_Ajax_Controller {
|
|||||||
|
|
||||||
add_action( 'wp_ajax_groq_ai_generate_text', [ $this, 'handle_generate_text' ] );
|
add_action( 'wp_ajax_groq_ai_generate_text', [ $this, 'handle_generate_text' ] );
|
||||||
add_action( 'wp_ajax_groq_ai_refresh_models', [ $this, 'handle_refresh_models' ] );
|
add_action( 'wp_ajax_groq_ai_refresh_models', [ $this, 'handle_refresh_models' ] );
|
||||||
|
add_action( 'wp_ajax_groq_ai_generate_term_text', [ $this, 'handle_generate_term_text' ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle_generate_term_text() {
|
||||||
|
if ( ! current_user_can( 'manage_options' ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => __( 'Je hebt geen toestemming voor deze actie.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 403 );
|
||||||
|
}
|
||||||
|
|
||||||
|
check_ajax_referer( 'groq_ai_generate_term', 'nonce' );
|
||||||
|
|
||||||
|
$prompt = isset( $_POST['prompt'] ) ? sanitize_textarea_field( wp_unslash( $_POST['prompt'] ) ) : '';
|
||||||
|
$taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( wp_unslash( $_POST['taxonomy'] ) ) : '';
|
||||||
|
$term_id = isset( $_POST['term_id'] ) ? absint( $_POST['term_id'] ) : 0;
|
||||||
|
$include_top_products = ! empty( $_POST['include_top_products'] );
|
||||||
|
$top_products_limit = isset( $_POST['top_products_limit'] ) ? absint( $_POST['top_products_limit'] ) : 10;
|
||||||
|
$top_products_limit = max( 1, min( 25, $top_products_limit ) );
|
||||||
|
|
||||||
|
if ( '' === $prompt || '' === $taxonomy || ! taxonomy_exists( $taxonomy ) || ! $term_id ) {
|
||||||
|
wp_send_json_error( [ 'message' => __( 'Prompt, taxonomy en term_id zijn verplicht.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 400 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$term = get_term( $term_id, $taxonomy );
|
||||||
|
if ( ! $term || is_wp_error( $term ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => __( 'Term niet gevonden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) ], 404 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $this->plugin->get_settings();
|
||||||
|
$provider_manager = $this->plugin->get_provider_manager();
|
||||||
|
$provider_key = $settings['provider'];
|
||||||
|
$provider = $provider_manager->get_provider( $provider_key );
|
||||||
|
|
||||||
|
if ( ! $provider ) {
|
||||||
|
$provider = $provider_manager->get_provider( 'groq' );
|
||||||
|
$provider_key = 'groq';
|
||||||
|
}
|
||||||
|
|
||||||
|
$conversation_id = $this->plugin->get_conversation_manager()->ensure_id( $provider_key, $settings['store_context'] );
|
||||||
|
$prompt_builder = $this->plugin->get_prompt_builder();
|
||||||
|
$system_prompt = method_exists( $prompt_builder, 'build_term_system_prompt' )
|
||||||
|
? $prompt_builder->build_term_system_prompt( $settings, $conversation_id, $term )
|
||||||
|
: $prompt_builder->build_system_prompt( $settings, $conversation_id );
|
||||||
|
|
||||||
|
$model = $this->plugin->get_selected_model( $provider, $settings );
|
||||||
|
$context_block = '';
|
||||||
|
if ( method_exists( $prompt_builder, 'build_term_context_block' ) ) {
|
||||||
|
$context_block = $prompt_builder->build_term_context_block(
|
||||||
|
$term,
|
||||||
|
[
|
||||||
|
'include_top_products' => $include_top_products,
|
||||||
|
'top_products_limit' => $top_products_limit,
|
||||||
|
],
|
||||||
|
$settings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$prompt_with_context = method_exists( $prompt_builder, 'prepend_term_context_to_prompt' )
|
||||||
|
? $prompt_builder->prepend_term_context_to_prompt( $prompt, $context_block )
|
||||||
|
: $prompt_builder->prepend_context_to_prompt( $prompt, $context_block );
|
||||||
|
|
||||||
|
$response_format = null;
|
||||||
|
$use_response_format = $this->plugin->should_use_response_format( $provider, $settings );
|
||||||
|
if ( $use_response_format && method_exists( $prompt_builder, 'get_term_response_format_definition' ) ) {
|
||||||
|
$response_format = $prompt_builder->get_term_response_format_definition( $settings );
|
||||||
|
$final_prompt = $prompt_with_context;
|
||||||
|
} elseif ( method_exists( $prompt_builder, 'append_term_response_instructions' ) ) {
|
||||||
|
$final_prompt = $prompt_builder->append_term_response_instructions( $prompt_with_context, $settings );
|
||||||
|
} else {
|
||||||
|
$final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $provider->generate_content(
|
||||||
|
[
|
||||||
|
'prompt' => $final_prompt,
|
||||||
|
'system_prompt' => $system_prompt,
|
||||||
|
'model' => $model,
|
||||||
|
'settings' => $settings,
|
||||||
|
'temperature' => 0.7,
|
||||||
|
'conversation_id' => $conversation_id,
|
||||||
|
'response_format' => $response_format,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $result ) ) {
|
||||||
|
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response_text = $this->extract_content_text( $result );
|
||||||
|
$parsed = null;
|
||||||
|
if ( method_exists( $prompt_builder, 'parse_term_structured_response' ) ) {
|
||||||
|
$parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings );
|
||||||
|
}
|
||||||
|
if ( ! is_array( $parsed ) ) {
|
||||||
|
$parsed = [
|
||||||
|
'description' => trim( (string) $response_text ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(
|
||||||
|
[
|
||||||
|
'description' => isset( $parsed['description'] ) ? $parsed['description'] : '',
|
||||||
|
'raw' => $response_text,
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle_generate_text() {
|
public function handle_generate_text() {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Groq_AI_Google_Analytics_Data_Client {
|
||||||
|
/** @var Groq_AI_Google_OAuth_Client */
|
||||||
|
private $oauth;
|
||||||
|
|
||||||
|
public function __construct( Groq_AI_Google_OAuth_Client $oauth ) {
|
||||||
|
$this->oauth = $oauth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple connectivity check for GA4 Data API.
|
||||||
|
*
|
||||||
|
* @param array $settings
|
||||||
|
* @param string $property_id
|
||||||
|
* @param string $start_date
|
||||||
|
* @param string $end_date
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_property_sessions_summary( $settings, $property_id, $start_date, $end_date ) {
|
||||||
|
$property_id = trim( (string) $property_id );
|
||||||
|
if ( '' === $property_id ) {
|
||||||
|
return new WP_Error( 'groq_ai_ga_missing', __( 'GA4 property ID ontbreekt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->oauth->get_access_token( $settings );
|
||||||
|
if ( is_wp_error( $token ) ) {
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$endpoint = 'https://analyticsdata.googleapis.com/v1beta/properties/' . rawurlencode( $property_id ) . ':runReport';
|
||||||
|
$body = [
|
||||||
|
'dateRanges' => [
|
||||||
|
[
|
||||||
|
'startDate' => $start_date,
|
||||||
|
'endDate' => $end_date,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'metrics' => [
|
||||||
|
[ 'name' => 'sessions' ],
|
||||||
|
[ 'name' => 'engagedSessions' ],
|
||||||
|
],
|
||||||
|
'limit' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = wp_remote_post(
|
||||||
|
$endpoint,
|
||||||
|
[
|
||||||
|
'timeout' => 20,
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $token,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'body' => wp_json_encode( $body ),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$raw_body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( (string) $raw_body, true );
|
||||||
|
|
||||||
|
if ( 200 !== $status_code || ! is_array( $data ) ) {
|
||||||
|
return new WP_Error( 'groq_ai_ga_error', __( 'GA4 Data API call mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = isset( $data['rows'] ) && is_array( $data['rows'] ) ? $data['rows'] : [];
|
||||||
|
$sessions = 0;
|
||||||
|
$engaged = 0;
|
||||||
|
foreach ( $rows as $row ) {
|
||||||
|
$metric_values = isset( $row['metricValues'] ) && is_array( $row['metricValues'] ) ? $row['metricValues'] : [];
|
||||||
|
if ( isset( $metric_values[0]['value'] ) ) {
|
||||||
|
$sessions += absint( $metric_values[0]['value'] );
|
||||||
|
}
|
||||||
|
if ( isset( $metric_values[1]['value'] ) ) {
|
||||||
|
$engaged += absint( $metric_values[1]['value'] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'sessions' => $sessions,
|
||||||
|
'engagedSessions' => $engaged,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns approximate GA4 sessions for a landing page path.
|
||||||
|
*
|
||||||
|
* @param array $settings
|
||||||
|
* @param string $property_id
|
||||||
|
* @param string $page_path e.g. /product-category/foo/
|
||||||
|
* @param string $start_date YYYY-MM-DD
|
||||||
|
* @param string $end_date YYYY-MM-DD
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_sessions_for_landing_page_path( $settings, $property_id, $page_path, $start_date, $end_date ) {
|
||||||
|
$property_id = trim( (string) $property_id );
|
||||||
|
$page_path = trim( (string) $page_path );
|
||||||
|
|
||||||
|
if ( '' === $property_id || '' === $page_path ) {
|
||||||
|
return new WP_Error( 'groq_ai_ga_missing', __( 'GA4 property ID of page path ontbreekt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->oauth->get_access_token( $settings );
|
||||||
|
if ( is_wp_error( $token ) ) {
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$endpoint = 'https://analyticsdata.googleapis.com/v1beta/properties/' . rawurlencode( $property_id ) . ':runReport';
|
||||||
|
|
||||||
|
$body = [
|
||||||
|
'dateRanges' => [
|
||||||
|
[
|
||||||
|
'startDate' => $start_date,
|
||||||
|
'endDate' => $end_date,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'dimensions' => [
|
||||||
|
[ 'name' => 'landingPagePlusQueryString' ],
|
||||||
|
],
|
||||||
|
'metrics' => [
|
||||||
|
[ 'name' => 'sessions' ],
|
||||||
|
[ 'name' => 'engagedSessions' ],
|
||||||
|
],
|
||||||
|
'dimensionFilter' => [
|
||||||
|
'filter' => [
|
||||||
|
'fieldName' => 'landingPagePlusQueryString',
|
||||||
|
'stringFilter' => [
|
||||||
|
'matchType' => 'CONTAINS',
|
||||||
|
'value' => $page_path,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'limit' => 5,
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = wp_remote_post(
|
||||||
|
$endpoint,
|
||||||
|
[
|
||||||
|
'timeout' => 20,
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $token,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
],
|
||||||
|
'body' => wp_json_encode( $body ),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$raw_body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( (string) $raw_body, true );
|
||||||
|
|
||||||
|
if ( 200 !== $status_code || ! is_array( $data ) ) {
|
||||||
|
return new WP_Error( 'groq_ai_ga_error', __( 'GA4 Data API call mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = isset( $data['rows'] ) && is_array( $data['rows'] ) ? $data['rows'] : [];
|
||||||
|
$sessions = 0;
|
||||||
|
$engaged = 0;
|
||||||
|
foreach ( $rows as $row ) {
|
||||||
|
$metric_values = isset( $row['metricValues'] ) && is_array( $row['metricValues'] ) ? $row['metricValues'] : [];
|
||||||
|
if ( isset( $metric_values[0]['value'] ) ) {
|
||||||
|
$sessions += absint( $metric_values[0]['value'] );
|
||||||
|
}
|
||||||
|
if ( isset( $metric_values[1]['value'] ) ) {
|
||||||
|
$engaged += absint( $metric_values[1]['value'] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'sessions' => $sessions,
|
||||||
|
'engagedSessions' => $engaged,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Groq_AI_Google_Context_Builder {
|
||||||
|
/** @var Groq_AI_Google_Search_Console_Client */
|
||||||
|
private $gsc;
|
||||||
|
|
||||||
|
/** @var Groq_AI_Google_Analytics_Data_Client */
|
||||||
|
private $ga;
|
||||||
|
|
||||||
|
public function __construct( Groq_AI_Google_Search_Console_Client $gsc, Groq_AI_Google_Analytics_Data_Client $ga ) {
|
||||||
|
$this->gsc = $gsc;
|
||||||
|
$this->ga = $ga;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $existing
|
||||||
|
* @param WP_Term $term
|
||||||
|
* @param array $settings
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function build_term_google_context( $existing, $term, $settings ) {
|
||||||
|
if ( ! $term || ! is_object( $term ) ) {
|
||||||
|
return (string) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
$enabled_gsc = ! empty( $settings['google_enable_gsc'] );
|
||||||
|
$enabled_ga = ! empty( $settings['google_enable_ga'] );
|
||||||
|
|
||||||
|
if ( ! $enabled_gsc && ! $enabled_ga ) {
|
||||||
|
return (string) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
$term_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0;
|
||||||
|
$taxonomy = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : '';
|
||||||
|
|
||||||
|
$range_days = 28;
|
||||||
|
$end_date = gmdate( 'Y-m-d' );
|
||||||
|
$start_date = gmdate( 'Y-m-d', time() - ( $range_days * DAY_IN_SECONDS ) );
|
||||||
|
|
||||||
|
$term_link = get_term_link( $term );
|
||||||
|
if ( is_wp_error( $term_link ) ) {
|
||||||
|
$term_link = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$page_path = '';
|
||||||
|
if ( is_string( $term_link ) && '' !== $term_link ) {
|
||||||
|
$parts = wp_parse_url( $term_link );
|
||||||
|
if ( is_array( $parts ) && isset( $parts['path'] ) ) {
|
||||||
|
$page_path = (string) $parts['path'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache_key = 'groq_ai_google_term_ctx_' . md5( $taxonomy . '|' . $term_id . '|' . $start_date . '|' . $end_date );
|
||||||
|
$cached = get_transient( $cache_key );
|
||||||
|
if ( is_string( $cached ) && '' !== $cached ) {
|
||||||
|
return trim( (string) $existing . "\n\n" . $cached );
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = sprintf(
|
||||||
|
/* translators: %d: days */
|
||||||
|
__( 'Google data (laatste %d dagen):', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$range_days
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $enabled_gsc ) {
|
||||||
|
$site_url = isset( $settings['google_gsc_site_url'] ) ? trim( (string) $settings['google_gsc_site_url'] ) : '';
|
||||||
|
if ( '' !== $site_url && '' !== $term_link ) {
|
||||||
|
$queries = $this->gsc->get_top_queries_for_page( $settings, $site_url, $term_link, $start_date, $end_date, 10 );
|
||||||
|
if ( is_wp_error( $queries ) ) {
|
||||||
|
$lines[] = __( 'Search Console: kon queries niet ophalen.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
} elseif ( empty( $queries ) ) {
|
||||||
|
$lines[] = __( 'Search Console: geen query data gevonden voor deze pagina.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
} else {
|
||||||
|
$lines[] = __( 'Search Console top zoekopdrachten (query → clicks/impr):', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
foreach ( $queries as $row ) {
|
||||||
|
$q = isset( $row['query'] ) ? (string) $row['query'] : '';
|
||||||
|
$c = isset( $row['clicks'] ) ? (float) $row['clicks'] : 0.0;
|
||||||
|
$i = isset( $row['impressions'] ) ? (float) $row['impressions'] : 0.0;
|
||||||
|
if ( '' === $q ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$lines[] = sprintf( '- %s → %d/%d', $q, (int) round( $c ), (int) round( $i ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $enabled_ga ) {
|
||||||
|
$property_id = isset( $settings['google_ga4_property_id'] ) ? trim( (string) $settings['google_ga4_property_id'] ) : '';
|
||||||
|
if ( '' !== $property_id && '' !== $page_path ) {
|
||||||
|
$stats = $this->ga->get_sessions_for_landing_page_path( $settings, $property_id, $page_path, $start_date, $end_date );
|
||||||
|
if ( is_wp_error( $stats ) ) {
|
||||||
|
$lines[] = __( 'Analytics: kon sessies niet ophalen.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
} else {
|
||||||
|
$sessions = isset( $stats['sessions'] ) ? absint( $stats['sessions'] ) : 0;
|
||||||
|
$engaged = isset( $stats['engagedSessions'] ) ? absint( $stats['engagedSessions'] ) : 0;
|
||||||
|
$lines[] = sprintf( __( 'Analytics (GA4): sessies ~%1$d, engaged sessies ~%2$d', GROQ_AI_PRODUCT_TEXT_DOMAIN ), $sessions, $engaged );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we only have the header, skip.
|
||||||
|
if ( count( $lines ) <= 1 ) {
|
||||||
|
return (string) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = implode( "\n", $lines );
|
||||||
|
set_transient( $cache_key, $context, 15 * MINUTE_IN_SECONDS );
|
||||||
|
|
||||||
|
return trim( (string) $existing . "\n\n" . $context );
|
||||||
|
}
|
||||||
|
}
|
||||||
101
includes/Services/Google/class-groq-ai-google-oauth-client.php
Normal file
101
includes/Services/Google/class-groq-ai-google-oauth-client.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Groq_AI_Google_OAuth_Client {
|
||||||
|
/**
|
||||||
|
* @param array $settings
|
||||||
|
* @return string|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_access_token( $settings ) {
|
||||||
|
$client_id = isset( $settings['google_oauth_client_id'] ) ? trim( (string) $settings['google_oauth_client_id'] ) : '';
|
||||||
|
$client_secret = isset( $settings['google_oauth_client_secret'] ) ? trim( (string) $settings['google_oauth_client_secret'] ) : '';
|
||||||
|
$refresh_token = isset( $settings['google_oauth_refresh_token'] ) ? trim( (string) $settings['google_oauth_refresh_token'] ) : '';
|
||||||
|
|
||||||
|
if ( '' === $client_id || '' === $client_secret || '' === $refresh_token ) {
|
||||||
|
return new WP_Error( 'groq_ai_google_oauth_missing', __( 'Google OAuth is niet (volledig) geconfigureerd.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$cache_key = 'groq_ai_google_access_token_' . md5( $client_id . '|' . $refresh_token );
|
||||||
|
$cached = get_transient( $cache_key );
|
||||||
|
if ( is_string( $cached ) && '' !== $cached ) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_post(
|
||||||
|
'https://oauth2.googleapis.com/token',
|
||||||
|
[
|
||||||
|
'timeout' => 20,
|
||||||
|
'headers' => [
|
||||||
|
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||||
|
],
|
||||||
|
'body' => [
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'client_secret' => $client_secret,
|
||||||
|
'refresh_token' => $refresh_token,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( (string) $body, true );
|
||||||
|
|
||||||
|
if ( 200 !== $status_code || ! is_array( $data ) ) {
|
||||||
|
return new WP_Error( 'groq_ai_google_oauth_refresh_failed', __( 'Google token refresh mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$access_token = isset( $data['access_token'] ) ? sanitize_text_field( (string) $data['access_token'] ) : '';
|
||||||
|
$expires_in = isset( $data['expires_in'] ) ? absint( $data['expires_in'] ) : 0;
|
||||||
|
|
||||||
|
if ( '' === $access_token ) {
|
||||||
|
return new WP_Error( 'groq_ai_google_oauth_refresh_failed', __( 'Geen access token ontvangen van Google.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$ttl = max( 60, $expires_in - 60 );
|
||||||
|
set_transient( $cache_key, $access_token, $ttl );
|
||||||
|
|
||||||
|
return $access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diagnostics helper: returns scopes for a given access token.
|
||||||
|
*
|
||||||
|
* @param string $access_token
|
||||||
|
* @return array|WP_Error { 'scope' => string, 'expires_in' => int }
|
||||||
|
*/
|
||||||
|
public function get_access_token_info( $access_token ) {
|
||||||
|
$access_token = trim( (string) $access_token );
|
||||||
|
if ( '' === $access_token ) {
|
||||||
|
return new WP_Error( 'groq_ai_google_tokeninfo_missing', __( 'Geen access token om te inspecteren.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_get(
|
||||||
|
add_query_arg( [ 'access_token' => $access_token ], 'https://oauth2.googleapis.com/tokeninfo' ),
|
||||||
|
[ 'timeout' => 15 ]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( (string) $body, true );
|
||||||
|
|
||||||
|
if ( 200 !== $status_code || ! is_array( $data ) ) {
|
||||||
|
return new WP_Error( 'groq_ai_google_tokeninfo_failed', __( 'Kon tokeninfo niet ophalen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope = isset( $data['scope'] ) ? trim( (string) $data['scope'] ) : '';
|
||||||
|
$expires_in = isset( $data['expires_in'] ) ? absint( $data['expires_in'] ) : 0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'scope' => $scope,
|
||||||
|
'expires_in' => $expires_in,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Groq_AI_Google_Search_Console_Client {
|
||||||
|
/** @var Groq_AI_Google_OAuth_Client */
|
||||||
|
private $oauth;
|
||||||
|
|
||||||
|
public function __construct( Groq_AI_Google_OAuth_Client $oauth ) {
|
||||||
|
$this->oauth = $oauth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $status_code
|
||||||
|
* @param string $raw_body
|
||||||
|
* @return WP_Error
|
||||||
|
*/
|
||||||
|
private function build_http_error( $status_code, $raw_body ) {
|
||||||
|
$status_code = absint( $status_code );
|
||||||
|
$raw_body = (string) $raw_body;
|
||||||
|
|
||||||
|
$message = __( 'Search Console API call mislukt.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
$details = '';
|
||||||
|
|
||||||
|
$data = json_decode( $raw_body, true );
|
||||||
|
if ( is_array( $data ) ) {
|
||||||
|
// Google APIs often respond with: { error: { code, message, status, details/errors } }
|
||||||
|
$err = isset( $data['error'] ) && is_array( $data['error'] ) ? $data['error'] : [];
|
||||||
|
$google_message = isset( $err['message'] ) ? trim( (string) $err['message'] ) : '';
|
||||||
|
$google_status = isset( $err['status'] ) ? trim( (string) $err['status'] ) : '';
|
||||||
|
if ( '' !== $google_status || '' !== $google_message ) {
|
||||||
|
$details = trim( $google_status . ( $google_status && $google_message ? ': ' : '' ) . $google_message );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( '' !== $details ) {
|
||||||
|
$message = sprintf(
|
||||||
|
/* translators: 1: HTTP status, 2: details */
|
||||||
|
__( 'Search Console API call mislukt (HTTP %1$d): %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$status_code,
|
||||||
|
$details
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$message = sprintf(
|
||||||
|
/* translators: %d: HTTP status */
|
||||||
|
__( 'Search Console API call mislukt (HTTP %d).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$status_code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_Error( 'groq_ai_gsc_error', $message );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $settings
|
||||||
|
* @return array|WP_Error Array of siteUrl strings.
|
||||||
|
*/
|
||||||
|
public function list_sites( $settings ) {
|
||||||
|
$token = $this->oauth->get_access_token( $settings );
|
||||||
|
if ( is_wp_error( $token ) ) {
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = wp_remote_get(
|
||||||
|
'https://searchconsole.googleapis.com/webmasters/v3/sites',
|
||||||
|
[
|
||||||
|
'timeout' => 20,
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $token,
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$raw_body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( (string) $raw_body, true );
|
||||||
|
|
||||||
|
if ( 200 !== $status_code || ! is_array( $data ) ) {
|
||||||
|
return $this->build_http_error( $status_code, $raw_body );
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries = isset( $data['siteEntry'] ) && is_array( $data['siteEntry'] ) ? $data['siteEntry'] : [];
|
||||||
|
$sites = [];
|
||||||
|
foreach ( $entries as $entry ) {
|
||||||
|
if ( ! is_array( $entry ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$site_url = isset( $entry['siteUrl'] ) ? trim( (string) $entry['siteUrl'] ) : '';
|
||||||
|
if ( '' !== $site_url ) {
|
||||||
|
$sites[] = $site_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sites = array_values( array_unique( $sites ) );
|
||||||
|
sort( $sites, SORT_NATURAL | SORT_FLAG_CASE );
|
||||||
|
|
||||||
|
return $sites;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $settings
|
||||||
|
* @param string $site_url
|
||||||
|
* @param string $page_url
|
||||||
|
* @param string $start_date YYYY-MM-DD
|
||||||
|
* @param string $end_date YYYY-MM-DD
|
||||||
|
* @param int $limit
|
||||||
|
* @return array|WP_Error
|
||||||
|
*/
|
||||||
|
public function get_top_queries_for_page( $settings, $site_url, $page_url, $start_date, $end_date, $limit = 10 ) {
|
||||||
|
$site_url = trim( (string) $site_url );
|
||||||
|
$page_url = trim( (string) $page_url );
|
||||||
|
$limit = max( 1, min( 25, absint( $limit ) ) );
|
||||||
|
|
||||||
|
if ( '' === $site_url || '' === $page_url ) {
|
||||||
|
return new WP_Error( 'groq_ai_gsc_missing', __( 'Search Console site URL of pagina URL ontbreekt.', GROQ_AI_PRODUCT_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->oauth->get_access_token( $settings );
|
||||||
|
if ( is_wp_error( $token ) ) {
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$endpoint = 'https://searchconsole.googleapis.com/webmasters/v3/sites/' . rawurlencode( $site_url ) . '/searchAnalytics/query';
|
||||||
|
|
||||||
|
$body = [
|
||||||
|
'startDate' => $start_date,
|
||||||
|
'endDate' => $end_date,
|
||||||
|
'dimensions' => [ 'query' ],
|
||||||
|
'rowLimit' => $limit,
|
||||||
|
'dimensionFilterGroups' => [
|
||||||
|
[
|
||||||
|
'filters' => [
|
||||||
|
[
|
||||||
|
'dimension' => 'page',
|
||||||
|
'operator' => 'equals',
|
||||||
|
'expression' => $page_url,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'aggregationType' => 'auto',
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = wp_remote_post(
|
||||||
|
$endpoint,
|
||||||
|
[
|
||||||
|
'timeout' => 20,
|
||||||
|
'headers' => [
|
||||||
|
'Authorization' => 'Bearer ' . $token,
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
],
|
||||||
|
'body' => wp_json_encode( $body ),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$status_code = wp_remote_retrieve_response_code( $response );
|
||||||
|
$raw_body = wp_remote_retrieve_body( $response );
|
||||||
|
$data = json_decode( (string) $raw_body, true );
|
||||||
|
|
||||||
|
if ( 200 !== $status_code || ! is_array( $data ) ) {
|
||||||
|
return $this->build_http_error( $status_code, $raw_body );
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = isset( $data['rows'] ) && is_array( $data['rows'] ) ? $data['rows'] : [];
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ( $rows as $row ) {
|
||||||
|
if ( ! is_array( $row ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$keys = isset( $row['keys'] ) && is_array( $row['keys'] ) ? $row['keys'] : [];
|
||||||
|
$query = isset( $keys[0] ) ? sanitize_text_field( (string) $keys[0] ) : '';
|
||||||
|
if ( '' === $query ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result[] = [
|
||||||
|
'query' => $query,
|
||||||
|
'clicks' => isset( $row['clicks'] ) ? (float) $row['clicks'] : 0.0,
|
||||||
|
'impressions' => isset( $row['impressions'] ) ? (float) $row['impressions'] : 0.0,
|
||||||
|
'ctr' => isset( $row['ctr'] ) ? (float) $row['ctr'] : 0.0,
|
||||||
|
'position' => isset( $row['position'] ) ? (float) $row['position'] : 0.0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,171 @@ class Groq_AI_Prompt_Builder {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function build_term_system_prompt( $settings, $conversation_id, $term ) {
|
||||||
|
$context = isset( $settings['store_context'] ) ? trim( $settings['store_context'] ) : '';
|
||||||
|
$term_name = is_object( $term ) && isset( $term->name ) ? (string) $term->name : '';
|
||||||
|
$base_instruction = __( 'Je bent een copywriter voor een WooCommerce winkel en schrijft SEO-vriendelijke categorie- en merkpagina teksten.', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
|
||||||
|
if ( $context ) {
|
||||||
|
$base_instruction = sprintf(
|
||||||
|
__( 'Je bent een copywriter voor een WooCommerce winkel. Gebruik de volgende winkelcontext indien beschikbaar: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( '' !== $term_name ) {
|
||||||
|
$base_instruction .= ' ' . sprintf(
|
||||||
|
__( 'Je schrijft nu voor de term: %s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$term_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
__( 'Conversatie-ID: %1$s. %2$s', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$conversation_id,
|
||||||
|
$base_instruction
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 );
|
||||||
@@ -232,6 +397,250 @@ class Groq_AI_Prompt_Builder {
|
|||||||
return $intro . "\n" . $context . "\n\n" . $prompt;
|
return $intro . "\n" . $context . "\n\n" . $prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function build_term_context_block( $term, $options = [], $settings = null ) {
|
||||||
|
if ( ! $term || ! is_object( $term ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$taxonomy = isset( $term->taxonomy ) ? sanitize_key( (string) $term->taxonomy ) : '';
|
||||||
|
$term_id = isset( $term->term_id ) ? absint( $term->term_id ) : 0;
|
||||||
|
if ( '' === $taxonomy || ! $term_id ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$include_top_products = ! empty( $options['include_top_products'] );
|
||||||
|
$top_products_limit = isset( $options['top_products_limit'] ) ? absint( $options['top_products_limit'] ) : 10;
|
||||||
|
$top_products_limit = max( 1, min( 25, $top_products_limit ) );
|
||||||
|
|
||||||
|
$parts = [];
|
||||||
|
$parts[] = sprintf( __( 'Term: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( (string) $term->name ) );
|
||||||
|
if ( isset( $term->slug ) && '' !== (string) $term->slug ) {
|
||||||
|
$parts[] = sprintf( __( 'Slug: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), sanitize_title( (string) $term->slug ) );
|
||||||
|
}
|
||||||
|
if ( isset( $term->count ) ) {
|
||||||
|
$parts[] = sprintf( __( 'Aantal producten: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), (string) absint( $term->count ) );
|
||||||
|
}
|
||||||
|
if ( isset( $term->description ) && '' !== trim( (string) $term->description ) ) {
|
||||||
|
$parts[] = sprintf( __( 'Huidige omschrijving: %s', GROQ_AI_PRODUCT_TEXT_DOMAIN ), wp_strip_all_tags( (string) $term->description ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $include_top_products ) {
|
||||||
|
$top_products = $this->get_top_products_for_term( $taxonomy, $term_id, $top_products_limit );
|
||||||
|
if ( ! empty( $top_products ) ) {
|
||||||
|
$lines = [];
|
||||||
|
foreach ( $top_products as $product_row ) {
|
||||||
|
$lines[] = sprintf( '- %s', $product_row );
|
||||||
|
}
|
||||||
|
$parts[] = __( 'Top verkochte producten (indicatief):', GROQ_AI_PRODUCT_TEXT_DOMAIN ) . "\n" . implode( "\n", $lines );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 = trim( (string) $google_context );
|
||||||
|
if ( '' !== $google_context ) {
|
||||||
|
$parts[] = $google_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode( "\n\n", array_filter( $parts ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepend_term_context_to_prompt( $prompt, $context ) {
|
||||||
|
$context = trim( (string) $context );
|
||||||
|
if ( '' === $context ) {
|
||||||
|
return $prompt;
|
||||||
|
}
|
||||||
|
$intro = __( 'Gebruik de volgende categorie/term-context bij het schrijven:', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
return $intro . "\n" . $context . "\n\n" . $prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_term_response_format_definition( $settings = null ) {
|
||||||
|
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||||
|
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
|
||||||
|
$title_pixels = $this->settings_manager->get_rankmath_meta_title_pixel_limit( $settings );
|
||||||
|
$desc_pixels = $this->settings_manager->get_rankmath_meta_description_pixel_limit( $settings );
|
||||||
|
|
||||||
|
$properties = [
|
||||||
|
'description' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => __( 'HTML-omschrijving voor de categorie/term met paragrafen en eventueel lijstjes.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
'minLength' => 20,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( $rankmath_enabled ) {
|
||||||
|
$properties['meta_title'] = [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => sprintf(
|
||||||
|
__( 'SEO-meta title (max. %1$d tekens en %2$d pixels).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
60,
|
||||||
|
$title_pixels
|
||||||
|
),
|
||||||
|
'maxLength' => 120,
|
||||||
|
];
|
||||||
|
$properties['meta_description'] = [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => sprintf(
|
||||||
|
__( 'SEO-meta description (max. %1$d tekens en %2$d pixels).', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
160,
|
||||||
|
$desc_pixels
|
||||||
|
),
|
||||||
|
'maxLength' => 320,
|
||||||
|
];
|
||||||
|
$properties['focus_keywords'] = [
|
||||||
|
'type' => 'array',
|
||||||
|
'description' => __( 'Lijst met korte zoekwoorden zonder hashtags of extra tekst.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
'maxItems' => max( 1, $keyword_limit ),
|
||||||
|
'items' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'minLength' => 1,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema = [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => $properties,
|
||||||
|
'required' => [ 'description' ],
|
||||||
|
'additionalProperties' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => 'json_schema',
|
||||||
|
'json_schema' => [
|
||||||
|
'name' => 'groq_ai_term_text',
|
||||||
|
'schema' => $schema,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function append_term_response_instructions( $prompt, $settings ) {
|
||||||
|
$instructions = (string) ( $this->get_term_structured_response_instructions( $settings ) ?? '' );
|
||||||
|
$prompt = trim( (string) $prompt );
|
||||||
|
if ( '' === $instructions ) {
|
||||||
|
return $prompt;
|
||||||
|
}
|
||||||
|
if ( false !== strpos( $prompt, $instructions ) ) {
|
||||||
|
return $prompt;
|
||||||
|
}
|
||||||
|
return $prompt . "\n\n" . $instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse_term_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_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$clean = trim( (string) $raw );
|
||||||
|
if ( preg_match( '/```(?:json)?\s*(.*?)```/is', $clean, $matches ) ) {
|
||||||
|
$clean = trim( $matches[1] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode( $clean, true );
|
||||||
|
if ( ! is_array( $decoded ) ) {
|
||||||
|
// Fallback: treat as plain text.
|
||||||
|
return [
|
||||||
|
'description' => trim( (string) $raw ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'description' => $description,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( isset( $decoded['meta_title'] ) ) {
|
||||||
|
$result['meta_title'] = $this->truncate_meta_field( (string) $decoded['meta_title'], 60 );
|
||||||
|
}
|
||||||
|
if ( isset( $decoded['meta_description'] ) ) {
|
||||||
|
$result['meta_description'] = $this->truncate_meta_field( (string) $decoded['meta_description'], 160 );
|
||||||
|
}
|
||||||
|
if ( isset( $decoded['focus_keywords'] ) ) {
|
||||||
|
if ( is_array( $decoded['focus_keywords'] ) ) {
|
||||||
|
$keywords = [];
|
||||||
|
foreach ( $decoded['focus_keywords'] as $kw ) {
|
||||||
|
$kw = trim( (string) $kw );
|
||||||
|
if ( '' !== $kw ) {
|
||||||
|
$keywords[] = $kw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$keywords = array_values( array_unique( $keywords ) );
|
||||||
|
$result['focus_keywords'] = implode( ', ', $keywords );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_term_structured_response_instructions( $settings = null ) {
|
||||||
|
$schema_parts = [
|
||||||
|
'"description":"..."',
|
||||||
|
];
|
||||||
|
|
||||||
|
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||||
|
if ( $rankmath_enabled ) {
|
||||||
|
$schema_parts[] = '"meta_title":"..."';
|
||||||
|
$schema_parts[] = '"meta_description":"..."';
|
||||||
|
$schema_parts[] = '"focus_keywords":["...","..."]';
|
||||||
|
}
|
||||||
|
|
||||||
|
$json_structure = '{' . implode( ',', $schema_parts ) . '}';
|
||||||
|
|
||||||
|
$instruction = sprintf(
|
||||||
|
__( 'Geef ALLEEN een geldig JSON-object terug met deze structuur: %s. Gebruik dubbele aanhalingstekens, geen Markdown of extra tekst. Gebruik \n voor regeleinden.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
|
||||||
|
$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 .= ' ' . __( 'Als in de context een sectie "Interne links" staat, verwerk dan 2–5 van deze links natuurlijk in de description als HTML-links (<a href="URL">Anker</a>).', GROQ_AI_PRODUCT_TEXT_DOMAIN );
|
||||||
|
return $instruction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_top_products_for_term( $taxonomy, $term_id, $limit = 10 ) {
|
||||||
|
$taxonomy = sanitize_key( (string) $taxonomy );
|
||||||
|
$term_id = absint( $term_id );
|
||||||
|
$limit = max( 1, min( 25, absint( $limit ) ) );
|
||||||
|
|
||||||
|
$query = new WP_Query(
|
||||||
|
[
|
||||||
|
'post_type' => 'product',
|
||||||
|
'post_status' => 'publish',
|
||||||
|
'posts_per_page' => $limit,
|
||||||
|
'no_found_rows' => true,
|
||||||
|
'meta_key' => 'total_sales',
|
||||||
|
'orderby' => 'meta_value_num',
|
||||||
|
'order' => 'DESC',
|
||||||
|
'tax_query' => [
|
||||||
|
[
|
||||||
|
'taxonomy' => $taxonomy,
|
||||||
|
'field' => 'term_id',
|
||||||
|
'terms' => [ $term_id ],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
if ( $query->have_posts() ) {
|
||||||
|
foreach ( $query->posts as $post ) {
|
||||||
|
$title = isset( $post->post_title ) ? wp_strip_all_tags( (string) $post->post_title ) : '';
|
||||||
|
$rows[] = $title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wp_reset_postdata();
|
||||||
|
|
||||||
|
return array_values( array_filter( $rows ) );
|
||||||
|
}
|
||||||
|
|
||||||
public function get_response_format_definition( $settings = null ) {
|
public function get_response_format_definition( $settings = null ) {
|
||||||
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
$rankmath_enabled = $this->settings_manager->is_module_enabled( 'rankmath', $settings );
|
||||||
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
|
$keyword_limit = $this->settings_manager->get_rankmath_focus_keyword_limit( $settings );
|
||||||
|
|||||||
@@ -32,9 +32,19 @@ class Groq_AI_Settings_Manager {
|
|||||||
'model' => '',
|
'model' => '',
|
||||||
'store_context' => '',
|
'store_context' => '',
|
||||||
'default_prompt' => '',
|
'default_prompt' => '',
|
||||||
|
'max_output_tokens' => 2048,
|
||||||
'groq_api_key' => '',
|
'groq_api_key' => '',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
'google_api_key' => '',
|
'google_api_key' => '',
|
||||||
|
'google_oauth_client_id' => '',
|
||||||
|
'google_oauth_client_secret' => '',
|
||||||
|
'google_oauth_refresh_token' => '',
|
||||||
|
'google_oauth_connected_email' => '',
|
||||||
|
'google_oauth_connected_at' => 0,
|
||||||
|
'google_enable_gsc' => true,
|
||||||
|
'google_enable_ga' => true,
|
||||||
|
'google_gsc_site_url' => '',
|
||||||
|
'google_ga4_property_id' => '',
|
||||||
'context_fields' => $this->get_default_context_fields(),
|
'context_fields' => $this->get_default_context_fields(),
|
||||||
'modules' => $this->get_default_modules_settings(),
|
'modules' => $this->get_default_modules_settings(),
|
||||||
'image_context_mode' => 'url',
|
'image_context_mode' => 'url',
|
||||||
@@ -78,9 +88,19 @@ class Groq_AI_Settings_Manager {
|
|||||||
'model' => '',
|
'model' => '',
|
||||||
'store_context' => '',
|
'store_context' => '',
|
||||||
'default_prompt' => '',
|
'default_prompt' => '',
|
||||||
|
'max_output_tokens' => 2048,
|
||||||
'groq_api_key' => '',
|
'groq_api_key' => '',
|
||||||
'openai_api_key' => '',
|
'openai_api_key' => '',
|
||||||
'google_api_key' => '',
|
'google_api_key' => '',
|
||||||
|
'google_oauth_client_id' => '',
|
||||||
|
'google_oauth_client_secret' => '',
|
||||||
|
'google_oauth_refresh_token' => '',
|
||||||
|
'google_oauth_connected_email' => '',
|
||||||
|
'google_oauth_connected_at' => 0,
|
||||||
|
'google_enable_gsc' => true,
|
||||||
|
'google_enable_ga' => true,
|
||||||
|
'google_gsc_site_url' => '',
|
||||||
|
'google_ga4_property_id' => '',
|
||||||
'context_fields' => $this->get_default_context_fields(),
|
'context_fields' => $this->get_default_context_fields(),
|
||||||
'modules' => $this->get_default_modules_settings(),
|
'modules' => $this->get_default_modules_settings(),
|
||||||
'image_context_mode' => 'url',
|
'image_context_mode' => 'url',
|
||||||
@@ -111,6 +131,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 ) {
|
||||||
@@ -124,9 +148,19 @@ 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,
|
||||||
'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'] ),
|
||||||
|
'google_oauth_client_id' => sanitize_text_field( $input['google_oauth_client_id'] ),
|
||||||
|
'google_oauth_client_secret' => sanitize_text_field( $input['google_oauth_client_secret'] ),
|
||||||
|
'google_oauth_refresh_token' => sanitize_text_field( $input['google_oauth_refresh_token'] ),
|
||||||
|
'google_oauth_connected_email' => sanitize_text_field( $input['google_oauth_connected_email'] ),
|
||||||
|
'google_oauth_connected_at' => absint( $input['google_oauth_connected_at'] ),
|
||||||
|
'google_enable_gsc' => ! empty( $raw_input['google_enable_gsc'] ),
|
||||||
|
'google_enable_ga' => ! empty( $raw_input['google_enable_ga'] ),
|
||||||
|
'google_gsc_site_url' => esc_url_raw( (string) $input['google_gsc_site_url'] ),
|
||||||
|
'google_ga4_property_id' => sanitize_text_field( (string) $input['google_ga4_property_id'] ),
|
||||||
'response_format_compat' => ! empty( $raw_input['response_format_compat'] ),
|
'response_format_compat' => ! empty( $raw_input['response_format_compat'] ),
|
||||||
'image_context_mode' => $image_mode,
|
'image_context_mode' => $image_mode,
|
||||||
'image_context_limit' => $image_limit,
|
'image_context_limit' => $image_limit,
|
||||||
|
|||||||
Reference in New Issue
Block a user