หากคุณเคยเสียเวลา 40 นาทีในการเขียนคำอธิบายสินค้า แต่ได้ผลลัพธ์ที่ไม่ค่อยน่าสนใจ คุณจะต้องชอบขั้นตอนการทำงานนี้: WooCommerce จะสร้างคำอธิบายแบบ "ยาว" และคำอธิบายแบบ "สั้น" จากชื่อสินค้า คุณสมบัติ และหมายเหตุทางธุรกิจเล็กน้อย — และคุณยังสามารถควบคุมการตรวจสอบความถูกต้องได้เอง

ความต้องการ / กรณีการใช้งาน

ในแคตตาล็อกของ WooCommerce การสร้างเนื้อหามักเป็นคอขวด ฉันเคยเห็นร้านค้าที่มีสินค้า 300 รายการอยู่ในสถานะ "รอดำเนินการ" เพียงเพราะขาดคำอธิบาย แม้ว่ารูปภาพและราคาจะพร้อมแล้วก็ตาม ผลที่ตามมาคือ SEO แย่ลง อัตราการแปลงลูกค้าลดลง และทีมงานต้องเลื่อนการเปิดตัวออกไป

AI มีประโยชน์ในที่นี้ในการสร้างร่างแรกที่มีความสอดคล้อง มีโครงสร้าง และมุ่งเน้นผลกำไร จากข้อมูลที่มีอยู่แล้ว WordPress : ชื่อเรื่อง หมวดหมู่ คุณลักษณะ แบรนด์ ขนาด วัสดุ ฯลฯ เป้าหมายไม่ใช่การใช้ระบบอัตโนมัติโดยไม่คิดไตร่ตรอง แต่เป็นการลดเวลาในการเขียนต่อผลิตภัณฑ์ ในขณะที่ยังคงรักษาการควบคุมด้านบรรณาธิการไว้

สุดท้ายแล้ว คุณจะรู้วิธีนำไปใช้:

  • ปุ่ม “สร้างด้วย AI” ในหน้าแก้ไขสินค้า (ส่วนผู้ดูแลระบบ WooCommerce)
  • การเรียกใช้ API ของ AI ผ่านทาง wp_remote_post() (Sans SDK) พร้อมระบบจัดการเวลาหมดและข้อผิดพลาด
  • ใช้แคชเพียงหนึ่งครั้งต่อข้อมูลชั่วคราว เพื่อหลีกเลี่ยงการจ่ายค่าบริการสำหรับข้อมูลชุดเดียวกันซ้ำอีก
  • การอัปเดตที่ปลอดภัยของ คำอธิบายโดยละเอียด et คำอธิบายโดยย่อ (ส่วนหนึ่ง) จากผลิตภัณฑ์
  • ระบบจำกัดอัตราค่าบริการแบบง่ายๆ เพื่อหลีกเลี่ยงอัตราค่าบริการที่พุ่งสูงขึ้น (และบิลค่าบริการที่ไม่คาดคิด)

สรุปด่วน

  • เราเพิ่มเมตาบ็อกซ์ของ WooCommerce พร้อมปุ่มที่เรียกใช้งานคำขอ AJAX สำหรับผู้ดูแลระบบ
  • เซิร์ฟเวอร์จะสร้างข้อความแจ้งเตือนจากข้อมูลผลิตภัณฑ์ (คุณลักษณะ หมวดหมู่ ฯลฯ)
  • การโทรด้วย AI wp_remote_post()กำหนดเวลาหมดอายุสั้น จำกัดจำนวนครั้งในการลองใหม่ และบันทึกข้อผิดพลาด
  • ตอบกลับแบบสะอาดด้วย wp_kses_post() ก่อนสอดใส่เข้าไป post_content et post_excerpt.
  • ใช้ Transients API ในการแคชเพื่อหลีกเลี่ยงการสร้างแคชใหม่หากไม่มีอะไรเปลี่ยนแปลง
  • คีย์ API ถูกจัดเก็บไว้แล้ว wp-config.php (ไม่เคยเขียนโค้ดแบบตายตัว ไม่เคยเขียนในฝั่ง JavaScript)

