Wildberries Description Generator 3.1

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

Size

157.5 KB

Version

3.1.16

Created

Jan 22, 2026

Updated

3 months ago

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