Wildberries Description Generator 2.0

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

Size

97.4 KB

Version

2.9.5

Created

Jan 15, 2026

Updated

about 1 month ago

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