Wildberries Description Generator 3.0

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

Size

151.5 KB

Version

3.1.15

Created

Jan 20, 2026

Updated

28 days ago

1// ==UserScript==
2// @name		Wildberries Description Generator 3.0
3// @description		Генератор SEO-описаний для товаров на Wildberries с анализом ключевых слов
4// @version		3.1.15
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 3.0: Расширение запущено');
12
13    // ============================================
14    // УТИЛИТЫ
15    // ============================================
16
17    function debounce(func, wait) {
18        let timeout;
19        return function executedFunction(...args) {
20            clearTimeout(timeout);
21            timeout = setTimeout(() => func(...args), wait);
22        };
23    }
24
25    function formatNumber(num) {
26        if (num >= 1000000) {
27            return (num / 1000000).toFixed(1) + 'M';
28        } else if (num >= 1000) {
29            return (num / 1000).toFixed(1) + 'K';
30        }
31        return num.toString();
32    }
33
34    function waitForElement(selector, timeout = 10000) {
35        return new Promise((resolve, reject) => {
36            const element = document.querySelector(selector);
37            if (element) {
38                return resolve(element);
39            }
40            
41            const observer = new MutationObserver(() => {
42                const element = document.querySelector(selector);
43                if (element) {
44                    observer.disconnect();
45                    resolve(element);
46                }
47            });
48            
49            observer.observe(document.body, {
50                childList: true,
51                subtree: true
52            });
53            
54            setTimeout(() => {
55                observer.disconnect();
56                reject(new Error(`Element ${selector} not found within ${timeout}ms`));
57            }, timeout);
58        });
59    }
60
61    // ============================================
62    // AI PROMPT GENERATORS
63    // ============================================
64
65    function generateMasksPrompt(productInfo) {
66        return `Ты — эксперт по SEO на Wildberries. Предложи поисковые маски и ключевые слова для товара.
67
68ДАННЫЕ О ТОВАРЕ:
69
70• Название: ${productInfo.title || 'не указано'}
71
72• Состав: ${productInfo.composition || 'не указан'}
73
74ЗАДАЧА:
75
76Предложи 2 типа запросов:
77
781. МАСКИ (15 штук) — короткие слова для поиска в аналитике WB:
79  - 1-2 слова максимум
80  - По маске система покажет все связанные запросы
81  - Используй как отдельные слова, так и с предлогами
82  - Пример: "сыворотка" → найдёт "сыворотка для лица", "сыворотка от морщин" и т.д.
83  - Пример с предлогами: "сыворотка для", "сыворотка от", "сыворотка против", "сыворотка с" и т.д.
84
852. КЛЮЧЕВЫЕ СЛОВА (15 штук) — точные запросы покупателей:
86  - 2-4 слова
87  - Конкретные фразы, которые вбивают в поиск
88  - Пример: "сыворотка для лица увлажняющая", "витамин с для кожи", "сыворотка с ниацинамидом"
89
90КАТЕГОРИИ ДЛЯ МАСОК:
91- Тип товара (2 маски): "сыворотка", "крем", "витамины"
92- Тип товара + Предлог (5 масок): "сыворотка для", "крем против", "витамины для", "лосьон с", "магний для"
93- Синонимы (2 маски): "серум", "концентрат", "бад"
94- Назначение (2 маски): "увлажнение", "от морщин", "иммунитет"
95- Ингредиенты из состава (3 маски): "гиалуроновая", "ретинол", "цинк"
96- Эффект (1 маска): "омоложение", "укрепление"
97
98КАТЕГОРИИ ДЛЯ КЛЮЧЕВЫХ СЛОВ:
99- Товар + назначение (5 ключа): "сыворотка для лица", "сыворотка с витамином с", "сыворотка от пигментных пятен", "серум для лица увлажняющий", "осветляющая сыворотка"
100- Товар + аудитория (2 ключа): "витамины для мужчин", "крем для сухой кожи"
101- Товар + ингредиент (5 ключа): "сыворотка с витамином с", "шампунь с кератином"
102- Товар + эффект (5 ключа): "крем от морщин", "бад для иммунитета"
103
104ПРАВИЛА:
105- Русский язык (кроме spf, ph)
106- НЕ бренды и НЕ конкуренты
107- Все слова уникальные, без повторов
108- Ингредиенты брать из состава товара
109
110ПРИМЕРЫ:
111
112Для "Сыворотка для лица с витамином С" (состав: аскорбиновая кислота, ниацинамид):
113{
114 "masks": ["сыворотка", "сыворотка для", "сыворотка от", "сыворотка с", "сыворотка против", "серум", "витамин с", "аскорбиновая", "ниацинамид", "для лица", "увлажнение", "осветление", "от пигментации", "антиоксидант"],
115 "keywords": ["сыворотка для лица", "сыворотка с витамином с", "сыворотка от пигментных пятен", "серум для лица увлажняющий", "осветляющая сыворотка", "сыворотка от морщин", "витамин с для кожи", "антивозрастная сыворотка", "сыворотка для сияния кожи", "ниацинамид сыворотка"]
116}
117
118Для "Витамины для мужчин с цинком" (состав: цинк, селен, витамин D):
119{
120 "masks": ["витамины", "витамины для", "витамины от", "витамины с", "бад", "для мужчин", "цинк", "селен", "витамин д", "иммунитет", "потенция", "энергия", "комплекс"],
121 "keywords": ["витамины для мужчин", "витамины с цинком", "бад для мужчин", "цинк для мужчин", "витамины для иммунитета", "мужские витамины комплекс", "витамины для потенции", "селен для мужчин", "витамин д для мужчин", "витамины для энергии"]
122}
123
124Верни ТОЛЬКО JSON:
125
126{
127 "masks": ["маска1", "маска2", ...],
128 "keywords": ["ключ1", "ключ2", ...]
129}
130
131Начни ответ сразу с {`;
132    }
133
134    function generateProductAnalysisPrompt(productInfo, keywords) {
135        return `Проанализируй товар и создай критерии для умной фильтрации поисковых запросов из аналитики Wildberries.
136
137ДАННЫЕ О ТОВАРЕ:
138• Название: ${productInfo.title || 'не указано'}
139• Состав: ${productInfo.composition || 'не указан'}
140• Базовые ключи: ${keywords.join(', ')}
141
142ЗАДАЧА:
143Создай критерии фильтрации, чтобы при сборе данных из аналитики мы НЕ ПОТЕРЯЛИ релевантные запросы.
144
145ОПРЕДЕЛИ:
146
1471. КАТЕГОРИЯ ТОВАРА (одна из):
148   - косметика_лицо (кремы, сыворотки, маски для лица)
149   - косметика_волосы (шампуни, маски, масла для волос)
150   - косметика_тело (кремы для тела, скрабы, масла для тела)
151   - бад_витамины (витамины, минералы, БАДы)
152   - бад_спорт (спортивное питание, протеины)
153   - другое
154
1552. ЦЕЛЕВАЯ АУДИТОРИЯ:
156   - для_мужчин / для_женщин / для_детей / универсальный
157
1583. НАЗНАЧЕНИЕ (основное применение):
159   - Например: "увлажнение кожи лица", "рост волос", "повышение иммунитета"
160
1614. КЛЮЧЕВЫЕ КОМПОНЕНТЫ (из состава):
162   - Список главных активных компонентов
163
1645. РАЗРЕШЕННЫЕ АНГЛИЙСКИЕ СЛОВА/БРЕНДЫ:
165   - Если в названии есть английские слова (например, "Elementary"), их НУЖНО разрешить
166   - Список слов, которые можно оставлять в запросах
167
1686. ИСКЛЮЧАЕМЫЕ НАЗНАЧЕНИЯ:
169   - Список назначений, которые НЕ подходят для этого товара
170   - Например, для "сыворотки для лица" исключить: "сыворотка для роста волос", "сыворотка для ресниц", "сыворотка для тела".
171   - ВАЖНО: Оставляй общие запросы без уточнения назначения (например, "сыворотка", "витамины")
172   - Слова не имеющие общепонятного значения: неофарм, урофарм, либридерм и другие. 
173
174ПРИМЕР:
175
176Товар: "Elementary Сыворотка для лица с витамином С"
177Состав: "Аскорбиновая кислота, Ниацинамид, Гиалуроновая кислота"
178
179ПРАВИЛЬНЫЙ ОТВЕТ:
180{
181  "category": "косметика_лицо",
182  "target_audience": "универсальный",
183  "purpose": "увлажнение и осветление кожи лица",
184  "key_components": ["витамин с", "аскорбиновая кислота", "ниацинамид", "гиалуроновая кислота"],
185  "allowed_english_words": ["elementary"],
186  "excluded_purposes": ["сыворотка для роста волос", "сыворотка для ресниц", "сыворотка для тела"]
187}
188
189Верни ТОЛЬКО JSON в формате выше. НЕ ПИШИ ничего кроме JSON. Начни ответ сразу с {`;
190    }
191
192    function generateDescriptionPrompt(productInfo, keywords, filteredQueries, queryPopularity = {}) {
193        const sortedQueries = filteredQueries
194            .map(q => ({ query: q, pop: queryPopularity[q.toLowerCase()] || 0 }))
195            .sort((a, b) => b.pop - a.pop);
196        
197        const highPriority = sortedQueries.slice(0, 15).map(q => q.query);
198        const mediumPriority = sortedQueries.slice(15, 40).map(q => q.query);
199        const lowPriority = sortedQueries.slice(40, 60).map(q => q.query);
200
201        return `Создай SEO-описание товара для Wildberries.
202
203ДАННЫЕ:
204• Название: ${productInfo.title || 'не указано'}
205• Состав: ${productInfo.composition || 'не указан'}
206• Базовые ключи: ${keywords.join(', ')}
207
208ЗАПРОСЫ ПО ПРИОРИТЕТУ:
209
210🔴 ВЫСОКИЙ ПРИОРИТЕТ (использовать ВСЕ, минимум 1 раз каждый):
211${highPriority.map((q, i) => `${i+1}. "${q}"`).join('\n')}
212
213🟡 СРЕДНИЙ ПРИОРИТЕТ (использовать 60-80%):
214${mediumPriority.length > 0 ? mediumPriority.map((q, i) => `${i+1}. "${q}"`).join('\n') : 'нет запросов'}
215
216🟢 НИЗКИЙ ПРИОРИТЕТ (использовать по возможности):
217${lowPriority.length > 0 ? lowPriority.map((q, i) => `${i+1}. "${q}"`).join('\n') : 'нет запросов'}
218
219ТРЕБОВАНИЯ:
2201) Объем 3500–4000 символов, только текст.
2212) Структура: 
222   - Введение (400-500 симв.) — суть товара, что это, зачем, для кого, что делает кратко.
223   - Проблема и решение (600-800 симв.) — основная проблема, которую решает это средство, к чему приводит проблема. Кратко как этот продукт решает проблему.
224   - Состав и действие (800-1000 симв.) — ингредиенты и их польза. Подробная информация о составе и компонентах которые усиливают друг друга. как они работают, зачем они нужны.
225   - Применение (600-800 симв.) — для кого, как работает
226   - Отзывы покупателей (300-400 симв.) — отдельный абзац, который начинается фразой "Наши покупатели отмечают ... через ...", где описан эффект и сроки появления результата
227   - Заключение (400-500 симв.) — результат для покупателя.  что получит покупатель при использовании средства.
2283) Плавные переходы между частями, без заголовков.
2294) Все базовые ключи — минимум 1 раз.
2305) Каждый запрос использовать не более 2 раз.
2316) Если компонента из запроса нет в составе — используй через «альтернативу» (засчитывается).
2327) Все компоненты состава — 1–2 раза, без списков, вплетай в текст.
2338) Размер текста минимум 3000 символов - ПРОВЕРЬ!
234
235ГРУППИРОВКА ЗАПРОСОВ:
2368) Сгруппируй запросы по смыслу:
237   • Тип/категория товара
238   • Проблемы/назначение
239   • Состав/ингредиенты
240   • Аудитория
241   • Эффект/результат
2429) В каждом абзаце используй запросы только из 1–2 групп.
24310) Не смешивай в одном абзаце «аудиторию», «состав» и «эффект» одновременно.
244
245АНТИСПАМ-ПРАВИЛА:
24611) В каждом абзаце максимум 2 предложения с ключевыми запросами подряд.
24712) Между предложениями с ключами — минимум 1 предложение без ключей.
24813) Не более 20% предложений начинаются с ключевого запроса.
24914) Встраивай ключи в середину предложений, не начинай ими фразы.
25015) Плотность ключей: не более 3 запросов на 100 слов.
25116) Одно и то же ключевое словосочетание — не чаще 1 раза на абзац.
25217) Не ставь два длинных ключа (длиннее 4 слов) в одном предложении.
25318) Чередуй длину предложений: 20–25% короткие (8–12 слов), остальные средние.
254
255СТИЛЬ:
25619) Не повторяй связки «подходит для», «помогает», «обеспечивает» более 2 раз подряд.
25720) Не начинай более 2 предложений подряд с одного слова.
25821) Информативно, конкретные факты, без воды.
259
260ЗАПРЕТЫ:
26122) Только русский язык. Без брендов, компаний, фамилий.
26223) Запрещены слова: «лекарство», «препарат», «революционный», «инновационный», «уникальный».
26324) Без заголовков, списков, вопросов, эмоджи, инструкций хранения и описания неактивных компонентов.
264
265ПРИМЕР ХОРОШЕГО АБЗАЦА (НЕ КОПИРУЙ ДОСЛОВНО, ТОЛЬКО СТИЛЬ):
266Средство с мягкой очищающей основой поддерживает комфорт кожи головы и помогает дольше сохранять ощущение свежести. В ежедневном уходе особенно ценится формула, где бессульфатный шампунь для жирных волос работает деликатно и без перегруза. Благодаря сочетанию цинка и гидролизата протеинов пшеницы волосы выглядят более ухоженными, а кожа головы — более сбалансированной.
267
268ПРИМЕР ПЛОХОГО АБЗАЦА (НЕ ДЕЛАЙ ТАК):
269Шампунь для жирных волос у корней очищает. Шампунь для жирной кожи головы помогает. Шампунь для волос женский укрепляет.
270
271ВЫВОД:
272Начни сразу с описания. Никаких вступлений, вопросов и пояснений.`;
273    }
274
275    // ============================================
276    // СТИЛИ
277    // ============================================
278
279    TM_addStyle(`
280        .wb-desc-modal {
281            position: fixed;
282            top: 0;
283            left: 0;
284            width: 100%;
285            height: 100%;
286            background: rgba(0, 0, 0, 0.5);
287            display: flex;
288            align-items: center;
289            justify-content: center;
290            z-index: 10000;
291        }
292        
293        .wb-desc-modal-content {
294            background: white;
295            border-radius: 12px;
296            padding: 24px;
297            max-width: 700px;
298            width: 90%;
299            max-height: 85vh;
300            overflow-y: auto;
301            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
302        }
303        
304        .wb-desc-modal-header {
305            font-size: 22px;
306            font-weight: 600;
307            margin-bottom: 20px;
308            color: #001a34;
309        }
310        
311        .wb-desc-input-group {
312            margin-bottom: 16px;
313        }
314        
315        .wb-desc-label {
316            display: block;
317            margin-bottom: 8px;
318            font-weight: 500;
319            color: #001a34;
320            font-size: 16px;
321        }
322        
323        .wb-desc-textarea {
324            width: 100%;
325            min-height: 100px;
326            padding: 12px;
327            border: 1px solid #d1d5db;
328            border-radius: 8px;
329            font-size: 16px;
330            font-family: inherit;
331            resize: vertical;
332            box-sizing: border-box;
333        }
334        
335        .wb-desc-result {
336            background: #f3f4f6;
337            padding: 16px;
338            border-radius: 8px;
339            margin-bottom: 16px;
340            max-height: 300px;
341            overflow-y: auto;
342            white-space: pre-wrap;
343            word-wrap: break-word;
344            font-size: 15px;
345            line-height: 1.6;
346        }
347        
348        .wb-desc-char-count {
349            text-align: right;
350            font-size: 14px;
351            color: #6b7280;
352            margin-top: 4px;
353        }
354        
355        .wb-desc-char-count.warning {
356            color: #f59e0b;
357        }
358        
359        .wb-desc-char-count.error {
360            color: #ef4444;
361        }
362        
363        .wb-desc-char-count.success {
364            color: #10b981;
365        }
366        
367        .wb-desc-buttons {
368            display: flex;
369            gap: 12px;
370            justify-content: flex-end;
371            margin-top: 20px;
372            flex-wrap: wrap;
373        }
374        
375        .wb-desc-btn {
376            padding: 10px 20px;
377            border: none;
378            border-radius: 8px;
379            font-size: 16px;
380            font-weight: 500;
381            cursor: pointer;
382            transition: all 0.2s;
383        }
384        
385        .wb-desc-btn-primary {
386            background: linear-gradient(135deg, #9333ea, #7c3aed);
387            color: white;
388        }
389        
390        .wb-desc-btn-primary:hover {
391            background: linear-gradient(135deg, #7e22ce, #6d28d9);
392            transform: translateY(-1px);
393            box-shadow: 0 4px 12px rgba(147, 51, 234, 0.3);
394        }
395        
396        .wb-desc-btn-primary:disabled {
397            background: #9ca3af;
398            cursor: not-allowed;
399            transform: none;
400            box-shadow: none;
401        }
402        
403        .wb-desc-btn-secondary {
404            background: #e5e7eb;
405            color: #374151;
406        }
407        
408        .wb-desc-btn-secondary:hover {
409            background: #d1d5db;
410        }
411        
412        .wb-desc-btn-success {
413            background: linear-gradient(135deg, #10b981, #059669);
414            color: white;
415        }
416        
417        .wb-desc-btn-success:hover {
418            background: linear-gradient(135deg, #059669, #047857);
419            transform: translateY(-1px);
420            box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
421        }
422        
423        .wb-desc-generator-btn {
424            margin-left: 12px;
425            padding: 10px 20px;
426            background: linear-gradient(135deg, #9333ea, #6366f1);
427            color: white;
428            border: none;
429            border-radius: 8px;
430            font-size: 16px;
431            font-weight: 500;
432            cursor: pointer;
433            transition: all 0.2s;
434        }
435        
436        .wb-desc-generator-btn:hover {
437            background: linear-gradient(135deg, #7e22ce, #4f46e5);
438            transform: translateY(-2px);
439            box-shadow: 0 4px 12px rgba(147, 51, 234, 0.4);
440        }
441        
442        .wb-desc-status {
443            margin-top: 12px;
444            padding: 12px 16px;
445            border-radius: 8px;
446            font-size: 15px;
447        }
448        
449        .wb-desc-status.info {
450            background: #dbeafe;
451            color: #1e40af;
452            border-left: 4px solid #3b82f6;
453        }
454        
455        .wb-desc-status.success {
456            background: #d1fae5;
457            color: #065f46;
458            border-left: 4px solid #10b981;
459        }
460        
461        .wb-desc-status.error {
462            background: #fee2e2;
463            color: #991b1b;
464            border-left: 4px solid #ef4444;
465        }
466        
467        .wb-desc-suggest-btn {
468            margin-top: 8px;
469            padding: 8px 16px;
470            background: linear-gradient(135deg, #6366f1, #8b5cf6);
471            color: white;
472            border: none;
473            border-radius: 6px;
474            font-size: 15px;
475            font-weight: 500;
476            cursor: pointer;
477            transition: all 0.2s;
478        }
479        
480        .wb-desc-suggest-btn:hover {
481            background: linear-gradient(135deg, #4f46e5, #7c3aed);
482            transform: translateY(-1px);
483        }
484        
485        .wb-desc-suggest-btn:disabled {
486            background: #9ca3af;
487            cursor: not-allowed;
488            transform: none;
489        }
490        
491        .wb-desc-masks-container {
492            margin-top: 12px;
493            padding: 16px;
494            background: #f9fafb;
495            border-radius: 8px;
496            border: 1px solid #e5e7eb;
497        }
498        
499        .wb-desc-masks-header {
500            font-weight: 600;
501            margin-bottom: 12px;
502            color: #374151;
503            font-size: 15px;
504            display: flex;
505            justify-content: space-between;
506            align-items: center;
507            flex-wrap: wrap;
508            gap: 8px;
509        }
510        
511        .wb-desc-masks-group {
512            margin-bottom: 12px;
513        }
514        
515        .wb-desc-masks-group:last-child {
516            margin-bottom: 0;
517        }
518        
519        .wb-desc-masks-group-title {
520            font-size: 12px;
521            color: #6b7280;
522            margin-bottom: 8px;
523            text-transform: uppercase;
524            letter-spacing: 0.5px;
525        }
526        
527        .wb-desc-masks-grid {
528            display: flex;
529            flex-wrap: wrap;
530            gap: 8px;
531        }
532        
533        .wb-mask-chip {
534            display: inline-flex;
535            align-items: center;
536            gap: 6px;
537            padding: 8px 14px;
538            border-radius: 20px;
539            font-size: 14px;
540            cursor: pointer;
541            transition: all 0.2s;
542            border: 2px solid transparent;
543            user-select: none;
544        }
545        
546        .wb-mask-chip:hover {
547            transform: translateY(-2px);
548            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
549        }
550        
551        .wb-mask-chip.selected {
552            border-color: #10b981;
553            box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
554        }
555        
556        .wb-mask-chip[data-type="маска"] { background: #dbeafe; color: #1e40af; }
557        .wb-mask-chip[data-type="ключ"] { background: #fef3c7; color: #92400e; }
558        .wb-mask-chip[data-type="синоним"] { background: #fce7f3; color: #9d174d; }
559        .wb-mask-chip[data-type="назначение"] { background: #d1fae5; color: #065f46; }
560        .wb-mask-chip[data-type="зона"] { background: #fef3c7; color: #92400e; }
561        .wb-mask-chip[data-type="ингредиент"] { background: #ede9fe; color: #5b21b6; }
562        .wb-mask-chip[data-type="смежный"] { background: #f3f4f6; color: #374151; }
563        
564        .wb-desc-suggested-keywords {
565            margin-top: 12px;
566            padding: 12px;
567            background: #f9fafb;
568            border-radius: 8px;
569            border: 1px солид #e5e7eb;
570        }
571        
572        .wb-desc-suggested-header {
573            font-weight: 600;
574            margin-bottom: 8px;
575            color: #374151;
576            font-size: 15px;
577        }
578        
579        .wb-desc-checkbox-group {
580            display: grid;
581            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
582            gap: 8px;
583            max-height: 200px;
584            overflow-y: auto;
585        }
586        
587        .wb-desc-checkbox-item {
588            display: flex;
589            align-items: center;
590            gap: 8px;
591        }
592        
593        .wb-desc-checkbox-item input[type="checkbox"] {
594            cursor: pointer;
595        }
596        
597        .wb-desc-checkbox-item label {
598           	cursor: pointer;
599            font-size: 15px;
600            color: #374151;
601            margin: 0;
602        }
603        
604        .wb-progress-wrapper {
605            margin: 16px 0;
606        }
607        
608        .wb-progress-label {
609            display: flex;
610            justify-content: space-between;
611            margin-bottom: 8px;
612            font-size: 14px;
613            color: #374151;
614        }
615        
616        .wb-progress-bar-container {
617            width: 100%;
618            height: 8px;
619            background: #e5e7eb;
620            border-radius: 4px;
621            overflow: hidden;
622        }
623        
624        .wb-progress-bar-fill {
625            height: 100%;
626            background: linear-gradient(90deg, #9333ea, #6366f1);
627            transition: width 0.3s ease;
628            border-radius: 4px;
629        }
630        
631        .wb-progress-details {
632            margin-top: 8px;
633            font-size: 13px;
634            color: #6b7280;
635        }
636        
637        .wb-desc-stats {
638            margin-top: 12px;
639            padding: 14px;
640            background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
641            border-radius: 8px;
642            font-size: 14px;
643        }
644        
645        .wb-desc-stats-row {
646            display: flex;
647            justify-content: space-between;
648            margin-bottom: 6px;
649        }
650        
651        .wb-desc-stats-row:last-child {
652            margin-bottom: 0;
653        }
654        
655        .wb-desc-usage-link {
656            color: #6366f1;
657            text-decoration: underline;
658            cursor: pointer;
659            font-size: 14px;
660        }
661        
662        .wb-desc-usage-link:hover {
663            color: #4f46e5;
664        }
665        
666        .wb-desc-analytics-modal {
667            position: fixed;
668            top: 0;
669            left: 0;
670            width: 100%;
671            height: 100%;
672            background: rgba(0, 0, 0, 0.5);
673            display: flex;
674            align-items: center;
675            justify-content: center;
676            z-index: 10001;
677        }
678        
679        .wb-desc-analytics-content {
680            background: white;
681            border-radius: 12px;
682            padding: 24px;
683            max-width: 900px;
684            width: 95%;
685            max-height: 85vh;
686            overflow-y: auto;
687            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
688        }
689        
690        .wb-desc-query-item {
691            padding: 10px 14px;
692            margin-bottom: 6px;
693            border-radius: 8px;
694            font-size: 14px;
695            display: flex;
696            justify-content: space-between;
697            align-items: center;
698            transition: all 0.2s;
699        }
700        
701        .wb-desc-query-item.used {
702            background: #d1fae5;
703            color: #065f46;
704        }
705        
706        .wb-desc-query-item.unused {
707            background: #f3f4f6;
708            color: #6b7280;
709        }
710        
711        .wb-desc-query-item.recoverable {
712            background: #fef3c7;
713            border-left: 3px solid #f59e0b;
714        }
715        
716        .wb-desc-query-text {
717            flex: 1;
718        }
719        
720        .wb-desc-query-popularity {
721            font-weight: 600;
722            margin-left: 12px;
723            min-width: 50px;
724            text-align: right;
725        }
726        
727        .wb-desc-minus-words-section {
728            margin-bottom: 16px;
729            padding: 14px;
730            background: linear-gradient(135deg, #fef3c7, #fde68a);
731            border-radius: 8px;
732            border: 1px solid #fbbf24;
733        }
734        
735        .wb-desc-minus-words-header {
736            font-weight: 600;
737            margin-bottom: 10px;
738            color: #92400e;
739            font-size: 15px;
740        }
741        
742        .wb-desc-minus-words-list {
743            display: flex;
744            flex-wrap: wrap;
745            gap: 8px;
746        }
747        
748        .wb-desc-minus-word-chip {
749            background: #fbbf24;
750            color: #78350f;
751            padding: 6px 12px;
752            border-radius: 16px;
753            font-size: 14px;
754            display: flex;
755            align-items: center;
756            gap: 6px;
757            cursor: pointer;
758            transition: all 0.2s;
759        }
760        
761        .wb-desc-minus-word-chip:hover {
762            background: #f59e0b;
763            transform: translateY(-1px);
764        }
765        
766        .wb-desc-minus-word-remove {
767            font-weight: bold;
768            font-size: 16px;
769        }
770        
771        .wb-desc-query-word {
772            cursor: pointer;
773            padding: 2px 4px;
774            border-radius: 3px;
775            transition: background 0.2s;
776        }
777        
778        .wb-desc-query-word:hover {
779            background: #fef3c7;
780        }
781        
782        .wb-desc-search-input {
783            width: 100%;
784            padding: 12px 14px;
785            border: 1px solid #d1d5db;
786            border-radius: 8px;
787            font-size: 15px;
788            margin-bottom: 16px;
789            box-sizing: border-box;
790        }
791        
792        .wb-desc-search-input:focus {
793            outline: none;
794            border-color: #9333ea;
795            box-shadow: 0 0 0 3px rgba(147, 51, 234, 0.1);
796        }
797        
798        .wb-auto-btn {
799            position: fixed;
800            top: 20px;
801            right: 20px;
802            padding: 12px 24px;
803            background: linear-gradient(135deg, #10b981, #059669);
804            color: white;
805            border: none;
806            border-radius: 8px;
807            font-size: 16px;
808            font-weight: 600;
809            cursor: pointer;
810            transition: all 0.2s;
811            z-index: 9999;
812            box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
813        }
814        
815        .wb-auto-btn:hover {
816            background: linear-gradient(135deg, #059669, #047857);
817            transform: translateY(-2px);
818            box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
819        }
820        
821        .wb-auto-btn:disabled {
822            background: #9ca3af;
823            cursor: not-allowed;
824            transform: none;
825            box-shadow: none;
826        }
827        
828        .wb-progress-modal {
829            position: fixed;
830            top: 20px;
831            right: 20px;
832            background: white;
833            border-radius: 12px;
834            padding: 20px;
835            min-width: 350px;
836            max-width: 400px;
837            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
838            z-index: 10000;
839        }
840        
841        .wb-progress-header {
842            font-size: 18px;
843            font-weight: 600;
844            margin-bottom: 16px;
845            color: #001a34;
846        }
847        
848        .wb-progress-stats {
849            display: flex;
850            gap: 16px;
851            margin-bottom: 16px;
852        }
853        
854        .wb-progress-stat {
855            flex: 1;
856            padding: 12px;
857            border-radius: 8px;
858            text-align: center;
859        }
860        
861        .wb-progress-stat.success {
862            background: linear-gradient(135deg, #d1fae5, #a7f3d0);
863            color: #065f46;
864        }
865        
866        .wb-progress-stat.error {
867            background: linear-gradient(135deg, #fee2e2, #fecaca);
868            color: #991b1b;
869        }
870        
871        .wb-progress-stat-number {
872            font-size: 28px;
873            font-weight: 700;
874            margin-bottom: 4px;
875        }
876        
877        .wb-progress-stat-label {
878            font-size: 12px;
879            text-transform: uppercase;
880            letter-spacing: 0.5px;
881        }
882        
883        .wb-progress-current {
884            padding: 12px;
885            background: #f3f4f6;
886            border-radius: 8px;
887            margin-bottom: 16px;
888            font-size: 14px;
889            color: #374151;
890        }
891        
892        .wb-progress-errors {
893            max-height: 200px;
894            overflow-y: auto;
895            margin-bottom: 16px;
896        }
897        
898        .wb-progress-error-item {
899            padding: 10px 12px;
900            background: #fee2e2;
901            border-radius: 6px;
902            margin-bottom: 8px;
903            font-size: 13px;
904            color: #991b1b;
905            cursor: pointer;
906            transition: background 0.2s;
907        }
908        
909        .wb-progress-error-item:hover {
910            background: #fecaca;
911        }
912        
913        .wb-progress-query-word {
914            cursor: pointer;
915            padding: 2px 4px;
916            border-radius: 3px;
917            transition: background 0.2s;
918        }
919        
920        .wb-progress-query-word:hover {
921            background: #fef3c7;
922        }
923        
924        .wb-progress-buttons {
925            display: flex;
926            gap: 12px;
927        }
928        
929        .wb-progress-btn {
930            flex: 1;
931            padding: 10px;
932            border: none;
933            border-radius: 8px;
934            font-size: 14px;
935            font-weight: 500;
936            cursor: pointer;
937            transition: all 0.2s;
938        }
939        
940        .wb-progress-btn-stop {
941            background: linear-gradient(135deg, #ef4444, #dc2626);
942            color: white;
943        }
944        
945        .wb-progress-btn-stop:hover {
946            background: linear-gradient(135deg, #dc2626, #b91c1c);
947        }
948        
949        .wb-progress-btn-close {
950            background: #e5e7eb;
951            color: #374151;
952        }
953        
954        .wb-progress-btn-close:hover {
955            background: #d1d5db;
956        }
957    `);
958
959    // ============================================
960    // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
961    // ============================================
962
963    let lastUrl = location.href;
964    let autoGenerationStopped = false;
965    
966    // Добавляем глобальные команды для тестирования сразу
967    if (typeof window.WB_TEST === 'undefined') {
968        window.WB_TEST = {
969            enableTestMode: async (startFromIndex = 19) => {
970                await GM.setValue('wb_test_mode', 'true');
971                await GM.setValue('wb_test_start_index', startFromIndex);
972                console.log(`🧪 ТЕСТОВЫЙ РЕЖИМ ВКЛЮЧЕН: Начало с товара #${startFromIndex}`);
973                console.log('Теперь нажмите кнопку "🤖 Авто описание"');
974            },
975            disableTestMode: async () => {
976                await GM.setValue('wb_test_mode', 'false');
977                await GM.setValue('wb_test_start_index', 0);
978                console.log('🧪 ТЕСТОВЫЙ РЕЖИМ ОТКЛЮЧЕН');
979            },
980            checkStatus: async () => {
981                const testMode = await GM.getValue('wb_test_mode', 'false');
982                const startIndex = await GM.getValue('wb_test_start_index', 0);
983                const processed = await GM.getValue('wb_auto_processed_products', '[]');
984                console.log('📊 СТАТУС:');
985                console.log('  Тестовый режим:', testMode);
986                console.log('  Стартовый индекс:', startIndex);
987                console.log('  Обработано товаров:', JSON.parse(processed).length);
988            },
989            reset: async () => {
990                await GM.setValue('wb_auto_processed_products', JSON.stringify([]));
991                await GM.setValue('wb_test_mode', 'false');
992                await GM.setValue('wb_test_start_index', 0);
993                console.log('🔄 ВСЕ НАСТРОЙКИ СБРОШЕНЫ');
994            }
995        };
996        
997        console.log('🧪 ТЕСТОВЫЕ КОМАНДЫ ДОСТУПНЫ:');
998        console.log('  WB_TEST.enableTestMode(19) - включить тест с 19-го товара');
999        console.log('  WB_TEST.disableTestMode() - выключить тестовый режим');
1000        console.log('  WB_TEST.checkStatus() - проверить статус');
1001        console.log('  WB_TEST.reset() - сбросить все настройки');
1002    }
1003
1004    // ============================================
1005    // МОНИТОРИНГ URL (SPA)
1006    // ============================================
1007
1008    const debouncedUrlCheck = debounce(() => {
1009        const url = location.href;
1010        if (url !== lastUrl) {
1011            lastUrl = url;
1012            console.log('Wildberries Description Generator: URL изменился');
1013        
1014            if (url.includes('seller.wildberries.ru/new-goods/card')) {
1015                setTimeout(init, 1000);
1016            }
1017        }
1018    }, 300);
1019
1020    new MutationObserver(debouncedUrlCheck).observe(document, { subtree: true, childList: true });
1021
1022    const originalPushState = history.pushState;
1023    history.pushState = function() {
1024        originalPushState.apply(this, arguments);
1025        setTimeout(() => {
1026            if (location.href.includes('seller.wildberries.ru/new-goods/card')) {
1027                console.log('Wildberries Description Generator: History API навигация');
1028                setTimeout(init, 500);
1029            }
1030        }, 100);
1031    };
1032
1033    // ============================================
1034    // ПОЛУЧЕНИЕ ИНФОРМАЦИИ О ТОВАРЕ
1035    // ============================================
1036
1037    function getProductInfo() {
1038        console.log('Wildberries Description Generator: Получение информации о товаре');
1039    
1040        const titleElement = document.querySelector('input[data-testid="card-form-main-field-name"]');
1041        const title = titleElement ? titleElement.value : '';
1042    
1043        console.log('Wildberries Description Generator: Поле названия найдено:', !!titleElement);
1044        console.log('Wildberries Description Generator: Название:', title || 'ПУСТО');
1045    
1046        let composition = '';
1047    
1048        const compositionLabel = Array.from(document.querySelectorAll('label, div')).find(el => 
1049            el.textContent && el.textContent.trim().toLowerCase() === 'состав'
1050        );
1051    
1052        console.log('Wildberries Description Generator: Label "Состав" найден:', !!compositionLabel);
1053    
1054        if (compositionLabel) {
1055            const parent = compositionLabel.closest('div[class*="Characteristics"]') || compositionLabel.parentElement;
1056            const compositionTextarea = parent ? parent.querySelector('textarea') : null;
1057            console.log('Wildberries Description Generator: Textarea состава найден:', !!compositionTextarea);
1058            if (compositionTextarea) {
1059                composition = compositionTextarea.value;
1060            }
1061        }
1062    
1063        if (!composition) {
1064            console.log('Wildberries Description Generator: Ищем состав через все textarea');
1065            const textareas = document.querySelectorAll('textarea');
1066            console.log('Wildberries Description Generator: Всего textarea на странице:', textareas.length);
1067            for (const textarea of textareas) {
1068                const value = textarea.value.toLowerCase();
1069                if (value.includes('кислота') || value.includes('масло') || value.includes('экстракт') || 
1070                    value.includes('витамин') || value.includes('глицерин') || value.length > 100) {
1071                    composition = textarea.value;
1072                    console.log('Wildberries Description Generator: Состав найден через поиск по textarea');
1073                    break;
1074                }
1075            }
1076        }
1077    
1078        console.log('Wildberries Description Generator: Состав:', composition ? composition.substring(0, 100) + '...' : 'НЕ НАЙДЕН');
1079    
1080        return { title, composition };
1081    }
1082
1083    // ============================================
1084    // СОЗДАНИЕ КНОПКИ ГЕНЕРАТОРА
1085    // ============================================
1086
1087    function createGeneratorButton() {
1088        const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
1089        
1090        if (!descriptionHeader) {
1091            console.log('Wildberries Description Generator: Заголовок описания не найден');
1092            return false;
1093        }
1094        
1095        if (document.querySelector('.wb-desc-generator-btn')) {
1096            console.log('Wildberries Description Generator: Кнопка уже добавлена');
1097            return true;
1098        }
1099        
1100        const button = document.createElement('button');
1101        button.className = 'wb-desc-generator-btn';
1102        button.textContent = '✨ Генератор описаний';
1103        button.type = 'button';
1104        button.addEventListener('click', function() {
1105            const expandButton = document.querySelector('div.Characteristics__expand__570w3PkC7D button');
1106            if (expandButton) {
1107                console.log('Wildberries Description Generator: Нажимаем "Показать все"');
1108                expandButton.click();
1109            } else {
1110                console.log('Wildberries Description Generator: Кнопка "Показать все" не найдена');
1111            }
1112            setTimeout(openModal, 500);
1113        });
1114        
1115        descriptionHeader.appendChild(button);
1116        console.log('Wildberries Description Generator: Кнопка добавлена');
1117        return true;
1118    }
1119
1120    // ============================================
1121    // ПОКАЗ СТАТУСА
1122    // ============================================
1123
1124    function showStatus(container, message, type) {
1125        container.innerHTML = `<div class="wb-desc-status ${type}">${message}</div>`;
1126    }
1127
1128    // ============================================
1129    // ПРОГРЕСС-БАР
1130    // ============================================
1131
1132    function createProgressBar(container) {
1133        container.innerHTML = `
1134            <div class="wb-progress-wrapper">
1135                <div class="wb-progress-label">
1136                    <span id="wb-progress-stage">Подготовка...</span>
1137                    <span id="wb-progress-percent">0%</span>
1138                </div>
1139                <div class="wb-progress-bar-container">
1140                    <div class="wb-progress-bar-fill" id="wb-progress-fill" style="width: 0%"></div>
1141                </div>
1142                <div class="wb-progress-details" id="wb-progress-details"></div>
1143            </div>
1144        `;
1145    }
1146
1147    function updateProgressBar(stage, percent, details = '') {
1148        const stageEl = document.getElementById('wb-progress-stage');
1149        const percentEl = document.getElementById('wb-progress-percent');
1150        const fillEl = document.getElementById('wb-progress-fill');
1151        const detailsEl = document.getElementById('wb-progress-details');
1152        
1153        if (stageEl) stageEl.textContent = stage;
1154        if (percentEl) percentEl.textContent = `${percent}%`;
1155        if (fillEl) fillEl.style.width = `${percent}%`;
1156        if (detailsEl) detailsEl.textContent = details;
1157    }
1158
1159    // ============================================
1160    // МОДАЛЬНОЕ ОКНО
1161    // ============================================
1162
1163    async function openModal() {
1164        console.log('Wildberries Description Generator: Открытие модального окна');
1165        
1166        const urlParams = new URLSearchParams(window.location.search);
1167        const currentNmID = urlParams.get('nmID');
1168        console.log('Wildberries Description Generator: Текущий nmID:', currentNmID);
1169        
1170        let savedKeywords = '';
1171        let savedMinusWords = '';
1172        
1173        if (currentNmID) {
1174            savedKeywords = await GM.getValue(`wb_product_${currentNmID}_keywords`, '');
1175            savedMinusWords = await GM.getValue(`wb_product_${currentNmID}_minus_words`, '');
1176            console.log('Wildberries Description Generator: Загружены сохраненные данные для товара', currentNmID);
1177        }
1178        
1179        const modal = document.createElement('div');
1180        modal.className = 'wb-desc-modal';
1181        modal.innerHTML = `
1182            <div class="wb-desc-modal-content">
1183                <div class="wb-desc-modal-header">✨ Генератор описаний для Wildberries</div>
1184                
1185                <div class="wb-desc-input-group">
1186                    <label class="wb-desc-label">Введите ключевые слова (каждое с новой строки):</label>
1187                    <textarea class="wb-desc-textarea" id="wb-keywords-input" placeholder="Например:&#10;сыворотка для лица&#10;витамин с&#10;увлажнение">${savedKeywords}</textarea>
1188                    <button class="wb-desc-suggest-btn" id="wb-suggest-keywords-btn">🔍 Предложить поисковые маски</button>
1189                    <div id="wb-suggested-keywords-container" style="display: none;"></div>
1190                </div>
1191                
1192                <div class="wb-desc-input-group">
1193                    <label	class="wb-desc-label">Минус-слова (каждое с новой строки):</label>
1194                    <textarea class="wb-desc-textarea" style="min-height: 80px;" id="wb-minus-words-input" placeholder="Например:&#10;mixit&#10;nivea&#10;корея">${savedMinusWords}</textarea>
1195                </div>
1196                
1197                <div id="wb-desc-result-container" style="display: none;">
1198                    <div class="wb-desc-label">Сгенерированное описание:</div>
1199                    <div class="wb-desc-result" id="wb-desc-result"></div>
1200                    <div class="wb-desc-char-count" id="wb-char-count"></div>
1201                    <div id="wb-desc-stats-container"></div>
1202                </div>
1203                
1204                <div id="wb-desc-status-container"></div>
1205                
1206                <div class="wb-desc-buttons">
1207                    <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-btn">Закрыть</button>
1208                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-generate-btn">🚀 Сгенерировать</button>
1209                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-btn" style="display: none;">🔄 Перегенерировать</button>
1210                    <button class="wb-desc-btn wb-desc-btn-success" id="wb-insert-btn" style="display: none;">✅ Вставить в описание</button>
1211                </div>
1212            </div>
1213        `;
1214        
1215        document.body.appendChild(modal);
1216        
1217        modal.addEventListener('click', (e) => {
1218            if (e.target === modal) {
1219                modal.remove();
1220            }
1221        });
1222        
1223        document.getElementById('wb-close-btn').addEventListener('click', () => {
1224            modal.remove();
1225        });
1226        
1227        document.getElementById('wb-suggest-keywords-btn').addEventListener('click', () => {
1228            suggestKeywords();
1229        });
1230        
1231        document.getElementById('wb-generate-btn').addEventListener('click', () => {
1232            generateDescription(modal);
1233        });
1234        
1235        document.getElementById('wb-regenerate-btn').addEventListener('click', () => {
1236            generateDescription(modal, true);
1237        });
1238        
1239        document.getElementById('wb-insert-btn').addEventListener('click', () => {
1240            insertDescription(modal);
1241        });
1242    }
1243
1244    // ============================================
1245    // ПРЕДЛОЖЕНИЕ КЛЮЧЕВЫХ СЛОВ / МАСОК
1246    // ============================================
1247
1248    async function suggestKeywords() {
1249        const keywordsInput = document.getElementById('wb-keywords-input');
1250        const suggestBtn = document.getElementById('wb-suggest-keywords-btn');
1251        const suggestedContainer = document.getElementById('wb-suggested-keywords-container');
1252        const statusContainer = document.getElementById('wb-desc-status-container');
1253        
1254        const keywordsText = keywordsInput.value.trim();
1255        
1256        if (!keywordsText) {
1257            showStatus(statusContainer, 'Пожалуйста, сначала введите базовые ключевые слова', 'error');
1258            return;
1259        }
1260        
1261        const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1262        
1263        suggestBtn.disabled = true;
1264        suggestBtn.textContent = '⏳ AI анализирует...';
1265        showStatus(statusContainer, 'AI анализирует товар и подбирает поисковые маски и ключевые слова...', 'info');
1266        
1267        try {
1268            const productInfo = getProductInfo();
1269            
1270            const suggestPrompt = generateMasksPrompt(productInfo);
1271
1272            console.log('Wildberries Description Generator: Запрос масок от AI');
1273            
1274            const suggestResponse = await RM.aiCall(suggestPrompt);
1275            
1276            let masks = [];
1277            let aiKeywords = [];
1278            try {
1279                const suggestData = JSON.parse(suggestResponse);
1280                masks = Array.isArray(suggestData.masks) ? suggestData.masks.filter(Boolean) : [];
1281                aiKeywords = Array.isArray(suggestData.keywords) ? suggestData.keywords.filter(Boolean) : [];
1282                console.log(`Wildberries Description Generator: AI предложил ${masks.length} масок и ${aiKeywords.length} ключей`);
1283            } catch (e) {
1284                console.error('Wildberries Description Generator: Ошибка парсинга масок:', e);
1285                showStatus(statusContainer, 'Ошибка при получении предложений. Попробуйте еще раз.', 'error');
1286                return;
1287            }
1288            
1289            if (masks.length === 0 && aiKeywords.length === 0) {
1290                showStatus(statusContainer, 'AI не смог предложить маски и ключевые слова', 'error');
1291                return;
1292            }
1293            
1294            const hasChips = masks.length + aiKeywords.length > 0;
1295            
1296            suggestedContainer.innerHTML = `
1297                <div class="wb-desc-masks-container">
1298                    <div class="wb-desc-masks-header">
1299                        <span>Поисковые маски и ключевые слова (кликните для выбора):</span>
1300                        ${hasChips ? `<button class="wb-desc-suggest-btn" id="wb-toggle-all-btn" style="padding: 4px 12px; font-size: 13px;">
1301                            Выбрать все
1302                        </button>` : ''}
1303                    </div>
1304                    ${masks.length ? `
1305                        <div class="wb-desc-masks-group">
1306                            <div class="wb-desc-masks-group-title">МАСКИ</div>
1307                            <div class="wb-desc-masks-grid">
1308                                ${masks.map(mask => `
1309                                    <div class="wb-mask-chip" data-type="маска" data-mask="${mask}">
1310                                        <span>${mask}</span>
1311                                    </div>
1312                                `).join('')}
1313                            </div>
1314                        </div>
1315                    ` : '<div style="padding: 8px 0; color: #6b7280; text-align: center;">AI не предложил маски</div>'}
1316                    ${aiKeywords.length ? `
1317                        <div class="wb-desc-masks-group">
1318                            <div class="wb-desc-masks-group-title">КЛЮЧЕВЫЕ СЛОВА</div>
1319                            <div class="wb-desc-masks-grid">
1320                                ${aiKeywords.map(keyword => `
1321                                    <div class="wb-mask-chip" data-type="ключ" data-mask="${keyword}">
1322                                        <span>${keyword}</span>
1323                                    </div>
1324                                `).join('')}
1325                            </div>
1326                        </div>
1327                    ` : ''}
1328                </div>
1329            `;
1330            
1331            suggestedContainer.style.display = 'block';
1332            
1333            suggestedContainer.querySelectorAll('.wb-mask-chip').forEach(chip => {
1334                chip.addEventListener('click', () => {
1335                    chip.classList.toggle('selected');
1336                    updateToggleButtonText();
1337                });
1338            });
1339            
1340            function updateToggleButtonText() {
1341                const chips = suggestedContainer.querySelectorAll('.wb-mask-chip');
1342                const allSelected = Array.from(chips).every(c => c.classList.contains('selected'));
1343                const toggleBtn = document.getElementById('wb-toggle-all-btn');
1344                if (toggleBtn) {
1345                    toggleBtn.textContent = allSelected ? 'Снять все' : 'Выбрать все';
1346                }
1347            }
1348            
1349            const toggleBtn = document.getElementById('wb-toggle-all-btn');
1350            if (toggleBtn) {
1351                toggleBtn.addEventListener('click', () => {
1352                    const chips = suggestedContainer.querySelectorAll('.wb-mask-chip');
1353                    const allSelected = Array.from(chips).every(c => c.classList.contains('selected'));
1354                    
1355                    chips.forEach(c => {
1356                        if (allSelected) {
1357                            c.classList.remove('selected');
1358                        } else {
1359                            c.classList.add('selected');
1360                        }
1361                    });
1362                    
1363                    updateToggleButtonText();
1364                });
1365            }
1366            
1367            showStatus(statusContainer, `AI предложил ${masks.length} масок и ${aiKeywords.length} ключевых слов. Выберите нужные и нажмите "Сгенерировать"`, 'success');
1368            
1369        } catch (error) {
1370            console.error('Wildberries Description Generator: Ошибка при предложении масок:', error);
1371            showStatus(statusContainer, 'Ошибка при получении предложений: ' + error.message, 'error');
1372        } finally {
1373            suggestBtn.disabled = false;
1374            suggestBtn.textContent = '🔍 Предложить поисковые маски';
1375        }
1376    }
1377
1378    // ============================================
1379    // СБОР ДАННЫХ С АНАЛИТИКИ
1380    // ============================================
1381
1382    async function collectAnalyticsData(keywords, minusWords) {
1383        console.log('Wildberries Description Generator: Начало сбора данных с аналитики');
1384        
1385        await GM.setValue('wb_keywords_to_process', JSON.stringify(keywords));
1386        await GM.setValue('wb_minus_words', JSON.stringify(minusWords));
1387        await GM.setValue('wb_analytics_data', JSON.stringify([]));
1388        await GM.setValue('wb_collection_status', 'pending');
1389        
1390        const analyticsUrl = 'https://seller.wildberries.ru/search-analytics/popular-search-queries';
1391        await GM.openInTab(analyticsUrl, false);
1392        
1393        console.log('Wildberries Description Generator: Открыта страница аналитики, ожидание сбора данных...');
1394        
1395        const maxWaitTime = 300000;
1396        const checkInterval = 2000;
1397        let waitedTime = 0;
1398        
1399        while (waitedTime < maxWaitTime) {
1400            await new Promise(resolve => setTimeout(resolve, checkInterval));
1401            waitedTime += checkInterval;
1402            
1403            const status = await GM.getValue('wb_collection_status', 'pending');
1404            
1405            if (status === 'completed') {
1406                const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
1407                const analyticsData = JSON.parse(analyticsDataStr);
1408                console.log('Wildberries Description Generator: Данные успешно собраны');
1409                return analyticsData;
1410            } else if (status === 'error') {
1411                console.error('Wildberries Description Generator: Ошибка при сборе данных');
1412                return [];
1413            }
1414            
1415            const progress = Math.min(60, 25 + Math.floor((waitedTime / maxWaitTime) * 35));
1416            updateProgressBar('Сбор данных из аналитики...', progress, `Прошло ${Math.floor(waitedTime / 1000)} сек.`);
1417        }
1418        
1419        console.error('Wildberries Description Generator: Превышено время ожидания сбора данных');
1420        return [];
1421    }
1422
1423    // ============================================
1424    // АВТОМАТИЧЕСКИЙ СБОР НА СТРАНИЦЕ АНАЛИТИКИ
1425    // ============================================
1426
1427    async function autoCollectOnAnalyticsPage() {
1428        if (!window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
1429            return;
1430        }
1431        
1432        console.log('Wildberries Description Generator: Обнаружена страница аналитики');
1433        
1434        const status = await GM.getValue('wb_collection_status', 'none');
1435        if (status !== 'pending') {
1436            return;
1437        }
1438        
1439        console.log('Wildberries Description Generator: Начинаем автоматический сбор данных');
1440        
1441        try {
1442            const keywordsStr = await GM.getValue('wb_keywords_to_process', '[]');
1443            const minusWordsStr = await GM.getValue('wb_minus_words', '[]');
1444            const productAnalysisStr = await GM.getValue('wb_product_analysis', '{}');
1445            const keywords = JSON.parse(keywordsStr);
1446            const minusWords = JSON.parse(minusWordsStr);
1447            const productAnalysis = JSON.parse(productAnalysisStr);
1448            
1449            console.log('Wildberries Description Generator: AI-критерии фильтрации:', productAnalysis);
1450            
1451            const analyticsData = [];
1452            
1453            await new Promise(resolve => setTimeout(resolve, 3000));
1454            
1455            try {
1456                const monthButton = Array.from(document.querySelectorAll('button[data-name="Segments"]')).find(btn => 
1457                    btn.textContent && btn.textContent.trim().toLowerCase().includes('месяц')
1458                );
1459                
1460                if (monthButton) {
1461                    console.log('Wildberries Description Generator: Кнопка "Месяц" найдена, нажимаем');
1462                    monthButton.click();
1463                    await new Promise(resolve => setTimeout(resolve, 2000));
1464                    console.log('Wildberries Description Generator: Период "Месяц" выбран');
1465                } else {
1466                    console.log('Wildberries Description Generator: Кнопка "Месяц" не найдена, используем текущий период');
1467                }
1468            } catch (e) {
1469                console.error('Wildberries Description Generator: Ошибка при выборе периода:', e);
1470            }
1471            
1472            for (const keyword of keywords) {
1473                console.log(`Wildberries Description Generator: Обработка ключевого слова: ${keyword}`);
1474                
1475                try {
1476                    const searchInput = document.querySelector('input[name="searchString"]');
1477                    if (!searchInput) {
1478                        console.error('Wildberries Description Generator: Поле поиска не найдено');
1479                        continue;
1480                    }
1481                    
1482                    searchInput.value = '';
1483                    searchInput.focus();
1484                    searchInput.value = keyword;
1485                    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
1486                    searchInput.dispatchEvent(new Event('change', { bubbles: true }));
1487                    
1488                    await new Promise(resolve => setTimeout(resolve, 5000));
1489                    
1490                    const rows = document.querySelectorAll('table tbody tr');
1491                    const keywordData = {
1492                        keyword: keyword,
1493                        queries: []
1494                    };
1495                    
1496                    console.log(`Wildberries Description Generator: Найдено строк в таблице: ${rows.length}`);
1497                    
1498                    rows.forEach(row => {
1499                        const cells = row.querySelectorAll('td');
1500                        if (cells.length >= 2) {
1501                            const query = cells[0]?.textContent?.trim();
1502                            const popularityText = cells[1]?.textContent?.trim();
1503                            
1504                            if (query && popularityText) {
1505                                const popularity = parseInt(popularityText.replace(/\s+/g, ''));
1506                                const queryLower = query.toLowerCase();
1507                                
1508                                const hasMinusWord = minusWords.some(minusWord => 
1509                                    queryLower.includes(minusWord.toLowerCase())
1510                                );
1511                                
1512                                if (hasMinusWord) {
1513                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
1514                                    return;
1515                                }
1516                                
1517                                const autoBanList = [
1518                                    'mixit', 'axis', 'nivea', 'garnier', 'loreal', 'maybelline', 'vichy', 'bioderma',
1519                                    'эвалар', 'солгар', 'now foods', 'доппельгерц', 'артнео', 'гельтек',
1520                                    'корея', 'корейск', 'япония', 'японск', 'франция', 'французск', 'америк', 'китай', 'китайск',
1521                                    'купить', 'цена', 'отзыв', 'инструкция', 'доставка'
1522                                ];
1523                                
1524                                const hasAutoBan = autoBanList.some(banned => 
1525                                    queryLower.includes(banned)
1526                                );
1527                                
1528                                if (hasAutoBan) {
1529                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (автофильтр: бренд/страна)`);
1530                                    return;
1531                                }
1532                                
1533                                const hasEnglish = /[a-z]/i.test(query);
1534                                
1535                                if (hasEnglish) {
1536                                    const allowedWords = productAnalysis.allowed_english_words || [];
1537                                    const isAllowedEnglish = allowedWords.some(allowed => 
1538                                        queryLower.includes(allowed.toLowerCase())
1539                                    );
1540                                    
1541                                    if (!isAllowedEnglish) {
1542                                        const normalizeText = (text) => {
1543                                            const latinToCyrillic = {
1544                                                'a': 'а', 'A': 'А', 'e': 'е', 'E': 'Е', 'o': 'о', 'O': 'О',
1545                                                'p': 'р', 'P': 'Р', 'c': 'с', 'C': 'С', 'y': 'у', 'Y': 'У',
1546                                                'x': 'х', 'X': 'Х', 'k': 'к', 'K': 'К', 'h': 'н', 'H': 'Н',
1547                                                'b': 'в', 'B': 'В', 'm': 'м', 'M': 'М', 't': 'т', 'T': 'Т'
1548                                            };
1549                                            return text.split('').map(char => latinToCyrillic[char] || char).join('').toLowerCase();
1550                                        };
1551                                        
1552                                        const normalizedQuery = normalizeText(query);
1553                                        const isSimilarToUserKeyword = keywords.some(k => {
1554                                            const normalizedKeyword = normalizeText(k);
1555                                            return normalizedQuery === normalizedKeyword;
1556                                        });
1557                                        
1558                                        if (!isSimilarToUserKeyword) {
1559                                            console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит неразрешенные английские слова)`);
1560                                            return;
1561                                        }
1562                                    } else {
1563                                        console.log(`Wildberries Description Generator: Разрешен запрос "${query}" (содержит разрешенное английское слово)`);
1564                                    }
1565                                }
1566                                
1567                                const excludedPurposes = productAnalysis.excluded_purposes || [];
1568                                const hasExcludedPurpose = excludedPurposes.some(excluded => 
1569                                    queryLower.includes(excluded.toLowerCase())
1570                                );
1571                                
1572                                if (hasExcludedPurpose) {
1573                                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (неподходящее назначение по AI-критериям)`);
1574                                    return;
1575                                }
1576                                
1577                                keywordData.queries.push({
1578                                    query,
1579                                    popularity
1580                                });
1581                            }
1582                        }
1583                    });
1584                    
1585                    analyticsData.push(keywordData);
1586                    console.log(`Wildberries Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
1587                    
1588                } catch (error) {
1589                    console.error(`Wildberries Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
1590                }
1591            }
1592            
1593            await GM.setValue('wb_analytics_data', JSON.stringify(analyticsData));
1594            await GM.setValue('wb_collection_status', 'completed');
1595            
1596            console.log('Wildberries Description Generator: Сбор данных завершен, можно закрыть вкладку');
1597            
1598            setTimeout(() => {
1599                window.close();
1600            }, 2000);
1601            
1602        } catch (error) {
1603            console.error('Wildberries Description Generator: Ошибка при автоматическом сборе данных:', error);
1604            await GM.setValue('wb_collection_status', 'error');
1605        }
1606    }
1607
1608    // ============================================
1609    // ГЕНЕРАЦИЯ ОПИСАНИЯ
1610    // ============================================
1611
1612    async function generateDescription(modal, skipDataCollection = false) {
1613        console.log('Wildberries Description Generator: Генерация описания');
1614        
1615        const keywordsInput = document.getElementById('wb-keywords-input');
1616        const minusWordsInput = document.getElementById('wb-minus-words-input');
1617        const generateBtn = document.getElementById('wb-generate-btn');
1618        const regenerateBtn = document.getElementById('wb-regenerate-btn');
1619        const insertBtn = document.getElementById('wb-insert-btn');
1620        const resultContainer = document.getElementById('wb-desc-result-container');
1621        const resultDiv = document.getElementById('wb-desc-result');
1622        const charCountDiv = document.getElementById('wb-char-count');
1623        const statusContainer = document.getElementById('wb-desc-status-container');
1624        const statsContainer = document.getElementById('wb-desc-stats-container');
1625        
1626        let keywordsText = keywordsInput.value.trim();
1627        
1628        const selectedSuggestions = Array.from(document.querySelectorAll('.wb-mask-chip.selected'))
1629            .map(chip => chip.dataset.mask);
1630        
1631        if (selectedSuggestions.length > 0) {
1632            const existingKeywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1633            const allKeywords = [...new Set([...existingKeywords, ...selectedSuggestions])];
1634            keywordsText = allKeywords.join('\n');
1635            console.log('Wildberries Description Generator: Добавлены маски/ключи:', selectedSuggestions);
1636        }
1637        
1638        const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1639        const minusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
1640        
1641        if (keywords.length === 0) {
1642            showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
1643            return;
1644        }
1645        
1646        const urlParams = new URLSearchParams(window.location.search);
1647        const currentNmID = urlParams.get('nmID');
1648        if (currentNmID) {
1649            await GM.setValue(`wb_product_${currentNmID}_keywords`, keywords.join('\n'));
1650            await GM.setValue(`wb_product_${currentNmID}_minus_words`, minusWords.join('\n'));
1651            console.log('Wildberries Description Generator: Сохранены ключевые слова и минус-слова для товара', currentNmID);
1652        }
1653        
1654        generateBtn.disabled = true;
1655        regenerateBtn.disabled = true;
1656        
1657        try {
1658            const productInfo = getProductInfo();
1659            let analyticsData = [];
1660            
1661            let queryPopularity = {};
1662            
1663            if (!skipDataCollection) {
1664                createProgressBar(statusContainer);
1665                updateProgressBar('AI анализирует товар...', 10);
1666                
1667                console.log('Wildberries Description Generator: AI анализирует товар для фильтрации');
1668                const analysisPrompt = generateProductAnalysisPrompt(productInfo, keywords);
1669
1670                const analysisResponse = await RM.aiCall(analysisPrompt);
1671                const productAnalysis = JSON.parse(analysisResponse);
1672                console.log('Wildberries Description Generator: AI-анализ товара:', productAnalysis);
1673            
1674                await GM.setValue('wb_product_analysis', JSON.stringify(productAnalysis));
1675                await GM.setValue('wb_analytics_minus_words', JSON.stringify([]));
1676                
1677                updateProgressBar('Сбор данных из аналитики...', 20, 'Откроется новая вкладка');
1678            
1679                analyticsData = await collectAnalyticsData(keywords, minusWords);
1680            
1681                if (analyticsData.length === 0) {
1682                    showStatus(statusContainer, 'Не удалось собрать данные из аналитики', 'error');
1683                    return;
1684                }
1685            } else {
1686                const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
1687                analyticsData = JSON.parse(analyticsDataStr);
1688                
1689                if (analyticsData.length === 0) {
1690                    showStatus(statusContainer, 'Нет сохраненных данных. Пожалуйста, сначала соберите данные.', 'error');
1691                    return;
1692                }
1693            }
1694        
1695            const allQueries = [];
1696            analyticsData.forEach(data => {
1697                data.queries.forEach(q => {
1698                    allQueries.push(q.query);
1699                    queryPopularity[q.query.toLowerCase()] = q.popularity;
1700                });
1701            });
1702        
1703            console.log(`Wildberries Description Generator: Всего запросов для генерации: ${allQueries.length}`);
1704        
1705            if (allQueries.length === 0) {
1706                showStatus(statusContainer, 'Не найдено подходящих запросов для генерации', 'error');
1707                return;
1708            }
1709        
1710            console.log('Wildberries Description Generator: Применяем минус-слова перед генерацией');
1711            console.log('Wildberries Description Generator: Минус-слова:', minusWords);
1712            console.log('Wildberries Description Generator: Запросов до фильтрации:', allQueries.length);
1713            
1714            const filteredQueries = allQueries.filter(query => {
1715                const queryLower = query.toLowerCase();
1716                const hasMinusWord = minusWords.some(minusWord => 
1717                    queryLower.includes(minusWord.toLowerCase())
1718                );
1719                
1720                if (hasMinusWord) {
1721                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
1722                }
1723                
1724                return !hasMinusWord;
1725            });
1726            
1727            console.log('Wildberries Description Generator: Запросов после фильтрации:', filteredQueries.length);
1728            
1729            if (filteredQueries.length === 0) {
1730                showStatus(statusContainer, 'Все запросы отфильтрованы минус-словами. Попробуйте уменьшить количество минус-слов.', 'error');
1731                return;
1732            }
1733        
1734            if (!skipDataCollection) {
1735                updateProgressBar('AI генерирует описание...', 70);
1736            } else {
1737                showStatus(statusContainer, 'AI генерирует описание...', 'info');
1738            }
1739        
1740            const descriptionPrompt = generateDescriptionPrompt(productInfo, keywords, filteredQueries, queryPopularity);
1741
1742            const description = await RM.aiCall(descriptionPrompt);
1743        
1744            await GM.setValue('wb_generated_description', description);
1745            await GM.setValue('wb_query_popularity', JSON.stringify(queryPopularity));
1746        
1747            let cleanedDescription = description;
1748            const englishToRussian = {
1749                'crucial': 'ключевой',
1750                'Crucial': 'Ключевой',
1751                'essential': 'важный',
1752                'Essential': 'Важный',
1753                'vital': 'жизненно важный',
1754                'Vital': 'Жизненно важный',
1755                'key': 'ключевой',
1756                'Key': 'Ключевой',
1757                'important': 'важный',
1758                'Important': 'Важный',
1759                'testosterone': 'тестостерон',
1760                'Testosterone': 'Тестостерон',
1761                'energy': 'энергия',
1762                'Energy': 'Энергия'
1763            };
1764            
1765            Object.entries(englishToRussian).forEach(([eng, rus]) => {
1766                const regex = new RegExp('\\b' + eng + '\\b', 'g');
1767                cleanedDescription = cleanedDescription.replace(regex, rus);
1768            });
1769        
1770            resultDiv.textContent = cleanedDescription;
1771            resultContainer.style.display = 'block';
1772        
1773            const charCount = cleanedDescription.length;
1774            charCountDiv.textContent = `Символов: ${charCount}`;
1775        
1776            if (charCount >= 3500 && charCount <= 4000) {
1777                charCountDiv.className = 'wb-desc-char-count success';
1778            } else if (charCount < 3500) {
1779                charCountDiv.className = 'wb-desc-char-count warning';
1780            } else {
1781                charCountDiv.className = 'wb-desc-char-count error';
1782            }
1783            
1784            if (statsContainer) {
1785                const newStatsContainer = document.createElement('div');
1786                newStatsContainer.id = 'wb-desc-stats-container';
1787                charCountDiv.parentElement.insertBefore(newStatsContainer, charCountDiv.nextSibling);
1788            }
1789            
1790            const analysis = await analyzeUsedKeywords(cleanedDescription, queryPopularity);
1791            const usagePercent = Math.round(analysis.usedQueries.length / analysis.totalQueriesAvailable * 100);
1792            
1793            if (analysis.unusedQueries.length > 0) {
1794                console.log(`Wildberries Description Generator: Обнаружено ${analysis.unusedQueries.length} неиспользованных запросов`);
1795                
1796                const sortedUnused = analysis.unusedQueries
1797                    .map(query => ({
1798                        query: query,
1799                        popularity: analysis.queryPopularity[query.toLowerCase()] || 0
1800                    }))
1801                    .sort((a, b) => b.popularity - a.popularity);
1802                
1803                console.log('Wildberries Description Generator: Топ-10 пропущенных запросов:', 
1804                    sortedUnused.slice(0, 10).map(q => `${q.query} (${q.popularity})`));
1805                
1806                try {
1807                    const topUnused = sortedUnused.slice(0, 30).map(q => q.query);
1808                    
1809                    const recoverablePrompt = `Проанализируй, почему эти запросы НЕ были использованы в описании товара.
1810
1811ДАННЫЕ О ТОВАРЕ:
1812• Название: ${productInfo.title || 'не указано'}
1813• Состав: ${productInfo.composition || 'не указан'}
1814
1815ОПИСАНИЕ (первые 1000 символов):
1816${cleanedDescription.substring(0, 1000)}...
1817
1818НЕИСПОЛЬЗОВАННЫЕ ЗАПРОСЫ (топ-30 по популярности):
1819${topUnused.map((q, i) => `${i+1}. "${q}"`).join('\n')}
1820
1821ЗАДАЧА:
1822Определи, какие из этих запросов МОЖНО было использовать, но AI пропустил.
1823
1824КРИТЕРИИ для включения запроса:
18251. Запрос релевантен товару (описывает товар, его назначение или компоненты)
18262. Компонент из запроса есть в составе ИЛИ можно использовать через логику замены
18273. Запрос не противоречит назначению товара
18284. Запрос не содержит бренды конкурентов
1829
1830Верни список запросов, которые МОЖНО было использовать, в формате JSON:
1831{
1832  "recoverable_queries": ["запрос 1", "запрос 2", ...],
1833  "reasons": {
1834    "запрос 1": "краткая причина почему можно использовать",
1835    "запрос 2": "краткая причина почему можно использовать"
1836  }
1837}
1838
1839НЕ ПИШИ ничего кроме JSON. Начни ответ сразу с {`;
1840
1841                    const aiAnalysisResponse = await RM.aiCall(recoverablePrompt);
1842                    const aiAnalysis = JSON.parse(aiAnalysisResponse);
1843                    
1844                    if (aiAnalysis.recoverable_queries && aiAnalysis.recoverable_queries.length > 0) {
1845                        console.log(`Wildberries Description Generator: AI нашел ${aiAnalysis.recoverable_queries.length} пропущенных запросов`);
1846                        console.log('Wildberries Description Generator: Причины:', aiAnalysis.reasons);
1847                        
1848                        await GM.setValue('wb_recoverable_queries', JSON.stringify(aiAnalysis.recoverable_queries));
1849                        await GM.setValue('wb_recoverable_reasons', JSON.stringify(aiAnalysis.reasons));
1850                    } else {
1851                        console.log('Wildberries Description Generator: AI не нашел пропущенных запросов');
1852                        await GM.setValue('wb_recoverable_queries', JSON.stringify([]));
1853                        await GM.setValue('wb_recoverable_reasons', JSON.stringify({}));
1854                    }
1855                } catch (e) {
1856                    console.error('Wildberries Description Generator: Ошибка при анализе пропущенных запросов:', e);
1857                    await GM.setValue('wb_recoverable_queries', JSON.stringify([]));
1858                    await GM.setValue('wb_recoverable_reasons', JSON.stringify({}));
1859                }
1860            }
1861            
1862            if (!statsContainer) {
1863                const newStatsContainer = document.createElement('div');
1864                newStatsContainer.id = 'wb-desc-stats-container';
1865                charCountDiv.parentElement.insertBefore(newStatsContainer, charCountDiv.nextSibling);
1866            }
1867            
1868            document.getElementById('wb-desc-stats-container').innerHTML = `
1869                <div class="wb-desc-stats">
1870                    <div class="wb-desc-stats-row">
1871                        <span><strong>Использовано запросов:</strong></span>
1872                        <span>${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} (${usagePercent}%)</span>
1873                    </div>
1874                    <div class="wb-desc-stats-row">
1875                        <span><strong>Общая частотность:</strong></span>
1876                        <span>${formatNumber(analysis.totalPopularity)}</span>
1877                    </div>
1878                </div>
1879            `;
1880        
1881            generateBtn.style.display = 'none';
1882            regenerateBtn.style.display = 'inline-block';
1883            insertBtn.style.display = 'inline-block';
1884        
1885            showStatus(statusContainer, '✅ Описание успешно сгенерировано! <span class="wb-desc-usage-link" id="wb-show-analytics-link">Показать аналитику использования запросов</span>', 'success');
1886            
1887            setTimeout(() => {
1888                const analyticsLink = document.getElementById('wb-show-analytics-link');
1889                if (analyticsLink) {
1890                    analyticsLink.addEventListener('click', () => {
1891                        showUsageAnalytics();
1892                    });
1893                }
1894            }, 100);
1895        
1896        } catch (error) {
1897            console.error('Wildberries Description Generator: Ошибка при генерации описания:', error);
1898            showStatus(statusContainer, 'Ошибка при генерации: ' + error.message, 'error');
1899        } finally {
1900            generateBtn.disabled = false;
1901            regenerateBtn.disabled = false;
1902        }
1903    }
1904
1905    // ============================================
1906    // АНАЛИЗ ИСПОЛЬЗОВАННЫХ КЛЮЧЕВЫХ СЛОВ
1907    // ============================================
1908
1909    async function analyzeUsedKeywords(description, queryPopularityParam = null) {
1910        console.log('Wildberries Description Generator: Анализ использованных ключевых слов');
1911    
1912        const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
1913        const analyticsData = JSON.parse(analyticsDataStr);
1914    
1915        const allQueries = [];
1916        let queryPopularity = queryPopularityParam || {};
1917        
1918        if (!queryPopularityParam) {
1919            const savedPopularity = await GM.getValue('wb_query_popularity', '{}');
1920            queryPopularity = JSON.parse(savedPopularity);
1921        }
1922    
1923        analyticsData.forEach(data => {
1924            data.queries.forEach(q => {
1925                allQueries.push(q.query);
1926                if (!queryPopularity[q.query.toLowerCase()]) {
1927                    queryPopularity[q.query.toLowerCase()] = q.popularity;
1928                }
1929            });
1930        });
1931    
1932        const descriptionLower = description.toLowerCase();
1933        const usedQueries = [];
1934        const unusedQueries = [];
1935        let totalPopularity = 0;
1936    
1937        allQueries.forEach(query => {
1938            if (descriptionLower.includes(query.toLowerCase())) {
1939                usedQueries.push(query);
1940                totalPopularity += queryPopularity[query.toLowerCase()] || 0;
1941            } else {
1942                unusedQueries.push(query);
1943            }
1944        });
1945    
1946        console.log(`Wildberries Description Generator: Использовано ${usedQueries.length} из ${allQueries.length} запросов`);
1947    
1948        return {
1949            usedQueries,
1950            unusedQueries,
1951            totalQueriesAvailable: allQueries.length,
1952            totalPopularity,
1953            queryPopularity
1954        };
1955    }
1956
1957    // ============================================
1958    // ПОКАЗ АНАЛИТИКИ ИСПОЛЬЗОВАНИЯ ЗАПРОСОВ
1959    // ============================================
1960
1961    async function showUsageAnalytics() {
1962        console.log('Wildberries Description Generator: Показ аналитики использования');
1963    
1964        const description = await GM.getValue('wb_generated_description', '');
1965        if (!description) {
1966            alert('Описание не найдено');
1967            return;
1968        }
1969    
1970        const analysis = await analyzeUsedKeywords(description);
1971    
1972        const minusWordsStr = await GM.getValue('wb_analytics_minus_words', '[]');
1973        const minusWords = JSON.parse(minusWordsStr);
1974        
1975        const recoverableQueriesStr = await GM.getValue('wb_recoverable_queries', '[]');
1976        const recoverableQueries = JSON.parse(recoverableQueriesStr);
1977        const recoverableReasonsStr = await GM.getValue('wb_recoverable_reasons', '{}');
1978        const recoverableReasons = JSON.parse(recoverableReasonsStr);
1979        
1980        console.log(`Wildberries Description Generator: Загружено ${recoverableQueries.length} пропущенных запросов от AI`);
1981        
1982        let usedQueries = [...analysis.usedQueries];
1983        let unusedQueries = [...analysis.unusedQueries];
1984        let currentMinusWords = [...minusWords];
1985        let searchQuery = '';
1986    
1987        function renderModal() {
1988            const usedContainer = document.getElementById('wb-used-queries-container');
1989            const unusedContainer = document.getElementById('wb-unused-queries-container');
1990            const usedScrollTop = usedContainer ? usedContainer.scrollTop : 0;
1991            const unusedScrollTop = unusedContainer ? unusedContainer.scrollTop : 0;
1992            
1993            const filteredUsed = usedQueries.filter(q => 
1994                q.toLowerCase().includes(searchQuery.toLowerCase())
1995            );
1996            const filteredUnused = unusedQueries.filter(q => 
1997                q.toLowerCase().includes(searchQuery.toLowerCase())
1998            );
1999            
2000            const analyticsModal = document.querySelector('.wb-desc-analytics-modal');
2001            if (!analyticsModal) return;
2002            
2003            analyticsModal.innerHTML = `
2004            <div class="wb-desc-analytics-content">
2005                <div class="wb-desc-modal-header">📊 Аналитика использования запросов</div>
2006                
2007                ${currentMinusWords.length > 0 ? `
2008                <div class="wb-desc-minus-words-section">
2009                    <div class="wb-desc-minus-words-header">Минус-слова (клик для удаления):</div>
2010                    <div class="wb-desc-minus-words-list">
2011                        ${currentMinusWords.map(word => `
2012                            <div class="wb-desc-minus-word-chip" data-word="${word}">
2013                                ${word}
2014                                <span class="wb-desc-minus-word-remove">×</span>
2015                            </div>
2016                        `).join('')}
2017                    </div>
2018                </div>
2019                ` : ''}
2020                
2021                <input type="text" class="wb-desc-search-input" id="wb-analytics-search" placeholder="🔍 Поиск по запросам..." value="${searchQuery}">
2022                
2023                <div style="margin-bottom: 16px; display: flex; gap: 20px; flex-wrap: wrap;">
2024                    <div><strong>Использовано:</strong> ${usedQueries.length} из ${analysis.totalQueriesAvailable} (${Math.round(usedQueries.length / analysis.totalQueriesAvailable * 100)}%)</div>
2025                    <div><strong>Общая частотность:</strong> ${formatNumber(analysis.totalPopularity)}</div>
2026                    ${searchQuery ? `<div><strong>Найдено:</strong> ${filteredUsed.length + filteredUnused.length}</div>` : ''}
2027                </div>
2028                
2029                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
2030                    <div>
2031                        <div style="margin-bottom: 12px; font-weight: 600; color: #065f46;">✅ Использованные (${filteredUsed.length}):</div>
2032                        <div style="max-height: 350px; overflow-y: auto;" id="wb-used-queries-container">
2033                            ${filteredUsed.length > 0 ? filteredUsed.map(query => `
2034                                <div class="wb-desc-query-item used">
2035                                    <span class="wb-desc-query-text">${highlightWords(query, currentMinusWords, true, 'used')}</span>
2036                                    <div style="display: flex; align-items: center; gap: 8px;">
2037                                        <span class="wb-desc-query-popularity">${formatNumber(analysis.queryPopularity[query.toLowerCase()] || 0)}</span>
2038										<span class="wb-desc-query-exclude" data-query="${query}" style="cursor: pointer; font-size: 18px; color: #991b1b; font-weight: bold;" title="Исключить запрос">×</span>
2039                                    </div>
2040                                </div>
2041                            `).join('') : '<div style="padding: 12px; color: #6b7280; text-align: center;">Нет результатов</div>'}
2042                        </div>
2043                    </div>
2044                    
2045                    <div>
2046                        <div style="margin-bottom: 12px; font-weight: 600; color: #6b7280;">⬜ Неиспользованные (${filteredUnused.length}):</div>
2047                        <div style="max-height: 350px; overflow-y: auto;" id="wb-unused-queries-container">
2048                            ${filteredUnused.length > 0 ? filteredUnused.map(query => {
2049        const isRecoverable = recoverableQueries.includes(query);
2050        const reason = recoverableReasons[query] || '';
2051        return `
2052                                    <div class="wb-desc-query-item unused ${isRecoverable ? 'recoverable' : ''}" ${reason ? `title="${reason}"` : ''}>
2053                                        <span class="wb-desc-query-text">
2054                                            ${isRecoverable ? '⚠️ ' : ''}${highlightWords(query, currentMinusWords, true, 'unused')}
2055                                        </span>
2056                                        <div style="display: flex; align-items: center; gap: 8px;">
2057                                            <span class="wb-desc-query-popularity">${formatNumber(analysis.queryPopularity[query.toLowerCase()] || 0)}</span>
2058                                            <span class="wb-desc-query-include" data-query="${query}" style="cursor: pointer; font-size: 18px; color: #059669; font-weight: bold;" title="Включить запрос">+</span>
2059                                        </div>
2060                                    </div>
2061                                `;
2062    }).join('') : '<div style="padding: 12px; color: #6b7280; text-align: center;">Нет результатов</div>'}
2063                        </div>
2064                    </div>
2065                </div>
2066                
2067                ${recoverableQueries.length > 0 ? `
2068                <div style="margin-top: 16px; padding: 12px; background: linear-gradient(135deg, #fef3c7, #fde68a); border-radius: 8px; border: 1px solid #f59e0b;">
2069                    <div style="font-weight: 600; color: #92400e; margin-bottom: 4px;">
2070                        ⚠️ AI обнаружил ${recoverableQueries.length} пропущенных запросов
2071                    </div>
2072                    <div style="font-size: 13px; color: #78350f;">
2073                        Эти запросы помечены значком ⚠️. Наведите курсор для просмотра причины.
2074                    </div>
2075                </div>
2076                ` : ''}
2077                
2078                <div class="wb-desc-buttons">
2079                    <button class="wb-desc-btn wb-desc-btn-secondary" id="wb-close-analytics-btn">Закрыть</button>
2080                    <button class="wb-desc-btn wb-desc-btn-primary" id="wb-regenerate-with-exclusions-btn">🔄 Перегенерировать с изменениями</button>
2081                </div>
2082            </div>
2083        `;
2084            
2085            setTimeout(() => {
2086                const newUsedContainer = document.getElementById('wb-used-queries-container');
2087                const newUnusedContainer = document.getElementById('wb-unused-queries-container');
2088                if (newUsedContainer) newUsedContainer.scrollTop = usedScrollTop;
2089                if (newUnusedContainer) newUnusedContainer.scrollTop = unusedScrollTop;
2090            }, 0);
2091            
2092            attachEventHandlers();
2093        }
2094        
2095        function highlightWords(text, words, clickable = false, type = 'unused') {
2096            if (!clickable) return text;
2097        
2098            const textWords = text.split(/\s+/);
2099            return textWords.map(word => {
2100                const cleanWord = word.toLowerCase().replace(/[.,!?;:]/g, '');
2101                return `<span class="wb-desc-query-word" data-word="${cleanWord}" data-type="${type}">${word}</span>`;
2102            }).join(' ');
2103        }
2104        
2105        function removeQueriesWithMinusWord(minusWord) {
2106            const minusWordLower = minusWord.toLowerCase();
2107            
2108            usedQueries = usedQueries.filter(query => 
2109                !query.toLowerCase().includes(minusWordLower)
2110            );
2111            
2112            unusedQueries = unusedQueries.filter(query => 
2113                !query.toLowerCase().includes(minusWordLower)
2114            );
2115        }
2116        
2117        function attachEventHandlers() {
2118            const analyticsModal = document.querySelector('.wb-desc-analytics-modal');
2119            if (!analyticsModal) return;
2120            
2121            const searchInput = document.getElementById('wb-analytics-search');
2122            if (searchInput) {
2123                searchInput.addEventListener('input', (e) => {
2124                    searchQuery = e.target.value;
2125                    const cursorPosition = e.target.selectionStart;
2126                    renderModal();
2127                    setTimeout(() => {
2128                        const newSearchInput = document.getElementById('wb-analytics-search');
2129                        if (newSearchInput) {
2130                            newSearchInput.focus();
2131                            newSearchInput.setSelectionRange(cursorPosition, cursorPosition);
2132                        }
2133                    }, 0);
2134                });
2135            }
2136            
2137            analyticsModal.addEventListener('click', (e) => {
2138                if (e.target === analyticsModal) {
2139                    analyticsModal.remove();
2140                }
2141            });
2142            
2143            const closeBtn = document.getElementById('wb-close-analytics-btn');
2144            if (closeBtn) {
2145                closeBtn.addEventListener('click', () => {
2146                    analyticsModal.remove();
2147                });
2148            }
2149            
2150            const regenerateBtn = document.getElementById('wb-regenerate-with-exclusions-btn');
2151            if (regenerateBtn) {
2152                regenerateBtn.addEventListener('click', async () => {
2153                    await GM.setValue('wb_analytics_minus_words', JSON.stringify(currentMinusWords));
2154                    
2155                    const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
2156                    const analyticsData = JSON.parse(analyticsDataStr);
2157                    
2158                    const updatedAnalyticsData = analyticsData.map(data => {
2159                        return {
2160                            keyword: data.keyword,
2161                            queries: data.queries.filter(q => usedQueries.includes(q.query))
2162                        };
2163                    });
2164                    
2165                    await GM.setValue('wb_analytics_data', JSON.stringify(updatedAnalyticsData));
2166                    
2167                    console.log('Wildberries Description Generator: Обновлены данные аналитики для перегенерации');
2168                    console.log('Использованные запросы:', usedQueries.length);
2169                    console.log('Минус-слова:', currentMinusWords);
2170                    
2171                    analyticsModal.remove();
2172                    await regenerateWithExclusions();
2173                });
2174            }
2175            
2176            analyticsModal.querySelectorAll('.wb-desc-minus-word-chip').forEach(chip => {
2177                chip.addEventListener('click', () => {
2178                    const word = chip.dataset.word;
2179                    currentMinusWords = currentMinusWords.filter(w => w !== word);
2180                    console.log(`Wildberries Description Generator: Минус-слово "${word}" удалено`);
2181                    renderModal();
2182                });
2183            });
2184            
2185            analyticsModal.querySelectorAll('.wb-desc-query-exclude').forEach(excludeBtn => {
2186                excludeBtn.addEventListener('click', () => {
2187                    const query = excludeBtn.dataset.query;
2188                    console.log(`Wildberries Description Generator: Перемещаем запрос "${query}" в неиспользованные`);
2189                    
2190                    usedQueries = usedQueries.filter(q => q !== query);
2191                    if (!unusedQueries.includes(query)) {
2192                        unusedQueries.push(query);
2193                    }
2194                    
2195                    renderModal();
2196                });
2197            });
2198            
2199            analyticsModal.querySelectorAll('.wb-desc-query-include').forEach(includeBtn => {
2200                includeBtn.addEventListener('click', () => {
2201                    const query = includeBtn.dataset.query;
2202                    console.log(`Wildberries Description Generator: Перемещаем запрос "${query}" в использованные`);
2203                    
2204                    unusedQueries = unusedQueries.filter(q => q !== query);
2205                    if (!usedQueries.includes(query)) {
2206                        usedQueries.push(query);
2207                    }
2208                    
2209                    const queryLower = query.toLowerCase();
2210                    currentMinusWords = currentMinusWords.filter(w => w !== queryLower);
2211                    
2212                    renderModal();
2213                });
2214            });
2215            
2216            analyticsModal.querySelectorAll('.wb-desc-query-word').forEach(wordSpan => {
2217                wordSpan.addEventListener('click', () => {
2218                    const word = wordSpan.dataset.word;
2219                    
2220                    console.log(`Wildberries Description Generator: Добавляем минус-слово "${word}"`);
2221                    
2222                    if (!currentMinusWords.includes(word)) {
2223                        currentMinusWords.push(word);
2224                    }
2225                    
2226                    removeQueriesWithMinusWord(word);
2227                    
2228                    renderModal();
2229                });
2230            });
2231        }
2232    
2233        const analyticsModal = document.createElement('div');
2234        analyticsModal.className = 'wb-desc-analytics-modal';
2235        document.body.appendChild(analyticsModal);
2236        
2237        renderModal();
2238    }
2239
2240    // ============================================
2241    // ПЕРЕГЕНЕРАЦИЯ С ИСКЛЮЧЕНИЯМИ
2242    // ============================================
2243
2244    async function regenerateWithExclusions() {
2245        console.log('Wildberries Description Generator: Перегенерация с исключениями');
2246    
2247        const analyticsMinusWordsStr = await GM.getValue('wb_analytics_minus_words', '[]');
2248        const analyticsMinusWords = JSON.parse(analyticsMinusWordsStr);
2249        
2250        console.log('Wildberries Description Generator: Минус-слова из аналитики для перегенерации:', analyticsMinusWords);
2251    
2252        const existingModal = document.querySelector('.wb-desc-modal');
2253        if (existingModal) {
2254            console.log('Wildberries Description Generator: Модальное окно уже открыто');
2255            
2256            const minusWordsInput = document.getElementById('wb-minus-words-input');
2257            if (minusWordsInput) {
2258                const currentMinusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
2259                const allMinusWords = [...new Set([...currentMinusWords, ...analyticsMinusWords])];
2260                minusWordsInput.value = allMinusWords.join('\n');
2261                
2262                console.log('Wildberries Description Generator: Обновлены минус-слова в модальном окне:', allMinusWords);
2263            }
2264            
2265            generateDescription(existingModal, true);
2266            return;
2267        }
2268    
2269        await openModal();
2270        await new Promise(resolve => setTimeout(resolve, 100));
2271    
2272        const minusWordsInput = document.getElementById('wb-minus-words-input');
2273        if (minusWordsInput) {
2274            const currentMinusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
2275            const allMinusWords = [...new Set([...currentMinusWords, ...analyticsMinusWords])];
2276            minusWordsInput.value = allMinusWords.join('\n');
2277            
2278            console.log('Wildberries Description Generator: Обновлены минус-слова в новом модальном окне:', allMinusWords);
2279        }
2280    
2281        const newModal = document.querySelector('.wb-desc-modal');
2282        if (newModal) {
2283            generateDescription(newModal, true);
2284        }
2285    }
2286
2287    // ============================================
2288    // ВСТАВКА ОПИСАНИЯ
2289    // ============================================
2290
2291    async function insertDescription(modal) {
2292        console.log('Wildberries Description Generator: Вставка описания');
2293    
2294        try {
2295            const description = await GM.getValue('wb_generated_description', '');
2296        
2297            if (!description) {
2298                alert('Описание не найдено. Пожалуйста, сгенерируйте описание сначала.');
2299                return;
2300            }
2301        
2302            const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
2303        
2304            if (!descriptionTextarea) {
2305                alert('Не удалось найти поле описания. Убедитесь, что вы находитесь на странице редактирования товара.');
2306                return;
2307            }
2308        
2309            descriptionTextarea.value = description;
2310            descriptionTextarea.dispatchEvent(new Event('input', { bubbles: true }));
2311            descriptionTextarea.dispatchEvent(new Event('change', { bubbles: true }));
2312        
2313            console.log('Wildberries Description Generator: Описание успешно вставлено');
2314        
2315            modal.remove();
2316        
2317            alert('✅ Описание успешно вставлено!');
2318        
2319        } catch (error) {
2320            console.error('Wildberries Description Generator: Ошибка при вставке описания:', error);
2321            alert('Ошибка при вставке описания. Попробуйте еще раз.');
2322        }
2323    }
2324
2325    // ============================================
2326    // АВТОГЕНЕРАЦИЯ ОПИСАНИЙ ДЛЯ ВСЕХ ТОВАРОВ
2327    // ============================================
2328
2329    function createAutoGenerationButton() {
2330        console.log('Wildberries Description Generator: Создание кнопки автогенерации');
2331        
2332        if (document.querySelector('.wb-auto-btn')) {
2333            console.log('Wildberries Description Generator: Кнопка автогенерации уже добавлена');
2334            return;
2335        }
2336        
2337        const button = document.createElement('button');
2338        button.className = 'wb-auto-btn';
2339        button.textContent = '🤖 Авто описание';
2340        button.addEventListener('click', startAutoGeneration);
2341        
2342        document.body.appendChild(button);
2343        console.log('Wildberries Description Generator: Кнопка автогенерации добавлена');
2344    }
2345    
2346    function getProductsFromPage() {
2347        console.log('Wildberries Description Generator: Получение списка товаров');
2348        
2349        const products = [];
2350        const rows = document.querySelectorAll('table tbody tr, [data-testid="product-row"], .product-item');
2351        
2352        console.log(`Wildberries Description Generator: Найдено строк: ${rows.length}`);
2353        
2354        rows.forEach((row, index) => {
2355            try {
2356                let nmID = null;
2357                
2358                const nmIDElement = row.querySelector('[data-testid="card-nmID-text"]');
2359                if (nmIDElement) {
2360                    const text = nmIDElement.textContent.trim();
2361                    const match = text.match(/Артикул\s+WB:\s*(\d+)/i);
2362                    if (match) {
2363                        nmID = match[1];
2364                        console.log(`Wildberries Description Generator: Найден артикул по data-testid: ${nmID}`);
2365                    }
2366                }
2367                
2368                if (!nmID) {
2369                    const link = row.querySelector('a[href*="nmID="]');
2370                    if (link) {
2371                        const match = link.href.match(/nmID=(\d+)/);
2372                        if (match) nmID = match[1];
2373                    }
2374                }
2375                
2376                if (!nmID) {
2377                    nmID = row.dataset.nmid || row.dataset.productId || row.dataset.id;
2378                }
2379                
2380                if (!nmID) {
2381                    const cells = row.querySelectorAll('td');
2382                    cells.forEach(cell => {
2383                        const text = cell.textContent.trim();
2384                        if (/^\d{8,9}$/.test(text)) {
2385                            nmID = text;
2386                        }
2387                    });
2388                }
2389                
2390                if (nmID) {
2391                    let title = '';
2392                    const titleElement = row.querySelector('[data-testid="card-title-text"]');
2393                    if (titleElement) {
2394                        title = titleElement.textContent.trim();
2395                    }
2396                    
2397                    products.push({
2398                        nmID: nmID,
2399                        title: title || `Товар ${nmID}`,
2400                        rowElement: row
2401                    });
2402                    
2403                    console.log(`Wildberries Description Generator: Найден товар ${nmID}: ${title}`);
2404                }
2405            } catch (e) {
2406                console.error(`Wildberries Description Generator: Ошибка при обработке строки ${index}:`, e);
2407            }
2408        });
2409        
2410        console.log(`Wildberries Description Generator: Всего найдено товаров: ${products.length}`);
2411        return products;
2412    }
2413    
2414    function showProgressModal() {
2415        const modal = document.createElement('div');
2416        modal.id = 'wb-progress-modal';
2417        modal.className = 'wb-progress-modal';
2418        modal.innerHTML = `
2419            <div class="wb-progress-header">🤖 Автогенерация описаний</div>
2420            
2421            <div class="wb-progress-stats">
2422                <div class="wb-progress-stat success">
2423                    <div class="wb-progress-stat-number" id="wb-success-count">0</div>
2424                    <div class="wb-progress-stat-label">Успешно</div>
2425                </div>
2426                <div class="wb-progress-stat error">
2427                    <div class="wb-progress-stat-number" id="wb-error-count">0</div>
2428                    <div class="wb-progress-stat-label">Проблема</div>
2429                </div>
2430            </div>
2431            
2432            <div class="wb-progress-current" id="wb-current-product">
2433                Ожидание...
2434            </div>
2435            
2436            <div id="wb-progress-errors-container" style="display: none;">
2437                <div style="font-weight: 600; margin-bottom: 8px; font-size: 14px; color: #374151;">
2438                    Товары с проблемами (клик для просмотра):
2439                </div>
2440                <div class="wb-progress-errors" id="wb-progress-errors"></div>
2441            </div>
2442            
2443            <div class="wb-progress-buttons">
2444                <button class="wb-progress-btn wb-progress-btn-stop" id="wb-stop-btn">⏹ Остановить</button>
2445                <button class="wb-progress-btn wb-progress-btn-close" id="wb-close-progress-btn" style="display: none;">Закрыть</button>
2446            </div>
2447        `;
2448        
2449        document.body.appendChild(modal);
2450        
2451        document.getElementById('wb-stop-btn').addEventListener('click', () => {
2452            autoGenerationStopped = true;
2453            document.getElementById('wb-stop-btn').disabled = true;
2454            document.getElementById('wb-stop-btn').textContent = '⏳ Остановка...';
2455            document.getElementById('wb-current-product').textContent = 'Остановка процесса...';
2456        });
2457        
2458        document.getElementById('wb-close-progress-btn').addEventListener('click', () => {
2459            modal.remove();
2460        });
2461        
2462        return modal;
2463    }
2464    
2465    function updateProgress(successCount, errorCount, currentProduct, errors) {
2466        const successEl = document.getElementById('wb-success-count');
2467        const errorEl = document.getElementById('wb-error-count');
2468        const currentEl = document.getElementById('wb-current-product');
2469        const errorsContainer = document.getElementById('wb-progress-errors-container');
2470        const errorsList = document.getElementById('wb-progress-errors');
2471        
2472        if (successEl) successEl.textContent = successCount;
2473        if (errorEl) errorEl.textContent = errorCount;
2474        if (currentEl) currentEl.textContent = currentProduct;
2475        
2476        if (errors && errors.length > 0 && errorsContainer && errorsList) {
2477            errorsContainer.style.display = 'block';
2478            errorsList.innerHTML = errors.map(err => `
2479                <div class="wb-progress-error-item" data-nmid="${err.nmID}">
2480                    <div style="font-weight: 600;">${err.title}</div>
2481                    <div style="font-size: 12px; margin-top: 4px;">Артикул: ${err.nmID}</div>
2482                    <div style="font-size: 12px; color: #7f1d1b;">${err.error}</div>
2483                </div>
2484            `).join('');
2485            
2486            errorsList.querySelectorAll('.wb-progress-error-item').forEach(item => {
2487                item.addEventListener('click', () => {
2488                    const nmID = item.dataset.nmid;
2489                    window.open(`https://seller.wildberries.ru/new-goods/card?nmID=${nmID}&type=EXIST_CARD`, '_blank');
2490                });
2491            });
2492        }
2493    }
2494    
2495    async function generateKeywordsForProduct(productInfo) {
2496        console.log('Wildberries Description Generator: Генерация ключевых слов через AI');
2497        
2498        const deduplicate = (list) => {
2499            const seen = new Set();
2500            return list.filter(item => {
2501                const key = item.toLowerCase();
2502                if (seen.has(key)) return false;
2503                seen.add(key);
2504                return true;
2505            });
2506        };
2507        
2508        try {
2509            const prompt = generateMasksPrompt(productInfo);
2510            const response = await RM.aiCall(prompt);
2511            const data = JSON.parse(response);
2512            
2513            const aiKeywords = Array.isArray(data.keywords) ? deduplicate(data.keywords.filter(Boolean)) : [];
2514            if (aiKeywords.length > 0) {
2515                console.log(`Wildberries Description Generator: AI сгенерировал ${aiKeywords.length} ключевых слов из нового промпта`);
2516                return aiKeywords;
2517            }
2518            
2519            const masks = Array.isArray(data.masks) ? deduplicate(data.masks.filter(Boolean)) : [];
2520            if (masks.length > 0) {
2521                console.log(`Wildberries Description Generator: Используем маски (${masks.length}) как ключи`);
2522                return masks.slice(0, 10);
2523            }
2524        } catch (e) {
2525            console.error('Wildberries Description Generator: Ошибка при генерации ключевых слов новым промптом:', e);
2526        }
2527        
2528        const fallbackSource = (productInfo.title || '').toLowerCase();
2529        const words = fallbackSource.split(/\s+/).filter(w => w.length > 3);
2530        return words.slice(0, 5);
2531    }
2532    
2533    async function processProduct(product, progressData) {
2534        console.log(`Wildberries Description Generator: Обработка товара ${product.nmID}`);
2535        
2536        try {
2537            updateProgress(
2538                progressData.successCount,
2539                progressData.errorCount,
2540                `Обработка: ${product.title}`,
2541                progressData.errors
2542            );
2543            
2544            // ТЕСТОВЫЙ РЕЖИМ: проверяем флаг
2545            const testMode = await GM.getValue('wb_test_mode', 'false');
2546            
2547            if (testMode === 'true') {
2548                console.log(`Wildberries Description Generator: ТЕСТОВЫЙ РЕЖИМ - имитация обработки товара ${product.nmID}`);
2549                await new Promise(resolve => setTimeout(resolve, 1000)); // Имитация задержки
2550                
2551                progressData.successCount++;
2552                const processedProducts = await GM.getValue('wb_auto_processed_products', '[]');
2553                const processed = JSON.parse(processedProducts);
2554                processed.push(product.nmID);
2555                await GM.setValue('wb_auto_processed_products', JSON.stringify(processed));
2556                
2557                console.log(`Wildberries Description Generator: ТЕСТОВЫЙ РЕЖИМ - товар ${product.nmID} "обработан"`);
2558                return { success: true };
2559            }
2560            
2561            await GM.setValue('wb_auto_current_product', JSON.stringify(product));
2562            await GM.setValue('wb_auto_mode', 'true');
2563            await GM.setValue('wb_auto_product_status', 'processing');
2564            
2565            const productUrl = `https://seller.wildberries.ru/new-goods/card?nmID=${product.nmID}&type=EXIST_CARD`;
2566            console.log(`Wildberries Description Generator: Открываем товар: ${productUrl}`);
2567            
2568            await GM.openInTab(productUrl, false);
2569            
2570            const maxWaitTime = 300000;
2571            const checkInterval = 2000;
2572            let waitedTime = 0;
2573            
2574            while (waitedTime < maxWaitTime) {
2575                await new Promise(resolve => setTimeout(resolve, checkInterval));
2576                waitedTime += checkInterval;
2577                
2578                if (autoGenerationStopped) {
2579                    throw new Error('Генерация остановлена пользователем');
2580                }
2581                
2582                const status = await GM.getValue('wb_auto_product_status', 'processing');
2583                
2584                if (status === 'completed') {
2585                    console.log(`Wildberries Description Generator: Товар ${product.nmID} успешно обработан`);
2586                    progressData.successCount++;
2587                    
2588                    const processedProducts = await GM.getValue('wb_auto_processed_products', '[]');
2589                    const processed = JSON.parse(processedProducts);
2590                    processed.push(product.nmID);
2591                    await GM.setValue('wb_auto_processed_products', JSON.stringify(processed));
2592                    
2593                    return { success: true };
2594                } else if (status === 'error') {
2595                    const errorMsg = await GM.getValue('wb_auto_product_error', 'Неизвестная ошибка');
2596                    console.error(`Wildberries Description Generator: Ошибка при обработке товара ${product.nmID}:`, errorMsg);
2597                    throw new Error(errorMsg);
2598                }
2599            }
2600            
2601            throw new Error('Превышено время ожидания обработки товара');
2602            
2603        } catch (error) {
2604            console.error(`Wildberries Description Generator: Ошибка при обработке товара ${product.nmID}:`, error);
2605            progressData.errorCount++;
2606            progressData.errors.push({
2607                nmID: product.nmID,
2608                title: product.title,
2609                error: error.message
2610            });
2611            
2612            return { success: false, error: error.message };
2613        } finally {
2614            await GM.setValue('wb_auto_product_status', 'none');
2615            await GM.setValue('wb_auto_mode', 'false');
2616        }
2617    }
2618    
2619    async function startAutoGeneration() {
2620        console.log('Wildberries Description Generator: Запуск автогенерации');
2621        
2622        autoGenerationStopped = false;
2623        
2624        // Проверяем тестовый режим
2625        const testMode = await GM.getValue('wb_test_mode', 'false');
2626        if (testMode === 'true') {
2627            console.log('🧪 ТЕСТОВЫЙ РЕЖИМ АКТИВИРОВАН - описания не будут генерироваться');
2628        }
2629        
2630        // Проверяем стартовый индекс для тестирования
2631        const startFromIndex = await GM.getValue('wb_test_start_index', 0);
2632        if (startFromIndex > 0) {
2633            console.log(`🧪 ТЕСТОВЫЙ РЕЖИМ: Начинаем с товара #${startFromIndex}`);
2634        }
2635        
2636        await GM.setValue('wb_auto_processed_products', JSON.stringify([]));
2637        await GM.setValue('wb_auto_generation_active', 'true');
2638        console.log('Wildberries Description Generator: Список обработанных товаров сброшен');
2639        
2640        showProgressModal();
2641        
2642        const autoBtn = document.querySelector('.wb-auto-btn');
2643        if (autoBtn) autoBtn.disabled = true;
2644        
2645        const progressData = {
2646            successCount: 0,
2647            errorCount: 0,
2648            errors: []
2649        };
2650        
2651        const processedProductsStr = await GM.getValue('wb_auto_processed_products', '[]');
2652        const processedProducts = JSON.parse(processedProductsStr);
2653        
2654        let allProducts = [];
2655        let previousProductCount = 0;
2656        let noNewProductsCount = 0;
2657        let productIndex = 0;
2658        
2659        while (true) {
2660            if (autoGenerationStopped) {
2661                console.log('Wildberries Description Generator: Автогенерация остановлена');
2662                break;
2663            }
2664            
2665            // Получаем текущие товары на странице
2666            const currentProducts = getProductsFromPage();
2667            console.log(`Wildberries Description Generator: Найдено товаров на странице: ${currentProducts.length}`);
2668            
2669            // Добавляем новые товары в общий список (избегаем дубликатов)
2670            for (const product of currentProducts) {
2671                if (!allProducts.find(p => p.nmID === product.nmID)) {
2672                    allProducts.push(product);
2673                }
2674            }
2675            
2676            console.log(`Wildberries Description Generator: Всего уникальных товаров: ${allProducts.length}`);
2677            
2678            // Фильтруем необработанные товары
2679            let productsToProcess = allProducts.filter(p => !processedProducts.includes(p.nmID));
2680            
2681            // Применяем стартовый индекс для тестирования
2682            if (startFromIndex > 0 && productIndex === 0) {
2683                console.log(`🧪 ТЕСТОВЫЙ РЕЖИМ: Пропускаем первые ${startFromIndex} товаров`);
2684                const skippedProducts = productsToProcess.slice(0, startFromIndex);
2685                for (const skipped of skippedProducts) {
2686                    processedProducts.push(skipped.nmID);
2687                }
2688                await GM.setValue('wb_auto_processed_products', JSON.stringify(processedProducts));
2689                productsToProcess = allProducts.filter(p => !processedProducts.includes(p.nmID));
2690                console.log(`🧪 ТЕСТОВЫЙ РЕЖИМ: Осталось обработать ${productsToProcess.length} товаров`);
2691            }
2692            
2693            if (productsToProcess.length === 0) {
2694                console.log('Wildberries Description Generator: Все товары обработаны');
2695                break;
2696            }
2697            
2698            // Обрабатываем первый необработанный товар
2699            const product = productsToProcess[0];
2700            productIndex++;
2701            console.log(`Wildberries Description Generator: Обработка товара #${productIndex}: ${product.nmID} (осталось ${productsToProcess.length})`);
2702            
2703            await processProduct(product, progressData);
2704            
2705            // Обновляем список обработанных товаров
2706            const updatedProcessedStr = await GM.getValue('wb_auto_processed_products', '[]');
2707            const updatedProcessed = JSON.parse(updatedProcessedStr);
2708            processedProducts.push(...updatedProcessed.filter(id => !processedProducts.includes(id)));
2709            
2710            updateProgress(
2711                progressData.successCount,
2712                progressData.errorCount,
2713                `Обработано ${processedProducts.length} товаров, найдено ${allProducts.length} на странице`,
2714                progressData.errors
2715            );
2716            
2717            // Проверяем, нужно ли подгружать ещё товары
2718            if (productsToProcess.length <= 1) {
2719                console.log('Wildberries Description Generator: Пытаемся подгрузить ещё товары через скролл');
2720                
2721                // Сохраняем текущее количество товаров
2722                const beforeScrollCount = allProducts.length;
2723                
2724                // Находим контейнер таблицы с товарами
2725                const tableWrapper = document.querySelector('.Table__wrapper__7g4VfuWpTS');
2726                
2727                if (!tableWrapper) {
2728                    console.log('Wildberries Description Generator: Контейнер таблицы не найден');
2729                    break;
2730                }
2731                
2732                // Пробуем несколько способов скролла
2733                for (let scrollAttempt = 0; scrollAttempt < 5; scrollAttempt++) {
2734                    console.log(`Wildberries Description Generator: Попытка скролла #${scrollAttempt + 1}`);
2735                    
2736                    // Способ 1: Скролл контейнера таблицы к концу
2737                    tableWrapper.scrollTop = tableWrapper.scrollHeight;
2738                    await new Promise(resolve => setTimeout(resolve, 2000));
2739                    
2740                    // Способ 2: Скролл на большое расстояние внутри таблицы
2741                    tableWrapper.scrollBy(0, 5000);
2742                    await new Promise(resolve => setTimeout(resolve, 2000));
2743                    
2744                    // Способ 3: Скролл к последнему товару внутри таблицы
2745                    const lastRow = tableWrapper.querySelector('table tbody tr:last-child');
2746                    if (lastRow) {
2747                        lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2748                        await new Promise(resolve => setTimeout(resolve, 2000));
2749                    }
2750                    
2751                    // Проверяем, появились ли новые товары
2752                    const newProducts = getProductsFromPage();
2753                    console.log(`Wildberries Description Generator: После скролла найдено товаров: ${newProducts.length} (было: ${beforeScrollCount})`);
2754                    
2755                    // Добавляем новые товары
2756                    let addedCount = 0;
2757                    for (const product of newProducts) {
2758                        if (!allProducts.find(p => p.nmID === product.nmID)) {
2759                            allProducts.push(product);
2760                            addedCount++;
2761                        }
2762                    }
2763                    
2764                    if (addedCount > 0) {
2765                        console.log(`Wildberries Description Generator: ✅ Подгружено ${addedCount} новых товаров! Всего: ${allProducts.length}`);
2766                        noNewProductsCount = 0;
2767                        break; // Выходим из цикла скролла, продолжаем обработку
2768                    } else {
2769                        console.log(`Wildberries Description Generator: Новых товаров не появилось (попытка ${scrollAttempt + 1}/5)`);
2770                    }
2771                }
2772                
2773                // Если после всех попыток товары не подгрузились
2774                const afterScrollCount = allProducts.length;
2775                if (afterScrollCount === beforeScrollCount) {
2776                    noNewProductsCount++;
2777                    console.log(`Wildberries Description Generator: Товары не подгрузились после 5 попыток (счётчик: ${noNewProductsCount}/3)`);
2778                    
2779                    if (noNewProductsCount >= 3) {
2780                        console.log('Wildberries Description Generator: Достигнут конец списка товаров');
2781                        break;
2782                    }
2783                }
2784                
2785                // Обновляем список необработанных товаров
2786                productsToProcess = allProducts.filter(p => !processedProducts.includes(p.nmID));
2787                console.log(`Wildberries Description Generator: Необработанных товаров: ${productsToProcess.length}`);
2788            }
2789            
2790            await new Promise(resolve => setTimeout(resolve, 1000));
2791        }
2792        
2793        console.log('Wildberries Description Generator: Автогенерация завершена');
2794        updateProgress(
2795            progressData.successCount,
2796            progressData.errorCount,
2797            `✅ Завершено! Успешно: ${progressData.successCount}, Ошибок: ${progressData.errorCount}`,
2798            progressData.errors
2799        );
2800        
2801        document.getElementById('wb-stop-btn').style.display = 'none';
2802        document.getElementById('wb-close-progress-btn').style.display = 'block';
2803        
2804        if (autoBtn) autoBtn.disabled = false;
2805        
2806        await GM.setValue('wb_auto_generation_active', 'false');
2807        
2808        // Сбрасываем тестовые настройки
2809        if (testMode === 'true') {
2810            console.log('🧪 ТЕСТОВЫЙ РЕЖИМ: Завершён');
2811            await GM.setValue('wb_test_mode', 'false');
2812            await GM.setValue('wb_test_start_index', 0);
2813        }
2814    }
2815    
2816    async function autoProcessProductCard() {
2817        const autoMode = await GM.getValue('wb_auto_mode', 'false');
2818        if (autoMode !== 'true') {
2819            return;
2820        }
2821        
2822        console.log('Wildberries Description Generator: Автоматическая обработка карточки товара');
2823        
2824        const progressIndicator = document.createElement('div');
2825        progressIndicator.id = 'wb-auto-progress-indicator';
2826        progressIndicator.style.cssText = `
2827            position: fixed;
2828            top: 20px;
2829            right: 20px;
2830            background: white;
2831            border-radius: 12px;
2832            padding: 20px;
2833            min-width: 300px;
2834            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
2835            z-index: 10000;
2836            font-family: system-ui, -apple-system, sans-serif;
2837        `;
2838        progressIndicator.innerHTML = `
2839            <div style="font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #001a34;">
2840                🤖 Автогенерация описания
2841            </div>
2842            <div id="wb-auto-stage" style="font-size: 14px; color: #374151; margin-bottom: 12px;">
2843                Инициализация...
2844            </div>
2845            <div style="width: 100%; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;">
2846                <div id="wb-auto-progress-bar" style="width: 0%; height: 100%; background: linear-gradient(90deg, #10b981, #059669); transition: width 0.3s;"></div>
2847            </div>
2848        `;
2849        document.body.appendChild(progressIndicator);
2850        
2851        const updateStage = (stage, progress) => {
2852            const stageEl = document.getElementById('wb-auto-stage');
2853            const progressBar = document.getElementById('wb-auto-progress-bar');
2854            if (stageEl) stageEl.textContent = stage;
2855            if (progressBar) progressBar.style.width = progress + '%';
2856        };
2857        
2858        try {
2859            const currentProductStr = await GM.getValue('wb_auto_current_product', '{}');
2860            const currentProduct = JSON.parse(currentProductStr);
2861            
2862            console.log('Wildberries Description Generator: Текущий товар:', currentProduct);
2863            
2864            updateStage('Загрузка страницы товара...', 5);
2865            await new Promise(resolve => setTimeout(resolve, 8000));
2866            
2867            updateStage('Раскрытие характеристик...', 10);
2868            console.log('Wildberries Description Generator: Раскрываем характеристики');
2869            
2870            let expandButton = document.querySelector('div.Characteristics__expand__570w3PkC7D button');
2871            
2872            if (!expandButton) {
2873                const allButtons = document.querySelectorAll('button');
2874                expandButton = Array.from(allButtons).find(btn => 
2875                    btn.textContent && btn.textContent.trim().toLowerCase().includes('показать все')
2876                );
2877            }
2878            
2879            if (!expandButton) {
2880                const characteristicsSection = document.querySelector('[class*="Characteristics"]');
2881                if (characteristicsSection) {
2882                    expandButton = characteristicsSection.querySelector('button');
2883                }
2884            }
2885            
2886            if (expandButton) {
2887                console.log('Wildberries Description Generator: Нажимаем "Показать все"');
2888                expandButton.click();
2889                console.log('Wildberries Description Generator: Ждем 8 секунд для полного раскрытия характеристик');
2890                await new Promise(resolve => setTimeout(resolve, 8000));
2891            } else {
2892                console.log('Wildberries Description Generator: Кнопка "Показать все" не найдена, ждем 3 секунды');
2893                await new Promise(resolve => setTimeout(resolve, 3000));
2894            }
2895            
2896            updateStage('Сбор информации о товаре...', 15);
2897            const productInfo = getProductInfo();
2898            console.log('Wildberries Description Generator: Информация о товаре получена');
2899            
2900            updateStage('AI генерирует ключевые слова...', 20);
2901            const keywords = await generateKeywordsForProduct(productInfo);
2902            console.log('Wildberries Description Generator: Ключевые слова сгенерированы:', keywords);
2903            
2904            if (keywords.length === 0) {
2905                throw new Error('Не удалось сгенерировать ключевые слова');
2906            }
2907            
2908            updateStage('AI анализирует товар...', 25);
2909            await GM.setValue('wb_keywords_to_process', JSON.stringify(keywords));
2910            await GM.setValue('wb_minus_words', JSON.stringify([]));
2911            await GM.setValue('wb_analytics_data', JSON.stringify([]));
2912            await GM.setValue('wb_collection_status', 'pending');
2913            
2914            console.log('Wildberries Description Generator: AI анализирует товар');
2915            const analysisPrompt = generateProductAnalysisPrompt(productInfo, keywords);
2916
2917            const analysisResponse = await RM.aiCall(analysisPrompt);
2918            const productAnalysis = JSON.parse(analysisResponse);
2919            console.log('Wildberries Description Generator: AI-анализ товара:', productAnalysis);
2920            
2921            await GM.setValue('wb_product_analysis', JSON.stringify(productAnalysis));
2922            await GM.setValue('wb_analytics_minus_words', JSON.stringify([]));
2923            
2924            updateStage('Открытие страницы аналитики...', 30);
2925            console.log('Wildberries Description Generator: Открываем аналитику для сбора данных');
2926            const analyticsUrl = 'https://seller.wildberries.ru/search-analytics/popular-search-queries';
2927            await GM.openInTab(analyticsUrl, false);
2928            
2929            updateStage('Сбор данных из аналитики...', 35);
2930            const maxWaitTime = 300000;
2931            const checkInterval = 2000;
2932            let waitedTime = 0;
2933            
2934            while (waitedTime < maxWaitTime) {
2935                await new Promise(resolve => setTimeout(resolve, checkInterval));
2936                waitedTime += checkInterval;
2937                
2938                const status = await GM.getValue('wb_collection_status', 'pending');
2939                
2940                if (status === 'completed') {
2941                    console.log('Wildberries Description Generator: Данные собраны, генерируем описание');
2942                    break;
2943                } else if (status === 'error') {
2944                    throw new Error('Ошибка при сборе данных из аналитики');
2945                }
2946                
2947                const progress = Math.min(60, 35 + Math.floor((waitedTime / maxWaitTime) * 25));
2948                updateStage(`Сбор данных из аналитики... (${Math.floor(waitedTime / 1000)}с)`, progress);
2949            }
2950            
2951            if (waitedTime >= maxWaitTime) {
2952                throw new Error('Превышено время ожидания сбора данных');
2953            }
2954            
2955            updateStage('Обработка собранных данных...', 65);
2956            const analyticsDataStr = await GM.getValue('wb_analytics_data', '[]');
2957            const analyticsData = JSON.parse(analyticsDataStr);
2958            
2959            const allQueries = [];
2960            const queryPopularity = {};
2961            analyticsData.forEach(data => {
2962                data.queries.forEach(q => {
2963                    allQueries.push(q.query);
2964                    queryPopularity[q.query.toLowerCase()] = q.popularity;
2965                });
2966            });
2967            
2968            console.log(`Wildberries Description Generator: Собрано ${allQueries.length} запросов`);
2969            
2970            if (allQueries.length === 0) {
2971                throw new Error('Не найдено подходящих запросов для генерации');
2972            }
2973            
2974            const minusWords = [];
2975            
2976            console.log('Wildberries Description Generator: Применяем минус-слова перед генерацией');
2977            console.log('Wildberries Description Generator: Минус-слова:', minusWords);
2978            console.log('Wildberries Description Generator: Запросов до фильтрации:', allQueries.length);
2979            
2980            const filteredQueries = allQueries.filter(query => {
2981                const queryLower = query.toLowerCase();
2982                const hasMinusWord = minusWords.some(minusWord => 
2983                    queryLower.includes(minusWord.toLowerCase())
2984                );
2985                
2986                if (hasMinusWord) {
2987                    console.log(`Wildberries Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
2988                }
2989                
2990                return !hasMinusWord;
2991            });
2992            
2993            console.log('Wildberries Description Generator: Запросов после фильтрации:', filteredQueries.length);
2994            
2995            if (filteredQueries.length === 0) {
2996                throw new Error('Все запросы отфильтрованы минус-словами. Попробуйте уменьшить количество минус-слов.');
2997            }
2998            
2999            updateStage('AI генерирует описание...', 70);
3000            console.log('Wildberries Description Generator: Генерируем описание');
3001            
3002            const descriptionPrompt = generateDescriptionPrompt(productInfo, keywords, filteredQueries, queryPopularity);
3003
3004            const description = await RM.aiCall(descriptionPrompt);
3005            
3006            await GM.setValue('wb_generated_description', description);
3007            
3008            updateStage('Постобработка текста...', 85);
3009            let cleanedDescription = description;
3010            const englishToRussian = {
3011                'crucial': 'ключевой',
3012                'Crucial': 'Ключевой',
3013                'essential': 'важный',
3014                'Essential': 'Важный',
3015                'vital': 'жизненно важный',
3016                'Vital': 'Жизненно важный',
3017                'key': 'ключевой',
3018                'Key': 'Ключевой',
3019                'important': 'важный',
3020                'Important': 'Важный',
3021                'testosterone': 'тестостерон',
3022                'Testosterone': 'Тестостерон',
3023                'energy': 'энергия',
3024                'Energy': 'Энергия'
3025            };
3026            
3027            Object.entries(englishToRussian).forEach(([eng, rus]) => {
3028                const regex = new RegExp('\\b' + eng + '\\b', 'g');
3029                cleanedDescription = cleanedDescription.replace(regex, rus);
3030            });
3031            
3032            console.log('Wildberries Description Generator: Постобработка завершена');
3033            
3034            updateStage('Вставка описания в карточку...', 90);
3035            console.log('Wildberries Description Generator: Вставляем описание');
3036            const descriptionTextarea = document.querySelector('textarea[data-testid="card-form-main-field-description"]');
3037            if (!descriptionTextarea) {
3038                throw new Error('Не удалось найти поле описания');
3039            }
3040            
3041            descriptionTextarea.value = cleanedDescription;
3042            descriptionTextarea.dispatchEvent(new Event('input', { bubbles: true }));
3043            descriptionTextarea.dispatchEvent(new Event('change', { bubbles: true }));
3044            
3045            updateStage('Сохранение товара...', 95);
3046            console.log('Wildberries Description Generator: Сохраняем товар');
3047            const saveButton = document.querySelector('button[data-testid="save-card-button-primary"]');
3048            if (saveButton) {
3049                await GM.setValue('wb_auto_close_tab', 'true');
3050                
3051                window.addEventListener('beforeunload', () => {
3052                    console.log('Wildberries Description Generator: beforeunload - закрываем вкладку');
3053                    window.close();
3054                });
3055                
3056                const origPushState = history.pushState;
3057                history.pushState = function() {
3058                    console.log('Wildberries Description Generator: history.pushState - закрываем вкладку');
3059                    window.close();
3060                    return origPushState.apply(this, arguments);
3061                };
3062                
3063                saveButton.click();
3064                console.log('Wildberries Description Generator: Кнопка сохранения нажата');
3065                
3066                setTimeout(() => {
3067                    console.log('Wildberries Description Generator: Попытка закрытия #1 (500мс)');
3068                    window.close();
3069                }, 500);
3070                
3071                setTimeout(() => {
3072                    console.log('Wildberries Description Generator: Попытка закрытия #2 (1000мс)');
3073                    window.close();
3074                }, 1000);
3075                
3076                setTimeout(() => {
3077                    console.log('Wildberries Description Generator: Попытка закрытия #3 (1500мс)');
3078                    window.close();
3079                }, 1500);
3080                
3081                setTimeout(() => {
3082                    console.log('Wildberries Description Generator: Попытка закрытия #4 (2000мс) - финальная');
3083                    window.close();
3084                }, 2000);
3085            } else {
3086                console.log('Wildberries Description Generator: Кнопка сохранения не найдена, пропускаем');
3087            }
3088            
3089            updateStage('✅ Готово! Закрытие вкладки...', 100);
3090            await GM.setValue('wb_auto_product_status', 'completed');
3091            await GM.setValue('wb_auto_mode', 'false');
3092            
3093            console.log('Wildberries Description Generator: Товар успешно обработан, закрываем вкладку');
3094            
3095        } catch (error) {
3096            console.error('Wildberries Description Generator: Ошибка при автоматической обработке товара:', error);
3097            await GM.setValue('wb_auto_product_status', 'error');
3098            await GM.setValue('wb_auto_product_error', error.message);
3099            await GM.setValue('wb_auto_mode', 'false');
3100            
3101            setTimeout(() => {
3102                window.close();
3103            }, 3000);
3104        } finally {
3105            setTimeout(() => {
3106                console.log('Wildberries Description Generator: ПРИНУДИТЕЛЬНОЕ закрытие вкладки');
3107                window.close();
3108            }, 2000);
3109        }
3110    }
3111
3112    // ============================================
3113    // ИНИЦИАЛИЗАЦИЯ
3114    // ============================================
3115
3116    function init() {
3117        console.log('Wildberries Description Generator: Инициализация');
3118    
3119        if (window.location.href.includes('seller.wildberries.ru/new-goods/card')) {
3120        
3121            GM.getValue('wb_auto_mode', 'false').then(autoMode => {
3122                console.log('Wildberries Description Generator: Режим автогенерации:', autoMode);
3123            
3124                if (autoMode === 'true') {
3125                    console.log('Wildberries Description Generator: Режим автогенерации, запускаем автообработку');
3126                    setTimeout(autoProcessProductCard, 2000);
3127                }
3128            
3129                console.log('Wildberries Description Generator: Добавляем кнопку генератора');
3130                const observer = new MutationObserver((mutations, obs) => {
3131                    const descriptionHeader = document.querySelector('.Description-header__zK-9sKs8RX');
3132                    if (descriptionHeader) {
3133                        createGeneratorButton();
3134                        obs.disconnect();
3135                    }
3136                });
3137            
3138                observer.observe(document.body, {
3139                    childList: true,
3140                    subtree: true
3141                });
3142            
3143                setTimeout(createGeneratorButton, 2000);
3144            });
3145        }
3146    
3147        if (window.location.href.includes('seller.wildberries.ru/new-goods/all-goods')) {
3148            console.log('Wildberries Description Generator: Страница списка товаров');
3149            setTimeout(createAutoGenerationButton, 2000);
3150        
3151            window.addEventListener('beforeunload', async () => {
3152                const generationActive = await GM.getValue('wb_auto_generation_active', 'false');
3153                if (generationActive === 'true') {
3154                    console.log('Wildberries Description Generator: Вкладка со списком закрывается, останавливаем генерацию');
3155                    await GM.setValue('wb_auto_generation_active', 'false');
3156                    await GM.setValue('wb_auto_mode', 'false');
3157                    await GM.setValue('wb_auto_product_status', 'error');
3158                    await GM.setValue('wb_auto_product_error', 'Вкладка со списком товаров была закрыта');
3159                }
3160            });
3161            
3162            // Добавляем глобальные команды для тестирования
3163            window.WB_TEST = {
3164                enableTestMode: async (startFromIndex = 19) => {
3165                    await GM.setValue('wb_test_mode', 'true');
3166                    await GM.setValue('wb_test_start_index', startFromIndex);
3167                    console.log(`🧪 ТЕСТОВЫЙ РЕЖИМ ВКЛЮЧЕН: Начало с товара #${startFromIndex}`);
3168                    console.log('Теперь нажмите кнопку "🤖 Авто описание"');
3169                },
3170                disableTestMode: async () => {
3171                    await GM.setValue('wb_test_mode', 'false');
3172                    await GM.setValue('wb_test_start_index', 0);
3173                    console.log('🧪 ТЕСТОВЫЙ РЕЖИМ ОТКЛЮЧЕН');
3174                },
3175                checkStatus: async () => {
3176                    const testMode = await GM.getValue('wb_test_mode', 'false');
3177                    const startIndex = await GM.getValue('wb_test_start_index', 0);
3178                    const processed = await GM.getValue('wb_auto_processed_products', '[]');
3179                    console.log('📊 СТАТУС:');
3180                    console.log('  Тестовый режим:', testMode);
3181                    console.log('  Стартовый индекс:', startIndex);
3182                    console.log('  Обработано товаров:', JSON.parse(processed).length);
3183                },
3184                reset: async () => {
3185                    await GM.setValue('wb_auto_processed_products', JSON.stringify([]));
3186                    await GM.setValue('wb_test_mode', 'false');
3187                    await GM.setValue('wb_test_start_index', 0);
3188                    console.log('🔄 ВСЕ НАСТРОЙКИ СБРОШЕНЫ');
3189                }
3190            };
3191            
3192            console.log('🧪 ТЕСТОВЫЕ КОМАНДЫ ДОСТУПНЫ:');
3193            console.log('  WB_TEST.enableTestMode(19) - включить тест с 19-го товара');
3194            console.log('  WB_TEST.disableTestMode() - выключить тестовый режим');
3195            console.log('  WB_TEST.checkStatus() - проверить статус');
3196            console.log('  WB_TEST.reset() - сбросить все настройки');
3197        }
3198    
3199        if (window.location.href.includes('seller.wildberries.ru/search-analytics/popular-search-queries')) {
3200            setTimeout(autoCollectOnAnalyticsPage, 2000);
3201        }
3202    }
3203
3204    if (document.readyState === 'loading') {
3205        document.addEventListener('DOMContentLoaded', init);
3206    } else {
3207        init();
3208    }
3209
3210    document.addEventListener('wb-open-generator-modal', () => {
3211        console.log('Wildberries Description Generator: Получено событие открытия модального окна');
3212        openModal();
3213    });
3214
3215})();
Wildberries Description Generator 3.0 | Robomonkey