ควรใช้ AI สำหรับเรื่องนั้นเมื่อใด

ใช้ AI เมื่อคุณมีข้อมูลปริมาณมาก โครงสร้างข้อมูลที่เชื่อถือได้ และต้องการความสม่ำเสมอ โดยทั่วไป:

  • ร้านค้าหลายผู้ขาย โดยที่ข้อมูลจะเข้ามาในรูปแบบข้อมูลดิบ (CSV, ERP) ซึ่งมีข้อความน้อยมาก
  • แคตตาล็อกที่มีความหลากหลาย (ขนาด/สี) ที่คุณต้องการคำอธิบายที่เป็นมาตรฐาน และจัดการความแตกต่างด้วยคุณลักษณะ
  • SEO “พื้นฐาน” : สร้างข้อความที่สะอาดตา อ่านง่าย และมีโครงสร้างที่มั่นคง (ย่อหน้า รายการ) จากนั้นจึงปรับแต่งผลลัพธ์เชิงกลยุทธ์ให้ดียิ่งขึ้น
  • ความเป็นสากล (เวอร์ชันขั้นสูง): สร้างเวอร์ชันภาษาอังกฤษ/เยอรมัน/สเปนจากฐานข้อมูลภาษาฝรั่งเศส โดยมีการตรวจสอบความถูกต้องโดยมนุษย์

ในสถานการณ์จริง สิ่งที่ได้ผลดีคือการสร้างผลิตภัณฑ์เมื่อมีการสร้างผลิตภัณฑ์นั้นขึ้นมา จากนั้นจึงสร้างใหม่เฉพาะเมื่อฟิลด์บางอย่างเปลี่ยนแปลง (คุณลักษณะ หมวดหมู่ แบรนด์) การแคชข้อมูลโดยใช้ "ลายนิ้วมือ" ของผลิตภัณฑ์จะช่วยได้มาก

เมื่อใดที่ไม่ควรใช้ AI

ควรหลีกเลี่ยงการใช้ AI หากวิธีการแบบดั้งเดิมมีความเสถียรกว่าหรือมีความเสี่ยงน้อยกว่า:

  • คำอธิบายที่ถูกต้องตามกฎหมายอย่างเคร่งครัด (เครื่องสำอาง อาหารเสริม ยา): ความเสี่ยงต่อการเกิดภาพหลอนมีอยู่จริง ควรใช้เทมเพลต PHP ที่มีฟิลด์/แอตทริบิวต์ ACF หรือฐานข้อมูลที่ได้รับการตรวจสอบความถูกต้องตามกฎหมาย
  • ผลิตภัณฑ์ที่ใช้เทคโนโลยีขั้นสูง แม้แต่ข้อผิดพลาดเพียงเล็กน้อยก็อาจส่งผลให้เกิดการส่งคืนสินค้าเพื่อขอรับบริการจากฝ่ายบริการลูกค้า (ความเข้ากันได้ มาตรฐาน) คุณยังสามารถใช้ AI ได้ แต่เฉพาะในข้อความแจ้งเตือนที่ล็อกไว้และมีการตรวจสอบความถูกต้องที่จำเป็นเท่านั้น
  • เว็บไซต์ที่มีข้อจำกัดด้านต้นทุนอย่างเข้มงวดมาก หากคุณมีสินค้า 20,000 รายการ และสร้างรายการใหม่บ่อยครั้ง ค่าใช้จ่าย API จะเพิ่มขึ้นอย่างรวดเร็วหากคุณไม่ใช้การแคชและการประมวลผลแบบกลุ่ม
  • เมื่อข้อมูลต้นทางของคุณไม่ดี (ชื่อเรื่องไม่สอดคล้องกัน คุณสมบัติว่างเปล่า) AI จะ "สร้าง" ข้อมูลขึ้นมาเพื่อเติมเต็มช่องว่าง ในกรณีนี้ ให้เริ่มต้นด้วยการทำความสะอาดแคตตาล็อก

