Wildberries Description Generator

Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов

Size

49.0 KB

Version

2.3.1

Created

Jan 13, 2026

Updated

about 1 month ago

1// ==UserScript==
2// @name		Wildberries Description Generator
3// @description		Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов
4// @version		2.3.1
5// @match		https://*.seller.wildberries.ru/*
6// @icon		https://seller.wildberries.ru/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Wildberries Description Generator: Расширение запущено');
12
13    // Добавляем стили для модального окна
14    TM_addStyle(`
15        .wb-desc-modal {
16            position: fixed;
17            top: 0;
18            left: 0;
19            width: 100%;
20            height: 100%;
21            background: rgba(0, 0, 0, 0.5);
22            display: flex;
23            align-items: center;
24            justify-content: center;
25            z-index: 10000;
26        }
27        
28        .wb-desc-modal-content {
29            background: white;
30            border-radius: 12px;
31            padding: 24px;
32            max-width: 600px;
33            width: 90%;
34            max-height: 80vh;
35            overflow-y: auto;
36            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
37        }
38        
39        .wb-desc-modal-header {
40            font-size: 20px;
41            font-weight: 600;
42            margin-bottom: 20px;
43            color: #001a34;
44        }
45        
46        .wb-desc-input-group {
47            margin-bottom: 16px;
48        }
49        
50        .wb-desc-label {
51            display: block;
52            margin-bottom: 8px;
53            font-weight: 500;
54            color: #001a34;
55        }
56        
57        .wb-desc-textarea {
58            width: 100%;
59            min-height: 100px;
60            padding: 12px;
61            border: 1px solid #d1d5db;
62            border-radius: 8px;
63            font-size: 14px;
64            font-family: inherit;
65            resize: vertical;
66        }
67        
68        .wb-desc-textarea:focus {
69            outline: none;
70            border-color: #9333ea;
71        }
72        
73        .wb-desc-result {
74            background: #f3f4f6;
75            padding: 16px;
76            border-radius: 8px;
77            margin-bottom: 16px;
78            max-height: 300px;
79            overflow-y: auto;
80            white-space: pre-wrap;
81            word-wrap: break-word;
82        }
83        
84        .wb-desc-char-count {
85            text-align: right;
86            font-size: 12px;
87            color: #6b7280;
88            margin-top: 4px;
89        }
90        
91        .wb-desc-char-count.warning {
92            color: #f59e0b;
93        }
94        
95        .wb-desc-char-count.error {
96            color: #ef4444;
97        }
98        
99        .wb-desc-buttons {
100            display: flex;
101            gap: 12px;
102            justify-content: flex-end;
103            margin-top: 20px;
104        }
105        
106        .wb-desc-btn {
107            padding: 10px 20px;
108            border: none;
109            border-radius: 8px;
110            font-size: 14px;
111            font-weight: 500;
112            cursor: pointer;
113            transition: all 0.2s;
114        }
115        
116        .wb-desc-btn-primary {
117            background: #9333ea;
118            color: white;
119        }
120        
121        .wb-desc-btn-primary:hover {
122            background: #7e22ce;
123        }
124        
125        .wb-desc-btn-primary:disabled {
126            background: #9ca3af;
127            cursor: not-allowed;
128        }
129        
130        .wb-desc-btn-secondary {
131            background: #e5e7eb;
132            color: #374151;
133        }
134        
135        .wb-desc-btn-secondary:hover {
136            background: #d1d5db;
137        }
138        
139        .wb-desc-btn-success {
140            background: #10b981;
141            color: white;
142        }
143        
144        .wb-desc-btn-success:hover {
145            background: #059669;
146        }
147        
148        .wb-desc-generator-btn {
149            margin-left: 12px;
150            padding: 10px 20px;
151            background: #9333ea;
152            color: white;
153            border: none;
154            border-radius: 8px;
155            font-size: 14px;
156            font-weight: 500;
157            cursor: pointer;
158            transition: all 0.2s;
159        }
160        
161        .wb-desc-generator-btn:hover {
162            background: #7e22ce;
163        }
164        
165        .wb-desc-status {
166            margin-top: 12px;
167            padding: 8px 12px;
168            border-radius: 6px;
169            font-size: 13px;
170        }
171        
172        .wb-desc-status.info {
173            background: #dbeafe;
174            color: #1e40af;
175        }
176        
177        .wb-desc-status.success {
178            background: #d1fae5;
179            color: #065f46;
180        }
181        
182        .wb-desc-status.error {
183            background: #fee2e2;
184            color: #991b1b;
185        }
186    `);
187
188    // Функция для создания кнопки генератора
189    function createGeneratorButton() {
190        const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
191        
192        if (!descriptionHeader) {
193            console.log('Wildberries Description Generator: Заголовок описания не найден');
194            return;
195        }
196        
197        // Проверяем, не добавлена ли уже кнопка
198        if (document.querySelector('.wb-desc-generator-btn')) {
199            console.log('Wildberries Description Generator: Кнопка уже добавлена');
200            return;
201        }
202        
203        const button = document.createElement('button');
204        button.className = 'wb-desc-generator-btn';
205        button.textContent = 'Генератор описаний';
206        button.type = 'button';
207		button.addEventListener('click', function() {
208    // Автоматически открываем ингредиенты
209    const expandButton = document.querySelector('div.Characteristics__expand__570w3PkC7D button');
210    if (expandButton) expandButton.click();
211    
212    // Открываем модальное окно
213    setTimeout(openModal, 500);
214});
215        
216        descriptionHeader.appendChild(button);
217        console.log('Wildberries Description Generator: Кнопка добавлена');
218    }
219
220    // Функция для открытия модального окна
221    function openModal() {
222        
223        console.log('Wildberries Description Generator: Открытие модального окна');
224        
225        const modal = document.createElement('div');
226        modal.className = 'wb-desc-modal';
227        modal.innerHTML = `
228            <div class="wb-desc-modal-content">
229                <div class="wb-desc-modal-header">Генератор описаний для Wildberries</div>
230                
231                <div class="wb-desc-input-group">
232                    <label class="wb-desc-label">Введите ключевые слова (каждое с новой строки):</label>
233                    <textarea class="wb-desc-textarea" id="wb-keywords-input" placeholder="Например:&#10;витамины&#10;иммунитет&#10;здоровье"></textarea>
234                </div>
235                
236                <div class="wb-desc-input-group">
237                    <label class="wb-desc-label">Минус-слова (каждое с новой строки):</label>
238                    <textarea class="wb-desc-textarea" style="min-height: 80px;" id="wb-minus-words-input" placeholder="Например:&#10;mixit&#10;nivea&#10;магнит"></textarea>
239                </div>
240                
241                <div id="wb-desc-result-container" style="display: none;">
242                    <div class="wb-desc-label">Сгенерированное описание:</div>
243                    <div class="wb-desc-result" id="wb-desc-result"></div>
244                    <div class="wb-desc-char-count" id="wb-char-count"></div>
245                </div>
246                
247                <div id="wb-desc-status-container"></div>
248                
249                <div class="wb-desc-buttons">
250                    <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-btn">Закрыть</button>
251                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-generate-btn">Сгенерировать</button>
252                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-btn" style="display: none;">Перегенерировать</button>
253                    <button class="wb-desc-btn wb-desc-btn-success" id="wb-insert-btn" style="display: none;">Вставить в описание</button>
254                </div>
255            </div>
256        `;
257        
258        document.body.appendChild(modal);
259        
260        // Обработчики событий
261        modal.addEventListener('click', (e) => {
262            if (e.target === modal) {
263                modal.remove();
264            }
265        });
266        
267        document.getElementById('wb-close-btn').addEventListener('click', () => {
268            modal.remove();
269        });
270        
271        document.getElementById('wb-generate-btn').addEventListener('click', () => {
272            generateDescription(modal);
273        });
274        
275        document.getElementById('wb-regenerate-btn').addEventListener('click', () => {
276            generateDescription(modal);
277        });
278        
279        document.getElementById('wb-insert-btn').addEventListener('click', () => {
280            insertDescription(modal);
281        });
282    }
283
284    // Функция для сбора данных с аналитики
285    async function collectAnalyticsData(keywords, minusWords) {
286        console.log('Wildberries Description Generator: Начало сбора данных с аналитики');
287        
288        // Сохраняем данные для доступа из другой вкладки
289        await GM.setValue('wb_keywords_to_process', JSON.stringify(keywords));
290        await GM.setValue('wb_minus_words', JSON.stringify(minusWords));
291        await GM.setValue('wb_analytics_data', JSON.stringify([]));
292        await GM.setValue('wb_collection_status', 'pending');
293        
294        // Открываем страницу аналитики в новой вкладке
295        const analyticsUrl = 'https://seller.wildberries.ru/search-analytics/popular-search-queries';
296        await GM.openInTab(analyticsUrl, false);
297        
298        console.log('Wildberries Description Generator: Открыта страница аналитики, ожидание сбора данных...');
299        
300        // Ждем завершения сбора данных (максимум 5 минут)
301        const maxWaitTime = 300000; // 5 минут
302        const checkInterval = 2000; // проверяем каждые 2 секунды
303        let waitedTime = 0;
304        
305        while (waitedTime < maxWaitTime) {
306            await new Promise(resolve => setTimeout(resolve, checkInterval));
307            waitedTime += checkInterval;
308            
309            const status = await GM.getValue('wb_collection_status', 'pending');
310            
311            if (status === 'completed') {
312                const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
313                const analyticsData = JSON.parse(analyticsDataStr);
314                console.log('Wildberries Description Generator: Данные успешно собраны');
315                return analyticsData;
316            } else if (status === 'error') {
317                console.error('Wildberries Description Generator: Ошибка при сборе данных');
318                return [];
319            }
320        }
321        
322        console.error('Wildberries Description Generator: Превышено время ожидания сбора данных');
323        return [];
324    }
325
326    // Функция для автоматического сбора данных на странице аналитики
327    async function autoCollectOnAnalyticsPage() {
328        // Проверяем, что мы на странице аналитики
329        if (!window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
330            return;
331        }
332        
333        console.log('Wildberries Description Generator: Обнаружена страница аналитики');
334        
335        // Проверяем, есть ли задача на сбор данных
336        const status = await GM.getValue('wb_collection_status', 'none');
337        if (status !== 'pending') {
338            return;
339        }
340        
341        console.log('Wildberries Description Generator: Начинаем автоматический сбор данных');
342        
343        try {
344            const keywordsStr = await GM.getValue('wb_keywords_to_process', '[]');
345            const minusWordsStr = await GM.getValue('wb_minus_words', '[]');
346            const keywords = JSON.parse(keywordsStr);
347            const minusWords = JSON.parse(minusWordsStr);
348            
349            const analyticsData = [];
350            
351            // Ждем загрузки страницы
352            await new Promise(resolve => setTimeout(resolve, 3000));
353            
354            for (const keyword of keywords) {
355                console.log(`Wildberries Description Generator: Обработка ключевого слова: ${keyword}`);
356                
357                try {
358                    // Находим поле поиска
359                    const searchInput = document.querySelector('input[name="searchString"]');
360                    if (!searchInput) {
361                        console.error('Wildberries Description Generator: Поле поиска не найдено');
362                        continue;
363                    }
364                    
365                    // Очищаем и вводим ключевое слово
366                    searchInput.value = '';
367                    searchInput.focus();
368                    searchInput.value = keyword;
369                    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
370                    searchInput.dispatchEvent(new Event('change', { bubbles: true }));
371                    
372                    // Ждем загрузки результатов
373                    await new Promise(resolve => setTimeout(resolve, 5000));
374                    
375                    // Собираем данные из таблицы
376                    const rows = document.querySelectorAll('table tbody tr');
377                    const keywordData = {
378                        keyword: keyword,
379                        queries: []
380                    };
381                    
382                    console.log(`Wildberries Description Generator: Найдено строк в таблице: ${rows.length}`);
383                    
384                    rows.forEach(row => {
385                        const cells = row.querySelectorAll('td');
386                        if (cells.length >= 2) {
387                            const query = cells[0]?.textContent?.trim();
388                            const popularity = cells[1]?.textContent?.trim();
389                            
390                            if (query && popularity) {
391                                // Фильтруем по минус-словам
392                                const queryLower = query.toLowerCase();
393                                const hasMinusWord = minusWords.some(minusWord => 
394                                    queryLower.includes(minusWord.toLowerCase())
395                                );
396                                
397                                if (!hasMinusWord) {
398                                    keywordData.queries.push({
399                                        query,
400                                        popularity
401                                    });
402                                } else {
403                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
404                                }
405                            }
406                        }
407                    });
408                    
409                    analyticsData.push(keywordData);
410                    console.log(`Wildberries Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
411                    
412                } catch (error) {
413                    console.error(`Wildberries Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
414                }
415            }
416            
417            // Сохраняем собранные данные
418            await GM.setValue('wb_analytics_data', JSON.stringify(analyticsData));
419            await GM.setValue('wb_collection_status', 'completed');
420            
421            console.log('Wildberries Description Generator: Сбор данных завершен, можно закрыть вкладку');
422            
423            // Закрываем вкладку через 2 секунды
424            setTimeout(() => {
425                window.close();
426            }, 2000);
427            
428        } catch (error) {
429            console.error('Wildberries Description Generator: Ошибка при автоматическом сборе данных:', error);
430            await GM.setValue('wb_collection_status', 'error');
431        }
432    }
433
434    // Функция для получения информации о товаре со страницы
435    function getProductInfo() {
436        console.log('Wildberries Description Generator: Сбор информации о товаре');
437        
438        const productInfo = {
439            title: '',
440            currentDescription: '',
441            composition: '',
442            attributes: []
443        };
444        
445        // 1. Получаем текущее описание
446        const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
447        if (descriptionTextarea) {
448            productInfo.currentDescription = descriptionTextarea.value || '';
449            console.log('Wildberries Description Generator: Текущее описание найдено');
450        }
451        
452        // 2. Получаем название товара - ИСПРАВЛЕННЫЙ ПОИСК
453        // Способ 1: По id "editable-title" (contenteditable)
454        const editableTitle = document.querySelector('#editable-title');
455        if (editableTitle) {
456            // Для contenteditable элементов берем textContent или innerText
457            productInfo.title = editableTitle.textContent || editableTitle.innerText || '';
458            console.log('Wildberries Description Generator: Название найдено по #editable-title');
459        }
460        
461        // Способ 2: Поиск по label "Наименование"
462        if (!productInfo.title) {
463            // Находим label с текстом "Наименование"
464            const labels = document.querySelectorAll('label, span, div');
465            const nameLabel = Array.from(labels).find(el => 
466                el.textContent && el.textContent.trim() === 'Наименование'
467            );
468            
469            if (nameLabel) {
470                console.log('Wildberries Description Generator: Найден label "Наименование"');
471                // Ищем поле ввода рядом с label
472                const parent = nameLabel.closest('div, .field-wrapper, .form-group');
473                if (parent) {
474                    // Ищем input или contenteditable
475                    const input = parent.querySelector('input, textarea, [contenteditable="true"]');
476                    if (input) {
477                        if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
478                            productInfo.title = input.value || '';
479                        } else {
480                            productInfo.title = input.textContent || input.innerText || '';
481                        }
482                        console.log('Wildberries Description Generator: Название найдено рядом с label');
483                    }
484                }
485            }
486        }
487        
488        // Способ 3: Общий поиск input
489        if (!productInfo.title) {
490            const nameInput = document.querySelector('input[name*="name"], input[placeholder*="Название"], input[placeholder*="наименование"]');
491            if (nameInput) {
492                productInfo.title = nameInput.value || '';
493                console.log('Wildberries Description Generator: Название найдено через общий поиск');
494            }
495        }
496        
497        // 3. Получаем состав товара
498        const compositionBlock = document.querySelector('div#Состав');
499        if (compositionBlock) {
500            console.log('Wildberries Description Generator: Найден блок "Состав"');
501            
502            let ingredients = [];
503            
504            // Способ 1: По data-testid
505            const byTestId = compositionBlock.querySelectorAll('[data-testid^="undefined-select-item-"]');
506            if (byTestId.length > 0) {
507                ingredients = Array.from(byTestId).map(el => el.textContent.trim()).filter(Boolean);
508                console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по data-testid`);
509            } 
510            // Способ 2: По классу
511            else {
512                const byClass = compositionBlock.querySelectorAll('.Selected-item__text__6P8EDRPmWD');
513                if (byClass.length > 0) {
514                    ingredients = Array.from(byClass).map(el => el.textContent.trim()).filter(Boolean);
515                    console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по классу`);
516                }
517            }
518            
519            // Способ 3: Поиск в чипах
520            if (ingredients.length === 0) {
521                const chips = compositionBlock.querySelectorAll('.Selected-item__fIyMG5Li-v, .New-multi-select-input__selected-item__KOh9hWF-7q');
522                chips.forEach(chip => {
523                    const text = chip.textContent.trim();
524                    const cleanText = text.replace(/remove$/, '').trim();
525                    if (cleanText) {
526                        ingredients.push(cleanText);
527                    }
528                });
529                console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по чипам`);
530            }
531            
532            // Объединяем ингредиенты
533            if (ingredients.length > 0) {
534                productInfo.composition = ingredients.join(', ');
535                console.log('Wildberries Description Generator: Состав товара извлечен');
536            } else {
537                console.log('Wildberries Description Generator: Ингредиенты не найдены в блоке "Состав"');
538            }
539        } else {
540            console.log('Wildberries Description Generator: Блок "Состав" не найден');
541            
542            // Старый способ
543            const compositionTextarea = document.querySelector('textarea[placeholder*="Состав"], textarea[name*="composition"]');
544            if (compositionTextarea && compositionTextarea.value) {
545                productInfo.composition = compositionTextarea.value;
546                console.log('Wildberries Description Generator: Состав найден через textarea');
547            }
548        }
549        
550        // 4. Дополнительные атрибуты (опционально)
551        try {
552            const attributes = {};
553            
554            // Цвет
555            const colorElement = document.querySelector('div#Цвет');
556            if (colorElement) {
557                const colorText = colorElement.querySelector('[data-testid^="undefined-select-item-"]')?.textContent;
558                if (colorText) attributes.color = colorText.trim();
559            }
560            
561            // Бренд
562            const brandInput = document.querySelector('input[placeholder*="Бренд"], input[name*="brand"]');
563            if (brandInput && brandInput.value) {
564                attributes.brand = brandInput.value;
565            }
566            
567            if (Object.keys(attributes).length > 0) {
568                productInfo.attributes = Object.entries(attributes).map(([key, value]) => `${key}: ${value}`);
569            }
570        } catch (e) {
571            console.log('Wildberries Description Generator: Ошибка при сборе атрибутов:', e);
572        }
573        
574        console.log('Wildberries Description Generator: Информация о товаре собрана', {
575            titleLength: productInfo.title.length,
576            descriptionLength: productInfo.currentDescription.length,
577            compositionLength: productInfo.composition.length,
578            attributesCount: productInfo.attributes.length
579        });
580        
581        return productInfo;
582    }
583
584    // Функция для анализа использованных ключей и расчета популярности
585    async function analyzeUsedKeywords(description) {
586        console.log('Wildberries Description Generator: Анализ использованных ключей');
587        
588        const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
589        const analyticsData = JSON.parse(analyticsDataStr);
590        
591        const descriptionLower = description.toLowerCase();
592        const usedQueries = [];
593        let totalPopularity = 0;
594        
595        // Проходим по всем собранным запросам
596        analyticsData.forEach(keywordData => {
597            keywordData.queries.forEach(queryData => {
598                const query = queryData.query.toLowerCase();
599                
600                // Проверяем, используется ли запрос в описании
601                if (descriptionLower.includes(query)) {
602                    // Парсим популярность (убираем пробелы)
603                    const popularityStr = queryData.popularity.replace(/\s/g, '');
604                    const popularity = parseInt(popularityStr) || 0;
605                    
606                    usedQueries.push({
607                        query: queryData.query,
608                        popularity: popularity
609                    });
610                    
611                    totalPopularity += popularity;
612                }
613            });
614        });
615        
616        console.log(`Wildberries Description Generator: Использовано ${usedQueries.length} запросов`);
617        console.log(`Wildberries Description Generator: Общая популярность: ${totalPopularity}`);
618        
619        return {
620            usedQueries,
621            totalPopularity,
622            totalQueriesAvailable: analyticsData.reduce((sum, kd) => sum + kd.queries.length, 0)
623        };
624    }
625
626    // Функция для форматирования числа с разделителями
627    function formatNumber(num) {
628        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
629    }
630
631    // Функция для генерации описания
632    async function generateDescription(modal) {
633        const keywordsInput = document.getElementById('wb-keywords-input');
634        const minusWordsInput = document.getElementById('wb-minus-words-input');
635        const generateBtn = document.getElementById('wb-generate-btn');
636        const regenerateBtn = document.getElementById('wb-regenerate-btn');
637        const insertBtn = document.getElementById('wb-insert-btn');
638        const resultContainer = document.getElementById('wb-desc-result-container');
639        const resultDiv = document.getElementById('wb-desc-result');
640        const charCountDiv = document.getElementById('wb-char-count');
641        const statusContainer = document.getElementById('wb-desc-status-container');
642        
643        const keywordsText = keywordsInput.value.trim();
644        const minusWordsText = minusWordsInput.value.trim();
645        
646        if (!keywordsText) {
647            showStatus(statusContainer, 'Пожалуйста, введите ключевые слова', 'error');
648            return;
649        }
650        
651        const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
652        const minusWords = minusWordsText.split('\n').map(k => k.trim().toLowerCase()).filter(k => k);
653        
654        if (keywords.length === 0) {
655            showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
656            return;
657        }
658        
659        // Показываем загрузку
660        generateBtn.disabled = true;
661        regenerateBtn.style.display = 'none';
662        insertBtn.style.display = 'none';
663        resultContainer.style.display = 'none';
664        
665        showStatus(statusContainer, 'Сбор данных с аналитики...', 'info');
666        
667        try {
668            // Собираем данные с аналитики
669            const analyticsData = await collectAnalyticsData(keywords, minusWords);
670            
671            showStatus(statusContainer, 'Генерация описания с помощью AI...', 'info');
672            
673            // Получаем информацию о товаре
674            const productInfo = getProductInfo();
675            
676            // Формируем промпт для AI (СОХРАНЕН БЕЗ ИЗМЕНЕНИЙ)
677            const prompt = `Создай SEO-оптимизированное описание товара для маркетплейса Wildberries.
678
679ВАЖНО: Это описание для внутренней SEO-оптимизации, его не увидят покупатели. Максимальная длина - 5000 символов.
680
681Информация о товаре:
682${productInfo.title ? `Название: ${productInfo.title}` : ''}
683${productInfo.currentDescription ? `Текущее описание: ${productInfo.currentDescription.substring(0, 500)}` : ''}
684${productInfo.composition ? `\nСостав (ингредиенты через запятую):\n${productInfo.composition}\n` : ''}
685
686Ключевые слова для оптимизации:
687${keywords.join(', ')}
688
689${minusWords.length > 0 ? `Минус-слова (НЕ использовать): ${minusWords.join(', ')}` : ''}
690
691Данные из аналитики Wildberries:
692${JSON.stringify(analyticsData, null, 2)}
693
694СТРОГИЕ ТРЕБОВАНИЯ:
695
6961. ФОРМАТ ОТВЕТА: Выдай только готовый текст без вступлений, заголовков и пояснений. Начинай сразу с описания товара.
697
6982. ИСПОЛЬЗОВАНИЕ СОСТАВА: 
699   ОБЯЗАТЕЛЬНО используй ингредиенты из блока "Состав" из ${productInfo.composition}!
700   - Выбирай только значимые ингредиенты, которые интересны покупателям
701   - Интегрируй их в предложения с ключевыми запросами
702   - Например: "Магний цитрат с витамином B6", "Экстракт женьшеня для энергии", "Цинк для иммунитета", "Ниацинамид", "Пантенол"
703   - НЕ используй вспомогательные компоненты (тальк, желатин, стеараты и т.д.)
704
7053. СТРУКТУРА ПРЕДЛОЖЕНИЙ - КРИТИЧЕСКИ ВАЖНО:
706   КАЖДОЕ предложение ОБЯЗАТЕЛЬНО должно содержать:
707   [Ключевой запрос из аналитики] + [ингредиент из состава] + [что делает] + [какой эффект]
708   или 
709   [Ключевой запрос из аналитики] + [что делает] + [какой эффект]
710   или
711   [ингредиент из состава] + [что делает] + [какой эффект]
712
713   Примеры правильных предложений:
714   - "Магний цитрат с витамином B6 – органическая форма магния, обладающая высокой биодоступностью."
715   - "Магний с витамином В6 является важнейшим элементом организма взрослых в борьбе со повседневным стрессом."
716   - "Коллаген для суставов с гиалуроновой кислотой способствует восстановлению хрящевой ткани и улучшает подвижность."
717   
718   ВАЖНО: Используй КАЖДЫЙ релевантный запрос из списка аналитики! Не пропускай запросы!
719
7204. ПРИОРИТЕТ ПРОСТЫХ ЗАПРОСОВ:
721   ОБЯЗАТЕЛЬНО используй простые запросы, которые точно совпадают с ключевыми словами!
722   - Если ключевое слово "магний" - ОБЯЗАТЕЛЬНО используй запрос "магний"
723   - Если ключевое слово "коллаген" - ОБЯЗАТЕЛЬНО используй запрос "коллаген"
724   - Если ключевое слово "для суставов" - ОБЯЗАТЕЛЬНО используй запрос "для суставов"
725   Эти простые запросы обычно имеют самую высокую популярность и КРИТИЧЕСКИ важны для SEO!
726
7275. СОРТИРОВКА ПО ПОПУЛЯРНОСТИ: Располагай предложения в порядке убывания популярности из аналитики. Начинай с запросов, которые имеют наибольшую популярность.
728
7296. УНИКАЛЬНОСТЬ ФРАЗ: Каждая ключевая фраза используется ТОЛЬКО ОДИН РАЗ:
730   ✓ "сыворотка для лица" - 1 раз
731   ✓ "увлажняющая сыворотка для лица" - 1 раз
732   ✗ НЕ повторяй одинаковые фразы
733
7347. ИСКЛЮЧЕНИЕ КОНКУРЕНТОВ И НЕРЕЛЕВАНТНЫХ ЗАПРОСОВ: 
735   Автоматически НЕ используй запросы, которые:
736   ✗ Содержат названия брендов конкурентов (mixit, nivea, garnier, loreal, эвалар, зубарева, солгар, now foods, доппельгерц и т.д.)
737   ✗ Содержат фамилии (зубарева, малышева, агапкин и т.д.)
738   ✗ Содержат имена собственные или названия компаний
739   ✗ Не относятся к товару (например, "магнит" вместо "магний", "аптека" как место покупки)
740   ✗ Содержат названия магазинов (магнит, пятерочка, wildberries, ozon, аптека и т.д.)
741   ✗ Содержат слова "купить", "цена", "отзывы", "инструкция", "заказать", "доставка" (это поисковые намерения, а не характеристики)
742   ✗ Являются опечатками или нерелевантными вариациями основного ключа
743   ✗ Содержат латинские буквы или английские слова (кроме общепринятых обозначений типа B6, D3)
744   
745   ПРАВИЛО БРЕНДА: Если в запросе есть слово, которое выглядит как название бренда (начинается с заглавной буквы, редко употребляется, похоже на фамилию или название компании) - НЕ используй этот запрос!
746   
747   Используй только запросы, которые описывают характеристики, свойства и применение товара на РУССКОМ языке.
748
7498. МАКСИМАЛЬНОЕ ИСПОЛЬЗОВАНИЕ ЗАПРОСОВ:
750   КРИТИЧЕСКИ ВАЖНО: Используй КАЖДЫЙ релевантный запрос из данных аналитики!
751   - Проходи по ВСЕМ запросам из списка аналитики по порядку популярности
752   - Для КАЖДОГО запроса создавай отдельное предложение
753   - НЕ пропускай запросы! Твоя цель - использовать максимум запросов
754   - Если запрос релевантен и не содержит брендов - ОБЯЗАТЕЛЬНО используй его
755   - Продолжай писать предложения, пока не закончатся релевантные запросы или не достигнешь 4000 символов
756
7579. ОБЪЕМ ТЕКСТА: 
758   Создай текст на 3800-4000 символов, используя МАКСИМАЛЬНОЕ количество запросов из аналитики.
759   Чем больше запросов используешь - тем лучше для SEO!
760
76110. ЯЗЫК: 
762   КРИТИЧЕСКИ ВАЖНО: Пиши ТОЛЬКО на русском языке!
763   
764   ✗ СТРОГО ЗАПРЕЩЕНО использовать английские слова:
765   - essential → незаменимый, важный, ключевой
766   - crucial → критически важный, ключевой, важнейший
767   - hundreds → сотни
768   - magnesium → магний
769   - protein → белок
770   - amino acids → аминокислоты
771   
772   ✗ ЗАПРЕЩЕНО использовать латинские буквы (кроме обозначений витаминов: B6, D3, C, E, A, K)
773   ✓ РАЗРЕШЕНЫ только обозначения витаминов латиницей: B1, B2, B3, B6, B12, D, D3, C, E, A, K, PP
774   ✓ Все остальное - ТОЛЬКО русскими буквами
775   
776   ПРАВИЛО: Если не знаешь русский перевод слова - используй другое русское слово с похожим значением!
777   Примеры замены:
778   - "essential amino acids" → "незаменимые аминокислоты" или "важнейшие аминокислоты"
779   - "crucial role" → "ключевая роль" или "важнейшая роль"
780   - "optimal ratio" → "оптимальное соотношение" или "идеальная пропорция"
781   
782   Если в запросе из аналитики есть английские слова - НЕ используй этот запрос!
783   Используй только запросы на чистом русском языке.
784
78511. ЕСТЕСТВЕННОСТЬ: Текст должен быть читабельным и естественным, несмотря на высокую плотность ключевых слов.
786
787КРИТИЧЕСКИ ВАЖНО: 
788- Используй КАЖДЫЙ релевантный запрос из аналитики (не пропускай!)
789- В КАЖДОМ предложении должен быть запрос из аналитики + ингредиент из состава
790- Цель: максимальная видимость = максимум использованных запросов
791- Пиши ТОЛЬКО на русском языке, исключая бренды и фамилии
792
793ИНСТРУКЦИЯ ПО ИСПОЛЬЗОВАНИЮ ЗАПРОСОВ:
794Проходи по списку запросов из аналитики ПОСЛЕДОВАТЕЛЬНО и для КАЖДОГО запроса:
7951. Проверь, содержит ли запрос бренды/фамилии/английские слова - если ДА, пропусти
7962. Если запрос чистый - ОБЯЗАТЕЛЬНО создай для него предложение
7973. Продолжай, пока не используешь ВСЕ релевантные запросы или не достигнешь 4000 символов
798
799Твоя цель - использовать МИНИМУМ 60-70% от всех доступных запросов!
800
801Вот инструкция для описания:
802
803ПОШАГОВАЯ ИНСТРУКЦИЯ ДЛЯ СОЗДАНИЯ ОПИСАНИЯ:
804
805ШАГ 1: АНАЛИЗ ИНГРЕДИЕНТОВ
806- Посмотри на состав товара: ${productInfo.composition}
807- Выдели 7-10 самых важных ингредиентов
808- Для каждого ингредиента определи: что это, для чего, какая польза
809
810ШАГ 2: СОЗДАНИЕ БАЗОВЫХ ПРЕДЛОЖЕНИЙ ПРО ИНГРЕДИЕНТЫ
811Создай по 2-3 предложения на КАЖДЫЙ важный ингредиент:
812Пример для "гиалуроновая кислота":
8131. "Гиалуроновая кислота в составе притягивает и удерживает влагу."
8142. "Молекулы гиалуроновой кислоты заполняют межклеточное пространство."
8153. "Для обезвоженной кожи гиалуроновая кислота является спасением."
816
817СДЕЛАЙ ЭТО ДЛЯ ВСЕХ ИНГРЕДИЕНТОВ ИЗ СОСТАВА!
818
819ШАГ 3: ИНТЕГРАЦИЯ С КЛЮЧЕВЫМИ ЗАПРОСАМИ
820- Возьми список запросов из аналитики
821- Для КАЖДОГО запроса найди, с каким ингредиентом его можно связать
822- Создай предложение: [Запрос] + [ингредиент] + [действие] + [эффект]
823Пример: "Увлажняющий крем с гиалуроновой кислотой интенсивно насыщает кожу влагой."
824
825ШАГ 4: ДОПОЛНИТЕЛЬНОЕ ИСПОЛЬЗОВАНИЕ ЗАПРОСОВ
826- Если остались неиспользованные запросы - создай для них предложения
827- Можно использовать запросы без привязки к конкретному ингредиенту
828- Главное - использовать максимальное количество запросов
829
830ШАГ 5: КОМПОНОВКА
831- Начни с самых популярных запросов
832- Чередуй: предложения с ингредиентами → предложения с запросами
833- Добейся объема 3800-4000 символов
834
835ВАЖНЕЙШЕЕ ПРАВИЛО: Если название ключевые запросы не соответствуют названию товара, но не надо их использовать! Например, если товар Лосьон для пяток, то не надо использовать Массажер для пяток или пилочка для пяток.
836Если товар Крем от прыщей, то не надо использовать Патчи от прыщей.   
837
838
839Сгенерируй описание:`;
840
841            console.log('Wildberries Description Generator: Отправка запроса к AI');
842            
843            // Генерируем описание с помощью AI
844            const description = await RM.aiCall(prompt);
845            
846            console.log('Wildberries Description Generator: Описание сгенерировано');
847            
848            // Анализируем использованные ключи
849            const analysis = await analyzeUsedKeywords(description);
850            
851            // Проверяем длину
852            const charCount = description.length;
853            
854            // Сохраняем описание
855            await GM.setValue('wb_generated_description', description);
856            
857            // Показываем результат
858            resultDiv.textContent = description;
859            resultContainer.style.display = 'block';
860            
861            // Обновляем счетчик символов с информацией о ключах
862            const popularityInfo = `${charCount} / 5000 символов | Использовано ${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} запросов | Общая популярность: ${formatNumber(analysis.totalPopularity)}`;
863            charCountDiv.textContent = popularityInfo;
864            
865            // Показываем кнопки
866            generateBtn.style.display = 'none';
867            regenerateBtn.style.display = 'inline-block';
868            insertBtn.style.display = 'inline-block';
869            
870            showStatus(statusContainer, `Описание успешно сгенерировано! (${charCount} символов)`, 'success');
871            
872        } catch (error) {
873            console.error('Wildberries Description Generator: Ошибка при генерации описания:', error);
874            showStatus(statusContainer, 'Ошибка при генерации описания. Попробуйте еще раз.', 'error');
875        } finally {
876            generateBtn.disabled = false;
877        }
878    }
879
880    // Функция для показа статуса
881    function showStatus(container, message, type) {
882        container.innerHTML = `<div class="wb-desc-status ${type}">${message}</div>`;
883    }
884
885    // Функция для вставки описания
886    async function insertDescription(modal) {
887        console.log('Wildberries Description Generator: Вставка описания');
888        
889        try {
890            const description = await GM.getValue('wb_generated_description', '');
891            
892            if (!description) {
893                alert('Описание не найдено. Пожалуйста, сгенерируйте описание сначала.');
894                return;
895            }
896            
897            // Находим поле описания
898            const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
899            
900            if (!descriptionTextarea) {
901                alert('Не удалось найти поле описания. Убедитесь, что вы находитесь на странице редактирования товара.');
902                return;
903            }
904            
905            // Вставляем описание
906            descriptionTextarea.value = description;
907            descriptionTextarea.dispatchEvent(new Event('input', { bubbles: true }));
908            descriptionTextarea.dispatchEvent(new Event('change', { bubbles: true }));
909            
910            console.log('Wildberries Description Generator: Описание успешно вставлено');
911            
912            // Закрываем модальное окно
913            modal.remove();
914            
915            alert('Описание успешно вставлено!');
916            
917        } catch (error) {
918            console.error('Wildberries Description Generator: Ошибка при вставке описания:', error);
919            alert('Ошибка при вставке описания. Попробуйте еще раз.');
920        }
921    }
922
923    // Функция для инициализации расширения
924    function init() {
925        console.log('Wildberries Description Generator: Инициализация');
926        
927        // Проверяем, что мы на странице редактирования товара
928        if (window.location.href.includes('seller.wildberries.ru/new-goods/card')) {
929            
930            // Ждем загрузки страницы и добавляем кнопку
931            const observer = new MutationObserver((mutations, obs) => {
932                const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
933                if (descriptionHeader) {
934                    createGeneratorButton();
935                    obs.disconnect();
936                }
937            });
938            
939            observer.observe(document.body, {
940                childList: true,
941                subtree: true
942            });
943            
944            // Также пробуем добавить кнопку сразу
945            setTimeout(createGeneratorButton, 2000);
946        }
947        
948        // Проверяем, что мы на странице аналитики и запускаем автосбор
949        if (window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
950            setTimeout(autoCollectOnAnalyticsPage, 2000);
951        }
952    }
953
954    // Запускаем инициализацию
955    if (document.readyState === 'loading') {
956        document.addEventListener('DOMContentLoaded', init);
957    } else {
958        init();
959    }
960
961})();
Wildberries Description Generator | Robomonkey