Ozon Description Generator 3.0

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

Size

134.7 KB

Version

3.2.65

Created

Jan 22, 2026

Updated

3 months ago

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