Ozon Description Generator 3.0

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

Size

139.1 KB

Version

3.2.67

Created

Jan 22, 2026

Updated

26 days ago

1// ==UserScript==
2// @name		Ozon Description Generator 3.0
3// @description		Генератор SEO-описаний для товаров на Ozon с анализом ключевых слов
4// @version		3.2.67
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
42ВАЖНОЕ ПРАВИЛО:
431. АНАЛИЗИРУЙ название товара и используй ТОЧНО ТОТ ТИП ТОВАРА, который указан в названии
442. НЕ заменяй тип товара на другой, даже если похоже
453. Если в названии "маска для лица" — ВСЕ ключи должны быть про МАСКУ, не про сыворотку, крем или другие товары
464. Если в названии "сыворотка" — все ключи про сыворотку
475. Если в названии "крем" — все ключи про крем
48
49Предложи 2 типа запросов:
50
511. МАСКИ (15 штук) — короткие слова:
52   - Используй ТОЧНО тип товара из названия
53   - Пример для "маска для лица": ["маска", "маска для", "для лица", ...]
54   - НЕЛЬЗЯ: ["сыворотка", "крем", "гель"] если это не указано в названии
55
562. КЛЮЧЕВЫЕ СЛОВА (15 штук) — точные запросы:
57   - Начинай с типа товара из названия
58   - Пример для "маска для лица": ["маска для лица", "тканевая маска для лица", ...]
59   - НЕЛЬЗЯ: ["сыворотка для лица", "крем для лица", ...] если это не маска
60
61КАТЕГОРИИ ДЛЯ МАСОК:
62- ТИП ТОВАРА ИЗ НАЗВАНИЯ (3 маски): основное название
63- ТИП ТОВАРА + предлог (4 маски)
64- Синонимы ТОГО ЖЕ ТИПА (2 маски)
65- Назначение (2 маски)
66- Ингредиенты из названия (3 маски)
67- Эффект (1 маска)
68
69КАТЕГОРИИ ДЛЯ КЛЮЧЕВЫХ СЛОВ:
70- ТИП ТОВАРА + назначение (6 ключей)
71- ТИП ТОВАРА + аудитория (2 ключа)
72- ТИП ТОВАРА + ингредиент (5 ключей)
73- ТИП ТОВАРА + эффект (2 ключей)
74
75ПРАВИЛА:
76- Используй ТОЧНО тип товара из названия, не меняй его
77- Все слова на русском (кроме названий компонентов типа "hyaluronic", "niacinamide")
78- Без брендов
79
80ПРИМЕР ДЛЯ "Маска для лица с гиалуроновой кислотой":
81{
82 "masks": ["маска", "маска для", "для лица", "тканевая", "патчи", "гиалуроновая", "алоэ", "увлажнение", "освежение", "уход", "косметика", "средство", "средство для", "уходовая", "гидратация"],
83 "keywords": ["маска для лица", "тканевая маска для лица", "маска для лица увлажняющая", "маска с гиалуроновой кислотой", "гиалуроновая маска для лица", "маска для лица с алоэ", "увлажняющая маска для лица", "маска для лица тканевая", "освежающая маска для лица", "маска для лица гиалуроновая", "маска для лица для увлажнения", "маска для лица ежедневного ухода", "маска для лица против сухости", "маска для лица уходовая", "маска для лица с алоэ вера"]
84}
85
86ПРИМЕР ДЛЯ "Сыворотка для лица с витамином С":
87{
88 "masks": ["сыворотка", "сыворотка для", "серум", "витамин с", "ниацинамид", "антиоксидант", "осветление", "для лица", "уход", "косметика", "средство", "средство для", "антивозрастная", "витаминная", "активная"],
89 "keywords": ["сыворотка для лица", "сыворотка с витамином с", "витамин с для лица", "осветляющая сыворотка", "сыворотка для лица с ниацинамидом", "антиоксидантная сыворотка", "сыворотка против пигментации", "антивозрастная сыворотка", "сыворотка для сияния кожи", "сыворотка для лица витаминная", "сыворотка для лица активная", "сыворотка для ровного тона", "сыворотка с антиоксидантами", "сыворотка для осветления", "сыворотка для ухода за лицом"]
90}
91
92Верни ТОЛЬКО JSON:
93{
94 "masks": ["маска1", "маска2", ...],
95 "keywords": ["ключ1", "ключ2", ...]
96}
97
98Начни ответ сразу с {`;
99    }
100
101    function generateDescriptionPrompt(productInfo, keywords, filteredQueries, queryPopularity = {}, customPrompt = '') {
102        const sortedQueries = filteredQueries
103            .map(q => ({ query: q, pop: queryPopularity[q.toLowerCase()] || 0 }))
104            .sort((a, b) => b.pop - a.pop);
105        
106        const highPriority = sortedQueries.slice(0, 25).map(q => q.query);
107        const mediumPriority = sortedQueries.slice(25, 70).map(q => q.query);
108        const lowPriority = sortedQueries.slice(70, 120).map(q => q.query);
109
110        const basePrompt = `Создай SEO-описание товара для Ozon.
111
112ДАННЫЕ:
113• Название: ${productInfo.title || 'не указано'}
114• Базовые ключи: ${keywords.join(', ')}
115
116ЗАПРОСЫ ПО ПРИОРИТЕТУ:
117
118🔴 ВЫСОКИЙ ПРИОРИТЕТ (использовать ВСЕ 100%, минимум 1 раз каждый):
119${highPriority.map((q, i) => `${i+1}. "${q}"`).join('\n')}
120
121🟡 СРЕДНИЙ ПРИОРИТЕТ (использовать 80-90%):
122${mediumPriority.length > 0 ? mediumPriority.map((q, i) => `${i+1}. "${q}"`).join('\n') : 'нет запросов'}
123
124🟢 НИЗКИЙ ПРИОРИТЕТ (использовать 50-70%):
125${lowPriority.length > 0 ? lowPriority.map((q, i) => `${i+1}. "${q}"`).join('\n') : 'нет запросов'}
126
127ТРЕБОВАНИЯ:
1281) Объем 3500–4000 символов, только текст.
1292) Структура (каждая часть с нового абзаца. между абзацами пустая строка): 
130   - Введение (400-500 симв.) — суть товара, что это, зачем, для кого, что делает кратко.
131   - Проблема и решение (600-800 симв.) — основная проблема, которую решает это средство, к чему приводит проблема. Кратко как этот продукт решает проблему.
132   - Состав и действие (800-1000 симв.) — ингредиенты и их польза. Подробная информация о составе и компонентах которые усиливают друг друга. как они работают, зачем они нужны.
133   - Применение (600-800 симв.) — для кого, как работает
134   - Отзывы покупателей (300-400 симв.) — отдельный абзац, который начинается фразой "Наши покупатели отмечают ... через ...", где описан эффект и сроки появления результата
135   - Заключение (400-500 симв.) — результат для покупателя.  что получит покупатель при использовании средства.
1363) Плавные переходы между частями, без заголовков.
1374) Все базовые ключи — минимум 1 раз.
1385) Каждый запрос использовать не более 2 раз.
1396) Если компонента из запроса нет в составе — используй через «альтернативу» (засчитывается).
1407) Размер текста минимум 3000 символов - ПРОВЕРЬ!
141
142ГРУППИРОВКА ЗАПРОСОВ:
1438) Сгруппируй запросы по смыслу:
144   • Тип/категория товара
145   • Проблемы/назначение
146   • Состав/ингредиенты
147   • Аудитория
148   • Эффект/результат
1499) В каждом абзаце используй запросы из 2-3 групп одновременно.
15010) Комбинируй разные типы запросов в одном предложении естественным образом.
151
152ПРАВИЛА ПЛОТНОСТИ КЛЮЧЕЙ:
15311) Плотность ключей: 4-5 запросов на 100 слов (это нормально для SEO).
15412) В каждом абзаце минимум 3-4 ключевых запроса.
15513) Встраивай ключи естественно в середину и начало предложений.
15614) Используй синонимы и вариации ключей для естественности.
15715) Одно и то же ключевое словосочетание — максимум 2 раза в тексте.
15816) Чередуй короткие (2-3 слова) и длинные (4-5 слов) ключи.
159
160СТИЛЬ:
16117) Информативно, конкретные факты, без воды.
16218) Между абзацами пустая строка.
16319) Чередуй длину предложений: 30% короткие (8-12 слов), 70% средние (13-20 слов).
16420) Используй разные конструкции предложений для естественности.
165
166ЗАПРЕТЫ:
16721) Только русский язык. Без брендов, компаний, фамилий.
16822) Запрещены слова: «лекарство», «препарат», «революционный», «инновационный», «уникальный».
16923) Без заголовков, списков, вопросов, эмоджи, инструкций хранения и описания неактивных компонентов.
170
171ПРИМЕР ХОРОШЕГО АБЗАЦА С ВЫСОКОЙ ПЛОТНОСТЬЮ КЛЮЧЕЙ:
172Натуральный гель для интимной гигиены обеспечивает деликатный уход и поддерживает естественный pH баланс. Средство для интимной гигиены с мягкой формулой подходит для ежедневного применения и помогает предотвратить дискомфорт. Гель с пантенолом и экстрактом лаванды увлажняет кожу деликатных зон, а водная основа делает текстуру легкой и комфортной. Интимный гель для женщин не содержит агрессивных компонентов и подходит даже для чувствительной кожи.
173
174ПРИМЕР ПЛОХОГО АБЗАЦА (НЕ ДЕЛАЙ ТАК):
175Гель для интимной гигиены очищает. Интимный гель увлажняет. Гель для женщин помогает.
176
177ВАЖНО:
178- Используй МАКСИМУУ запросов из списка
179- Вплетай их естественно, но плотно
180- Не бойся использовать 4-5 ключей на 100 слов
181- Главное - сохранять читаемость и естественность
182
183${customPrompt ? `\n\nДОПОЛНИТЕЛЬНЫЕ ТРЕБОВАНИЯ:\n${customPrompt}\n` : ''}
184
185ВЫВОД:
186Начни сразу с описания. Никаких вступлений, вопросов и пояснений.`;
187
188        return basePrompt;
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        // СТРОГАЯ ПРОВЕРКА SKU
862        if (!sku || !/^\d+$/.test(sku)) {
863            console.error('Ozon Description Generator: Некорректный SKU в URL:', sku);
864            return { title: '', sku: null };
865        }
866        
867        // Получаем название товара
868        let title = '';
869        const titleInput = document.querySelector('input[name="name"]');
870        if (titleInput) {
871            title = titleInput.value;
872        }
873        
874        return { title, sku };
875    }
876
877    // ============================================
878    // СОЗДАНИЕ КНОПКИ ГЕНЕРАТОРА
879    // ============================================
880
881    function createGeneratorButton() {
882        // Ищем контейнер с аннотацией (описанием)
883        const annotationContainer = document.querySelector('[id="attribute#4191"]')?.closest('.index_formField_P3SmU');
884        
885        if (!annotationContainer) {
886            return false;
887        }
888        
889        if (document.querySelector('.ozon-desc-generator-btn')) {
890            return true;
891        }
892        
893        const buttonsContainer = document.createElement('div');
894        buttonsContainer.style.cssText = 'display: flex; gap: 8px; margin-top: 12px;';
895        
896        const generatorButton = document.createElement('button');
897        generatorButton.className = 'ozon-desc-generator-btn';
898        generatorButton.textContent = '✨ Генератор описаний';
899        generatorButton.type = 'button';
900        generatorButton.style.width = 'auto';
901        generatorButton.style.flex = '1';
902        generatorButton.addEventListener('click', function(e) {
903            e.preventDefault();
904            openModal();
905        });
906        
907        const richContentButton = document.createElement('button');
908        richContentButton.className = 'ozon-desc-generator-btn';
909        richContentButton.textContent = '📄 Отправить в Rich-контент';
910        richContentButton.type = 'button';
911        richContentButton.style.width = 'auto';
912        richContentButton.style.flex = '1';
913        richContentButton.style.background = 'linear-gradient(135deg, #10b981, #059669)';
914        richContentButton.addEventListener('click', function(e) {
915            e.preventDefault();
916            sendToRichContent();
917        });
918        richContentButton.addEventListener('mouseenter', function() {
919            this.style.background = 'linear-gradient(135deg, #059669, #047857)';
920        });
921        richContentButton.addEventListener('mouseleave', function() {
922            this.style.background = 'linear-gradient(135deg, #10b981, #059669)';
923        });
924        
925        buttonsContainer.appendChild(generatorButton);
926        buttonsContainer.appendChild(richContentButton);
927        
928        annotationContainer.appendChild(buttonsContainer);
929        return true;
930    }
931
932    // ============================================
933    // ПОКАЗ СТАТУСА
934    // ============================================
935
936    function showStatus(container, message, type) {
937        container.innerHTML = `<div class="ozon-desc-status ${type}">${message}</div>`;
938    }
939
940    // ============================================
941    // МОДАЛЬНОЕ ОКНО
942    // ============================================
943
944    async function openModal() {
945        const productInfo = await getProductInfo();
946        const currentSKU = productInfo.sku;
947    
948        let savedKeywords = '';
949        let savedMinusWords = '';
950        let savedPrompt = '';
951    
952        if (currentSKU) {
953            savedKeywords = await GM.getValue(`ozon_product_${currentSKU}_keywords`, '');
954            savedMinusWords = await GM.getValue(`ozon_product_${currentSKU}_minus_words`, '');
955            savedPrompt = await GM.getValue(`ozon_product_${currentSKU}_custom_prompt`, '');
956        }
957    
958        // Предустановленные промпты
959        const presetPrompts = [
960            { name: 'Без дополнительных требований', value: '' },
961            { name: 'Акцент на натуральность', value: 'Сделай акцент на натуральности состава, экологичности и безопасности для здоровья.' },
962            { name: 'Премиум-сегмент', value: 'Используй стиль премиум-сегмента: подчеркни эксклюзивность, высокое качество и статусность продукта.' },
963            { name: 'Для чувствительной кожи', value: 'Особое внимание уделить гипоаллергенности, мягкости формулы и подходу для чувствительной кожи.' },
964            { name: 'Антивозрастной уход', value: 'Акцентируй внимание на антивозрастных свойствах, омоложении и борьбе с признаками старения.' },
965            { name: 'Быстрый результат', value: 'Подчеркни быстроту достижения видимого результата и эффективность средства.' }
966        ];
967    
968        const modal = document.createElement('div');
969        modal.className = 'ozon-desc-modal';
970        modal.innerHTML = `
971            <div class="ozon-desc-modal-content">
972                <div class="ozon-desc-modal-header">✨ Генератор описаний для Ozon</div>
973                
974                <div class="ozon-desc-input-group">
975                    <label class="ozon-desc-label">Введите ключевые слова (каждое с новой строки):</label>
976                    <textarea class="ozon-desc-textarea" id="ozon-keywords-input" placeholder="Например:&#10;сыворотка для лица&#10;витамин с&#10;увлажнение">${savedKeywords}</textarea>
977                    <button class="ozon-desc-suggest-btn" id="ozon-suggest-keywords-btn">🔍 Предложить поисковые маски</button>
978                    <div id="ozon-suggested-keywords-container" style="display: none;"></div>
979                </div>
980                
981                <div class="ozon-desc-input-group">
982                    <label class="ozon-desc-label">Минус-слова (каждое с новой строки):</label>
983                    <textarea class="ozon-desc-textarea" style="min-height: 80px;" id="ozon-minus-words-input" placeholder="Например:&#10;mixit&#10;nivea&#10;корея">${savedMinusWords}</textarea>
984                </div>
985                
986                <div class="ozon-desc-input-group">
987                    <label class="ozon-desc-label">Дополнительные требования к описанию:</label>
988                    <select class="ozon-desc-textarea" id="ozon-prompt-preset-select" style="min-height: auto; padding: 10px; margin-bottom: 8px;">
989                        ${presetPrompts.map(preset => `<option value="${preset.value}">${preset.name}</option>`).join('')}
990                    </select>
991                    <textarea class="ozon-desc-textarea" style="min-height: 80px;" id="ozon-custom-prompt-input" placeholder="Или напишите свои требования к стилю и содержанию описания...">${savedPrompt}</textarea>
992                </div>
993                
994                <div id="ozon-desc-result-container" style="display: none;">
995                    <div class="ozon-desc-label">Сгенерированное описание:</div>
996                    <div class="ozon-desc-result" id="ozon-desc-result"></div>
997                    <div class="ozon-desc-char-count" id="ozon-char-count"></div>
998                    <div id="ozon-desc-stats-container"></div>
999                </div>
1000                
1001                <div id="ozon-desc-status-container"></div>
1002                
1003                <div class="ozon-desc-buttons">
1004                    <button class="ozon-desc-btn ozon-desc-btn-secondary" id="ozon-close-btn">Закрыть</button>
1005                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-generate-btn">🚀 Сгенерировать</button>
1006                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-regenerate-btn" style="display: none;">🔄 Перегенерировать</button>
1007                    <button class="ozon-desc-btn ozon-desc-btn-success" id="ozon-insert-btn" style="display: none;">✅ Вставить в описание</button>
1008                </div>
1009            </div>
1010        `;
1011    
1012        document.body.appendChild(modal);
1013    
1014        // Обработчик выбора пресета промпта
1015        document.getElementById('ozon-prompt-preset-select').addEventListener('change', (e) => {
1016            const customPromptInput = document.getElementById('ozon-custom-prompt-input');
1017            if (e.target.value) {
1018                customPromptInput.value = e.target.value;
1019            }
1020        });
1021    
1022        modal.addEventListener('click', (e) => {
1023            if (e.target === modal) {
1024                modal.remove();
1025            }
1026        });
1027    
1028        document.getElementById('ozon-close-btn').addEventListener('click', () => {
1029            modal.remove();
1030        });
1031    
1032        document.getElementById('ozon-suggest-keywords-btn').addEventListener('click', () => {
1033            suggestKeywords();
1034        });
1035    
1036        document.getElementById('ozon-generate-btn').addEventListener('click', () => {
1037            generateDescription(modal);
1038        });
1039    
1040        document.getElementById('ozon-regenerate-btn').addEventListener('click', () => {
1041            generateDescription(modal, true);
1042        });
1043    
1044        document.getElementById('ozon-insert-btn').addEventListener('click', () => {
1045            insertDescription(modal);
1046        });
1047    }
1048
1049    // ============================================
1050    // ПРЕДЛОЖЕНИЕ КЛЮЧЕВЫХ СЛОВ / МАСОК
1051    // ============================================
1052
1053    async function suggestKeywords() {
1054        const keywordsInput = document.getElementById('ozon-keywords-input');
1055        const suggestBtn = document.getElementById('ozon-suggest-keywords-btn');
1056        const suggestedContainer = document.getElementById('ozon-suggested-keywords-container');
1057        const statusContainer = document.getElementById('ozon-desc-status-container');
1058        
1059        const keywordsText = keywordsInput.value.trim();
1060        
1061        if (!keywordsText) {
1062            showStatus(statusContainer, 'Пожалуйста, сначала введите базовые ключевые слова', 'error');
1063            return;
1064        }
1065        
1066        suggestBtn.disabled = true;
1067        suggestBtn.textContent = '⏳ AI анализирует...';
1068        showStatus(statusContainer, 'AI анализирует товар и подбирает поисковые маски и ключевые слова...', 'info');
1069        
1070        try {
1071            const productInfo = await getProductInfo();
1072            
1073            const suggestPrompt = generateMasksPrompt(productInfo);
1074            console.log('Ozon Description Generator: Запрос масок от AI');
1075            
1076            const suggestResponse = await RM.aiCall(suggestPrompt);
1077            
1078            let masks = [];
1079            let aiKeywords = [];
1080            try {
1081                const suggestData = JSON.parse(suggestResponse);
1082                masks = Array.isArray(suggestData.masks) ? suggestData.masks.filter(Boolean) : [];
1083                aiKeywords = Array.isArray(suggestData.keywords) ? suggestData.keywords.filter(Boolean) : [];
1084                console.log(`Ozon Description Generator: AI предложил ${masks.length} масок и ${aiKeywords.length} ключей`);
1085            } catch (e) {
1086                console.error('Ozon Description Generator: Ошибка парсинга масок:', e);
1087                showStatus(statusContainer, 'Ошибка при получении предложений. Попробуйте еще раз.', 'error');
1088                return;
1089            }
1090            
1091            if (masks.length === 0 && aiKeywords.length === 0) {
1092                showStatus(statusContainer, 'AI не смог предложить маски и ключевые слова', 'error');
1093                return;
1094            }
1095            
1096            const hasChips = masks.length + aiKeywords.length > 0;
1097            
1098            suggestedContainer.innerHTML = `
1099                <div class="ozon-desc-masks-container">
1100                    <div class="ozon-desc-masks-header">
1101                        <span>Поисковые маски и ключевые слова (кликните для выбора):</span>
1102                        ${hasChips ? `<button class="ozon-desc-suggest-btn" id="ozon-toggle-all-btn" style="padding: 4px 12px; font-size: 13px;">
1103                            Выбрать все
1104                        </button>` : ''}
1105                    </div>
1106                    ${masks.length ? `
1107                        <div class="ozon-desc-masks-group">
1108                            <div class="ozon-desc-masks-group-title">МАСКИ</div>
1109                            <div class="ozon-desc-masks-grid">
1110                                ${masks.map(mask => `
1111                                    <div class="ozon-mask-chip" data-type="маска" data-mask="${mask}">
1112                                        <span>${mask}</span>
1113                                    </div>
1114                                `).join('')}
1115                            </div>
1116                        </div>
1117                    ` : ''}
1118                    ${aiKeywords.length ? `
1119                        <div class="ozon-desc-masks-group">
1120                            <div class="ozon-desc-masks-group-title">КЛЮЧЕВЫЕ СЛОВА</div>
1121                            <div class="ozon-desc-masks-grid">
1122                                ${aiKeywords.map(keyword => `
1123                                    <div class="ozon-mask-chip" data-type="ключ" data-mask="${keyword}">
1124                                        <span>${keyword}</span>
1125                                    </div>
1126                                `).join('')}
1127                            </div>
1128                        </div>
1129                    ` : ''}
1130                </div>
1131            `;
1132            
1133            suggestedContainer.style.display = 'block';
1134            
1135            suggestedContainer.querySelectorAll('.ozon-mask-chip').forEach(chip => {
1136                chip.addEventListener('click', () => {
1137                    chip.classList.toggle('selected');
1138                    updateToggleButtonText();
1139                });
1140            });
1141            
1142            function updateToggleButtonText() {
1143                const chips = suggestedContainer.querySelectorAll('.ozon-mask-chip');
1144                const allSelected = Array.from(chips).every(c => c.classList.contains('selected'));
1145                const toggleBtn = document.getElementById('ozon-toggle-all-btn');
1146                if (toggleBtn) {
1147                    toggleBtn.textContent = allSelected ? 'Снять все' : 'Выбрать все';
1148                }
1149            }
1150            
1151            const toggleBtn = document.getElementById('ozon-toggle-all-btn');
1152            if (toggleBtn) {
1153                toggleBtn.addEventListener('click', () => {
1154                    const chips = suggestedContainer.querySelectorAll('.ozon-mask-chip');
1155                    const allSelected = Array.from(chips).every(c => c.classList.contains('selected'));
1156                    
1157                    chips.forEach(c => {
1158                        if (allSelected) {
1159                            c.classList.remove('selected');
1160                        } else {
1161                            c.classList.add('selected');
1162                        }
1163                    });
1164                    
1165                    updateToggleButtonText();
1166                });
1167            }
1168            
1169            showStatus(statusContainer, `AI предложил ${masks.length} масок и ${aiKeywords.length} ключевых слов. Выберите нужные и нажмите "Сгенерировать"`, 'success');
1170            
1171        } catch (error) {
1172            console.error('Ozon Description Generator: Ошибка при предложении масок:', error);
1173            showStatus(statusContainer, 'Ошибка при получении предложений: ' + error.message, 'error');
1174        } finally {
1175            suggestBtn.disabled = false;
1176            suggestBtn.textContent = '🔍 Предложить поисковые маски';
1177        }
1178    }
1179
1180    // ============================================
1181    // СБОР ДАННЫХ С АНАЛИТИКИ
1182    // ============================================
1183
1184    async function collectAnalyticsData(keywords, minusWords) {
1185        console.log('Ozon Description Generator: Начало сбора данных с аналитики');
1186        console.log('Ozon Description Generator: Минус-слова для фильтрации:', minusWords);
1187        
1188        // Проверяем, не идет ли уже сбор данных
1189        const currentStatus = await GM.getValue('ozon_collection_status', 'none');
1190        if (currentStatus === 'pending') {
1191            console.log('Ozon Description Generator: Обнаружен статус pending, сбрасываем и начинаем заново');
1192            await GM.setValue('ozon_collection_status', 'none');
1193        }
1194        
1195        await GM.setValue('ozon_keywords_to_process', JSON.stringify(keywords));
1196        await GM.setValue('ozon_minus_words', JSON.stringify(minusWords));
1197        await GM.setValue('ozon_analytics_data', JSON.stringify([]));
1198        await GM.setValue('ozon_collection_status', 'pending');
1199        
1200        const analyticsUrl = 'https://seller.ozon.ru/app/analytics/what-to-sell/all-queries';
1201        console.log('Ozon Description Generator: Открываем страницу аналитики');
1202        await GM.openInTab(analyticsUrl, false);
1203        
1204        console.log('Ozon Description Generator: Открыта страница аналитики, ожидание сбора данных...');
1205        
1206        const maxWaitTime = 300000;
1207        const checkInterval = 2000;
1208        let waitedTime = 0;
1209        
1210        while (waitedTime < maxWaitTime) {
1211            await new Promise(resolve => setTimeout(resolve, checkInterval));
1212            waitedTime += checkInterval;
1213            
1214            const status = await GM.getValue('ozon_collection_status', 'pending');
1215            
1216            if (status === 'completed') {
1217                const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1218                const analyticsData = JSON.parse(analyticsDataStr);
1219                console.log('Ozon Description Generator: Данные успешно собраны');
1220                return analyticsData;
1221            } else if (status === 'error') {
1222                console.error('Ozon Description Generator: Ошибка при сборе данных');
1223                return [];
1224            }
1225        }
1226        
1227        console.error('Ozon Description Generator: Превышено время ожидания сбора данных');
1228        return [];
1229    }
1230
1231    // ============================================
1232    // АВТОМАТИЧЕСКИЙ СБОР НА СТРАНИЦЕ АНАЛИТИКИ
1233    // ============================================
1234
1235    async function autoCollectOnAnalyticsPage() {
1236        if (!window.location.href.includes('seller.ozon.ru/app/analytics/what-to-sell/all-queries')) {
1237            return;
1238        }
1239        
1240        console.log('Ozon Description Generator: Обнаружена страница аналитики');
1241        
1242        const status = await GM.getValue('ozon_collection_status', 'none');
1243        if (status !== 'pending') {
1244            return;
1245        }
1246        
1247        console.log('Ozon Description Generator: Начинаем автоматический сбор данных');
1248        
1249        try {
1250            const keywordsStr = await GM.getValue('ozon_keywords_to_process', '[]');
1251            const minusWordsStr = await GM.getValue('ozon_minus_words', '[]');
1252            const keywords = JSON.parse(keywordsStr);
1253            const minusWords = JSON.parse(minusWordsStr);
1254            
1255            const analyticsData = [];
1256            
1257            await new Promise(resolve => setTimeout(resolve, 3000));
1258            
1259            // Выбираем период 28 дней
1260            try {
1261                const periodButton = document.querySelector('button[data-active="true"]');
1262                if (periodButton && periodButton.textContent.includes('7 дней')) {
1263                    console.log('Ozon Description Generator: Меняем период на 28 дней');
1264                    periodButton.click();
1265                    await new Promise(resolve => setTimeout(resolve, 1000));
1266                    
1267                    // Ищем кнопку 28 дней в выпадающем меню
1268                    const buttons = document.querySelectorAll('button');
1269                    const days28Button = Array.from(buttons).find(btn => 
1270                        btn.textContent && btn.textContent.trim().includes('28 дней')
1271                    );
1272                    
1273                    if (days28Button) {
1274                        days28Button.click();
1275                        await new Promise(resolve => setTimeout(resolve, 2000));
1276                        console.log('Ozon Description Generator: Период "28 дней" выбран');
1277                    }
1278                }
1279            } catch (e) {
1280                console.error('Ozon Description Generator: Ошибка при выборе периода:', e);
1281            }
1282            
1283            for (const keyword of keywords) {
1284                console.log(`Ozon Description Generator: Обработка ключевого слова: ${keyword}`);
1285                
1286                try {
1287                    const searchInput = document.querySelector('input[placeholder="Поисковый запрос"]');
1288                    if (!searchInput) {
1289                        console.error('Ozon Description Generator: Поле поиска не найдено');
1290                        continue;
1291                    }
1292                    
1293                    searchInput.value = '';
1294                    searchInput.focus();
1295                    searchInput.value = keyword;
1296                    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
1297                    searchInput.dispatchEvent(new Event('change', { bubbles: true }));
1298                    
1299                    await new Promise(resolve => setTimeout(resolve, 5000));
1300                    
1301                    const rows = document.querySelectorAll('table tbody tr');
1302                    const keywordData = {
1303                        keyword: keyword,
1304                        queries: []
1305                    };
1306                    
1307                    console.log(`Ozon Description Generator: Найдено строк в таблице: ${rows.length}`);
1308                    
1309                    rows.forEach(row => {
1310                        const cells = row.querySelectorAll('td');
1311                        if (cells.length >= 2) {
1312                            const query = cells[0]?.textContent?.trim();
1313                            const popularityText = cells[1]?.textContent?.trim();
1314                            
1315                            if (query && popularityText) {
1316                                const popularity = parseInt(popularityText.replace(/\s+/g, ''));
1317                                const queryLower = query.toLowerCase();
1318                                
1319                                const hasMinusWord = minusWords.some(minusWord => 
1320                                    queryLower.includes(minusWord.toLowerCase())
1321                                );
1322                                
1323                                if (hasMinusWord) {
1324                                    console.log(`Ozon Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
1325                                    return;
1326                                }
1327                                
1328                                keywordData.queries.push({
1329                                    query,
1330                                    popularity
1331                                });
1332                            }
1333                        }
1334                    });
1335                    
1336                    analyticsData.push(keywordData);
1337                    console.log(`Ozon Description Generator: Собрано ${keywordData.queries.length} запросов для "${keyword}"`);
1338                    
1339                } catch (error) {
1340                    console.error(`Ozon Description Generator: Ошибка при обработке ключевого слова "${keyword}":`, error);
1341                }
1342            }
1343            
1344            await GM.setValue('ozon_analytics_data', JSON.stringify(analyticsData));
1345            await GM.setValue('ozon_collection_status', 'completed');
1346            
1347            console.log('Ozon Description Generator: Сбор данных завершен, закрываем вкладку через 1 секунду');
1348            
1349            setTimeout(() => {
1350                console.log('Ozon Description Generator: Закрываем вкладку аналитики');
1351                window.close();
1352            }, 1000);
1353            
1354        } catch (error) {
1355            console.error('Ozon Description Generator: Ошибка при автоматическом сборе данных:', error);
1356            await GM.setValue('ozon_collection_status', 'error');
1357            
1358            // Закрываем вкладку даже при ошибке
1359            setTimeout(() => {
1360                window.close();
1361            }, 2000);
1362        }
1363    }
1364
1365    // ============================================
1366    // ГЕНЕРАЦИЯ ОПИСАНИЯ
1367    // ============================================
1368
1369    async function generateDescription(modal, skipDataCollection = false) {
1370        console.log('Ozon Description Generator: Генерация описания');
1371        
1372        const keywordsInput = document.getElementById('ozon-keywords-input');
1373        const minusWordsInput = document.getElementById('ozon-minus-words-input');
1374        const customPromptInput = document.getElementById('ozon-custom-prompt-input');
1375        const generateBtn = document.getElementById('ozon-generate-btn');
1376        const regenerateBtn = document.getElementById('ozon-regenerate-btn');
1377        const insertBtn = document.getElementById('ozon-insert-btn');
1378        const resultContainer = document.getElementById('ozon-desc-result-container');
1379        const resultDiv = document.getElementById('ozon-desc-result');
1380        const charCountDiv = document.getElementById('ozon-char-count');
1381        const statusContainer = document.getElementById('ozon-desc-status-container');
1382        const statsContainer = document.getElementById('ozon-desc-stats-container');
1383        
1384        let keywordsText = keywordsInput.value.trim();
1385        
1386        const selectedSuggestions = Array.from(document.querySelectorAll('.ozon-mask-chip.selected'))
1387            .map(chip => chip.dataset.mask);
1388        
1389        if (selectedSuggestions.length > 0) {
1390            const existingKeywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1391            const allKeywords = [...new Set([...existingKeywords, ...selectedSuggestions])];
1392            keywordsText = allKeywords.join('\n');
1393            console.log('Ozon Description Generator: Добавлены маски/ключи:', selectedSuggestions);
1394        }
1395        
1396        const keywords = keywordsText.split('\n').map(k => k.trim()).filter(k => k);
1397        const minusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
1398        const customPrompt = customPromptInput.value.trim();
1399        
1400        if (keywords.length === 0) {
1401            showStatus(statusContainer, 'Пожалуйста, введите хотя бы одно ключевое слово', 'error');
1402            return;
1403        }
1404        
1405        const productInfo = await getProductInfo();
1406        const currentSKU = productInfo.sku;
1407        
1408        if (currentSKU) {
1409            await GM.setValue(`ozon_product_${currentSKU}_keywords`, keywords.join('\n'));
1410            await GM.setValue(`ozon_product_${currentSKU}_minus_words`, minusWords.join('\n'));
1411            await GM.setValue(`ozon_product_${currentSKU}_custom_prompt`, customPrompt);
1412            console.log('Ozon Description Generator: Сохранены ключевые слова, минус-слова и промпт для товара', currentSKU);
1413        }
1414        
1415        generateBtn.disabled = true;
1416        regenerateBtn.disabled = true;
1417        
1418        try {
1419            let analyticsData = [];
1420            let queryPopularity = {};
1421            
1422            if (!skipDataCollection) {
1423                showStatus(statusContainer, 'Сбор данных из аналитики...', 'info');
1424                
1425                analyticsData = await collectAnalyticsData(keywords, minusWords);
1426                
1427                if (analyticsData.length === 0) {
1428                    showStatus(statusContainer, 'Не удалось собрать данные из аналитики', 'error');
1429                    return;
1430                }
1431            } else {
1432                const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1433                analyticsData = JSON.parse(analyticsDataStr);
1434                
1435                if (analyticsData.length === 0) {
1436                    showStatus(statusContainer, 'Нет сохраненных данных. Пожалуйста, сначала соберите данные.', 'error');
1437                    return;
1438                }
1439            }
1440            
1441            const allQueries = [];
1442            analyticsData.forEach(data => {
1443                data.queries.forEach(q => {
1444                    allQueries.push(q.query);
1445                    queryPopularity[q.query.toLowerCase()] = q.popularity;
1446                });
1447            });
1448            
1449            console.log(`Ozon Description Generator: Всего запросов для генерации: ${allQueries.length}`);
1450            
1451            if (allQueries.length === 0) {
1452                showStatus(statusContainer, 'Не найдено подходящих запросов для генерации', 'error');
1453                return;
1454            }
1455            
1456            console.log('Ozon Description Generator: Применяем минус-слова перед генерацией');
1457            console.log('Ozon Description Generator: Минус-слова:', minusWords);
1458            console.log('Ozon Description Generator: Запросов до фильтрации:', allQueries.length);
1459            
1460            const filteredQueries = allQueries.filter(query => {
1461                const queryLower = query.toLowerCase();
1462                const hasMinusWord = minusWords.some(minusWord => 
1463                    queryLower.includes(minusWord.toLowerCase())
1464                );
1465                
1466                if (hasMinusWord) {
1467                    console.log(`Ozon Description Generator: Исключен запрос "${query}" (содержит минус-слово)`);
1468                }
1469                
1470                return !hasMinusWord;
1471            });
1472            
1473            console.log('Ozon Description Generator: Запросов после фильтрации:', filteredQueries.length);
1474            
1475            if (filteredQueries.length === 0) {
1476                showStatus(statusContainer, 'Все запросы отфильтрованы минус-словами. Попробуйте уменьшить количество минус-слов.', 'error');
1477                return;
1478            }
1479            
1480            showStatus(statusContainer, 'AI генерирует описание...', 'info');
1481            
1482            const descriptionPrompt = generateDescriptionPrompt(productInfo, keywords, filteredQueries, queryPopularity, customPrompt);
1483            const description = await RM.aiCall(descriptionPrompt);
1484            
1485            await GM.setValue('ozon_generated_description', description);
1486            await GM.setValue('ozon_query_popularity', JSON.stringify(queryPopularity));
1487            
1488            resultDiv.textContent = description;
1489            resultContainer.style.display = 'block';
1490            
1491            const charCount = description.length;
1492            charCountDiv.textContent = `Символов: ${charCount}`;
1493            charCountDiv.className = 'ozon-desc-char-count success';
1494            
1495            const analysis = await analyzeUsedKeywords(description, queryPopularity, minusWords);
1496            const usagePercent = Math.round(analysis.usedQueries.length / analysis.totalQueriesAvailable * 100);
1497            
1498            if (!statsContainer) {
1499                const newStatsContainer = document.createElement('div');
1500                newStatsContainer.id = 'ozon-desc-stats-container';
1501                charCountDiv.parentElement.insertBefore(newStatsContainer, charCountDiv.nextSibling);
1502            }
1503            
1504            document.getElementById('ozon-desc-stats-container').innerHTML = `
1505                <div class="ozon-desc-stats">
1506                    <div class="ozon-desc-stats-row">
1507                        <span><strong>Использовано запросов:</strong></span>
1508                        <span>${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} (${usagePercent}%)</span>
1509                    </div>
1510                    <div class="ozon-desc-stats-row">
1511                        <span><strong>Общая частотность:</strong></span>
1512                        <span>${formatNumber(analysis.totalPopularity)}</span>
1513                    </div>
1514                </div>
1515            `;
1516            
1517            generateBtn.style.display = 'none';
1518            regenerateBtn.style.display = 'inline-block';
1519            insertBtn.style.display = 'inline-block';
1520            
1521            showStatus(statusContainer, '✅ Описание успешно сгенерировано! <span class="ozon-desc-usage-link" id="ozon-show-analytics-link">Показать аналитику использования запросов</span>', 'success');
1522            
1523            setTimeout(() => {
1524                const analyticsLink = document.getElementById('ozon-show-analytics-link');
1525                if (analyticsLink) {
1526                    analyticsLink.addEventListener('click', () => {
1527                        showUsageAnalytics();
1528                    });
1529                }
1530            }, 100);
1531            
1532        } catch (error) {
1533            console.error('Ozon Description Generator: Ошибка при генерации описания:', error);
1534            showStatus(statusContainer, 'Ошибка при генерации: ' + error.message, 'error');
1535        } finally {
1536            generateBtn.disabled = false;
1537            regenerateBtn.disabled = false;
1538        }
1539    }
1540
1541    // ============================================
1542    // АНАЛИЗ ИСПОЛЬЗОВАННЫХ КЛЮЧЕВЫХ СЛОВ
1543    // ============================================
1544
1545    async function analyzeUsedKeywords(description, queryPopularityParam = null, minusWords = []) {
1546        console.log('Ozon Description Generator: Анализ использованных ключевых слов');
1547
1548        const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1549        const analyticsData = JSON.parse(analyticsDataStr);
1550
1551        const allQueries = [];
1552        let queryPopularity = queryPopularityParam || {};
1553        
1554        if (!queryPopularityParam) {
1555            const savedPopularity = await GM.getValue('ozon_query_popularity', '{}');
1556            queryPopularity = JSON.parse(savedPopularity);
1557        }
1558
1559        analyticsData.forEach(data => {
1560            data.queries.forEach(q => {
1561                allQueries.push(q.query);
1562                if (!queryPopularity[q.query.toLowerCase()]) {
1563                    queryPopularity[q.query.toLowerCase()] = q.popularity;
1564                }
1565            });
1566        });
1567        
1568        // Фильтруем запросы с учетом минус-слов
1569        const filteredQueries = allQueries.filter(query => {
1570            const queryLower = query.toLowerCase();
1571            const hasMinusWord = minusWords.some(minusWord => 
1572                queryLower.includes(minusWord.toLowerCase())
1573            );
1574            return !hasMinusWord;
1575        });
1576
1577        const descriptionLower = description.toLowerCase();
1578        const usedQueries = [];
1579        const unusedQueries = [];
1580        let totalPopularity = 0;
1581
1582        filteredQueries.forEach(query => {
1583            if (descriptionLower.includes(query.toLowerCase())) {
1584                usedQueries.push(query);
1585                totalPopularity += queryPopularity[query.toLowerCase()] || 0;
1586            } else {
1587                unusedQueries.push(query);
1588            }
1589        });
1590
1591        console.log(`Ozon Description Generator: Использовано ${usedQueries.length} из ${filteredQueries.length} запросов (после фильтрации минус-слов)`);
1592
1593        return {
1594            usedQueries,
1595            unusedQueries,
1596            totalQueriesAvailable: filteredQueries.length,
1597            totalPopularity,
1598            queryPopularity
1599        };
1600    }
1601
1602    // ============================================
1603    // ПОКАЗ АНАЛИТИКИ ИСПОЛЬЗОВАНИЯ ЗАПРОСОВ
1604    // ============================================
1605
1606    async function showUsageAnalytics() {
1607        console.log('Ozon Description Generator: Показ аналитики использования');
1608    
1609        const description = await GM.getValue('ozon_generated_description', '');
1610        if (!description) {
1611            alert('Описание не найдено');
1612            return;
1613        }
1614    
1615        const analysis = await analyzeUsedKeywords(description);
1616    
1617        const minusWordsStr = await GM.getValue('ozon_analytics_minus_words', '[]');
1618        const minusWords = JSON.parse(minusWordsStr);
1619        
1620        let usedQueries = [...analysis.usedQueries];
1621        let unusedQueries = [...analysis.unusedQueries];
1622        let currentMinusWords = [...minusWords];
1623        let searchQuery = '';
1624    
1625        function renderModal() {
1626            const usedContainer = document.getElementById('ozon-used-queries-container');
1627            const unusedContainer = document.getElementById('ozon-unused-queries-container');
1628            const usedScrollTop = usedContainer ? usedContainer.scrollTop : 0;
1629            const unusedScrollTop = unusedContainer ? unusedContainer.scrollTop : 0;
1630            
1631            const filteredUsed = usedQueries.filter(q => 
1632                q.toLowerCase().includes(searchQuery.toLowerCase())
1633            );
1634            const filteredUnused = unusedQueries.filter(q => 
1635                q.toLowerCase().includes(searchQuery.toLowerCase())
1636            );
1637            
1638            const analyticsModal = document.querySelector('.ozon-desc-analytics-modal');
1639            if (!analyticsModal) return;
1640            
1641            analyticsModal.innerHTML = `
1642            <div class="ozon-desc-analytics-content">
1643                <div class="ozon-desc-modal-header">📊 Аналитика использования запросов</div>
1644                
1645                ${currentMinusWords.length > 0 ? `
1646                <div class="ozon-desc-minus-words-section">
1647                    <div class="ozon-desc-minus-words-header">Минус-слова (клик для удаления):</div>
1648                    <div class="ozon-desc-minus-words-list">
1649                        ${currentMinusWords.map(word => `
1650                            <div class="ozon-desc-minus-word-chip" data-word="${word}">
1651                                ${word}
1652                                <span class="ozon-desc-minus-word-remove">×</span>
1653                            </div>
1654                        `).join('')}
1655                    </div>
1656                </div>
1657                ` : ''}
1658                
1659                <input type="text" class="ozon-desc-search-input" id="ozon-analytics-search" placeholder="🔍 Поиск по запросам..." value="${searchQuery}">
1660                
1661                <div style="margin-bottom: 16px; display: flex; gap: 20px; flex-wrap: wrap;">
1662                    <div><strong>Использовано:</strong> ${analysis.usedQueries.length} из ${analysis.totalQueriesAvailable} (${Math.round(analysis.usedQueries.length / analysis.totalQueriesAvailable * 100)}%)</div>
1663                    <div><strong>Общая частотность:</strong> ${formatNumber(analysis.totalPopularity)}</div>
1664                    ${searchQuery ? `<div><strong>Найдено:</strong> ${filteredUsed.length + filteredUnused.length}</div>` : ''}
1665                </div>
1666                
1667                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
1668                    <div>
1669                        <div style="margin-bottom: 12px; font-weight: 600; color: #065f46;">✅ Использованные (${filteredUsed.length}):</div>
1670                        <div style="max-height: 350px; overflow-y: auto;" id="ozon-used-queries-container">
1671                            ${filteredUsed.length > 0 ? filteredUsed.map(query => `
1672                                <div class="ozon-desc-query-item used">
1673                                    <span class="ozon-desc-query-text">${highlightWords(query, currentMinusWords, true, 'used')}</span>
1674                                    <div style="display: flex; align-items: center; gap: 8px;">
1675                                        <span class="ozon-desc-query-popularity">${formatNumber(analysis.queryPopularity[query.toLowerCase()] || 0)}</span>
1676                                        <span class="ozon-desc-query-exclude" data-query="${query}" style="cursor: pointer; font-size: 18px; color: #991b1b; font-weight: bold;" title="Исключить запрос">×</span>
1677                                    </div>
1678                                </div>
1679                            `).join('') : '<div style="padding: 12px; color: #6b7280; text-align: center;">Нет результатов</div>'}
1680                        </div>
1681                    </div>
1682                    
1683                    <div>
1684                        <div style="margin-bottom: 12px; font-weight: 600; color: #6b7280;">⬜ Неиспользованные (${filteredUnused.length}):</div>
1685                        <div style="max-height: 350px; overflow-y: auto;" id="ozon-unused-queries-container">
1686                            ${filteredUnused.length > 0 ? filteredUnused.map(query => `
1687                                <div class="ozon-desc-query-item unused">
1688                                    <span class="ozon-desc-query-text">${highlightWords(query, currentMinusWords, true, 'unused')}</span>
1689                                    <div style="display: flex; align-items: center; gap: 8px;">
1690                                        <span class="ozon-desc-query-popularity">${formatNumber(analysis.queryPopularity[query.toLowerCase()] || 0)}</span>
1691                                        <span class="ozon-desc-query-include" data-query="${query}" style="cursor: pointer; font-size: 18px; color: #059669; font-weight: bold;" title="Включить запрос">+</span>
1692                                    </div>
1693                                </div>
1694                            `).join('') : '<div style="padding: 12px; color: #6b7280; text-align: center;">Нет результатов</div>'}
1695                        </div>
1696                    </div>
1697                </div>
1698                
1699                <div class="ozon-desc-buttons">
1700                    <button class="ozon-desc-btn ozon-desc-btn-secondary" id="ozon-close-analytics-btn">Закрыть</button>
1701                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-regenerate-with-exclusions-btn">🔄 Перегенерировать с изменениями</button>
1702                </div>
1703            </div>
1704        `;
1705            
1706            setTimeout(() => {
1707                const newUsedContainer = document.getElementById('ozon-used-queries-container');
1708                const newUnusedContainer = document.getElementById('ozon-unused-queries-container');
1709                if (newUsedContainer) newUsedContainer.scrollTop = usedScrollTop;
1710                if (newUnusedContainer) newUnusedContainer.scrollTop = unusedScrollTop;
1711            }, 0);
1712            
1713            attachEventHandlers();
1714        }
1715        
1716        function highlightWords(text, words, clickable = false, type = 'unused') {
1717            if (!clickable) return text;
1718        
1719            const textWords = text.split(/\s+/);
1720            return textWords.map(word => {
1721                const cleanWord = word.toLowerCase().replace(/[.,!?;:]/g, '');
1722                return `<span class="ozon-desc-query-word" data-word="${cleanWord}" data-type="${type}">${word}</span>`;
1723            }).join(' ');
1724        }
1725        
1726        function removeQueriesWithMinusWord(minusWord) {
1727            const minusWordLower = minusWord.toLowerCase();
1728            
1729            usedQueries = usedQueries.filter(query => 
1730                !query.toLowerCase().includes(minusWordLower)
1731            );
1732            
1733            unusedQueries = unusedQueries.filter(query => 
1734                !query.toLowerCase().includes(minusWordLower)
1735            );
1736        }
1737        
1738        function attachEventHandlers() {
1739            const analyticsModal = document.querySelector('.ozon-desc-analytics-modal');
1740            if (!analyticsModal) return;
1741            
1742            const searchInput = document.getElementById('ozon-analytics-search');
1743            if (searchInput) {
1744                searchInput.addEventListener('input', (e) => {
1745                    searchQuery = e.target.value;
1746                    const cursorPosition = e.target.selectionStart;
1747                    renderModal();
1748                    setTimeout(() => {
1749                        const newSearchInput = document.getElementById('ozon-analytics-search');
1750                        if (newSearchInput) {
1751                            newSearchInput.focus();
1752                            newSearchInput.setSelectionRange(cursorPosition, cursorPosition);
1753                        }
1754                    }, 0);
1755                });
1756            }
1757            
1758            analyticsModal.addEventListener('click', (e) => {
1759                if (e.target === analyticsModal) {
1760                    analyticsModal.remove();
1761                }
1762            });
1763            
1764            const closeBtn = document.getElementById('ozon-close-analytics-btn');
1765            if (closeBtn) {
1766                closeBtn.addEventListener('click', () => {
1767                    analyticsModal.remove();
1768                });
1769            }
1770            
1771            const regenerateBtn = document.getElementById('ozon-regenerate-with-exclusions-btn');
1772            if (regenerateBtn) {
1773                regenerateBtn.addEventListener('click', async () => {
1774                    await GM.setValue('ozon_analytics_minus_words', JSON.stringify(currentMinusWords));
1775                    
1776                    const analyticsDataStr = await GM.getValue('ozon_analytics_data', '[]');
1777                    const analyticsData = JSON.parse(analyticsDataStr);
1778                    
1779                    const updatedAnalyticsData = analyticsData.map(data => {
1780                        return {
1781                            keyword: data.keyword,
1782                            queries: data.queries.filter(q => usedQueries.includes(q.query))
1783                        };
1784                    });
1785                    
1786                    await GM.setValue('ozon_analytics_data', JSON.stringify(updatedAnalyticsData));
1787                    
1788                    console.log('Ozon Description Generator: Обновлены данные аналитики для перегенерации');
1789                    console.log('Использованные запросы:', usedQueries.length);
1790                    console.log('Минус-слова:', currentMinusWords);
1791                    
1792                    analyticsModal.remove();
1793                    await regenerateWithExclusions();
1794                });
1795            }
1796            
1797            analyticsModal.querySelectorAll('.ozon-desc-minus-word-chip').forEach(chip => {
1798                chip.addEventListener('click', () => {
1799                    const word = chip.dataset.word;
1800                    currentMinusWords = currentMinusWords.filter(w => w !== word);
1801                    console.log(`Ozon Description Generator: Минус-слово "${word}" удалено`);
1802                    renderModal();
1803                });
1804            });
1805            
1806            analyticsModal.querySelectorAll('.ozon-desc-query-exclude').forEach(excludeBtn => {
1807                excludeBtn.addEventListener('click', () => {
1808                    const query = excludeBtn.dataset.query;
1809                    console.log(`Ozon Description Generator: Перемещаем запрос "${query}" в неиспользованные`);
1810                    
1811                    usedQueries = usedQueries.filter(q => q !== query);
1812                    if (!unusedQueries.includes(query)) {
1813                        unusedQueries.push(query);
1814                    }
1815                    
1816                    renderModal();
1817                });
1818            });
1819            
1820            analyticsModal.querySelectorAll('.ozon-desc-query-include').forEach(includeBtn => {
1821                includeBtn.addEventListener('click', () => {
1822                    const query = includeBtn.dataset.query;
1823                    console.log(`Ozon Description Generator: Перемещаем запрос "${query}" в использованные`);
1824                    
1825                    unusedQueries = unusedQueries.filter(q => q !== query);
1826                    if (!usedQueries.includes(query)) {
1827                        usedQueries.push(query);
1828                    }
1829                    
1830                    const queryLower = query.toLowerCase();
1831                    currentMinusWords = currentMinusWords.filter(w => w !== queryLower);
1832                    
1833                    renderModal();
1834                });
1835            });
1836            
1837            analyticsModal.querySelectorAll('.ozon-desc-query-word').forEach(wordSpan => {
1838                wordSpan.addEventListener('click', () => {
1839                    const word = wordSpan.dataset.word;
1840                    
1841                    console.log(`Ozon Description Generator: Добавляем минус-слово "${word}"`);
1842                    
1843                    if (!currentMinusWords.includes(word)) {
1844                        currentMinusWords.push(word);
1845                    }
1846                    
1847                    removeQueriesWithMinusWord(word);
1848                    
1849                    renderModal();
1850                });
1851            });
1852        }
1853    
1854        const analyticsModal = document.createElement('div');
1855        analyticsModal.className = 'ozon-desc-analytics-modal';
1856        document.body.appendChild(analyticsModal);
1857        
1858        renderModal();
1859    }
1860
1861    // ============================================
1862    // ПЕРЕГЕНЕРАЦИЯ С ИСКЛЮЧЕНИЯМИ
1863    // ============================================
1864
1865    async function regenerateWithExclusions() {
1866        console.log('Ozon Description Generator: Перегенерация с исключениями');
1867    
1868        const analyticsMinusWordsStr = await GM.getValue('ozon_analytics_minus_words', '[]');
1869        const analyticsMinusWords = JSON.parse(analyticsMinusWordsStr);
1870        
1871        console.log('Ozon Description Generator: Минус-слова из аналитики для перегенерации:', analyticsMinusWords);
1872    
1873        const existingModal = document.querySelector('.ozon-desc-modal');
1874        if (existingModal) {
1875            console.log('Ozon Description Generator: Модальное окно уже открыто');
1876            
1877            const minusWordsInput = document.getElementById('ozon-minus-words-input');
1878            if (minusWordsInput) {
1879                const currentMinusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
1880                const allMinusWords = [...new Set([...currentMinusWords, ...analyticsMinusWords])];
1881                minusWordsInput.value = allMinusWords.join('\n');
1882                
1883                console.log('Ozon Description Generator: Обновлены минус-слова в модальном окне:', allMinusWords);
1884            }
1885            
1886            generateDescription(existingModal, true);
1887            return;
1888        }
1889    
1890        await openModal();
1891        await new Promise(resolve => setTimeout(resolve, 100));
1892    
1893        const minusWordsInput = document.getElementById('ozon-minus-words-input');
1894        if (minusWordsInput) {
1895            const currentMinusWords = minusWordsInput.value.split('\n').map(k => k.trim()).filter(k => k);
1896            const allMinusWords = [...new Set([...currentMinusWords, ...analyticsMinusWords])];
1897            minusWordsInput.value = allMinusWords.join('\n');
1898            
1899            console.log('Ozon Description Generator: Обновлены минус-слова в новом модальном окне:', allMinusWords);
1900        }
1901    
1902        const newModal = document.querySelector('.ozon-desc-modal');
1903        if (newModal) {
1904            generateDescription(newModal, true);
1905        }
1906    }
1907
1908    // ============================================
1909    // ОТПРАВКА В RICH-КОНТЕНТ
1910    // ============================================
1911
1912    async function insertDescription(modal) {
1913        console.log('Ozon Description Generator: Вставка описания в поле');
1914        
1915        const description = await GM.getValue('ozon_generated_description', '');
1916        
1917        if (!description) {
1918            alert('Описание не найдено. Пожалуйста, сначала сгенерируйте описание.');
1919            return;
1920        }
1921        
1922        const proseMirrorDiv = document.querySelector('[id="attribute#4191"] .ProseMirror');
1923        
1924        if (!proseMirrorDiv) {
1925            alert('Не удалось найти поле описания. Убедитесь, что вы находитесь на странице редактирования товара.');
1926            return;
1927        }
1928        
1929        // Вставляем описание с сохранением пустых строк между абзацами
1930        const paragraphs = description.split('\n\n').filter(p => p.trim());
1931        const htmlContent = paragraphs.map(p => `<p>${p.replace(/\n/g, '<br>')}</p>`).join('<p><br></p>');
1932        
1933        proseMirrorDiv.innerHTML = htmlContent;
1934        proseMirrorDiv.dispatchEvent(new Event('input', { bubbles: true }));
1935        proseMirrorDiv.dispatchEvent(new Event('change', { bubbles: true }));
1936        
1937        console.log('Ozon Description Generator: Описание вставлено');
1938        
1939        modal.remove();
1940        
1941        alert('✅ Описание успешно вставлено!');
1942    }
1943
1944    async function sendToRichContent() {
1945        console.log('Ozon Description Generator: Отправка в rich-контент');
1946    
1947        try {
1948            // Получаем описание из ProseMirror редактора
1949            const proseMirrorDiv = document.querySelector('[id="attribute#4191"] .ProseMirror');
1950            
1951            if (!proseMirrorDiv) {
1952                alert('Не удалось найти поле описания. Убедитесь, что вы на странице редактирования товара.');
1953                return;
1954            }
1955            
1956            const description = proseMirrorDiv.textContent.trim();
1957            
1958            if (!description) {
1959                alert('Описание пустое. Пожалуйста, сначала сгенерируйте и вставьте описание.');
1960                return;
1961            }
1962            
1963            console.log('Ozon Description Generator: Описание скопировано, переходим на страницу медиа');
1964            
1965            // Сохраняем описание для использования на странице медиа
1966            await GM.setValue('ozon_description_for_rich', description);
1967            
1968            // Находим кнопку перехода на вкладку "Медиа"
1969            const mediaTabButton = Array.from(document.querySelectorAll('button.c9s110-a3.c9s110-a0.c9s110-a.c9s110-a4.c9s110-a6')).find(btn => 
1970                btn.textContent && btn.textContent.includes('Медиа')
1971            );
1972            
1973            if (!mediaTabButton) {
1974                alert('Не удалось найти кнопку перехода на вкладку "Медиа"');
1975                return;
1976            }
1977            
1978            console.log('Ozon Description Generator: Нажимаем кнопку "Медиа"');
1979            mediaTabButton.click();
1980            
1981            // Ждем загрузки страницы медиа и вставляем rich-контент
1982            setTimeout(async () => {
1983                await insertRichContent();
1984            }, 1000);
1985            
1986        } catch (error) {
1987            console.error('Ozon Description Generator: Ошибка при отправке в rich-контент:', error);
1988            alert('Ошибка при отправке в rich-контент: ' + error.message);
1989        }
1990    }
1991
1992    async function insertRichContent() {
1993        console.log('Ozon Description Generator: Вставка rich-контента');
1994    
1995        try {
1996            const description = await GM.getValue('ozon_description_for_rich', '');
1997            
1998            if (!description) {
1999                console.error('Ozon Description Generator: Описание не найдено в хранилище');
2000                return;
2001            }
2002            
2003            // Ищем поле Rich-контента JSON
2004            const richContentInput = document.querySelector('textarea[id^="baseInput___"]');
2005            
2006            if (!richContentInput) {
2007                alert('Не удалось найти поле Rich-контента. Убедитесь, что вы на странице редактирования товара.');
2008                return;
2009            }
2010            
2011            // Разбиваем описание на абзацы с сохранением пустых строк
2012            const paragraphs = description.split('\n\n').filter(p => p.trim());
2013            
2014            // Создаем JSON для rich-контента с абзацами
2015            const richContentJSON = {
2016                'content': [
2017                    {
2018                        'widgetName': 'raTextBlock',
2019                        'title': {
2020                            'content': [],
2021                            'size': 'size5',
2022                            'color': 'color1'
2023                        },
2024                        'theme': 'default',
2025                        'padding': 'type2',
2026                        'gapSize': 'm',
2027                        'text': {
2028                            'size': 'size2',
2029                            'align': 'left',
2030                            'color': 'color1',
2031                            'content': paragraphs
2032                        }
2033                    }
2034                ],
2035                'version': 0.3
2036            };
2037            
2038            // Вставляем JSON в поле
2039            richContentInput.value = JSON.stringify(richContentJSON, null, 2);
2040            richContentInput.dispatchEvent(new Event('input', { bubbles: true }));
2041            richContentInput.dispatchEvent(new Event('change', { bubbles: true }));
2042            
2043            console.log('Ozon Description Generator: Rich-контент успешно вставлен');
2044            
2045            // Очищаем сохраненное описание
2046            await GM.setValue('ozon_description_for_rich', '');
2047            
2048            // Проверяем, идет ли автогенерация
2049            const expectedKeys = Object.keys(localStorage).filter(key => key.startsWith('wbAutoExpected_'));
2050            const isAutogen = expectedKeys.length > 0;
2051            
2052            // Показываем alert только если это НЕ автогенерация
2053            if (!isAutogen) {
2054                alert('✅ Описание успешно отправлено в Rich-контент!');
2055            }
2056            
2057        } catch (error) {
2058            console.error('Ozon Description Generator: Ошибка при вставке rich-контента:', error);
2059            alert('Ошибка при вставке rich-контента: ' + error.message);
2060        }
2061    }
2062
2063    // ============================================
2064    // АВТОГЕНЕРАЦИЯ ПО ВСЕМ ТОВАРАМ
2065    // ============================================
2066
2067    // Вспомогательные функции для работы с localStorage
2068    function generateCheckId() {
2069        return Date.now() + '_' + Math.random().toString(36).substr(2, 9);
2070    }
2071
2072    function setAutoCheck(nmID, title, checkId) {
2073        const data = {
2074            nmID,
2075            title,
2076            checkId,
2077            timestamp: Date.now()
2078        };
2079        localStorage.setItem('wbAutoCheck', JSON.stringify(data));
2080        localStorage.setItem(`wbAutoExpected_${checkId}`, 'true');
2081        console.log('Ozon Description Generator: Сохранен autoCheck:', data);
2082    }
2083
2084    function getAutoCheck() {
2085        const data = localStorage.getItem('wbAutoCheck');
2086        return data ? JSON.parse(data) : null;
2087    }
2088
2089    function clearAutoCheck(checkId) {
2090        localStorage.removeItem('wbAutoCheck');
2091        if (checkId) {
2092            localStorage.removeItem(`wbAutoExpected_${checkId}`);
2093            localStorage.removeItem('wbAutoResult');
2094        }
2095        console.log('Ozon Description Generator: Очищен autoCheck');
2096    }
2097
2098    function setAutoResult(checkId, success, error = null) {
2099        const result = {
2100            checkId,
2101            success,
2102            error,
2103            timestamp: Date.now()
2104        };
2105        localStorage.setItem('wbAutoResult', JSON.stringify(result));
2106        console.log('Ozon Description Generator: Сохранен результат:', result);
2107    }
2108
2109    function getAutoResult() {
2110        const data = localStorage.getItem('wbAutoResult');
2111        return data ? JSON.parse(data) : null;
2112    }
2113
2114    function isCheckIdExpected(checkId) {
2115        return localStorage.getItem(`wbAutoExpected_${checkId}`) === 'true';
2116    }
2117
2118    function isTimestampFresh(timestamp, maxAgeMs = 180000) { // 3 минуты
2119        return (Date.now() - timestamp) < maxAgeMs;
2120    }
2121
2122    // Создание кнопки автогенерации на странице списка товаров
2123    function createAutogenButton() {
2124        if (document.querySelector('.ozon-autogen-btn')) {
2125            return;
2126        }
2127
2128        // Ищем контейнер с кнопками действий
2129        const actionsContainer = document.querySelector('.cs5110-a5 .cs5110-b0 div');
2130        
2131        if (!actionsContainer) {
2132            return;
2133        }
2134
2135        const autogenButton = document.createElement('button');
2136        autogenButton.className = 'ozon-autogen-btn';
2137        autogenButton.textContent = '🤖 Автогенерация';
2138        autogenButton.type = 'button';
2139        autogenButton.setAttribute('data-ozon-autogen', 'true');
2140        autogenButton.addEventListener('click', openAutogenModal);
2141
2142        actionsContainer.insertBefore(autogenButton, actionsContainer.firstChild);
2143        console.log('Ozon Description Generator: Кнопка автогенерации добавлена');
2144    }
2145
2146    // Модальное окно настроек автогенерации
2147    async function openAutogenModal() {
2148        console.log('Ozon Description Generator: Открытие модального окна автогенерации');
2149    
2150        // Предустановленные промпты
2151        const presetPrompts = [
2152            { name: 'Без дополнительных требований', value: '' },
2153            { name: 'Акцент на натуральность', value: 'Сделай акцент на натуральности состава, экологичности и безопасности для здоровья.' },
2154            { name: 'Премиум-сегмент', value: 'Используй стиль премиум-сегмента: подчеркни эксклюзивность, высокое качество и статусность продукта.' },
2155            { name: 'Для чувствительной кожи', value: 'Особое внимание уделить гипоаллергенности, мягкости формулы и подходу для чувствительной кожи.' },
2156            { name: 'Антивозрастной уход', value: 'Акцентируй внимание на антивозрастных свойствах, омоложении и борьбе с признаками старения.' },
2157            { name: 'Быстрый результат', value: 'Подчеркни быстроту достижения видимого результата и эффективность средства.' }
2158        ];
2159
2160        const modal = document.createElement('div');
2161        modal.className = 'ozon-desc-modal';
2162        modal.innerHTML = `
2163            <div class="ozon-desc-modal-content">
2164                <div class="ozon-desc-modal-header">🤖 Автогенерация описаний</div>
2165                
2166                <div class="ozon-desc-input-group">
2167                    <label class="ozon-desc-label">Ключевые слова (необязательно, каждое с новой строки):</label>
2168                    <textarea class="ozon-desc-textarea" id="ozon-autogen-keywords-input" placeholder="Если не заполнено, AI сам подберет ключевые слова для каждого товара"></textarea>
2169                </div>
2170                
2171                <div class="ozon-desc-input-group">
2172                    <label class="ozon-desc-label">Дополнительные требования к описанию:</label>
2173                    <select class="ozon-desc-textarea" id="ozon-autogen-prompt-preset-select" style="min-height: auto; padding: 10px; margin-bottom: 8px;">
2174                        ${presetPrompts.map(preset => `<option value="${preset.value}">${preset.name}</option>`).join('')}
2175                    </select>
2176                    <textarea class="ozon-desc-textarea" style="min-height: 80px;" id="ozon-autogen-custom-prompt-input" placeholder="Или напишите свои требования к стилю и содержанию описания..."></textarea>
2177                </div>
2178                
2179                <div class="ozon-checkbox-container">
2180                    <input type="checkbox" class="ozon-checkbox" id="ozon-autogen-rich-checkbox" checked>
2181                    <label class="ozon-checkbox-label" for="ozon-autogen-rich-checkbox">Отправить в Rich-контент</label>
2182                </div>
2183                
2184                <div class="ozon-checkbox-container">
2185                    <input type="checkbox" class="ozon-checkbox" id="ozon-autogen-test-mode-checkbox">
2186                    <label class="ozon-checkbox-label" for="ozon-autogen-test-mode-checkbox">🧪 Тестовый режим (без AI, вставка "тест")</label>
2187                </div>
2188                
2189                <div id="ozon-autogen-status-container"></div>
2190                
2191                <div class="ozon-desc-buttons">
2192                    <button class="ozon-desc-btn ozon-desc-btn-secondary" id="ozon-autogen-close-btn">Закрыть</button>
2193                    <button class="ozon-desc-btn ozon-desc-btn-primary" id="ozon-autogen-start-btn">🚀 Начать автогенерацию</button>
2194                </div>
2195            </div>
2196        `;
2197
2198        document.body.appendChild(modal);
2199    
2200        // Обработчик выбора пресета промпта
2201        document.getElementById('ozon-autogen-prompt-preset-select').addEventListener('change', (e) => {
2202            const customPromptInput = document.getElementById('ozon-autogen-custom-prompt-input');
2203            if (e.target.value) {
2204                customPromptInput.value = e.target.value;
2205            }
2206        });
2207
2208        modal.addEventListener('click', (e) => {
2209            if (e.target === modal) {
2210                modal.remove();
2211            }
2212        });
2213
2214        document.getElementById('ozon-autogen-close-btn').addEventListener('click', () => {
2215            modal.remove();
2216        });
2217
2218        document.getElementById('ozon-autogen-start-btn').addEventListener('click', () => {
2219            startAutogeneration(modal);
2220        });
2221    }
2222
2223    // Начало автогенерации
2224    async function startAutogeneration(modal) {
2225        const keywordsInput = document.getElementById('ozon-autogen-keywords-input');
2226        const customPromptInput = document.getElementById('ozon-autogen-custom-prompt-input');
2227        const richCheckbox = document.getElementById('ozon-autogen-rich-checkbox');
2228        const testModeCheckbox = document.getElementById('ozon-autogen-test-mode-checkbox');
2229        
2230        const globalKeywords = keywordsInput.value.trim();
2231        const customPrompt = customPromptInput.value.trim();
2232        const sendToRich = richCheckbox.checked;
2233        const testMode = testModeCheckbox.checked;
2234
2235        console.log('Ozon Description Generator: Начало автогенерации');
2236        console.log('Глобальные ключевые слова:', globalKeywords);
2237        console.log('Дополнительные требования:', customPrompt);
2238        console.log('Отправить в Rich-контент:', sendToRich);
2239        console.log('Тестовый режим:', testMode);
2240
2241        // Сохраняем настройки в localStorage
2242        localStorage.setItem('ozon_autogen_global_keywords', globalKeywords);
2243        localStorage.setItem('ozon_autogen_custom_prompt', customPrompt);
2244        localStorage.setItem('ozon_autogen_send_to_rich', sendToRich);
2245        localStorage.setItem('ozon_autogen_test_mode', testMode);
2246        localStorage.setItem('ozon_autogen_processed', '0');
2247        localStorage.setItem('ozon_autogen_errors', '0');
2248        
2249        // Очищаем список обработанных товаров при новом запуске
2250        localStorage.setItem('ozon_autogen_processed_skus', '[]');
2251        console.log('Ozon Description Generator: Список обработанных товаров очищен');
2252
2253        modal.remove();
2254
2255        // Показываем окно прогресса
2256        showAutogenProgress();
2257
2258        // Начинаем обработку товаров
2259        processNextProduct();
2260    }
2261
2262    // Обработка следующего товара
2263    async function processNextProduct() {
2264        console.log('Ozon Description Generator: processNextProduct вызван');
2265
2266        // Прокручиваем страницу вниз, чтобы загрузить товары
2267        window.scrollBy(0, 500);
2268        await new Promise(resolve => setTimeout(resolve, 1000));
2269
2270        // Получаем список уже обработанных товаров
2271        const processedProducts = JSON.parse(localStorage.getItem('ozon_autogen_processed_skus') || '[]');
2272        console.log('Ozon Description Generator: Уже обработано товаров:', processedProducts.length);
2273
2274        // Ищем ссылки на карточки товаров - пробуем разные варианты
2275        let productLinks = Array.from(document.querySelectorAll('a[href*="/app/products/"]')).filter(link => {
2276            const href = link.getAttribute('href');
2277            return href && href.includes('/app/products/') && /\/\d+\//.test(href);
2278        });
2279        
2280        // Если не нашли ссылки, ищем кнопки "Редактировать" по классам и title
2281        if (productLinks.length === 0) {
2282            console.log('Ozon Description Generator: Ссылки не найдены, ищем кнопки редактирования по классам');
2283            
2284            // Ищем кнопки с классом c9r110-a и title "редактировать товар"
2285            const editButtons = Array.from(document.querySelectorAll('button.c9r110-a[title*="едактировать"]'));
2286            
2287            if (editButtons.length > 0) {
2288                console.log(`Ozon Description Generator: Найдено кнопок редактирования: ${editButtons.length}`);
2289                
2290                // Ищем первую необработанную кнопку
2291                let editButton = null;
2292                let nmID = '';
2293                let productName = 'Товар';
2294                
2295                for (const btn of editButtons) {
2296                    const row = btn.closest('tr');
2297                    const skuElement = row?.querySelector('.index_skuText_61dv5');
2298                    
2299                    if (skuElement) {
2300                        const sku = skuElement.textContent.trim();
2301                        
2302                        // Проверяем, не обработан ли уже этот товар
2303                        if (!processedProducts.includes(sku)) {
2304                            editButton = btn;
2305                            nmID = sku;
2306                            const nameCell = row?.querySelector('td:nth-child(2)');
2307                            if (nameCell) {
2308                                productName = nameCell.textContent.trim();
2309                            }
2310                            console.log('Ozon Description Generator: Найден необработанный товар с SKU:', nmID);
2311                            break;
2312                        } else {
2313                            console.log('Ozon Description Generator: Товар с SKU', sku, 'уже обработан, пропускаем');
2314                        }
2315                    }
2316                }
2317                
2318                if (!editButton) {
2319                    console.log('Ozon Description Generator: Все товары на странице уже обработаны');
2320                    await stopAutogeneration();
2321                    return;
2322                }
2323                
2324                // Если не нашли SKU, генерируем временный
2325                if (!nmID) {
2326                    nmID = Date.now().toString();
2327                    console.log('Ozon Description Generator: SKU не найден, используем временный:', nmID);
2328                }
2329                
2330                console.log(`Ozon Description Generator: Обработка товара: ${productName} (SKU: ${nmID})`);
2331                
2332                // Добавляем товар в список обработанных
2333                processedProducts.push(nmID);
2334                localStorage.setItem('ozon_autogen_processed_skus', JSON.stringify(processedProducts));
2335                
2336                // Генерируем checkId и сохраняем данные
2337                const checkId = generateCheckId();
2338                setAutoCheck(nmID, productName, checkId);
2339                
2340                // Обновляем текущий товар в прогрессе
2341                localStorage.setItem('ozon_autogen_current_product', productName);
2342
2343                // Кликаем на кнопку
2344                console.log('Ozon Description Generator: Кликаем на кнопку редактирования');
2345                editButton.click();
2346                
2347                // Ждем результата через polling
2348                await waitForAutoResult(checkId);
2349                return;
2350            }
2351        }
2352        
2353        console.log(`Ozon Description Generator: Найдено ссылок на товары: ${productLinks.length}`);
2354
2355        if (productLinks.length === 0) {
2356            console.log('Ozon Description Generator: Ссылки и кнопки не найдены, завершаем автогенерацию');
2357            await stopAutogeneration();
2358            return;
2359        }
2360
2361        // Берем первую ссылку
2362        const productLink = productLinks[0];
2363        const href = productLink.getAttribute('href');
2364        const nmIDMatch = href.match(/\/products\/(\d+)\//);
2365        const nmID = nmIDMatch ? nmIDMatch[1] : null;
2366
2367        if (!nmID) {
2368            console.log('Ozon Description Generator: Не удалось извлечь nmID из ссылки');
2369            await stopAutogeneration();
2370            return;
2371        }
2372
2373        // Получаем название товара из строки таблицы
2374        const row = productLink.closest('tr');
2375        const productName = row?.querySelector('td:nth-child(2)')?.textContent?.trim() || `Товар ${nmID}`;
2376
2377        console.log(`Ozon Description Generator: Обработка товара: ${productName} (nmID: ${nmID})`);
2378
2379        // Добавляем товар в список обработанных
2380        processedProducts.push(nmID);
2381        localStorage.setItem('ozon_autogen_processed_skus', JSON.stringify(processedProducts));
2382
2383        // Генерируем checkId и сохраняем данные
2384        const checkId = generateCheckId();
2385        setAutoCheck(nmID, productName, checkId);
2386
2387        // Обновляем текущий товар в прогрессе
2388        localStorage.setItem('ozon_autogen_current_product', productName);
2389
2390        // Кликаем на ссылку
2391        productLink.click();
2392
2393        // Ждем результата через polling
2394        await waitForAutoResult(checkId);
2395    }
2396
2397    // Ожидание результата через polling
2398    async function waitForAutoResult(checkId) {
2399        console.log('Ozon Description Generator: Ожидание результата для checkId:', checkId);
2400        
2401        const maxWaitTime = 300000; // 5 минут
2402        const pollInterval = 500;
2403        let waitedTime = 0;
2404
2405        while (waitedTime < maxWaitTime) {
2406            await new Promise(resolve => setTimeout(resolve, pollInterval));
2407            waitedTime += pollInterval;
2408
2409            const result = getAutoResult();
2410            
2411            if (result && result.checkId === checkId) {
2412                console.log('Ozon Description Generator: Получен результат:', result);
2413                
2414                // Обновляем счетчики
2415                if (result.success) {
2416                    const processed = parseInt(localStorage.getItem('ozon_autogen_processed') || '0');
2417                    localStorage.setItem('ozon_autogen_processed', (processed + 1).toString());
2418                } else {
2419                    const errors = parseInt(localStorage.getItem('ozon_autogen_errors') || '0');
2420                    localStorage.setItem('ozon_autogen_errors', (errors + 1).toString());
2421                }
2422
2423                // Очищаем временные данные
2424                clearAutoCheck(checkId);
2425
2426                // Обрабатываем следующий товар
2427                setTimeout(() => processNextProduct(), 2000);
2428                return;
2429            }
2430        }
2431
2432        console.error('Ozon Description Generator: Превышено время ожидания результата');
2433        const errors = parseInt(localStorage.getItem('ozon_autogen_errors') || '0');
2434        localStorage.setItem('ozon_autogen_errors', (errors + 1).toString());
2435        clearAutoCheck(checkId);
2436        
2437        // Обрабатываем следующий товар
2438        setTimeout(() => processNextProduct(), 2000);
2439    }
2440
2441    // ============================================
2442    // СКРЫТИЕ ОКНА ПРОГРЕССА ОБРАБОТКИ ТОВАРА
2443    // ============================================
2444
2445    // Скрытие окна прогресса обработки товара
2446    function hideProductProgress() {
2447        const progressDiv = document.querySelector('.ozon-product-progress');
2448        if (progressDiv) {
2449            progressDiv.remove();
2450        }
2451    }
2452
2453    // ============================================
2454    // СОЗДАНИЕ ОКНА ПРОГРЕССА ОБРАБОТКИ ТОВАРА
2455    // ============================================
2456
2457    // Показ окна прогресса обработки товара
2458    function showProductProgress() {
2459        if (document.querySelector('.ozon-product-progress')) {
2460            return;
2461        }
2462
2463        const progressDiv = document.createElement('div');
2464        progressDiv.className = 'ozon-product-progress';
2465        progressDiv.innerHTML = `
2466            <div class="ozon-product-progress-header">Обработка товара</div>
2467            <div class="ozon-product-progress-stage pending" data-stage="info">Получение информации о товаре</div>
2468            <div class="ozon-product-progress-stage pending" data-stage="keywords">Подбор ключевых слов</div>
2469            <div class="ozon-product-progress-stage pending" data-stage="analytics">Сбор данных из аналитики</div>
2470            <div class="ozon-product-progress-stage pending" data-stage="generation">Генерация описания</div>
2471            <div class="ozon-product-progress-stage pending" data-stage="insert">Вставка описания</div>
2472            <div class="ozon-product-progress-stage pending" data-stage="rich">Отправка в Rich-контент</div>
2473            <div class="ozon-product-progress-stage pending" data-stage="save">Сохранение товара</div>
2474        `;
2475
2476        document.body.appendChild(progressDiv);
2477    }
2478
2479    // ============================================
2480    // ПРОКРУТИВКА СТРАНИЦЫ
2481    // ============================================
2482
2483    // Обновление прогресса обработки товара
2484    function updateProductProgress(stageName, status) {
2485        const stageMap = {
2486            'Получение информации о товаре': 'info',
2487            'Подбор ключевых слов': 'keywords',
2488            'Сбор данных из аналитики': 'analytics',
2489            'Генерация описания': 'generation',
2490            'Вставка описания': 'insert',
2491            'Отправка в Rich-контент': 'rich',
2492            'Сохранение товара': 'save',
2493            'Ошибка': 'error'
2494        };
2495
2496        const stageId = stageMap[stageName];
2497        if (!stageId) return;
2498
2499        const stageEl = document.querySelector(`.ozon-product-progress-stage[data-stage="${stageId}"]`);
2500        if (stageEl) {
2501            stageEl.className = `ozon-product-progress-stage ${status}`;
2502        }
2503    }
2504
2505    // Обработка товара на странице редактирования при автогенерации
2506    async function handleProductPageAutogen() {
2507        console.log('Ozon Description Generator: Проверка автогенерации на странице товара');
2508        
2509        // Ищем все ключи wbAutoExpected_*
2510        const expectedKeys = Object.keys(localStorage).filter(key => key.startsWith('wbAutoExpected_'));
2511        
2512        if (expectedKeys.length === 0) {
2513            console.log('Ozon Description Generator: Нет ожидаемых задач автогенерации');
2514            return;
2515        }
2516
2517        const checkId = expectedKeys[0].replace('wbAutoExpected_', '');
2518        console.log('Ozon Description Generator: Найден checkId:', checkId);
2519
2520        // Проверяем, что задача свежая
2521        const autoCheck = getAutoCheck();
2522        if (!autoCheck || autoCheck.checkId !== checkId) {
2523            console.log('Ozon Description Generator: autoCheck не найден или не совпадает');
2524            clearAutoCheck(checkId);
2525            return;
2526        }
2527
2528        console.log('Ozon Description Generator: Начинаем обработку товара:', autoCheck.title);
2529
2530        // Показываем окно прогресса обработки товара
2531        showProductProgress();
2532
2533        try {
2534            // Проверяем, на какой странице мы находимся
2535            const currentUrl = window.location.href;
2536            
2537            if (currentUrl.includes('/edit/general-info')) {
2538                // Шаг 1: Получаем название товара
2539                updateProductProgress('Получение информации о товаре', 'active');
2540                console.log('Ozon Description Generator: Ожидаем загрузки страницы товара');
2541                
2542                await new Promise(resolve => setTimeout(resolve, 5000));
2543                
2544                let title = '';
2545                let attempts = 0;
2546                const maxAttempts = 20;
2547                
2548                while (attempts < maxAttempts) {
2549                    const titleInput = document.querySelector('input[name="name"]');
2550                    if (titleInput && titleInput.value.trim()) {
2551                        title = titleInput.value;
2552                        console.log('Ozon Description Generator: Название товара найдено:', title);
2553                        break;
2554                    }
2555                    console.log('Ozon Description Generator: Попытка', attempts + 1, '- название еще не загружено');
2556                    await new Promise(resolve => setTimeout(resolve, 500));
2557                    attempts++;
2558                }
2559                
2560                if (!title) {
2561                    throw new Error('Не удалось загрузить название товара');
2562                }
2563                
2564                // Сохраняем название
2565                localStorage.setItem(`ozon_autogen_title_${checkId}`, title);
2566                
2567                // Переходим на all-attrs
2568                console.log('Ozon Description Generator: Название загружено, переходим на all-attrs');
2569                const newUrl = currentUrl.replace('/edit/general-info', '/edit/all-attrs');
2570                window.location.href = newUrl;
2571                return;
2572            }
2573            
2574            // Если мы на странице all-attrs - продолжаем обработку
2575            if (currentUrl.includes('/edit/all-attrs')) {
2576                // Получаем сохраненное название
2577                const title = localStorage.getItem(`ozon_autogen_title_${checkId}`) || autoCheck.title;
2578                console.log('Ozon Description Generator: Используем название:', title);
2579                
2580                updateProductProgress('Получение информации о товаре', 'active');
2581                await new Promise(resolve => setTimeout(resolve, 5000));
2582                
2583                const productInfo = { title };
2584                updateProductProgress('Получение информации о товаре', 'completed');
2585                
2586                // Очищаем временное сохранение названия
2587                localStorage.removeItem(`ozon_autogen_title_${checkId}`);
2588                
2589                // Проверяем тестовый режим
2590                const testMode = localStorage.getItem('ozon_autogen_test_mode') === 'true';
2591                const sendToRich = localStorage.getItem('ozon_autogen_send_to_rich') === 'true';
2592                
2593                if (testMode) {
2594                    console.log('Ozon Description Generator: 🧪 ТЕСТОВЫЙ РЕЖИМ - пропускаем AI, вставляем "тест"');
2595                    
2596                    updateProductProgress('Подбор ключевых слов', 'completed');
2597                    updateProductProgress('Генерация описания', 'completed');
2598                    
2599                    updateProductProgress('Вставка описания', 'active');
2600                    const proseMirrorDiv = document.querySelector('[id="attribute#4191"] .ProseMirror');
2601                    if (proseMirrorDiv) {
2602                        proseMirrorDiv.innerHTML = '<p>тест</p>';
2603                        proseMirrorDiv.dispatchEvent(new Event('input', { bubbles: true }));
2604                        proseMirrorDiv.dispatchEvent(new Event('change', { bubbles: true }));
2605                        console.log('Ozon Description Generator: 🧪 Тестовое описание "тест" вставлено');
2606                        updateProductProgress('Вставка описания', 'completed');
2607                    }
2608                    
2609                    if (sendToRich) {
2610                        updateProductProgress('Отправка в Rich-контент', 'active');
2611                        await GM.setValue('ozon_description_for_rich', 'тест');
2612                        
2613                        const mediaTabButton = Array.from(document.querySelectorAll('button.c9s110-a3.c9s110-a0.c9s110-a.c9s110-a4.c9s110-a6')).find(btn => 
2614                            btn.textContent && btn.textContent.includes('Медиа')
2615                        );
2616                        
2617                        if (mediaTabButton) {
2618                            console.log('Ozon Description Generator: Переходим на вкладку Медиа');
2619                            mediaTabButton.click();
2620                            await new Promise(resolve => setTimeout(resolve, 1000));
2621                            await insertRichContent();
2622                        }
2623                        updateProductProgress('Отправка в Rich-контент', 'completed');
2624                    }
2625                    
2626                } else {
2627                    // ОБЫЧНЫЙ РЕЖИМ - весь код с AI
2628                    
2629                    updateProductProgress('Подбор ключевых слов', 'active');
2630                    const globalKeywords = localStorage.getItem('ozon_autogen_global_keywords') || '';
2631                    const customPrompt = localStorage.getItem('ozon_autogen_custom_prompt') || '';
2632                    let keywords = [];
2633                    
2634                    if (globalKeywords) {
2635                        keywords = globalKeywords.split('\n').map(k => k.trim()).filter(k => k);
2636                        console.log('Ozon Description Generator: Используем глобальные ключевые слова:', keywords);
2637                        updateProductProgress('Подбор ключевых слов', 'completed');
2638                    } else {
2639                        console.log('Ozon Description Generator: AI подбирает ключевые слова');
2640                        const suggestPrompt = generateMasksPrompt(productInfo);
2641                        const suggestResponse = await RM.aiCall(suggestPrompt);
2642                        const suggestData = JSON.parse(suggestResponse);
2643                        keywords = [...(suggestData.masks || []), ...(suggestData.keywords || [])];
2644                        console.log('Ozon Description Generator: AI подобрал', keywords.length, 'ключевых слов');
2645                        updateProductProgress('Подбор ключевых слов', 'completed');
2646                    }
2647                    
2648                    updateProductProgress('Сбор данных из аналитики', 'active');
2649                    const analyticsData = await collectAnalyticsData(keywords, []);
2650                    
2651                    if (analyticsData.length === 0) {
2652                        throw new Error('Не удалось собрать данные из аналитики');
2653                    }
2654                    console.log('Ozon Description Generator: Данные из аналитики собраны');
2655                    updateProductProgress('Сбор данных из аналитики', 'completed');
2656                    
2657                    updateProductProgress('Генерация описания', 'active');
2658                    const allQueries = [];
2659                    const queryPopularity = {};
2660                    analyticsData.forEach(data => {
2661                        data.queries.forEach(q => {
2662                            allQueries.push(q.query);
2663                            queryPopularity[q.query.toLowerCase()] = q.popularity;
2664                        });
2665                    });
2666                    
2667                    console.log('Ozon Description Generator: Генерируем описание с', allQueries.length, 'запросами');
2668                    const descriptionPrompt = generateDescriptionPrompt(productInfo, keywords, allQueries, queryPopularity, customPrompt);
2669                    const description = await RM.aiCall(descriptionPrompt);
2670                    console.log('Ozon Description Generator: Описание сгенерировано, длина:', description.length);
2671                    updateProductProgress('Генерация описания', 'completed');
2672                    
2673                    updateProductProgress('Вставка описания', 'active');
2674                    const proseMirrorDiv = document.querySelector('[id="attribute#4191"] .ProseMirror');
2675                    if (proseMirrorDiv) {
2676                        proseMirrorDiv.innerHTML = `<p>${description.replace(/\n\n/g, '</p><p>').replace(/\n/g, '<br>')}</p>`;
2677                        proseMirrorDiv.dispatchEvent(new Event('input', { bubbles: true }));
2678                        proseMirrorDiv.dispatchEvent(new Event('change', { bubbles: true }));
2679                        console.log('Ozon Description Generator: Описание вставлено');
2680                        updateProductProgress('Вставка описания', 'completed');
2681                    }
2682                    
2683                    if (sendToRich) {
2684                        updateProductProgress('Отправка в Rich-контент', 'active');
2685                        await GM.setValue('ozon_description_for_rich', description);
2686                        
2687                        const mediaTabButton = Array.from(document.querySelectorAll('button.c9s110-a3.c9s110-a0.c9s110-a.c9s110-a4.c9s110-a6')).find(btn => 
2688                            btn.textContent && btn.textContent.includes('Медиа')
2689                        );
2690                        
2691                        if (mediaTabButton) {
2692                            console.log('Ozon Description Generator: Переходим на вкладку Медиа');
2693                            mediaTabButton.click();
2694                            await new Promise(resolve => setTimeout(resolve, 1000));
2695                            await insertRichContent();
2696                        }
2697                        updateProductProgress('Отправка в Rich-контент', 'completed');
2698                    }
2699                }
2700                
2701                // Ждем 3 секунды перед сохранением
2702                console.log('Ozon Description Generator: Ожидание 3 секунды перед сохранением');
2703                await new Promise(resolve => setTimeout(resolve, 3000));
2704                
2705                // Сохраняем товар
2706                updateProductProgress('Сохранение товара', 'active');
2707                await new Promise(resolve => setTimeout(resolve, 3000));
2708                
2709                const buttons = Array.from(document.querySelectorAll('button'));
2710                const moderationButton = buttons.find(btn => 
2711                    btn.textContent && btn.textContent.includes('Отправить на модерацию')
2712                );
2713                
2714                if (moderationButton) {
2715                    console.log('Ozon Description Generator: Кнопка "Отправить на модерацию" найдена');
2716                    
2717                    const clickEvent = new MouseEvent('click', {
2718                        view: window,
2719                        bubbles: true,
2720                        cancelable: true
2721                    });
2722                    moderationButton.dispatchEvent(clickEvent);
2723                    
2724                    console.log('Ozon Description Generator: Клик выполнен, товар отправлен на модерацию');
2725                    updateProductProgress('Сохранение товара', 'completed');
2726                    
2727                    // Сохраняем успешный результат
2728                    setAutoResult(checkId, true);
2729                    
2730                    hideProductProgress();
2731                    
2732                    // Ждем полсекунды перед закрытием вкладки
2733                    console.log('Ozon Description Generator: Ожидание 0.5 секунды перед закрытием вкладки');
2734                    await new Promise(resolve => setTimeout(resolve, 500));
2735                    
2736                    console.log('Ozon Description Generator: Закрываем вкладку товара');
2737                    window.close();
2738                } else {
2739                    throw new Error('Кнопка "Отправить на модерацию" не найдена');
2740                }
2741            }
2742            
2743        } catch (error) {
2744            console.error('Ozon Description Generator: Ошибка при обработке товара:', error);
2745            updateProductProgress('Ошибка', 'error');
2746            
2747            // Очищаем временное сохранение названия
2748            localStorage.removeItem(`ozon_autogen_title_${checkId}`);
2749            
2750            // Сохраняем результат с ошибкой
2751            setAutoResult(checkId, false, error.message);
2752            
2753            console.log('Ozon Description Generator: Ошибка, закрываем вкладку через 3 секунды');
2754            
2755            setTimeout(() => {
2756                hideProductProgress();
2757                window.close();
2758            }, 3000);
2759        }
2760    }
2761
2762    // ============================================
2763    // ИНИЦИАЛИЗАЦИЯ
2764    // ============================================
2765
2766    function init() {
2767        console.log('Ozon Description Generator: Инициализация');
2768        
2769        // Страница списка товаров
2770        if (window.location.href === 'https://seller.ozon.ru/app/products' || 
2771            (window.location.href.includes('seller.ozon.ru/app/products') && !window.location.href.includes('/edit/'))) {
2772            console.log('Ozon Description Generator: Страница списка товаров');
2773            
2774            // Постоянно следим за кнопкой автогенерации
2775            const debouncedCreateButton = debounce(createAutogenButton, 500);
2776            
2777            const observer = new MutationObserver(() => {
2778                debouncedCreateButton();
2779            });
2780            
2781            observer.observe(document.body, {
2782                childList: true,
2783                subtree: true
2784            });
2785            
2786            setTimeout(createAutogenButton, 2000);
2787            
2788            // Проверяем, нужно ли продолжить автогенерацию - СРАЗУ и через интервалы
2789            checkAndContinueAutogen();
2790            
2791            // Проверяем каждые 2 секунды, нужно ли продолжить
2792            const autogenCheckInterval = setInterval(async () => {
2793                const autogenStatus = await GM.getValue('ozon_autogen_status', 'stopped');
2794                if (autogenStatus === 'running') {
2795                    console.log('Ozon Description Generator: Обнаружена активная автогенерация, продолжаем');
2796                    clearInterval(autogenCheckInterval);
2797                    showAutogenProgress();
2798                    setTimeout(processNextProduct, 3000);
2799                }
2800            }, 2000);
2801            
2802            // Останавливаем проверку через 30 секунд
2803            setTimeout(() => clearInterval(autogenCheckInterval), 30000);
2804        }
2805        
2806        // Страница редактирования товара
2807        if (window.location.href.includes('seller.ozon.ru/app/products/') && 
2808            (window.location.href.includes('/edit/all-attrs') || window.location.href.includes('/edit/general-info'))) {
2809            console.log('Ozon Description Generator: Страница редактирования товара');
2810            
2811            // Проверяем, идет ли автогенерация
2812            checkAndStartAutogen();
2813            
2814            const observer = new MutationObserver((mutations, obs) => {
2815                const annotationContainer = document.querySelector('[id="attribute#4191"]');
2816                if (annotationContainer) {
2817                    createGeneratorButton();
2818                    obs.disconnect();
2819                }
2820            });
2821            
2822            observer.observe(document.body, {
2823                childList: true,
2824                subtree: true
2825            });
2826            
2827            setTimeout(createGeneratorButton, 2000);
2828        }
2829        
2830        if (window.location.href.includes('seller.ozon.ru/app/products/') && window.location.href.includes('/edit/media')) {
2831            console.log('Ozon Description Generator: Страница медиа');
2832            setTimeout(insertRichContent, 2000);
2833        }
2834        
2835        if (window.location.href.includes('seller.ozon.ru/app/analytics/what-to-sell/all-queries')) {
2836            setTimeout(autoCollectOnAnalyticsPage, 2000);
2837        }
2838    }
2839    
2840    // Проверка и продолжение автогенерации на странице списка
2841    async function checkAndContinueAutogen() {
2842        console.log('Ozon Description Generator: Проверяем необходимость продолжения автогенерации');
2843        
2844        // Проверяем, есть ли активная задача
2845        const autoCheck = getAutoCheck();
2846        
2847        if (autoCheck && isTimestampFresh(autoCheck.timestamp)) {
2848            console.log('Ozon Description Generator: Найдена активная задача автогенерации');
2849            showAutogenProgress();
2850            
2851            // Ждем результата
2852            await waitForAutoResult(autoCheck.checkId);
2853        }
2854    }
2855    
2856    // Проверка и запуск автогенерации
2857    async function checkAndStartAutogen() {
2858        // Проверяем, есть ли ожидаемая задача для этой вкладки
2859        const expectedKeys = Object.keys(localStorage).filter(key => key.startsWith('wbAutoExpected_'));
2860        
2861        if (expectedKeys.length > 0) {
2862            console.log('Ozon Description Generator: Обнаружена задача автогенерации, запускаем обработку');
2863            await handleProductPageAutogen();
2864        }
2865    }
2866
2867    // Показ окна прогресса автогенерации
2868    async function showAutogenProgress() {
2869        if (document.querySelector('.ozon-autogen-progress')) {
2870            return;
2871        }
2872
2873        const progressDiv = document.createElement('div');
2874        progressDiv.className = 'ozon-autogen-progress';
2875        progressDiv.innerHTML = `
2876            <div class="ozon-autogen-progress-header">
2877                <span>🤖 Автогенерация</span>
2878                <span class="ozon-autogen-progress-close" id="ozon-autogen-progress-close">×</span>
2879            </div>
2880            <div class="ozon-autogen-progress-info" id="ozon-autogen-progress-info">
2881                Ожидание...
2882            </div>
2883            <div class="ozon-autogen-progress-stats">
2884                <div class="ozon-autogen-progress-stat">
2885                    <div class="ozon-autogen-progress-stat-label">Обработано</div>
2886                    <div class="ozon-autogen-progress-stat-value success" id="ozon-autogen-processed">0</div>
2887                </div>
2888                <div class="ozon-autogen-progress-stat">
2889                    <div class="ozon-autogen-progress-stat-label">Ошибок</div>
2890                    <div class="ozon-autogen-progress-stat-value error" id="ozon-autogen-errors">0</div>
2891                </div>
2892            </div>
2893            <div class="ozon-autogen-progress-controls">
2894                <button class="ozon-autogen-progress-btn pause" id="ozon-autogen-pause-btn">⏸ Пауза</button>
2895                <button class="ozon-autogen-progress-btn stop" id="ozon-autogen-stop-btn">⏹ Остановить</button>
2896            </div>
2897        `;
2898
2899        document.body.appendChild(progressDiv);
2900
2901        document.getElementById('ozon-autogen-progress-close').addEventListener('click', () => {
2902            progressDiv.remove();
2903        });
2904
2905        document.getElementById('ozon-autogen-pause-btn').addEventListener('click', toggleAutogenPause);
2906        document.getElementById('ozon-autogen-stop-btn').addEventListener('click', stopAutogeneration);
2907
2908        // Обновляем прогресс каждую секунду
2909        setInterval(updateAutogenProgress, 1000);
2910    }
2911
2912    // Обновление прогресса автогенерации
2913    async function updateAutogenProgress() {
2914        const progressDiv = document.querySelector('.ozon-autogen-progress');
2915        if (!progressDiv) return;
2916
2917        const processed = parseInt(localStorage.getItem('ozon_autogen_processed') || '0');
2918        const errors = parseInt(localStorage.getItem('ozon_autogen_errors') || '0');
2919        const currentProduct = localStorage.getItem('ozon_autogen_current_product') || '';
2920
2921        const processedEl = document.getElementById('ozon-autogen-processed');
2922        const errorsEl = document.getElementById('ozon-autogen-errors');
2923        const infoEl = document.getElementById('ozon-autogen-progress-info');
2924
2925        if (processedEl) processedEl.textContent = processed;
2926        if (errorsEl) errorsEl.textContent = errors;
2927        if (infoEl) {
2928            if (currentProduct) {
2929                infoEl.textContent = `Обработка: ${currentProduct}`;
2930            } else {
2931                infoEl.textContent = 'Ожидание...';
2932            }
2933        }
2934    }
2935
2936    // Переключение паузы автогенерации
2937    async function toggleAutogenPause() {
2938        // Пауза больше не нужна в новой логике
2939        console.log('Ozon Description Generator: Пауза не поддерживается в новой версии');
2940    }
2941
2942    // Остановка автогенерации
2943    async function stopAutogeneration() {
2944        console.log('Ozon Description Generator: Автогенерация остановлена');
2945        
2946        // Очищаем все данные автогенерации
2947        const autoCheck = getAutoCheck();
2948        if (autoCheck) {
2949            clearAutoCheck(autoCheck.checkId);
2950        }
2951        
2952        localStorage.removeItem('ozon_autogen_global_keywords');
2953        localStorage.removeItem('ozon_autogen_send_to_rich');
2954        localStorage.removeItem('ozon_autogen_test_mode');
2955        localStorage.removeItem('ozon_autogen_current_product');
2956        
2957        const progressDiv = document.querySelector('.ozon-autogen-progress');
2958        if (progressDiv) {
2959            setTimeout(() => progressDiv.remove(), 2000);
2960        }
2961    }
2962
2963    if (document.readyState === 'loading') {
2964        document.addEventListener('DOMContentLoaded', init);
2965    } else {
2966        init();
2967    }
2968
2969})();
Ozon Description Generator 3.0 | Robomonkey