Wildberries Description Generator 2.0

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

Size

87.0 KB

Version

2.8.4

Created

Jan 15, 2026

Updated

3 months ago

1// ==UserScript==
2// @name		Wildberries Description Generator 2.0
3// @description		Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов
4// @version		2.8.4
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        .wb-desc-usage-link {
252            color: #6366f1;
253            text-decoration: underline;
254            cursor: pointer;
255            font-size: 14px;
256        }
257        
258        .wb-desc-usage-link:hover {
259            color: #4f46e5;
260        }
261        
262        .wb-desc-analytics-modal {
263            position: fixed;
264            top: 0;
265            left: 0;
266            width: 100%;
267            height: 100%;
268            background: rgba(0, 0, 0, 0.5);
269            display: flex;
270            align-items: center;
271            justify-content: center;
272            z-index: 10001;
273        }
274        
275        .wb-desc-analytics-content {
276            background: white;
277            border-radius: 12px;
278            padding: 24px;
279            max-width: 800px;
280            width: 90%;
281            max-height: 80vh;
282            overflow-y: auto;
283            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
284        }
285        
286        .wb-desc-query-item {
287            padding: 8px 12px;
288            margin-bottom: 4px;
289            border-radius: 6px;
290            font-size: 15px;
291            display: flex;
292            justify-content: space-between;
293            align-items: center;
294        }
295        
296        .wb-desc-query-item.used {
297            background: #d1fae5;
298            color: #065f46;
299        }
300        
301        .wb-desc-query-item.unused {
302            background: #f3f4f6;
303            color: #6b7280;
304        }
305        
306        .wb-desc-query-text {
307            flex: 1;
308        }
309        
310        .wb-desc-query-popularity {
311            font-weight: 600;
312            margin-left: 12px;
313        }
314    `);
315
316    // Добавьте в начало скрипта:
317    let lastUrl = location.href;
318
319    // Мониторим изменения URL (для SPA)
320    new MutationObserver(() => {
321        const url = location.href;
322        if (url !== lastUrl) {
323            lastUrl = url;
324            console.log('Wildberries Description Generator: URL изменился');
325        
326            // Если перешли на страницу товара - инициализируем
327            if (url.includes('seller.wildberries.ru/new-goods/card')) {
328                setTimeout(init, 1000);
329            }
330        }
331    }).observe(document, { subtree: true, childList: true });
332
333    // Также отслеживаем history API
334    const originalPushState = history.pushState;
335    history.pushState = function() {
336        originalPushState.apply(this, arguments);
337        setTimeout(() => {
338            if (location.href.includes('seller.wildberries.ru/new-goods/card')) {
339                console.log('Wildberries Description Generator: History API навигация');
340                setTimeout(init, 500);
341            }
342        }, 100);
343    };
344
345    // Функция для создания кнопки генератора
346    function createGeneratorButton() {
347        const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
348        
349        if (!descriptionHeader) {
350            console.log('Wildberries Description Generator: Заголовок описания не найден');
351            return;
352        }
353        
354        // Проверяем, не добавлена ли уже кнопка
355        if (document.querySelector('.wb-desc-generator-btn')) {
356            console.log('Wildberries Description Generator: Кнопка уже добавлена');
357            return;
358        }
359        
360        const button = document.createElement('button');
361        button.className = 'wb-desc-generator-btn';
362        button.textContent = 'Генератор описаний';
363        button.type = 'button';
364        button.addEventListener('click', function() {
365            // Автоматически открываем ингредиенты
366            const expandButton = document.querySelector('div.Characteristics__expand__570w3PkC7D button');
367            if (expandButton) expandButton.click();
368    
369            // Открываем модальное окно
370            setTimeout(openModal, 500);
371        });
372        
373        descriptionHeader.appendChild(button);
374        console.log('Wildberries Description Generator: Кнопка добавлена');
375    }
376
377    // Функция для открытия модального окна
378    function openModal() {
379        
380        console.log('Wildberries Description Generator: Открытие модального окна');
381        
382        const modal = document.createElement('div');
383        modal.className = 'wb-desc-modal';
384        modal.innerHTML = `
385            <div class="wb-desc-modal-content">
386                <div class="wb-desc-modal-header">Генератор описаний для Wildberries</div>
387                
388                <div class="wb-desc-input-group">
389                    <label class="wb-desc-label">Введите ключевые слова (каждое с новой строки):</label>
390                    <textarea class="wb-desc-textarea" id="wb-keywords-input" placeholder="Например:&#10;витамины&#10;иммунитет&#10;здоровье"></textarea>
391                    <button class="wb-desc-suggest-btn" id="wb-suggest-keywords-btn">Предложить дополнительные ключи</button>
392                    <div id="wb-suggested-keywords-container" style="display: none;"></div>
393                </div>
394                
395                <div class="wb-desc-input-group">
396                    <label class="wb-desc-label">Минус-слова (каждое с новой строки):</label>
397                    <textarea class="wb-desc-textarea" style="min-height: 80px;" id="wb-minus-words-input" placeholder="Например:&#10;mixit&#10;nivea&#10;магнит"></textarea>
398                </div>
399                
400                <div id="wb-desc-result-container" style="display: none;">
401                    <div class="wb-desc-label">Сгенерированное описание:</div>
402                    <div class="wb-desc-result" id="wb-desc-result"></div>
403                    <div class="wb-desc-char-count" id="wb-char-count"></div>
404                </div>
405                
406                <div id="wb-desc-status-container"></div>
407                
408                <div class="wb-desc-buttons">
409                    <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-btn">Закрыть</button>
410                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-generate-btn">Сгенерировать</button>
411                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-btn" style="display: none;">Перегенерировать</button>
412                    <button class="wb-desc-btn wb-desc-btn-success" id="wb-insert-btn" style="display: none;">Вставить в описание</button>
413                </div>
414            </div>
415        `;
416        
417        document.body.appendChild(modal);
418        
419        // Обработчики событий
420        modal.addEventListener('click', (e) => {
421            if (e.target === modal) {
422                modal.remove();
423            }
424        });
425        
426        document.getElementById('wb-close-btn').addEventListener('click', () => {
427            modal.remove();
428        });
429        
430        document.getElementById('wb-suggest-keywords-btn').addEventListener('click', () => {
431            suggestKeywords(modal);
432        });
433        
434        document.getElementById('wb-generate-btn').addEventListener('click', () => {
435            generateDescription(modal);
436        });
437        
438        document.getElementById('wb-regenerate-btn').addEventListener('click', () => {
439            generateDescription(modal);
440        });
441        
442        document.getElementById('wb-insert-btn').addEventListener('click', () => {
443            insertDescription(modal);
444        });
445    }
446
447    // Функция для предложения дополнительных ключей
448    async function suggestKeywords(modal) {
449        const keywordsInput = document.getElementById('wb-keywords-input');
450        const suggestBtn = document.getElementById('wb-suggest-keywords-btn');
451        const suggestedContainer = document.getElementById('wb-suggested-keywords-container');
452        const statusContainer = document.getElementById('wb-desc-status-container');
453        
454        const keywordsText = keywordsInput.value.trim();
455        
456        if (!keywordsText) {
457            showStatus(statusContainer, 'Пожалуйста, сначала введите базовые ключевые слова', 'error');
458            return;
459        }
460        
461        const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
462        
463        suggestBtn.disabled = true;
464        showStatus(statusContainer, 'AI анализирует товар и подбирает ключи...', 'info');
465        
466        try {
467            // Получаем информацию о товаре
468            const productInfo = getProductInfo();
469            
470            // Запрос к AI для предложения ключей
471            const suggestPrompt = `Проанализируй товар и предложи дополнительные ключевые слова для сбора аналитики Wildberries.
472
473ДАННЫЕ О ТОВАРЕ:
474• Название: ${productInfo.title || 'не указано'}
475• Состав: ${productInfo.composition || 'не указан'}
476• Базовые ключи: ${keywords.join(', ')}
477
478ЗАДАЧА:
479Предложи 10-15 дополнительных релевантных ключевых слов для поиска в аналитике Wildberries.
480
481ВАЖНО: 
482- Предлагай СИНОНИМЫ и АЛЬТЕРНАТИВНЫЕ названия товара
483- НЕ расширяй базовый ключ (например, если "масло для загара" → НЕ предлагай "сухое масло для загара")
484- Предлагай ДРУГИЕ типы продуктов с тем же назначением
485- Учитывай состав и назначение товара
486- НЕ предлагай бренды или названия конкурентов
487- Ключи должны быть короткими (1-4 слова)
488
489ПРИМЕРЫ ПРАВИЛЬНЫХ предложений:
490- Базовый ключ: "масло для загара" → Предложи: "средство для загара", "лошон для загара", "усилитель загара", "активатор загара", "крем для загара"
491- Базовый ключ: "пенка для умывания" → Предложи: "средство для умывания", "гель для умывания", "для умывания лица", "очищающее средство"
492- Базовый ключ: "тестостерон" → Предложи: "для тестостерона", "повышение тестостерона", "бустер тестостерона", "тестобустер"
493- Базовый ключ: "витамины" → Предложи: "витаминный комплекс", "мультивитамины", "бад", "добавка"
494
495ПРИМЕРЫ НЕПРАВИЛЬНЫХ предложений (НЕ ДЕЛАЙ ТАК):
496- Базовый ключ: "масло для загара" → ❌ "сухое масло для загара", "натуральное масло для загара" (это расширение базового ключа)
497- Базовый ключ: "пенка для умывания" → ❌ "косметика", "уход за кожей", "красота" (слишком общие)
498- Базовый ключ: "тестостерон" → ❌ "здоровье", "бады" (слишком общие)
499
500ВАЖНО: 
501- Предлагай ДРУГИЕ способы назвать тот же товар
502- Думай как покупатель: "Как еще можно назвать этот товар?"
503- НЕ добавляй прилагательные к базовому ключу
504
505Верни ТОЛЬКО список ключей в формате JSON:
506{
507  "keywords": ["ключ 1", "ключ 2", "ключ 3", ...]
508}
509
510НЕ ПИШИ ничего кроме JSON. Начни ответ сразу с {`;
511
512            console.log('Wildberries Description Generator: Запрос предложений ключей от AI');
513            
514            const suggestResponse = await RM.aiCall(suggestPrompt);
515            
516            // Парсим ответ
517            let suggestedKeywords = [];
518            try {
519                const suggestData = JSON.parse(suggestResponse);
520                suggestedKeywords = suggestData.keywords || [];
521                console.log(`Wildberries Description Generator: AI предложил ${suggestedKeywords.length} ключей`);
522            } catch (e) {
523                console.error('Wildberries Description Generator: Ошибка парсинга предложенных ключей:', e);
524                showStatus(statusContainer, 'Ошибка при получении предложений. Попробуйте еще раз.', 'error');
525                return;
526            }
527            
528            if (suggestedKeywords.length === 0) {
529                showStatus(statusContainer, 'AI не смог предложить дополнительные ключи', 'error');
530                return;
531            }
532            
533            // Показываем предложенные ключи с чекбоксами
534            suggestedContainer.innerHTML = `
535                <div class="wb-desc-suggested-keywords">
536                    <div class="wb-desc-suggested-header">
537                        Предложенные ключи (выберите нужные):
538                        <button class="wb-desc-suggest-btn" id="wb-toggle-all-btn" style="margin-left: 12px; padding: 4px 12px; font-size: 12px;">Выделить все</button>
539                    </div>
540                    <div class="wb-desc-checkbox-group">
541                        ${suggestedKeywords.map((keyword, index) => `
542                            <div class="wb-desc-checkbox-item">
543                                <input type="checkbox" id="wb-suggested-${index}" value="${keyword}">
544                                <label for="wb-suggested-${index}">${keyword}</label>
545                            </div>
546                        `).join('')}
547                    </div>
548                </div>
549            `;
550            
551            suggestedContainer.style.display = 'block';
552            
553            // Добавляем обработчик для кнопки "Выделить все"
554            const toggleAllBtn = document.getElementById('wb-toggle-all-btn');
555            toggleAllBtn.addEventListener('click', () => {
556                const checkboxes = document.querySelectorAll('#wb-suggested-keywords-container input[type="checkbox"]');
557                const allChecked = Array.from(checkboxes).every(cb => cb.checked);
558                
559                checkboxes.forEach(cb => {
560                    cb.checked = !allChecked;
561                });
562                
563                toggleAllBtn.textContent = allChecked ? 'Выделить все' : 'Снять выделение';
564            });
565            
566            showStatus(statusContainer, `AI предложил ${suggestedKeywords.length} дополнительных ключей. Выберите нужные и нажмите "Сгенерировать"`, 'success');
567            
568        } catch (error) {
569            console.error('Wildberries Description Generator: Ошибка при предложении ключей:', error);
570            showStatus(statusContainer, 'Ошибка при получении предложений. Попробуйте еще раз.', 'error');
571        } finally {
572            suggestBtn.disabled = false;
573        }
574    }
575
576    // Функция для сбора данных с аналитики
577    async function collectAnalyticsData(keywords, minusWords) {
578        console.log('Wildberries Description Generator: Начало сбора данных с аналитики');
579        
580        // Сохраняем данные для доступа из другой вкладки
581        await GM.setValue('wb_keywords_to_process', JSON.stringify(keywords));
582        await GM.setValue('wb_minus_words', JSON.stringify(minusWords));
583        await GM.setValue('wb_analytics_data', JSON.stringify([]));
584        await GM.setValue('wb_collection_status', 'pending');
585        
586        // Открываем страницу аналитики в новой вкладке
587        const analyticsUrl = 'https://seller.wildberries.ru/search-analytics/popular-search-queries';
588        await GM.openInTab(analyticsUrl, false);
589        
590        console.log('Wildberries Description Generator: Открыта страница аналитики, ожидание сбора данных...');
591        
592        // Ждем завершения сбора данных (максимум 5 минут)
593        const maxWaitTime = 300000; // 5 минут
594        const checkInterval = 2000; // проверяем каждые 2 секунды
595        let waitedTime = 0;
596        
597        while (waitedTime < maxWaitTime) {
598            await new Promise(resolve => setTimeout(resolve, checkInterval));
599            waitedTime += checkInterval;
600            
601            const status = await GM.getValue('wb_collection_status', 'pending');
602            
603            if (status === 'completed') {
604                const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
605                const analyticsData = JSON.parse(analyticsDataStr);
606                console.log('Wildberries Description Generator: Данные успешно собраны');
607                return analyticsData;
608            } else if (status === 'error') {
609                console.error('Wildberries Description Generator: Ошибка при сборе данных');
610                return [];
611            }
612        }
613        
614        console.error('Wildberries Description Generator: Превышено время ожидания сбора данных');
615        return [];
616    }
617
618    // Функция для автоматического сбора данных на странице аналитики
619    async function autoCollectOnAnalyticsPage() {
620        // Проверяем, что мы на странице аналитики
621        if (!window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
622            return;
623        }
624        
625        console.log('Wildberries Description Generator: Обнаружена страница аналитики');
626        
627        // Проверяем, есть ли задача на сбор данных
628        const status = await GM.getValue('wb_collection_status', 'none');
629        if (status !== 'pending') {
630            return;
631        }
632        
633        console.log('Wildberries Description Generator: Начинаем автоматический сбор данных');
634        
635        try {
636            const keywordsStr = await GM.getValue('wb_keywords_to_process', '[]');
637            const minusWordsStr = await GM.getValue('wb_minus_words', '[]');
638            const productAnalysisStr = await GM.getValue('wb_product_analysis', '{}');
639            const keywords = JSON.parse(keywordsStr);
640            const minusWords = JSON.parse(minusWordsStr);
641            const productAnalysis = JSON.parse(productAnalysisStr);
642            
643            console.log('Wildberries Description Generator: AI-критерии фильтрации:', productAnalysis);
644            
645            const analyticsData = [];
646            
647            // Ждем загрузки страницы
648            await new Promise(resolve => setTimeout(resolve, 3000));
649            
650            for (const keyword of keywords) {
651                console.log(`Wildberries Description Generator: Обработка ключевого слова: ${keyword}`);
652                
653                try {
654                    // Находим поле поиска
655                    const searchInput = document.querySelector('input[name="searchString"]');
656                    if (!searchInput) {
657                        console.error('Wildberries Description Generator: Поле поиска не найдено');
658                        continue;
659                    }
660                    
661                    // Очищаем и вводим ключевое слово
662                    searchInput.value = '';
663                    searchInput.focus();
664                    searchInput.value = keyword;
665                    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
666                    searchInput.dispatchEvent(new Event('change', { bubbles: true }));
667                    
668                    // Ждем загрузки результатов
669                    await new Promise(resolve => setTimeout(resolve, 5000));
670                    
671                    // Собираем данные из таблицы
672                    const rows = document.querySelectorAll('table tbody tr');
673                    const keywordData = {
674                        keyword: keyword,
675                        queries: []
676                    };
677                    
678                    console.log(`Wildberries Description Generator: Найдено строк в таблице: ${rows.length}`);
679                    
680                    rows.forEach(row => {
681                        const cells = row.querySelectorAll('td');
682                        if (cells.length >= 2) {
683                            const query = cells[0]?.textContent?.trim();
684                            const popularity = cells[1]?.textContent?.trim();
685                            
686                            if (query && popularity) {
687                                const queryLower = query.toLowerCase();
688                                
689                                // 1. Фильтруем по минус-словам
690                                const hasMinusWord = minusWords.some(minusWord => 
691                                    queryLower.includes(minusWord.toLowerCase())
692                                );
693                                
694                                if (hasMinusWord) {
695                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
696                                    return;
697                                }
698                                
699                                // 2. Автоматическая фильтрация брендов и стран
700                                const autoBanList = [
701                                    'mixit', 'axis', 'nivea', 'garnier', 'loreal', 'maybelline', 'vichy', 'bioderma',
702                                    'эвалар', 'солгар', 'now foods', 'доппельгерц', 'артнео', 'гельтек',
703                                    'корея', 'корейск', 'япония', 'японск', 'франция', 'французск', 'америк', 'китай', 'китайск',
704                                    'купить', 'цена', 'отзыв', 'инструкция', 'доставка'
705                                ];
706                                
707                                const hasAutoBan = autoBanList.some(banned => 
708                                    queryLower.includes(banned)
709                                );
710                                
711                                if (hasAutoBan) {
712                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (автофильтр: бренд/страна)`);
713                                    return;
714                                }
715                                
716                                // 3. УМНАЯ фильтрация английских слов с использованием AI-критериев
717                                const hasEnglish = /[a-z]/i.test(query);
718                                
719                                if (hasEnglish) {
720                                    // Проверяем, разрешено ли это английское слово по AI-критериям
721                                    const allowedWords = productAnalysis.allowed_english_words || [];
722                                    const isAllowedEnglish = allowedWords.some(allowed => 
723                                        queryLower.includes(allowed.toLowerCase())
724                                    );
725                                    
726                                    if (!isAllowedEnglish) {
727                                        // Проверяем, похож ли запрос на базовый ключ (с учетом замены латиницы)
728                                        const normalizeText = (text) => {
729                                            const latinToCyrillic = {
730                                                'a': 'а', 'A': 'А', 'e': 'е', 'E': 'Е', 'o': 'о', 'O': 'О',
731                                                'p': 'р', 'P': 'Р', 'c': 'с', 'C': 'С', 'y': 'у', 'Y': 'У',
732                                                'x': 'х', 'X': 'Х', 'k': 'к', 'K': 'К', 'h': 'н', 'H': 'Н',
733                                                'b': 'в', 'B': 'В', 'm': 'м', 'M': 'М', 't': 'т', 'T': 'Т'
734                                            };
735                                            return text.split('').map(char => latinToCyrillic[char] || char).join('').toLowerCase();
736                                        };
737                                        
738                                        const normalizedQuery = normalizeText(query);
739                                        const isSimilarToUserKeyword = keywords.some(k => {
740                                            const normalizedKeyword = normalizeText(k);
741                                            return normalizedQuery === normalizedKeyword;
742                                        });
743                                        
744                                        if (!isSimilarToUserKeyword) {
745                                            console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит неразрешенные английские слова)`);
746                                            return;
747                                        }
748                                    } else {
749                                        console.log(`Wildberries Description Generator: Разрешен запрос "${query}" (содержит разрешенное английское слово)`);
750                                    }
751                                }
752                                
753                                // 4. УМНАЯ фильтрация по назначению с использованием AI-критериев
754                                const excludedPurposes = productAnalysis.excluded_purposes || [];
755                                const hasExcludedPurpose = excludedPurposes.some(excluded => 
756                                    queryLower.includes(excluded.toLowerCase())
757                                );
758                                
759                                if (hasExcludedPurpose) {
760                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (неподходящее назначение по AI-критериям)`);
761                                    return;
762                                }
763                                
764                                // Если все проверки пройдены - добавляем запрос
765                                keywordData.queries.push({
766                                    query,
767                                    popularity
768                                });
769                            }
770                        }
771                    });
772                    
773                    analyticsData.push(keywordData);
774                    console.log(`Wildberries Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
775                    
776                } catch (error) {
777                    console.error(`Wildberries Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
778                }
779            }
780            
781            // Сохраняем собранные данные
782            await GM.setValue('wb_analytics_data', JSON.stringify(analyticsData));
783            await GM.setValue('wb_collection_status', 'completed');
784            
785            console.log('Wildberries Description Generator: Сбор данных завершен, можно закрыть вкладку');
786            
787            // Закрываем вкладку через 2 секунды
788            setTimeout(() => {
789                window.close();
790            }, 2000);
791            
792        } catch (error) {
793            console.error('Wildberries Description Generator: Ошибка при автоматическом сборе данных:', error);
794            await GM.setValue('wb_collection_status', 'error');
795        }
796    }
797
798    // Функция для получения информации о товаре со страницы
799    function getProductInfo() {
800        console.log('Wildberries Description Generator: Сбор информации о товаре');
801        
802        const productInfo = {
803            title: '',
804            currentDescription: '',
805            composition: '',
806            attributes: []
807        };
808        
809        // 1. Получаем текущее описание
810        const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
811        if (descriptionTextarea) {
812            productInfo.currentDescription = descriptionTextarea.value || '';
813            console.log('Wildberries Description Generator: Текущее описание найдено');
814        }
815        
816        // 2. Получаем название товара - ИСПРАВЛЕННЫЙ ПОИСК
817        // Способ 1: По id "editable-title" (contenteditable)
818        const editableTitle = document.querySelector('#editable-title');
819        if (editableTitle) {
820            // Для contenteditable элементов берем textContent или innerText
821            productInfo.title = editableTitle.textContent || editableTitle.innerText || '';
822            console.log('Wildberries Description Generator: Название найдено по #editable-title');
823        }
824        
825        // Способ 2: Поиск по label "Наименование"
826        if (!productInfo.title) {
827            // Находим label с текстом "Наименование"
828            const labels = document.querySelectorAll('label, span, div');
829            const nameLabel = Array.from(labels).find(el => 
830                el.textContent && el.textContent.trim() === 'Наименование'
831            );
832            
833            if (nameLabel) {
834                console.log('Wildberries Description Generator: Найден label "Наименование"');
835                // Ищем поле ввода рядом с label
836                const parent = nameLabel.closest('div, .field-wrapper, .form-group');
837                if (parent) {
838                    // Ищем input или contenteditable
839                    const input = parent.querySelector('input, textarea, [contenteditable="true"]');
840                    if (input) {
841                        if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
842                            productInfo.title = input.value || '';
843                        } else {
844                            productInfo.title = input.textContent || input.innerText || '';
845                        }
846                        console.log('Wildberries Description Generator: Название найдено рядом с label');
847                    }
848                }
849            }
850        }
851        
852        // Способ 3: Общий поиск input
853        if (!productInfo.title) {
854            const nameInput = document.querySelector('input[name*="name"], input[placeholder*="Название"], input[placeholder*="наименование"]');
855            if (nameInput) {
856                productInfo.title = nameInput.value || '';
857                console.log('Wildberries Description Generator: Название найдено через общий поиск');
858            }
859        }
860        
861        // 3. Получаем состав товара
862        const compositionBlock = document.querySelector('div#Состав');
863        if (compositionBlock) {
864            console.log('Wildberries Description Generator: Найден блок "Состав"');
865            
866            let ingredients = [];
867            
868            // Способ 1: По data-testid
869            const byTestId = compositionBlock.querySelectorAll('[data-testid^="undefined-select-item-"]');
870            if (byTestId.length > 0) {
871                ingredients = Array.from(byTestId).map(el => el.textContent.trim()).filter(Boolean);
872                console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по data-testid`);
873            } 
874            // Способ 2: По классу
875            else {
876                const byClass = compositionBlock.querySelectorAll('.Selected-item__text__6P8EDRPmWD');
877                if (byClass.length > 0) {
878                    ingredients = Array.from(byClass).map(el => el.textContent.trim()).filter(Boolean);
879                    console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по классу`);
880                }
881            }
882            
883            // Способ 3: Поиск в чипах
884            if (ingredients.length === 0) {
885                const chips = compositionBlock.querySelectorAll('.Selected-item__fIyMG5Li-v, .New-multi-select-input__selected-item__KOh9hWF-7q');
886                chips.forEach(chip => {
887                    const text = chip.textContent.trim();
888                    const cleanText = text.replace(/remove$/, '').trim();
889                    if (cleanText) {
890                        ingredients.push(cleanText);
891                    }
892                });
893                console.log(`Wildberries Description Generator: Извлечено ${ingredients.length} ингредиентов по чипам`);
894            }
895            
896            // Объединяем ингредиенты
897            if (ingredients.length > 0) {
898                productInfo.composition = ingredients.join(', ');
899                console.log('Wildberries Description Generator: Состав товара извлечен');
900            } else {
901                console.log('Wildberries Description Generator: Ингредиенты не найдены в блоке "Состав"');
902            }
903        } else {
904            console.log('Wildberries Description Generator: Блок "Состав" не найден');
905            
906            // Старый способ
907            const compositionTextarea = document.querySelector('textarea[placeholder*="Состав"], textarea[name*="composition"]');
908            if (compositionTextarea && compositionTextarea.value) {
909                productInfo.composition = compositionTextarea.value;
910                console.log('Wildberries Description Generator: Состав найден через textarea');
911            }
912        }
913        
914        // 4. Дополнительные атрибуты (опционально)
915        try {
916            const attributes = {};
917            
918            // Цвет
919            const colorElement = document.querySelector('div#Цвет');
920            if (colorElement) {
921                const colorText = colorElement.querySelector('[data-testid^="undefined-select-item-"]')?.textContent;
922                if (colorText) attributes.color = colorText.trim();
923            }
924            
925            // Бренд
926            const brandInput = document.querySelector('input[placeholder*="Бренд"], input[name*="brand"]');
927            if (brandInput && brandInput.value) {
928                attributes.brand = brandInput.value;
929            }
930            
931            if (Object.keys(attributes).length > 0) {
932                productInfo.attributes = Object.entries(attributes).map(([key, value]) => `${key}: ${value}`);
933            }
934        } catch (e) {
935            console.log('Wildberries Description Generator: Ошибка при сборе атрибутов:', e);
936        }
937        
938        console.log('Wildberries Description Generator: Информация о товаре собрана', {
939            titleLength: productInfo.title.length,
940            descriptionLength: productInfo.currentDescription.length,
941            compositionLength: productInfo.composition.length,
942            attributesCount: productInfo.attributes.length
943        });
944        
945        return productInfo;
946    }
947
948    // Функция для анализа использованных ключей и расчета популярности
949    async function analyzeUsedKeywords(description) {
950        console.log('Wildberries Description Generator: Анализ использованных ключей');
951        
952        const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
953        const analyticsData = JSON.parse(analyticsDataStr);
954        
955        const descriptionLower = description.toLowerCase();
956        const usedQueries = [];
957        const usedQueriesSet = new Set(); // Для отслеживания уникальных запросов
958        let totalPopularity = 0;
959        
960        // Проходим по всем собранным запросам
961        analyticsData.forEach(keywordData => {
962            keywordData.queries.forEach(queryData => {
963                const query = queryData.query.toLowerCase();
964                
965                // Проверяем, используется ли запрос И не был ли уже добавлен
966                if (descriptionLower.includes(query) && !usedQueriesSet.has(query)) {
967                    // Парсим популярность (убираем пробелы)
968                    const popularityStr = queryData.popularity.replace(/\s/g, '');
969                    const popularity = parseInt(popularityStr) || 0;
970                    
971                    usedQueries.push({
972                        query: queryData.query,
973                        popularity: popularity
974                    });
975                    
976                    usedQueriesSet.add(query); // Добавляем в Set для отслеживания
977                    totalPopularity += popularity;
978                }
979            });
980        });
981        
982        const totalQueriesAvailable = analyticsData.reduce((sum, kd) => sum + kd.queries.length, 0);
983        
984        console.log(`Wildberries Description Generator: Использовано ${usedQueries.length} запросов из ${totalQueriesAvailable} доступных`);
985        console.log(`Wildberries Description Generator: Общая популярность: ${totalPopularity}`);
986        
987        return {
988            usedQueries,
989            totalPopularity,
990            totalQueriesAvailable
991        };
992    }
993
994    // Функция для форматирования числа с разделителями
995    function formatNumber(num) {
996        return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
997    }
998
999    // Функция для показа детальной аналитики использования запросов
1000    async function showUsageAnalytics() {
1001        console.log('Wildberries Description Generator: Открытие детальной аналитики');
1002        
1003        const description = await GM.getValue('wb_generated_description', '');
1004        const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
1005        const analyticsData = JSON.parse(analyticsDataStr);
1006        
1007        if (!description || !analyticsData || analyticsData.length === 0) {
1008            alert('Нет данных для анализа');
1009            return;
1010        }
1011        
1012        const descriptionLower = description.toLowerCase();
1013        
1014        // Собираем все запросы с информацией об использовании
1015        const allQueriesWithUsage = [];
1016        const uniqueQueriesSet = new Set(); // Для отслеживания уникальных запросов
1017        
1018        analyticsData.forEach(keywordData => {
1019            keywordData.queries.forEach(queryData => {
1020                const query = queryData.query;
1021                const queryLower = query.toLowerCase();
1022                
1023                // Пропускаем если уже добавили этот запрос
1024                if (uniqueQueriesSet.has(queryLower)) {
1025                    return;
1026                }
1027                
1028                uniqueQueriesSet.add(queryLower);
1029                
1030                const popularityStr = queryData.popularity.replace(/\s/g, '');
1031                const popularity = parseInt(popularityStr) || 0;
1032                
1033                const isUsed = descriptionLower.includes(queryLower);
1034                
1035                allQueriesWithUsage.push({
1036                    query: queryData.query,
1037                    popularity: popularity,
1038                    isUsed: isUsed,
1039                    keyword: keywordData.keyword
1040                });
1041            });
1042        });
1043        
1044        // Сортируем: сначала использованные, потом по популярности
1045        allQueriesWithUsage.sort((a, b) => {
1046            if (a.isUsed && !b.isUsed) return -1;
1047            if (!a.isUsed && b.isUsed) return 1;
1048            return b.popularity - a.popularity;
1049        });
1050        
1051        const usedCount = allQueriesWithUsage.filter(q => q.isUsed).length;
1052        const unusedCount = allQueriesWithUsage.filter(q => !q.isUsed).length;
1053        
1054        // Создаем модальное окно с аналитикой
1055        const analyticsModal = document.createElement('div');
1056        analyticsModal.className = 'wb-desc-analytics-modal';
1057        analyticsModal.innerHTML = `
1058            <div class="wb-desc-analytics-content">
1059                <div class="wb-desc-modal-header">Детальная аналитика использования запросов</div>
1060                
1061                <div style="margin-bottom: 16px;">
1062                    <input type="text" id="wb-search-queries" placeholder="Поиск по запросам..." style="width: 100%; padding: 10px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 15px;">
1063                </div>
1064                
1065                <div style="margin-bottom: 16px; font-size: 15px;">
1066                    <div style="color: #065f46; font-weight: 600;">✅ Использовано: <span id="wb-used-count">${usedCount}</span> запросов</div>
1067                    <div style="color: #6b7280; font-weight: 600;">❌ Не использовано: <span id="wb-unused-count">${unusedCount}</span> запросов</div>
1068                    <div style="margin-top: 12px; color: #6b7280; font-size: 14px;">
1069                        Нажмите ❌ чтобы исключить запрос или ➕ чтобы добавить
1070                    </div>
1071                </div>
1072                
1073                <div id="wb-queries-list" style="max-height: 500px; overflow-y: auto;">
1074                    ${allQueriesWithUsage.map((item, index) => `
1075                        <div class="wb-desc-query-item ${item.isUsed ? 'used' : 'unused'}" data-query="${item.query}" data-index="${index}" data-used="${item.isUsed}">
1076                            <div style="display: flex; align-items: center; gap: 8px; flex: 1;">
1077                                <button class="wb-query-toggle-btn" style="background: none; border: none; cursor: pointer; font-size: 18px; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">
1078                                    ${item.isUsed ? '❌' : '➕'}
1079                                </button>
1080                                <div class="wb-desc-query-text">
1081                                    ${item.isUsed ? '✅ ' : '❌ '} ${item.query}
1082                                </div>
1083                            </div>
1084                            <div class="wb-desc-query-popularity">${formatNumber(item.popularity)}</div>
1085                        </div>
1086                    `).join('')}
1087                </div>
1088                
1089                <div class="wb-desc-buttons">
1090                    <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-analytics-btn">Закрыть</button>
1091                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-with-changes-btn" style="display: none;">Перегенерировать</button>
1092                </div>
1093            </div>
1094        `;
1095        
1096        document.body.appendChild(analyticsModal);
1097        
1098        // Отслеживаем изменения
1099        let hasChanges = false;
1100        const originalState = new Map();
1101        allQueriesWithUsage.forEach((item, index) => {
1102            originalState.set(index, item.isUsed);
1103        });
1104        
1105        // Обработчик для поиска
1106        const searchInput = document.getElementById('wb-search-queries');
1107        searchInput.addEventListener('input', (e) => {
1108            const searchTerm = e.target.value.toLowerCase();
1109            const queryItems = analyticsModal.querySelectorAll('.wb-desc-query-item');
1110            
1111            queryItems.forEach(item => {
1112                const query = item.dataset.query.toLowerCase();
1113                if (query.includes(searchTerm)) {
1114                    item.style.display = 'flex';
1115                } else {
1116                    item.style.display = 'none';
1117                }
1118            });
1119        });
1120        
1121        // Обработчик для кнопок переключения
1122        const regenerateBtn = document.getElementById('wb-regenerate-with-changes-btn');
1123        const usedCountSpan = document.getElementById('wb-used-count');
1124        const unusedCountSpan = document.getElementById('wb-unused-count');
1125        
1126        analyticsModal.querySelectorAll('.wb-query-toggle-btn').forEach((btn) => {
1127            btn.addEventListener('click', () => {
1128                const queryItem = btn.closest('.wb-desc-query-item');
1129                const isCurrentlyUsed = queryItem.dataset.used === 'true';
1130                const newUsedState = !isCurrentlyUsed;
1131                
1132                // Обновляем состояние
1133                queryItem.dataset.used = newUsedState;
1134                
1135                // Обновляем визуал
1136                if (newUsedState) {
1137                    queryItem.classList.remove('unused');
1138                    queryItem.classList.add('used');
1139                    btn.textContent = '❌';
1140                    queryItem.querySelector('.wb-desc-query-text').innerHTML = 
1141                        '✅ ' + queryItem.dataset.query;
1142                } else {
1143                    queryItem.classList.remove('used');
1144                    queryItem.classList.add('unused');
1145                    btn.textContent = '➕';
1146                    queryItem.querySelector('.wb-desc-query-text').innerHTML = 
1147                        '❌ ' + queryItem.dataset.query;
1148                }
1149                
1150                // Обновляем счетчики
1151                const currentUsedCount = analyticsModal.querySelectorAll('.wb-desc-query-item[data-used="true"]').length;
1152                const currentUnusedCount = allQueriesWithUsage.length - currentUsedCount;
1153                usedCountSpan.textContent = currentUsedCount;
1154                unusedCountSpan.textContent = currentUnusedCount;
1155                
1156                // Проверяем, есть ли изменения
1157                hasChanges = false;
1158                analyticsModal.querySelectorAll('.wb-desc-query-item').forEach((item, idx) => {
1159                    const currentState = item.dataset.used === 'true';
1160                    const originalStateValue = originalState.get(idx);
1161                    if (currentState !== originalStateValue) {
1162                        hasChanges = true;
1163                    }
1164                });
1165                
1166                // Показываем/скрываем кнопку перегенерации
1167                if (hasChanges) {
1168                    regenerateBtn.style.display = 'inline-block';
1169                } else {
1170                    regenerateBtn.style.display = 'none';
1171                }
1172            });
1173        });
1174        
1175        // Обработчик для перегенерации с изменениями
1176        regenerateBtn.addEventListener('click', async () => {
1177            // Собираем список запросов для исключения (те, которые были использованы, но теперь отключены)
1178            const excludedQueries = [];
1179            const includedQueries = [];
1180            
1181            analyticsModal.querySelectorAll('.wb-desc-query-item').forEach((item, index) => {
1182                const query = item.dataset.query;
1183                const isCurrentlyUsed = item.dataset.used === 'true';
1184                const wasOriginallyUsed = originalState.get(index);
1185                
1186                // Если был использован, но теперь отключен - исключаем
1187                if (wasOriginallyUsed && !isCurrentlyUsed) {
1188                    excludedQueries.push(query);
1189                }
1190                
1191                // Если не был использован, но теперь включен - добавляем в приоритет
1192                if (!wasOriginallyUsed && isCurrentlyUsed) {
1193                    includedQueries.push(query);
1194                }
1195            });
1196            
1197            console.log(`Wildberries Description Generator: Исключено ${excludedQueries.length} запросов, добавлено ${includedQueries.length} запросов`);
1198            
1199            // Сохраняем изменения
1200            await GM.setValue('wb_excluded_queries', JSON.stringify(excludedQueries));
1201            await GM.setValue('wb_included_queries', JSON.stringify(includedQueries));
1202            
1203            // Закрываем аналитику
1204            analyticsModal.remove();
1205            
1206            // Запускаем перегенерацию БЕЗ открытия нового модального окна
1207            await regenerateWithExclusions();
1208        });
1209        
1210        // Обработчики событий
1211        analyticsModal.addEventListener('click', (e) => {
1212            if (e.target === analyticsModal) {
1213                analyticsModal.remove();
1214            }
1215        });
1216        
1217        document.getElementById('wb-close-analytics-btn').addEventListener('click', () => {
1218            analyticsModal.remove();
1219        });
1220    }
1221
1222    // Функция для генерации описания
1223    async function generateDescription(modal) {
1224        const keywordsInput = document.getElementById('wb-keywords-input');
1225        const minusWordsInput = document.getElementById('wb-minus-words-input');
1226        const generateBtn = document.getElementById('wb-generate-btn');
1227        const regenerateBtn = document.getElementById('wb-regenerate-btn');
1228        const insertBtn = document.getElementById('wb-insert-btn');
1229        const resultContainer = document.getElementById('wb-desc-result-container');
1230        const resultDiv = document.getElementById('wb-desc-result');
1231        const charCountDiv = document.getElementById('wb-char-count');
1232        const statusContainer = document.getElementById('wb-desc-status-container');
1233        
1234        const keywordsText = keywordsInput.value.trim();
1235        const minusWordsText = minusWordsInput.value.trim();
1236        
1237        if (!keywordsText) {
1238            showStatus(statusContainer, 'Пожалуйста, введите ключевые слова', 'error');
1239            return;
1240        }
1241        
1242        let keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1243        const minusWords = minusWordsText.split('\n').map(k => k.trim().toLowerCase()).filter(k => k);
1244        
1245        // Добавляем выбранные дополнительные ключи
1246        const suggestedCheckboxes = document.querySelectorAll('#wb-suggested-keywords-container input[type="checkbox"]:checked');
1247        if (suggestedCheckboxes.length > 0) {
1248            const selectedSuggested = Array.from(suggestedCheckboxes).map(cb => cb.value);
1249            keywords = [...keywords, ...selectedSuggested];
1250            console.log(`Wildberries Description Generator: Добавлено ${selectedSuggested.length} дополнительных ключей`);
1251        }
1252        
1253        if (keywords.length === 0) {
1254            showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
1255            return;
1256        }
1257        
1258        // Показываем загрузку
1259        generateBtn.disabled = true;
1260        regenerateBtn.style.display = 'none';
1261        insertBtn.style.display = 'none';
1262        resultContainer.style.display = 'none';
1263        
1264        // ШАГ 1: AI анализирует товар ДО сбора данных
1265        showStatus(statusContainer, 'AI анализирует товар и создает критерии фильтрации...', 'info');
1266        
1267        try {
1268            // Получаем информацию о товаре
1269            const productInfo = getProductInfo();
1270            
1271            // AI анализирует товар и создает критерии фильтрации
1272            const analysisPrompt = `Проанализируй товар и создай критерии для умной фильтрации поисковых запросов из аналитики Wildberries.
1273
1274ДАННЫЕ О ТОВАРЕ:
1275• Название: ${productInfo.title || 'не указано'}
1276• Состав: ${productInfo.composition || 'не указан'}
1277• Базовые ключи: ${keywords.join(', ')}
1278
1279ЗАДАЧА:
1280Создай критерии фильтрации, чтобы при сборе данных из аналитики мы НЕ ПОТЕРЯЛИ релевантные запросы.
1281
1282ОПРЕДЕЛИ:
1283
12841. КАТЕГОРИЯ ТОВАРА (одна из):
1285   - косметика_лицо (кремы, сыворотки, маски для лица)
1286   - косметика_волосы (шампуни, маски, масла для волос)
1287   - косметика_тело (кремы для тела, скрабы, масла для тела)
1288   - бад_витамины (витамины, минералы, БАДы)
1289   - бад_спорт (спортивное питание, протеины)
1290   - другое
1291
12922. ЦЕЛЕВАЯ АУДИТОРИЯ:
1293   - для_мужчин / для_женщин / для_детей / универсальный
1294
12953. НАЗНАЧЕНИЕ (основное применение):
1296   - Например: "увлажнение кожи лица", "рост волос", "повышение иммунитета"
1297
12984. КЛЮЧЕВЫЕ КОМПОНЕНТЫ (из состава):
1299   - Список главных активных компонентов
1300
13015. РАЗРЕШЕННЫЕ АНГЛИЙСКИЕ СЛОВА/БРЕНДЫ:
1302   - Если в названии есть английские слова (например, "Elementary"), их НУЖНО разрешить
1303   - Список слов, которые можно оставлять в запросах
1304
13056. ИСКЛЮЧАЕМЫЕ НАЗНАЧЕНИЯ:
1306   - Список назначений, которые НЕ подходят для этого товара
1307   - Например, для "сыворотки для лица" исключить: "сыворотка для роста волос", "сыворотка для ресниц", "сыворотка для тела"
1308   - ВАЖНО: Оставляй общие запросы без уточнения назначения (например, "сыворотка", "витамины")
1309
1310ПРИМЕРЫ:
1311
1312Товар: "Elementary Сыворотка для лица с витамином С"
1313Состав: "Аскорбиновая кислота, Ниацинамид, Гиалуроновая кислота"
1314
1315ПРАВИЛЬНЫЙ ОТВЕТ:
1316{
1317  "category": "косметика_лицо",
1318  "target_audience": "универсальный",
1319  "purpose": "увлажнение и осветление кожи лица",
1320  "key_components": ["витамин с", "аскорбиновая кислота", "ниацинамид", "гиалуроновая кислота"],
1321  "allowed_english_words": ["elementary"],
1322  "excluded_purposes": ["сыворотка для роста волос", "сыворотка для ресниц", "сыворотка для тела"]
1323}
1324
1325Верни ТОЛЬКО JSON в формате выше. НЕ ПИШИ ничего кроме JSON. Начни ответ сразу с {`;
1326
1327            console.log('Wildberries Description Generator: AI анализирует товар');
1328            
1329            const analysisResponse = await RM.aiCall(analysisPrompt);
1330            
1331            // Парсим ответ
1332            let productAnalysis;
1333            try {
1334                productAnalysis = JSON.parse(analysisResponse);
1335                console.log('Wildberries Description Generator: AI-анализ товара:', productAnalysis);
1336            } catch (e) {
1337                console.error('Wildberries Description Generator: Ошибка парсинга анализа товара:', e);
1338                showStatus(statusContainer, 'Ошибка при анализе товара. Попробуйте еще раз.', 'error');
1339                return;
1340            }
1341            
1342            // Сохраняем анализ для использования при сборе данных
1343            await GM.setValue('wb_product_analysis', JSON.stringify(productAnalysis));
1344            
1345            // ШАГ 2: Собираем данные с аналитики с умной фильтрацией
1346            showStatus(statusContainer, 'Сбор данных с аналитики (с умной фильтрацией)...', 'info');
1347            
1348            // Собираем данные с аналитики
1349            const analyticsData = await collectAnalyticsData(keywords, minusWords);
1350            
1351            // Собираем все запросы из аналитики
1352            const allQueries = [];
1353            if (analyticsData && analyticsData.length > 0) {
1354                analyticsData.forEach(data => {
1355                    data.queries.forEach(q => {
1356                        allQueries.push(q.query);
1357                    });
1358                });
1359            }
1360            
1361            console.log(`Wildberries Description Generator: Собрано ${allQueries.length} запросов из аналитики`);
1362            
1363            // Проверяем, что данные собраны
1364            if (allQueries.length === 0) {
1365                showStatus(statusContainer, 'Не удалось собрать данные с аналитики. Возможно, страница не загрузилась или изменилась структура. Попробуйте еще раз.', 'error');
1366                return;
1367            }
1368            
1369            // Проверяем, есть ли исключенные запросы из аналитики
1370            const excludedQueriesStr = await GM.getValue('wb_excluded_queries', '[]');
1371            const excludedQueries = JSON.parse(excludedQueriesStr);
1372            
1373            let filteredQueries = allQueries;
1374            
1375            if (excludedQueries.length > 0) {
1376                console.log(`Wildberries Description Generator: Исключаем ${excludedQueries.length} запросов по выбору пользователя`);
1377                const excludedLower = excludedQueries.map(q => q.toLowerCase());
1378                filteredQueries = allQueries.filter(q => !excludedLower.includes(q.toLowerCase()));
1379                console.log(`Wildberries Description Generator: После исключения осталось ${filteredQueries.length} запросов`);
1380                
1381                // Очищаем список исключенных запросов
1382                await GM.setValue('wb_excluded_queries', '[]');
1383            }
1384            
1385            // Генерируем описание с отфильтрованными запросами
1386            showStatus(statusContainer, 'Генерация описания с помощью AI...', 'info');
1387            
1388            const descriptionPrompt = `Создай SEO-описание товара для Wildberries (внутренняя SEO-оптимизация).
1389
1390ДАННЫЕ:
1391• Название: ${productInfo.title || 'не указано'}
1392• Состав: ${productInfo.composition || 'не указан'}
1393• Ключевые слова: ${keywords.join(', ')}
1394
1395ЗАПРОСЫ ДЛЯ ИСПОЛЬЗОВАНИЯ (используй МАКСИМУУ из этого списка):
1396${filteredQueries.map((q, i) => `${i+1}. "${q}"`).join('\\n')}
1397
1398=== ЖЕСТКИЕ ТРЕБОВАНИЯ ===
1399
14001. ОБЪЕМ И СТРУКТУРА:
1401   • 3800-4200 символов, только текст описания
1402   • ЕСТЕСТВЕННАЯ СТРУКТУРА: Введение → Основная часть → Практическая часть → Заключение
1403   • НЕ допускай резких переходов между темами
1404
14052. ИСПОЛЬЗОВАНИЕ ЗАПРОСОВ - КРИТИЧЕСКИ ВАЖНО:
1406   • ОБЯЗАТЕЛЬНО используй базовые ключевые слова: ${keywords.join(', ')} - минимум 1 раз каждое
1407   • ТВОЯ ГЛАВНАЯ ЗАДАЧА: Использовать МАКСИМУТ запросов из списка выше
1408   • ЦЕЛЬ: Минимум 80-90% запросов из списка (${Math.floor(filteredQueries.length * 0.8)}-${Math.floor(filteredQueries.length * 0.9)} из ${filteredQueries.length})
1409   • ЭТО НЕ РЕКОМЕНДАЦИЯ - ЭТО ОБЯЗАТЕЛЬНОЕ ТРЕБОВАНИЕ!
1410   • Список запросов выше - это РЕАЛЬНЫЕ поисковые запросы пользователей Wildberries
1411   • Каждый запрос из списка - это возможность попасть в поиск
1412   • ИСПОЛЬЗУЙ запросы ТОЧНО как они написаны, можно только склонять
1413   • Примеры ПРАВИЛЬНОГО использования:
1414     - Запрос "сыворотка для лица от прыщей" → "Эта сыворотка для лица от прыщей содержит..."
1415     - Запрос "сыворотка с ниацинамидом 10%" → "Сыворотка с ниацинамидом 10% помогает..."
1416     - Запрос "средство от акне для подростков" → "Средство от акне для подростков разработано..."
1417   • НЕ БОЙСЯ повторять похожие конструкции - это SEO-текст!
1418   • Плотность: минимум 2.5-3 запроса на 100 символов
1419
14203. ЛОГИКА ЗАМЕНЫ ДЛЯ ОТСУТСТВУЮЩИХ КОМПОНЕНТОВ:
1421   • Если в запросе упоминается компонент, которого НЕТ в составе - используй логику ЗАМЕНЫ
1422   • Пример: Состав содержит "коллаген", запрос "сыворотка с ретинолом" (ретинола нет в составе)
1423   • ПРАВИЛЬНО: "Сыворотка с коллагеном - это отличная замена сывороток с ретинолом и других антивозрастных средств"
1424   • ПРАВИЛЬНО: "Помощь магния может стать альтернативой добавкам с мелатонином для улучшения сна"
1425   • ПРАВИЛЬНО: "Средство с гиалуроновой кислотой заменяет кремы с коллагеном для увлажнения кожи"
1426   • Шаблон: "[Товар с компонентом из состава] - это отличная замена [запрос с отсутствующим компонентом]"
1427   • Это позволяет использовать ВСЕ запросы, даже если компонента нет в составе!
1428
14294. ИСПОЛЬЗОВАНИЕ СОСТАВА:
1430   • ОБЯЗАТЕЛЬНО используй ВСЕ компоненты из состава: ${productInfo.composition}
1431   • Каждый компонент используй 2-4 раз в разных контекстах
1432   • Не перечисляй, а вплетай в повествование
1433   • Объединяй схожие компоненты в группы
1434
14355. ПЛОТНОСТЬ И СВЯЗНОСТЬ:
1436   • Каждые 30-40 символов — новый запрос из списка
1437   • Начинай с самых популярных запросов (они в начале списка)
1438   • Проходи по списку ПОСЛЕДОВАТЕЛЬНО, используя запросы один за другим
1439   • Создавай логические переходы между абзацами
1440   • ВАЖНО: Используй ВСЕ запросы, даже если компонента нет - через логику замены!
1441
14426. СТРУКТУРА ПОВЕСТВОВАНИЯ (разделяй на абзацы):
1443
1444   ЧАСТЬ 1: ВВЕДЕНИЕ (10-15% текста)
1445   • Без заголовка, общее описание товара и его назначения, без слов инновационный, революционный
1446   • Основная проблема, которую решает
1447   • Ключевое преимущество
1448   • ИСПОЛЬЗУЙ 30-40 запросов из списка
1449
1450   ЧАСТЬ 2: ОСНОВНАЯ ЧАСТЬ (60-70% текста)
1451   • Подробно о составе и действии компонентов
1452   • Группировка по темам: восстановление → увлажнение → защита
1453   • Как работает продукт (механизм действия)
1454   • Исследования эффективности, но без утверждений. Например: "Исследования показывают, что ...."
1455   • ИСПОЛЬЗУЙ 80-100 запросов из списка
1456
1457   ЧАСТЬ 3: ПРАКТИЧЕСКАЯ ЧАСТЬ (15-20% текста)
1458   • Для кого подходит (естественный переход через "Благодаря...")
1459   • Когда лучше принимать (время, до / после еды, до / во время тренировок или что то другое) и с какими ещё витаминами сочетается. Не пиши сколько капсул принимать.
1460   • Ожидаемые результаты и преимущества, но без обещаний и эффектов. Например: "Наши покупатели отмечают, что ..."
1461   • ИСПОЛЬЗУЙ 30-40 запросов из списка
1462
1463   ЧАСТЬ 4: ЗАКЛЮЧЕНИЕ (5-10% текста)
1464   • Краткое резюме ключевых преимуществ
1465   • Естественный завершающий акцент без рекомендаций про врачей
1466   • ИСПОЛЬЗУЙ 20-30 запросов из списка
1467
14687. ЗАПРЕТЫ:
1469   ✗ ЛЮБЫЕ английские слова (СТРОГО только русский язык, даже для научных терминов)
1470   ✗ Crucial, essential, vital, key, important, testosterone, energy - переводи на русский: ключевой, важный, существенный, тестостерон, энергия
1471   ✗ Бренды, конкуренты, фамилии, названия компаний
1472   ✗ "Вода": "эликсир", "герой", "ритуал", "скажет спасибо", "настоящий", "буквально"
1473   ✗ Повторы одной фразы (используй синонимы и вариации)
1474   ✗ Инструкционный стиль в конце (никаких "Хранить при температуре...")
1475   ✗ Резкие переходы между темами
1476   ✗ Маркированные списки (•) - используй только текст
1477
14788. ПРОВЕРКА (перед ответом):
1479   ✅ ОБЯЗАТЕЛЬНО использованы ВСЕ базовые ключевые слова: ${keywords.join(', ')} - минимум 1 раз каждое (ПРОВЕРЬ!)
1480   ✅ ОБЯЗАТЕЛЬНО использованы ВСЕ компоненты из состава: ${productInfo.composition} (ПРОВЕРЬ!)
1481   ✅ Использовано ли минимум ${Math.floor(filteredQueries.length * 0.8)} запросов из ${filteredQueries.length} (ПРОВЕРЬ!)
1482   ✅ Нет английских слов (ПРОВЕРЬ на английские слова!)
1483   ✅ Объем 3800-4200 символов
1484   ✅ Плотность: минимум 2.5 запроса на 100 символов
1485   ✅ Естественная структура повествования
1486   ✅ Логичные переходы между абзацами
1487   ✅ ВСЕ слова переведены на русский (crucial → ключевой, testosterone → тестостерон, energy → энергия)
1488
1489=== НАЧНИ ОПИСАНИЕ СРАЗУ ===
1490
1491НЕ ПИШИ вступлений вроде "Я готов помочь" или "Мне нужны данные".
1492НЕ ПРОСИ дополнительные данные.
1493НЕ ЗАДАВАЙ вопросы.
1494НЕ ИСПОЛЬЗУЙ: 
1495✗ Слова революционный, инновационный, инвестируйте
1496✗ ЛЮБЫЕ английские слова (crucial, essential, vital, key, testosterone, energy → переводи на русский)
1497✗ Описание неактивных компонентов (тальк, целлюлоза, стеариновая кислота)
1498✗ Не используй заголовки, вопросы и эмоджи !!!
1499✗ Маркированные списки (•)
1500
1501ПРОСТО СГЕНЕРИРУЙ SEO-ОПИСАНИЕ на основе предоставленных данных.
1502
1503ВАЖНО: 
15041. Твоя задача - вплести в текст МАКСИМУТ запросов из списка! 
15052. Проходи по списку последовательно и используй каждый запрос!
15063. Если компонента из запроса нет в составе - используй логику ЗАМЕНЫ!
1507
1508ПЕРЕД ОТПРАВКОЙ ПРОВЕРЬ:
15091. Все ли базовые ключевые слова (${keywords.join(', ')}) присутствуют в тексте?
15102. Все ли компоненты состава (${productInfo.composition}) упомянуты?
15113. Использовано ли минимум ${Math.floor(filteredQueries.length * 0.8)} запросов из ${filteredQueries.length}?
15124. Нет ли английских слов?
1513
1514Сгенерируй описание:`;
1515
1516            console.log('Wildberries Description Generator: Генерация описания');
1517            
1518            // Генерируем описание с помощью AI
1519            const description = await RM.aiCall(descriptionPrompt);
1520            
1521            console.log('Wildberries Description Generator: Описание сгенерировано');
1522            
1523            // ЖЕСТКАЯ ПОСТОБРАБОТКА: Заменяем английские слова на русские
1524            let cleanedDescription = description;
1525            
1526            // Словарь замен английских слов
1527            const englishToRussian = {
1528                'crucial': 'ключевой',
1529                'Crucial': 'Ключевой',
1530                'essential': 'важный',
1531                'Essential': 'Важный',
1532                'vital': 'жизненно важный',
1533                'Vital': 'Жизненно важный',
1534                'key': 'ключевой',
1535                'Key': 'Ключевой',
1536                'important': 'важный',
1537                'Important': 'Важный',
1538                'testosterone': 'тестостерон',
1539                'Testosterone': 'Тестостерон',
1540                'energy': 'энергия',
1541                'Energy': 'Энергия'
1542            };
1543            
1544            // Заменяем все английские слова
1545            Object.entries(englishToRussian).forEach(([eng, rus]) => {
1546                const regex = new RegExp('\\b' + eng + '\\b', 'g');
1547                cleanedDescription = cleanedDescription.replace(regex, rus);
1548            });
1549            
1550            console.log('Wildberries Description Generator: Постобработка завершена');
1551            
1552            // Анализируем использованные ключи
1553            const analysis = await analyzeUsedKeywords(cleanedDescription);
1554            
1555            // Проверяем длину
1556            const charCount = cleanedDescription.length;
1557            
1558            // Сохраняем описание
1559            await GM.setValue('wb_generated_description', cleanedDescription);
1560            
1561            // Показываем результат
1562            resultDiv.textContent = cleanedDescription;
1563            resultContainer.style.display = 'block';
1564            
1565            // Обновляем счетчик символов с информацией о ключах (делаем кликабельным)
1566            const popularityInfo = `${charCount} / 5000 символов | <span class="wb-desc-usage-link" id="wb-usage-link">Использовано ${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} запросов</span> | Общая популярность: ${formatNumber(analysis.totalPopularity)}`;
1567            charCountDiv.innerHTML = popularityInfo;
1568            
1569            // Добавляем обработчик клика на ссылку
1570            const usageLink = document.getElementById('wb-usage-link');
1571            if (usageLink) {
1572                usageLink.addEventListener('click', showUsageAnalytics);
1573            }
1574            
1575            // Показываем кнопки
1576            generateBtn.style.display = 'none';
1577            regenerateBtn.style.display = 'inline-block';
1578            insertBtn.style.display = 'inline-block';
1579            
1580            showStatus(statusContainer, `Описание успешно сгенерировано! (${charCount} символов)`, 'success');
1581            
1582        } catch (error) {
1583            console.error('Wildberries Description Generator: Ошибка при генерации описания:', error);
1584            showStatus(statusContainer, 'Ошибка при генерации описания. Попробуйте еще раз.', 'error');
1585        } finally {
1586            generateBtn.disabled = false;
1587        }
1588    }
1589
1590    // Функция для показа статуса
1591    function showStatus(container, message, type) {
1592        container.innerHTML = `<div class="wb-desc-status ${type}">${message}</div>`;
1593    }
1594
1595    // Функция для перегенерации с исключенными запросами
1596    async function regenerateWithExclusions() {
1597        console.log('Wildberries Description Generator: Перегенерация с исключениями');
1598        
1599        // Проверяем, открыто ли уже модальное окно
1600        const existingModal = document.querySelector('.wb-desc-modal');
1601        if (existingModal) {
1602            console.log('Wildberries Description Generator: Модальное окно уже открыто, запускаем генерацию');
1603            // Если окно уже открыто, просто запускаем генерацию
1604            const generateBtn = document.getElementById('wb-generate-btn');
1605            if (generateBtn) {
1606                generateBtn.click();
1607            }
1608            return;
1609        }
1610        
1611        // Открываем модальное окно
1612        openModal();
1613        
1614        // Ждем, пока модальное окно откроется
1615        await new Promise(resolve => setTimeout(resolve, 100));
1616        
1617        // Автоматически запускаем генерацию
1618        const generateBtn = document.getElementById('wb-generate-btn');
1619        if (generateBtn) {
1620            generateBtn.click();
1621        }
1622    }
1623
1624    // Функция для вставки описания
1625    async function insertDescription(modal) {
1626        console.log('Wildberries Description Generator: Вставка описания');
1627        
1628        try {
1629            const description = await GM.getValue('wb_generated_description', '');
1630            
1631            if (!description) {
1632                alert('Описание не найдено. Пожалуйста, сгенерируйте описание сначала.');
1633                return;
1634            }
1635            
1636            // Находим поле описания
1637            const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
1638            
1639            if (!descriptionTextarea) {
1640                alert('Не удалось найти поле описания. Убедитесь, что вы находитесь на странице редактирования товара.');
1641                return;
1642            }
1643            
1644            // Вставляем описание
1645            descriptionTextarea.value = description;
1646            descriptionTextarea.dispatchEvent(new Event('input', { bubbles: true }));
1647            descriptionTextarea.dispatchEvent(new Event('change', { bubbles: true }));
1648            
1649            console.log('Wildberries Description Generator: Описание успешно вставлено');
1650            
1651            // Закрываем модальное окно
1652            modal.remove();
1653            
1654            alert('Описание успешно вставлено!');
1655            
1656        } catch (error) {
1657            console.error('Wildberries Description Generator: Ошибка при вставке описания:', error);
1658            alert('Ошибка при вставке описания. Попробуйте еще раз.');
1659        }
1660    }
1661
1662    // Функция для инициализации расширения
1663    function init() {
1664        console.log('Wildberries Description Generator: Инициализация');
1665        
1666        // Проверяем, что мы на странице редактирования товара
1667        if (window.location.href.includes('seller.wildberries.ru/new-goods/card')) {
1668            
1669            // Ждем загрузки страницы и добавляем кнопку
1670            const observer = new MutationObserver((mutations, obs) => {
1671                const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
1672                if (descriptionHeader) {
1673                    createGeneratorButton();
1674                    obs.disconnect();
1675                }
1676            });
1677            
1678            observer.observe(document.body, {
1679                childList: true,
1680                subtree: true
1681            });
1682            
1683            // Также пробуем добавить кнопку сразу
1684            setTimeout(createGeneratorButton, 2000);
1685        }
1686        
1687        // Проверяем, что мы на странице аналитики и запускаем автосбор
1688        if (window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
1689            setTimeout(autoCollectOnAnalyticsPage, 2000);
1690        }
1691    }
1692
1693    // Запускаем инициализацию
1694    if (document.readyState === 'loading') {
1695        document.addEventListener('DOMContentLoaded', init);
1696    } else {
1697        init();
1698    }
1699
1700})();