รูปแบบที่ไม่พึงประสงค์ที่พบได้บ่อยคือ การเรียกใช้งานการสร้างหน้าเว็บทุกครั้งที่มีการบันทึกอัตโนมัติ คุณจะลงเอยด้วยการเรียกใช้ API หลายสิบครั้งสำหรับสินค้าชิ้นเดียว เนื่องจาก Gutenberg บันทึกอัตโนมัติ WooCommerce อัปเดตเมตาแท็ก และ hook ของคุณจะถูกเรียกใช้งานแบบต่อเนื่อง

ข้อกำหนดเบื้องต้น

ภายในเดือนเมษายน 2026 ตั้งเป้าหมายอย่างน้อยดังนี้:

  • WordPress 6.9.4 (บริบทของคุณ) และ PHP ฮิต +.
  • 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') ?: '');

เอกสารอ้างอิงอย่างเป็นทางการที่เป็นประโยชน์

สถาปัตยกรรมโซลูชัน

ผังการไหล (แผนผังข้อความ):

การจัดการผลิตภัณฑ์ (ปุ่มสร้าง) → การจัดการ AJAX (nonce + ความสามารถ) → การดึงข้อมูลผลิตภัณฑ์ → การคำนวณแฮช → แคชชั่วคราว? → อย่างอื่น wp_remote_post() → API AI → การแยกวิเคราะห์ JSON → การตรวจสอบความปลอดภัย (wp_kses_post()) → อัปเดตผลิตภัณฑ์ (เนื้อหา + บทคัดย่อ) → ส่งกลับ JSON ไปยังผู้ดูแลระบบ

เกิดอะไรขึ้นเบื้องหลัง

  • ผู้ดูแลระบบ UI กล่องเมตาที่มีปุ่มและช่อง "หมายเหตุ" ที่เป็นตัวเลือก โค้ด JavaScript ไม่มีการใส่คีย์ API เลย
  • จุดสิ้นสุด AJAX : รักษาความปลอดภัยด้วย nonce + current_user_can('edit_product', $product_id).
  • รวดเร็ว สร้างขึ้นจากองค์ประกอบที่เชื่อถือได้ (แอตทริบิวต์ หมวดหมู่ แท็ก และราคาจาก WooCommerce หากต้องการ)
  • แคช : ข้อมูลชั่วคราวที่สร้างขึ้นจากค่าแฮชของข้อมูล หากไม่มีการเปลี่ยนแปลงใดๆ ระบบจะส่งคืนคำอธิบายที่สร้างไว้แล้ว
  • การรักษาสุขอนามัย AI สามารถส่งคืน HTML ได้ เฉพาะสิ่งที่ WordPress อนุญาตในโพสต์เท่านั้นที่จะได้รับอนุญาตผ่านทาง AI 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) โหลดสคริปต์ JavaScript เฉพาะในหน้าจอผลิตภัณฑ์เท่านั้น

ข้อผิดพลาดที่ผมเห็นบ่อยคือ การเรียกใช้สคริปต์ทุกที่ในแผงควบคุมผู้ดูแลระบบ วิธีนี้ทำให้ระบบทำงานช้าลงโดยไม่จำเป็น และอาจทำให้เกิดความขัดแย้งกับปลั๊กอินอื่นๆ ได้

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 หากมีให้บริการ มิเช่นนั้นเราจะแสดงข้อผิดพลาดที่ชัดเจน อย่าสับสนสิ่งนี้กับ hook 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) เรียกใช้ AI 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 (เช่น hooks, การแก้ไขหากเปิดใช้งาน ฯลฯ)

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) แทนที่จะใช้ hook อัตโนมัติ?

