feat: Add Gemini AI integration for product title and description optimization
- Implemented Gemini API service for generating optimized product titles and descriptions based on Google Merchant Center guidelines. - Added settings for Gemini API key and model selection in user settings. - Enhanced product management views to support AI-generated suggestions for titles and descriptions. - Enabled state saving for various data tables across campaign, terms, logs, and products views. - Introduced AI prompt templates for generating product descriptions and categories.
This commit is contained in:
@@ -104,10 +104,12 @@
|
||||
<?php
|
||||
$openai_enabled = \services\GoogleAdsApi::get_setting( 'openai_enabled' ) !== '0';
|
||||
$claude_enabled = \services\GoogleAdsApi::get_setting( 'claude_enabled' ) !== '0';
|
||||
$gemini_enabled = \services\GoogleAdsApi::get_setting( 'gemini_enabled' ) !== '0';
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
var AI_OPENAI_ENABLED = <?= $openai_enabled ? 'true' : 'false'; ?>;
|
||||
var AI_CLAUDE_ENABLED = <?= $claude_enabled ? 'true' : 'false'; ?>;
|
||||
var AI_GEMINI_ENABLED = <?= $gemini_enabled ? 'true' : 'false'; ?>;
|
||||
var PRODUCTS_COLUMNS_STORAGE_KEY = 'products.columns.visibility';
|
||||
var PRODUCTS_LOCKED_COLUMNS = [ 0, 20 ];
|
||||
|
||||
@@ -308,6 +310,7 @@ function products_render_columns_picker( table_instance )
|
||||
$( function()
|
||||
{
|
||||
var products_table = new DataTable( '#products', {
|
||||
stateSave: true,
|
||||
ajax: {
|
||||
type: 'POST',
|
||||
url: '/products/get_products/',
|
||||
@@ -372,7 +375,7 @@ $( function()
|
||||
products_table.ajax.reload( null, false );
|
||||
}
|
||||
|
||||
function submit_delete_campaign_ad_group( campaign_id, ad_group_id, delete_scope )
|
||||
function submit_delete_campaign_ad_group( campaign_id, ad_group_id, delete_scope, on_success )
|
||||
{
|
||||
function parse_json_loose( raw )
|
||||
{
|
||||
@@ -412,6 +415,12 @@ $( function()
|
||||
function handle_success( message )
|
||||
{
|
||||
show_toast( message || 'Grupa reklam zostala usunieta.', 'success' );
|
||||
|
||||
if ( typeof on_success === 'function' )
|
||||
{
|
||||
on_success();
|
||||
}
|
||||
|
||||
localStorage.removeItem( 'products_ad_group_id' );
|
||||
load_products_ad_groups( campaign_id, '' ).done( function() {
|
||||
$.when( load_scope_alerts(), load_zero_impressions_products() ).always( function() {
|
||||
@@ -1138,7 +1147,12 @@ $( function()
|
||||
btnClass: 'btn-default',
|
||||
action: function()
|
||||
{
|
||||
return submit_delete_campaign_ad_group( campaign_id, ad_group_id, 'local' );
|
||||
var modal = this;
|
||||
submit_delete_campaign_ad_group( campaign_id, ad_group_id, 'local', function()
|
||||
{
|
||||
modal.close();
|
||||
} );
|
||||
return false;
|
||||
}
|
||||
},
|
||||
google: {
|
||||
@@ -1146,7 +1160,12 @@ $( function()
|
||||
btnClass: 'btn-red',
|
||||
action: function()
|
||||
{
|
||||
return submit_delete_campaign_ad_group( campaign_id, ad_group_id, 'google' );
|
||||
var modal = this;
|
||||
submit_delete_campaign_ad_group( campaign_id, ad_group_id, 'google', function()
|
||||
{
|
||||
modal.close();
|
||||
} );
|
||||
return false;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
@@ -1383,8 +1402,10 @@ $( function()
|
||||
'<input type="text" value="" product_id="' + $( this ).attr( 'product_id' ) + '" placeholder="Tytuł produktu" class="name form-control" required />' +
|
||||
( AI_OPENAI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest" data-field="title" data-provider="openai" title="Zaproponuj tytuł przez ChatGPT"><i class="fa-solid fa-wand-magic-sparkles"></i> GPT</button>' : '' ) +
|
||||
( AI_CLAUDE_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest btn-ai-claude" data-field="title" data-provider="claude" title="Zaproponuj tytuł przez Claude"><i class="fa-solid fa-brain"></i> Claude</button>' : '' ) +
|
||||
( AI_GEMINI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest btn-ai-gemini" data-field="title" data-provider="gemini" title="Zaproponuj tytuł przez Gemini"><i class="fa-solid fa-diamond"></i> Gemini</button>' : '' ) +
|
||||
'</div>' +
|
||||
'<small>0/150 znaków</small>' +
|
||||
'<div class="title-ai-alternatives" style="margin-top:8px;display:none;"></div>' +
|
||||
'</div>' +
|
||||
'<div class="form-group">' +
|
||||
'<label>URL strony produktu <small class="text-muted">(opcjonalnie, dla lepszego kontekstu AI)</small></label>' +
|
||||
@@ -1405,6 +1426,7 @@ $( function()
|
||||
'</div>' +
|
||||
( AI_OPENAI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest" data-field="description" data-provider="openai" title="Zaproponuj opis przez ChatGPT"><i class="fa-solid fa-wand-magic-sparkles"></i> GPT</button>' : '' ) +
|
||||
( AI_CLAUDE_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest btn-ai-claude" data-field="description" data-provider="claude" title="Zaproponuj opis przez Claude"><i class="fa-solid fa-brain"></i> Claude</button>' : '' ) +
|
||||
( AI_GEMINI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest btn-ai-gemini" data-field="description" data-provider="gemini" title="Zaproponuj opis przez Gemini"><i class="fa-solid fa-diamond"></i> Gemini</button>' : '' ) +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="form-group">' +
|
||||
@@ -1415,6 +1437,7 @@ $( function()
|
||||
'</select>' +
|
||||
( AI_OPENAI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest" data-field="category" data-provider="openai" title="Zaproponuj kategorię przez ChatGPT"><i class="fa-solid fa-wand-magic-sparkles"></i> GPT</button>' : '' ) +
|
||||
( AI_CLAUDE_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest btn-ai-claude" data-field="category" data-provider="claude" title="Zaproponuj kategorię przez Claude"><i class="fa-solid fa-brain"></i> Claude</button>' : '' ) +
|
||||
( AI_GEMINI_ENABLED ? '<button type="button" class="btn btn-sm btn-ai-suggest btn-ai-gemini" data-field="category" data-provider="gemini" title="Zaproponuj kategorię przez Gemini"><i class="fa-solid fa-diamond"></i> Gemini</button>' : '' ) +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</form>',
|
||||
@@ -1478,8 +1501,56 @@ $( function()
|
||||
var $description = this.$content.find( '.description' );
|
||||
var $productUrl = this.$content.find( '.product-url' );
|
||||
var $googleCategory = this.$content.find( '.google-category' );
|
||||
var $titleAlternatives = this.$content.find( '.title-ai-alternatives' );
|
||||
var product_id = $inputField.attr( 'product_id' );
|
||||
|
||||
function set_title_value( value ) {
|
||||
value = String( value || '' );
|
||||
$inputField.val( value );
|
||||
var len = value.length;
|
||||
$charCount.text( len + '/150 znaków' );
|
||||
$inputField.toggleClass( 'is-invalid', len > 150 );
|
||||
}
|
||||
|
||||
function render_title_alternatives( bestTitle, candidates ) {
|
||||
var current = $.trim( String( bestTitle || '' ) );
|
||||
var seen = {};
|
||||
var list = [];
|
||||
|
||||
( candidates || [] ).forEach( function( item ) {
|
||||
var title = $.trim( String( item || '' ) );
|
||||
if ( !title ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var key = title.toLowerCase();
|
||||
if ( key === current.toLowerCase() || seen[ key ] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
seen[ key ] = true;
|
||||
list.push( title );
|
||||
} );
|
||||
|
||||
if ( !list.length ) {
|
||||
$titleAlternatives.hide().empty();
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '<div class="js-title-alts-list" style="margin-top:8px;"><small class="text-muted">Alternatywy:</small>';
|
||||
|
||||
list.forEach( function( title, idx ) {
|
||||
html += '<div style="margin-top:4px;">'
|
||||
+ '<button type="button" class="btn btn-xs btn-default js-title-alt-apply" data-title-alt="' + escape_html( title ) + '" style="width:100%;text-align:left;">'
|
||||
+ ( idx + 1 ) + '. ' + escape_html( title )
|
||||
+ '</button>'
|
||||
+ '</div>';
|
||||
} );
|
||||
|
||||
html += '</div>';
|
||||
$titleAlternatives.html( html ).show();
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/products/get_product_data/',
|
||||
type: 'POST',
|
||||
@@ -1488,8 +1559,7 @@ $( function()
|
||||
var data = JSON.parse( response );
|
||||
if ( data.status == 'ok' ) {
|
||||
if ( data.product_details.title ) {
|
||||
$inputField.val( data.product_details.title );
|
||||
$charCount.text( data.product_details.title.length + '/150 znaków' );
|
||||
set_title_value( data.product_details.title );
|
||||
}
|
||||
if ( data.product_details.description ) {
|
||||
$description.val( data.product_details.description );
|
||||
@@ -1544,7 +1614,7 @@ $( function()
|
||||
var field = $btn.data( 'field' );
|
||||
var provider = $btn.data( 'provider' ) || 'openai';
|
||||
var originalHtml = $btn.html();
|
||||
var providerLabel = provider === 'claude' ? 'Claude' : 'ChatGPT';
|
||||
var providerLabel = provider === 'claude' ? 'Claude' : ( provider === 'gemini' ? 'Gemini' : 'ChatGPT' );
|
||||
|
||||
$btn.prop( 'disabled', true ).html( '<i class="fa-solid fa-spinner fa-spin"></i>' );
|
||||
|
||||
@@ -1556,10 +1626,8 @@ $( function()
|
||||
var data = JSON.parse( response );
|
||||
if ( data.status == 'ok' ) {
|
||||
if ( field == 'title' ) {
|
||||
$inputField.val( data.suggestion );
|
||||
var len = data.suggestion.length;
|
||||
$charCount.text( len + '/150 znaków' );
|
||||
$inputField.toggleClass( 'is-invalid', len > 150 );
|
||||
set_title_value( data.suggestion );
|
||||
render_title_alternatives( data.suggestion, data.title_candidates || [] );
|
||||
} else if ( field == 'description' ) {
|
||||
$description.val( data.suggestion );
|
||||
} else if ( field == 'category' ) {
|
||||
@@ -1598,6 +1666,22 @@ $( function()
|
||||
});
|
||||
});
|
||||
|
||||
this.$content.on( 'click', '.js-title-alts-toggle', function() {
|
||||
var $list = jc.$content.find( '.js-title-alts-list' );
|
||||
$list.toggle();
|
||||
|
||||
$( this ).html(
|
||||
$list.is( ':visible' )
|
||||
? '<i class="fa-solid fa-eye-slash"></i> Ukryj alternatywy'
|
||||
: '<i class="fa-solid fa-list"></i> Pokaż alternatywy (' + $list.find( '.js-title-alt-apply' ).length + ')'
|
||||
);
|
||||
} );
|
||||
|
||||
this.$content.on( 'click', '.js-title-alt-apply', function() {
|
||||
var selectedTitle = $( this ).attr( 'data-title-alt' ) || '';
|
||||
set_title_value( selectedTitle );
|
||||
} );
|
||||
|
||||
$form.on( 'submit', function( e ) {
|
||||
e.preventDefault();
|
||||
jc.$$formSubmit.trigger( 'click' );
|
||||
|
||||
Reference in New Issue
Block a user