1 Commits

8 changed files with 516 additions and 55 deletions

View File

@@ -119,7 +119,8 @@ class SitiWebUpdater {
'url' => $this->plugin["PluginURI"], 'url' => $this->plugin["PluginURI"],
'slug' => $slug, 'slug' => $slug,
'package' => $new_files, 'package' => $new_files,
'new_version' => $latest_version 'new_version' => $latest_version,
'icons' => $this->prepare_icon_set(),
); );
$transient->response[$this->basename] = (object) $plugin; // Return it in response $transient->response[$this->basename] = (object) $plugin; // Return it in response
@@ -160,10 +161,11 @@ class SitiWebUpdater {
'homepage' => $this->plugin["PluginURI"], 'homepage' => $this->plugin["PluginURI"],
'short_description' => $this->plugin["Description"], 'short_description' => $this->plugin["Description"],
'sections' => array( 'sections' => array(
'Description' => $this->plugin["Description"], 'description' => wp_kses_post( wpautop( $this->plugin["Description"] ) ),
'Updates' => $this->github_response['body'], 'changelog' => $this->get_release_notes_html(),
), ),
'download_link' => $this->github_response['zipball_url'] 'download_link' => $this->github_response['zipball_url'],
'icons' => $this->prepare_icon_set(),
); );
return (object) $plugin; // Return the data return (object) $plugin; // Return the data
@@ -199,4 +201,37 @@ class SitiWebUpdater {
return $result; return $result;
} }
private function get_release_notes_html() {
$notes = isset( $this->github_response['body'] ) ? (string) $this->github_response['body'] : '';
if ( '' === trim( $notes ) ) {
return __( 'Nog geen changelog beschikbaar.', 'siti-ai-product-content-generator' );
}
return wp_kses_post( wpautop( $notes ) );
}
private function get_plugin_icon_url() {
$file = plugin_dir_path( $this->file ) . 'assets/images/plugin-icon.svg';
if ( ! file_exists( $file ) ) {
return '';
}
return plugins_url( 'assets/images/plugin-icon.svg', $this->file );
}
private function prepare_icon_set() {
$icon_url = $this->get_plugin_icon_url();
if ( '' === $icon_url ) {
return array();
}
return array(
'1x' => $icon_url,
'2x' => $icon_url,
'svg' => $icon_url,
'default' => $icon_url,
);
}
} }

View File

@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<defs>
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#6C38FF"/>
<stop offset="100%" stop-color="#00C7C7"/>
</linearGradient>
</defs>
<rect width="256" height="256" rx="48" fill="url(#g)"/>
<path fill="#ffffff" d="M68 176c-8.8 0-16-7.2-16-16v-64c0-8.8 7.2-16 16-16h56c8.8 0 16 7.2 16 16v12h-24v-4c0-3.3-2.7-6-6-6H84c-3.3 0-6 2.7-6 6v44c0 3.3 2.7 6 6 6h26c3.3 0 6-2.7 6-6v-4h24v12c0 8.8-7.2 16-16 16H68zm120-32h-48v-32h48v-16l32 32-32 32v-16z"/>
<text x="128" y="210" text-anchor="middle" font-family="'Montserrat', Arial, sans-serif" font-size="28" fill="#ffffff" opacity="0.9">SitiAI</text>
</svg>

After

Width:  |  Height:  |  Size: 735 B

View File

@@ -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.7.0 * Version: 1.8.0
* Author: SitiAI * Author: SitiAI
* Text Domain: siti-ai-product-content-generator * Text Domain: siti-ai-product-content-generator
* Domain Path: /languages * Domain Path: /languages
@@ -350,6 +350,22 @@ final class Groq_AI_Product_Text_Plugin {
return $this->get_settings_manager()->get_term_bottom_description_char_limit( $settings ); return $this->get_settings_manager()->get_term_bottom_description_char_limit( $settings );
} }
public function get_google_safety_settings( $settings = null ) {
return $this->get_settings_manager()->get_google_safety_settings( $settings );
}
public function get_google_safety_categories() {
return $this->get_settings_manager()->get_google_safety_categories();
}
public function get_google_safety_thresholds() {
return $this->get_settings_manager()->get_google_safety_thresholds();
}
public function get_loggable_settings_snapshot( $settings = null ) {
return $this->get_settings_manager()->get_loggable_settings_snapshot( $settings );
}
public function should_use_response_format( Groq_AI_Provider_Interface $provider, $settings ) { public function should_use_response_format( Groq_AI_Provider_Interface $provider, $settings ) {
return ! $this->is_response_format_compat_enabled( $settings ) && $provider->supports_response_format(); return ! $this->is_response_format_compat_enabled( $settings ) && $provider->supports_response_format();
} }