ใน WooCommerce สินค้าสามารถบันทึกได้หลายครั้งโดยไม่ต้องคลิก "อัปเดต" เช่น การบันทึกอัตโนมัติ การแก้ไข การอัปเดตข้อมูลจากปลั๊กอินอื่น การซิงค์สินค้าคงคลัง ฯลฯ หากคุณเชื่อมต่อ AI เข้ากับระบบ 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 สำหรับโพสต์เดียว และลบสคริปต์ออก

ฉันยังคงเห็นเว็บไซต์ที่นำเอาคำตอบจาก AI แบบ "ดิบๆ" มาใส่ไว้ post_contentหาก AI ตรวจพบลิงก์ที่น่าสงสัยหรือโค้ด HTML ที่ผิดปกติ นั่นหมายความว่าคุณได้เปิดประตูสู่ช่องโหว่ที่ไม่จำเป็นแล้ว นี่ไม่ใช่การโจมตี XSS แบบ "อัตโนมัติ" (WordPress มีระบบกรองอยู่แล้วในบางส่วน) แต่คุณกำลังทำให้เรื่องยุ่งยากขึ้นสำหรับตัวเอง

ทำไมต้องขอเวลานอกสั้นๆ?

ในส่วนผู้ดูแลระบบ การตั้งค่าหมดเวลา 60 วินาทีจะบล็อก UI และทำให้ 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: บันทึก "คำขอ" (ข้อมูลเมตาของโพสต์) และตอบกลับทันที
  • งาน Cron (WP-Cron หรือเซิร์ฟเวอร์ Cron) จะประมวลผลคำขอในคิว โดยประมวลผลครั้งละ 5 คำขอต่อนาที

ฉันไม่ได้ใส่โค้ดทั้งหมดไว้ที่นี่เพื่อให้สามารถคัดลอกได้ง่าย แต่ประเด็นสำคัญคือ อย่าเรียกใช้ AI ในคำขอของผู้ดูแลระบบ หากโครงสร้างพื้นฐานของคุณไม่เสถียร

ตัวเลือกที่ 3: ความเข้ากันได้กับ Divi 5 / Elementor / Avada

กระบวนการสร้างลิงก์เกิดขึ้นในฝั่ง WooCommerce ดังนั้นจึงยังคงใช้งานร่วมกันได้ ส่วนที่น่าสนใจคือ การแสดง "ป้ายกำกับ" หรือ "บล็อกไฮไลท์" บนหน้าสินค้าผ่านเครื่องมือสร้างสินค้าของคุณ

  • Divi 5 คุณสามารถสร้างโมดูลที่อ่านข้อมูลได้ post_excerpt หรือฟิลด์เมตา "จุดแข็ง" ที่สร้างขึ้นโดย AI หากคุณยังคงใช้งานต่อไป post_content/post_excerptดิวิไม่มีอะไรพิเศษให้ทำ
  • Elementor ใช้ช่อง "คำอธิบายสินค้าแบบย่อ" แล้วมันจะแสดงขึ้นมา อัตโนมัติ ข้อความที่ตัดตอนมาฉบับปรับปรุงแล้ว
  • Avada : คอมโพเนนต์ WooCommerce “เนื้อหาผลิตภัณฑ์” / “คำอธิบายผลิตภัณฑ์” แสดงฟิลด์เหล่านี้โดยไม่ต้องปรับแต่งใดๆ

เคล็ดลับที่ผมใช้บ่อยคือ: ขอให้ AI สร้างรายการให้ <ul> ระบุข้อดีในคำอธิบายแบบยาว จากนั้นจัดรูปแบบรายการนี้โดยใช้ธีม/เครื่องมือสร้าง คุณจะได้รับความสอดคล้องทางด้านภาพโดยไม่ต้องเขียนโค้ดเพิ่มเติม

