Ozon Description Generator 3.0

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

Size

133.4 KB

Version

3.2.96

Created

Jan 24, 2026

Updated

24 days ago

1// ==UserScript==
2// @name		Ozon Description Generator 3.0
3// @description		Генератор SEO-описаний для товаров на Ozon с анализом ключевых слов
4// @version		3.2.96
5// @match		https://*.seller.ozon.ru/*
6// @icon		https://cdn.ozon.ru/s3/cms/branding/favicon/favicon-32x32-9e4e5b2c9b.png
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // ============================================
12    // УТИЛИТЫ
13    // ============================================
14
15    function debounce(func, wait) {
16        let timeout;
17        return function executedFunction(...args) {
18            clearTimeout(timeout);
19            timeout = setTimeout(() => func(...args), wait);
20        };
21    }
22
23    function formatNumber(num) {
24        if (num >= 1000000) {
25            return (num / 1000000).toFixed(1) + 'M';
26        } else if (num >= 1000) {
27            return (num / 1000).toFixed(1) + 'K';
28        }
29        return num.toString();
30    }
31
32    // ============================================
33    // AI PROMPT GENERATORS
34    // ============================================
35
36    function generateMasksPrompt(productInfo) {
37        const compositionSection = productInfo.composition 
38            ? `• Состав товара: ${productInfo.composition}\n` 
39            : '';
40        
41        return `Ты — эксперт по SEO на OZON. Предложи поисковые маски и ключевые слова ДЛЯ КОНКРЕТНОГО ТОВАРА.
42
43ДАННЫЕ О ТОВАРЕ:
44• Название: ${productInfo.title || 'не указано'}
45${compositionSection}
46ВАЖНОЕ ПРАВИЛО:
471. АНАЛИЗИРУЙ название товара и используй ТОЧНО ТОТ ТИП ТОВАРА, который указан в названии
482. НЕ заменяй тип товара на другой, даже если похоже
493. Если в названии "маска для лица" — ВСЕ ключи должны быть про МАСКУ, не про сыворотку, крем или другие товары
504. Если в названии "сыворотка" — все ключи про сыворотку
515. Если в названии "крем" — все ключи про крем
52${productInfo.composition ? '6. ИСПОЛЬЗУЙ маски с ключевыми ингредиентами из состава товара\n' : ''}
536. ОБЯЗАТЕЛЬНО включай СИНОНИМЫ и СМЕЖНЫЕ ФОРМУЛИРОВКИ типа товара (пенка → пена, средство; крем → средство; маска → средство)
547. Используй РАЗНЫЕ ВАРИАНТЫ одного понятия для максимального охвата запросов
55
56Предложи 2 типа запросов:
57
581. МАСКИ (10 штук) — короткие слова и фразы:
59   - Используй ТОЧНО тип товара из названия (2-3 маски)
60   - Добавь СИНОНИМЫ типа товара (2-3 маски): если "пенка" → добавь "пена", "средство"
61   - Добавь назначение/применение (2 маски): "для умывания", "для лица", "для очищения"
62   - Добавь категорийные маски (2 маски): "умывание", "очищение", "уход"
63   ${productInfo.composition ? '- Добавь маски с ключевыми ингредиентами из состава (1 маска)\n' : ''}
64   - Пример для "пенка для умывания": ["пенка", "пена", "средство для", "для умывания", "для лица", "умывание", "очищение", "гель для", "умывалка", "для снятия"]
65
662. КЛЮЧЕВЫЕ СЛОВА (10 штук) — точные запросы:
67   - Начинай с типа товара из названия (2 ключа)
68   - Добавь варианты с СИНОНИМАМИ (3 ключа): "пенка для умывания" → "пена для умывания", "средство для умывания"
69   - Добавь смежные категории (2 ключа): "гель для умывания", "средство для снятия макияжа"
70   - Добавь с назначением (2 ключа): "для умывания лица", "для очищения кожи"
71   ${productInfo.composition ? '- Добавь ключевые слова с компонентами из состава (1 ключ)\n' : ''}
72   - Пример для "пенка для умывания": ["пенка для умывания", "пена для умывания", "средство для умывания", "гель для умывания", "для умывания лица", "средство для снятия макияжа", "пенка с гиалуроновой", "очищающая пенка", "умывалка для лица", "средство для очищения"]
73
74КАТЕГОРИИ ДЛЯ МАСОК:
75- ТИП ТОВАРА ИЗ НАЗВАНИЯ (2-3 маски): основное название + синонимы
76- Назначение/применение (2 маски): "для умывания", "для лица"
77- Категорийные (2 маски): "умывание", "очищение"
78- Смежные типы товара (2 маски): если "пенка" → "гель", "средство"
79- Ингредиенты из названия${productInfo.composition ? ' и состава' : ''} (1 маска)
80
81КАТЕГОРИИ ДЛЯ КЛЮЧЕВЫХ СЛОВ:
82- ТИП ТОВАРА + назначение (2 ключа)
83- СИНОНИМЫ типа товара + назначение (3 ключа)
84- Смежные категории + назначение (2 ключа)
85- ТИП ТОВАРА + аудитория (1 ключ)
86- ТИП ТОВАРА + ингредиент${productInfo.composition ? ' из состава' : ''} (2 ключа)
87
88ПРАВИЛА:
89- Используй ТОЧНО тип товара из названия, но ОБЯЗАТЕЛЬНО добавь его синонимы
90- ОБЯЗАТЕЛЬНО включай разные формулировки: "пенка" → "пена", "средство", "гель"
91- Все слова на русском (кроме названий компонентов типа "hyaluronic", "niacinamide")
92- Без брендов
93${productInfo.composition ? '- ОБЯЗАТЕЛЬНО используй компоненты из состава для создания ключевых слов\n' : ''}
94
95ПРИМЕРЫ СИНОНИМОВ ПО КАТЕГОРИЯМ:
96• Пенка → пена, средство, гель, умывалка
97• Крем → средство, продукт, косметика
98• Сыворотка → средство, концентрат, эссенция
99• Маска → средство, патч
100• БАД → добавка, комплекс, препарат (не лекарство!)
101• Витамины → комплекс, добавка, питание
102• Протеин → белок, добавка, питание
103
104ПРИМЕР ДЛЯ "Пенка для умывания с гиалуроновой кислотой":
105{
106 "masks": ["пенка", "пена", "средство для", "для умывания", "для лица", "умывание", "гель для", "умывалка", "гиалуроновая", "очищение"],
107 "keywords": ["пенка для умывания", "пена для умывания", "средство для умывания", "гель для умывания", "для умывания лица", "средство для снятия макияжа", "пенка с гиалуроновой", "очищающая пенка", "умывалка для лица", "средство для очищения"]
108}
109
110ПРИМЕР ДЛЯ "Сыворотка для лица с витамином С":
111{
112 "masks": ["сыворотка", "средство для", "для лица", "витамин с", "осветление", "концентрат", "эссенция", "уход", "косметика", "для кожи"],
113 "keywords": ["сыворотка для лица", "средство для лица", "сыворотка с витамином с", "осветляющая сыворотка", "концентрат для лица", "эссенция для лица", "средство с витамином с", "сыворотка для кожи", "косметика для лица", "уход за лицом"]
114}
115
116Верни ТОЛЬКО JSON:
117{
118 "masks": ["маска1", "маска2", ..., "маска10"],
119 "keywords": ["ключ1", "ключ2", ..., "ключ10"]
120}
121
122Начни ответ сразу с {`;
123    }
124
125    function generateDescriptionPrompt(productInfo, keywords, filteredQueries, queryPopularity = {}, customPrompt = '') {
126        const sortedQueries = filteredQueries
127            .map(q => ({ query: q, pop: queryPopularity[q.toLowerCase()] || 0 }))
128            .sort((a, b) => b.pop - a.pop);
129        
130        const highPriority = sortedQueries.slice(0, 25).map(q => q.query);
131        const mediumPriority = sortedQueries.slice(25, 70).map(q => q.query);
132        const lowPriority = sortedQueries.slice(70, 120).map(q => q.query);
133        
134        console.log(`Ozon Description Generator: Запросов для промпта - Высокий: ${highPriority.length}, Средний: ${mediumPriority.length}, Низкий: ${lowPriority.length}, Всего: ${highPriority.length + mediumPriority.length + lowPriority.length}`);
135
136        const compositionSection = productInfo.composition 
137            ? `• Состав товара: ${productInfo.composition}\n` 
138            : '';
139
140        const basePrompt = `Создай SEO-описание товара для Ozon.
141
142ДАННЫЕ:
143• Название: ${productInfo.title || 'не указано'}
144${compositionSection}• Базовые ключи: ${keywords.join(', ')}
145
146ЗАПРОСЫ ПО ПРИОРИТЕТУ:
147
148🔴 ВЫСОКИЙ ПРИОРИТЕТ (использовать ВСЕ 100%, минимум 1 раз каждый):
149${highPriority.map((q, i) => `${i+1}. "${q}"`).join('\n')}
150
151🟡 СРЕДНИЙ ПРИОРИТЕТ (использовать 80-90%):
152${mediumPriority.length > 0 ? mediumPriority.map((q, i) => `${i+1}. "${q}"`).join('\n') : 'нет запросов'}
153
154🟢 НИЗКИЙ ПРИОРИТЕТ (использовать 50-70%):
155${lowPriority.length > 0 ? lowPriority.map((q, i) => `${i+1}. "${q}"`).join('\n') : 'нет запросов'}
156
157ТРЕБОВАНИЯ:
1581) Объем 3500–4000 символов, только текст.
1592) Структура (каждая часть с нового абзаца. между абзацами пустая строка): 
160   - Введение (400-500 симв.) — суть товара, что это, зачем, для кого, что делает кратко.
161   - Проблема и решение (600-800 симв.) — основная проблема, которую решает это средство, к чему приводит проблема. Кратко как этот продукт решает проблему.
162   - Состав и действие (800-1000 симв.) — ${productInfo.composition ? 'ОБЯЗАТЕЛЬНО используй компоненты из состава товара. ' : ''}ингредиенты и их польза. Подробная информация о составе и компонентах которые усиливают друг друга. как они работают, зачем они нужны.
163   - Применение (600-800 симв.) — для кого, как работает
164   - Отзывы покупателей (300-400 симв.) — отдельный абзац, который начинается фразой "Наши покупатели отмечают ... через ...", где описан эффект и сроки появления результата
165   - Заключение (400-500 симв.) — результат для покупателя.  что получит покупатель при использовании средства.
1663) Плавные переходы между частями, без заголовков.
1674) Все базовые ключи — минимум 1 раз.
1685) Каждый запрос использовать не более 2 раз.
1696) Если компонента из запроса нет в составе — используй через «альтернативу» (засчитывается).
1707) Размер текста минимум 3000 символов - ПРОВЕРЬ!
171${productInfo.composition ? '8) ВАЖНО: В разделе "Состав и действие" ОБЯЗАТЕЛЬНО используй компоненты из состава товара. Опиши их действие и пользу.\n' : ''}
172
173ГРУППИРОВКА ЗАПРОСОВ:
174${productInfo.composition ? '9' : '8'}) Сгруппируй запросы по смыслу:
175   • Тип/категория товара
176   • Проблемы/назначение
177   • Состав/ингредиенты
178   • Аудитория
179   • Эффект/результат
180${productInfo.composition ? '10' : '9'}) В каждом абзаце используй запросы из 2-3 групп одновременно.
181${productInfo.composition ? '11' : '10'}) Комбинируй разные типы запросов в одном предложении естественным образом.
182
183ПРАВИЛА ПЛОТНОСТИ КЛЮЧЕЙ:
184${productInfo.composition ? '12' : '11'}) Плотность ключей: 4-5 запросов на 100 слов (это нормально для SEO).
185${productInfo.composition ? '13' : '12'}) В каждом абзаце минимум 3-4 ключевых запроса.
186${productInfo.composition ? '14' : '13'}) Встраивай ключи естественно в середину и начало предложений.
187${productInfo.composition ? '15' : '14'}) Используй синонимы и вариации ключей для естественности.
188${productInfo.composition ? '16' : '15'}) Одно и то же ключевое словосочетание — максимум 2 раза в тексте.
189${productInfo.composition ? '17' : '16'}) Чередуй короткие (2-3 слова) и длинные (4-5 слов) ключи.
190
191СТИЛЬ:
192${productInfo.composition ? '18' : '17'}) Информативно, конкретные факты, без воды.
193${productInfo.composition ? '19' : '18'}) Между абзацами пустая строка.
194${productInfo.composition ? '20' : '19'}) Чередуй длину предложений: 30% короткие (8-12 слов), 70% средние (13-20 слов).
195${productInfo.composition ? '21' : '20'}) Используй разные конструкции предложений для естественности.
196
197СТРОГИЕ ЗАПРЕТЫ:
198${productInfo.composition ? '22' : '21'}) ТОЛЬКО РУССКИЙ ЯЗЫК. Все слова должны быть на русском языке.
199${productInfo.composition ? '23' : '22'}) ЗАПРЕЩЕНы английские слова, транслитерация, латиница (кроме химических формул типа "pH").
200${productInfo.composition ? '24' : '23'}) ЗАПРЕЩЕНы названия брендов, компаний, производителей, торговых марок.
201${productInfo.composition ? '25' : '24'}) ЗАПРЕЩЕНы имена, фамилии, названия конкретных продуктов конкурентов.
202${productInfo.composition ? '26' : '25'}) ЗАПРЕЩЕНы слова: «лекарство», «препарат», «революционный», «инновационный», «уникальный».
203${productInfo.composition ? '27' : '26'}) Без заголовков, списков, вопросов, эмоджи, инструкций хранения и описания неактивных компонентов.
204
205ПРИМЕРЫ ЗАПРЕЩЕННЫХ СЛОВ (НЕ ИСПОЛЬЗУЙ):
206❌ Nivea, Garnier, L'Oreal, Mixit, CeraVe, La Roche-Posay
207❌ serum, cream, face, skin, beauty, natural, organic
208❌ anti-age, anti-aging, lifting, peeling
209❌ hyaluronic acid (правильно: гиалуроновая кислота)
210❌ vitamin C (правильно: витамин С)
211
212ПРАВИЛЬНЫЕ ВАРИАНТЫ:
213✅ Вместо "serum" → "сыворотка"
214✅ Вместо "cream" → "крем"
215✅ Вместо "anti-age" → "антивозрастной" или "против старения"
216✅ Вместо "lifting" → "подтягивающий эффект" или "лифтинг-эффект"
217✅ Вместо названий брендов → общие категории товара
218
219ПРИМЕР ХОРОШЕГО АБЗАЦА С ВЫСОКОЙ ПЛОТНОСТЬЮ КЛЮЧЕЙ:
220Натуральный гель для интимной гигиены обеспечивает деликатный уход и поддерживает естественный pH баланс. Средство для интимной гигиены с мягкой формулой подходит для ежедневного применения и помогает предотвратить дискомфорт. Гель с пантенолом и экстрактом лаванды увлажняет кожу деликатных зон, а водная основа делает текстуру легкой и комфортной. Интимный гель для женщин не содержит агрессивных компонентов и подходит даже для чувствительной кожи.
221
222ПРИМЕР ПЛОХОГО АБЗАЦА (НЕ ДЕЛАЙ ТАК):
223Гель для интимной гигиены очищает. Интимный гель увлажняет. Гель для женщин помогает.
224
225ВАЖНО:
226- Используй МАКСИМУМ запросов из списка
227- Вплетай их естественно, но плотно
228- Не пиши как и
229- Не бойся использовать 4-5 ключей на 100 слов
230- Главное - сохранять читаемость и естественность
231- ПРОВЕРЬ текст перед отправкой: нет ли английских слов, брендов, конкурентов
232${productInfo.composition ? '- ОБЯЗАТЕЛЬНО используй компоненты из состава товара в разделе "Состав и действие"\n' : ''}
233
234${customPrompt ? `\n\nДОПОЛНИТЕЛЬНЫЕ ТРЕБОВАНИЯ:\n${customPrompt}\n` : ''}
235
236ВЫВОД:
237Начни сразу с описания. Никаких вступлений, вопросов и пояснений.`;
238
239        return basePrompt;
240    }
241
242    // ============================================
243    // СТИЛИ
244    // ============================================
245
246    TM_addStyle(`
247        .ozon-desc-modal {
248            position: fixed;
249            top: 0;
250            left: 0;
251            width: 100%;
252            height: 100%;
253            background: rgba(0, 0, 0, 0.5);
254            display: flex;
255            align-items: center;
256            justify-content: center;
257            z-index: 10000;
258        }
259        
260        .ozon-desc-modal-content {
261            background: white;
262            border-radius: 12px;
263            padding: 24px;
264            max-width: 700px;
265            width: 90%;
266            max-height: 85vh;
267            overflow-y: auto;
268            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
269        }
270        
271        .ozon-desc-modal-header {
272            font-size: 22px;
273            font-weight: 600;
274            margin-bottom: 20px;
275            color: #001a34;
276        }
277        
278        .ozon-desc-input-group {
279            margin-bottom: 16px;
280        }
281        
282        .ozon-desc-label {
283            display: block;
284            margin-bottom: 8px;
285            font-weight: 500;
286            color: #001a34;
287            font-size: 16px;
288        }
289        
290        .ozon-desc-textarea {
291            width: 100%;
292            min-height: 100px;
293            padding: 12px;
294            border: 1px solid #d1d5db;
295            border-radius: 8px;
296            font-size: 16px;
297            font-family: inherit;
298            resize: vertical;
299            box-sizing: border-box;
300        }
301        
302        .ozon-desc-result {
303            background: #f3f4f6;
304            padding: 16px;
305            border-radius: 8px;
306            margin-bottom: 16px;
307            max-height: 300px;
308            overflow-y: auto;
309            white-space: pre-wrap;
310            word-wrap: break-word;
311            font-size: 15px;
312            line-height: 1.6;
313        }
314        
315        .ozon-desc-char-count {
316            text-align: right;
317            font-size: 14px;
318            color: #6b7280;
319            margin-top: 4px;
320        }
321        
322        .ozon-desc-char-count.success {
323            color: #10b981;
324        }
325        
326        .ozon-desc-buttons {
327            display: flex;
328            gap: 12px;
329            justify-content: flex-end;
330            margin-top: 20px;
331            flex-wrap: wrap;
332        }
333        
334        .ozon-desc-btn {
335            padding: 10px 20px;
336            border: none;
337            border-radius: 8px;
338            font-size: 16px;
339            font-weight: 500;
340            cursor: pointer;
341            transition: all 0.2s;
342        }
343        
344        .ozon-desc-btn-primary {
345            background: linear-gradient(135deg, #005bff, #0041cc);
346            color: white;
347        }
348        
349        .ozon-desc-btn-primary:hover {
350            background: linear-gradient(135deg, #0041cc, #0033a0);
351            transform: translateY(-1px);
352        }
353        
354        .ozon-desc-btn-primary:disabled {
355            background: #9ca3af;
356            cursor: not-allowed;
357            transform: none;
358        }
359        
360        .ozon-desc-btn-secondary {
361            background: #e5e7eb;
362            color: #374151;
363        }
364        
365        .ozon-desc-btn-secondary:hover {
366            background: #d1d5db;
367        }
368        
369        .ozon-desc-btn-success {
370            background: linear-gradient(135deg, #10b981, #059669);
371            color: white;
372        }
373        
374        .ozon-desc-btn-success:hover {
375            background: linear-gradient(135deg, #059669, #047857);
376            transform: translateY(-1px);
377        }
378        
379        .ozon-desc-generator-btn {
380            margin-top: 12px;
381            padding: 10px 20px;
382            background: linear-gradient(135deg, #005bff, #0041cc);
383            color: white;
384            border: none;
385            border-radius: 8px;
386            font-size: 16px;
387            font-weight: 500;
388            cursor: pointer;
389            transition: all 0.2s;
390            width: 100%;
391        }
392        
393        .ozon-desc-generator-btn:hover {
394            background: linear-gradient(135deg, #0041cc, #0033a0);
395            transform: translateY(-2px);
396        }
397        
398        .ozon-desc-status {
399            margin-top: 12px;
400            padding: 12px 16px;
401            border-radius: 8px;
402            font-size: 15px;
403        }
404        
405        .ozon-desc-status.info {
406            background: #dbeafe;
407            color: #1e40af;
408            border-left: 4px solid #3b82f6;
409        }
410        
411        .ozon-desc-status.success {
412            background: #d1fae5;
413            color: #065f46;
414            border-left: 4px solid #10b981;
415        }
416        
417        .ozon-desc-status.error {
418            background: #fee2e2;
419            color: #991b1b;
420            border-left: 4px solid #ef4444;
421        }
422        
423        .ozon-desc-suggest-btn {
424            margin-top: 8px;
425            padding: 8px 16px;
426            background: linear-gradient(135deg, #6366f1, #8b5cf6);
427            color: white;
428            border: none;
429            border-radius: 6px;
430            font-size: 15px;
431            font-weight: 500;
432            cursor: pointer;
433            transition: all 0.2s;
434        }
435        
436        .ozon-desc-suggest-btn:hover {
437            background: linear-gradient(135deg, #4f46e5, #7c3aed);
438            transform: translateY(-1px);
439        }
440        
441        .ozon-desc-suggest-btn:disabled {
442            background: #9ca3af;
443            cursor: not-allowed;
444            transform: none;
445        }
446        
447        .ozon-desc-masks-container {
448            margin-top: 12px;
449            padding: 16px;
450            background: #f9fafb;
451            border-radius: 8px;
452            border: 1px solid #e5e7eb;
453        }
454        
455        .ozon-desc-masks-header {
456            font-weight: 600;
457            margin-bottom: 12px;
458            color: #374151;
459            font-size: 15px;
460            display: flex;
461            justify-content: space-between;
462            align-items: center;
463            flex-wrap: wrap;
464            gap: 8px;
465        }
466        
467        .ozon-desc-masks-group {
468            margin-bottom: 12px;
469        }
470        
471        .ozon-desc-masks-group:last-child {
472            margin-bottom: 0;
473        }
474        
475        .ozon-desc-masks-group-title {
476            font-size: 12px;
477            color: #6b7280;
478            margin-bottom: 8px;
479            text-transform: uppercase;
480            letter-spacing: 0.5px;
481        }
482        
483        .ozon-desc-masks-grid {
484            display: flex;
485            flex-wrap: wrap;
486            gap: 8px;
487        }
488        
489        .ozon-mask-chip {
490            display: inline-flex;
491            align-items: center;
492            gap: 6px;
493            padding: 8px 14px;
494            border-radius: 20px;
495            font-size: 14px;
496            cursor: pointer;
497            transition: all 0.2s;
498            border: 2px solid transparent;
499            user-select: none;
500        }
501        
502        .ozon-mask-chip:hover {
503            transform: translateY(-2px);
504            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
505        }
506        
507        .ozon-mask-chip.selected {
508            border-color: #10b981;
509            box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
510        }
511        
512        .ozon-mask-chip[data-type="маска"] { background: #dbeafe; color: #1e40af; }
513        .ozon-mask-chip[data-type="ключ"] { background: #fef3c7; color: #92400e; }
514        
515        .ozon-desc-stats {
516            margin-top: 12px;
517            padding: 14px;
518            background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
519            border-radius: 8px;
520            font-size: 14px;
521        }
522        
523        .ozon-desc-stats-row {
524            display: flex;
525            justify-content: space-between;
526            margin-bottom: 6px;
527        }
528        
529        .ozon-desc-stats-row:last-child {
530            margin-bottom: 0;
531        }
532        
533        .ozon-desc-usage-link {
534            color: #6366f1;
535            text-decoration: underline;
536            cursor: pointer;
537            font-size: 14px;
538        }
539        
540        .ozon-desc-usage-link:hover {
541            color: #4f46e5;
542        }
543        
544        .ozon-desc-analytics-modal {
545            position: fixed;
546            top: 0;
547            left: 0;
548            width: 100%;
549            height: 100%;
550            background: rgba(0, 0, 0, 0.5);
551            display: flex;
552            align-items: center;
553            justify-content: center;
554            z-index: 10001;
555        }
556        
557        .ozon-desc-analytics-content {
558            background: white;
559            border-radius: 12px;
560            padding: 24px;
561            max-width: 900px;
562            width: 95%;
563            max-height: 85vh;
564            overflow-y: auto;
565            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
566        }
567        
568        .ozon-desc-query-item {
569            padding: 10px 14px;
570            margin-bottom: 6px;
571            border-radius: 8px;
572            font-size: 14px;
573            display: flex;
574            justify-content: space-between;
575            align-items: center;
576            transition: all 0.2s;
577        }
578        
579        .ozon-desc-query-item.used {
580            background: #d1fae5;
581            color: #065f46;
582        }
583        
584        .ozon-desc-query-item.unused {
585            background: #f3f4f6;
586            color: #6b7280;
587        }
588        
589        .ozon-desc-query-text {
590            flex: 1;
591        }
592        
593        .ozon-desc-query-popularity {
594            font-weight: 600;
595            margin-left: 12px;
596            min-width: 50px;
597            text-align: right;
598        }
599        
600        .ozon-desc-minus-words-section {
601            margin-bottom: 16px;
602            padding: 14px;
603            background: linear-gradient(135deg, #fef3c7, #fde68a);
604            border-radius: 8px;
605            border: 1px solid #fbbf24;
606        }
607        
608        .ozon-desc-minus-words-header {
609            font-weight: 600;
610            margin-bottom: 10px;
611            color: #92400e;
612            font-size: 15px;
613        }
614        
615        .ozon-desc-minus-words-list {
616            display: flex;
617            flex-wrap: wrap;
618            gap: 8px;
619        }
620        
621        .ozon-desc-minus-word-chip {
622            background: #fbbf24;
623            color: #78350f;
624            padding: 6px 12px;
625            border-radius: 16px;
626            font-size: 14px;
627            display: flex;
628            align-items: center;
629            gap: 6px;
630            cursor: pointer;
631            transition: all 0.2s;
632        }
633        
634        .ozon-desc-minus-word-chip:hover {
635            background: #f59e0b;
636            transform: translateY(-1px);
637        }
638        
639        .ozon-desc-minus-word-remove {
640            font-weight: bold;
641            font-size: 16px;
642        }
643        
644        .ozon-desc-query-word {
645            cursor: pointer;
646            padding: 2px 4px;
647            border-radius: 3px;
648            transition: background 0.2s;
649        }
650        
651        .ozon-desc-query-word:hover {
652            background: #fef3c7;
653        }
654        
655        .ozon-desc-search-input {
656            width: 100%;
657            padding: 12px 14px;
658            border: 1px solid #d1d5db;
659            border-radius: 8px;
660            font-size: 15px;
661            margin-bottom: 16px;
662            box-sizing: border-box;
663        }
664        
665        .ozon-desc-search-input:focus {
666            outline: none;
667            border-color: #005bff;
668            box-shadow: 0 0 0 3px rgba(0, 91, 255, 0.1);
669        }
670        
671        /* Стили для автогенерации */
672        .ozon-autogen-btn {
673            padding: 12px 24px;
674            background: linear-gradient(135deg, #f59e0b, #d97706);
675            color: white;
676            border: none;
677            border-radius: 8px;
678            font-size: 16px;
679            font-weight: 600;
680            cursor: pointer;
681            transition: all 0.2s;
682            box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
683        }
684        
685        .ozon-autogen-btn:hover {
686            background: linear-gradient(135deg, #d97706, #b45309);
687            transform: translateY(-2px);
688            box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
689        }
690        
691        .ozon-autogen-progress {
692            position: fixed;
693            top: 20px;
694            right: 20px;
695            background: white;
696            border-radius: 12px;
697            padding: 20px;
698            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
699            z-index: 10002;
700            min-width: 350px;
701            max-width: 400px;
702        }
703        
704        .ozon-autogen-progress-header {
705            font-size: 18px;
706            font-weight: 600;
707            margin-bottom: 16px;
708            color: #001a34;
709            display: flex;
710            justify-content: space-between;
711            align-items: center;
712        }
713        
714        .ozon-autogen-progress-close {
715            cursor: pointer;
716            font-size: 24px;
717            color: #6b7280;
718            line-height: 1;
719        }
720        
721        .ozon-autogen-progress-close:hover {
722            color: #374151;
723        }
724        
725        .ozon-autogen-progress-info {
726            margin-bottom: 12px;
727            padding: 12px;
728            background: #f3f4f6;
729            border-radius: 8px;
730            font-size: 14px;
731        }
732        
733        .ozon-autogen-progress-stats {
734            display: flex;
735            gap: 16px;
736            margin-bottom: 16px;
737            flex-wrap: wrap;
738        }
739        
740        .ozon-autogen-progress-stat {
741            flex: 1;
742            min-width: 100px;
743        }
744        
745        .ozon-autogen-progress-stat-label {
746            font-size: 12px;
747            color: #6b7280;
748            margin-bottom: 4px;
749        }
750        
751        .ozon-autogen-progress-stat-value {
752            font-size: 20px;
753            font-weight: 600;
754            color: #001a34;
755        }
756        
757        .ozon-autogen-progress-stat-value.success {
758            color: #10b981;
759        }
760        
761        .ozon-autogen-progress-stat-value.error {
762            color: #ef4444;
763        }
764        
765        .ozon-autogen-progress-controls {
766            display: flex;
767            gap: 8px;
768        }
769        
770        .ozon-autogen-progress-btn {
771            flex: 1;
772            padding: 8px 16px;
773            border: none;
774            border-radius: 6px;
775            font-size: 14px;
776            font-weight: 500;
777            cursor: pointer;
778            transition: all 0.2s;
779        }
780        
781        .ozon-autogen-progress-btn.pause {
782            background: #fbbf24;
783            color: #78350f;
784        }
785        
786        .ozon-autogen-progress-btn.pause:hover {
787            background: #f59e0b;
788        }
789        
790        .ozon-autogen-progress-btn.stop {
791            background: #ef4444;
792            color: white;
793        }
794        
795        .ozon-autogen-progress-btn.stop:hover {
796            background: #dc2626;
797        }
798        
799        .ozon-autogen-progress-btn.resume {
800            background: #10b981;
801            color: white;
802        }
803        
804        .ozon-autogen-progress-btn.resume:hover {
805            background: #059669;
806        }
807        
808        .ozon-product-progress {
809            position: fixed;
810            top: 20px;
811            right: 20px;
812            background: white;
813            border-radius: 12px;
814            padding: 20px;
815            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
816            z-index: 10002;
817            min-width: 300px;
818        }
819        
820        .ozon-product-progress-header {
821            font-size: 16px;
822            font-weight: 600;
823            margin-bottom: 12px;
824            color: #001a34;
825        }
826        
827        .ozon-product-progress-stage {
828            padding: 8px 12px;
829            margin-bottom: 8px;
830            border-radius: 6px;
831            font-size: 14px;
832            display: flex;
833            align-items: center;
834            gap: 8px;
835        }
836        
837        .ozon-product-progress-stage.pending {
838            background: #f3f4f6;
839            color: #6b7280;
840        }
841        
842        .ozon-product-progress-stage.active {
843            background: #dbeafe;
844            color: #1e40af;
845            font-weight: 500;
846        }
847        
848        .ozon-product-progress-stage.completed {
849            background: #d1fae5;
850            color: #065f46;
851        }
852        
853        .ozon-product-progress-stage.error {
854            background: #fee2e2;
855            color: #991b1b;
856        }
857        
858        .ozon-checkbox-container {
859            display: flex;
860            align-items: center;
861            gap: 8px;
862            margin-top: 12px;
863        }
864        
865        .ozon-checkbox {
866            width: 18px;
867            height: 18px;
868            cursor: pointer;
869        }
870        
871        .ozon-checkbox-label {
872            font-size: 15px;
873            color: #374151;
874            cursor: pointer;
875            user-select: none;
876        }
877    `);
878
879    // ============================================
880    // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
881    // ============================================
882
883    let lastUrl = location.href;
884
885    // ============================================
886    // МОНИТОРИНГ URL (SPA)
887    // ============================================
888
889    const debouncedUrlCheck = debounce(() => {
890        const url = location.href;
891        if (url !== lastUrl) {
892            lastUrl = url;
893        
894            if (url.includes('seller.ozon.ru/app/products/') && url.includes('/edit/all-attrs')) {
895                setTimeout(init, 1000);
896            }
897        }
898    }, 300);
899
900    new MutationObserver(debouncedUrlCheck).observe(document, { subtree: true, childList: true });
901
902    // ============================================
903    // ПОЛУЧЕНИЕ ИНФОРМАЦИИ О ТОВАРЕ
904    // ============================================
905
906    async function getProductInfo() {
907        // Получаем SKU из URL
908        const urlMatch = window.location.href.match(/\/products\/(\d+)\//);
909        const sku = urlMatch ? urlMatch[1] : null;
910    
911        // СТРОГАЯ ПРОВЕРКА SKU
912        if (!sku || !/^\d+$/.test(sku)) {
913            console.error('Ozon Description Generator: Некорректный SKU в URL:', sku);
914            return { title: '', sku: null, composition: '' };
915        }
916    
917        // Получаем название товара
918        let title = '';
919        const titleInput = document.querySelector('input[name="name"]');
920        if (titleInput) {
921            title = titleInput.value;
922        }
923    
924        // Получаем состав товара - ИСПРАВЛЕН СЕЛЕКТОР
925        let composition = '';
926        const compositionTextarea = document.querySelector('textarea[name="attribute#8050"]');
927        if (compositionTextarea) {
928            composition = compositionTextarea.value.trim();
929            console.log('Ozon Description Generator: Состав товара найден:', composition);
930        } else {
931            console.log('Ozon Description Generator: Поле состава не найдено на текущей странице');
932        }
933    
934        // Если название не найдено на текущей странице, пробуем получить из сохраненных данных
935        if (!title && sku) {
936            title = await GM.getValue(`ozon_product_${sku}_title`, '');
937            console.log('Ozon Description Generator: Название товара получено из сохраненных данных:', title);
938        }
939    
940        // Если состав не найден на текущей странице, пробуем получить из сохраненных данных
941        if (!composition && sku) {
942            composition = await GM.getValue(`ozon_product_${sku}_composition`, '');
943            console.log('Ozon Description Generator: Состав товара получен из сохраненных данных:', composition);
944        }
945    
946        return { title, sku, composition };
947    }
948
949    // ============================================
950    // СОЗДАНИЕ КНОПКИ ГЕНЕРАТОРА
951    // ============================================
952
953    function createGeneratorButton() {
954        // Ищем контейнер с аннотацией (описанием)
955        const annotationContainer = document.querySelector('[id="attribute#4191"]')?.closest('.index_formField_P3SmU');
956    
957        if (!annotationContainer) {
958            return false;
959        }
960    
961        if (document.querySelector('.ozon-desc-generator-btn')) {
962            return true;
963        }
964    
965        const buttonsContainer = document.createElement('div');
966        buttonsContainer.style.cssText = 'display: flex; gap: 8px; margin-top: 12px;';
967    
968        const generatorButton = document.createElement('button');
969        generatorButton.className = 'ozon-desc-generator-btn';
970        generatorButton.textContent = '✨ Генератор описаний';
971        generatorButton.type = 'button';
972        generatorButton.style.width = 'auto';
973        generatorButton.style.flex = '1';
974        generatorButton.addEventListener('click', function(e) {
975            e.preventDefault();
976            openModal();
977        });
978    
979        const richContentButton = document.createElement('button');
980        richContentButton.className = 'ozon-desc-generator-btn';
981        richContentButton.textContent = '📄 Отправить в Rich-контент';
982        richContentButton.type = 'button';
983        richContentButton.style.width = 'auto';
984        richContentButton.style.flex = '1';
985        richContentButton.style.background = 'linear-gradient(135deg, #10b981, #059669)';
986        richContentButton.addEventListener('click', function(e) {
987            e.preventDefault();
988            sendToRichContent();
989        });
990        richContentButton.addEventListener('mouseenter', function() {
991            this.style.background = 'linear-gradient(135deg, #059669, #047857)';
992        });
993        richContentButton.addEventListener('mouseleave', function() {
994            this.style.background = 'linear-gradient(135deg, #10b981, #059669)';
995        });
996    
997        buttonsContainer.appendChild(generatorButton);
998        buttonsContainer.appendChild(richContentButton);
999    
1000        annotationContainer.appendChild(buttonsContainer);
1001        return true;
1002    }
1003
1004    // ============================================
1005    // ПОКАЗ СТАТУСА
1006    // ============================================
1007
1008    function showStatus(container, message, type) {
1009        container.innerHTML = `<div class="ozon-desc-status ${type}">${message}</div>`;
1010    }
1011
1012    // ============================================
1013    // МОДАЛЬНОЕ ОКНО
1014    // ============================================
1015
1016    async function openModal() {
1017        const productInfo = await getProductInfo();
1018        const currentSKU = productInfo.sku;
1019    
1020        let savedKeywords = '';
1021        let savedMinusWords = '';
1022        let savedPrompt = '';
1023    
1024        if (currentSKU) {
1025            savedKeywords = await GM.getValue(`ozon_product_${currentSKU}_keywords`, '');
1026            savedMinusWords = await GM.getValue(`ozon_product_${currentSKU}_minus_words`, '');
1027            savedPrompt = await GM.getValue(`ozon_product_${currentSKU}_custom_prompt`, '');
1028        }
1029    
1030        // Предустановленные промпты
1031        const presetPrompts = [
1032            { name: 'Без дополнительных требований', value: '' },
1033            { name: 'Акцент на натуральность', value: 'Сделай акцент на натуральности состава, экологичности и безопасности для здоровья.' },
1034            { name: 'Премиум-сегмент', value: 'Используй стиль премиум-сегмента: подчеркни эксклюзивность, высокое качество и статусность продукта.' },
1035            { name: 'Для чувствительной кожи', value: 'Особое внимание уделить гипоаллергенности, мягкости формулы и подходу для чувствительной кожи.' },
1036            { name: 'Антивозрастной уход', value: 'Акцентируй внимание на антивозрастных свойствах, омоложении и борьбе с признаками старения.' },
1037            { name: 'Быстрый результат', value: 'Подчеркни быстроту достижения видимого результата и эффективность средства.' }
1038        ];
1039    
1040        const modal = document.createElement('div');
1041        modal.className = 'ozon-desc-modal';
1042        modal.innerHTML = `
1043            <div class="ozon-desc-modal-content">
1044                <div class="ozon-desc-modal-header">✨ Генератор описаний для Ozon</div>
1045                
1046                <div class="ozon-desc-input-group">
1047                    <label class="ozon-desc-label">Введите ключевые слова (каждое с новой строки):</label>
1048                    <textarea class="ozon-desc-textarea" id="ozon-keywords-input" placeholder="Например:&#10;сыворотка для лица&#10;витамин с&#10;увлажнение">${savedKeywords}</textarea>
1049                    <button class="ozon-desc-suggest-btn" id="ozon-suggest-keywords-btn">🔍 Предложить поисковые маски</button>
1050                    <div id="ozon-suggested-keywords-container" style="display: none;"></div>
1051                </div>
1052                
1053                <div class="ozon-desc-input-group">
1054                    <label class="ozon-desc-label">Минус-слова (каждое с новой строки):</label>
1055                    <textarea class="ozon-desc-textarea" style="min-height: 80px;" id="ozon-minus-words-input" placeholder="Например:&#10;mixit&#10;nivea&#10;корея">${savedMinusWords}</textarea>
1056                </div>
1057                
1058                <div class="ozon-desc-input-group">
1059                    <label class="ozon-desc-label">Дополнительные требования к описанию:</label>
1060                    <select class="ozon-desc-textarea" id="ozon-prompt-preset-select" style="min-height: auto; padding: 10px; margin-bottom: 8px;">
1061                        ${presetPrompts.map(preset => `<option value="${preset.value}">${preset.name}</option>`).join('')}
1062                    </select>
1063                    <textarea class="ozon-desc-textarea" style="min-height: 80px;" id="ozon-custom-prompt-input" placeholder="Или напишите свои требования к стилю и содержанию описания...">${savedPrompt}</textarea>
1064                </div>
1065                
1066                <div id="ozon-desc-result-container" style="display: none;">
1067                    <div class="ozon-desc-label">Сгенерированное описание:</div>
1068                    <div class="ozon-desc-result" id="ozon-desc-result"></div>
1069                    <div class="ozon-desc-char-count" id="ozon-char-count"></div>
1070                    <div id="ozon-desc-stats-container"></div>
1071                </div>
1072                
1073                <div id="ozon-desc-status-container"></div>
1074                
1075                <div class="ozon-desc-buttons">
1076                    <button class="ozon-desc-btn ozon-desc-btn-secondary" id="ozon-close-btn">Закрыть</button>
1077                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-generate-btn">🚀 Сгенерировать</button>
1078                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-regenerate-btn" style="display: none;">🔄 Перегенерировать</button>
1079                    <button class="ozon-desc-btn ozon-desc-btn-success" id="ozon-insert-btn" style="display: none;">✅ Вставить в описание</button>
1080                </div>
1081            </div>
1082        `;
1083    
1084        document.body.appendChild(modal);
1085    
1086        // Обработчик выбора пресета промпта
1087        document.getElementById('ozon-prompt-preset-select').addEventListener('change', (e) => {
1088            const customPromptInput = document.getElementById('ozon-custom-prompt-input');
1089            if (e.target.value) {
1090                customPromptInput.value = e.target.value;
1091            }
1092        });
1093    
1094        modal.addEventListener('click', (e) => {
1095            if (e.target === modal) {
1096                modal.remove();
1097            }
1098        });
1099    
1100        document.getElementById('ozon-close-btn').addEventListener('click', () => {
1101            modal.remove();
1102        });
1103    
1104        document.getElementById('ozon-suggest-keywords-btn').addEventListener('click', () => {
1105            suggestKeywords();
1106        });
1107    
1108        document.getElementById('ozon-generate-btn').addEventListener('click', () => {
1109            generateDescription(modal);
1110        });
1111    
1112        document.getElementById('ozon-regenerate-btn').addEventListener('click', () => {
1113            generateDescription(modal, true);
1114        });
1115    
1116        document.getElementById('ozon-insert-btn').addEventListener('click', () => {
1117            insertDescription(modal);
1118        });
1119    }
1120
1121    // ============================================
1122    // ВСТАВКА ОПИСАНИЯ
1123    // ============================================
1124
1125    function insertDescription(modal) {
1126        const resultDiv = document.getElementById('ozon-desc-result');
1127        const description = resultDiv.textContent;
1128        
1129        if (!description) {
1130            alert('Нет описания для вставки');
1131            return;
1132        }
1133        
1134        const proseMirrorDiv = document.querySelector('[id="attribute#4191"] .ProseMirror');
1135        if (proseMirrorDiv) {
1136            const paragraphs = description.split('\n\n').filter(p => p.trim());
1137            const htmlContent = paragraphs.map(p => `<p>${p.replace(/\n/g, '<br>')}</p>`).join('<p><br></p>');
1138            proseMirrorDiv.innerHTML = htmlContent;
1139            proseMirrorDiv.dispatchEvent(new Event('input', { bubbles: true }));
1140            proseMirrorDiv.dispatchEvent(new Event('change', { bubbles: true }));
1141            console.log('Ozon Description Generator: Описание вставлено');
1142            modal.remove();
1143        } else {
1144            alert('Не удалось найти поле описания');
1145        }
1146    }
1147
1148    // ============================================
1149    // ПРЕДЛОЖЕНИЕ КЛЮЧЕВЫХ СЛОВ / МАСОК
1150    // ============================================
1151
1152    async function suggestKeywords() {
1153        const keywordsInput = document.getElementById('ozon-keywords-input');
1154        const suggestBtn = document.getElementById('ozon-suggest-keywords-btn');
1155        const suggestedContainer = document.getElementById('ozon-suggested-keywords-container');
1156        const statusContainer = document.getElementById('ozon-desc-status-container');
1157    
1158        const keywordsText = keywordsInput.value.trim();
1159    
1160        if (!keywordsText) {
1161            showStatus(statusContainer, 'Пожалуйста, сначала введите базовые ключевые слова', 'error');
1162            return;
1163        }
1164    
1165        suggestBtn.disabled = true;
1166        suggestBtn.textContent = '⏳ AI анализирует...';
1167        showStatus(statusContainer, 'AI анализирует товар и подбирает поисковые маски и ключевые слова...', 'info');
1168    
1169        try {
1170            const productInfo = await getProductInfo();
1171            const userKeywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1172        
1173            const suggestPrompt = generateMasksPrompt(productInfo);
1174            console.log('Ozon Description Generator: Запрос масок от AI с пользовательскими ключами:', userKeywords);
1175        
1176            const suggestResponse = await RM.aiCall(suggestPrompt);
1177        
1178            let masks = [];
1179            let aiKeywords = [];
1180            try {
1181                // Очищаем ответ от markdown форматирования
1182                let cleanedResponse = suggestResponse.trim();
1183                
1184                // Удаляем markdown блоки кода если есть
1185                cleanedResponse = cleanedResponse.replace(/```json\s*/g, '').replace(/```\s*/g, '');
1186                
1187                // Ищем JSON объект в ответе
1188                const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
1189                if (jsonMatch) {
1190                    cleanedResponse = jsonMatch[0];
1191                }
1192                
1193                console.log('Ozon Description Generator: Очищенный ответ:', cleanedResponse);
1194                
1195                const suggestData = JSON.parse(cleanedResponse);
1196                masks = Array.isArray(suggestData.masks) ? suggestData.masks.filter(Boolean) : [];
1197                aiKeywords = Array.isArray(suggestData.keywords) ? suggestData.keywords.filter(Boolean) : [];
1198                console.log(`Ozon Description Generator: AI предложил ${masks.length} масок и ${aiKeywords.length} ключей`);
1199            } catch (e) {
1200                console.error('Ozon Description Generator: Ошибка парсинга масок:', e);
1201                console.error('Ozon Description Generator: Ответ AI:', suggestResponse);
1202                showStatus(statusContainer, 'Ошибка при обработке ответа AI. Попробуйте еще раз.', 'error');
1203                return;
1204            }
1205        
1206            if (masks.length === 0 && aiKeywords.length === 0) {
1207                showStatus(statusContainer, 'AI не смог предложить маски и ключевые слова', 'error');
1208                return;
1209            }
1210        
1211            const hasChips = masks.length + aiKeywords.length > 0;
1212        
1213            suggestedContainer.innerHTML = `
1214                <div class="ozon-desc-masks-container">
1215                    <div class="ozon-desc-masks-header">
1216                        <span>Поисковые маски и ключевые слова (кликните для выбора):</span>
1217                        ${hasChips ? `<button class="ozon-desc-suggest-btn" id="ozon-toggle-all-btn" style="padding: 4px 12px; font-size: 13px;">
1218                            Выбрать все
1219                        </button>` : ''}
1220                    </div>
1221                    ${masks.length ? `
1222                        <div class="ozon-desc-masks-group">
1223                            <div class="ozon-desc-masks-group-title">МАСКИ</div>
1224                            <div class="ozon-desc-masks-grid">
1225                                ${masks.map(mask => `
1226                                    <div class="ozon-mask-chip" data-type="маска" data-mask="${mask}">
1227                                        <span>${mask}</span>
1228                                    </div>
1229                                `).join('')}
1230                            </div>
1231                        </div>
1232                    ` : ''}
1233                    ${aiKeywords.length ? `
1234                        <div class="ozon-desc-masks-group">
1235                            <div class="ozon-desc-masks-group-title">КЛЮЧЕВЫЕ СЛОВА</div>
1236                            <div class="ozon-desc-masks-grid">
1237                                ${aiKeywords.map(keyword => `
1238                                    <div class="ozon-mask-chip" data-type="ключ" data-mask="${keyword}">
1239                                        <span>${keyword}</span>
1240                                    </div>
1241                                `).join('')}
1242                            </div>
1243                        </div>
1244                    ` : ''}
1245                </div>
1246            `;
1247        
1248            suggestedContainer.style.display = 'block';
1249        
1250            suggestedContainer.querySelectorAll('.ozon-mask-chip').forEach(chip => {
1251                chip.addEventListener('click', () => {
1252                    chip.classList.toggle('selected');
1253                    updateToggleButtonText();
1254                });
1255            });
1256        
1257            function updateToggleButtonText() {
1258                const chips = suggestedContainer.querySelectorAll('.ozon-mask-chip');
1259                const allSelected = Array.from(chips).every(c => c.classList.contains('selected'));
1260                const toggleBtn = document.getElementById('ozon-toggle-all-btn');
1261                if (toggleBtn) {
1262                    toggleBtn.textContent = allSelected ? 'Снять все' : 'Выбрать все';
1263                }
1264            }
1265        
1266            const toggleBtn = document.getElementById('ozon-toggle-all-btn');
1267            if (toggleBtn) {
1268                toggleBtn.addEventListener('click', () => {
1269                    const chips = suggestedContainer.querySelectorAll('.ozon-mask-chip');
1270                    const allSelected = Array.from(chips).every(c => c.classList.contains('selected'));
1271                    
1272                    chips.forEach(c => {
1273                        if (allSelected) {
1274                            c.classList.remove('selected');
1275                        } else {
1276                            c.classList.add('selected');
1277                        }
1278                    });
1279                    
1280                    updateToggleButtonText();
1281                });
1282            }
1283        
1284            showStatus(statusContainer, `AI предложил ${masks.length} масок и ${aiKeywords.length} ключевых слов. Выберите нужные и нажмите "Сгенерировать"`, 'success');
1285        
1286        } catch (error) {
1287            console.error('Ozon Description Generator: Ошибка при предложении масок:', error);
1288            showStatus(statusContainer, 'Ошибка при получении предложений: ' + error.message, 'error');
1289        } finally {
1290            suggestBtn.disabled = false;
1291            suggestBtn.textContent = '🔍 Предложить поисковые маски';
1292        }
1293    }
1294
1295    // ============================================
1296    // СБОР ДАННЫХ С АНАЛИТИКИ
1297    // ============================================
1298
1299    async function collectAnalyticsData(keywords, minusWords) {
1300        console.log('Ozon Description Generator: Начало сбора данных с аналитики');
1301        console.log('Ozon Description Generator: Минус-слова для фильтрации:', minusWords);
1302        
1303        // Проверяем, не идет ли уже сбор данных
1304        const currentStatus = await GM.getValue('ozon_collection_status', 'none');
1305        if (currentStatus === 'pending') {
1306            console.log('Ozon Description Generator: Обнаружен статус pending, сбрасываем и начинаем заново');
1307            await GM.setValue('ozon_collection_status', 'none');
1308        }
1309        
1310        await GM.setValue('ozon_keywords_to_process', JSON.stringify(keywords));
1311        await GM.setValue('ozon_minus_words', JSON.stringify(minusWords));
1312        await GM.setValue('ozon_analytics_data', JSON.stringify([]));
1313        await GM.setValue('ozon_collection_status', 'pending');
1314        
1315        const analyticsUrl = 'https://seller.ozon.ru/app/analytics/what-to-sell/all-queries';
1316        console.log('Ozon Description Generator: Открываем страницу аналитики');
1317        await GM.openInTab(analyticsUrl, false);
1318        
1319        console.log('Ozon Description Generator: Открыта страница аналитики, ожидание сбора данных...');
1320        
1321        const maxWaitTime = 300000;
1322        const checkInterval = 2000;
1323        let waitedTime = 0;
1324        
1325        while (waitedTime < maxWaitTime) {
1326            await new Promise(resolve => setTimeout(resolve, checkInterval));
1327            waitedTime += checkInterval;
1328            
1329            const status = await GM.getValue('ozon_collection_status', 'pending');
1330            
1331            if (status === 'completed') {
1332                const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1333                const analyticsData = JSON.parse(analyticsDataStr);
1334                console.log('Ozon Description Generator: Данные успешно собраны');
1335                return analyticsData;
1336            } else if (status === 'error') {
1337                console.error('Ozon Description Generator: Ошибка при сборе данных');
1338                return [];
1339            }
1340        }
1341        
1342        console.error('Ozon Description Generator: Превышено время ожидания сбора данных');
1343        return [];
1344    }
1345
1346    // ============================================
1347    // АВТОМАТИЧЕСКИЙ СБОР НА СТРАНИЦЕ АНАЛИТИКИ
1348    // ============================================
1349
1350    async function autoCollectOnAnalyticsPage() {
1351        if (!window.location.href.includes('seller.ozon.ru/app/analytics/what-to-sell/all-queries')) {
1352            return;
1353        }
1354        
1355        console.log('Ozon Description Generator: Обнаружена страница аналитики');
1356        
1357        const status = await GM.getValue('ozon_collection_status', 'none');
1358        if (status !== 'pending') {
1359            return;
1360        }
1361        
1362        console.log('Ozon Description Generator: Начинаем автоматический сбор данных');
1363        
1364        try {
1365            const keywordsStr = await GM.getValue('ozon_keywords_to_process', '[]');
1366            const minusWordsStr = await GM.getValue('ozon_minus_words', '[]');
1367            const keywords = JSON.parse(keywordsStr);
1368            const minusWords = JSON.parse(minusWordsStr);
1369            
1370            const analyticsData = [];
1371            
1372            await new Promise(resolve => setTimeout(resolve, 3000));
1373            
1374            // Выбираем период 28 дней
1375            try {
1376                const periodButton = document.querySelector('button[data-active="true"]');
1377                if (periodButton && periodButton.textContent.includes('7 дней')) {
1378                    console.log('Ozon Description Generator: Меняем период на 28 дней');
1379                    periodButton.click();
1380                    await new Promise(resolve => setTimeout(resolve, 1000));
1381                    
1382                    // Ищем кнопку 28 дней в выпадающем меню
1383                    const buttons = document.querySelectorAll('button');
1384                    const days28Button = Array.from(buttons).find(btn => 
1385                        btn.textContent && btn.textContent.trim().includes('28 дней')
1386                    );
1387                    
1388                    if (days28Button) {
1389                        days28Button.click();
1390                        await new Promise(resolve => setTimeout(resolve, 2000));
1391                        console.log('Ozon Description Generator: Период "28 дней" выбран');
1392                    }
1393                }
1394            } catch (e) {
1395                console.error('Ozon Description Generator: Ошибка при выборе периода:', e);
1396            }
1397            
1398            for (const keyword of keywords) {
1399                console.log(`Ozon Description Generator: Обработка ключевого слова: ${keyword}`);
1400                
1401                try {
1402                    const searchInput = document.querySelector('input[placeholder="Поисковый запрос"]');
1403                    if (!searchInput) {
1404                        console.error('Ozon Description Generator: Поле поиска не найдено');
1405                        continue;
1406                    }
1407                    
1408                    searchInput.value = '';
1409                    searchInput.focus();
1410                    searchInput.value = keyword;
1411                    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
1412                    searchInput.dispatchEvent(new Event('change', { bubbles: true }));
1413                    
1414                    await new Promise(resolve => setTimeout(resolve, 5000));
1415                    
1416                    const rows = document.querySelectorAll('table tbody tr');
1417                    const keywordData = {
1418                        keyword: keyword,
1419                        queries: []
1420                    };
1421                    
1422                    console.log(`Ozon Description Generator: Найдено строк в таблице: ${rows.length}`);
1423                    
1424                    rows.forEach(row => {
1425                        const cells = row.querySelectorAll('td');
1426                        if (cells.length >= 2) {
1427                            const query = cells[0]?.textContent?.trim();
1428                            const popularityText = cells[1]?.textContent?.trim();
1429                            
1430                            if (query && popularityText) {
1431                                const popularity = parseInt(popularityText.replace(/\s+/g, ''));
1432                                const queryLower = query.toLowerCase();
1433                                
1434                                const hasMinusWord = minusWords.some(minusWord => 
1435                                    queryLower.includes(minusWord.toLowerCase())
1436                                );
1437                                
1438                                if (hasMinusWord) {
1439                                    console.log(`Ozon Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
1440                                    return;
1441                                }
1442                                
1443                                keywordData.queries.push({
1444                                    query,
1445                                    popularity
1446                                });
1447                            }
1448                        }
1449                    });
1450                    
1451                    analyticsData.push(keywordData);
1452                    console.log(`Ozon Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
1453                    
1454                } catch (error) {
1455                    console.error(`Ozon Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
1456                }
1457            }
1458            
1459            await GM.setValue('ozon_analytics_data', JSON.stringify(analyticsData));
1460            await GM.setValue('ozon_collection_status', 'completed');
1461            
1462            console.log('Ozon Description Generator: Сбор данных завершен, закрываем вкладку через 1 секунду');
1463            
1464            setTimeout(() => {
1465                console.log('Ozon Description Generator: Закрываем вкладку аналитики');
1466                window.close();
1467            }, 1000);
1468            
1469        } catch (error) {
1470            console.error('Ozon Description Generator: Ошибка при автоматическом сборе данных:', error);
1471            await GM.setValue('ozon_collection_status', 'error');
1472            
1473            // Закрываем вкладку даже при ошибке
1474            setTimeout(() => {
1475                window.close();
1476            }, 2000);
1477        }
1478    }
1479
1480    // ============================================
1481    // ГЕНЕРАЦИЯ ОПИСАНИЯ
1482    // ============================================
1483
1484    async function generateDescription(modal, skipDataCollection = false) {
1485        console.log('Ozon Description Generator: Генерация описания');
1486        
1487        const keywordsInput = document.getElementById('ozon-keywords-input');
1488        const minusWordsInput = document.getElementById('ozon-minus-words-input');
1489        const customPromptInput = document.getElementById('ozon-custom-prompt-input');
1490        const generateBtn = document.getElementById('ozon-generate-btn');
1491        const regenerateBtn = document.getElementById('ozon-regenerate-btn');
1492        const insertBtn = document.getElementById('ozon-insert-btn');
1493        const resultContainer = document.getElementById('ozon-desc-result-container');
1494        const resultDiv = document.getElementById('ozon-desc-result');
1495        const charCountDiv = document.getElementById('ozon-char-count');
1496        const statusContainer = document.getElementById('ozon-desc-status-container');
1497        const statsContainer = document.getElementById('ozon-desc-stats-container');
1498        
1499        let keywordsText = keywordsInput.value.trim();
1500        
1501        const selectedSuggestions = Array.from(document.querySelectorAll('.ozon-mask-chip.selected'))
1502            .map(chip => chip.dataset.mask);
1503        
1504        if (selectedSuggestions.length > 0) {
1505            const existingKeywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1506            const allKeywords = [...new Set([...existingKeywords, ...selectedSuggestions])];
1507            keywordsText = allKeywords.join('\n');
1508            console.log('Ozon Description Generator: Добавлены маски/ключи:', selectedSuggestions);
1509        }
1510        
1511        const allKeywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1512        const minusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
1513        const customPrompt = customPromptInput.value.trim();
1514        
1515        if (allKeywords.length === 0) {
1516            showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
1517            return;
1518        }
1519        
1520        // Разделяем на маски (1-2 слова) и ключевые запросы (3+ слова)
1521        const masks = allKeywords.filter(k => k.split(/\s+/).length <= 2);
1522        const keywordPhrases = allKeywords.filter(k => k.split(/\s+/).length >= 3);
1523        
1524        console.log('Ozon Description Generator: Маски для аналитики:', masks.length);
1525        console.log('Ozon Description Generator: Ключевые запросы для описания:', keywordPhrases.length);
1526        
1527        const productInfo = await getProductInfo();
1528        const currentSKU = productInfo.sku;
1529        
1530        if (currentSKU) {
1531            await GM.setValue(`ozon_product_${currentSKU}_keywords`, allKeywords.join('\n'));
1532            await GM.setValue(`ozon_product_${currentSKU}_minus_words`, minusWords.join('\n'));
1533            await GM.setValue(`ozon_product_${currentSKU}_custom_prompt`, customPrompt);
1534            console.log('Ozon Description Generator: Сохранены ключевые слова, минус-слова и промпт для товара', currentSKU);
1535        }
1536        
1537        generateBtn.disabled = true;
1538        regenerateBtn.disabled = true;
1539        
1540        try {
1541            let analyticsData = [];
1542            let queryPopularity = {};
1543            
1544            // Собираем данные из аналитики только для масок
1545            if (masks.length > 0) {
1546                if (!skipDataCollection) {
1547                    showStatus(statusContainer, 'Сбор данных из аналитики для коротких масок...', 'info');
1548                    
1549                    analyticsData = await collectAnalyticsData(masks, minusWords);
1550                    
1551                    if (analyticsData.length === 0) {
1552                        showStatus(statusContainer, 'Не удалось собрать данные из аналитики', 'error');
1553                        return;
1554                    }
1555                } else {
1556                    const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1557                    analyticsData = JSON.parse(analyticsDataStr);
1558                    
1559                    if (analyticsData.length === 0) {
1560                        showStatus(statusContainer, 'Нет сохраненных данных. Пожалуйста, сначала соберите данные.', 'error');
1561                        return;
1562                    }
1563                }
1564                
1565                // Собираем все запросы из аналитики
1566                const allQueries = [];
1567                analyticsData.forEach(data => {
1568                    data.queries.forEach(q => {
1569                        allQueries.push(q.query);
1570                        queryPopularity[q.query.toLowerCase()] = q.popularity;
1571                    });
1572                });
1573                
1574                console.log(`Ozon Description Generator: Всего запросов из аналитики: ${allQueries.length}`);
1575                
1576                // Фильтруем запросы с минус-словами
1577                const filteredQueries = allQueries.filter(query => {
1578                    const queryLower = query.toLowerCase();
1579                    const hasMinusWord = minusWords.some(minusWord => 
1580                        queryLower.includes(minusWord.toLowerCase())
1581                    );
1582                    
1583                    if (hasMinusWord) {
1584                        console.log(`Ozon Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
1585                    }
1586                    
1587                    return !hasMinusWord;
1588                });
1589                
1590                console.log('Ozon Description Generator: Запросов после фильтрации:', filteredQueries.length);
1591                
1592                // Объединяем запросы из аналитики с ключевыми фразами
1593                const allQueriesForDescription = [...filteredQueries, ...keywordPhrases];
1594                
1595                console.log('Ozon Description Generator: Всего запросов для описания:', allQueriesForDescription.length);
1596                
1597                if (allQueriesForDescription.length === 0) {
1598                    showStatus(statusContainer, 'Нет запросов для генерации описания', 'error');
1599                    return;
1600                }
1601                
1602                showStatus(statusContainer, 'AI генерирует описание...', 'info');
1603                
1604                const descriptionPrompt = generateDescriptionPrompt(productInfo, allKeywords, allQueriesForDescription, queryPopularity, customPrompt);
1605                const description = await RM.aiCall(descriptionPrompt);
1606                
1607                await GM.setValue('ozon_generated_description', description);
1608                await GM.setValue('ozon_query_popularity', JSON.stringify(queryPopularity));
1609                
1610                resultDiv.textContent = description;
1611                resultContainer.style.display = 'block';
1612                
1613                const charCount = description.length;
1614                charCountDiv.textContent = `Символов: ${charCount}`;
1615                charCountDiv.className = 'ozon-desc-char-count success';
1616                
1617                const analysis = await analyzeUsedKeywords(description, queryPopularity, minusWords);
1618                const usagePercent = Math.round(analysis.usedQueries.length / analysis.totalQueriesAvailable * 100);
1619                
1620                if (!statsContainer) {
1621                    const newStatsContainer = document.createElement('div');
1622                    newStatsContainer.id = 'ozon-desc-stats-container';
1623                    charCountDiv.parentElement.insertBefore(newStatsContainer, charCountDiv.nextSibling);
1624                }
1625                
1626                document.getElementById('ozon-desc-stats-container').innerHTML = `
1627                    <div class="ozon-desc-stats">
1628                        <div class="ozon-desc-stats-row">
1629                            <span><strong>Использовано запросов:</strong></span>
1630                            <span>${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} (${usagePercent}%)</span>
1631                        </div>
1632                        <div class="ozon-desc-stats-row">
1633                            <span><strong>Общая частотность:</strong></span>
1634                            <span>${formatNumber(analysis.totalPopularity)}</span>
1635                        </div>
1636                    </div>
1637                `;
1638                
1639                generateBtn.style.display = 'none';
1640                regenerateBtn.style.display = 'inline-block';
1641                insertBtn.style.display = 'inline-block';
1642                
1643                showStatus(statusContainer, '✅ Описание успешно сгенерировано! <span class="ozon-desc-usage-link" id="ozon-show-analytics-link">Показать аналитику использования запросов</span>', 'success');
1644                
1645                setTimeout(() => {
1646                    const analyticsLink = document.getElementById('ozon-show-analytics-link');
1647                    if (analyticsLink) {
1648                        analyticsLink.addEventListener('click', () => {
1649                            showUsageAnalytics();
1650                        });
1651                    }
1652                }, 100);
1653            } else {
1654                // Если нет масок, используем только ключевые фразы
1655                showStatus(statusContainer, 'AI генерирует описание с ключевыми запросами...', 'info');
1656                
1657                const descriptionPrompt = generateDescriptionPrompt(productInfo, allKeywords, keywordPhrases, {}, customPrompt);
1658                const description = await RM.aiCall(descriptionPrompt);
1659                
1660                await GM.setValue('ozon_generated_description', description);
1661                
1662                resultDiv.textContent = description;
1663                resultContainer.style.display = 'block';
1664                
1665                const charCount = description.length;
1666                charCountDiv.textContent = `Символов: ${charCount}`;
1667                charCountDiv.className = 'ozon-desc-char-count success';
1668                
1669                generateBtn.style.display = 'none';
1670                regenerateBtn.style.display = 'inline-block';
1671                insertBtn.style.display = 'inline-block';
1672                
1673                showStatus(statusContainer, '✅ Описание успешно сгенерировано!', 'success');
1674            }
1675            
1676        } catch (error) {
1677            console.error('Ozon Description Generator: Ошибка при генерации описания:', error);
1678            showStatus(statusContainer, 'Ошибка при генерации: ' + error.message, 'error');
1679        } finally {
1680            generateBtn.disabled = false;
1681            regenerateBtn.disabled = false;
1682        }
1683    }
1684
1685    // ============================================
1686    // АНАЛИЗ ИСПОЛЬЗОВАННЫХ КЛЮЧЕВЫХ СЛОВ
1687    // ============================================
1688
1689    async function analyzeUsedKeywords(description, queryPopularityParam = null, minusWords = []) {
1690        console.log('Ozon Description Generator: Анализ использованных ключевых слов');
1691
1692        const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1693        const analyticsData = JSON.parse(analyticsDataStr);
1694
1695        const allQueries = [];
1696        let queryPopularity = queryPopularityParam || {};
1697        
1698        if (!queryPopularityParam) {
1699            const savedPopularity = await GM.getValue('ozon_query_popularity', '{}');
1700            queryPopularity = JSON.parse(savedPopularity);
1701        }
1702
1703        analyticsData.forEach(data => {
1704            data.queries.forEach(q => {
1705                allQueries.push(q.query);
1706                if (!queryPopularity[q.query.toLowerCase()]) {
1707                    queryPopularity[q.query.toLowerCase()] = q.popularity;
1708                }
1709            });
1710        });
1711        
1712        // Фильтруем запросы с учетом минус-слов
1713        const filteredQueries = allQueries.filter(query => {
1714            const queryLower = query.toLowerCase();
1715            const hasMinusWord = minusWords.some(minusWord => 
1716                queryLower.includes(minusWord.toLowerCase())
1717            );
1718            return !hasMinusWord;
1719        });
1720
1721        const descriptionLower = description.toLowerCase();
1722        const usedQueries = [];
1723        const unusedQueries = [];
1724        let totalPopularity = 0;
1725
1726        filteredQueries.forEach(query => {
1727            if (descriptionLower.includes(query.toLowerCase())) {
1728                usedQueries.push(query);
1729                totalPopularity += queryPopularity[query.toLowerCase()] || 0;
1730            } else {
1731                unusedQueries.push(query);
1732            }
1733        });
1734
1735        console.log(`Ozon Description Generator: Использовано ${usedQueries.length} из ${filteredQueries.length} запросов (после фильтрации минус-слов)`);
1736
1737        return {
1738            usedQueries,
1739            unusedQueries,
1740            totalQueriesAvailable: filteredQueries.length,
1741            totalPopularity,
1742            queryPopularity
1743        };
1744    }
1745
1746    // ============================================
1747    // ПОКАЗ АНАЛИТИКИ ИСПОЛЬЗОВАНИЯ ЗАПРОСОВ
1748    // ============================================
1749
1750    async function showUsageAnalytics() {
1751        console.log('Ozon Description Generator: Показ аналитики использования');
1752    
1753        const description = await GM.getValue('ozon_generated_description', '');
1754        if (!description) {
1755            alert('Описание не найдено');
1756            return;
1757        }
1758    
1759        const analysis = await analyzeUsedKeywords(description);
1760    
1761        // Получаем минус-слова (пустой массив если не заданы)
1762        const minusWords = [];
1763        
1764        let usedQueries = [...analysis.usedQueries];
1765        let unusedQueries = [...analysis.unusedQueries];
1766        let currentMinusWords = [...minusWords];
1767        let searchQuery = '';
1768    
1769        function renderModal() {
1770            const usedContainer = document.getElementById('ozon-used-queries-container');
1771            const unusedContainer = document.getElementById('ozon-unused-queries-container');
1772            const usedScrollTop = usedContainer ? usedContainer.scrollTop : 0;
1773            const unusedScrollTop = unusedContainer ? unusedContainer.scrollTop : 0;
1774            
1775            const filteredUsed = usedQueries.filter(q => 
1776                q.toLowerCase().includes(searchQuery.toLowerCase())
1777            );
1778            const filteredUnused = unusedQueries.filter(q => 
1779                q.toLowerCase().includes(searchQuery.toLowerCase())
1780            );
1781            
1782            const analyticsModal = document.querySelector('.ozon-desc-analytics-modal');
1783            if (!analyticsModal) return;
1784            
1785            analyticsModal.innerHTML = `
1786            <div class="ozon-desc-analytics-content">
1787                <div class="ozon-desc-modal-header">📊 Аналитика использования запросов</div>
1788                
1789                ${currentMinusWords.length > 0 ? `
1790                <div class="ozon-desc-minus-words-section">
1791                    <div class="ozon-desc-minus-words-header">Минус-слова (клик для удаления):</div>
1792                    <div class="ozon-desc-minus-words-list">
1793                        ${currentMinusWords.map(word => `
1794                            <div class="ozon-desc-minus-word-chip" data-word="${word}">
1795                                ${word}
1796                                <span class="ozon-desc-minus-word-remove">×</span>
1797                            </div>
1798                        `).join('')}
1799                    </div>
1800                </div>
1801                ` : ''}
1802                
1803                <input type="text" class="ozon-desc-search-input" id="ozon-analytics-search" placeholder="🔍 Поиск по запросам..." value="${searchQuery}">
1804                
1805                <div style="margin-bottom: 16px; display: flex; gap: 20px; flex-wrap: wrap;">
1806                    <div><strong>Использовано:</strong> ${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} (${Math.round(analysis.usedQueries.length / analysis.totalQueriesAvailable * 100)}%)</div>
1807                    <div><strong>Общая частотность:</strong> ${formatNumber(analysis.totalPopularity)}</div>
1808                    ${searchQuery ? `<div><strong>Найдено:</strong> ${filteredUsed.length + filteredUnused.length}</div>` : ''}
1809                </div>
1810                
1811                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
1812                    <div>
1813                        <div style="margin-bottom: 12px; font-weight: 600; color: #065f46;">✅ Использованные (${filteredUsed.length}):</div>
1814                        <div style="max-height: 350px; overflow-y: auto;" id="ozon-used-queries-container">
1815                            ${filteredUsed.length > 0 ? filteredUsed.map(query => `
1816                                <div class="ozon-desc-query-item used">
1817                                    <span class="ozon-desc-query-text">${highlightWords(query, currentMinusWords, true, 'used')}</span>
1818                                    <div style="display: flex; align-items: center; gap: 8px;">
1819                                        <span class="ozon-desc-query-popularity">${formatNumber(analysis.queryPopularity[query.toLowerCase()] || 0)}</span>
1820                                        <span class="ozon-desc-query-exclude" data-query="${query}" style="cursor: pointer; font-size: 18px; color: #991b1b; font-weight: bold;" title="Исключить запрос">×</span>
1821                                    </div>
1822                                </div>
1823                            `).join('') : '<div style="padding: 12px; color: #6b7280; text-align: center;">Нет результатов</div>'}
1824                        </div>
1825                    </div>
1826                    
1827                    <div>
1828                        <div style="margin-bottom: 12px; font-weight: 600; color: #6b7280;">⬜ Неиспользованные (${filteredUnused.length}):</div>
1829                        <div style="max-height: 350px; overflow-y: auto;" id="ozon-unused-queries-container">
1830                            ${filteredUnused.length > 0 ? filteredUnused.map(query => `
1831                                <div class="ozon-desc-query-item unused">
1832                                    <span class="ozon-desc-query-text">${highlightWords(query, currentMinusWords, true, 'unused')}</span>
1833                                    <div style="display: flex; align-items: center; gap: 8px;">
1834                                        <span class="ozon-desc-query-popularity">${formatNumber(analysis.queryPopularity[query.toLowerCase()] || 0)}</span>
1835                                        <span class="ozon-desc-query-include" data-query="${query}" style="cursor: pointer; font-size: 18px; color: #059669; font-weight: bold;" title="Включить запрос">+</span>
1836                                    </div>
1837                                </div>
1838                            `).join('') : '<div style="padding: 12px; color: #6b7280; text-align: center;">Нет результатов</div>'}
1839                        </div>
1840                    </div>
1841                </div>
1842                
1843                <div class="ozon-desc-buttons">
1844                    <button class="ozon-desc-btn ozon-desc-btn-secondary" id="ozon-close-analytics-btn">Закрыть</button>
1845                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-regenerate-with-exclusions-btn">🔄 Перегенерировать с изменениями</button>
1846                </div>
1847            </div>
1848        `;
1849            
1850            setTimeout(() => {
1851                const newUsedContainer = document.getElementById('ozon-used-queries-container');
1852                const newUnusedContainer = document.getElementById('ozon-unused-queries-container');
1853                if (newUsedContainer) newUsedContainer.scrollTop = usedScrollTop;
1854                if (newUnusedContainer) newUnusedContainer.scrollTop = unusedScrollTop;
1855            }, 0);
1856            
1857            attachEventHandlers();
1858        }
1859        
1860        function highlightWords(text, words, clickable = false, type = 'unused') {
1861            if (!clickable) return text;
1862        
1863            const textWords = text.split(/\s+/);
1864            return textWords.map(word => {
1865                const cleanWord = word.toLowerCase().replace(/[.,!?;:]/g, '');
1866                return `<span class="ozon-desc-query-word" data-word="${cleanWord}" data-type="${type}">${word}</span>`;
1867            }).join(' ');
1868        }
1869        
1870        function removeQueriesWithMinusWord(minusWord) {
1871            const minusWordLower = minusWord.toLowerCase();
1872            
1873            usedQueries = usedQueries.filter(query => 
1874                !query.toLowerCase().includes(minusWordLower)
1875            );
1876            
1877            unusedQueries = unusedQueries.filter(query => 
1878                !query.toLowerCase().includes(minusWordLower)
1879            );
1880        }
1881        
1882        function attachEventHandlers() {
1883            const analyticsModal = document.querySelector('.ozon-desc-analytics-modal');
1884            if (!analyticsModal) return;
1885            
1886            const searchInput = document.getElementById('ozon-analytics-search');
1887            if (searchInput) {
1888                searchInput.addEventListener('input', (e) => {
1889                    searchQuery = e.target.value;
1890                    const cursorPosition = e.target.selectionStart;
1891                    renderModal();
1892                    setTimeout(() => {
1893                        const newSearchInput = document.getElementById('ozon-analytics-search');
1894                        if (newSearchInput) {
1895                            newSearchInput.focus();
1896                            newSearchInput.setSelectionRange(cursorPosition, cursorPosition);
1897                        }
1898                    }, 0);
1899                });
1900            }
1901            
1902            analyticsModal.addEventListener('click', (e) => {
1903                if (e.target === analyticsModal) {
1904                    analyticsModal.remove();
1905                }
1906            });
1907            
1908            const closeBtn = document.getElementById('ozon-close-analytics-btn');
1909            if (closeBtn) {
1910                closeBtn.addEventListener('click', () => {
1911                    analyticsModal.remove();
1912                });
1913            }
1914            
1915            const regenerateBtn = document.getElementById('ozon-regenerate-with-exclusions-btn');
1916            if (regenerateBtn) {
1917                regenerateBtn.addEventListener('click', async () => {
1918                    await GM.setValue('ozon_analytics_minus_words', JSON.stringify(currentMinusWords));
1919                    
1920                    const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1921                    const analyticsData = JSON.parse(analyticsDataStr);
1922                    
1923                    const updatedAnalyticsData = analyticsData.map(data => {
1924                        return {
1925                            keyword: data.keyword,
1926                            queries: data.queries.filter(q => usedQueries.includes(q.query))
1927                        };
1928                    });
1929                    
1930                    await GM.setValue('ozon_analytics_data', JSON.stringify(updatedAnalyticsData));
1931                    
1932                    console.log('Ozon Description Generator: Обновлены данные аналитики для перегенерации');
1933                    console.log('Использованные запросы:', usedQueries.length);
1934                    console.log('Минус-слова:', currentMinusWords);
1935                    
1936                    analyticsModal.remove();
1937                    await regenerateWithExclusions();
1938                });
1939            }
1940            
1941            analyticsModal.querySelectorAll('.ozon-desc-minus-word-chip').forEach(chip => {
1942                chip.addEventListener('click', () => {
1943                    const word = chip.dataset.word;
1944                    currentMinusWords = currentMinusWords.filter(w => w !== word);
1945                    console.log(`Ozon Description Generator: Минус-слово "${word}" удалено`);
1946                    renderModal();
1947                });
1948            });
1949            
1950            analyticsModal.querySelectorAll('.ozon-desc-query-exclude').forEach(excludeBtn => {
1951                excludeBtn.addEventListener('click', () => {
1952                    const query = excludeBtn.dataset.query;
1953                    console.log(`Ozon Description Generator: Перемещаем запрос "${query}" в неиспользованные`);
1954                    
1955                    usedQueries = usedQueries.filter(q => q !== query);
1956                    if (!unusedQueries.includes(query)) {
1957                        unusedQueries.push(query);
1958                    }
1959                    
1960                    renderModal();
1961                });
1962            });
1963            
1964            analyticsModal.querySelectorAll('.ozon-desc-query-include').forEach(includeBtn => {
1965                includeBtn.addEventListener('click', () => {
1966                    const query = includeBtn.dataset.query;
1967                    console.log(`Ozon Description Generator: Перемещаем запрос "${query}" в использованные`);
1968                    
1969                    unusedQueries = unusedQueries.filter(q => q !== query);
1970                    if (!usedQueries.includes(query)) {
1971                        usedQueries.push(query);
1972                    }
1973                    
1974                    const queryLower = query.toLowerCase();
1975                    currentMinusWords = currentMinusWords.filter(w => w !== queryLower);
1976                    
1977                    renderModal();
1978                });
1979            });
1980            
1981            analyticsModal.querySelectorAll('.ozon-desc-query-word').forEach(wordSpan => {
1982                wordSpan.addEventListener('click', () => {
1983                    const word = wordSpan.dataset.word;
1984                    
1985                    console.log(`Ozon Description Generator: Добавляем минус-слово "${word}"`);
1986                    
1987                    if (!currentMinusWords.includes(word)) {
1988                        currentMinusWords.push(word);
1989                    }
1990                    
1991                    removeQueriesWithMinusWord(word);
1992                    
1993                    renderModal();
1994                });
1995            });
1996        }
1997    
1998        const analyticsModal = document.createElement('div');
1999        analyticsModal.className = 'ozon-desc-analytics-modal';
2000        document.body.appendChild(analyticsModal);
2001        
2002        renderModal();
2003    }
2004
2005    // ============================================
2006    // ПЕРЕГЕНЕРАЦИЯ С ИСКЛЮЧЕНИЯМИ
2007    // ============================================
2008
2009    async function regenerateWithExclusions() {
2010        console.log('Ozon Description Generator: Перегенерация с исключениями');
2011    
2012        // Получаем минус-слова из аналитики
2013        const analyticsMinusWordsStr = await GM.getValue('ozon_analytics_minus_words', '[]');
2014        const analyticsMinusWords = JSON.parse(analyticsMinusWordsStr);
2015        
2016        console.log('Ozon Description Generator: Минус-слова из аналитики для перегенерации:', analyticsMinusWords);
2017    
2018        // Создаем модальное окно
2019        const existingModal = document.querySelector('.ozon-desc-modal');
2020        if (existingModal) {
2021            console.log('Ozon Description Generator: Модальное окно уже открыто');
2022            
2023            const minusWordsInput = document.getElementById('ozon-minus-words-input');
2024            if (minusWordsInput) {
2025                const currentMinusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
2026                const allMinusWords = [...new Set([...currentMinusWords, ...analyticsMinusWords])];
2027                minusWordsInput.value = allMinusWords.join('\n');
2028                
2029                console.log('Ozon Description Generator: Обновлены минус-слова в модальном окне:', allMinusWords);
2030            }
2031            
2032            generateDescription(existingModal, true);
2033            return;
2034        }
2035    
2036        await openModal();
2037        await new Promise(resolve => setTimeout(resolve, 100));
2038    
2039        // Обновляем минус-слова в новом модальном окне
2040        const minusWordsInput = document.getElementById('ozon-minus-words-input');
2041        if (minusWordsInput) {
2042            const currentMinusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
2043            const allMinusWords = [...new Set([...currentMinusWords, ...analyticsMinusWords])];
2044            minusWordsInput.value = allMinusWords.join('\n');
2045            
2046            console.log('Ozon Description Generator: Обновлены минус-слова в новом модальном окне:', allMinusWords);
2047        }
2048    
2049        // Обрабатываем новый запрос
2050        const newModal = document.querySelector('.ozon-desc-modal');
2051        if (newModal) {
2052            generateDescription(newModal, true);
2053        }
2054    }
2055
2056    // ============================================
2057    // ОТПРАВКА В RICH-КОНТЕНТ
2058    // ============================================
2059
2060    async function sendToRichContent() {
2061        console.log('Ozon Description Generator: Отправка в rich-контент');
2062
2063        try {
2064            // Получаем описание из ProseMirror редактора
2065            const proseMirrorDiv = document.querySelector('[id="attribute#4191"] .ProseMirror');
2066            
2067            if (!proseMirrorDiv) {
2068                alert('Не удалось найти поле описания. Убедитесь, что вы на странице редактирования товара.');
2069                return;
2070            }
2071            
2072            // Получаем текст с сохранением структуры абзацев
2073            const paragraphs = [];
2074            proseMirrorDiv.querySelectorAll('p').forEach(p => {
2075                const text = p.textContent.trim();
2076                if (text && text !== '') {
2077                    paragraphs.push(text);
2078                }
2079            });
2080            
2081            const description = paragraphs.join('\n\n');
2082            
2083            if (!description) {
2084                alert('Описание пустое. Пожалуйста, сначала сгенерируйте и вставьте описание.');
2085                return;
2086            }
2087            
2088            console.log('Ozon Description Generator: Описание скопировано, переходим на страницу медиа');
2089            console.log('Ozon Description Generator: Количество абзацев:', paragraphs.length);
2090            
2091            // Сохраняем описание для использования на странице медиа
2092            await GM.setValue('ozon_description_for_rich', description);
2093            
2094            // Получаем текущий URL и заменяем all-attrs на media
2095            const currentUrl = window.location.href;
2096            let mediaUrl = currentUrl;
2097            
2098            if (currentUrl.includes('/edit/all-attrs')) {
2099                mediaUrl = currentUrl.replace('/edit/all-attrs', '/edit/media');
2100            } else if (currentUrl.includes('/edit/general-info')) {
2101                mediaUrl = currentUrl.replace('/edit/general-info', '/edit/media');
2102            } else {
2103                // Если не можем определить, пробуем найти кнопку
2104                const allButtons = Array.from(document.querySelectorAll('button'));
2105                const mediaTabButton = allButtons.find(btn => {
2106                    const textContent = btn.textContent.trim();
2107                    return textContent === 'Медиа' || textContent.includes('Медиа');
2108                });
2109                
2110                if (mediaTabButton) {
2111                    console.log('Ozon Description Generator: Нажимаем кнопку "Медиа"');
2112                    mediaTabButton.click();
2113                    
2114                    // Ждем загрузки страницы медиа и вставляем rich-контент
2115                    setTimeout(async () => {
2116                        await insertRichContent();
2117                    }, 2000);
2118                    return;
2119                } else {
2120                    alert('Не удалось найти способ перехода на страницу медиа');
2121                    return;
2122                }
2123            }
2124            
2125            console.log('Ozon Description Generator: Переходим на URL:', mediaUrl);
2126            window.location.href = mediaUrl;
2127            
2128        } catch (error) {
2129            console.error('Ozon Description Generator: Ошибка при отправке в rich-контент:', error);
2130            alert('Ошибка при отправке в rich-контент: ' + error.message);
2131        }
2132    }
2133
2134    async function insertRichContent() {
2135        console.log('Ozon Description Generator: Вставка rich-контента');
2136
2137        try {
2138            const description = await GM.getValue('ozon_description_for_rich', '');
2139
2140            if (!description) {
2141                console.error('Ozon Description Generator: Описание не найдено в хранилище');
2142                return;
2143            }
2144
2145            // Ждем загрузки страницы медиа и появления поля Rich-контента
2146            console.log('Ozon Description Generator: Ожидаем загрузки страницы медиа...');
2147            
2148            // Начальная задержка для загрузки страницы
2149            await new Promise(resolve => setTimeout(resolve, 3000));
2150            
2151            let richContentInput = null;
2152            let attempts = 0;
2153            const maxAttempts = 20;
2154            
2155            while (attempts < maxAttempts) {
2156                richContentInput = document.querySelector('textarea[id^="baseInput___"]');
2157                if (richContentInput) {
2158                    console.log('Ozon Description Generator: Поле Rich-контента найдено');
2159                    break;
2160                }
2161                console.log(`Ozon Description Generator: Попытка ${attempts + 1} - поле еще не загружено`);
2162                await new Promise(resolve => setTimeout(resolve, 500));
2163                attempts++;
2164            }
2165
2166            if (!richContentInput) {
2167                console.error('Ozon Description Generator: Поле Rich-контента не найдено после всех попыток');
2168                alert('Не удалось найти поле Rich-контента. Убедитесь, что вы на странице редактирования товара.');
2169                return;
2170            }
2171
2172            // ОЧИЩАЕМ ПОЛЕ ПЕРЕД ВСТАВКОЙ
2173            console.log('Ozon Description Generator: Очищаем существующий контент в Rich-контенте');
2174            richContentInput.value = '';
2175            richContentInput.dispatchEvent(new Event('input', { bubbles: true }));
2176            richContentInput.dispatchEvent(new Event('change', { bubbles: true }));
2177            
2178            // Небольшая задержка после очистки
2179            await new Promise(resolve => setTimeout(resolve, 500));
2180
2181            // Разбиваем описание на абзацы - каждый абзац отдельно
2182            const paragraphs = description.split('\n\n').filter(p => p.trim());
2183            
2184            console.log('Ozon Description Generator: Количество абзацев:', paragraphs.length);
2185
2186            // Создаем массив контента - каждый абзац отдельным элементом
2187            const contentArray = [];
2188            paragraphs.forEach((paragraph, index) => {
2189                contentArray.push(paragraph.trim());
2190                // Добавляем пустую строку после каждого абзаца, кроме последнего
2191                if (index < paragraphs.length - 1) {
2192                    contentArray.push('');
2193                }
2194            });
2195
2196            // Создаем JSON для rich-контента с абзацами
2197            const richContentJSON = {
2198                'content': [
2199                    {
2200                        'widgetName': 'raTextBlock',
2201                        'title': {
2202                            'content': [],
2203                            'size': 'size5',
2204                            'color': 'color1'
2205                        },
2206                        'theme': 'default',
2207                        'padding': 'type2',
2208                        'gapSize': 'm',
2209                        'text': {
2210                            'size': 'size2',
2211                            'align': 'left',
2212                            'color': 'color1',
2213                            'content': contentArray
2214                        }
2215                    }
2216                ],
2217                'version': 0.3
2218            };
2219
2220            // Вставляем JSON в поле
2221            richContentInput.value = JSON.stringify(richContentJSON, null, 2);
2222            richContentInput.dispatchEvent(new Event('input', { bubbles: true }));
2223            richContentInput.dispatchEvent(new Event('change', { bubbles: true }));
2224
2225            console.log('Ozon Description Generator: Rich-контент успешно вставлен');
2226            console.log('Ozon Description Generator: Структура контента:', JSON.stringify(contentArray, null, 2));
2227
2228            // Очищаем сохраненное описание
2229            await GM.setValue('ozon_description_for_rich', '');
2230
2231            // Проверяем, идет ли автогенерация
2232            const expectedKeys = Object.keys(localStorage).filter(key => key.startsWith('wbAutoExpected_'));
2233            const isAutogen = expectedKeys.length > 0;
2234
2235            // Показываем alert только если это НЕ автогенерация
2236            if (!isAutogen) {
2237                alert('✅ Описание успешно отправлено в Rich-контент!');
2238            }
2239
2240        } catch (error) {
2241            console.error('Ozon Description Generator: Ошибка при вставке rich-контента:', error);
2242            alert('Ошибка при вставке rich-контента: ' + error.message);
2243        }
2244    }
2245
2246    // ============================================
2247    // АВТОГЕНЕРАЦИЯ ПО ВСЕМ ТОВАРАМ
2248    // ============================================
2249
2250    // Вспомогательные функции для работы с localStorage
2251    function generateCheckId() {
2252        return Date.now() + '_' + Math.random().toString(36).substr(2, 9);
2253    }
2254
2255    function setAutoCheck(nmID, title, checkId) {
2256        const data = {
2257            nmID,
2258            title,
2259            checkId,
2260            timestamp: Date.now()
2261        };
2262        localStorage.setItem('wbAutoCheck', JSON.stringify(data));
2263        localStorage.setItem(`wbAutoExpected_${checkId}`, 'true');
2264        console.log('Ozon Description Generator: Сохранен autoCheck:', data);
2265    }
2266
2267    function getAutoCheck() {
2268        const data = localStorage.getItem('wbAutoCheck');
2269        return data ? JSON.parse(data) : null;
2270    }
2271
2272    function clearAutoCheck(checkId) {
2273        localStorage.removeItem('wbAutoCheck');
2274        if (checkId) {
2275            localStorage.removeItem(`wbAutoExpected_${checkId}`);
2276            localStorage.removeItem('wbAutoResult');
2277        }
2278        console.log('Ozon Description Generator: Очищен autoCheck');
2279    }
2280
2281    function setAutoResult(checkId, success, error = null) {
2282        const result = {
2283            checkId,
2284            success,
2285            error,
2286            timestamp: Date.now()
2287        };
2288        localStorage.setItem('wbAutoResult', JSON.stringify(result));
2289        console.log('Ozon Description Generator: Сохранен результат:', result);
2290    }
2291
2292    function getAutoResult() {
2293        const data = localStorage.getItem('wbAutoResult');
2294        return data ? JSON.parse(data) : null;
2295    }
2296
2297    function isTimestampFresh(timestamp, maxAgeMs = 180000) { // 3 минуты
2298        return (Date.now() - timestamp) < maxAgeMs;
2299    }
2300
2301    // Создание кнопки автогенерации на странице списка товаров
2302    function createAutogenButton() {
2303        if (document.querySelector('.ozon-autogen-btn')) {
2304            return;
2305        }
2306
2307        // Ищем контейнер с кнопками действий
2308        const actionsContainer = document.querySelector('.cs5110-a5 .cs5110-b0 div');
2309        
2310        if (!actionsContainer) {
2311            return;
2312        }
2313
2314        const autogenButton = document.createElement('button');
2315        autogenButton.className = 'ozon-autogen-btn';
2316        autogenButton.textContent = '🤖 Автогенерация';
2317        autogenButton.type = 'button';
2318        autogenButton.setAttribute('data-ozon-autogen', 'true');
2319        autogenButton.addEventListener('click', openAutogenModal);
2320
2321        actionsContainer.insertBefore(autogenButton, actionsContainer.firstChild);
2322        console.log('Ozon Description Generator: Кнопка автогенерации добавлена');
2323    }
2324
2325    // Модальное окно настроек автогенерации
2326    async function openAutogenModal() {
2327        console.log('Ozon Description Generator: Открытие модального окна автогенерации');
2328    
2329        // Предустановленные промпты
2330        const presetPrompts = [
2331            { name: 'Без дополнительных требований', value: '' },
2332            { name: 'Акцент на натуральность', value: 'Сделай акцент на натуральности состава, экологичности и безопасности для здоровья.' },
2333            { name: 'Премиум-сегмент', value: 'Используй стиль премиум-сегмента: подчеркни эксклюзивность, высокое качество и статусность продукта.' },
2334            { name: 'Для чувствительной кожи', value: 'Особое внимание уделить гипоаллергенности, мягкости формулы и подходу для чувствительной кожи.' },
2335            { name: 'Антивозрастной уход', value: 'Акцентируй внимание на антивозрастных свойствах, омоложении и борьбе с признаками старения.' },
2336            { name: 'Быстрый результат', value: 'Подчеркни быстроту достижения видимого результата и эффективность средства.' }
2337        ];
2338
2339        const modal = document.createElement('div');
2340        modal.className = 'ozon-desc-modal';
2341        modal.innerHTML = `
2342            <div class="ozon-desc-modal-content">
2343                <div class="ozon-desc-modal-header">🤖 Автогенерация описаний</div>
2344                
2345                <div class="ozon-desc-input-group">
2346                    <label class="ozon-desc-label">Введите ключевые слова (каждое с новой строки):</label>
2347                    <textarea class="ozon-desc-textarea" id="ozon-autogen-keywords-input" placeholder="Если не заполнено, AI сам подберет ключевые слова для каждого товара"></textarea>
2348                </div>
2349                
2350                <div class="ozon-desc-input-group">
2351                    <label class="ozon-desc-label">Дополнительные требования к описанию:</label>
2352                    <select class="ozon-desc-textarea" id="ozon-autogen-prompt-preset-select" style="min-height: auto; padding: 10px; margin-bottom: 8px;">
2353                        ${presetPrompts.map(preset => `<option value="${preset.value}">${preset.name}</option>`).join('')}
2354                    </select>
2355                    <textarea class="ozon-desc-textarea" style="min-height: 80px;" id="ozon-autogen-custom-prompt-input" placeholder="Или напишите свои требования к стилю и содержанию описания..."></textarea>
2356                </div>
2357                
2358                <div class="ozon-checkbox-container">
2359                    <input type="checkbox" class="ozon-checkbox" id="ozon-autogen-rich-checkbox" checked>
2360                    <label class="ozon-checkbox-label" for="ozon-autogen-rich-checkbox">Отправить в Rich-контент</label>
2361                </div>
2362                
2363                <div class="ozon-checkbox-container">
2364                    <input type="checkbox" class="ozon-checkbox" id="ozon-autogen-test-mode-checkbox">
2365                    <label class="ozon-checkbox-label" for="ozon-autogen-test-mode-checkbox">🧪 Тестовый режим (без AI, вставка "тест")</label>
2366                </div>
2367                
2368                <div id="ozon-autogen-status-container"></div>
2369                
2370                <div class="ozon-desc-buttons">
2371                    <button class="ozon-desc-btn ozon-desc-btn-secondary" id="ozon-autogen-close-btn">Закрыть</button>
2372                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-autogen-start-btn">🚀 Начать автогенерацию</button>
2373                </div>
2374            </div>
2375        `;
2376
2377        document.body.appendChild(modal);
2378    
2379        // Обработчик выбора пресета промпта
2380        document.getElementById('ozon-autogen-prompt-preset-select').addEventListener('change', (e) => {
2381            const customPromptInput = document.getElementById('ozon-autogen-custom-prompt-input');
2382            if (e.target.value) {
2383                customPromptInput.value = e.target.value;
2384            }
2385        });
2386
2387        modal.addEventListener('click', (e) => {
2388            if (e.target === modal) {
2389                modal.remove();
2390            }
2391        });
2392
2393        document.getElementById('ozon-autogen-close-btn').addEventListener('click', () => {
2394            modal.remove();
2395        });
2396
2397        document.getElementById('ozon-autogen-start-btn').addEventListener('click', () => {
2398            startAutogeneration(modal);
2399        });
2400    }
2401
2402    // Начало автогенерации
2403    async function startAutogeneration(modal) {
2404        console.log('Ozon Description Generator: Автогенерация описаний запускается');
2405        
2406        const keywordsInput = document.getElementById('ozon-autogen-keywords-input');
2407        const customPromptInput = document.getElementById('ozon-autogen-custom-prompt-input');
2408        const richCheckbox = document.getElementById('ozon-autogen-rich-checkbox');
2409        const testModeCheckbox = document.getElementById('ozon-autogen-test-mode-checkbox');
2410        
2411        const globalKeywords = keywordsInput.value.trim();
2412        const customPrompt = customPromptInput.value.trim();
2413        const sendToRich = richCheckbox.checked;
2414        const testMode = testModeCheckbox.checked;
2415
2416        console.log('Ozon Description Generator: Начало автогенерации');
2417        console.log('Глобальные ключевые слова:', globalKeywords);
2418        console.log('Дополнительные требования:', customPrompt);
2419        console.log('Отправить в Rich-контент:', sendToRich);
2420        console.log('Тестовый режим:', testMode);
2421
2422        // Сохраняем настройки в localStorage
2423        localStorage.setItem('ozon_autogen_global_keywords', globalKeywords);
2424        localStorage.setItem('ozon_autogen_send_to_rich', sendToRich);
2425        localStorage.setItem('ozon_autogen_test_mode', testMode);
2426        localStorage.setItem('ozon_autogen_processed', '0');
2427        localStorage.setItem('ozon_autogen_errors', '0');
2428        
2429        // Очищаем список обработанных товаров при новом запуске
2430        localStorage.setItem('ozon_autogen_processed_skus', '[]');
2431        console.log('Ozon Description Generator: Список обработанных товаров очищен');
2432
2433        modal.remove();
2434
2435        // Показываем окно прогресса
2436        showAutogenProgress();
2437
2438        // TODO: Запустить обработку первого товара
2439        console.log('Ozon Description Generator: Автогенерация запущена, ожидание обработки товаров');
2440    }
2441
2442    // ============================================
2443    // СКРЫТИЕ ОКНА ПРОГРЕССА ОБРАБОТКИ ТОВАРА
2444    // ============================================
2445
2446    // Скрытие окна прогресса обработки товара
2447    function hideProductProgress() {
2448        const progressDiv = document.querySelector('.ozon-product-progress');
2449        if (progressDiv) {
2450            progressDiv.remove();
2451        }
2452    }
2453
2454    // ============================================
2455    // СОЗДАНИЕ ОКНА ПРОГРЕССА ОБРАБОТКИ ТОВАРА
2456    // ============================================
2457
2458    // Показ окна прогресса обработки товара
2459    function showProductProgress() {
2460        if (document.querySelector('.ozon-product-progress')) {
2461            return;
2462        }
2463
2464        const progressDiv = document.createElement('div');
2465        progressDiv.className = 'ozon-product-progress';
2466        progressDiv.innerHTML = `
2467            <div class="ozon-product-progress-header">Обработка товара</div>
2468            <div class="ozon-product-progress-stage pending" data-stage="info">Получение информации о товаре</div>
2469            <div class="ozon-product-progress-stage pending" data-stage="keywords">Подбор ключевых слов</div>
2470            <div class="ozon-product-progress-stage pending" data-stage="analytics">Сбор данных из аналитики</div>
2471            <div class="ozon-product-progress-stage pending" data-stage="generation">Генерация описания</div>
2472            <div class="ozon-product-progress-stage pending" data-stage="insert">Вставка описания</div>
2473            <div class="ozon-product-progress-stage pending" data-stage="rich">Отправка в Rich-контент</div>
2474            <div class="ozon-product-progress-stage pending" data-stage="save">Сохранение товара</div>
2475        `;
2476
2477        document.body.appendChild(progressDiv);
2478    }
2479
2480    // ============================================
2481    // ПРОКРУТИВКА СТРАНИЦЫ
2482    // ============================================
2483
2484    // Обновление прогресса обработки товара
2485    function updateProductProgress(stageName, status) {
2486        const stageMap = {
2487            'Получение информации о товаре': 'info',
2488            'Подбор ключевых слов': 'keywords',
2489            'Сбор данных из аналитики': 'analytics',
2490            'Генерация описания': 'generation',
2491            'Вставка описания': 'insert',
2492            'Отправка в Rich-контент': 'rich',
2493            'Сохранение товара': 'save',
2494            'Ошибка': 'error'
2495        };
2496
2497        const stageId = stageMap[stageName];
2498        if (!stageId) return;
2499
2500        const stageEl = document.querySelector(`.ozon-product-progress-stage[data-stage="${stageId}"]`);
2501        if (stageEl) {
2502            stageEl.className = `ozon-product-progress-stage ${status}`;
2503        }
2504    }
2505
2506    // Обработка товара на странице редактирования при автогенерации
2507    async function handleProductPageAutogen() {
2508        console.log('Ozon Description Generator: Проверка автогенерации на странице товара');
2509        
2510        // Ищем все ключи wbAutoExpected_*
2511        const expectedKeys = Object.keys(localStorage).filter(key => key.startsWith('wbAutoExpected_'));
2512        
2513        if (expectedKeys.length === 0) {
2514            console.log('Ozon Description Generator: Нет ожидаемых задач автогенерации');
2515            return;
2516        }
2517
2518        const checkId = expectedKeys[0].replace('wbAutoExpected_', '');
2519        console.log('Ozon Description Generator: Найден checkId:', checkId);
2520
2521        // Проверяем, что задача свежая
2522        const autoCheck = getAutoCheck();
2523        if (!autoCheck || autoCheck.checkId !== checkId) {
2524            console.log('Ozon Description Generator: autoCheck не найден или не совпадает');
2525            clearAutoCheck(checkId);
2526            return;
2527        }
2528
2529        console.log('Ozon Description Generator: Начинаем обработку товара:', autoCheck.title);
2530
2531        // Проверяем флаг продолжения после перезагрузки
2532        const continueAfterReload = localStorage.getItem('ozon_autogen_continue_after_reload');
2533        if (continueAfterReload === 'true') {
2534            console.log('Ozon Description Generator: Обнаружен флаг продолжения после перезагрузки');
2535            localStorage.removeItem('ozon_autogen_continue_after_reload');
2536            
2537            // Ждем загрузки страницы
2538            await new Promise(resolve => setTimeout(resolve, 2000));
2539            
2540            // TODO: Реализовать полную обработку с AI
2541            console.log('Ozon Description Generator: Обычный режим - требуется реализация');
2542            
2543            // Временно отправляем ошибку
2544            setAutoResult(checkId, false, 'Обычный режим еще не реализован');
2545            hideProductProgress();
2546        }
2547    }
2548
2549    // ============================================
2550    // ИНИЦИАЛИЗАЦИЯ
2551    // ============================================
2552
2553    function init() {
2554        console.log('Ozon Description Generator: Инициализация');
2555        
2556        // Страница списка товаров
2557        if (window.location.href === 'https://seller.ozon.ru/app/products' || 
2558            (window.location.href.includes('seller.ozon.ru/app/products') && !window.location.href.includes('/edit/'))) {
2559            console.log('Ozon Description Generator: Страница списка товаров');
2560            
2561            // Постоянно следим за кнопкой автогенерации
2562            const debouncedCreateButton = debounce(createAutogenButton, 500);
2563            
2564            const observer = new MutationObserver(() => {
2565                debouncedCreateButton();
2566            });
2567            
2568            observer.observe(document.body, {
2569                childList: true,
2570                subtree: true
2571            });
2572            
2573            setTimeout(createAutogenButton, 2000);
2574            
2575            // Проверяем, нужно ли продолжить автогенерацию - СРАЗУ и через интервалы
2576            checkAndContinueAutogen();
2577            
2578            // Проверяем каждые 2 секунды, нужно ли продолжить
2579            const autogenCheckInterval = setInterval(async () => {
2580                const autogenStatus = await GM.getValue('ozon_autogen_status', 'stopped');
2581                if (autogenStatus === 'running') {
2582                    console.log('Ozon Description Generator: Обнаружен статус running, сбрасываем и начинаем заново');
2583                    await GM.setValue('ozon_autogen_status', 'stopped');
2584                }
2585            }, 2000);
2586            
2587            // Останавливаем проверку через 30 секунд
2588            setTimeout(() => clearInterval(autogenCheckInterval), 30000);
2589        }
2590        
2591        // Страница редактирования товара
2592        if (window.location.href.includes('seller.ozon.ru/app/products/') && 
2593            (window.location.href.includes('/edit/all-attrs') || window.location.href.includes('/edit/general-info'))) {
2594            console.log('Ozon Description Generator: Страница редактирования товара');
2595            
2596            // Сохраняем название товара если оно доступно
2597            const urlMatch = window.location.href.match(/\/products\/(\d+)\//);
2598            const sku = urlMatch ? urlMatch[1] : null;
2599            
2600            if (sku) {
2601                // Пытаемся найти название товара на странице
2602                const checkAndSaveTitle = async () => {
2603                    const titleInput = document.querySelector('input[name="name"]');
2604                    if (titleInput && titleInput.value.trim()) {
2605                        const title = titleInput.value.trim();
2606                        await GM.setValue(`ozon_product_${sku}_title`, title);
2607                        console.log('Ozon Description Generator: Название товара сохранено:', title);
2608                    }
2609                    
2610                    // Также сохраняем состав товара если доступен - ИСПРАВЛЕН СЕЛЕКТОР
2611                    const compositionTextarea = document.querySelector('textarea[name="attribute#8050"]');
2612                    if (compositionTextarea && compositionTextarea.value.trim()) {
2613                        const composition = compositionTextarea.value.trim();
2614                        await GM.setValue(`ozon_product_${sku}_composition`, composition);
2615                        console.log('Ozon Description Generator: Состав товара сохранен:', composition);
2616                    }
2617                };
2618                
2619                // Проверяем сразу и через 2 секунды
2620                setTimeout(checkAndSaveTitle, 100);
2621                setTimeout(checkAndSaveTitle, 2000);
2622            }
2623            
2624            // Проверяем, идет ли автогенерация
2625            checkAndStartAutogen();
2626            
2627            const observer = new MutationObserver((mutations, obs) => {
2628                const annotationContainer = document.querySelector('[id="attribute#4191"]');
2629                if (annotationContainer) {
2630                    createGeneratorButton();
2631                    obs.disconnect();
2632                }
2633            });
2634            
2635            observer.observe(document.body, {
2636                childList: true,
2637                subtree: true
2638            });
2639            
2640            setTimeout(createGeneratorButton, 2000);
2641        }
2642        
2643        if (window.location.href.includes('seller.ozon.ru/app/products/') && window.location.href.includes('/edit/media')) {
2644            console.log('Ozon Description Generator: Страница медиа');
2645            setTimeout(insertRichContent, 2000);
2646        }
2647        
2648        if (window.location.href.includes('seller.ozon.ru/app/analytics/what-to-sell/all-queries')) {
2649            setTimeout(autoCollectOnAnalyticsPage, 2000);
2650        }
2651    }
2652    
2653    // Проверка и продолжение автогенерации на странице списка
2654    async function checkAndContinueAutogen() {
2655        console.log('Ozon Description Generator: Проверяем необходимость продолжения автогенерации');
2656        
2657        // Проверяем, есть ли активная задача
2658        const autoCheck = getAutoCheck();
2659        
2660        if (autoCheck && isTimestampFresh(autoCheck.timestamp)) {
2661            console.log('Ozon Description Generator: Найдена активная задача автогенерации');
2662            showAutogenProgress();
2663            
2664            // TODO: Реализовать ожидание результата
2665            console.log('Ozon Description Generator: Ожидание результата для checkId:', autoCheck.checkId);
2666        }
2667    }
2668    
2669    // Проверка и запуск автогенерации
2670    async function checkAndStartAutogen() {
2671        // Проверяем флаг продолжения после перезагрузки
2672        const continueAfterReload = localStorage.getItem('ozon_autogen_continue_after_reload');
2673        if (continueAfterReload === 'true') {
2674            console.log('Ozon Description Generator: Обнаружен флаг продолжения после перезагрузки');
2675            localStorage.removeItem('ozon_autogen_continue_after_reload');
2676            
2677            // Ждем загрузки страницы
2678            await new Promise(resolve => setTimeout(resolve, 2000));
2679            
2680            // Запускаем обработку товара
2681            await handleProductPageAutogen();
2682            return;
2683        }
2684        
2685        // Проверяем, есть ли ожидаемая задача для этой вкладки
2686        const expectedKeys = Object.keys(localStorage).filter(key => key.startsWith('wbAutoExpected_'));
2687        
2688        if (expectedKeys.length > 0) {
2689            console.log('Ozon Description Generator: Обнаружена задача автогенерации, запускаем обработку');
2690            await handleProductPageAutogen();
2691        }
2692    }
2693
2694    // Показ окна прогресса автогенерации
2695    async function showAutogenProgress() {
2696        if (document.querySelector('.ozon-autogen-progress')) {
2697            return;
2698        }
2699
2700        const progressDiv = document.createElement('div');
2701        progressDiv.className = 'ozon-autogen-progress';
2702        progressDiv.innerHTML = `
2703            <div class="ozon-autogen-progress-header">
2704                <span>🤖 Автогенерация</span>
2705                <span class="ozon-autogen-progress-close" id="ozon-autogen-progress-close">×</span>
2706            </div>
2707            <div class="ozon-autogen-progress-info" id="ozon-autogen-progress-info">
2708                Ожидание...
2709            </div>
2710            <div class="ozon-autogen-progress-stats">
2711                <div class="ozon-autogen-progress-stat">
2712                    <div class="ozon-autogen-progress-stat-label">Обработано</div>
2713                    <div class="ozon-autogen-progress-stat-value success" id="ozon-autogen-processed">0</div>
2714                </div>
2715                <div class="ozon-autogen-progress-stat">
2716                    <div class="ozon-autogen-progress-stat-label">Ошибок</div>
2717                    <div class="ozon-autogen-progress-stat-value error" id="ozon-autogen-errors">0</div>
2718                </div>
2719            </div>
2720            <div class="ozon-autogen-progress-controls">
2721                <button class="ozon-autogen-progress-btn pause" id="ozon-autogen-pause-btn">⏸ Пауза</button>
2722                <button class="ozon-autogen-progress-btn stop" id="ozon-autogen-stop-btn">⏹ Остановить</button>
2723            </div>
2724        `;
2725
2726        document.body.appendChild(progressDiv);
2727
2728        document.getElementById('ozon-autogen-progress-close').addEventListener('click', () => {
2729            progressDiv.remove();
2730        });
2731
2732        document.getElementById('ozon-autogen-pause-btn').addEventListener('click', toggleAutogenPause);
2733        document.getElementById('ozon-autogen-stop-btn').addEventListener('click', stopAutogeneration);
2734
2735        // Обновляем прогресс каждую секунду
2736        setInterval(updateAutogenProgress, 1000);
2737    }
2738
2739    // Обновление прогресса автогенерации
2740    async function updateAutogenProgress() {
2741        const progressDiv = document.querySelector('.ozon-autogen-progress');
2742        if (!progressDiv) return;
2743
2744        const processed = parseInt(localStorage.getItem('ozon_autogen_processed') || '0');
2745        const errors = parseInt(localStorage.getItem('ozon_autogen_errors') || '0');
2746        const currentProduct = localStorage.getItem('ozon_autogen_current_product') || '';
2747
2748        const processedEl = document.getElementById('ozon-autogen-processed');
2749        const errorsEl = document.getElementById('ozon-autogen-errors');
2750        const infoEl = document.getElementById('ozon-autogen-progress-info');
2751
2752        if (processedEl) processedEl.textContent = processed;
2753        if (errorsEl) errorsEl.textContent = errors;
2754        if (infoEl) {
2755            if (currentProduct) {
2756                infoEl.textContent = `Обработка: ${currentProduct}`;
2757            } else {
2758                infoEl.textContent = 'Ожидание...';
2759            }
2760        }
2761    }
2762
2763    // Переключение паузы автогенерации
2764    async function toggleAutogenPause() {
2765        // Пауза больше не нужна в новой версии
2766        console.log('Ozon Description Generator: Пауза не поддерживается в новой версии');
2767    }
2768
2769    // Остановка автогенерации
2770    async function stopAutogeneration() {
2771        console.log('Ozon Description Generator: Автогенерация остановлена');
2772        
2773        // Очищаем все данные автогенерации
2774        const autoCheck = getAutoCheck();
2775        if (autoCheck) {
2776            clearAutoCheck(autoCheck.checkId);
2777        }
2778        
2779        localStorage.removeItem('ozon_autogen_global_keywords');
2780        localStorage.removeItem('ozon_autogen_send_to_rich');
2781        localStorage.removeItem('ozon_autogen_test_mode');
2782        localStorage.removeItem('ozon_autogen_current_product');
2783        
2784        // НЕ закрываем окно прогресса автоматически - только вручную через крестик
2785        console.log('Ozon Description Generator: Окно прогресса остается открытым для ручного закрытия');
2786    }
2787
2788    if (document.readyState === 'loading') {
2789        document.addEventListener('DOMContentLoaded', init);
2790    } else {
2791        init();
2792    }
2793
2794})();
Ozon Description Generator 3.0 | Robomonkey