View File

@@ -217,6 +217,9 @@ class Groq_AI_Product_Text_Settings_Page {
$google_connected_email = isset( $settings['google_oauth_connected_email'] ) ? (string) $settings['google_oauth_connected_email'] : ''; $google_connected_email = isset( $settings['google_oauth_connected_email'] ) ? (string) $settings['google_oauth_connected_email'] : '';
$google_connected_at = isset( $settings['google_oauth_connected_at'] ) ? absint( $settings['google_oauth_connected_at'] ) : 0; $google_connected_at = isset( $settings['google_oauth_connected_at'] ) ? absint( $settings['google_oauth_connected_at'] ) : 0;
$oauth_redirect = add_query_arg( 'action', 'groq_ai_google_oauth_callback', admin_url( 'admin-post.php' ) ); $oauth_redirect = add_query_arg( 'action', 'groq_ai_google_oauth_callback', admin_url( 'admin-post.php' ) );
$google_safety_settings = $this->plugin->get_google_safety_settings( $settings );
$google_safety_categories = $this->plugin->get_google_safety_categories();
$google_safety_thresholds = $this->plugin->get_google_safety_thresholds();
?> ?>
<div class="wrap"> <div class="wrap">
@@ -281,6 +284,30 @@ class Groq_AI_Product_Text_Settings_Page {
<td> <td>
<input type="password" id="groq-ai-api-<?php echo esc_attr( $provider_key ); ?>" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[<?php echo esc_attr( $option_field ); ?>]" value="<?php echo esc_attr( $value ); ?>" autocomplete="off" /> <input type="password" id="groq-ai-api-<?php echo esc_attr( $provider_key ); ?>" class="regular-text" name="<?php echo esc_attr( $option_key ); ?>[<?php echo esc_attr( $option_field ); ?>]" value="<?php echo esc_attr( $value ); ?>" autocomplete="off" />
<p class="description"><?php printf( esc_html__( 'Voer de API-sleutel in voor %s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), esc_html( $provider->get_label() ) ); ?></p> <p class="description"><?php printf( esc_html__( 'Voer de API-sleutel in voor %s.', GROQ_AI_PRODUCT_TEXT_DOMAIN ), esc_html( $provider->get_label() ) ); ?></p>
<?php if ( 'google' === $provider_key && ! empty( $google_safety_categories ) ) : ?>
<div class="groq-ai-google-safety-settings" style="margin-top:16px; padding:16px; border:1px solid #dcdcde; background:#f6f7f7;">
<strong><?php esc_html_e( 'Gemini safety filters', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></strong>
<p class="description" style="margin-top:4px;"><?php esc_html_e( 'Kies optioneel welke beleidscategorieën je zelf instelt. Laat op "Google standaard" om geen safetySettings mee te sturen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></p>
<?php foreach ( $google_safety_categories as $category_key => $info ) :
$category_label = isset( $info['label'] ) ? $info['label'] : $category_key;
$category_description = isset( $info['description'] ) ? $info['description'] : '';
$selected_threshold = isset( $google_safety_settings[ $category_key ] ) ? $google_safety_settings[ $category_key ] : '';
$field_id = 'groq-ai-google-safety-' . sanitize_html_class( $category_key );
?>
<label for="<?php echo esc_attr( $field_id ); ?>" style="display:block; margin:12px 0 4px;">
<span style="display:block; margin-bottom:4px;"><strong><?php echo esc_html( $category_label ); ?></strong></span>
<select id="<?php echo esc_attr( $field_id ); ?>" name="<?php echo esc_attr( $option_key ); ?>[google_safety_settings][<?php echo esc_attr( $category_key ); ?>]" style="max-width:280px;">
<?php foreach ( $google_safety_thresholds as $threshold_key => $threshold_label ) : ?>
<option value="<?php echo esc_attr( $threshold_key ); ?>" <?php selected( $selected_threshold, $threshold_key ); ?>><?php echo esc_html( $threshold_label ); ?></option>
<?php endforeach; ?>
</select>
<?php if ( '' !== $category_description ) : ?>
<p class="description" style="margin:4px 0 0;"><?php echo esc_html( $category_description ); ?></p>
<?php endif; ?>
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
@@ -892,6 +919,34 @@ class Groq_AI_Product_Text_Settings_Page {
<h2><?php esc_html_e( 'AI-respons', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2> <h2><?php esc_html_e( 'AI-respons', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<pre style="background:#f9f9f9;border:1px solid #dcdcde;padding:12px;white-space:pre-wrap;"><?php echo esc_html( $log['response'] ); ?></pre> <pre style="background:#f9f9f9;border:1px solid #dcdcde;padding:12px;white-space:pre-wrap;"><?php echo esc_html( $log['response'] ); ?></pre>
<?php
$request_params = [];
if ( ! empty( $log['request_json'] ) ) {
$request_params = json_decode( $log['request_json'], true );
$request_params = is_array( $request_params ) ? $request_params : [];
}
if ( ! empty( $request_params ) ) :
$request_pretty = wp_json_encode( $request_params, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
$request_pretty = $request_pretty ? $request_pretty : wp_json_encode( $request_params );
?>
<h2><?php esc_html_e( 'Request parameters', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<pre style="background:#fff;border:1px solid #dcdcde;padding:12px;white-space:pre-wrap;"><?php echo esc_html( $request_pretty ); ?></pre>
<?php endif; ?>
<?php
$usage_meta = [];
if ( ! empty( $log['usage_json'] ) ) {
$usage_meta = json_decode( $log['usage_json'], true );
$usage_meta = is_array( $usage_meta ) ? $usage_meta : [];
}
if ( ! empty( $usage_meta ) ) :
$usage_pretty = wp_json_encode( $usage_meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
$usage_pretty = $usage_pretty ? $usage_pretty : wp_json_encode( $usage_meta );
?>
<h2><?php esc_html_e( 'Usage metadata', GROQ_AI_PRODUCT_TEXT_DOMAIN ); ?></h2>
<pre style="background:#f6f7f7;border:1px solid #dcdcde;padding:12px;white-space:pre-wrap;"><?php echo esc_html( $usage_pretty ); ?></pre>
<?php endif; ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php <?php

View File

@@ -198,7 +198,26 @@ class Groq_AI_Ajax_Controller {
$final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings ); $final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings );
} }
$request_parameters = $this->build_request_parameters_snapshot(
$settings,
[
'provider' => $provider_key,
'conversation_id' => $conversation_id,
'temperature' => 0.7,
'response_format_mode' => $use_response_format ? 'structured' : 'prompt',
'response_format_definition' => $response_format,
'term_context' => [
'term_id' => $term_id,
'taxonomy' => $taxonomy,
],
'term_options' => $usage_meta['term_options'],
'origin' => $origin,
'google_safety_settings' => isset( $settings['google_safety_settings'] ) ? $settings['google_safety_settings'] : [],
]
);
$model = $this->plugin->get_selected_model( $provider, $settings ); $model = $this->plugin->get_selected_model( $provider, $settings );
$request_parameters['model'] = $model;
$result = $provider->generate_content( $result = $provider->generate_content(
[ [
'prompt' => $final_prompt, 'prompt' => $final_prompt,
@@ -212,20 +231,21 @@ class Groq_AI_Ajax_Controller {
); );
if ( is_wp_error( $result ) ) { if ( is_wp_error( $result ) ) {
if ( $logger ) { if ( $logger ) {
$logger->log_generation_event( $logger->log_generation_event(
[ [
'provider' => $provider_key, 'provider' => $provider_key,
'model' => $model, 'model' => $model,
'prompt' => $final_prompt, 'prompt' => $final_prompt,
'response' => '', 'response' => '',
'usage' => $usage_meta, 'usage' => $usage_meta,
'status' => 'error', 'status' => 'error',
'error_message' => $result->get_error_message(), 'error_message' => $result->get_error_message(),
'post_id' => 0, 'post_id' => 0,
] 'parameters' => $request_parameters,
); ]
} );
}
return $result; return $result;
} }
@@ -241,20 +261,21 @@ class Groq_AI_Ajax_Controller {
$parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings ); $parsed = $prompt_builder->parse_term_structured_response( $response_text, $settings );
} }
if ( is_wp_error( $parsed ) ) { if ( is_wp_error( $parsed ) ) {
if ( $logger ) { if ( $logger ) {
$logger->log_generation_event( $logger->log_generation_event(
[ [
'provider' => $provider_key, 'provider' => $provider_key,
'model' => $model, 'model' => $model,
'prompt' => $final_prompt, 'prompt' => $final_prompt,
'response' => $response_text, 'response' => $response_text,
'usage' => $response_usage, 'usage' => $response_usage,
'status' => 'error', 'status' => 'error',
'error_message' => $parsed->get_error_message(), 'error_message' => $parsed->get_error_message(),
'post_id' => 0, 'post_id' => 0,
] 'parameters' => $request_parameters,
); ]
} );
}
return $parsed; return $parsed;
} }
if ( ! is_array( $parsed ) ) { if ( ! is_array( $parsed ) ) {
@@ -263,19 +284,20 @@ class Groq_AI_Ajax_Controller {
]; ];
} }
if ( $logger ) { if ( $logger ) {
$logger->log_generation_event( $logger->log_generation_event(
[ [
'provider' => $provider_key, 'provider' => $provider_key,
'model' => $model, 'model' => $model,
'prompt' => $final_prompt, 'prompt' => $final_prompt,
'response' => $response_text, 'response' => $response_text,
'usage' => $response_usage, 'usage' => $response_usage,
'status' => 'success', 'status' => 'success',
'post_id' => 0, 'post_id' => 0,
] 'parameters' => $request_parameters,
); ]
} );
}
return [ return [
'top_description' => isset( $parsed['top_description'] ) ? $parsed['top_description'] : ( isset( $parsed['description'] ) ? $parsed['description'] : '' ), 'top_description' => isset( $parsed['top_description'] ) ? $parsed['top_description'] : ( isset( $parsed['description'] ) ? $parsed['description'] : '' ),
@@ -492,6 +514,23 @@ class Groq_AI_Ajax_Controller {
$final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings ); $final_prompt = $prompt_builder->append_response_instructions( $prompt_with_context, $settings );
} }
$request_parameters = $this->build_request_parameters_snapshot(
$settings,
[
'provider' => $provider_key,
'model' => $model,
'post_id' => $post_id,
'conversation_id' => $conversation_id,
'temperature' => 0.7,
'response_format_mode' => $use_response_format ? 'structured' : 'prompt',
'response_format_definition' => $response_format,
'context_fields' => $context_fields,
'attribute_includes' => isset( $settings['product_attribute_includes'] ) ? $settings['product_attribute_includes'] : [],
'image_context' => $image_context_meta,
'google_safety_settings' => isset( $settings['google_safety_settings'] ) ? $settings['google_safety_settings'] : [],
]
);
$result = $provider->generate_content( $result = $provider->generate_content(
[ [
'prompt' => $final_prompt, 'prompt' => $final_prompt,
@@ -518,6 +557,7 @@ class Groq_AI_Ajax_Controller {
'post_id' => $post_id, 'post_id' => $post_id,
'status' => 'error', 'status' => 'error',
'error_message' => $result->get_error_message(), 'error_message' => $result->get_error_message(),
'parameters' => $request_parameters,
] ]
); );
wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 ); wp_send_json_error( [ 'message' => $result->get_error_message() ], 500 );
@@ -543,6 +583,7 @@ class Groq_AI_Ajax_Controller {
'post_id' => $post_id, 'post_id' => $post_id,
'status' => 'error', 'status' => 'error',
'error_message' => $response->get_error_message(), 'error_message' => $response->get_error_message(),
'parameters' => $request_parameters,
] ]
); );
wp_send_json_error( [ 'message' => $response->get_error_message() ], 500 ); wp_send_json_error( [ 'message' => $response->get_error_message() ], 500 );
@@ -557,6 +598,7 @@ class Groq_AI_Ajax_Controller {
'usage' => $response_usage, 'usage' => $response_usage,
'post_id' => $post_id, 'post_id' => $post_id,
'status' => 'success', 'status' => 'success',
'parameters' => $request_parameters,
] ]
); );
@@ -607,4 +649,16 @@ class Groq_AI_Ajax_Controller {
return (string) $result; return (string) $result;
} }
private function build_request_parameters_snapshot( $settings, array $additional = [] ) {
$snapshot = [
'settings' => $this->plugin->get_loggable_settings_snapshot( $settings ),
];
foreach ( $additional as $key => $value ) {
$snapshot[ $key ] = $value;
}
return $snapshot;
}
} }