ความปลอดภัยและแนวทางปฏิบัติที่ดีที่สุด

ห้ามเปิดเผยคีย์ API ทางฝั่งไคลเอ็นต์เด็ดขาด

ผู้ดูแลระบบ JS กำลังโทรอยู่ admin-ajax.phpกุญแจยังคงอยู่ที่ wp-config.phpหากคุณใส่คีย์ไว้ในสคริปต์ แม้แต่ในแผงควบคุมผู้ดูแลระบบ คีย์นั้นก็จะถูกคัดลอกไปยังที่ใดที่หนึ่ง (แคช ส่วนขยายเบราว์เซอร์ พร็อกซี) ในที่สุด

ตรวจสอบและจำกัด

  • ความสามารถในการ : edit_product อย่างน้อยที่สุด อย่าปล่อยให้บทบาท "shop_manager" ที่ควบคุมไม่ได้ก่อให้เกิดการสร้างโมเดลถึง 10,000 รุ่น
  • nonce : มีการตั้งค่าไว้แล้วเพื่อป้องกันคำขอ CSRF
  • อัตราจำกัด : จำกัดเวลาการใช้งานชั่วคราว 30 วินาทีต่อผู้ใช้และผลิตภัณฑ์ หากต้องการใช้งานเพิ่มเติม ให้เพิ่มโควต้าต่อวัน (ตัวนับการใช้งานชั่วคราว)

กรองการตอบสนองของ AI ให้บริสุทธิ์

wp_kses_post() เป็นค่าต่ำสุด หากคุณต้องการกำหนดเงื่อนไขที่เข้มงวดกว่านี้ คุณสามารถส่งรายการแท็กที่อนุญาตไปยังได้ wp_kses() และปฏิเสธลิงก์ภายนอกใดๆ

GDPR / ข้อมูลที่ส่ง

ห้ามส่งข้อมูลส่วนบุคคล (ชื่อลูกค้า ที่อยู่ ฯลฯ) ที่นี่เราส่งเฉพาะข้อมูลผลิตภัณฑ์เท่านั้น หากคุณวางแผนที่จะปรับแต่งคำอธิบายสำหรับผู้ใช้ คุณจะเข้าสู่บริบท GDPR ที่ละเอียดอ่อนยิ่งขึ้น (พื้นฐานทางกฎหมาย ผู้รับเหมาช่วง หน่วยงานคุ้มครองข้อมูลส่วนบุคคล ฯลฯ)

ข้อผิดพลาดที่ควรหลีกเลี่ยงอย่างสมจริง

  • คัดลอกโค้ดไปวางผิดที่ : ข้อความที่คัดลอกมาวาง functions.php ธีมหลักจะหายไปในการอัปเดตครั้งต่อไป
  • ลืมเครื่องหมายเซมิโคลอนไปได้เลย : ข้อผิดพลาด PHP เพียงครั้งเดียว → หน้าจอว่างเปล่าในหน้าผู้ดูแลระบบ ทดสอบบนระบบทดสอบ (staging)
  • ตะขอที่ไม่เหมาะสม : ทริกเกอร์บน init ou save_post หากไม่มีมาตรการป้องกัน → การโทรจาก AI โดยไม่ตั้งใจ
  • ทดสอบในสภาพแวดล้อมการใช้งานจริงโดยไม่มีการสำรองข้อมูล คุณสามารถเขียนทับคำอธิบายที่มีอยู่แล้วได้ทีละหลายรายการ
  • 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()อย่าบันทึกข้อความแจ้งเตือนทั้งหมดหากคุณใส่ข้อมูลที่ละเอียดอ่อนลงไป

ถ้าวิธีนั้นไม่ได้ผล

นี่คือแผนภูมิการวินิจฉัยที่ผมใช้บ่อยมากในการแก้ไขปัญหาการเชื่อมต่อระบบประเภทนี้

