Если вы когда-либо тратили 40 минут на написание описания товара, в итоге получив невнятный вариант, вам понравится этот рабочий процесс: WooCommerce генерирует «длинное» описание и «короткий» фрагмент на основе заголовка, атрибутов и нескольких бизнес-заметок — при этом вы сохраняете контроль над проверкой.
Потребность / Вариант использования
В каталогах WooCommerce создание контента часто становится узким местом. Я видел магазины, где 300 товаров находились в статусе «ожидание» просто потому, что отсутствовали описания, хотя фотографии и цены были готовы. Результат: плохая SEO-оптимизация, резкое падение конверсии и задержка запуска командой.
Искусственный интеллект здесь полезен для создания связного, структурированного и ориентированного на прибыль первого варианта на основе уже имеющихся данных. WordPress : заголовок, категории, атрибуты, бренд, размеры, материал и т. д. Цель состоит не в слепой автоматизации, а в сокращении времени на написание описания каждого продукта при сохранении редакционного контроля.
В итоге вы узнаете, как это реализовать:
- Кнопка «Создать с помощью ИИ» в редакторе товаров (административная панель WooCommerce).
- Вызов API ИИ через
wp_remote_post()(без SDK) с обработкой тайм-аутов и ошибок. - Один кэш на каждый временный цикл, чтобы избежать повторной оплаты за одно и то же поколение.
- Безопасное обновление подробное описание et краткое описание (отрывок) из продукта.
- Простая система ограничения скорости для предотвращения скачков цен (и неожиданных счетов).
Краткое резюме
- Мы добавляем мета-блок WooCommerce с кнопкой, которая запускает AJAX-запрос в админке.
- Сервер формирует запрос на основе данных о товаре (атрибуты, категории и т. д.).
- ИИ звонит с
wp_remote_post()короткий таймаут, ограниченное количество повторных попыток, зарегистрированные ошибки. - Очищенный ответ с
wp_kses_post()перед вставкой вpost_contentetpost_excerpt. - Используйте кэширование через API временных данных, чтобы избежать повторной генерации, если ничего не изменилось.
- Ключ API хранится в
wp-config.php(никогда не закодировано жестко, никогда на стороне JavaScript).
Когда следует использовать ИИ для этого?
Используйте ИИ, когда у вас есть значительный объем данных, надежная структура данных и необходимость обеспечения согласованности. Как правило:
- Магазины, в которых представлены товары от разных поставщиков где записи поступают в виде необработанных данных (CSV, ERP) с минимальным количеством текста.
- Каталоги с вариантами (размер/цвет) — там, где вам нужны единообразные описания, а различия будут управляться атрибутами.
- «Базовый уровень» SEO : создать четкий, легко читаемый текст со стабильной структурой (абзацы, списки), а затем доработать стратегические результаты.
- Интернационализация (расширенный вариант): создание версии на английском/немецком/испанском языке на основе французского языка с проверкой человеком.
В реальных условиях хорошо работает генерация товара при его создании, а затем повторная генерация только при изменении определенных полей (атрибутов, категории, бренда). Кэширование на основе «отпечатка» товара значительно помогает.
Когда НЕ следует использовать ИИ
Избегайте использования ИИ, если традиционное решение более надежно или менее рискованно:
- Строго юридически обоснованные описания (косметика, пищевые добавки, лекарства): риск галлюцинаций реален. Предпочтительнее использовать PHP-шаблоны с полями/атрибутами ACF или юридически подтвержденную базу данных.
- Высокотехнологичная продукция где даже малейшая ошибка приводит к возврату средств в службу поддержки клиентов (совместимость, стандарты). Вы по-прежнему можете использовать ИИ, но только в заблокированном окне с обязательной проверкой.
- Сайты с очень жесткими ограничениями по стоимости Если у вас 20 000 товаров и вы часто обновляете их, то расходы на API быстро возрастут, если вы не используете кэширование и пакетную обработку.
- Когда ваши исходные данные некачественные (Несогласованные заголовки, пустые атрибуты). Искусственный интеллект «придумает» способ заполнить пробелы. В этом случае начните с очистки каталога.
Распространенная ошибка — запуск генерации при каждом автоматическом сохранении. В итоге вы получаете десятки вызовов API для одного товара, потому что Gutenberg автоматически сохраняет данные, WooCommerce обновляет метатеги, и ваш хук срабатывает каскадно.
Предпосылки
К апрелю 2026 года необходимо достичь как минимум следующих результатов:
- WordPress 6.9.4 (ваш контекст) и PHP + 8.1.
- WooCommerce Актуальная версия (ветка 2026). Приведенный ниже код основан на стабильных API WordPress, а не на уязвимых внутренних механизмах WooCommerce.
- Доступ к
wp-config.php(или переменные окружения) для хранения ключа API.
Сохраните ключ API (wp-config.php)
Добавить это в wp-config.php (В идеале — через систему управления секретами вашего хостинг-провайдера, но в качестве базового варианта остается неизменным).
/**
* Clé API OpenAI (exemple).
* Ne la commitez jamais dans Git. Ne la mettez jamais dans un plugin.
*/
define('BPCAB_OPENAI_API_KEY', 'sk-proj-...');
Если вы предпочитаете использовать переменные среды, вы можете сделать следующее:
define('BPCAB_OPENAI_API_KEY', getenv('BPCAB_OPENAI_API_KEY') ?: '');
Полезные официальные справочные материалы
- wp_remote_post() (Ресурсы для разработчиков WordPress)
- Нонсы (WordPress)
- wp_kses_post() (очистка HTML-кода)
- Временные данные API
- JSON в PHP (php.net)
Архитектура решения
Схема потока (текстовая схема):
Администрирование товара (кнопка «Сгенерировать») → AJAX-администрирование (nonce + возможности) → получение данных о товаре → вычисление хеша → временное кэширование? → в противном случае wp_remote_post() → API для ИИ → Парсинг JSON → Санитарная обработка (
wp_kses_post()) → Обновление продукта (контент + выдержка) → JSON возвращен администратору
Что происходит за кулисами?
- Администратор пользовательского интерфейса : мета-блок с кнопкой + необязательное поле "Примечания". В JavaScript-коде никогда не используется ключ API.
- AJAX-конечная точка : защищено одноразовым кодом +
current_user_can('edit_product', $product_id). - Незамедлительный Создан на основе надежных элементов (атрибуты WooCommerce, категории, теги, цены, если хотите).
- Кэш Временное значение, основанное на хеше данных. Если ничего не изменилось, возвращается уже сгенерированное описание.
- Санитарная Искусственный интеллект может возвращать HTML-код. Доступ к записи разрешен только в том виде, в котором это позволяет WordPress.
wp_kses_post().
Полный код — шаг за шагом
Я советую вам выбрать мюплагин Чтобы предотвратить поломку дочерней темы или плагина с фрагментами кода во время обновления, создайте: wp-content/mu-plugins/bpcab-ai-woo-descriptions.php.
1) Сохраните мета-блок в административной панели товара.
Мы используем экран productЭтот мета-бокс остаётся совместимым независимо от используемого вами конструктора интерфейса (Divi/Elementor/Avada), поскольку он создаётся на стороне административной панели WooCommerce.
<?php
/**
* Plugin Name: BPCAB - IA descriptions produits WooCommerce
* Description: Génère des descriptions produits via IA depuis l'admin WooCommerce (WP 6.9.4+, PHP 8.1+).
* Version: 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
add_action('add_meta_boxes', function () {
add_meta_box(
'bpcab_ai_product_desc',
'Descriptions IA',
'bpcab_render_ai_metabox',
'product',
'side',
'high'
);
});
function bpcab_render_ai_metabox(WP_Post $post): void {
$product_id = (int) $post->ID;
if (!current_user_can('edit_product', $product_id)) {
echo '<p>Vous n’avez pas les droits pour modifier ce produit.</p>';
return;
}
wp_nonce_field('bpcab_ai_generate_desc', 'bpcab_ai_nonce');
echo '<p>
<label for="bpcab_ai_notes"><strong>Notes internes (optionnel)</strong></label>
<textarea id="bpcab_ai_notes" style="width:100%;min-height:70px;" placeholder="Ex: ton premium, mentionner la garantie 2 ans, éviter les superlatifs..."></textarea>
</p>';
echo '<p>
<button type="button" class="button button-primary" id="bpcab-ai-generate" data-product-id="' . esc_attr((string) $product_id) . '">
Générer avec IA
</button>
</p>';
echo '<div id="bpcab-ai-status" style="margin-top:8px;"></div>';
}
2) Загрузите JS-скрипт только на экране товара.
Часто встречающаяся ошибка — это постановка скрипта в очередь повсюду в панели администратора. Это неоправданно замедляет работу и может создавать конфликты с другими плагинами.
add_action('admin_enqueue_scripts', function (string $hook_suffix) {
// Écrans typiques : post.php (édition), post-new.php (création)
if (!in_array($hook_suffix, ['post.php', 'post-new.php'], true)) {
return;
}
$screen = get_current_screen();
if (!$screen || $screen->post_type !== 'product') {
return;
}
wp_enqueue_script(
'bpcab-ai-woo-admin',
plugins_url('bpcab-ai-woo-admin.js', __FILE__),
['jquery'],
'1.0.0',
true
);
wp_localize_script('bpcab-ai-woo-admin', 'BPCAB_AI', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bpcab_ai_generate_desc'),
]);
});
Далее создайте файл. wp-content/mu-plugins/bpcab-ai-woo-admin.jsДа, mu-плагин может загружать отдельный JS-файл, если путь указан правильно. Если ваша инфраструктура блокирует это, поместите JS-файл в обычный плагин (это часто проще).
(function ($) {
function setStatus(html) {
$('#bpcab-ai-status').html(html);
}
$(document).on('click', '#bpcab-ai-generate', function () {
var productId = $(this).data('product-id');
var notes = $('#bpcab_ai_notes').val() || '';
setStatus('<p>Génération en cours… (ne fermez pas cet onglet)</p>');
$.ajax({
url: BPCAB_AI.ajaxUrl,
method: 'POST',
dataType: 'json',
data: {
action: 'bpcab_ai_generate_product_desc',
nonce: BPCAB_AI.nonce,
product_id: productId,
notes: notes
}
})
.done(function (resp) {
if (!resp || !resp.success) {
var msg = (resp && resp.data && resp.data.message) ? resp.data.message : 'Erreur inconnue.';
setStatus('<p style="color:#b32d2e;"><strong>Échec:</strong> ' + msg + '</p>');
return;
}
setStatus(
'<p style="color:#1e7e34;"><strong>OK:</strong> Descriptions mises à jour.</p>' +
'<p>Astuce: cliquez sur “Mettre à jour” pour déclencher les hooks habituels de votre stack (cache, SEO, etc.).</p>'
);
})
.fail(function (xhr) {
setStatus('<p style="color:#b32d2e;"><strong>Erreur AJAX:</strong> vérifiez la console et vos logs PHP.</p>');
});
});
})(jQuery);
3) Создайте AJAX-конечную точку (безопасность + валидация).
Мы проверяем nonce, права доступа и идентификатор продукта. И мы предотвращаем вызовы, если ключ API не настроен.
add_action('wp_ajax_bpcab_ai_generate_product_desc', function () {
// Vérification nonce
$nonce = isset($_POST['nonce']) ? sanitize_text_field((string) $_POST['nonce']) : '';
if (!wp_verify_nonce($nonce, 'bpcab_ai_generate_desc')) {
wp_send_json_error(['message' => 'Nonce invalide. Rechargez la page produit.'], 403);
}
$product_id = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
if ($product_id <= 0) {
wp_send_json_error(['message' => 'ID produit invalide.'], 400);
}
if (!current_user_can('edit_product', $product_id)) {
wp_send_json_error(['message' => 'Droits insuffisants.'], 403);
}
if (!defined('BPCAB_OPENAI_API_KEY') || !BPCAB_OPENAI_API_KEY) {
wp_send_json_error(['message' => 'Clé API manquante. Définissez BPCAB_OPENAI_API_KEY dans wp-config.php.'], 500);
}
$notes = isset($_POST['notes']) ? wp_kses_post((string) $_POST['notes']) : '';
$result = bpcab_ai_generate_and_update_product($product_id, $notes);
if (is_wp_error($result)) {
wp_send_json_error([
'message' => $result->get_error_message(),
'code' => $result->get_error_code(),
], 500);
}
wp_send_json_success(['updated' => true]);
});
4) Получите данные о продукте и сформируйте запрос.
Если мы используем WooCommerce, мы возвращаем понятное сообщение об ошибке. Не путайте это с хуком. save_post Пока поток не стабилизируется, AJAX обеспечивает большую предсказуемость.
function bpcab_ai_generate_and_update_product(int $product_id, string $notes = '') {
if (!function_exists('wc_get_product')) {
return new WP_Error('woocommerce_missing', 'WooCommerce ne semble pas actif.');
}
$product = wc_get_product($product_id);
if (!$product) {
return new WP_Error('product_missing', 'Produit introuvable.');
}
// Rate limiting basique (par produit + utilisateur) pour éviter les rafales.
$user_id = get_current_user_id();
$rl_key = 'bpcab_ai_rl_' . $user_id . '_' . $product_id;
if (get_transient($rl_key)) {
return new WP_Error('rate_limited', 'Vous générez trop vite. Attendez 30 secondes et réessayez.');
}
set_transient($rl_key, 1, 30);
$payload = bpcab_build_product_payload_for_prompt($product);
// Empreinte: si les données n'ont pas changé, on peut servir depuis cache.
$fingerprint = hash('sha256', wp_json_encode([
'payload' => $payload,
'notes' => $notes,
'v' => '1.0.0', // incrémentez si vous changez la logique de prompt
]));
$cache_key = 'bpcab_ai_desc_' . $product_id . '_' . substr($fingerprint, 0, 12);
$cached = get_transient($cache_key);
if (is_array($cached) && isset($cached['long'], $cached['short'])) {
return bpcab_update_product_descriptions($product_id, $cached['long'], $cached['short']);
}
$prompt = bpcab_build_prompt($payload, $notes);
$ai = bpcab_call_openai_responses_api($prompt);
if (is_wp_error($ai)) {
return $ai;
}
// Sanitation: on autorise un HTML "post" standard, pas de scripts, pas d'iframes.
$long = wp_kses_post($ai['long'] ?? '');
$short = wp_kses_post($ai['short'] ?? '');
// Fallback si l'IA renvoie vide (ça arrive avec des prompts trop stricts).
if (mb_strlen(wp_strip_all_tags($long)) < 80) {
return new WP_Error('ai_empty', 'La réponse IA est trop courte ou vide. Vérifiez vos données produit et le prompt.');
}
if (mb_strlen(wp_strip_all_tags($short)) < 30) {
$short = wp_trim_words(wp_strip_all_tags($long), 35, '…');
}
// Cache 30 jours (vous pouvez réduire si votre catalogue change souvent).
set_transient($cache_key, ['long' => $long, 'short' => $short], 30 * DAY_IN_SECONDS);
return bpcab_update_product_descriptions($product_id, $long, $short);
}
function bpcab_build_product_payload_for_prompt(WC_Product $product): array {
$product_id = $product->get_id();
$cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']);
$tags = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'names']);
// Attributs WooCommerce (pa_*) et attributs custom.
$attributes_out = [];
foreach ($product->get_attributes() as $attr) {
if ($attr->is_taxonomy()) {
$taxonomy = $attr->get_name();
$label = wc_attribute_label($taxonomy);
$terms = wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);
$attributes_out[] = [
'label' => $label,
'values' => array_values(array_filter(array_map('sanitize_text_field', $terms))),
];
} else {
$attributes_out[] = [
'label' => sanitize_text_field($attr->get_name()),
'values' => array_values(array_filter(array_map('sanitize_text_field', $attr->get_options()))),
];
}
}
$sku = (string) $product->get_sku();
$price = $product->get_price();
$regular = $product->get_regular_price();
$sale = $product->get_sale_price();
return [
'title' => sanitize_text_field($product->get_name()),
'sku' => sanitize_text_field($sku),
'categories' => array_values(array_filter(array_map('sanitize_text_field', (array) $cats))),
'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $tags))),
'attributes' => $attributes_out,
'price' => $price !== '' ? (string) $price : '',
'regular_price' => $regular !== '' ? (string) $regular : '',
'sale_price' => $sale !== '' ? (string) $sale : '',
'short_description_existing' => wp_strip_all_tags((string) $product->get_short_description()),
'description_existing' => wp_strip_all_tags((string) $product->get_description()),
];
}
function bpcab_build_prompt(array $payload, string $notes = ''): string {
$attrs_lines = [];
foreach (($payload['attributes'] ?? []) as $attr) {
$label = $attr['label'] ?? '';
$values = $attr['values'] ?? [];
if (!$label || empty($values)) {
continue;
}
$attrs_lines[] = '- ' . $label . ' : ' . implode(', ', $values);
}
$notes_clean = trim(wp_strip_all_tags($notes));
// Prompt orienté e-commerce: bénéfices, usage, contraintes, pas de promesses non vérifiées.
$prompt = "Vous êtes un rédacteur e-commerce senior. Rédigez pour WooCommerce une description produit en FR.nn";
$prompt .= "Règles STRICTES:n";
$prompt .= "1) N'inventez aucune caractéristique non fournie.n";
$prompt .= "2) Pas de superlatifs gratuits ("le meilleur", "incroyable").n";
$prompt .= "3) Style clair, concret, orienté bénéfices et usages.n";
$prompt .= "4) HTML autorisé: <p>, <ul>, <li>, <strong>, <em>. Pas de titres H1/H2.n";
$prompt .= "5) Retournez STRICTEMENT un JSON valide avec les clés: long_html, short_html.nn";
$prompt .= "Données produit:n";
$prompt .= "- Titre: " . ($payload['title'] ?? '') . "n";
if (!empty($payload['sku'])) {
$prompt .= "- SKU: " . $payload['sku'] . "n";
}
if (!empty($payload['categories'])) {
$prompt .= "- Catégories: " . implode(', ', (array) $payload['categories']) . "n";
}
if (!empty($payload['tags'])) {
$prompt .= "- Tags: " . implode(', ', (array) $payload['tags']) . "n";
}
if (!empty($attrs_lines)) {
$prompt .= "- Attributs:n" . implode("n", $attrs_lines) . "n";
}
// Le prix est optionnel: utile si vous voulez positionnement "entrée de gamme/premium".
if (!empty($payload['price'])) {
$prompt .= "- Prix actuel (indicatif): " . $payload['price'] . "n";
}
if ($notes_clean !== '') {
$prompt .= "nNotes internes:n" . $notes_clean . "n";
}
// Si une description existe déjà, on peut demander une amélioration plutôt qu'une réécriture totale.
$existing = trim((string) ($payload['description_existing'] ?? ''));
if ($existing !== '' && mb_strlen($existing) > 80) {
$prompt .= "nTexte existant (à améliorer sans changer le sens):n" . $existing . "n";
}
$prompt .= "nFormat attendu (exemple):n";
$prompt .= "{"long_html":"<p>...</p>","short_html":"<p>...</p>"}n";
return $prompt;
}
5) Вызовите API ИИ через функцию wp_remote_post()
Пример с использованием API «Responses» OpenAI. Если вы используете другого поставщика (Anthropic, Mistral, Google), структура изменится, но принципы останутся теми же: тайм-аут, обработка ошибок, строгий анализ.
Я принудительно формирую JSON-ответ в командной строке, а затем обрабатываю его на стороне сервера. Это позволяет избежать 80% «подробных» ответов, которые нарушают процесс вставки.
function bpcab_call_openai_responses_api(string $prompt) {
$endpoint = 'https://api.openai.com/v1/responses';
$body = [
// Modèle: choisissez un modèle "mini" pour réduire les coûts si la qualité vous suffit.
// Adaptez selon votre compte et les modèles disponibles.
'model' => 'gpt-4.1-mini',
'input' => [
[
'role' => 'user',
'content' => [
[
'type' => 'input_text',
'text' => $prompt,
],
],
],
],
// Limite raisonnable: descriptions produit, pas un roman.
'max_output_tokens' => 700,
'temperature' => 0.6,
];
$args = [
'timeout' => 25, // Évitez 60s: en admin, ça se ressent vite.
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
],
'body' => wp_json_encode($body),
];
$res = wp_remote_post($endpoint, $args);
if (is_wp_error($res)) {
return new WP_Error('http_error', 'Erreur HTTP vers l’API IA: ' . $res->get_error_message());
}
$code = (int) wp_remote_retrieve_response_code($res);
$raw = (string) wp_remote_retrieve_body($res);
if ($code < 200 || $code >= 300) {
// Log minimal (évitez de logger des données sensibles).
error_log('[BPCAB AI] API non-200: ' . $code . ' body=' . substr($raw, 0, 500));
return new WP_Error('api_non_200', 'Réponse IA invalide (HTTP ' . $code . '). Vérifiez votre clé/quota.');
}
$json = json_decode($raw, true);
if (!is_array($json)) {
return new WP_Error('json_decode', 'Réponse IA non JSON (json_decode a échoué).');
}
// Récupération du texte: selon l’API, la sortie peut être structurée.
// On essaie plusieurs chemins connus, puis on échoue proprement.
$text = '';
// Chemin courant: output_text agrégé
if (isset($json['output_text']) && is_string($json['output_text'])) {
$text = $json['output_text'];
}
// Fallback: certaines réponses contiennent output[] avec content[]
if ($text === '' && !empty($json['output']) && is_array($json['output'])) {
foreach ($json['output'] as $item) {
if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
continue;
}
foreach ($item['content'] as $c) {
if (is_array($c) && ($c['type'] ?? '') === 'output_text' && isset($c['text'])) {
$text .= (string) $c['text'];
}
}
}
}
$text = trim($text);
if ($text === '') {
error_log('[BPCAB AI] Sortie vide. Raw=' . substr($raw, 0, 500));
return new WP_Error('empty_output', 'L’API IA a renvoyé une sortie vide.');
}
// On attend un JSON strict dans $text
$out = json_decode($text, true);
if (!is_array($out)) {
error_log('[BPCAB AI] JSON attendu mais non parsable. Text=' . substr($text, 0, 500));
return new WP_Error('bad_format', 'Format IA inattendu. Ajustez le prompt (JSON strict).');
}
$long = isset($out['long_html']) ? (string) $out['long_html'] : '';
$short = isset($out['short_html']) ? (string) $out['short_html'] : '';
return [
'long' => $long,
'short' => $short,
];
}
6) Обновите продукт (содержание + отрывок)
Базовая запись WordPress обновляется. Затем WooCommerce считывает эти поля для отображения описания. Я использую wp_update_post() чтобы оставаться в рамках стандартного рабочего процесса WordPress (хуки, правки (если включены) и т. д.).
function bpcab_update_product_descriptions(int $product_id, string $long_html, string $short_html) {
$update = [
'ID' => $product_id,
'post_content' => $long_html,
'post_excerpt' => $short_html,
];
$res = wp_update_post(wp_slash($update), true, false);
if (is_wp_error($res)) {
return new WP_Error('update_failed', 'Échec mise à jour produit: ' . $res->get_error_message());
}
// Optionnel: forcer une mise à jour du produit WooCommerce (index, lookup tables, etc.)
if (function_exists('wc_get_product')) {
$product = wc_get_product($product_id);
if ($product) {
$product->save();
}
}
return true;
}
Полный собранный код
Скопируйте и вставьте этот файл в wp-content/mu-plugins/bpcab-ai-woo-descriptions.phpТакже создайте JS-файл. bpcab-ai-woo-admin.js рядом с ним (указано выше).
<?php
/**
* Plugin Name: BPCAB - IA descriptions produits WooCommerce
* Description: Génère des descriptions produits via IA depuis l'admin WooCommerce (WP 6.9.4+, PHP 8.1+).
* Version: 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Meta box sur l'écran produit.
*/
add_action('add_meta_boxes', function () {
add_meta_box(
'bpcab_ai_product_desc',
'Descriptions IA',
'bpcab_render_ai_metabox',
'product',
'side',
'high'
);
});
function bpcab_render_ai_metabox(WP_Post $post): void {
$product_id = (int) $post->ID;
if (!current_user_can('edit_product', $product_id)) {
echo '<p>Vous n’avez pas les droits pour modifier ce produit.</p>';
return;
}
wp_nonce_field('bpcab_ai_generate_desc', 'bpcab_ai_nonce');
echo '<p>
<label for="bpcab_ai_notes"><strong>Notes internes (optionnel)</strong></label>
<textarea id="bpcab_ai_notes" style="width:100%;min-height:70px;" placeholder="Ex: ton premium, mentionner la garantie 2 ans, éviter les superlatifs..."></textarea>
</p>';
echo '<p>
<button type="button" class="button button-primary" id="bpcab-ai-generate" data-product-id="' . esc_attr((string) $product_id) . '">
Générer avec IA
</button>
</p>';
echo '<div id="bpcab-ai-status" style="margin-top:8px;"></div>';
}
/**
* JS admin (uniquement sur l'écran produit).
*/
add_action('admin_enqueue_scripts', function (string $hook_suffix) {
if (!in_array($hook_suffix, ['post.php', 'post-new.php'], true)) {
return;
}
$screen = get_current_screen();
if (!$screen || $screen->post_type !== 'product') {
return;
}
wp_enqueue_script(
'bpcab-ai-woo-admin',
plugins_url('bpcab-ai-woo-admin.js', __FILE__),
['jquery'],
'1.0.0',
true
);
wp_localize_script('bpcab-ai-woo-admin', 'BPCAB_AI', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bpcab_ai_generate_desc'),
]);
});
/**
* Endpoint AJAX sécurisé.
*/
add_action('wp_ajax_bpcab_ai_generate_product_desc', function () {
$nonce = isset($_POST['nonce']) ? sanitize_text_field((string) $_POST['nonce']) : '';
if (!wp_verify_nonce($nonce, 'bpcab_ai_generate_desc')) {
wp_send_json_error(['message' => 'Nonce invalide. Rechargez la page produit.'], 403);
}
$product_id = isset($_POST['product_id']) ? (int) $_POST['product_id'] : 0;
if ($product_id <= 0) {
wp_send_json_error(['message' => 'ID produit invalide.'], 400);
}
if (!current_user_can('edit_product', $product_id)) {
wp_send_json_error(['message' => 'Droits insuffisants.'], 403);
}
if (!defined('BPCAB_OPENAI_API_KEY') || !BPCAB_OPENAI_API_KEY) {
wp_send_json_error(['message' => 'Clé API manquante. Définissez BPCAB_OPENAI_API_KEY dans wp-config.php.'], 500);
}
$notes = isset($_POST['notes']) ? wp_kses_post((string) $_POST['notes']) : '';
$result = bpcab_ai_generate_and_update_product($product_id, $notes);
if (is_wp_error($result)) {
wp_send_json_error([
'message' => $result->get_error_message(),
'code' => $result->get_error_code(),
], 500);
}
wp_send_json_success(['updated' => true]);
});
function bpcab_ai_generate_and_update_product(int $product_id, string $notes = '') {
if (!function_exists('wc_get_product')) {
return new WP_Error('woocommerce_missing', 'WooCommerce ne semble pas actif.');
}
$product = wc_get_product($product_id);
if (!$product) {
return new WP_Error('product_missing', 'Produit introuvable.');
}
$user_id = get_current_user_id();
$rl_key = 'bpcab_ai_rl_' . $user_id . '_' . $product_id;
if (get_transient($rl_key)) {
return new WP_Error('rate_limited', 'Vous générez trop vite. Attendez 30 secondes et réessayez.');
}
set_transient($rl_key, 1, 30);
$payload = bpcab_build_product_payload_for_prompt($product);
$fingerprint = hash('sha256', wp_json_encode([
'payload' => $payload,
'notes' => $notes,
'v' => '1.0.0',
]));
$cache_key = 'bpcab_ai_desc_' . $product_id . '_' . substr($fingerprint, 0, 12);
$cached = get_transient($cache_key);
if (is_array($cached) && isset($cached['long'], $cached['short'])) {
return bpcab_update_product_descriptions($product_id, $cached['long'], $cached['short']);
}
$prompt = bpcab_build_prompt($payload, $notes);
$ai = bpcab_call_openai_responses_api($prompt);
if (is_wp_error($ai)) {
return $ai;
}
$long = wp_kses_post($ai['long'] ?? '');
$short = wp_kses_post($ai['short'] ?? '');
if (mb_strlen(wp_strip_all_tags($long)) < 80) {
return new WP_Error('ai_empty', 'La réponse IA est trop courte ou vide. Vérifiez vos données produit et le prompt.');
}
if (mb_strlen(wp_strip_all_tags($short)) < 30) {
$short = wp_trim_words(wp_strip_all_tags($long), 35, '…');
}
set_transient($cache_key, ['long' => $long, 'short' => $short], 30 * DAY_IN_SECONDS);
return bpcab_update_product_descriptions($product_id, $long, $short);
}
function bpcab_build_product_payload_for_prompt(WC_Product $product): array {
$product_id = $product->get_id();
$cats = wp_get_post_terms($product_id, 'product_cat', ['fields' => 'names']);
$tags = wp_get_post_terms($product_id, 'product_tag', ['fields' => 'names']);
$attributes_out = [];
foreach ($product->get_attributes() as $attr) {
if ($attr->is_taxonomy()) {
$taxonomy = $attr->get_name();
$label = wc_attribute_label($taxonomy);
$terms = wp_get_post_terms($product_id, $taxonomy, ['fields' => 'names']);
$attributes_out[] = [
'label' => $label,
'values' => array_values(array_filter(array_map('sanitize_text_field', $terms))),
];
} else {
$attributes_out[] = [
'label' => sanitize_text_field($attr->get_name()),
'values' => array_values(array_filter(array_map('sanitize_text_field', $attr->get_options()))),
];
}
}
$sku = (string) $product->get_sku();
$price = $product->get_price();
$regular = $product->get_regular_price();
$sale = $product->get_sale_price();
return [
'title' => sanitize_text_field($product->get_name()),
'sku' => sanitize_text_field($sku),
'categories' => array_values(array_filter(array_map('sanitize_text_field', (array) $cats))),
'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $tags))),
'attributes' => $attributes_out,
'price' => $price !== '' ? (string) $price : '',
'regular_price' => $regular !== '' ? (string) $regular : '',
'sale_price' => $sale !== '' ? (string) $sale : '',
'short_description_existing' => wp_strip_all_tags((string) $product->get_short_description()),
'description_existing' => wp_strip_all_tags((string) $product->get_description()),
];
}
function bpcab_build_prompt(array $payload, string $notes = ''): string {
$attrs_lines = [];
foreach (($payload['attributes'] ?? []) as $attr) {
$label = $attr['label'] ?? '';
$values = $attr['values'] ?? [];
if (!$label || empty($values)) {
continue;
}
$attrs_lines[] = '- ' . $label . ' : ' . implode(', ', $values);
}
$notes_clean = trim(wp_strip_all_tags($notes));
$prompt = "Vous êtes un rédacteur e-commerce senior. Rédigez pour WooCommerce une description produit en FR.nn";
$prompt .= "Règles STRICTES:n";
$prompt .= "1) N'inventez aucune caractéristique non fournie.n";
$prompt .= "2) Pas de superlatifs gratuits ("le meilleur", "incroyable").n";
$prompt .= "3) Style clair, concret, orienté bénéfices et usages.n";
$prompt .= "4) HTML autorisé: <p>, <ul>, <li>, <strong>, <em>. Pas de titres H1/H2.n";
$prompt .= "5) Retournez STRICTEMENT un JSON valide avec les clés: long_html, short_html.nn";
$prompt .= "Données produit:n";
$prompt .= "- Titre: " . ($payload['title'] ?? '') . "n";
if (!empty($payload['sku'])) {
$prompt .= "- SKU: " . $payload['sku'] . "n";
}
if (!empty($payload['categories'])) {
$prompt .= "- Catégories: " . implode(', ', (array) $payload['categories']) . "n";
}
if (!empty($payload['tags'])) {
$prompt .= "- Tags: " . implode(', ', (array) $payload['tags']) . "n";
}
if (!empty($attrs_lines)) {
$prompt .= "- Attributs:n" . implode("n", $attrs_lines) . "n";
}
if (!empty($payload['price'])) {
$prompt .= "- Prix actuel (indicatif): " . $payload['price'] . "n";
}
if ($notes_clean !== '') {
$prompt .= "nNotes internes:n" . $notes_clean . "n";
}
$existing = trim((string) ($payload['description_existing'] ?? ''));
if ($existing !== '' && mb_strlen($existing) > 80) {
$prompt .= "nTexte existant (à améliorer sans changer le sens):n" . $existing . "n";
}
$prompt .= "nFormat attendu (exemple):n";
$prompt .= "{"long_html":"<p>...</p>","short_html":"<p>...</p>"}n";
return $prompt;
}
function bpcab_call_openai_responses_api(string $prompt) {
$endpoint = 'https://api.openai.com/v1/responses';
$body = [
'model' => 'gpt-4.1-mini',
'input' => [
[
'role' => 'user',
'content' => [
[
'type' => 'input_text',
'text' => $prompt,
],
],
],
],
'max_output_tokens' => 700,
'temperature' => 0.6,
];
$args = [
'timeout' => 25,
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
],
'body' => wp_json_encode($body),
];
$res = wp_remote_post($endpoint, $args);
if (is_wp_error($res)) {
return new WP_Error('http_error', 'Erreur HTTP vers l’API IA: ' . $res->get_error_message());
}
$code = (int) wp_remote_retrieve_response_code($res);
$raw = (string) wp_remote_retrieve_body($res);
if ($code < 200 || $code >= 300) {
error_log('[BPCAB AI] API non-200: ' . $code . ' body=' . substr($raw, 0, 500));
return new WP_Error('api_non_200', 'Réponse IA invalide (HTTP ' . $code . '). Vérifiez votre clé/quota.');
}
$json = json_decode($raw, true);
if (!is_array($json)) {
return new WP_Error('json_decode', 'Réponse IA non JSON (json_decode a échoué).');
}
$text = '';
if (isset($json['output_text']) && is_string($json['output_text'])) {
$text = $json['output_text'];
}
if ($text === '' && !empty($json['output']) && is_array($json['output'])) {
foreach ($json['output'] as $item) {
if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
continue;
}
foreach ($item['content'] as $c) {
if (is_array($c) && ($c['type'] ?? '') === 'output_text' && isset($c['text'])) {
$text .= (string) $c['text'];
}
}
}
}
$text = trim($text);
if ($text === '') {
error_log('[BPCAB AI] Sortie vide. Raw=' . substr($raw, 0, 500));
return new WP_Error('empty_output', 'L’API IA a renvoyé une sortie vide.');
}
$out = json_decode($text, true);
if (!is_array($out)) {
error_log('[BPCAB AI] JSON attendu mais non parsable. Text=' . substr($text, 0, 500));
return new WP_Error('bad_format', 'Format IA inattendu. Ajustez le prompt (JSON strict).');
}
return [
'long' => isset($out['long_html']) ? (string) $out['long_html'] : '',
'short' => isset($out['short_html']) ? (string) $out['short_html'] : '',
];
}
function bpcab_update_product_descriptions(int $product_id, string $long_html, string $short_html) {
$update = [
'ID' => $product_id,
'post_content' => $long_html,
'post_excerpt' => $short_html,
];
$res = wp_update_post(wp_slash($update), true, false);
if (is_wp_error($res)) {
return new WP_Error('update_failed', 'Échec mise à jour produit: ' . $res->get_error_message());
}
if (function_exists('wc_get_product')) {
$product = wc_get_product($product_id);
if ($product) {
$product->save();
}
}
return true;
}
Код Пояснение
Почему используется кнопка (AJAX), а не автоматический хук?
В WooCommerce товар можно сохранять несколько раз без нажатия кнопки «Обновить»: автосохранение, ревизии, обновления метаданных из других плагинов, синхронизация запасов и т. д. Если подключить ИИ к save_post_product Без мер предосторожности вы будете запускать фантомные вызовы API.
Кнопка инициирует целенаправленное действие. Это упрощает отладку: вы нажимаете, видите статус, исправляете ошибку.
Почему именно хеш-ориентированный транзиент?
Простой кэш "для каждого продукта" (например: bpcab_ai_desc_123Это быстро вводит в заблуждение, поскольку продукт меняется. Хэш SHA-256 полезной нагрузки + примечаний гарантирует: одинаковые входные данные → одинаковый выход → отсутствие возврата средств.
Реальный частный случай: если вы изменяете подсказку (структуру, правила), вам нужно очистить кэш. Отсюда и небольшое поле. v в хэше.
Почему используется функция wp_kses_post(), а не sanitize_text_field()?
sanitize_text_field() Это разрушает HTML-код. Однако описание в WooCommerce должно содержать списки, жирный текст и абзацы. На практике, wp_kses_post() Это правильный компромисс: HTML разрешен для одной публикации, скрипты удалены.
Я до сих пор вижу сайты, которые вставляют "сырой" ответ ИИ в post_contentЕсли ИИ обнаружит подозрительную ссылку или необычный HTML-код, вы откроете ненужную дверь. Это не "автоматическая" XSS-атака (WordPress уже в некоторых местах фильтрует такие атаки), но вы сами себе усложняете задачу.
Почему такая короткая пауза?
В административной панели 60-секундный тайм-аут блокирует пользовательский интерфейс и приводит к зависанию WordPress. 20–30 секунд — это хороший максимум. Если у вас очень сложные продукты, рассмотрите возможность использования асинхронной генерации (cron/queue) или изучите расширенные параметры.
Стоимость и оптимизация API
Стоимость зависит от модели и количества токенов. Типичная страница продукта (запрос + ответ) может стоить примерно... от 800 до 2000 токенов В зависимости от количества атрибутов и требуемой длины. В случае «мини»-модели стоимость генерации часто очень низкая, но в больших масштабах это имеет значение.
Простая оценка (которая будет адаптирована к вашей модели)
- Предположим, что на один продукт приходится 1200 токенов (вход + выход).
- 1000 товаров в месяц → 1,2 млн токенов в месяц.
- Цена на "мини"-модель обычно приемлемая, но если вы регенерируете (без кэширования) вы быстро умножаете результат на 3 или 10.
Оптимизации, которые действительно работают
- Кэширование по отпечатку пальца (уже реализовано): это наилучшая окупаемость инвестиций.
- Ограничить вывод с
max_output_tokens: позволяет избежать длинных ответов. - Меньшая модель Для оптовых закупок более мощная модель подходит только для продукции премиум-класса.
- Партия (расширенный вариант): обработка 50 товаров в ночном cron с ежедневным бюджетом.
- Сократите подсказку Не отправляйте пустые атрибуты, а также существующее описание, если оно пустое.
Расширенные варианты и сценарии использования
Вариант 1: Сгенерировать только краткое описание (отрывок).
Это полезно, когда ваше длинное описание уже написано (или предоставлено производителем), но вам нужен удобный для продаж и единообразный вариант для страниц категорий.
- Измените запрос таким образом, чтобы он запрашивал только следующее:
short_html. - В
bpcab_update_product_descriptions()только обновлениеpost_excerpt.
Вариант 2: Асинхронная генерация (избегание тайм-аутов)
На медленных хостинг-серверах внешний вызов может занять более 25 секунд. В этом случае запустите отложенную задачу:
- AJAX: регистрирует «запрос» (метаданные POST) и немедленно отвечает.
- Задание cron (WP-Cron или cron-сервер) обрабатывает запросы в очереди, по 5 запросов в минуту.
Я не выкладываю здесь весь код, чтобы его можно было скопировать, но суть в том, чтобы Не выполняйте вызов ИИ в запросе администратора. если ваша инфраструктура нестабильна.
Вариант 3: совместимость с Divi 5 / Elementor / Avada
Генерация происходит на стороне WooCommerce, поэтому совместимость сохраняется. Самое интересное: отображение «значка» или блока «Основные моменты» на странице товара с помощью вашего конструктора.
- Дива 5 Вы можете создать модуль, который будет считывать
post_excerptили сгенерированное ИИ метаполе «сильные стороны». Если вы останетесь наpost_content/post_excerptВ Divi нет ничего особенного, чем бы она занималась. - Elementor Воспользуйтесь виджетом «Краткое описание товара», и оно отобразится. автоматически Обновленный отрывок.
- Авада Компонент WooCommerce «Содержание товара» / «Краткое описание товара» отображает эти поля без изменений.
Совет, который я часто использую: попросите ИИ составить список. <ul> Включите преимущества в подробное описание, а затем оформите этот список с помощью темы/конструктора. Вы получите визуальную согласованность без дополнительного кодирования.
Безопасность и передовой опыт
Никогда не раскрывайте ключ API на стороне клиента.
Администратор JavaScript просто звонит. admin-ajax.phpКлюч остается в wp-config.phpЕсли вы поместите ключ в скрипт, даже в административной панели, он в конечном итоге будет скопирован куда-нибудь (в кэш, расширения браузера, прокси).
Проверить и ограничить
- Обработка и услуги :
edit_productминимум. Не допускайте, чтобы неконтролируемая роль "shop_manager" запускала 10 000 поколений. - данное время : уже внедрена система предотвращения CSRF-запросов.
- Ограничение скорости : 30-секундный временный интервал на пользователя и продукт. Для большего увеличения добавьте суточную квоту (временный «счетчик»).
Очистка ответа ИИ
wp_kses_post() Это минимальный набор требований. Если вы хотите быть строже, вы можете передать список разрешенных тегов. wp_kses() и отклонять любые внешние ссылки.
GDPR / отправленные данные
Не отправляйте персональные данные (имя клиента, адрес и т. д.). Здесь мы отправляем только данные о товаре. Если вы планируете персонализировать описания для пользователя, вы попадаете в более конфиденциальный контекст GDPR (правовое основание, субподрядчик, орган по защите данных и т. д.).
Реалистичные ошибки, которых следует избегать.
- Копирование кода не в то место. : фрагмент текста, вставленный в
functions.phpВ следующем обновлении родительская тема будет удалена. - Забудьте про точку с запятой. : единичная ошибка PHP → белый экран в админке. Протестировано на тестовом сервере.
- Неподходящий зацеп : триггер включен
initousave_postбез мер безопасности → непреднамеренные вызовы ИИ. - Тестирование в производственной среде без резервного копирования. Вы можете перезаписывать существующие описания сразу для нескольких пользователей.
- PHP слишком устарел. Некоторые из приведенных выше вариантов синтаксиса/типизации предполагают использование PHP 8.1 и выше. В версии 7.4 это приводит к ошибкам.
Как тестировать и отлаживать
1) Включите корректное ведение журнала.
В wp-config.php (на этапе подготовки) активировать:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
Затем вы прочитаете wp-content/debug.logОфициальная документация: Отладка в WordPress.
2) Сначала протестируйте на «простом» продукте.
- Чёткий заголовок
- заполнено 2–3 атрибута
- Категория А
Если это сработает, перейдите к продукту с переменными характеристиками и множеством атрибутов. Слишком длинные запросы увеличивают риск получения несоответствующего (или усеченного) результата.
3) Проверьте AJAX-запрос
- Откройте браузер DevTools → Сеть →
admin-ajax.php. - Посмотрите на HTTP-код (200/403/500) и возвращенный JSON.
4) Проверьте ответ API.
Если возникают ошибки форматирования JSON, записывайте в лог только фрагменты (это уже сделано с помощью...). substr()Не записывайте весь текст запроса, если в него включена конфиденциальная информация.
Если это не сработает
Вот диагностическая таблица, которую я часто использую при устранении неполадок, связанных с подобными интеграциями.
| симптом | Причина вероятна | проверка | Решение |
|---|---|---|---|
| Кнопка «Сгенерировать» ничего не делает. | JavaScript не загружен на экране товара. | Отсутствие консоли/сети инструментов разработчика. bpcab-ai-woo-admin.js |
Проверить admin_enqueue_scriptsпуть plugins_url()и что файл JS существует |
| Ошибка 403 «Неверный Nonce» | Уведомление не отправлено или страница слишком старая. | Сеть → Полезная нагрузка POST содержит nonce ? |
Перезагрузите страницу товара, проверьте. wp_localize_script и действия нунция |
| Ошибка 500 «Отсутствует ключ API» | Постоянно отсутствует / пусто | Проверить wp-config.php и окружающая среда |
Определять BPCAB_OPENAI_API_KEYПри необходимости очистите кэш операций. |
| Ошибка HTTP 401/403 на стороне API ИИ. | Недействительный ключ, несанкционированный проект | Логи: “API non-200” + код | Сгенерируйте новый ключ, проверьте права доступа к проекту на стороне поставщика. |
| HTTP 429 | Превышена квота / лимит тарифа поставщика | Журналы + панель управления поставщика | Добавить функцию задержки/повторной попытки, снизить тактовую частоту, включить кэширование и использовать более облегченную модель. |
| «Неожиданный формат ИИ» | Искусственный интеллект возвращает текст, не являющийся JSON-файлом. | Частичный лог выходных данных | Сделайте требования более строгими, снизьте temperatureУменьшите требуемую длину |
| Описания были удалены «случайным образом». | Тестирование в рабочей среде + многократные клики + автосохранение | История изменений / журналы | Работа над подготовкой, добавление более длительной блокировки (временной), требование подтверждения. |
Две распространённые ошибки
- Сниппет не работает из-за плагина для сниппетов. Если вы используете плагин типа "Фрагменты кода", ошибка синтаксического анализа может отключить фрагмент кода, что приведет к нестабильному состоянию административного интерфейса. Плагин mu-plugin более стабилен для такого типа кода.
- Скрытый конфликт На некоторых сайтах используется агрессивный кэш постоянных объектов. Временные данные могут передаваться между средами, если они неправильно настроены. Если вы видите описания, которые не соответствуют действительности, начните с временного отключения кэша объектов в тестовой среде.
Ресурсы
- WordPress: wp_remote_post()
- WordPress: API временных данных
- WordPress: wp_kses_post()
- WordPress: Нонсы
- Исходный код WordPress (зеркало на GitHub)
- WordPress Core Trac
- OpenAI: Ответы API (справочник)
- PHP: json_decode()
- WooCommerce на WordPress.org
FAQ
Работает ли это с вариативными товарами (вариантами)?
Да, но приведенный выше код генерирует описание родительский продуктЕсли вам нужен текст для каждого варианта, вам придётся перебрать все варианты и сохранить их в отдельных метатегах (и адаптировать отображение). Я редко так делаю: это затратно и часто излишне с точки зрения SEO.
Могу ли я запретить ИИ упоминать цену?
Да: удалите поле price Относительно содержимого запроса и соответствующей строки в приглашении. Я рекомендую не указывать цены, если вы часто проводите рекламные акции, иначе текст устареет.
Почему запрашивается вывод в формате JSON, а не в простом HTML?
Потому что вам нужны два поля (длинное + короткое) и надежный парсинг. JSON уменьшает количество случаев, когда ИИ добавляет предложения некорректного формата. А когда возникает ошибка, вы видите ее немедленно (ошибка «bad_format»).
Могу ли я вместо этого использовать Anthropic, Mistral или Google?
Да. Сохраняйте ту же архитектуру (AJAX-администрирование → wp_remote_post() → разобрать → wp_kses_post() → обновление). Изменяются только конечная точка и формат JSON. Если вы укажете точный провайдер, я смогу предоставить вам функцию. bpcab_call_* эквивалент.
Существует ли риск создания дублированного контента?
Если ваши товары очень похожи (одинаковые характеристики, почти идентичные названия), ИИ может создавать похожие тексты. Чтобы ограничить это: добавьте к запросу ограничение «отличаются по применению» и укажите уникальный элемент (бренд, материал, основное преимущество). Но в случае однородных каталогов некоторая степень сходства неизбежна.
Как предотвратить изобретение новых функций искусственным интеллектом?
Полностью избежать этого невозможно. Риск значительно снижается за счет:
- Явный запрет на данное изобретение (уже сделано).
- Снижение
temperature(0.3-0.6). - Предоставление только структурированных данных (атрибутов) и избегание неоднозначных «примечаний».
У меня возникает ошибка "Неожиданный формат ИИ". Что мне следует сделать в первую очередь?
Уменьшение сложности: меньше атрибутов, max_output_tokens Если данные обрезаются, появляется еще более строгий запрос («Возвращать только JSON, без текста до/после»). По моему опыту, в 9 случаях из 10 это просто вывод не в формате JSON.
Почему на кнопке написано «ОК», но на лицевой стороне ничего не видно?
Часто проблема заключается в том, что кэш страниц (или кэш объектов) использует более старую версию. Очистите кэш и убедитесь, что ваша тема отображается корректно. the_content() и стандартный фрагмент кода WooCommerce. В Elementor/Avada/Divi убедитесь, что вместо стандартного поля не отображается пользовательское поле.
Можно ли предварительно просмотреть описание до того, как оно будет перезаписано?
Да: вместо звонка wp_update_post()отправить обратно long_html et short_html В ответе AJAX отобразите их в модальном окне, а затем добавьте вторую кнопку «Применить». Это версия, которую я использую в магазинах, где редакторами являются несколько человек.
Может ли создание большого количества таких запросов навредить моему SEO?
Если вы публикуете 500 сгенерированных описаний товаров без проверки орфографии и грамматики, вы рискуете качеством (а значит, и SEO-показанием). Оптимальный рабочий процесс выглядит так: генерация с помощью ИИ → проверка человеком → публикация. А для стратегически важных товаров их нужно переписывать.