View File

@@ -30,7 +30,7 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
} }
public function supports_response_format() { public function supports_response_format() {
return false; return true;
} }
public function supports_image_context() { public function supports_image_context() {
@@ -153,6 +153,18 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
} }
$max_tokens = max( 128, min( 8192, $max_tokens ) ); $max_tokens = max( 128, min( 8192, $max_tokens ) );
$generation_config = [
'temperature' => isset( $args['temperature'] ) ? (float) $args['temperature'] : 0.7,
'maxOutputTokens' => $max_tokens,
];
$response_format = isset( $args['response_format'] ) ? $args['response_format'] : null;
$schema_payload = $this->prepare_response_schema_payload( $response_format );
if ( ! empty( $schema_payload ) ) {
$generation_config['responseMimeType'] = 'application/json';
$generation_config['responseJsonSchema'] = $schema_payload;
}
$payload = [ $payload = [
'contents' => [ 'contents' => [
[ [
@@ -160,12 +172,17 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
'parts' => $parts, 'parts' => $parts,
], ],
], ],
'generationConfig' => [ 'generationConfig' => $generation_config,
'temperature' => isset( $args['temperature'] ) ? (float) $args['temperature'] : 0.7,
'maxOutputTokens' => $max_tokens,
],
]; ];
$safety_settings_payload = $this->build_safety_settings_payload(
isset( $settings['google_safety_settings'] ) ? $settings['google_safety_settings'] : []
);
if ( ! empty( $safety_settings_payload ) ) {
$payload['safetySettings'] = $safety_settings_payload;
}
$response = wp_remote_post( $response = wp_remote_post(
$endpoint, $endpoint,
[ [
@@ -204,7 +221,11 @@ 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_metadata = isset( $body['usageMetadata'] ) && is_array( $body['usageMetadata'] ) ? $body['usageMetadata'] : [];
$usage = $usage_metadata;
if ( ! empty( $usage_metadata ) ) {
$usage = array_merge( $usage, $this->map_usage_metadata_counts( $usage_metadata ) );
}
$finish_reason = isset( $body['candidates'][0]['finishReason'] ) ? sanitize_text_field( (string) $body['candidates'][0]['finishReason'] ) : ''; $finish_reason = isset( $body['candidates'][0]['finishReason'] ) ? sanitize_text_field( (string) $body['candidates'][0]['finishReason'] ) : '';
if ( '' !== $finish_reason ) { if ( '' !== $finish_reason ) {
$usage['finish_reason'] = $finish_reason; $usage['finish_reason'] = $finish_reason;
@@ -216,4 +237,112 @@ class Groq_AI_Provider_Google implements Groq_AI_Provider_Interface {
'raw_response' => $body, 'raw_response' => $body,
]; ];
} }
private function build_safety_settings_payload( $settings ) {
if ( empty( $settings ) || ! is_array( $settings ) ) {
return [];
}
$categories = class_exists( 'Groq_AI_Settings_Manager' ) ? array_keys( Groq_AI_Settings_Manager::get_google_safety_categories_list() ) : [];
$thresholds = class_exists( 'Groq_AI_Settings_Manager' ) ? array_keys( Groq_AI_Settings_Manager::get_google_safety_thresholds_list() ) : [];
if ( empty( $categories ) || empty( $thresholds ) ) {
return [];
}
$payload = [];
foreach ( $settings as $category => $threshold ) {
$category = sanitize_text_field( (string) $category );
$threshold = sanitize_text_field( (string) $threshold );
if ( ! in_array( $category, $categories, true ) || ! in_array( $threshold, $thresholds, true ) ) {
continue;
}
$payload[] = [
'category' => $category,
'threshold' => $threshold,
];
}
return $payload;
}
private function prepare_response_schema_payload( $response_format ) {
if ( empty( $response_format ) || ! is_array( $response_format ) ) {
return [];
}
if ( isset( $response_format['type'] ) && 'json_schema' === $response_format['type'] ) {
if ( isset( $response_format['json_schema']['schema'] ) && is_array( $response_format['json_schema']['schema'] ) ) {
return $this->sanitize_schema_definition( $response_format['json_schema']['schema'] );
}
if ( isset( $response_format['schema'] ) && is_array( $response_format['schema'] ) ) {
return $this->sanitize_schema_definition( $response_format['schema'] );
}
}
return [];
}
private function sanitize_schema_definition( $schema ) {
if ( ! is_array( $schema ) ) {
return [];
}
$encoded = wp_json_encode( $schema );
if ( ! $encoded ) {
return [];
}
$decoded = json_decode( $encoded, true );
if ( ! is_array( $decoded ) ) {
return [];
}
$this->remove_disallowed_schema_keys( $decoded );
return $decoded;
}
private function remove_disallowed_schema_keys( array &$schema ) {
$disallowed = [ 'additionalProperties' ];
foreach ( $schema as $key => &$value ) {
if ( in_array( $key, $disallowed, true ) ) {
unset( $schema[ $key ] );
continue;
}
if ( is_array( $value ) ) {
$this->remove_disallowed_schema_keys( $value );
}
}
unset( $value );
}
private function map_usage_metadata_counts( $metadata ) {
if ( ! is_array( $metadata ) ) {
return [];
}
$mapped = [];
if ( isset( $metadata['promptTokenCount'] ) ) {
$mapped['prompt_tokens'] = absint( $metadata['promptTokenCount'] );
}
if ( isset( $metadata['candidatesTokenCount'] ) ) {
$mapped['completion_tokens'] = absint( $metadata['candidatesTokenCount'] );
}
if ( isset( $metadata['totalTokenCount'] ) ) {
$mapped['total_tokens'] = absint( $metadata['totalTokenCount'] );
}
return $mapped;
}
} }

View File

@@ -26,9 +26,32 @@ class Groq_AI_Generation_Logger {
$table = $this->get_logs_table_name(); $table = $this->get_logs_table_name();
$usage = isset( $args['usage'] ) && is_array( $args['usage'] ) ? $args['usage'] : []; $usage = isset( $args['usage'] ) && is_array( $args['usage'] ) ? $args['usage'] : [];
$prompt_tokens = isset( $usage['prompt_tokens'] ) ? absint( $usage['prompt_tokens'] ) : null; $parameters = isset( $args['parameters'] ) && is_array( $args['parameters'] ) ? $args['parameters'] : [];
$completion_tokens = isset( $usage['completion_tokens'] ) ? absint( $usage['completion_tokens'] ) : null; $prompt_tokens = $this->extract_usage_token_value(
$total_tokens = isset( $usage['total_tokens'] ) ? absint( $usage['total_tokens'] ) : null; $usage,
[
'prompt_tokens',
'promptTokenCount',
'input_tokens',
'inputTokenCount',
]
);
$completion_tokens = $this->extract_usage_token_value(
$usage,
[
'completion_tokens',
'output_tokens',
'candidatesTokenCount',
'outputTokenCount',
]
);
$total_tokens = $this->extract_usage_token_value(
$usage,
[
'total_tokens',
'totalTokenCount',
]
);
$wpdb->insert( $wpdb->insert(
$table, $table,
@@ -46,6 +69,7 @@ class Groq_AI_Generation_Logger {
'status' => isset( $args['status'] ) ? sanitize_text_field( $args['status'] ) : 'success', 'status' => isset( $args['status'] ) ? sanitize_text_field( $args['status'] ) : 'success',
'error_message' => isset( $args['error_message'] ) ? $args['error_message'] : '', 'error_message' => isset( $args['error_message'] ) ? $args['error_message'] : '',
'usage_json' => ! empty( $usage ) ? wp_json_encode( $usage ) : null, 'usage_json' => ! empty( $usage ) ? wp_json_encode( $usage ) : null,
'request_json' => ! empty( $parameters ) ? wp_json_encode( $parameters ) : null,
] ]
); );
} }
@@ -77,6 +101,7 @@ class Groq_AI_Generation_Logger {
public function maybe_create_table() { public function maybe_create_table() {
if ( get_option( self::OPTION_TABLE_CREATED ) ) { if ( get_option( self::OPTION_TABLE_CREATED ) ) {
$this->logs_table_exists = true; $this->logs_table_exists = true;
$this->maybe_upgrade_table_schema();
return; return;
} }
@@ -106,6 +131,7 @@ class Groq_AI_Generation_Logger {
status varchar(20) NOT NULL, status varchar(20) NOT NULL,
error_message text DEFAULT NULL, error_message text DEFAULT NULL,
usage_json longtext DEFAULT NULL, usage_json longtext DEFAULT NULL,
request_json longtext DEFAULT NULL,
PRIMARY KEY (id), PRIMARY KEY (id),
KEY provider (provider), KEY provider (provider),
KEY post_id (post_id) KEY post_id (post_id)
@@ -117,6 +143,26 @@ class Groq_AI_Generation_Logger {
update_option( self::OPTION_TABLE_CREATED, 1 ); update_option( self::OPTION_TABLE_CREATED, 1 );
} }
private function extract_usage_token_value( $usage, $keys ) {
foreach ( (array) $keys as $key ) {
if ( isset( $usage[ $key ] ) ) {
return absint( $usage[ $key ] );
}
}
return null;
}
private function maybe_upgrade_table_schema() {
global $wpdb;
$table = $this->get_logs_table_name();
$column = $wpdb->get_var( $wpdb->prepare( "SHOW COLUMNS FROM {$table} LIKE %s", 'request_json' ) );
if ( ! $column ) {
$this->create_table();
}
}
private function get_logs_table_name() { private function get_logs_table_name() {
global $wpdb; global $wpdb;

View File

@@ -47,6 +47,7 @@ class Groq_AI_Settings_Manager {
'google_enable_ga' => true, 'google_enable_ga' => true,
'google_gsc_site_url' => '', 'google_gsc_site_url' => '',
'google_ga4_property_id' => '', 'google_ga4_property_id' => '',
'google_safety_settings' => [],
'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',
@@ -60,6 +61,7 @@ class Groq_AI_Settings_Manager {
$settings = wp_parse_args( (array) $settings, $defaults ); $settings = wp_parse_args( (array) $settings, $defaults );
$settings['context_fields'] = $this->normalize_context_fields( isset( $settings['context_fields'] ) ? $settings['context_fields'] : [] ); $settings['context_fields'] = $this->normalize_context_fields( isset( $settings['context_fields'] ) ? $settings['context_fields'] : [] );
$settings['modules'] = $this->sanitize_modules_settings( isset( $settings['modules'] ) ? $settings['modules'] : [] ); $settings['modules'] = $this->sanitize_modules_settings( isset( $settings['modules'] ) ? $settings['modules'] : [] );
$settings['google_safety_settings'] = $this->sanitize_google_safety_settings( isset( $settings['google_safety_settings'] ) ? $settings['google_safety_settings'] : [] );
$settings['model'] = Groq_AI_Model_Exclusions::ensure_allowed( $settings['provider'], isset( $settings['model'] ) ? $settings['model'] : '' ); $settings['model'] = Groq_AI_Model_Exclusions::ensure_allowed( $settings['provider'], isset( $settings['model'] ) ? $settings['model'] : '' );
$image_mode = isset( $settings['image_context_mode'] ) ? sanitize_text_field( $settings['image_context_mode'] ) : 'url'; $image_mode = isset( $settings['image_context_mode'] ) ? sanitize_text_field( $settings['image_context_mode'] ) : 'url';
@@ -120,6 +122,7 @@ class Groq_AI_Settings_Manager {
'google_enable_ga' => true, 'google_enable_ga' => true,
'google_gsc_site_url' => '', 'google_gsc_site_url' => '',
'google_ga4_property_id' => '', 'google_ga4_property_id' => '',
'google_safety_settings' => [],
'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',
@@ -193,6 +196,7 @@ class Groq_AI_Settings_Manager {
'google_enable_ga' => ! empty( $raw_input['google_enable_ga'] ), 'google_enable_ga' => ! empty( $raw_input['google_enable_ga'] ),
'google_gsc_site_url' => esc_url_raw( (string) $input['google_gsc_site_url'] ), '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'] ), 'google_ga4_property_id' => sanitize_text_field( (string) $input['google_ga4_property_id'] ),
'google_safety_settings' => $this->sanitize_google_safety_settings( isset( $raw_input['google_safety_settings'] ) ? $raw_input['google_safety_settings'] : [] ),
'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,
@@ -422,6 +426,94 @@ class Groq_AI_Settings_Manager {
return $this->sanitize_term_description_char_limit_value( $value, 1200 ); return $this->sanitize_term_description_char_limit_value( $value, 1200 );
} }
public function get_google_safety_settings( $settings = null ) {
if ( null === $settings ) {
$settings = $this->all();
}
return $this->sanitize_google_safety_settings( isset( $settings['google_safety_settings'] ) ? $settings['google_safety_settings'] : [] );
}
public function get_google_safety_categories() {
return self::get_google_safety_categories_list();
}
public function get_google_safety_thresholds() {
return self::get_google_safety_thresholds_list();
}
public function get_loggable_settings_snapshot( $settings = null ) {
if ( null === $settings ) {
$settings = $this->all();
}
$allowed_keys = [
'store_context',
'default_prompt',
'max_output_tokens',
'product_attribute_includes',
'context_fields',
'modules',
'image_context_mode',
'image_context_limit',
'response_format_compat',
'term_top_description_char_limit',
'term_bottom_description_char_limit',
'term_bottom_description_meta_key',
'google_safety_settings',
'google_enable_gsc',
'google_enable_ga',
'google_gsc_site_url',
'google_ga4_property_id',
];
$snapshot = [];
foreach ( $allowed_keys as $key ) {
if ( array_key_exists( $key, $settings ) ) {
$snapshot[ $key ] = $settings[ $key ];
}
}
return $snapshot;
}
public static function get_google_safety_categories_list() {
return [
'HARM_CATEGORY_HARASSMENT' => [
'label' => __( 'Harassment & intimidatie', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'description' => __( 'Detecteert bedreigingen en pesterijen in de output.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
'HARM_CATEGORY_HATE_SPEECH' => [
'label' => __( 'Haatspraak', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'description' => __( 'Beperkt discriminerende of denigrerende taal.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
'HARM_CATEGORY_SEXUALLY_EXPLICIT' => [
'label' => __( 'Seksueel expliciet', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'description' => __( 'Filtert beschrijvingen van seksuele handelingen of fetish-content.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
'HARM_CATEGORY_DANGEROUS_CONTENT' => [
'label' => __( 'Gevaarlijke activiteiten', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'description' => __( 'Voorkomt instructies rond geweld, wapens of gevaarlijke middelen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
'HARM_CATEGORY_CIVIC_INTEGRITY' => [
'label' => __( 'Civieke integriteit', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'description' => __( 'Vermindert desinformatie rond verkiezingen en burgerprocessen.', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
],
];
}
public static function get_google_safety_thresholds_list() {
return [
'' => __( 'Google standaard (niet meesturen)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'HARM_BLOCK_THRESHOLD_UNSPECIFIED' => __( 'Onbekende drempel (laat Google beslissen)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'BLOCK_LOW_AND_ABOVE' => __( 'Blokkeer lage ernst en hoger', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'BLOCK_MEDIUM_AND_ABOVE' => __( 'Blokkeer middel en hoger', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'BLOCK_ONLY_HIGH' => __( 'Blokkeer alleen hoge ernst', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
'BLOCK_NONE' => __( 'Sta alles toe (geen blokkade)', GROQ_AI_PRODUCT_TEXT_DOMAIN ),
];
}
public function is_response_format_compat_enabled( $settings = null ) { public function is_response_format_compat_enabled( $settings = null ) {
if ( null === $settings ) { if ( null === $settings ) {
$settings = $this->all(); $settings = $this->all();
@@ -505,4 +597,27 @@ class Groq_AI_Settings_Manager {
return min( 10, $limit ); return min( 10, $limit );
} }
private function sanitize_google_safety_settings( $settings ) {
if ( ! is_array( $settings ) ) {
return [];
}
$categories = array_keys( self::get_google_safety_categories_list() );
$thresholds = array_keys( self::get_google_safety_thresholds_list() );
$clean = [];
foreach ( $settings as $category => $threshold ) {
$category = sanitize_text_field( (string) $category );
$threshold = sanitize_text_field( (string) $threshold );
if ( '' === $threshold || ! in_array( $category, $categories, true ) || ! in_array( $threshold, $thresholds, true ) ) {
continue;
}
$clean[ $category ] = $threshold;
}
return $clean;
}
} }