อาการ สาเหตุที่เป็นไปได้ การตรวจสอบ Solution
ปุ่ม "สร้าง" ไม่มีการทำงานใดๆ JavaScript ไม่โหลดในหน้าจอผลิตภัณฑ์ DevTools Console/Network, การขาดหายไปของ 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ล้างแคช opcache หากจำเป็น
ข้อผิดพลาด HTTP 401/403 บนฝั่ง AI API คีย์ไม่ถูกต้อง โครงการไม่ได้รับอนุญาต บันทึก: “API ไม่ใช่รหัส 200” + รหัส สร้างคีย์ใหม่ ตรวจสอบสิทธิ์การเข้าถึงโปรเจ็กต์ทางฝั่งผู้ให้บริการ
HTTP 429 โควต้าเกินกำหนด / อัตราค่าบริการของซัพพลายเออร์สูงสุด บันทึกข้อมูล + แดชบอร์ดซัพพลายเออร์ เพิ่มฟังก์ชันการหน่วงเวลา/ลองใหม่ ลดความเร็วสัญญาณนาฬิกา เปิดใช้งานการแคช และใช้โมเดลที่มีน้ำหนักเบากว่า
“รูปแบบ AI ที่ไม่คาดคิด” AI ส่งคืนข้อความที่ไม่ใช่ JSON บันทึกผลลัพธ์บางส่วน ทำให้ข้อความแจ้งเตือนเข้มงวดขึ้นหรือต่ำลง temperatureลดความยาวที่ร้องขอลง
คำอธิบายถูกบดขยี้ "แบบสุ่ม" ทดสอบบนระบบใช้งานจริง + การคลิกหลายครั้ง + การบันทึกอัตโนมัติ ประวัติการแก้ไข / บันทึก ดำเนินการจัดเตรียมอุปกรณ์ เพิ่มการล็อกที่ยาวขึ้น (ชั่วคราว) และขอการยืนยัน

สองข้อผิดพลาดที่พบได้บ่อย

  • โค้ดตัวอย่างเสียหายเนื่องจากปลั๊กอินโค้ดตัวอย่าง หากคุณใช้ปลั๊กอินประเภท "โค้ดตัวอย่าง" ข้อผิดพลาดในการวิเคราะห์อาจทำให้โค้ดตัวอย่างใช้งานไม่ได้ ส่งผลให้หน้าจอแสดงผลสำหรับผู้ดูแลระบบอยู่ในสถานะที่ไม่เสถียร ปลั๊กอิน mu-plugin มีความเสถียรมากกว่าสำหรับโค้ดประเภทนี้
  • ความขัดแย้งที่ซ่อนเร้น บางเว็บไซต์มีการแคชวัตถุแบบถาวรที่ค่อนข้างเข้มงวด หากตั้งค่าไม่ถูกต้อง วัตถุชั่วคราวอาจถูกแชร์ระหว่างสภาพแวดล้อมต่างๆ ได้ หากคุณพบคำอธิบายที่ไม่ตรงกัน ให้เริ่มต้นด้วยการปิดใช้งานแคชวัตถุชั่วคราวในสภาพแวดล้อมทดสอบ

ทรัพยากร

คำถามที่พบบ่อย

ใช้งานได้กับสินค้าที่มีตัวเลือกหลากหลาย (หลายแบบ) หรือไม่?

ใช่ แต่โค้ดด้านบนสร้างคำอธิบายของ ผลิตภัณฑ์หลักถ้าคุณต้องการข้อความสำหรับแต่ละเวอร์ชัน คุณต้องวนซ้ำไปตามเวอร์ชันต่างๆ และจัดเก็บไว้ในเมตาแท็กเฉพาะ (และปรับการแสดงผล) โดยส่วนตัวแล้วฉันไม่ค่อยทำแบบนี้ เพราะมันมีค่าใช้จ่ายสูงและมักไม่จำเป็นจากมุมมอง SEO

