Wildberries Description Generator 2.0

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

Size

59.6 KB

Version

2.4.6

Created

Jan 14, 2026

Updated

3 months ago

1// ==UserScript==
2// @name		Wildberries Description Generator 2.0
3// @description		Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов
4// @version		2.4.6
5// @match		https://*.seller.wildberries.ru/*
6// @icon		https://static-basket-02.wbbasket.ru/vol20/root-monorepo/latest/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: 22px;
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            font-size: 16px;
56        }
57        
58        .wb-desc-textarea {
59            width: 100%;
60            min-height: 100px;
61            padding: 12px;
62            border: 1px solid #d1d5db;
63            border-radius: 8px;
64            font-size: 16px;
65            font-family: inherit;
66            resize: vertical;
67        }
68        
69        .wb-desc-textarea:focus {
70            outline: none;
71            border-color: #9333ea;
72        }
73        
74        .wb-desc-result {
75            background: #f3f4f6;
76            padding: 16px;
77            border-radius: 8px;
78            margin-bottom: 16px;
79            max-height: 300px;
80            overflow-y: auto;
81            white-space: pre-wrap;
82            word-wrap: break-word;
83            font-size: 16px;
84        }
85        
86        .wb-desc-char-count {
87            text-align: right;
88            font-size: 14px;
89            color: #6b7280;
90            margin-top: 4px;
91        }
92        
93        .wb-desc-char-count.warning {
94            color: #f59e0b;
95        }
96        
97        .wb-desc-char-count.error {
98            color: #ef4444;
99        }
100        
101        .wb-desc-buttons {
102            display: flex;
103            gap: 12px;
104            justify-content: flex-end;
105            margin-top: 20px;
106        }
107        
108        .wb-desc-btn {
109            padding: 10px 20px;
110            border: none;
111            border-radius: 8px;
112            font-size: 16px;
113            font-weight: 500;
114            cursor: pointer;
115            transition: all 0.2s;
116        }
117        
118        .wb-desc-btn-primary {
119            background: #9333ea;
120            color: white;
121        }
122        
123        .wb-desc-btn-primary:hover {
124            background: #7e22ce;
125        }
126        
127        .wb-desc-btn-primary:disabled {
128            background: #9ca3af;
129            cursor: not-allowed;
130        }
131        
132        .wb-desc-btn-secondary {
133            background: #e5e7eb;
134            color: #374151;
135        }
136        
137        .wb-desc-btn-secondary:hover {
138            background: #d1d5db;
139        }
140        
141        .wb-desc-btn-success {
142            background: #10b981;
143            color: white;
144        }
145        
146        .wb-desc-btn-success:hover {
147            background: #059669;
148        }
149        
150        .wb-desc-generator-btn {
151            margin-left: 12px;
152            padding: 10px 20px;
153            background: #9333ea;
154            color: white;
155            border: none;
156            border-radius: 8px;
157            font-size: 16px;
158            font-weight: 500;
159            cursor: pointer;
160            transition: all 0.2s;
161        }
162        
163        .wb-desc-generator-btn:hover {
164            background: #7e22ce;
165        }
166        
167        .wb-desc-status {
168            margin-top: 12px;
169            padding: 8px 12px;
170            border-radius: 6px;
171            font-size: 15px;
172        }
173        
174        .wb-desc-status.info {
175            background: #dbeafe;
176            color: #1e40af;
177        }
178        
179        .wb-desc-status.success {
180            background: #d1fae5;
181            color: #065f46;
182        }
183        
184        .wb-desc-status.error {
185            background: #fee2e2;
186            color: #991b1b;
187        }
188        
189        .wb-desc-suggest-btn {
190            margin-top: 8px;
191            padding: 8px 16px;
192            background: #6366f1;
193            color: white;
194            border: none;
195            border-radius: 6px;
196            font-size: 15px;
197            font-weight: 500;
198            cursor: pointer;
199            transition: all 0.2s;
200        }
201        
202        .wb-desc-suggest-btn:hover {
203            background: #4f46e5;
204        }
205        
206        .wb-desc-suggest-btn:disabled {
207            background: #9ca3af;
208            cursor: not-allowed;
209        }
210        
211        .wb-desc-suggested-keywords {
212            margin-top: 12px;
213            padding: 12px;
214            background: #f9fafb;
215            border-radius: 8px;
216            border: 1px solid #e5e7eb;
217        }
218        
219        .wb-desc-suggested-header {
220            font-weight: 500;
221            margin-bottom: 8px;
222            color: #374151;
223            font-size: 15px;
224        }
225        
226        .wb-desc-checkbox-group {
227            display: grid;
228            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
229            gap: 8px;
230            max-height: 200px;
231            overflow-y: auto;
232        }
233        
234        .wb-desc-checkbox-item {
235            display: flex;
236            align-items: center;
237            gap: 8px;
238        }
239        
240        .wb-desc-checkbox-item input[type="checkbox"] {
241            cursor: pointer;
242        }
243        
244        .wb-desc-checkbox-item label {
245            cursor: pointer;
246            font-size: 15px;
247            color: #374151;
248            margin: 0;
249        }
250    `);
251
252    // Добавьте в начало скрипта:
253    let lastUrl = location.href;
254
255    // Мониторим изменения URL (для SPA)
256    new MutationObserver(() => {
257        const url = location.href;
258        if (url !== lastUrl) {
259            lastUrl = url;
260            console.log('Wildberries Description Generator: URL изменился');
261        
262            // Если перешли на страницу товара - инициализируем
263            if (url.includes('seller.wildberries.ru/new-goods/card')) {
264                setTimeout(init, 1000);
265            }
266        }
267    }).observe(document, { subtree: true, childList: true });
268
269    // Также отслеживаем history API
270    const originalPushState = history.pushState;
271    history.pushState = function() {
272        originalPushState.apply(this, arguments);
273        setTimeout(() => {
274            if (location.href.includes('seller.wildberries.ru/new-goods/card')) {
275                console.log('Wildberries Description Generator: History API навигация');
276                setTimeout(init, 500);
277            }
278        }, 100);
279    };
280
281    // Функция для создания кнопки генератора
282    function createGeneratorButton() {
283        const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
284        
285        if (!descriptionHeader) {
286            console.log('Wildberries Description Generator: Заголовок описания не найден');
287            return;
288        }
289        
290        // Проверяем, не добавлена ли уже кнопка
291        if (document.querySelector('.wb-desc-generator-btn')) {
292            console.log('Wildberries Description Generator: Кнопка уже добавлена');
293            return;
294        }
295        
296        const button = document.createElement('button');
297        button.className = 'wb-desc-generator-btn';
298        button.textContent = 'Генератор описаний';
299        button.type = 'button';
300        button.addEventListener('click', function() {
301            // Автоматически открываем ингредиенты
302            const expandButton = document.querySelector('div.Characteristics__expand__570w3PkC7D button');
303            if (expandButton) expandButton.click();
304    
305            // Открываем модальное окно
306            setTimeout(openModal, 500);
307        });
308        
309        descriptionHeader.appendChild(button);
310        console.log('Wildberries Description Generator: Кнопка добавлена');
311    }
312
313    // Функция для открытия модального окна
314    function openModal() {
315        
316        console.log('Wildberries Description Generator: Открытие модального окна');
317        
318        const modal = document.createElement('div');
319        modal.className = 'wb-desc-modal';
320        modal.innerHTML = `
321            <div class="wb-desc-modal-content">
322                <div class="wb-desc-modal-header">Генератор описаний для Wildberries</div>
323                
324                <div class="wb-desc-input-group">
325                    <label class="wb-desc-label">Введите ключевые слова (каждое с новой строки):</label>
326                    <textarea class="wb-desc-textarea" id="wb-keywords-input" placeholder="Например:&#10;витамины&#10;иммунитет&#10;здоровье"></textarea>
327                    <button class="wb-desc-suggest-btn" id="wb-suggest-keywords-btn">Предложить дополнительные ключи</button>
328                    <div id="wb-suggested-keywords-container" style="display: none;"></div>
329                </div>
330                
331                <div class="wb-desc-input-group">
332                    <label class="wb-desc-label">Минус-слова (каждое с новой строки):</label>
333                    <textarea class="wb-desc-textarea" style="min-height: 80px;" id="wb-minus-words-input" placeholder="Например:&#10;mixit&#10;nivea&#10;магнит"></textarea>
334                </div>
335                
336                <div id="wb-desc-result-container" style="display: none;">
337                    <div class="wb-desc-label">Сгенерированное описание:</div>
338                    <div class="wb-desc-result" id="wb-desc-result"></div>
339                    <div class="wb-desc-char-count" id="wb-char-count"></div>
340                </div>
341                
342                <div id="wb-desc-status-container"></div>
343                
344                <div class="wb-desc-buttons">
345                    <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-btn">Закрыть</button>
346                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-generate-btn">Сгенерировать</button>
347                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-btn" style="display: none;">Перегенерировать</button>
348                    <button class="wb-desc-btn wb-desc-btn-success" id="wb-insert-btn" style="display: none;">Вставить в описание</button>
349                </div>
350            </div>
351        `;
352        
353        document.body.appendChild(modal);
354        
355        // Обработчики событий
356        modal.addEventListener('click', (e) => {
357            if (e.target === modal) {
358                modal.remove();
359            }
360        });
361        
362        document.getElementById('wb-close-btn').addEventListener('click', () => {
363            modal.remove();
364        });
365        
366        document.getElementById('wb-suggest-keywords-btn').addEventListener('click', () => {
367            suggestKeywords(modal);
368        });
369        
370        document.getElementById('wb-generate-btn').addEventListener('click', () => {
371            generateDescription(modal);
372        });
373        
374        document.getElementById('wb-regenerate-btn').addEventListener('click', () => {
375            generateDescription(modal);
376        });
377        
378        document.getElementById('wb-insert-btn').addEventListener('click', () => {
379            insertDescription(modal);
380        });
381    }
382
383    // Функция для предложения дополнительных ключей
384    async function suggestKeywords(modal) {
385        const keywordsInput = document.getElementById('wb-keywords-input');
386        const suggestBtn = document.getElementById('wb-suggest-keywords-btn');
387        const suggestedContainer = document.getElementById('wb-suggested-keywords-container');
388        const statusContainer = document.getElementById('wb-desc-status-container');
389        
390        const keywordsText = keywordsInput.value.trim();
391        
392        if (!keywordsText) {
393            showStatus(statusContainer, 'Пожалуйста, сначала введите базовые ключевые слова', 'error');
394            return;
395        }
396        
397        const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
398        
399        suggestBtn.disabled = true;
400        showStatus(statusContainer, 'AI анализирует товар и подбирает ключи...', 'info');
401        
402        try {
403            // Получаем информацию о товаре
404            const productInfo = getProductInfo();
405            
406            // Запрос к AI для предложения ключей
407            const suggestPrompt = `Проанализируй товар и предложи дополнительные ключевые слова для сбора аналитики Wildberries.
408
409ДАННЫЕ О ТОВАРЕ:
410• Название: ${productInfo.title || 'не указано'}
411• Состав: ${productInfo.composition || 'не указан'}
412• Базовые ключи: ${keywords.join(', ')}
413
414ЗАДАЧА:
415Предложи 10-15 дополнительных релевантных ключевых слов для поиска в аналитике Wildberries.
416
417ВАЖНО: 
418- Предлагай СИНОНИМЫ и АЛЬТЕРНАТИВНЫЕ названия товара
419- НЕ расширяй базовый ключ (например, если "масло для загара" → НЕ предлагай "сухое масло для загара")
420- Предлагай ДРУГИЕ типы продуктов с тем же назначением
421- Учитывай состав и назначение товара
422- НЕ предлагай бренды или названия конкурентов
423- Ключи должны быть короткими (1-4 слова)
424
425ПРИМЕРЫ ПРАВИЛЬНЫХ предложений:
426- Базовый ключ: "масло для загара" → Предложи: "средство для загара", "лошон для загара", "усилитель загара", "активатор загара", "крем для загара"
427- Базовый ключ: "пенка для умывания" → Предложи: "средство для умывания", "гель для умывания", "для умывания лица", "очищающее средство"
428- Базовый ключ: "тестостерон" → Предложи: "для тестостерона", "повышение тестостерона", "бустер тестостерона", "тестобустер"
429- Базовый ключ: "витамины" → Предложи: "витаминный комплекс", "мультивитамины", "бад", "добавка"
430
431ПРИМЕРЫ НЕПРАВИЛЬНЫХ предложений (НЕ ДЕЛАЙ ТАК):
432- Базовый ключ: "масло для загара" → ❌ "сухое масло для загара", "натуральное масло для загара" (это расширение базового ключа)
433- Базовый ключ: "пенка для умывания" → ❌ "косметика", "уход за кожей", "красота" (слишком общие)
434- Базовый ключ: "тестостерон" → ❌ "здоровье", "бады" (слишком общие)
435
436ВАЖНО: 
437- Предлагай ДРУГИЕ способы назвать тот же товар
438- Думай как покупатель: "Как еще можно назвать этот товар?"
439- НЕ добавляй прилагательные к базовому ключу
440
441Верни ТОЛЬКО список ключей в формате JSON:
442{
443  "keywords": ["ключ 1", "ключ 2", "ключ 3", ...]
444}
445
446НЕ ПИШИ ничего кроме JSON. Начни ответ сразу с {`;
447
448            console.log('Wildberries Description Generator: Запрос предложений ключей от AI');
449            
450            const suggestResponse = await RM.aiCall(suggestPrompt);
451            
452            // Парсим ответ
453            let suggestedKeywords = [];
454            try {
455                const suggestData = JSON.parse(suggestResponse);
456                suggestedKeywords = suggestData.keywords || [];
457                console.log(`Wildberries Description Generator: AI предложил ${suggestedKeywords.length} ключей`);
458            } catch (e) {
459                console.error('Wildberries Description Generator: Ошибка парсинга предложенных ключей:', e);
460                showStatus(statusContainer, 'Ошибка при получении предложений. Попробуйте еще раз.', 'error');
461                return;
462            }
463            
464            if (suggestedKeywords.length === 0) {
465                showStatus(statusContainer, 'AI не смог предложить дополнительные ключи', 'error');
466                return;
467            }
468            
469            // Показываем предложенные ключи с чекбоксами
470            suggestedContainer.innerHTML = `
471                <div class="wb-desc-suggested-keywords">
472                    <div class="wb-desc-suggested-header">
473                        Предложенные ключи (выберите нужные):
474                        <button class="wb-desc-suggest-btn" id="wb-toggle-all-btn" style="margin-left: 12px; padding: 4px 12px; font-size: 12px;">Выделить все</button>
475                    </div>
476                    <div class="wb-desc-checkbox-group">
477                        ${suggestedKeywords.map((keyword, index) => `
478                            <div class="wb-desc-checkbox-item">
479                                <input type="checkbox" id="wb-suggested-${index}" value="${keyword}">
480                                <label for="wb-suggested-${index}">${keyword}</label>
481                            </div>
482                        `).join('')}
483                    </div>
484                </div>
485            `;
486            
487            suggestedContainer.style.display = 'block';
488            
489            // Добавляем обработчик для кнопки "Выделить все"
490            const toggleAllBtn = document.getElementById('wb-toggle-all-btn');
491            toggleAllBtn.addEventListener('click', () => {
492                const checkboxes = document.querySelectorAll('#wb-suggested-keywords-container input[type="checkbox"]');
493                const allChecked = Array.from(checkboxes).every(cb => cb.checked);
494                
495                checkboxes.forEach(cb => {
496                    cb.checked = !allChecked;
497                });
498                
499                toggleAllBtn.textContent = allChecked ? 'Выделить все' : 'Снять выделение';
500            });
501            
502            showStatus(statusContainer, `AI предложил ${suggestedKeywords.length} дополнительных ключей. Выберите нужные и нажмите "Сгенерировать"`, 'success');
503            
504        } catch (error) {
505            console.error('Wildberries Description Generator: Ошибка при предложении ключей:', error);
506            showStatus(statusContainer, 'Ошибка при получении предложений. Попробуйте еще раз.', 'error');
507        } finally {
508            suggestBtn.disabled = false;
509        }
510    }
511
512    // Функция для сбора данных с аналитики
513    async function collectAnalyticsData(keywords, minusWords) {
514        console.log('Wildberries Description Generator: Начало сбора данных с аналитики');
515        
516        // Сохраняем данные для доступа из другой вкладки
517        await GM.setValue('wb_keywords_to_process', JSON.stringify(keywords));
518        await GM.setValue('wb_minus_words', JSON.stringify(minusWords));
519        await GM.setValue('wb_analytics_data', JSON.stringify([]));
520        await GM.setValue('wb_collection_status', 'pending');
521        
522        // Открываем страницу аналитики в новой вкладке
523        const analyticsUrl = 'https://seller.wildberries.ru/search-analytics/popular-search-queries';
524        await GM.openInTab(analyticsUrl, false);
525        
526        console.log('Wildberries Description Generator: Открыта страница аналитики, ожидание сбора данных...');
527        
528        // Ждем завершения сбора данных (максимум 5 минут)
529        const maxWaitTime = 300000; // 5 минут
530        const checkInterval = 2000; // проверяем каждые 2 секунды
531        let waitedTime = 0;
532        
533        while (waitedTime < maxWaitTime) {
534            await new Promise(resolve => setTimeout(resolve, checkInterval));
535            waitedTime += checkInterval;
536            
537            const status = await GM.getValue('wb_collection_status', 'pending');
538            
539            if (status === 'completed') {
540                const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
541                const analyticsData = JSON.parse(analyticsDataStr);
542                console.log('Wildberries Description Generator: Данные успешно собраны');
543                return analyticsData;
544            } else if (status === 'error') {
545                console.error('Wildberries Description Generator: Ошибка при сборе данных');
546                return [];
547            }
548        }
549        
550        console.error('Wildberries Description Generator: Превышено время ожидания сбора данных');
551        return [];
552    }
553
554    // Функция для автоматического сбора данных на странице аналитики
555    async function autoCollectOnAnalyticsPage() {
556        // Проверяем, что мы на странице аналитики
557        if (!window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
558            return;
559        }
560        
561        console.log('Wildberries Description Generator: Обнаружена страница аналитики');
562        
563        // Проверяем, есть ли задача на сбор данных
564        const status = await GM.getValue('wb_collection_status', 'none');
565        if (status !== 'pending') {
566            return;
567        }
568        
569        console.log('Wildberries Description Generator: Начинаем автоматический сбор данных');
570        
571        try {
572            const keywordsStr = await GM.getValue('wb_keywords_to_process', '[]');
573            const minusWordsStr = await GM.getValue('wb_minus_words', '[]');
574            const keywords = JSON.parse(keywordsStr);
575            const minusWords = JSON.parse(minusWordsStr);
576            
577            const analyticsData = [];
578            
579            // Ждем загрузки страницы
580            await new Promise(resolve => setTimeout(resolve, 3000));
581            
582            for (const keyword of keywords) {
583                console.log(`Wildberries Description Generator: Обработка ключевого слова: ${keyword}`);
584                
585                try {
586                    // Находим поле поиска
587                    const searchInput = document.querySelector('input[name="searchString"]');
588                    if (!searchInput) {
589                        console.error('Wildberries Description Generator: Поле поиска не найдено');
590                        continue;
591                    }
592                    
593                    // Очищаем и вводим ключевое слово
594                    searchInput.value = '';
595                    searchInput.focus();
596                    searchInput.value = keyword;
597                    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
598                    searchInput.dispatchEvent(new Event('change', { bubbles: true }));
599                    
600                    // Ждем загрузки результатов
601                    await new Promise(resolve => setTimeout(resolve, 5000));
602                    
603                    // Собираем данные из таблицы
604                    const rows = document.querySelectorAll('table tbody tr');
605                    const keywordData = {
606                        keyword: keyword,
607                        queries: []
608                    };
609                    
610                    console.log(`Wildberries Description Generator: Найдено строк в таблице: ${rows.length}`);
611                    
612                    rows.forEach(row => {
613                        const cells = row.querySelectorAll('td');
614                        if (cells.length >= 2) {
615                            const query = cells[0]?.textContent?.trim();
616                            const popularity = cells[1]?.textContent?.trim();
617                            
618                            if (query && popularity) {
619                                // Фильтруем по минус-словам
620                                const queryLower = query.toLowerCase();
621                                const hasMinusWord = minusWords.some(minusWord => 
622                                    queryLower.includes(minusWord.toLowerCase())
623                                );
624                                
625                                if (!hasMinusWord) {
626                                    keywordData.queries.push({
627                                        query,
628                                        popularity
629                                    });
630                                } else {
631                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
632                                }
633                            }
634                        }
635                    });
636                    
637                    analyticsData.push(keywordData);
638                    console.log(`Wildberries Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
639                    
640                } catch (error) {
641                    console.error(`Wildberries Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
642                }
643            }
644            
645            // Сохраняем собранные данные
646            await GM.setValue('wb_analytics_data', JSON.stringify(analyticsData));
647            await GM.setValue('wb_collection_status', 'completed');
648            
649            console.log('Wildberries Description Generator: Сбор данных завершен, можно закрыть вкладку');
650            
651            // Закрываем вкладку через 2 секунды
652            setTimeout(() => {
653                window.close();
654            }, 2000);
655            
656        } catch (error) {
657            console.error('Wildberries Description Generator: Ошибка при автоматическом сборе данных:', error);
658            await GM.setValue('wb_collection_status', 'error');
659        }
660    }
661
662    // Функция для получения информации о товаре со страницы
663    function getProductInfo() {
664        console.log('Wildberries Description Generator: Сбор информации о товаре');
665        
666        const productInfo = {
667            title: '',
668            currentDescription: '',
669            composition: '',
670            attributes: []
671        };
672        
673        // 1. Получаем текущее описание
674        const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
675        if (descriptionTextarea) {
676            productInfo.currentDescription = descriptionTextarea.value || '';
677            console.log('Wildberries Description Generator: Текущее описание найдено');
678        }
679        
680        // 2. Получаем название товара - ИСПРАВЛЕННЫЙ ПОИСК
681        // Способ 1: По id "editable-title" (contenteditable)
682        const editableTitle = document.querySelector('#editable-title');
683        if (editableTitle) {
684            // Для contenteditable элементов берем textContent или innerText
685            productInfo.title = editableTitle.textContent || editableTitle.innerText || '';
686            console.log('Wildberries Description Generator: Название найдено по #editable-title');
687        }
688        
689        // Способ 2: Поиск по label "Наименование"
690        if (!productInfo.title) {
691            // Находим label с текстом "Наименование"
692            const labels = document.querySelectorAll('label, span, div');
693            const nameLabel = Array.from(labels).find(el => 
694                el.textContent && el.textContent.trim() === 'Наименование'
695            );
696            
697            if (nameLabel) {
698                console.log('Wildberries Description Generator: Найден label "Наименование"');
699                // Ищем поле ввода рядом с label
700                const parent = nameLabel.closest('div, .field-wrapper, .form-group');
701                if (parent) {
702                    // Ищем input или contenteditable
703                    const input = parent.querySelector('input, textarea, [contenteditable="true"]');
704                    if (input) {
705                        if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
706                            productInfo.title = input.value || '';
707                        } else {
708                            productInfo.title = input.textContent || input.innerText || '';
709                        }
710                        console.log('Wildberries Description Generator: Название найдено рядом с label');
711                    }
712                }
713            }
714        }
715        
716        // Способ 3: Общий поиск input
717        if (!productInfo.title) {
718            const nameInput = document.querySelector('input[name*="name"], input[placeholder*="Название"], input[placeholder*="наименование"]');
719            if (nameInput) {
720                productInfo.title = nameInput.value || '';
721                console.log('Wildberries Description Generator: Название найдено через общий поиск');
722            }
723        }
724        
725        // 3. Получаем состав товара
726        const compositionBlock = document.querySelector('div#Состав');
727        if (compositionBlock) {
728            console.log('Wildberries Description Generator: Найден блок "Состав"');
729            
730            let ingredients = [];
731            
732            // Способ 1: По data-testid
733            const byTestId = compositionBlock.querySelectorAll('[data-testid^="undefined-select-item-"]');
734            if (byTestId.length > 0) {
735                ingredients = Array.from(byTestId).map(el => el.textContent.trim()).filter(Boolean);
736                console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по data-testid`);
737            } 
738            // Способ 2: По классу
739            else {
740                const byClass = compositionBlock.querySelectorAll('.Selected-item__text__6P8EDRPmWD');
741                if (byClass.length > 0) {
742                    ingredients = Array.from(byClass).map(el => el.textContent.trim()).filter(Boolean);
743                    console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по классу`);
744                }
745            }
746            
747            // Способ 3: Поиск в чипах
748            if (ingredients.length === 0) {
749                const chips = compositionBlock.querySelectorAll('.Selected-item__fIyMG5Li-v, .New-multi-select-input__selected-item__KOh9hWF-7q');
750                chips.forEach(chip => {
751                    const text = chip.textContent.trim();
752                    const cleanText = text.replace(/remove$/, '').trim();
753                    if (cleanText) {
754                        ingredients.push(cleanText);
755                    }
756                });
757                console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по чипам`);
758            }
759            
760            // Объединяем ингредиенты
761            if (ingredients.length > 0) {
762                productInfo.composition = ingredients.join(', ');
763                console.log('Wildberries Description Generator: Состав товара извлечен');
764            } else {
765                console.log('Wildberries Description Generator: Ингредиенты не найдены в блоке "Состав"');
766            }
767        } else {
768            console.log('Wildberries Description Generator: Блок "Состав" не найден');
769            
770            // Старый способ
771            const compositionTextarea = document.querySelector('textarea[placeholder*="Состав"], textarea[name*="composition"]');
772            if (compositionTextarea && compositionTextarea.value) {
773                productInfo.composition = compositionTextarea.value;
774                console.log('Wildberries Description Generator: Состав найден через textarea');
775            }
776        }
777        
778        // 4. Дополнительные атрибуты (опционально)
779        try {
780            const attributes = {};
781            
782            // Цвет
783            const colorElement = document.querySelector('div#Цвет');
784            if (colorElement) {
785                const colorText = colorElement.querySelector('[data-testid^="undefined-select-item-"]')?.textContent;
786                if (colorText) attributes.color = colorText.trim();
787            }
788            
789            // Бренд
790            const brandInput = document.querySelector('input[placeholder*="Бренд"], input[name*="brand"]');
791            if (brandInput && brandInput.value) {
792                attributes.brand = brandInput.value;
793            }
794            
795            if (Object.keys(attributes).length > 0) {
796                productInfo.attributes = Object.entries(attributes).map(([key, value]) => `${key}: ${value}`);
797            }
798        } catch (e) {
799            console.log('Wildberries Description Generator: Ошибка при сборе атрибутов:', e);
800        }
801        
802        console.log('Wildberries Description Generator: Информация о товаре собрана', {
803            titleLength: productInfo.title.length,
804            descriptionLength: productInfo.currentDescription.length,
805            compositionLength: productInfo.composition.length,
806            attributesCount: productInfo.attributes.length
807        });
808        
809        return productInfo;
810    }
811
812    // Функция для анализа использованных ключей и расчета популярности
813    async function analyzeUsedKeywords(description) {
814        console.log('Wildberries Description Generator: Анализ использованных ключей');
815        
816        const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
817        const analyticsData = JSON.parse(analyticsDataStr);
818        
819        const descriptionLower = description.toLowerCase();
820        const usedQueries = [];
821        let totalPopularity = 0;
822        
823        // Проходим по всем собранным запросам
824        analyticsData.forEach(keywordData => {
825            keywordData.queries.forEach(queryData => {
826                const query = queryData.query.toLowerCase();
827                
828                // Проверяем, используется ли запрос в описании
829                if (descriptionLower.includes(query)) {
830                    // Парсим популярность (убираем пробелы)
831                    const popularityStr = queryData.popularity.replace(/\s/g, '');
832                    const popularity = parseInt(popularityStr) || 0;
833                    
834                    usedQueries.push({
835                        query: queryData.query,
836                        popularity: popularity
837                    });
838                    
839                    totalPopularity += popularity;
840                }
841            });
842        });
843        
844        console.log(`Wildberries Description Generator: Использовано ${usedQueries.length} запросов`);
845        console.log(`Wildberries Description Generator: Общая популярность: ${totalPopularity}`);
846        
847        return {
848            usedQueries,
849            totalPopularity,
850            totalQueriesAvailable: analyticsData.reduce((sum, kd) => sum + kd.queries.length, 0)
851        };
852    }
853
854    // Функция для форматирования числа с разделителями
855    function formatNumber(num) {
856        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
857    }
858
859    // Функция для генерации описания
860    async function generateDescription(modal) {
861        const keywordsInput = document.getElementById('wb-keywords-input');
862        const minusWordsInput = document.getElementById('wb-minus-words-input');
863        const generateBtn = document.getElementById('wb-generate-btn');
864        const regenerateBtn = document.getElementById('wb-regenerate-btn');
865        const insertBtn = document.getElementById('wb-insert-btn');
866        const resultContainer = document.getElementById('wb-desc-result-container');
867        const resultDiv = document.getElementById('wb-desc-result');
868        const charCountDiv = document.getElementById('wb-char-count');
869        const statusContainer = document.getElementById('wb-desc-status-container');
870        
871        const keywordsText = keywordsInput.value.trim();
872        const minusWordsText = minusWordsInput.value.trim();
873        
874        if (!keywordsText) {
875            showStatus(statusContainer, 'Пожалуйста, введите ключевые слова', 'error');
876            return;
877        }
878        
879        let keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
880        const minusWords = minusWordsText.split('\n').map(k => k.trim().toLowerCase()).filter(k => k);
881        
882        // Добавляем выбранные дополнительные ключи
883        const suggestedCheckboxes = document.querySelectorAll('#wb-suggested-keywords-container input[type="checkbox"]:checked');
884        if (suggestedCheckboxes.length > 0) {
885            const selectedSuggested = Array.from(suggestedCheckboxes).map(cb => cb.value);
886            keywords = [...keywords, ...selectedSuggested];
887            console.log(`Wildberries Description Generator: Добавлено ${selectedSuggested.length} дополнительных ключей`);
888        }
889        
890        if (keywords.length === 0) {
891            showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
892            return;
893        }
894        
895        // Показываем загрузку
896        generateBtn.disabled = true;
897        regenerateBtn.style.display = 'none';
898        insertBtn.style.display = 'none';
899        resultContainer.style.display = 'none';
900        
901        showStatus(statusContainer, 'Сбор данных с аналитики...', 'info');
902        
903        try {
904            // Собираем данные с аналитики
905            const analyticsData = await collectAnalyticsData(keywords, minusWords);
906            
907            // Получаем информацию о товаре
908            const productInfo = getProductInfo();
909            
910            // ЭТАП 1: Фильтруем и планируем использование запросов
911            showStatus(statusContainer, 'Анализ и планирование запросов...', 'info');
912            
913            const planningPrompt = `Проанализируй данные аналитики и создай план использования запросов для SEO-описания товара.
914
915ДАННЫЕ:
916• Название: ${productInfo.title || 'не указано'}
917• Состав: ${productInfo.composition || 'не указан'}
918• Ключевые слова: ${keywords.join(', ')}
919• Минус-слова: ${minusWords.join(', ') || 'нет'}
920
921${
922    analyticsData && analyticsData.length > 0 
923        ? `ДАННЫЕ АНАЛИТИКИ:
924${analyticsData.map(data => {
925        return `\nКлючевое слово: "${data.keyword}"\nЗапросы:\n${data.queries.map((q, i) => `${i+1}. "${q.query}" (популярность: ${q.popularity})`).join('\n')}`;
926    }).join('\n')}`
927        : 'ДАННЫЕ АНАЛИТИКИ: Запросы не предоставлены.'
928    }
929
930ЗАДАЧА:
9311. Отфильтруй запросы, удалив ТОЛЬКО:
932   - Бренды: эвалар, артнео, солгар, now foods, доппельгерц, афобазол, новопассит, фенибут, нормотим, ашваганда
933   - Фамилии: зубарева, агапкин, малышева, лихи, роберт
934   - Минус-слова: ${minusWords.join(', ') || 'нет'}
935   - Нерелевантное: купить, цена, отзывы, инструкция, доставка, книга, раскраска, игрушка, подарок, кольцо
936   - Животных: для кошек, для собак, кот баюн
937
9382. ВАЖНО: НЕ удаляй запросы, которые являются расширением базовых ключей!
939   - Например, если базовый ключ "средство от прыщей", то "средство от прыщей на спине" - ОСТАВЬ
940   - Если базовый ключ "масло для загара", то "сухое масло для загара" - ОСТАВЬ
941   - Расширенные запросы тоже важны для SEO!
942
9433. Отсортируй чистые запросы по популярности
944
9454. Выбери ТОП-70 самых популярных чистых запросов для использования в описании
946
9475. Верни ТОЛЬКО список выбранных запросов в формате JSON:
948{
949  "queries": ["запрос 1", "запрос 2", "запрос 3", ...]
950}
951
952НЕ ПИШИ ничего кроме JSON. Начни ответ сразу с {`;
953
954            console.log('Wildberries Description Generator: Этап 1 - Планирование запросов');
955            
956            const planResponse = await RM.aiCall(planningPrompt);
957            
958            // Парсим план
959            let selectedQueries = [];
960            try {
961                const planData = JSON.parse(planResponse);
962                selectedQueries = planData.queries || [];
963                console.log(`Wildberries Description Generator: Выбрано ${selectedQueries.length} запросов для использования`);
964            } catch (e) {
965                console.error('Wildberries Description Generator: Ошибка парсинга плана:', e);
966                // Если не удалось распарсить, используем все запросы
967                selectedQueries = analyticsData.flatMap(data => data.queries.map(q => q.query));
968            }
969            
970            // ЭТАП 2: Генерируем описание с использованием выбранных запросов
971            showStatus(statusContainer, 'Генерация описания с помощью AI...', 'info');
972            
973            const descriptionPrompt = `Создай SEO-описание товара для Wildberries (внутренняя SEO-оптимизация).
974
975ДАННЫЕ:
976• Название: ${productInfo.title || 'не указано'}
977• Состав: ${productInfo.composition || 'не указан'}
978• Ключевые слова: ${keywords.join(', ')}
979
980ЗАПРОСЫ ДЛЯ ИСПОЛЬЗОВАНИЯ (используй МАКСИМУМ из этого списка):
981${selectedQueries.map((q, i) => `${i+1}. "${q}"`).join('\n')}
982
983=== ЖЕСТКИЕ ТРЕБОВАНИЯ ===
984
9851. ОБЪЕМ И СТРУКТУРА:
9863800-4200 символов, только текст описания
987   • ЕСТЕСТВЕННАЯ СТРУКТУРА: Введение → Основная часть → Практическая часть → Заключение
988   • НЕ допускай резких переходов между темами
989
9902. ИСПОЛЬЗОВАНИЕ ЗАПРОСОВ:
991   • ОБЯЗАТЕЛЬНО используй базовые ключевые слова: ${keywords.join(', ')} - минимум 1 раз каждое
992   • ОБЯЗАТЕЛЬНО проверь перед отправкой, что ВСЕ базовые ключи присутствуют в тексте
993   • ИСПОЛЬЗУЙ МАКСИМУМ запросов из списка выше (80-90%)
994   • Запросы можно СКЛОНЯТЬ и адаптировать для естественности текста
995   • Пример ПРАВИЛЬНО: "добавка для увеличения тестостерона для мужчин" (вместо "тестостерон для мужчин в этом комплексе")
996   • Пример ПРАВИЛЬНО: "витамины для спокойствия с магнием" (вместо "витамины для спокойствия - это магний")
997   • Главное - сохранить СМЫСЛ и КЛЮЧЕВЫЕ СЛОВА из запроса, но сделать текст естественным
998   • Шаблон: "[Запрос из списка, можно склонять] с [компонент из состава] [действие] [эффект]"
999**Распределение**: 30% введение/основная, 40% практическая, 30% заключение
1000
10013. ИСПОЛЬЗОВАНИЕ СОСТАВА:
1002   • ОБЯЗАТЕЛЬНО используй ВСЕ компоненты из состава: ${productInfo.composition}
1003   • Каждый компонент используй 2-4 раз в разных контекстах
1004   • Не перечисляй, а вплетай в повествование
1005   • Объединяй схожие компоненты в группы
1006
10074. ПЛОТНОСТЬ И СВЯЗНОСТЬ:
1008   • Каждые 50-70 символов — новый запрос из списка
1009   • Начинай с самых популярных запросов (они в начале списка)
1010   • Чередуй: 1 SEO-предложение → 1-2 естественных предложения
1011   • Создавай логические переходы между абзацами
1012   • ВАЖНО: Текст должен быть естественным и логичным, не жертвуй смыслом ради точного совпадения
1013
10145. СТРУКТУРА ПОВЕСТВОВАНИЯ (разделяй на абзацы):
1015
1016   ЧАСТЬ 1: ВВЕДЕНИЕ (10-15% текста)
1017   • Без заголовка, общее описание товара и его назначение, без слов инновационный, революционный
1018   • Основная проблема, которую решает
1019   • Ключевое преимущество
1020
1021   ЧАСТЬ 2: ОСНОВНАЯ ЧАСТЬ (60-70% текста)
1022   • Подробно о составе и действии компонентов
1023   • Группировка по темам: восстановление → увлажнение → защита
1024   • Как работает продукт (механизм действия)
1025   • Исследования эффективности, но без утверждений. Например: "Исследования показывают, что ...."
1026
1027   ЧАСТЬ 3: ПРАКТИЧЕСКАЯ ЧАСТЬ (15-20% текста)
1028   • Для кого подходит (естественный переход через "Благодаря...")
1029   • Когда лучше принимать (время, до / после еды, до / во время тренировок или что то другое) и с какими ещё витаминами сочетается. Не пиши сколько капсул принимать.
1030    Ожидаемые результаты и преимущества, но без обещаний и эффектов. Например: "Наши покупатели отмечают, что ... 
1031
1032   ЧАСТЬ 4: ЗАКЛЮЧЕНИЕ (5-10% текста)
1033   • Краткое резюме ключевых преимуществ
1034   • Естественный завершающий акцент без рекомендаций про врачей
1035
10366. ЗАПРЕТЫ:
1037   ✗ ЛЮБЫЕ английские слова (СТРОГО только русский язык, даже для научных терминов)
1038Crucial, essential, vital, key, important, testosterone, energy - переводи на русский: ключевой, важный, существенный, тестостерон, энергия
1039   ✗ Бренды, конкуренты, фамилии, названия компаний
1040"Вода": "эликсир", "герой", "ритуал", "скажет спасибо", "настоящий", "буквально"
1041   ✗ Повторы одной фразы (используй синонимы и вариации)
1042   ✗ Инструкционный стиль в конце (никаких "Хранить при температуре...")
1043   ✗ Резкие переходы между темами
1044   ✗ Маркированные списки () - используй только текст
1045   ✗ Нелогичные конструкции типа "тестостерон для мужчин в этом комплексе - это не просто добавка"
1046
10477. ПРОВЕРКА (перед ответом):
1048   ✅ ОБЯЗАТЕЛЬНО использованы ВСЕ базовые ключевые слова: ${keywords.join(', ')} - минимум 1 раз каждое (ПРОВЕРЬ!)
1049   ✅ ОБЯЗАТЕЛЬНО использованы ВСЕ компоненты из состава: ${productInfo.composition} (ПРОВЕРЬ!)
1050   ✅ Использовано 50+ разных запросов из списка
1051   ✅ Нет английских слов (ПРОВЕРЬ на английские слова!)
1052   ✅ Объем 3800-4200 символов
1053   ✅ Плотность: 1.5-2.0 запроса на 100 символов
1054   ✅ Естественная структура повествования
1055   ✅ Логичные переходы между абзацами
1056   ✅ ВСЕ слова переведены на русский (crucial → ключевой, testosterone → тестостерон, energy → энергия)
1057   ✅ Текст естественный и логичный, запросы склонены правильно
1058
1059=== НАЧНИ ОПИСАНИЕ СРАЗУ ===
1060
1061НЕ ПИШИ вступлений вроде "Я готов помочь" или "Мне нужны данные".
1062НЕ ПРОСИ дополнительные данные.
1063НЕ ЗАДАВАЙ вопросы.
1064НЕ ИСПОЛЬЗУЙ: 
1065✗ Слова революционный, инновационный, инвестируйте
1066✗ ЛЮБЫЕ английские слова (crucial, essential, vital, key, testosterone, energy → переводи на русский)
1067✗ Описание неактивных компонентов (тальк, целлюлоза, стеариновая кислота)
1068✗ Не используй заголовки, вопросы и эмоджи !!!
1069✗ Маркированные списки ()
1070
1071ПРОСТО СГЕНЕРИРУЙ SEO-ОПИСАНИЕ на основе предоставленных данных.
1072
1073ВАЖНО: Склоняй запросы для естественности текста, но сохраняй все ключевые слова из них!
1074
1075ПЕРЕД ОТПРАВКОЙ ПРОВЕРЬ:
10761. Все ли базовые ключевые слова (${keywords.join(', ')}) присутствуют в тексте?
10772. Все ли компоненты состава (${productInfo.composition}) упомянуты?
10783. Нет ли английских слов?
1079
1080Сгенерируй описание:`;
1081
1082            console.log('Wildberries Description Generator: Этап 2 - Генерация описания');
1083            
1084            // Генерируем описание с помощью AI
1085            const description = await RM.aiCall(descriptionPrompt);
1086            
1087            console.log('Wildberries Description Generator: Описание сгенерировано');
1088            
1089            // Анализируем использованные ключи
1090            const analysis = await analyzeUsedKeywords(description);
1091            
1092            // Проверяем длину
1093            const charCount = description.length;
1094            
1095            // Сохраняем описание
1096            await GM.setValue('wb_generated_description', description);
1097            
1098            // Показываем результат
1099            resultDiv.textContent = description;
1100            resultContainer.style.display = 'block';
1101            
1102            // Обновляем счетчик символов с информацией о ключах
1103            const popularityInfo = `${charCount} / 5000 символов | Использовано ${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} запросов | Общая популярность: ${formatNumber(analysis.totalPopularity)}`;
1104            charCountDiv.textContent = popularityInfo;
1105            
1106            // Показываем кнопки
1107            generateBtn.style.display = 'none';
1108            regenerateBtn.style.display = 'inline-block';
1109            insertBtn.style.display = 'inline-block';
1110            
1111            showStatus(statusContainer, `Описание успешно сгенерировано! (${charCount} символов)`, 'success');
1112            
1113        } catch (error) {
1114            console.error('Wildberries Description Generator: Ошибка при генерации описания:', error);
1115            showStatus(statusContainer, 'Ошибка при генерации описания. Попробуйте еще раз.', 'error');
1116        } finally {
1117            generateBtn.disabled = false;
1118        }
1119    }
1120
1121    // Функция для показа статуса
1122    function showStatus(container, message, type) {
1123        container.innerHTML = `<div class="wb-desc-status ${type}">${message}</div>`;
1124    }
1125
1126    // Функция для вставки описания
1127    async function insertDescription(modal) {
1128        console.log('Wildberries Description Generator: Вставка описания');
1129        
1130        try {
1131            const description = await GM.getValue('wb_generated_description', '');
1132            
1133            if (!description) {
1134                alert('Описание не найдено. Пожалуйста, сгенерируйте описание сначала.');
1135                return;
1136            }
1137            
1138            // Находим поле описания
1139            const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
1140            
1141            if (!descriptionTextarea) {
1142                alert('Не удалось найти поле описания. Убедитесь, что вы находитесь на странице редактирования товара.');
1143                return;
1144            }
1145            
1146            // Вставляем описание
1147            descriptionTextarea.value = description;
1148            descriptionTextarea.dispatchEvent(new Event('input', { bubbles: true }));
1149            descriptionTextarea.dispatchEvent(new Event('change', { bubbles: true }));
1150            
1151            console.log('Wildberries Description Generator: Описание успешно вставлено');
1152            
1153            // Закрываем модальное окно
1154            modal.remove();
1155            
1156            alert('Описание успешно вставлено!');
1157            
1158        } catch (error) {
1159            console.error('Wildberries Description Generator: Ошибка при вставке описания:', error);
1160            alert('Ошибка при вставке описания. Попробуйте еще раз.');
1161        }
1162    }
1163
1164    // Функция для инициализации расширения
1165    function init() {
1166        console.log('Wildberries Description Generator: Инициализация');
1167        
1168        // Проверяем, что мы на странице редактирования товара
1169        if (window.location.href.includes('seller.wildberries.ru/new-goods/card')) {
1170            
1171            // Ждем загрузки страницы и добавляем кнопку
1172            const observer = new MutationObserver((mutations, obs) => {
1173                const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
1174                if (descriptionHeader) {
1175                    createGeneratorButton();
1176                    obs.disconnect();
1177                }
1178            });
1179            
1180            observer.observe(document.body, {
1181                childList: true,
1182                subtree: true
1183            });
1184            
1185            // Также пробуем добавить кнопку сразу
1186            setTimeout(createGeneratorButton, 2000);
1187        }
1188        
1189        // Проверяем, что мы на странице аналитики и запускаем автосбор
1190        if (window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
1191            setTimeout(autoCollectOnAnalyticsPage, 2000);
1192        }
1193    }
1194
1195    // Запускаем инициализацию
1196    if (document.readyState === 'loading') {
1197        document.addEventListener('DOMContentLoaded', init);
1198    } else {
1199        init();
1200    }
1201
1202})();