ฉันสามารถป้องกันไม่ให้ AI พูดถึงราคาได้หรือไม่?

ใช่: ลบช่องข้อมูลนั้นออก price ของข้อมูลที่ส่งและบรรทัดที่เกี่ยวข้องในข้อความแจ้งเตือน ฉันแนะนำว่าอย่าใส่ราคาหากคุณจัดโปรโมชั่นบ่อยๆ มิเช่นนั้นข้อความของคุณจะล้าสมัย

เหตุใดจึงขอผลลัพธ์ในรูปแบบ JSON แทนที่จะเป็น HTML ธรรมดา?

เนื่องจากคุณต้องการสองฟิลด์ (แบบยาว + แบบสั้น) และการแยกวิเคราะห์ที่มีประสิทธิภาพ JSON ช่วยลดกรณีที่ AI เพิ่มประโยคที่อยู่นอกรูปแบบ และเมื่อเกิดข้อผิดพลาด คุณจะเห็นได้ทันที (ข้อผิดพลาด "bad_format")

ฉันสามารถใช้ Anthropic, Mistral หรือ Google แทนได้ไหม?

ใช่ค่ะ คงโครงสร้างเดิมไว้ (AJAX admin → wp_remote_post() → วิเคราะห์ → wp_kses_post() → อัปเดต) เฉพาะปลายทางและรูปแบบ JSON เท่านั้นที่เปลี่ยนแปลง หากคุณแจ้งผู้ให้บริการที่แน่นอนมา ฉันสามารถจัดหาฟังก์ชันให้คุณได้ bpcab_call_* เทียบเท่า

การทำเช่นนี้มีความเสี่ยงที่จะทำให้เกิดเนื้อหาซ้ำซ้อนหรือไม่?

หากผลิตภัณฑ์ของคุณมีความคล้ายคลึงกันมาก (คุณสมบัติเหมือนกัน ชื่อสินค้าเกือบเหมือนกัน) AI อาจสร้างข้อความที่คล้ายกัน เพื่อลดปัญหานี้ ให้เพิ่มข้อจำกัด "แยกความแตกต่างตามการใช้งาน" ในข้อความแจ้งเตือน และใส่รายละเอียดเฉพาะที่ไม่ซ้ำกัน (แบรนด์ วัสดุ ประโยชน์หลัก) แต่สำหรับแคตตาล็อกที่มีสินค้าประเภทเดียวกัน ความคล้ายคลึงกันบ้างเป็นสิ่งที่หลีกเลี่ยงไม่ได้

เราจะป้องกันไม่ให้ AI สร้างฟีเจอร์ใหม่ๆ ได้อย่างไร?

คุณจะไม่สามารถหลีกเลี่ยงความเสี่ยงนี้ได้อย่างสมบูรณ์ แต่คุณสามารถลดความเสี่ยงลงได้อย่างมากโดย:

  • ห้ามการประดิษฐ์ดังกล่าวโดยชัดแจ้ง (ได้ดำเนินการไปแล้ว)
  • ลดระดับลง temperature (-0.3 0.6)
  • ให้ข้อมูลที่มีโครงสร้าง (คุณลักษณะ) เท่านั้น และหลีกเลี่ยง "หมายเหตุ" ที่คลุมเครือ

ฉันได้รับข้อผิดพลาด "รูปแบบ AI ที่ไม่คาดคิด" ฉันควรทำอะไรก่อนดี?

ลดความซับซ้อน: ลดจำนวนคุณลักษณะลง 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 ของคุณ) ขั้นตอนการทำงานที่ดีคือ: การสร้างโดย AI → การตรวจสอบโดยมนุษย์ → การเผยแพร่ และสำหรับผลิตภัณฑ์เชิงกลยุทธ์ คุณควรเขียนใหม่ทั้งหมด