Ozon AI Answer Generator

AI-powered answer generator for Ozon seller questions

Size

128.6 KB

Version

1.3.67

Created

Mar 25, 2026

Updated

22 days ago

1// ==UserScript==
2// @name		Ozon AI Answer Generator
3// @description		AI-powered answer generator for Ozon seller questions
4// @version		1.3.67
5// @match		https://*.seller.ozon.ru/*
6// @icon		https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.xmlhttpRequest
10// @grant		none
11// @require		https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
12// ==/UserScript==
13(function() {
14    'use strict';
15
16    TM_log('Ozon AI Answer Generator initialized');
17
18    // Debounce функция для оптимизации
19    function debounce(func, wait) {
20        let timeout;
21        return function executedFunction(...args) {
22            const later = () => {
23                clearTimeout(timeout);
24                func(...args);
25            };
26            clearTimeout(timeout);
27            timeout = setTimeout(later, wait);
28        };
29    }
30
31    // Функция для добавления стилей
32    function addStyles() {
33        const styles = `
34            .ai-generator-container {
35                margin: 16px 0;
36                padding: 16px;
37                background: #f5f5f7;
38                border-radius: 8px;
39                border: 1px solid #e0e0e0;
40            }
41            
42            .ai-generator-title {
43                font-size: 14px;
44                font-weight: 600;
45                margin-bottom: 12px;
46                color: #1a1a1a;
47            }
48            
49            .ai-prompt-input {
50                width: 100%;
51                padding: 10px 12px;
52                border: 1px solid #d1d1d6;
53                border-radius: 6px;
54                font-size: 14px;
55                font-family: inherit;
56                resize: vertical;
57                min-height: 60px;
58                margin-bottom: 12px;
59                box-sizing: border-box;
60            }
61            
62            .ai-prompt-input:focus {
63                outline: none;
64                border-color: #005bff;
65                box-shadow: 0 0 0 3px rgba(0, 91, 255, 0.1);
66            }
67            
68            .ai-generate-btn {
69                background: #005bff;
70                color: white;
71                border: none;
72                padding: 10px 20px;
73                border-radius: 6px;
74                font-size: 14px;
75                font-weight: 500;
76                cursor: pointer;
77                transition: background 0.2s;
78                width: 100%;
79            }
80            
81            .ai-generate-btn:hover {
82                background: #0047cc;
83            }
84            
85            .ai-generate-btn:disabled {
86                background: #d1d1d6;
87                cursor: not-allowed;
88            }
89            
90            .ai-generate-btn.loading {
91                position: relative;
92                color: transparent;
93            }
94            
95            .ai-generate-btn.loading::after {
96                content: '';
97                position: absolute;
98                width: 16px;
99                height: 16px;
100                top: 50%;
101                left: 50%;
102                margin-left: -8px;
103                margin-top: -8px;
104                border: 2px solid #ffffff;
105                border-radius: 50%;
106                border-top-color: transparent;
107                animation: spinner 0.6s linear infinite;
108            }
109            
110            @keyframes spinner {
111                to { transform: rotate(360deg); }
112            }
113            
114            .ai-prompt-hint {
115                font-size: 12px;
116                color: #666;
117                margin-bottom: 8px;
118            }
119            .answer-textarea-container {
120                margin-top: 12px;
121                padding: 12px;
122                background: #f8f9fa;
123                border-radius: 6px;
124                border: 1px solid #dee2e6;
125            }
126            
127            .answer-textarea {
128                width: 100%;
129                min-height: 150px;
130                padding: 8px;
131                border: 1px solid #ced4da;
132                border-radius: 4px;
133                font-size: 13px;
134                font-family: inherit;
135                resize: vertical;
136                box-sizing: border-box;
137            }
138            
139            .answer-textarea:focus {
140                outline: none;
141                border-color: #005bff;
142                box-shadow: 0 0 0 2px rgba(0, 91, 255, 0.1);
143            }
144        `;
145        
146        TM_addStyle(styles);
147    }
148
149    // Функция для создания UI генератора
150    function createGeneratorUI(modal) {
151        // Проверяем, не создан ли уже UI
152        if (modal.querySelector('.ai-generator-container')) {
153            TM_log('AI Generator UI already exists');
154            return;
155        }
156
157        // Находим заголовок "Ответ на вопрос"
158        const answerTitleContainer = modal.querySelector('.mt7');
159        if (!answerTitleContainer) {
160            TM_log('ERROR: Answer title container not found in modal');
161            return;
162        }
163
164        // Находим textarea для ответа (для проверки)
165        const textarea = modal.querySelector('textarea');
166        if (!textarea) {
167            TM_log('ERROR: Textarea not found in modal');
168            return;
169        }
170
171        // Создаем контейнер для AI генератора
172        const container = document.createElement('div');
173        container.className = 'ai-generator-container';
174        
175        container.innerHTML = `
176            <div class="ai-generator-title">🤖 AI Генератор ответов</div>
177            <div class="ai-prompt-hint">Дополнительные инструкции для AI (необязательно):</div>
178            <textarea class="ai-prompt-input" placeholder="Например: Ответь кратко и дружелюбно, упомяни возрастные ограничения..."></textarea>
179            <button class="ai-generate-btn" data-generated="false">Сгенерировать ответ</button>
180        `;
181
182        // Вставляем контейнер после заголовка "Ответ на вопрос"
183        answerTitleContainer.insertAdjacentElement('afterend', container);
184
185        // Добавляем обработчик на кнопку
186        const generateBtn = container.querySelector('.ai-generate-btn');
187        const promptInput = container.querySelector('.ai-prompt-input');
188
189        generateBtn.addEventListener('click', async () => {
190            await generateAnswer(modal, generateBtn, promptInput, textarea);
191        });
192
193        TM_log('AI Generator UI created successfully');
194    }
195
196    // Функция для генерации ответа через AI
197    async function generateAnswer(modal, button, promptInput, textarea) {
198        try {
199            // Отключаем кнопку и показываем загрузку
200            button.disabled = true;
201            button.classList.add('loading');
202
203            // Получаем информацию о продукте
204            const productNameElement = modal.querySelector('.mb1');
205            const productName = productNameElement ? productNameElement.textContent.trim() : 'Неизвестный продукт';
206
207            // Получаем артикул продавца из модального окна - ищем по тексту "Артикул"
208            const articleLabel = Array.from(modal.querySelectorAll('*'))
209                .find(el => el.textContent.trim() === 'Артикул' && el.children.length === 0);
210            const articleContainer = articleLabel ? articleLabel.parentElement : null;
211            
212            // Ищем следующий элемент с текстом (это будет значение артикула)
213            let sellerArticle = '';
214            if (articleContainer) {
215                const allDivs = Array.from(articleContainer.querySelectorAll('div'));
216                const articleValueDiv = allDivs.find(div => 
217                    div.textContent.trim() !== 'Артикул' && 
218                    div.textContent.trim().length > 0 &&
219                    div.children.length === 0
220                );
221                sellerArticle = articleValueDiv ? articleValueDiv.textContent.trim() : '';
222            }
223
224            TM_log('=== ГЕНЕРАЦИЯ ОТВЕТА ===');
225            TM_log('Артикул продавца:', sellerArticle);
226
227            // Получаем текст вопроса - ищем по тексту "Вопрос"
228            const questionLabel = Array.from(modal.querySelectorAll('*'))
229                .find(el => el.textContent.trim() === 'Вопрос' && el.children.length === 0);
230            
231            if (!questionLabel) {
232                TM_log('ERROR: Question label not found');
233                throw new Error('Не удалось найти метку "Вопрос"');
234            }
235            
236            // Находим родительский контейнер
237            const questionContainer = questionLabel.parentElement;
238            if (!questionContainer) {
239                TM_log('ERROR: Question container not found');
240                throw new Error('Не удалось найти контейнер вопроса');
241            }
242            
243            // Ищем текст вопроса - это следующий div с текстом
244            const allDivs = Array.from(questionContainer.querySelectorAll('div'));
245            const questionTextDiv = allDivs.find(div => 
246                div.textContent.trim() !== 'Вопрос' && 
247                div.textContent.trim().length > 10 &&
248                div.children.length === 0
249            );
250            
251            const questionText = questionTextDiv ? questionTextDiv.textContent.trim() : '';
252
253            if (!questionText) {
254                TM_log('ERROR: Question text not found');
255                TM_log('Question container HTML:', questionContainer.innerHTML);
256                throw new Error('Не удалось найти текст вопроса');
257            }
258
259            TM_log('Название продукта:', productName);
260            TM_log('Текст вопроса:', questionText);
261
262            // Получаем дополнительный промпт
263            const additionalPrompt = promptInput.value.trim();
264            if (additionalPrompt) {
265                TM_log('Дополнительные инструкции:', additionalPrompt);
266            }
267            
268            // Ищем товар в базе знаний по артикулу продавца
269            const productInfo = findProductInKnowledgeBase(sellerArticle);
270            let knowledgeBaseInfo = '';
271            
272            if (productInfo) {
273                knowledgeBaseInfo = '\n\nДополнительная информация о товаре из базы знаний:\n' + formatProductInfo(productInfo);
274                TM_log('✅ ТОВАР НАЙДЕН В БАЗЕ ЗНАНИЙ');
275                TM_log('Информация из базы знаний:', knowledgeBaseInfo);
276            } else {
277                TM_log('❌ ТОВАР НЕ НАЙДЕН В БАЗЕ ЗНАНИЙ');
278                TM_log('Артикул для поиска:', sellerArticle);
279            }
280
281            // Используем кастомный промпт или дефолтный
282            const promptTemplate = getCurrentPrompt();
283            
284            // Заменяем переменные в промпте
285            let mainPrompt = promptTemplate
286                .replace('{productName}', productName)
287                .replace('{questionText}', questionText)
288                .replace('{knowledgeBaseInfo}', knowledgeBaseInfo)
289                .replace('{additionalPrompt}', additionalPrompt);
290
291            TM_log('=== ПРОМПТ ДЛЯ AI ===');
292            TM_log(mainPrompt);
293            TM_log('=== КОНЕЦ ПРОМПТА ===');
294
295            // Вызываем AI
296            const answer = await callAI(mainPrompt);
297
298            TM_log('=== ОТВЕТ ОТ AI ===');
299            TM_log(answer);
300            TM_log('=== КОНЕЦ ОТВЕТА ===');
301
302            // Проверяем валидность ответа
303            if (!isValidAnswer(answer)) {
304                TM_log('⚠️ AI вернул невалидный ответ или SKIP');
305                alert('AI не смог сгенерировать ответ: недостаточно информации о товаре');
306                return;
307            }
308
309            // Вставляем ответ в textarea
310            textarea.value = answer;
311            
312            // Генерируем событие input для обновления состояния формы
313            const inputEvent = new Event('input', { bubbles: true });
314            textarea.dispatchEvent(inputEvent);
315
316            // Меняем текст кнопки на "Перегенерировать"
317            button.textContent = 'Перегенерировать ответ';
318            button.setAttribute('data-generated', 'true');
319
320            TM_log('✅ Ответ успешно сгенерирован и вставлен');
321
322        } catch (error) {
323            TM_log('❌ Ошибка при генерации ответа:', error.message);
324            alert('Ошибка при генерации ответа: ' + error.message);
325        } finally {
326            // Включаем кнопку обратно
327            button.disabled = false;
328            button.classList.remove('loading');
329        }
330    }
331
332    // Функция для проверки, является ли ответ валидным
333    function isValidAnswer(answer) {
334        if (!answer || typeof answer !== 'string') {
335            return false;
336        }
337        
338        const trimmedAnswer = answer.trim().toUpperCase();
339        
340        // Проверяем на SKIP
341        if (trimmedAnswer === 'SKIP') {
342            return false;
343        }
344        
345        // Проверяем на слишком короткий ответ (меньше 10 символов)
346        if (answer.trim().length < 10) {
347            return false;
348        }
349        
350        // Проверяем на типичные фразы отказа
351        const refusalPhrases = [
352            'не могу ответить',
353            'недостаточно информации',
354            'не знаю',
355            'cannot answer',
356            'don\'t know',
357            'insufficient information',
358            'нет информации'
359        ];
360        
361        const lowerAnswer = answer.toLowerCase();
362        for (const phrase of refusalPhrases) {
363            if (lowerAnswer.includes(phrase)) {
364                return false;
365            }
366        }
367        
368        return true;
369    }
370
371    // Наблюдатель за появлением модального окна
372    function observeModal() {
373        const observer = new MutationObserver(debounce(() => {
374            // Ищем модальное окно по тексту заголовка "Вопрос о товаре"
375            const allHeadings = document.querySelectorAll('*');
376            for (const element of allHeadings) {
377                // Проверяем, что это заголовок с текстом "Вопрос о товаре"
378                if (element.textContent.trim() === 'Вопрос о товаре' && 
379                    element.classList.contains('heading-500')) {
380                    // Ищем родительское модальное окно
381                    let modal = element;
382                    while (modal && modal.tagName !== 'BODY') {
383                        // Модальное окно обычно имеет aria-label или role
384                        if (modal.hasAttribute('aria-label') || modal.getAttribute('role') === 'dialog') {
385                            TM_log('Question modal detected by content');
386                            createGeneratorUI(modal);
387                            return;
388                        }
389                        modal = modal.parentElement;
390                    }
391                }
392            }
393        }, 300));
394
395        observer.observe(document.body, {
396            childList: true,
397            subtree: true
398        });
399
400        TM_log('Modal observer started');
401    }
402
403    // Инициализация
404    function init() {
405        TM_log('Starting Ozon AI Answer Generator...');
406        
407        // Добавляем стили
408        addStyles();
409        
410        // Проверяем, открыто ли уже модальное окно
411        const allHeadings = document.querySelectorAll('*');
412        for (const element of allHeadings) {
413            if (element.textContent.trim() === 'Вопрос о товаре' && 
414                element.classList.contains('heading-500')) {
415                let modal = element;
416                while (modal && modal.tagName !== 'BODY') {
417                    if (modal.hasAttribute('aria-label') || modal.getAttribute('role') === 'dialog') {
418                        TM_log('Existing question modal found');
419                        createGeneratorUI(modal);
420                        break;
421                    }
422                    modal = modal.parentElement;
423                }
424                break;
425            }
426        }
427        
428        // Запускаем наблюдатель
429        observeModal();
430    }
431
432    // Запускаем после загрузки DOM
433    if (document.readyState === 'loading') {
434        document.addEventListener('DOMContentLoaded', init);
435    } else {
436        init();
437    }
438
439    // ============= МАССОВАЯ ГЕНЕРАЦИЯ ОТВЕТОВ =============
440    
441    // Хранилище для сгенерированных ответов
442    let generatedAnswers = new Map();
443    
444    // Хранилище базы знаний
445    let knowledgeBase = null;
446
447    // Хранилище промпта
448    let customPrompt = null;
449
450    // Хранилище промпта для отзывов
451    let reviewsPrompt = null;
452
453    // Хранилище настроек модели
454    let modelSettings = {
455        provider: 'rmcall', // 'rmcall' или 'openrouter'
456        model: 'google/gemini-2.0-flash-exp:free',
457        apiKey: '',
458        customModels: [] // Список кастомных моделей
459    };
460
461    // Дефолтный промпт
462    const DEFAULT_PROMPT = 'Ты - профессиональный менеджер по работе с клиентами на маркетплейсе Ozon.\n\n' +
463        'Продукт: {productName}\n\n' +
464        'Вопрос покупателя: {questionText}{knowledgeBaseInfo}\n\n' +
465        'Задача: Сгенерируй профессиональный, вежливый и информативный ответ на вопрос покупателя о товаре.\n\n' +
466        'Требования к ответу:\n' +
467        '- Будь вежливым и дружелюбным\n' +
468        '- Отвечай по существу вопроса\n' +
469        '- Используй информацию о продукте\n' +
470        '- Ответ должен быть кратким (2-4 предложения)\n' +
471        '- Не придумывай характеристики, которых нет в названии продукта\n' +
472        '- Если нужна дополнительная информация, вежливо предложи обратиться к описанию товара\n' +
473        '- Учитывай при ответе Дополнительные инструкции {additionalPrompt} если они есть\n' +
474        '- ВАЖНО: Если у тебя недостаточно информации для ответа или ты не знаешь что ответить, напиши ТОЛЬКО слово "SKIP" без дополнительных объяснений';
475
476    // Дефолтный промпт для отзывов
477    const DEFAULT_REVIEWS_PROMPT = 'Ты - профессиональный менеджер по работе с клиентами на маркетплейсе Ozon.\n\n' +
478        'Продукт: {productName}\n\n' +
479        'Оценка: {rating} звезд\n\n' +
480        'Комментарий покупателя: {comment}{knowledgeBaseInfo}\n\n' +
481        'Задача: Сгенерируй профессиональный, вежливый и благодарный ответ на отзыв покупателя.\n\n' +
482        'Требования к ответу:\n' +
483        '- Будь вежливым и благодарным\n' +
484        '- Если оценка 4-5 звезд и нет комментария - просто поблагодари за высокую оценку\n' +
485        '- Если оценка 3 звезды и ниже без комментария - вырази сожаление и предложи написать в чат для решения проблем\n' +
486        '- Если есть комментарий - отвечай по существу, используя информацию о продукте\n' +
487        '- Ответ должен быть кратким (2-3 предложения)\n' +
488        '- Не придумывай характеристики, которых нет в информации о продукте\n' +
489        '- ВАЖНО: Если у тебя недостаточно информации для ответа, напиши ТОЛЬКО слово "SKIP" без дополнительных объяснений';
490
491    // Функция для загрузки базы знаний из localStorage
492    async function loadKnowledgeBase() {
493        try {
494            const stored = localStorage.getItem('ozon_knowledge_base');
495            if (stored) {
496                knowledgeBase = JSON.parse(stored);
497                TM_log('Knowledge base loaded:', knowledgeBase.length, 'products');
498                updateKnowledgeBaseStatus();
499            }
500        } catch (error) {
501            console.error('Error loading knowledge base:', error);
502        }
503    }
504
505    // Функция для сохранения базы знаний в localStorage
506    function saveKnowledgeBase(data) {
507        try {
508            localStorage.setItem('ozon_knowledge_base', JSON.stringify(data));
509            knowledgeBase = data;
510            TM_log('Knowledge base saved:', data.length, 'products');
511            updateKnowledgeBaseStatus();
512        } catch (error) {
513            console.error('Error saving knowledge base:', error);
514            alert('Ошибка при сохранении базы знаний: ' + error.message);
515        }
516    }
517
518    // Функция для обновления статуса базы знаний
519    function updateKnowledgeBaseStatus() {
520        const statusDiv = document.querySelector('.kb-status');
521        if (statusDiv) {
522            if (knowledgeBase && knowledgeBase.length > 0) {
523                statusDiv.textContent = `База знаний загружена: ${knowledgeBase.length} товаров`;
524                statusDiv.style.color = '#28a745';
525            } else {
526                statusDiv.textContent = 'База знаний не загружена';
527                statusDiv.style.color = '#666';
528            }
529        }
530    }
531
532    // Функция для поиска товара в базе знаний по SKU
533    function findProductInKnowledgeBase(sellerArticle) {
534        if (!knowledgeBase || knowledgeBase.length === 0) {
535            TM_log('Knowledge base is empty');
536            return null;
537        }
538
539        if (!sellerArticle) {
540            TM_log('Seller article is empty');
541            return null;
542        }
543
544        TM_log('Searching for seller article:', sellerArticle);
545
546        // Ищем товар по артикулу - проверяем несколько возможных названий колонок
547        const possibleColumns = [
548            'Штрихкод (Серийный номер / EAN)',
549            'SKU',
550            'Артикул',
551            'Штрихкод',
552            'Серийный номер',
553            'EAN',
554            'Артикул продавца'
555        ];
556        
557        let foundProduct = null;
558        let foundColumn = '';
559        
560        for (const item of knowledgeBase) {
561            for (const column of possibleColumns) {
562                const itemSku = item[column];
563                if (itemSku && itemSku.toString().trim() === sellerArticle) {
564                    foundProduct = item;
565                    foundColumn = column;
566                    break;
567                }
568            }
569            if (foundProduct) break;
570        }
571
572        if (foundProduct) {
573            TM_log(`Product found in knowledge base using column "${foundColumn}"`);
574            TM_log('Product data:', foundProduct);
575        } else {
576            TM_log('Product not found in knowledge base for article:', sellerArticle);
577            TM_log('Available columns in first product:', knowledgeBase[0] ? Object.keys(knowledgeBase[0]) : 'No products');
578        }
579
580        return foundProduct;
581    }
582
583    // Функция для форматирования информации о товаре из базы знаний
584    function formatProductInfo(product) {
585        const info = [];
586        
587        if (product['Название товара']) info.push(`Название: ${product['Название товара']}`);
588        if (product['Бренд*']) info.push(`Бренд: ${product['Бренд*']}`);
589        if (product['Тип*']) info.push(`Тип: ${product['Тип*']}`);
590        if (product['Состав*']) info.push(`Состав: ${product['Состав*']}`);
591        if (product['Аннотация']) info.push(`Описание: ${product['Аннотация']}`);
592        if (product['Целевая аудитория']) info.push(`Целевая аудитория: ${product['Целевая аудитория']}`);
593        if (product['Направление БАД']) info.push(`Направление: ${product['Направление БАД']}`);
594        if (product['Вкусовой акцент (вкус)']) info.push(`Вкус: ${product['Вкусовой акцент (вкус)']}`);
595        if (product['Форма выпуска продукта']) info.push(`Форма выпуска: ${product['Форма выпуска продукта']}`);
596        if (product['Способ применения']) info.push(`Способ применения: ${product['Способ применения']}`);
597        if (product['Срок годности в днях']) info.push(`Срок годности: ${product['Срок годности в днях']} дней`);
598        if (product['Страна-изготовитель']) info.push(`Страна-изготовитель: ${product['Страна-изготовитель']}`);
599        if (product['Для детей']) info.push(`Для детей: ${product['Для детей']}`);
600        if (product['Минимальный возраст от']) info.push(`Минимальный возраст: ${product['Минимальный возраст от']}`);
601        
602        return info.join('\n');
603    }
604
605    // Функция для обработки загрузки XLS файла
606    function handleFileUpload(event) {
607        const file = event.target.files[0];
608        if (!file) return;
609
610        const reader = new FileReader();
611        
612        reader.onload = function(e) {
613            try {
614                const data = new Uint8Array(e.target.result);
615                const workbook = XLSX.read(data, { type: 'array' });
616                
617                // Берем первый лист
618                const firstSheetName = workbook.SheetNames[0];
619                const worksheet = workbook.Sheets[firstSheetName];
620                
621                // Конвертируем в JSON
622                const jsonData = XLSX.utils.sheet_to_json(worksheet);
623                
624                TM_log('XLS parsed:', jsonData.length, 'rows');
625                
626                if (jsonData.length === 0) {
627                    alert('Файл пустой или не содержит данных');
628                    return;
629                }
630                
631                // Сохраняем в localStorage
632                saveKnowledgeBase(jsonData);
633                alert(`База знаний загружена успешно!\nЗагружено товаров: ${jsonData.length}`);
634                
635            } catch (error) {
636                console.error('Error parsing XLS:', error);
637                alert('Ошибка при чтении файла: ' + error.message);
638            }
639        };
640        
641        reader.onerror = function(error) {
642            console.error('Error reading file:', error);
643            alert('Ошибка при чтении файла');
644        };
645        
646        reader.readAsArrayBuffer(file);
647    }
648
649    // Функция для добавления стилей массовой генерации
650    function addBulkGenerationStyles() {
651        const styles = `
652            .bulk-generation-panel {
653                margin: 20px 0;
654                padding: 20px;
655                background: #ffffff;
656                border-radius: 8px;
657                border: 2px solid #005bff;
658                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
659            }
660            
661            .bulk-generation-title {
662                font-size: 16px;
663                font-weight: 600;
664                margin-bottom: 16px;
665                color: #1a1a1a;
666            }
667            
668            .bulk-generation-buttons {
669                display: flex;
670                gap: 12px;
671                margin-bottom: 12px;
672                flex-wrap: wrap;
673            }
674            
675            .bulk-generate-btn, .bulk-answer-all-btn {
676                background: #005bff;
677                color: white;
678                border: none;
679                padding: 12px 24px;
680                border-radius: 6px;
681                font-size: 14px;
682                font-weight: 500;
683                cursor: pointer;
684                transition: background 0.2s;
685            }
686            
687            .bulk-generate-btn:hover, .bulk-answer-all-btn:hover {
688                background: #0047cc;
689            }
690            
691            .bulk-generate-btn:disabled, .bulk-answer-all-btn:disabled {
692                background: #d1d1d6;
693                cursor: not-allowed;
694            }
695            
696            .bulk-answer-all-btn {
697                background: #28a745;
698            }
699            
700            .bulk-answer-all-btn:hover {
701                background: #218838;
702            }
703            
704            .kb-upload-btn {
705                background: #6f42c1;
706                color: white;
707                border: none;
708                padding: 12px 24px;
709                border-radius: 6px;
710                font-size: 14px;
711                font-weight: 500;
712                cursor: pointer;
713                transition: background 0.2s;
714            }
715            
716            .kb-upload-btn:hover {
717                background: #5a32a3;
718            }
719            
720            .prompt-edit-btn {
721                background: #fd7e14;
722                color: white;
723                border: none;
724                padding: 12px 24px;
725                border-radius: 6px;
726                font-size: 14px;
727                font-weight: 500;
728                cursor: pointer;
729                transition: background 0.2s;
730            }
731            
732            .prompt-edit-btn:hover {
733                background: #e8590c;
734            }
735            
736            .model-select-btn {
737                background: #17a2b8;
738                color: white;
739                border: none;
740                padding: 12px 24px;
741                border-radius: 6px;
742                font-size: 14px;
743                font-weight: 500;
744                cursor: pointer;
745                transition: background 0.2s;
746            }
747            
748            .model-select-btn:hover {
749                background: #138496;
750            }
751            
752            .kb-file-input {
753                display: none;
754            }
755            
756            .prompt-modal-overlay {
757                position: fixed;
758                top: 0;
759                left: 0;
760                right: 0;
761                bottom: 0;
762                background: rgba(0, 0, 0, 0.5);
763                display: flex;
764                align-items: center;
765                justify-content: center;
766                z-index: 10000;
767            }
768            
769            .prompt-modal {
770                background: white;
771                border-radius: 8px;
772                width: 90%;
773                max-width: 800px;
774                max-height: 90vh;
775                display: flex;
776                flex-direction: column;
777                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
778            }
779            
780            .prompt-modal-header {
781                display: flex;
782                justify-content: space-between;
783                align-items: center;
784                padding: 20px;
785                border-bottom: 1px solid #e0e0e0;
786            }
787            
788            .prompt-modal-title {
789                margin: 0;
790                font-size: 18px;
791                font-weight: 600;
792                color: #1a1a1a;
793            }
794            
795            .prompt-modal-close {
796                background: none;
797                border: none;
798                font-size: 24px;
799                color: #666;
800                cursor: pointer;
801                padding: 0;
802                width: 30px;
803                height: 30px;
804                display: flex;
805                align-items: center;
806                justify-content: center;
807                border-radius: 4px;
808                transition: background 0.2s;
809            }
810            
811            .prompt-modal-close:hover {
812                background: #f0f0f0;
813            }
814            
815            .prompt-modal-body {
816                padding: 20px;
817                flex: 1;
818                overflow-y: auto;
819            }
820            
821            .prompt-modal-hint {
822                font-size: 13px;
823                color: #666;
824                margin-bottom: 12px;
825                padding: 10px;
826                background: #f8f9fa;
827                border-radius: 4px;
828                border-left: 3px solid #fd7e14;
829            }
830            
831            .prompt-modal-textarea {
832                width: 100%;
833                min-height: 300px;
834                padding: 12px;
835                border: 1px solid #d1d1d6;
836                border-radius: 6px;
837                font-size: 14px;
838                font-family: 'Courier New', monospace;
839                resize: vertical;
840                box-sizing: border-box;
841            }
842            
843            .prompt-modal-textarea:focus {
844                outline: none;
845                border-color: #fd7e14;
846                box-shadow: 0 0 0 3px rgba(253, 126, 20, 0.1);
847            }
848            
849            .prompt-modal-footer {
850                display: flex;
851                justify-content: space-between;
852                align-items: center;
853                padding: 20px;
854                border-top: 1px solid #e0e0e0;
855            }
856            
857            .prompt-modal-actions {
858                display: flex;
859                gap: 12px;
860            }
861            
862            .prompt-modal-reset {
863                background: #6c757d;
864                color: white;
865                border: none;
866                padding: 10px 20px;
867                border-radius: 6px;
868                font-size: 14px;
869                font-weight: 500;
870                cursor: pointer;
871                transition: background 0.2s;
872            }
873            
874            .prompt-modal-reset:hover {
875                background: #5a6268;
876            }
877            
878            .prompt-modal-cancel {
879                background: #f0f0f0;
880                color: #333;
881                border: none;
882                padding: 10px 20px;
883                border-radius: 6px;
884                font-size: 14px;
885                font-weight: 500;
886                cursor: pointer;
887                transition: background 0.2s;
888            }
889            
890            .prompt-modal-cancel:hover {
891                background: #e0e0e0;
892            }
893            
894            .prompt-modal-save {
895                background: #fd7e14;
896                color: white;
897                border: none;
898                padding: 10px 20px;
899                border-radius: 6px;
900                font-size: 14px;
901                font-weight: 500;
902                cursor: pointer;
903                transition: background 0.2s;
904            }
905            
906            .prompt-modal-save:hover {
907                background: #e8590c;
908            }
909            
910            .model-modal-form-group {
911                margin-bottom: 20px;
912            }
913            
914            .model-modal-label {
915                display: block;
916                font-size: 14px;
917                font-weight: 600;
918                color: #1a1a1a;
919                margin-bottom: 8px;
920            }
921            
922            .model-modal-select {
923                width: 100%;
924                padding: 10px 12px;
925                border: 1px solid #d1d1d6;
926                border-radius: 6px;
927                font-size: 14px;
928                font-family: inherit;
929                box-sizing: border-box;
930                background: white;
931            }
932            
933            .model-modal-select:focus {
934                outline: none;
935                border-color: #17a2b8;
936                box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.1);
937            }
938            
939            .model-modal-input {
940                width: 100%;
941                padding: 10px 12px;
942                border: 1px solid #d1d1d6;
943                border-radius: 6px;
944                font-size: 14px;
945                font-family: inherit;
946                box-sizing: border-box;
947            }
948            
949            .model-modal-input:focus {
950                outline: none;
951                border-color: #17a2b8;
952                box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.1);
953            }
954            
955            .model-modal-input:disabled {
956                background: #f5f5f5;
957                cursor: not-allowed;
958            }
959        `;
960        
961        TM_addStyle(styles);
962    }
963
964    // Универсальная функция для поиска контейнера таблицы с вопросами
965    function findQuestionsTableContainer() {
966        console.log('Searching for questions table container...');
967        
968        // Стратегия 1: Ищем таблицу по заголовку колонки "Вопрос"
969        const tables = document.querySelectorAll('table');
970        for (const table of tables) {
971            const headers = table.querySelectorAll('th');
972            for (const header of headers) {
973                if (header.textContent.trim() === 'Вопрос') {
974                    console.log('Found table by "Вопрос" header');
975                    // Ищем самый внешний контейнер с overflow или специфичным классом
976                    let container = table;
977                    let bestContainer = table.parentElement;
978                    
979                    while (container.parentElement && container.parentElement.tagName !== 'BODY') {
980                        container = container.parentElement;
981                        const style = window.getComputedStyle(container);
982                        
983                        // Ищем контейнер с overflow: auto или scroll
984                        if (style.overflow === 'auto' || style.overflow === 'scroll' || 
985                            style.overflowX === 'auto' || style.overflowX === 'scroll' ||
986                            style.overflowY === 'auto' || style.overflowY === 'scroll') {
987                            bestContainer = container;
988                        }
989                    }
990                    
991                    console.log('Found container:', bestContainer.className);
992                    // Возвращаем родительский контейнер, чтобы вставить панель ВНЕ Vue-контейнера
993                    return bestContainer.parentElement || bestContainer;
994                }
995            }
996        }
997        
998        // Стратегия 2: Ищем по data-widget атрибуту
999        const widget = document.querySelector('[data-widget="@seller-ui/reviews"]');
1000        if (widget) {
1001            const table = widget.querySelector('table');
1002            if (table) {
1003                console.log('Found table by data-widget');
1004                // Ищем ближайший контейнер с overflow
1005                let container = table.parentElement;
1006                while (container && container !== widget) {
1007                    const style = window.getComputedStyle(container);
1008                    if (style.overflow === 'auto' || style.overflow === 'scroll' ||
1009                        style.overflowX === 'auto' || style.overflowX === 'scroll') {
1010                        return container.parentElement || container;
1011                    }
1012                    container = container.parentElement;
1013                }
1014                return table.parentElement;
1015            }
1016        }
1017        
1018        // Стратегия 3: Ищем таблицу с tbody, содержащим кнопки с вопросами
1019        for (const table of tables) {
1020            const questionButtons = table.querySelectorAll('tbody button');
1021            if (questionButtons.length > 0) {
1022                console.log('Found table by question buttons');
1023                // Ищем родительский контейнер с overflow
1024                let container = table.parentElement;
1025                while (container && container.tagName !== 'BODY') {
1026                    const style = window.getComputedStyle(container);
1027                    if (style.overflow === 'auto' || style.overflow === 'scroll' ||
1028                        style.overflowX === 'auto' || style.overflowX === 'scroll') {
1029                        return container.parentElement || container;
1030                    }
1031                    container = container.parentElement;
1032                }
1033                return table.parentElement;
1034            }
1035        }
1036        
1037        console.error('Table container not found with any strategy');
1038        return null;
1039    }
1040
1041    // Функция для создания панели массовой генерации
1042    function createBulkGenerationPanel() {
1043        console.log('createBulkGenerationPanel called');
1044        const tableContainer = findQuestionsTableContainer();
1045        console.log('Table container found:', !!tableContainer);
1046        if (!tableContainer) {
1047            console.error('Table container not found');
1048            return;
1049        }
1050
1051        // Проверяем, не создана ли уже панель
1052        const existingPanel = document.querySelector('.bulk-generation-panel');
1053        console.log('Existing panel found:', !!existingPanel);
1054        if (existingPanel) {
1055            console.log('Bulk generation panel already exists');
1056            return;
1057        }
1058
1059        console.log('Creating bulk generation panel...');
1060        const panel = document.createElement('div');
1061        panel.className = 'bulk-generation-panel';
1062        
1063        // Добавляем атрибут для изоляции от Vue
1064        panel.setAttribute('data-v-app', 'false');
1065        
1066        panel.innerHTML = `
1067            <div class="bulk-generation-title">🤖 Массовая генерация ответов</div>
1068            <div class="bulk-generation-buttons">
1069                <button class="bulk-generate-btn">Сгенерировать ответы</button>
1070                <button class="bulk-answer-all-btn" disabled>Ответить всем</button>
1071                <button class="kb-upload-btn">📚 Загрузить базу знаний</button>
1072                <button class="prompt-edit-btn">✏️ Промпт</button>
1073                <button class="model-select-btn">⚙️ Модель</button>
1074                <input type="file" class="kb-file-input" accept=".xls,.xlsx" />
1075            </div>
1076            <div class="bulk-generation-status"></div>
1077            <div class="kb-status">База знаний не загружена</div>
1078        `;
1079
1080        console.log('Inserting panel before table container...');
1081        tableContainer.insertAdjacentElement('beforebegin', panel);
1082        console.log('Panel inserted');
1083
1084        // Добавляем обработчики
1085        const generateBtn = panel.querySelector('.bulk-generate-btn');
1086        const answerAllBtn = panel.querySelector('.bulk-answer-all-btn');
1087        const kbUploadBtn = panel.querySelector('.kb-upload-btn');
1088        const promptEditBtn = panel.querySelector('.prompt-edit-btn');
1089        const kbFileInput = panel.querySelector('.kb-file-input');
1090
1091        generateBtn.addEventListener('click', () => bulkGenerateAnswers());
1092        answerAllBtn.addEventListener('click', () => bulkAnswerAll());
1093        
1094        kbUploadBtn.addEventListener('click', () => {
1095            kbFileInput.click();
1096        });
1097        
1098        promptEditBtn.addEventListener('click', () => {
1099            showPromptModal();
1100        });
1101        
1102        const modelSelectBtn = panel.querySelector('.model-select-btn');
1103        
1104        modelSelectBtn.addEventListener('click', () => {
1105            showModelModal();
1106        });
1107        
1108        kbFileInput.addEventListener('change', handleFileUpload);
1109
1110        console.log('Bulk generation panel created');
1111        
1112        // Загружаем базу знаний из localStorage
1113        loadKnowledgeBase();
1114        
1115        // Загружаем кастомный промпт из localStorage
1116        loadCustomPrompt();
1117    }
1118
1119    // Функция для получения всех видимых вопросов
1120    function getVisibleQuestions() {
1121        // Ищем все строки в tbody, которые содержат ссылку на товар и кнопку с вопросом
1122        const allRows = document.querySelectorAll('tbody tr');
1123        const questions = [];
1124
1125        allRows.forEach((row, index) => {
1126            // Ищем ссылку на товар в 3-й колонке
1127            const productLink = row.querySelector('td:nth-child(3) a');
1128            // Ищем кнопку с вопросом в 4-й колонке
1129            const questionButton = row.querySelector('td:nth-child(4) button');
1130            
1131            // Ищем SKU - это div с классом body-400 в 3-й колонке
1132            const skuElements = row.querySelectorAll('td:nth-child(3) div');
1133            let sku = '';
1134            for (const el of skuElements) {
1135                const text = el.textContent.trim();
1136                // SKU обычно числовой и длиной 8-12 символов
1137                if (text.length >= 8 && text.length <= 12 && /^\d+$/.test(text)) {
1138                    sku = text;
1139                    break;
1140                }
1141            }
1142            
1143            // Получаем количество ответов из 5-й колонки
1144            const answersCountElement = row.querySelector('td:nth-child(5)');
1145            const answersCount = answersCountElement ? parseInt(answersCountElement.textContent.trim()) : 0;
1146            
1147            if (productLink && questionButton) {
1148                questions.push({
1149                    index: index,
1150                    row: row,
1151                    productName: productLink.textContent.trim(),
1152                    questionText: questionButton.textContent.trim(),
1153                    questionButton: questionButton,
1154                    sku: sku,
1155                    answersCount: answersCount
1156                });
1157            }
1158        });
1159
1160        return questions;
1161    }
1162
1163    // Функция для массовой генерации ответов
1164    async function bulkGenerateAnswers() {
1165        const generateBtn = document.querySelector('.bulk-generate-btn');
1166        const answerAllBtn = document.querySelector('.bulk-answer-all-btn');
1167        const statusDiv = document.querySelector('.bulk-generation-status');
1168
1169        try {
1170            generateBtn.disabled = true;
1171            answerAllBtn.disabled = true;
1172            generatedAnswers.clear();
1173
1174            const questions = getVisibleQuestions();
1175            
1176            if (questions.length === 0) {
1177                statusDiv.textContent = 'Нет вопросов для обработки';
1178                return;
1179            }
1180
1181            statusDiv.textContent = `Генерация ответов: 0 из ${questions.length}`;
1182
1183            let skippedCount = 0;
1184            let alreadyAnsweredCount = 0;
1185
1186            for (let i = 0; i < questions.length; i++) {
1187                const question = questions[i];
1188                
1189                try {
1190                    // Пропускаем вопросы, на которые уже есть ответы
1191                    if (question.answersCount > 0) {
1192                        console.log(`Question ${i + 1} skipped: already has ${question.answersCount} answer(s)`);
1193                        alreadyAnsweredCount++;
1194                        continue;
1195                    }
1196                    
1197                    // Подсвечиваем текущий вопрос
1198                    question.row.classList.add('question-row-processing');
1199                    
1200                    statusDiv.textContent = `Генерация ответов: ${i + 1} из ${questions.length}`;
1201
1202                    // Генерируем ответ
1203                    const answer = await generateAnswerForQuestion(question.productName, question.questionText, question.sku);
1204                    
1205                    // Проверяем валидность ответа
1206                    if (!isValidAnswer(answer)) {
1207                        console.log(`Question ${i + 1} skipped: AI returned invalid or SKIP answer`);
1208                        question.row.classList.remove('question-row-processing');
1209                        question.row.classList.add('question-row-error');
1210                        skippedCount++;
1211                        continue;
1212                    }
1213                    
1214                    // Сохраняем ответ
1215                    generatedAnswers.set(i, answer);
1216                    
1217                    // Добавляем textarea с ответом
1218                    addAnswerTextarea(question.row, answer, i);
1219                    
1220                    // Убираем подсветку обработки и добавляем подсветку завершения
1221                    question.row.classList.remove('question-row-processing');
1222                    question.row.classList.add('question-row-completed');
1223                    
1224                    console.log(`Answer generated for question ${i + 1}:`, answer);
1225
1226                } catch (error) {
1227                    console.error(`Error generating answer for question ${i + 1}:`, error);
1228                    TM_log(`ERROR: Error generating answer for question ${i + 1}:`, error.message || error.toString());
1229                    question.row.classList.remove('question-row-processing');
1230                    question.row.classList.add('question-row-error');
1231                    skippedCount++;
1232                    // Пропускаем вопрос при ошибке
1233                }
1234            }
1235
1236            let statusText = `Генерация завершена: ${generatedAnswers.size} из ${questions.length} ответов`;
1237            if (alreadyAnsweredCount > 0) {
1238                statusText += ` (уже отвечено: ${alreadyAnsweredCount})`;
1239            }
1240            if (skippedCount > 0) {
1241                statusText += ` (пропущено: ${skippedCount})`;
1242            }
1243            statusDiv.textContent = statusText;
1244            answerAllBtn.disabled = generatedAnswers.size === 0;
1245
1246        } catch (error) {
1247            console.error('Bulk generation error:', error);
1248            statusDiv.textContent = 'Ошибка при массовой генерации';
1249        } finally {
1250            generateBtn.disabled = false;
1251        }
1252    }
1253
1254    // Функция для генерации ответа на один вопрос
1255    async function generateAnswerForQuestion(productName, questionText, sku) {
1256        // Ищем товар в базе знаний по SKU
1257        const productInfo = findProductInKnowledgeBase(sku);
1258        let knowledgeBaseInfo = '';
1259        
1260        if (productInfo) {
1261            knowledgeBaseInfo = '\n\nДополнительная информация о товаре из базы знаний:\n' + formatProductInfo(productInfo);
1262            console.log('Using knowledge base info for bulk generation');
1263        }
1264        
1265        // Используем кастомный промпт или дефолтный
1266        const promptTemplate = getCurrentPrompt();
1267            
1268        // Заменяем переменные в промпте
1269        const prompt = promptTemplate
1270            .replace('{productName}', productName)
1271            .replace('{questionText}', questionText)
1272            .replace('{knowledgeBaseInfo}', knowledgeBaseInfo)
1273            .replace('{additionalPrompt}', '');
1274
1275        const answer = await callAI(prompt);
1276        return answer;
1277    }
1278
1279    // Функция для добавления textarea с ответом
1280    function addAnswerTextarea(row, answer, index) {
1281        // Проверяем, не добавлен ли уже textarea
1282        const existingContainer = row.querySelector('.answer-textarea-container');
1283        if (existingContainer) {
1284            const textarea = existingContainer.querySelector('.answer-textarea');
1285            textarea.value = answer;
1286            autoResizeTextarea(textarea);
1287            return;
1288        }
1289
1290        const questionCell = row.querySelector('td:nth-child(4)');
1291        if (!questionCell) return;
1292
1293        const container = document.createElement('div');
1294        container.className = 'answer-textarea-container';
1295        
1296        container.innerHTML = `
1297            <div class="answer-label">Сгенерированный ответ:</div>
1298            <textarea class="answer-textarea" data-question-index="${index}">${answer}</textarea>
1299        `;
1300
1301        questionCell.appendChild(container);
1302
1303        // Обновляем значение в Map при редактировании
1304        const textarea = container.querySelector('.answer-textarea');
1305        
1306        // Автоматически изменяем размер textarea
1307        autoResizeTextarea(textarea);
1308        
1309        // Предотвращаем открытие модального окна при клике на textarea или контейнер
1310        container.addEventListener('click', (e) => {
1311            e.stopPropagation();
1312        });
1313        
1314        textarea.addEventListener('click', (e) => {
1315            e.stopPropagation();
1316        });
1317        
1318        textarea.addEventListener('input', () => {
1319            generatedAnswers.set(index, textarea.value);
1320            autoResizeTextarea(textarea);
1321            updateAnswerAllButton();
1322        });
1323        
1324        // Активируем кнопку "Ответить всем"
1325        updateAnswerAllButton();
1326    }
1327    
1328    // Функция для обновления состояния кнопки "Ответить всем"
1329    function updateAnswerAllButton() {
1330        const answerAllBtn = document.querySelector('.bulk-answer-all-btn');
1331        if (!answerAllBtn) return;
1332        
1333        // Проверяем есть ли хотя бы один ответ
1334        const questions = getVisibleQuestions();
1335        let hasAnswers = false;
1336        
1337        for (const question of questions) {
1338            const answerTextarea = question.row.querySelector('.answer-textarea');
1339            if (answerTextarea && answerTextarea.value.trim()) {
1340                hasAnswers = true;
1341                break;
1342            }
1343        }
1344        
1345        answerAllBtn.disabled = !hasAnswers;
1346    }
1347    
1348    // Функция для автоматического изменения размера textarea
1349    function autoResizeTextarea(textarea) {
1350        // Сбрасываем высоту для правильного расчета
1351        textarea.style.height = 'auto';
1352        
1353        // Устанавливаем высоту на основе scrollHeight
1354        const newHeight = Math.max(150, textarea.scrollHeight);
1355        textarea.style.height = newHeight + 'px';
1356        
1357        // Также адаптируем ширину, если текст очень длинный
1358        const lineLength = textarea.value.split('\n').reduce((max, line) => Math.max(max, line.length), 0);
1359        if (lineLength > 100) {
1360            textarea.style.width = '100%';
1361        }
1362    }
1363
1364    // Функция для автоматической отправки всех ответов
1365    async function bulkAnswerAll() {
1366        TM_log('=== BULK ANSWER ALL STARTED ===');
1367        const answerAllBtn = document.querySelector('.bulk-answer-all-btn');
1368        const statusDiv = document.querySelector('.bulk-generation-status');
1369        
1370        TM_log('Button found:', !!answerAllBtn);
1371        TM_log('Status div found:', !!statusDiv);
1372
1373        try {
1374            answerAllBtn.disabled = true;
1375            TM_log('Button disabled');
1376            
1377            const questions = getVisibleQuestions();
1378            TM_log('Questions found:', questions.length);
1379            let successCount = 0;
1380            let totalToSend = 0;
1381
1382            // Сначала подсчитываем сколько ответов нужно отправить
1383            for (let i = 0; i < questions.length; i++) {
1384                const question = questions[i];
1385                const answerTextareaOnPage = question.row.querySelector('.answer-textarea');
1386                if (answerTextareaOnPage && answerTextareaOnPage.value.trim()) {
1387                    totalToSend++;
1388                    TM_log(`Question ${i}: has answer, length: ${answerTextareaOnPage.value.trim().length}`);
1389                }
1390            }
1391
1392            TM_log('Total answers to send:', totalToSend);
1393
1394            if (totalToSend === 0) {
1395                statusDiv.textContent = 'Нет ответов для отправки';
1396                answerAllBtn.disabled = false;
1397                TM_log('No answers to send, exiting');
1398                return;
1399            }
1400
1401            statusDiv.textContent = `Отправка ответов: 0 из ${totalToSend}`;
1402
1403            for (let i = 0; i < questions.length; i++) {
1404                const question = questions[i];
1405                
1406                // Получаем ответ из textarea на странице (не в модальном окне!)
1407                const answerTextareaOnPage = question.row.querySelector('.answer-textarea');
1408                if (!answerTextareaOnPage || !answerTextareaOnPage.value.trim()) {
1409                    TM_log(`Question ${i + 1} skipped: no answer`);
1410                    continue;
1411                }
1412                
1413                const answer = answerTextareaOnPage.value.trim();
1414
1415                try {
1416                    TM_log(`Processing question ${i + 1}, answer length:`, answer.length);
1417                    statusDiv.textContent = `Отправка ответов: ${successCount + 1} из ${totalToSend}`;
1418                    
1419                    // Кликаем на вопрос
1420                    TM_log('About to click question button');
1421                    question.questionButton.click();
1422                    TM_log('Clicked on question button');
1423                    
1424                    // Ждем открытия модального окна
1425                    TM_log('Waiting for modal to open...');
1426                    await waitForModal();
1427                    TM_log('Modal opened');
1428                    
1429                    // Находим модальное окно - ищем div с текстом "Ответ на вопрос"
1430                    const answerSectionTitle = Array.from(document.querySelectorAll('*'))
1431                        .find(el => el.textContent.trim() === 'Ответ на вопрос' && el.tagName === 'DIV');
1432                    
1433                    if (!answerSectionTitle) {
1434                        throw new Error('Answer section not found');
1435                    }
1436                    TM_log('Answer section title found');
1437                    
1438                    // Находим родительский контейнер с классом mt7
1439                    let answerContainer = answerSectionTitle.parentElement;
1440                    if (!answerContainer || !answerContainer.classList.contains('mt7')) {
1441                        throw new Error('Answer container not found');
1442                    }
1443                    TM_log('Answer container found');
1444
1445                    // Находим textarea внутри этого контейнера - ищем по label "Ваш ответ"
1446                    const label = Array.from(answerContainer.querySelectorAll('label'))
1447                        .find(l => l.textContent.trim() === 'Ваш ответ');
1448                    
1449                    if (!label) {
1450                        throw new Error('Label "Ваш ответ" not found');
1451                    }
1452                    
1453                    // Получаем id из атрибута for
1454                    const textareaId = label.getAttribute('for');
1455                    if (!textareaId) {
1456                        throw new Error('Textarea id not found in label');
1457                    }
1458                    TM_log('Textarea id from label:', textareaId);
1459                    
1460                    // Находим textarea по id
1461                    const textareaInModal = document.getElementById(textareaId);
1462                    if (!textareaInModal) {
1463                        throw new Error('Textarea not found by id: ' + textareaId);
1464                    }
1465                    TM_log('Textarea found in modal by id');
1466
1467                    // Вставляем ответ, который мы взяли СО СТРАНИЦЫ
1468                    textareaInModal.value = answer;
1469                    TM_log('Answer set to modal textarea, length:', textareaInModal.value.length);
1470                    
1471                    // Генерируем события для обновления состояния
1472                    const inputEvent = new Event('input', { bubbles: true });
1473                    textareaInModal.dispatchEvent(inputEvent);
1474                    
1475                    const changeEvent = new Event('change', { bubbles: true });
1476                    textareaInModal.dispatchEvent(changeEvent);
1477                    
1478                    // Также пробуем установить фокус и снять его для активации валидации
1479                    textareaInModal.focus();
1480                    textareaInModal.blur();
1481                    
1482                    TM_log('Events dispatched');
1483
1484                    // Ждем немного для обновления UI
1485                    await sleep(1000);
1486
1487                    // Находим кнопку "Отправить ответ" - ищем по тексту внутри кнопки
1488                    const submitButton = Array.from(answerContainer.querySelectorAll('button[type="submit"]'))
1489                        .find(btn => btn.textContent.includes('Отправить ответ'));
1490                    
1491                    if (!submitButton) {
1492                        TM_log('Submit button not found, available buttons:', 
1493                            Array.from(answerContainer.querySelectorAll('button')).map(b => b.textContent));
1494                        throw new Error('Submit button not found');
1495                    }
1496                    TM_log('Submit button found, text:', submitButton.textContent);
1497                    TM_log('Submit button disabled:', submitButton.disabled);
1498
1499                    // Если кнопка все еще disabled, пробуем еще раз обновить textarea
1500                    if (submitButton.disabled) {
1501                        TM_log('Button still disabled, trying to re-trigger events...');
1502                        textareaInModal.value = answer;
1503                        textareaInModal.dispatchEvent(new Event('input', { bubbles: true }));
1504                        textareaInModal.dispatchEvent(new Event('change', { bubbles: true }));
1505                        await sleep(500);
1506                        TM_log('Submit button disabled after retry:', submitButton.disabled);
1507                    }
1508
1509                    submitButton.click();
1510                    TM_log('Submit button clicked');
1511                    
1512                    // Ждем немного после отправки
1513                    await sleep(1500);
1514                    
1515                    // Закрываем модальное окно - ищем кнопку с aria-label "Крестик для закрытия"
1516                    const closeButton = Array.from(document.querySelectorAll('button'))
1517                        .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия');
1518                    
1519                    if (closeButton) {
1520                        closeButton.click();
1521                        TM_log('Close button clicked');
1522                    } else {
1523                        TM_log('Close button not found, trying ESC key');
1524                        // Альтернативный способ - нажатие ESC
1525                        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }));
1526                    }
1527                    
1528                    // Ждем закрытия модального окна
1529                    await waitForModalClose();
1530                    TM_log('Modal closed');
1531                    
1532                    successCount++;
1533                    TM_log(`Answer ${successCount} sent successfully`);
1534
1535                } catch (error) {
1536                    TM_log(`ERROR: Error sending answer for question ${i + 1}:`, error.message);
1537                    console.error('Full error:', error);
1538                    // Закрываем модальное окно при ошибке
1539                    const closeButton = Array.from(document.querySelectorAll('button'))
1540                        .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия');
1541                    if (closeButton) {
1542                        closeButton.click();
1543                    } else {
1544                        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }));
1545                    }
1546                    await sleep(500);
1547                }
1548            }
1549
1550            statusDiv.textContent = `Отправка завершена: ${successCount} из ${totalToSend} ответов отправлено`;
1551            TM_log('=== BULK ANSWER ALL COMPLETED ===');
1552
1553        } catch (error) {
1554            TM_log('ERROR: Bulk answer error:', error.message);
1555            console.error('Full bulk answer error:', error);
1556            statusDiv.textContent = 'Ошибка при отправке ответов';
1557        } finally {
1558            answerAllBtn.disabled = false;
1559            TM_log('Button re-enabled');
1560        }
1561    }
1562
1563    // Вспомогательная функция ожидания модального окна
1564    function waitForModal() {
1565        return new Promise((resolve) => {
1566            const checkModal = () => {
1567                // Ищем div с текстом "Ответ на вопрос"
1568                const answerSection = Array.from(document.querySelectorAll('*'))
1569                    .find(el => el.textContent.trim() === 'Ответ на вопрос' && el.tagName === 'DIV');
1570                
1571                if (answerSection) {
1572                    // Проверяем, что есть textarea с label "Ваш ответ"
1573                    const container = answerSection.parentElement;
1574                    if (container) {
1575                        const label = Array.from(container.querySelectorAll('label'))
1576                            .find(l => l.textContent.trim() === 'Ваш ответ');
1577                        if (label) {
1578                            resolve();
1579                            return;
1580                        }
1581                    }
1582                }
1583                setTimeout(checkModal, 100);
1584            };
1585            checkModal();
1586        });
1587    }
1588
1589    // Вспомогательная функция ожидания закрытия модального окна
1590    function waitForModalClose() {
1591        return new Promise((resolve) => {
1592            const checkModal = () => {
1593                // Проверяем, что модальное окно с вопросом закрыто
1594                const answerSection = Array.from(document.querySelectorAll('*'))
1595                    .find(el => el.textContent.trim() === 'Ответ на вопрос' && el.tagName === 'DIV');
1596                
1597                if (!answerSection) {
1598                    resolve();
1599                    return;
1600                }
1601                
1602                // Проверяем, что есть кнопка закрытия
1603                const closeButton = Array.from(document.querySelectorAll('button'))
1604                    .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия');
1605                
1606                if (closeButton) {
1607                    resolve();
1608                    return;
1609                }
1610                
1611                setTimeout(checkModal, 100);
1612            };
1613            checkModal();
1614        });
1615    }
1616
1617    // Вспомогательная функция задержки
1618    function sleep(ms) {
1619        return new Promise(resolve => setTimeout(resolve, ms));
1620    }
1621
1622    // Функция для загрузки промпта из localStorage
1623    async function loadCustomPrompt() {
1624        try {
1625            const stored = localStorage.getItem('ozon_custom_prompt');
1626            if (stored) {
1627                customPrompt = stored;
1628                console.log('Custom prompt loaded');
1629            }
1630        } catch (error) {
1631            console.error('Error loading custom prompt:', error);
1632        }
1633    }
1634
1635    // Функция для загрузки промпта для отзывов из localStorage
1636    async function loadReviewsPrompt() {
1637        try {
1638            const stored = localStorage.getItem('ozon_reviews_prompt');
1639            if (stored) {
1640                reviewsPrompt = stored;
1641                console.log('Reviews prompt loaded');
1642            }
1643        } catch (error) {
1644            console.error('Error loading reviews prompt:', error);
1645        }
1646    }
1647
1648    // Функция для сохранения промпта в localStorage
1649    function saveCustomPrompt(prompt) {
1650        try {
1651            localStorage.setItem('ozon_custom_prompt', prompt);
1652            customPrompt = prompt;
1653            console.log('Custom prompt saved');
1654        } catch (error) {
1655            console.error('Error saving custom prompt:', error);
1656            alert('Ошибка при сохранении промпта: ' + error.message);
1657        }
1658    }
1659
1660    // Функция для сохранения промпта для отзывов в localStorage
1661    function saveReviewsPrompt(prompt) {
1662        try {
1663            localStorage.setItem('ozon_reviews_prompt', prompt);
1664            reviewsPrompt = prompt;
1665            console.log('Reviews prompt saved');
1666        } catch (error) {
1667            console.error('Error saving reviews prompt:', error);
1668            alert('Ошибка при сохранении промпта для отзывов: ' + error.message);
1669        }
1670    }
1671
1672    // Функция для получения текущего промпта
1673    function getCurrentPrompt() {
1674        return customPrompt || DEFAULT_PROMPT;
1675    }
1676
1677    // Функция для получения текущего промпта для отзывов
1678    function getCurrentReviewsPrompt() {
1679        return reviewsPrompt || DEFAULT_REVIEWS_PROMPT;
1680    }
1681
1682    // Функция для показа модального окна с промптом
1683    function showPromptModal() {
1684        // Проверяем, не открыто ли уже модальное окно
1685        if (document.querySelector('.prompt-modal-overlay')) {
1686            return;
1687        }
1688
1689        const overlay = document.createElement('div');
1690        overlay.className = 'prompt-modal-overlay';
1691        
1692        const modal = document.createElement('div');
1693        modal.className = 'prompt-modal';
1694        
1695        const currentPrompt = getCurrentPrompt();
1696        
1697        modal.innerHTML = `
1698            <div class="prompt-modal-header">
1699                <h3 class="prompt-modal-title">Редактирование промпта</h3>
1700                <button class="prompt-modal-close"></button>
1701            </div>
1702            <div class="prompt-modal-body">
1703                <div class="prompt-modal-hint">
1704                    Используйте переменные: {productName}, {questionText}, {knowledgeBaseInfo}
1705                </div>
1706                <textarea class="prompt-modal-textarea">${currentPrompt}</textarea>
1707            </div>
1708            <div class="prompt-modal-footer">
1709                <button class="prompt-modal-reset">Сбросить на дефолтный</button>
1710                <div class="prompt-modal-actions">
1711                    <button class="prompt-modal-cancel">Отмена</button>
1712                    <button class="prompt-modal-save">Сохранить</button>
1713                </div>
1714            </div>
1715        `;
1716        
1717        overlay.appendChild(modal);
1718        document.body.appendChild(overlay);
1719        
1720        // Обработчики
1721        const closeBtn = modal.querySelector('.prompt-modal-close');
1722        const cancelBtn = modal.querySelector('.prompt-modal-cancel');
1723        const saveBtn = modal.querySelector('.prompt-modal-save');
1724        const resetBtn = modal.querySelector('.prompt-modal-reset');
1725        const textarea = modal.querySelector('.prompt-modal-textarea');
1726        
1727        const closeModal = () => {
1728            overlay.remove();
1729        };
1730        
1731        closeBtn.addEventListener('click', closeModal);
1732        cancelBtn.addEventListener('click', closeModal);
1733        overlay.addEventListener('click', (e) => {
1734            if (e.target === overlay) closeModal();
1735        });
1736        
1737        saveBtn.addEventListener('click', () => {
1738            const newPrompt = textarea.value.trim();
1739            if (newPrompt) {
1740                saveCustomPrompt(newPrompt);
1741                alert('Промпт сохранен успешно!');
1742                closeModal();
1743            } else {
1744                alert('Промпт не может быть пустым');
1745            }
1746        });
1747        
1748        resetBtn.addEventListener('click', () => {
1749            if (confirm('Вы уверены, что хотите сбросить промпт на дефолтный?')) {
1750                textarea.value = DEFAULT_PROMPT;
1751                localStorage.removeItem('ozon_custom_prompt');
1752                customPrompt = null;
1753                alert('Промпт сброшен на дефолтный');
1754            }
1755        });
1756    }
1757
1758    // Функция для показа модального окна с промптом для отзывов
1759    function showReviewsPromptModal() {
1760        // Проверяем, не открыто ли уже модальное окно
1761        if (document.querySelector('.prompt-modal-overlay')) {
1762            return;
1763        }
1764
1765        const overlay = document.createElement('div');
1766        overlay.className = 'prompt-modal-overlay';
1767        
1768        const modal = document.createElement('div');
1769        modal.className = 'prompt-modal';
1770        
1771        const currentPrompt = getCurrentReviewsPrompt();
1772        
1773        modal.innerHTML = `
1774            <div class="prompt-modal-header">
1775                <h3 class="prompt-modal-title">Редактирование промпта для отзывов</h3>
1776                <button class="prompt-modal-close"></button>
1777            </div>
1778            <div class="prompt-modal-body">
1779                <div class="prompt-modal-hint">
1780                    Используйте переменные: {productName}, {rating}, {comment}, {knowledgeBaseInfo}
1781                </div>
1782                <textarea class="prompt-modal-textarea">${currentPrompt}</textarea>
1783            </div>
1784            <div class="prompt-modal-footer">
1785                <button class="prompt-modal-reset">Сбросить на дефолтный</button>
1786                <div class="prompt-modal-actions">
1787                    <button class="prompt-modal-cancel">Отмена</button>
1788                    <button class="prompt-modal-save">Сохранить</button>
1789                </div>
1790            </div>
1791        `;
1792        
1793        overlay.appendChild(modal);
1794        document.body.appendChild(overlay);
1795        
1796        // Обработчики
1797        const closeBtn = modal.querySelector('.prompt-modal-close');
1798        const cancelBtn = modal.querySelector('.prompt-modal-cancel');
1799        const saveBtn = modal.querySelector('.prompt-modal-save');
1800        const resetBtn = modal.querySelector('.prompt-modal-reset');
1801        const textarea = modal.querySelector('.prompt-modal-textarea');
1802        
1803        const closeModal = () => {
1804            overlay.remove();
1805        };
1806        
1807        closeBtn.addEventListener('click', closeModal);
1808        cancelBtn.addEventListener('click', closeModal);
1809        overlay.addEventListener('click', (e) => {
1810            if (e.target === overlay) closeModal();
1811        });
1812        
1813        saveBtn.addEventListener('click', () => {
1814            const newPrompt = textarea.value.trim();
1815            if (newPrompt) {
1816                saveReviewsPrompt(newPrompt);
1817                alert('Промпт для отзывов сохранен успешно!');
1818                closeModal();
1819            } else {
1820                alert('Промпт не может быть пустым');
1821            }
1822        });
1823        
1824        resetBtn.addEventListener('click', () => {
1825            if (confirm('Вы уверены, что хотите сбросить промпт на дефолтный?')) {
1826                textarea.value = DEFAULT_REVIEWS_PROMPT;
1827                localStorage.removeItem('ozon_reviews_prompt');
1828                reviewsPrompt = null;
1829                alert('Промпт сброшен на дефолтный');
1830            }
1831        });
1832    }
1833
1834    // Функция для показа модального окна выбора модели
1835    function showModelModal() {
1836        // Проверяем, не открыто ли уже модальное окно
1837        if (document.querySelector('.prompt-modal-overlay')) {
1838            return;
1839        }
1840
1841        // Загружаем настройки модели из localStorage
1842        loadModelSettings();
1843
1844        const overlay = document.createElement('div');
1845        overlay.className = 'prompt-modal-overlay';
1846        
1847        const modal = document.createElement('div');
1848        modal.className = 'prompt-modal';
1849        
1850        modal.innerHTML = `
1851            <div class="prompt-modal-header">
1852                <h3 class="prompt-modal-title">Настройки AI модели</h3>
1853                <button class="prompt-modal-close"></button>
1854            </div>
1855            <div class="prompt-modal-body">
1856                <div class="model-modal-form-group">
1857                    <label class="model-modal-label">Провайдер:</label>
1858                    <select class="model-modal-select" id="model-provider-select">
1859                        <option value="rmcall" ${modelSettings.provider === 'rmcall' ? 'selected' : ''}>RM Call (стандартный)</option>
1860                        <option value="openrouter" ${modelSettings.provider === 'openrouter' ? 'selected' : ''}>OpenRouter</option>
1861                    </select>
1862                </div>
1863                
1864                <div class="model-modal-form-group" id="model-select-group" style="display: ${modelSettings.provider === 'openrouter' ? 'block' : 'none'};">
1865                    <label class="model-modal-label">
1866                        Модель:
1867                        <button class="model-test-btn" id="test-models-btn" style="margin-left: 10px; padding: 4px 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">
1868                            🔍 Проверить модели
1869                        </button>
1870                    </label>
1871                    <select class="model-modal-select" id="model-name-select" size="8" style="height: auto;">
1872                        ${getModelOptions()}
1873                    </select>
1874                    <div id="model-test-status" style="margin-top: 8px; font-size: 12px; color: #666;"></div>
1875                    <div style="margin-top: 12px; display: flex; gap: 8px;">
1876                        <input type="text" id="custom-model-input" placeholder="Введите ID модели (например, nvidia/nemotron-3-super-120b-a12b:free)" style="flex: 1; padding: 8px; border: 1px solid #d1d1d6; border-radius: 4px; font-size: 13px;" />
1877                        <button id="add-model-btn" style="padding: 8px 16px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">➕ Добавить</button>
1878                        <button id="remove-model-btn" style="padding: 8px 16px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">🗑️ Удалить</button>
1879                    </div>
1880                </div>
1881                
1882                <div class="model-modal-form-group" id="api-key-group" style="display: ${modelSettings.provider === 'openrouter' ? 'block' : 'none'};">
1883                    <label class="model-modal-label">API ключ OpenRouter:</label>
1884                    <input type="password" class="model-modal-input" id="api-key-input" placeholder="Введите API ключ" value="${modelSettings.apiKey || ''}" />
1885                    <div class="prompt-modal-hint" style="margin-top: 8px;">
1886                        Получите бесплатный API ключ на <a href="https://openrouter.ai/keys" target="_blank" style="color: #17a2b8;">openrouter.ai/keys</a>
1887                    </div>
1888                </div>
1889            </div>
1890            <div class="prompt-modal-footer">
1891                <div></div>
1892                <div class="prompt-modal-actions">
1893                    <button class="prompt-modal-cancel">Отмена</button>
1894                    <button class="prompt-modal-save">Сохранить</button>
1895                </div>
1896            </div>
1897        `;
1898        
1899        overlay.appendChild(modal);
1900        document.body.appendChild(overlay);
1901        
1902        // Обработчики
1903        const closeBtn = modal.querySelector('.prompt-modal-close');
1904        const cancelBtn = modal.querySelector('.prompt-modal-cancel');
1905        const saveBtn = modal.querySelector('.prompt-modal-save');
1906        const providerSelect = modal.querySelector('#model-provider-select');
1907        const modelNameSelect = modal.querySelector('#model-name-select');
1908        const apiKeyInput = modal.querySelector('#api-key-input');
1909        const modelSelectGroup = modal.querySelector('#model-select-group');
1910        const apiKeyGroup = modal.querySelector('#api-key-group');
1911        const testModelsBtn = modal.querySelector('#test-models-btn');
1912        
1913        // Устанавливаем текущую модель
1914        if (modelSettings.model) {
1915            modelNameSelect.value = modelSettings.model;
1916        }
1917        
1918        const closeModal = () => {
1919            overlay.remove();
1920        };
1921        
1922        // Обработчик изменения провайдера
1923        providerSelect.addEventListener('change', () => {
1924            const isOpenRouter = providerSelect.value === 'openrouter';
1925            modelSelectGroup.style.display = isOpenRouter ? 'block' : 'none';
1926            apiKeyGroup.style.display = isOpenRouter ? 'block' : 'none';
1927        });
1928        
1929        // Обработчик проверки моделей
1930        testModelsBtn.addEventListener('click', async () => {
1931            await testAllModels(modelNameSelect, apiKeyInput.value.trim());
1932        });
1933        
1934        // Обработчик добавления модели
1935        const addModelBtn = modal.querySelector('#add-model-btn');
1936        const removeModelBtn = modal.querySelector('#remove-model-btn');
1937        const customModelInput = modal.querySelector('#custom-model-input');
1938        
1939        addModelBtn.addEventListener('click', () => {
1940            const modelId = customModelInput.value.trim();
1941            if (!modelId) {
1942                alert('Пожалуйста, введите ID модели');
1943                return;
1944            }
1945            
1946            // Проверяем, не добавлена ли уже эта модель
1947            if (!modelSettings.customModels) {
1948                modelSettings.customModels = [];
1949            }
1950            
1951            if (modelSettings.customModels.includes(modelId)) {
1952                alert('Эта модель уже добавлена');
1953                return;
1954            }
1955            
1956            // Добавляем модель
1957            modelSettings.customModels.push(modelId);
1958            saveModelSettings();
1959            
1960            // Обновляем список моделей
1961            modelNameSelect.innerHTML = getModelOptions();
1962            modelNameSelect.value = modelId;
1963            
1964            customModelInput.value = '';
1965            alert('Модель добавлена успешно!');
1966        });
1967        
1968        removeModelBtn.addEventListener('click', () => {
1969            const selectedModel = modelNameSelect.value;
1970            if (!selectedModel) {
1971                alert('Пожалуйста, выберите модель для удаления');
1972                return;
1973            }
1974            
1975            // Проверяем, является ли модель кастомной
1976            if (!modelSettings.customModels || !modelSettings.customModels.includes(selectedModel)) {
1977                alert('Можно удалять только добавленные вами модели');
1978                return;
1979            }
1980            
1981            if (!confirm(`Вы уверены, что хотите удалить модель "${selectedModel}"?`)) {
1982                return;
1983            }
1984            
1985            // Удаляем модель
1986            modelSettings.customModels = modelSettings.customModels.filter(m => m !== selectedModel);
1987            saveModelSettings();
1988            
1989            // Обновляем список моделей
1990            modelNameSelect.innerHTML = getModelOptions();
1991            
1992            alert('Модель удалена успешно!');
1993        });
1994        
1995        closeBtn.addEventListener('click', closeModal);
1996        cancelBtn.addEventListener('click', closeModal);
1997        overlay.addEventListener('click', (e) => {
1998            if (e.target === overlay) closeModal();
1999        });
2000        
2001        saveBtn.addEventListener('click', () => {
2002            const provider = providerSelect.value;
2003            const model = modelNameSelect.value;
2004            const apiKey = apiKeyInput.value.trim();
2005            
2006            // Валидация
2007            if (provider === 'openrouter' && !apiKey) {
2008                alert('Пожалуйста, введите API ключ для OpenRouter');
2009                return;
2010            }
2011            
2012            // Сохраняем настройки
2013            modelSettings.provider = provider;
2014            modelSettings.model = model;
2015            modelSettings.apiKey = apiKey;
2016            
2017            saveModelSettings();
2018            alert('Настройки модели сохранены успешно!');
2019            closeModal();
2020        });
2021    }
2022
2023    // Функция для тестирования всех моделей
2024    async function testAllModels(selectElement, apiKey) {
2025        if (!apiKey) {
2026            alert('Пожалуйста, введите API ключ для проверки моделей');
2027            return;
2028        }
2029
2030        const statusDiv = document.querySelector('#model-test-status');
2031        const testBtn = document.querySelector('#test-models-btn');
2032        
2033        testBtn.disabled = true;
2034        testBtn.textContent = '⏳ Проверка...';
2035        statusDiv.textContent = 'Проверка моделей...';
2036        
2037        const models = [];
2038        for (let i = 0; i < selectElement.options.length; i++) {
2039            models.push({
2040                value: selectElement.options[i].value,
2041                text: selectElement.options[i].text,
2042                option: selectElement.options[i]
2043            });
2044        }
2045        
2046        let workingCount = 0;
2047        let failedCount = 0;
2048        
2049        for (let i = 0; i < models.length; i++) {
2050            const model = models[i];
2051            statusDiv.textContent = `Проверка ${i + 1} из ${models.length}: ${model.text}`;
2052            
2053            try {
2054                const isWorking = await testModel(model.value, apiKey);
2055                
2056                if (isWorking) {
2057                    model.option.text = '✅ ' + model.text.replace('✅ ', '').replace('❌ ', '');
2058                    workingCount++;
2059                } else {
2060                    model.option.text = '❌ ' + model.text.replace('✅ ', '').replace('❌ ', '');
2061                    failedCount++;
2062                }
2063            } catch (error) {
2064                console.error(`Model ${model.value} test failed:`, error);
2065                model.option.text = '❌ ' + model.text.replace('✅ ', '').replace('❌ ', '');
2066                failedCount++;
2067            }
2068            
2069            // Небольшая задержка между запросами
2070            await sleep(500);
2071        }
2072        
2073        testBtn.disabled = false;
2074        testBtn.textContent = '🔍 Проверить модели';
2075        statusDiv.innerHTML = `<span style="color: #28a745;">✅ Работает: ${workingCount}</span> | <span style="color: #dc3545;">❌ Не работает: ${failedCount}</span>`;
2076    }
2077
2078    // Функция для тестирования одной модели
2079    async function testModel(modelName, apiKey) {
2080        try {
2081            const response = await GM.xmlhttpRequest({
2082                method: 'POST',
2083                url: 'https://openrouter.ai/api/v1/chat/completions',
2084                headers: {
2085                    'Content-Type': 'application/json',
2086                    'Authorization': `Bearer ${apiKey}`,
2087                    'HTTP-Referer': window.location.href,
2088                    'X-Title': 'Ozon AI Answer Generator'
2089                },
2090                data: JSON.stringify({
2091                    model: modelName,
2092                    messages: [
2093                        {
2094                            role: 'user',
2095                            content: 'Ответь одним словом: привет'
2096                        }
2097                    ],
2098                    max_tokens: 10
2099                }),
2100                timeout: 10000
2101            });
2102            
2103            if (response.status === 200) {
2104                const data = JSON.parse(response.responseText);
2105                return !!(data.choices && data.choices[0] && data.choices[0].message);
2106            }
2107            
2108            return false;
2109        } catch (error) {
2110            console.error(`Model ${modelName} test failed:`, error);
2111            return false;
2112        }
2113    }
2114
2115    // Функция для загрузки настроек модели из localStorage
2116    function loadModelSettings() {
2117        try {
2118            const stored = localStorage.getItem('ozon_model_settings');
2119            if (stored) {
2120                const settings = JSON.parse(stored);
2121                modelSettings.provider = settings.provider || 'rmcall';
2122                modelSettings.model = settings.model || 'google/gemini-2.0-flash-exp:free';
2123                modelSettings.apiKey = settings.apiKey || '';
2124                modelSettings.customModels = settings.customModels || [];
2125                console.log('Model settings loaded:', modelSettings);
2126            }
2127        } catch (error) {
2128            console.error('Error loading model settings:', error);
2129        }
2130    }
2131
2132    // Функция для генерации списка моделей
2133    function getModelOptions() {
2134        const defaultModels = [
2135            { value: 'arcee-ai/trinity-large-preview:free', label: 'Arcee Trinity Large (бесплатно)' },
2136            { value: 'openrouter/free', label: 'OpenRouter Free' }
2137        ];
2138        
2139        let options = '';
2140        
2141        // Добавляем дефолтные модели
2142        for (const model of defaultModels) {
2143            options += `<option value="${model.value}">${model.label}</option>`;
2144        }
2145        
2146        // Добавляем кастомные модели
2147        if (modelSettings.customModels && modelSettings.customModels.length > 0) {
2148            options += '<option disabled>──────────</option>';
2149            for (const modelId of modelSettings.customModels) {
2150                options += `<option value="${modelId}">🔧 ${modelId}</option>`;
2151            }
2152        }
2153        
2154        return options;
2155    }
2156
2157    // Функция для сохранения настроек модели в localStorage
2158    function saveModelSettings() {
2159        try {
2160            localStorage.setItem('ozon_model_settings', JSON.stringify(modelSettings));
2161            console.log('Model settings saved:', modelSettings);
2162        } catch (error) {
2163            console.error('Error saving model settings:', error);
2164            alert('Ошибка при сохранении настроек модели: ' + error.message);
2165        }
2166    }
2167
2168    // Универсальная функция для вызова AI
2169    async function callAI(prompt) {
2170        // Загружаем настройки модели
2171        loadModelSettings();
2172        
2173        console.log('Using AI provider:', modelSettings.provider);
2174        console.log('Prompt being sent to AI:', prompt);
2175        
2176        if (modelSettings.provider === 'openrouter') {
2177            // Используем OpenRouter API
2178            if (!modelSettings.apiKey) {
2179                throw new Error('API ключ OpenRouter не настроен. Откройте настройки модели и введите API ключ.');
2180            }
2181            
2182            console.log('Calling OpenRouter API with model:', modelSettings.model);
2183            
2184            try {
2185                const response = await GM.xmlhttpRequest({
2186                    method: 'POST',
2187                    url: 'https://openrouter.ai/api/v1/chat/completions',
2188                    headers: {
2189                        'Content-Type': 'application/json',
2190                        'Authorization': `Bearer ${modelSettings.apiKey}`,
2191                        'HTTP-Referer': window.location.href,
2192                        'X-Title': 'Ozon AI Answer Generator'
2193                    },
2194                    data: JSON.stringify({
2195                        model: modelSettings.model,
2196                        messages: [
2197                            {
2198                                role: 'user',
2199                                content: prompt
2200                            }
2201                        ]
2202                    })
2203                });
2204                
2205                console.log('OpenRouter response status:', response.status);
2206                console.log('OpenRouter response:', response.responseText);
2207                
2208                if (response.status !== 200) {
2209                    console.error('OpenRouter API error:', response);
2210                    throw new Error(`OpenRouter API error: ${response.status} - ${response.statusText}`);
2211                }
2212                
2213                const data = JSON.parse(response.responseText);
2214                
2215                if (!data.choices || !data.choices[0] || !data.choices[0].message) {
2216                    console.error('Invalid OpenRouter response:', data);
2217                    throw new Error('Неверный формат ответа от OpenRouter API');
2218                }
2219                
2220                return data.choices[0].message.content;
2221                
2222            } catch (error) {
2223                console.error('OpenRouter API call failed:', error);
2224                throw new Error(`Ошибка вызова OpenRouter API: ${error.message}`);
2225            }
2226        } else {
2227            // Используем стандартный RM.aiCall
2228            console.log('Calling RM.aiCall');
2229            return await RM.aiCall(prompt);
2230        }
2231    }
2232
2233    // Инициализация массовой генерации
2234    function initBulkGeneration() {
2235        // Проверяем, что мы на странице со списком вопросов
2236        if (!window.location.href.includes('/app/reviews/questions')) {
2237            return;
2238        }
2239
2240        console.log('Initializing bulk generation...');
2241        
2242        addBulkGenerationStyles();
2243        
2244        // Ждем загрузки таблицы
2245        const observer = new MutationObserver(debounce(() => {
2246            const tableContainer = findQuestionsTableContainer();
2247            const existingPanel = document.querySelector('.bulk-generation-panel');
2248            
2249            if (tableContainer) {
2250                // Если панель существует, но находится не перед контейнером таблицы
2251                if (existingPanel) {
2252                    // Проверяем, находится ли панель перед текущим контейнером таблицы
2253                    const panelNextSibling = existingPanel.nextElementSibling;
2254                    if (panelNextSibling !== tableContainer) {
2255                        console.log('Panel found but in wrong position, moving it...');
2256                        tableContainer.insertAdjacentElement('beforebegin', existingPanel);
2257                        console.log('Panel moved to correct position');
2258                    }
2259                } else {
2260                    // Панели нет, создаем новую
2261                    console.log('Panel not found, creating new one...');
2262                    createBulkGenerationPanel();
2263                }
2264            }
2265        }, 300));
2266
2267        observer.observe(document.body, {
2268            childList: true,
2269            subtree: true
2270        });
2271
2272        // Пробуем создать панель сразу
2273        const tableContainer = findQuestionsTableContainer();
2274        if (tableContainer) {
2275            createBulkGenerationPanel();
2276        }
2277    }
2278
2279    // Запускаем инициализацию массовой генерации
2280    initBulkGeneration();
2281
2282    // ============= МАССОВАЯ ГЕНЕРАЦИЯ ОТВЕТОВ НА ОТЗЫВЫ =============
2283    
2284    // Состояние процесса обработки отзывов
2285    let reviewsProcessState = {
2286        isRunning: false,
2287        shouldStop: false,
2288        processedCount: 0,
2289        currentBatch: 0
2290    };
2291
2292    // Функция для добавления стилей для отзывов
2293    function addReviewsGenerationStyles() {
2294        const styles = `
2295            .reviews-generation-panel {
2296                margin: 20px 0;
2297                padding: 20px;
2298                background: #ffffff;
2299                border-radius: 8px;
2300                border: 2px solid #28a745;
2301                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
2302            }
2303            
2304            .reviews-generation-title {
2305                font-size: 16px;
2306                font-weight: 600;
2307                margin-bottom: 16px;
2308                color: #1a1a1a;
2309            }
2310            
2311            .reviews-generation-buttons {
2312                display: flex;
2313                gap: 12px;
2314                margin-bottom: 12px;
2315                flex-wrap: wrap;
2316            }
2317            
2318            .reviews-generate-btn, .reviews-stop-btn {
2319                background: #28a745;
2320                color: white;
2321                border: none;
2322                padding: 12px 24px;
2323                border-radius: 6px;
2324                font-size: 14px;
2325                font-weight: 500;
2326                cursor: pointer;
2327                transition: background 0.2s;
2328            }
2329            
2330            .reviews-generate-btn:hover {
2331                background: #218838;
2332            }
2333            
2334            .reviews-stop-btn {
2335                background: #dc3545;
2336            }
2337            
2338            .reviews-stop-btn:hover {
2339                background: #c82333;
2340            }
2341            
2342            .reviews-generate-btn:disabled, .reviews-stop-btn:disabled {
2343                background: #d1d1d6;
2344                cursor: not-allowed;
2345            }
2346            
2347            .reviews-prompt-edit-btn {
2348                background: #fd7e14;
2349                color: white;
2350                border: none;
2351                padding: 12px 24px;
2352                border-radius: 6px;
2353                font-size: 14px;
2354                font-weight: 500;
2355                cursor: pointer;
2356                transition: background 0.2s;
2357            }
2358            
2359            .reviews-prompt-edit-btn:hover {
2360                background: #e8590c;
2361            }
2362            
2363            .reviews-generation-status {
2364                font-size: 14px;
2365                color: #666;
2366                margin-top: 12px;
2367            }
2368            
2369            .review-row-processing {
2370                background-color: #fff3cd !important;
2371            }
2372            
2373            .review-row-completed {
2374                background-color: #d4edda !important;
2375            }
2376            
2377            .review-row-error {
2378                background-color: #f8d7da !important;
2379            }
2380        `;
2381        
2382        TM_addStyle(styles);
2383    }
2384
2385    // Функция для поиска контейнера таблицы с отзывами
2386    function findReviewsTableContainer() {
2387        console.log('Searching for reviews table container...');
2388        
2389        // Ищем таблицу по заголовку колонки
2390        const tables = document.querySelectorAll('table');
2391        for (const table of tables) {
2392            const headers = table.querySelectorAll('th');
2393            for (const header of headers) {
2394                const headerText = header.textContent.trim();
2395                // Проверяем наличие характерных заголовков для страницы отзывов
2396                if (headerText === 'Товар' || headerText === 'Комментарий' || headerText === 'Оценка') {
2397                    console.log('Found reviews table by header:', headerText);
2398                    let container = table;
2399                    let bestContainer = table.parentElement;
2400                    
2401                    while (container.parentElement && container.parentElement.tagName !== 'BODY') {
2402                        container = container.parentElement;
2403                        const style = window.getComputedStyle(container);
2404                        
2405                        if ((style.overflow === 'auto' || style.overflowX === 'auto') || 
2406                            (container.className && container.className.includes('n1d-'))) {
2407                            bestContainer = container;
2408                        }
2409                    }
2410                    
2411                    console.log('Found container:', bestContainer.className);
2412                    return bestContainer;
2413                }
2414            }
2415        }
2416        
2417        console.error('Reviews table container not found');
2418        return null;
2419    }
2420
2421    // Функция для получения всех видимых отзывов
2422    function getVisibleReviews() {
2423        const allRows = document.querySelectorAll('tbody tr');
2424        const reviews = [];
2425
2426        allRows.forEach((row, index) => {
2427            // Получаем все ячейки
2428            const cells = row.querySelectorAll('td');
2429            
2430            if (cells.length < 10) return;
2431            
2432            // Столбец 3 (индекс 2) - товар
2433            const productCell = cells[2];
2434            const productLink = productCell ? productCell.querySelector('a') : null;
2435            const productName = productLink ? productLink.textContent.trim() : '';
2436            
2437            // Ищем SKU в 3-й колонке
2438            const skuElements = productCell ? productCell.querySelectorAll('div') : [];
2439            let sku = '';
2440            for (const el of skuElements) {
2441                const text = el.textContent.trim();
2442                if (text.length >= 5 && text.length <= 12 && /^\d+$/.test(text)) {
2443                    sku = text;
2444                    break;
2445                }
2446            }
2447            
2448            // Столбец 5 (индекс 4) - комментарий
2449            const commentCell = cells[4];
2450            const comment = commentCell ? commentCell.textContent.trim() : '';
2451            
2452            // Столбец 7 (индекс 6) - рейтинг (звезды)
2453            const ratingCell = cells[6];
2454            const rating = ratingCell ? parseInt(ratingCell.textContent.trim()) : 0;
2455            
2456            // Столбец 10 (индекс 9) - количество ответов
2457            const answersCell = cells[9];
2458            const answersCount = answersCell ? parseInt(answersCell.textContent.trim()) : 0;
2459            
2460            if (productName) {
2461                reviews.push({
2462                    index: index,
2463                    row: row,
2464                    productName: productName,
2465                    comment: comment,
2466                    rating: rating,
2467                    sku: sku,
2468                    answersCount: answersCount
2469                });
2470            }
2471        });
2472
2473        return reviews;
2474    }
2475
2476    // Функция для старта генерации ответов на отзывы
2477    async function startReviewsGeneration() {
2478        TM_log('startReviewsGeneration called');
2479        const generateBtn = document.querySelector('.reviews-generate-btn');
2480        const stopBtn = document.querySelector('.reviews-stop-btn');
2481        const statusDiv = document.querySelector('.reviews-generation-status');
2482
2483        // Если процесс уже запущен - игнорируем
2484        if (reviewsProcessState.isRunning) {
2485            TM_log('Process already running, ignoring');
2486            return;
2487        }
2488
2489        // Если это продолжение после остановки
2490        if (reviewsProcessState.shouldStop) {
2491            reviewsProcessState.shouldStop = false;
2492            generateBtn.textContent = 'Сгенерировать ответы на отзывы';
2493        }
2494
2495        reviewsProcessState.isRunning = true;
2496        reviewsProcessState.shouldStop = false;
2497        generateBtn.disabled = true;
2498        stopBtn.disabled = false;
2499
2500        TM_log('Starting reviews generation process...');
2501
2502        try {
2503            await processReviewsBatch();
2504        } catch (error) {
2505            console.error('Reviews generation error:', error);
2506            statusDiv.textContent = 'Ошибка при генерации ответов на отзывы';
2507        } finally {
2508            reviewsProcessState.isRunning = false;
2509            generateBtn.disabled = false;
2510            stopBtn.disabled = true;
2511        }
2512    }
2513
2514    // Функция для остановки генерации
2515    function stopReviewsGeneration() {
2516        TM_log('stopReviewsGeneration called');
2517        reviewsProcessState.shouldStop = true;
2518        const generateBtn = document.querySelector('.reviews-generate-btn');
2519        const stopBtn = document.querySelector('.reviews-stop-btn');
2520        
2521        generateBtn.textContent = 'Продолжить';
2522        generateBtn.disabled = false;
2523        stopBtn.disabled = true;
2524        
2525        const statusDiv = document.querySelector('.reviews-generation-status');
2526        statusDiv.textContent = `Остановлено. Обработано отзывов: ${reviewsProcessState.processedCount}`;
2527    }
2528
2529    // Функция для создания панели массовой генерации отзывов
2530    function createReviewsGenerationPanel() {
2531        console.log('createReviewsGenerationPanel called');
2532        const tableContainer = findReviewsTableContainer();
2533        console.log('Table container found:', !!tableContainer);
2534        if (!tableContainer) {
2535            console.error('Reviews table container not found');
2536            return;
2537        }
2538
2539        // Проверяем, не создана ли уже панель
2540        const existingPanel = document.querySelector('.reviews-generation-panel');
2541        console.log('Existing panel found:', !!existingPanel);
2542        if (existingPanel) {
2543            console.log('Reviews generation panel already exists');
2544            return;
2545        }
2546
2547        console.log('Creating reviews generation panel...');
2548        const panel = document.createElement('div');
2549        panel.className = 'reviews-generation-panel';
2550        
2551        panel.innerHTML = `
2552            <div class="reviews-generation-title">💬 Массовая генерация ответов на отзывы</div>
2553            <div class="reviews-generation-buttons">
2554                <button class="reviews-generate-btn">Сгенерировать ответы на отзывы</button>
2555                <button class="reviews-stop-btn" disabled>Остановить</button>
2556                <button class="reviews-prompt-edit-btn">✏️ Промпт для отзывов</button>
2557            </div>
2558            <div class="reviews-generation-status"></div>
2559        `;
2560
2561        console.log('Inserting panel before table container...');
2562        tableContainer.insertAdjacentElement('beforebegin', panel);
2563        console.log('Panel inserted');
2564
2565        // Добавляем обработчики
2566        const generateBtn = panel.querySelector('.reviews-generate-btn');
2567        const stopBtn = panel.querySelector('.reviews-stop-btn');
2568        const promptEditBtn = panel.querySelector('.reviews-prompt-edit-btn');
2569
2570        TM_log('Adding event listeners to reviews panel buttons...');
2571        
2572        generateBtn.addEventListener('click', () => {
2573            TM_log('Generate button clicked!');
2574            startReviewsGeneration();
2575        });
2576        
2577        stopBtn.addEventListener('click', () => {
2578            TM_log('Stop button clicked!');
2579            stopReviewsGeneration();
2580        });
2581        
2582        promptEditBtn.addEventListener('click', () => {
2583            TM_log('Prompt edit button clicked!');
2584            showReviewsPromptModal();
2585        });
2586
2587        TM_log('Reviews generation panel created with event listeners');
2588        
2589        // Загружаем промпт для отзывов из localStorage
2590        loadReviewsPrompt();
2591    }
2592
2593    // Функция для генерации ответа на отзыв
2594    async function generateAnswerForReview(productName, rating, comment, sku) {
2595        // Ищем товар в базе знаний по SKU
2596        const productInfo = findProductInKnowledgeBase(sku);
2597        let knowledgeBaseInfo = '';
2598        
2599        if (productInfo) {
2600            knowledgeBaseInfo = '\n\nДополнительная информация о товаре из базы знаний:\n' + formatProductInfo(productInfo);
2601            console.log('Using knowledge base info for review generation');
2602        }
2603        
2604        // Используем кастомный промпт или дефолтный
2605        const promptTemplate = getCurrentReviewsPrompt();
2606        
2607        // Заменяем переменные в промпте
2608        const prompt = promptTemplate
2609            .replace('{productName}', productName)
2610            .replace('{rating}', rating.toString())
2611            .replace('{comment}', comment || 'Нет комментария')
2612            .replace('{knowledgeBaseInfo}', knowledgeBaseInfo);
2613
2614        console.log('=== ПРОМПТ ДЛЯ ОТЗЫВА ===');
2615        console.log(prompt);
2616        console.log('=== КОНЕЦ ПРОМПТА ===');
2617
2618        const answer = await callAI(prompt);
2619        
2620        console.log('=== ОТВЕТ ОТ AI ===');
2621        console.log(answer);
2622        console.log('=== КОНЕЦ ОТВЕТА ===');
2623        
2624        return answer;
2625    }
2626
2627    // Функция ожидания открытия модального окна с отзывом
2628    function waitForReviewModal() {
2629        return new Promise((resolve) => {
2630            const checkModal = () => {
2631                // Ищем модальное окно с отзывом - обычно содержит textarea для ответа
2632                const modal = document.querySelector('[role="dialog"]');
2633                if (modal) {
2634                    const textarea = modal.querySelector('textarea');
2635                    if (textarea) {
2636                        resolve();
2637                        return;
2638                    }
2639                }
2640                setTimeout(checkModal, 100);
2641            };
2642            checkModal();
2643        });
2644    }
2645
2646    // Функция для отправки ответа на отзыв
2647    async function submitReviewAnswer(answer) {
2648        // Находим модальное окно
2649        const modal = document.querySelector('[role="dialog"]');
2650        if (!modal) {
2651            throw new Error('Modal not found');
2652        }
2653        
2654        // Находим textarea
2655        const textarea = modal.querySelector('textarea');
2656        if (!textarea) {
2657            throw new Error('Textarea not found');
2658        }
2659        
2660        // Вставляем ответ
2661        textarea.value = answer;
2662        
2663        // Генерируем события для обновления состояния
2664        const inputEvent = new Event('input', { bubbles: true });
2665        textarea.dispatchEvent(inputEvent);
2666        
2667        const changeEvent = new Event('change', { bubbles: true });
2668        textarea.dispatchEvent(changeEvent);
2669        
2670        textarea.focus();
2671        textarea.blur();
2672        
2673        // Ждем немного для обновления UI
2674        await sleep(1000);
2675        
2676        // Находим кнопку отправки
2677        const submitButton = Array.from(modal.querySelectorAll('button[type="submit"]'))
2678            .find(btn => btn.textContent.includes('Отправить') || btn.textContent.includes('Ответить'));
2679        
2680        if (!submitButton) {
2681            throw new Error('Submit button not found');
2682        }
2683        
2684        // Если кнопка disabled, пробуем еще раз обновить textarea
2685        if (submitButton.disabled) {
2686            textarea.value = answer;
2687            textarea.dispatchEvent(new Event('input', { bubbles: true }));
2688            textarea.dispatchEvent(new Event('change', { bubbles: true }));
2689            await sleep(500);
2690        }
2691        
2692        submitButton.click();
2693        
2694        // Ждем немного после отправки
2695        await sleep(1500);
2696    }
2697
2698    // Функция для закрытия модального окна с отзывом
2699    async function closeReviewModal() {
2700        // Ищем кнопку закрытия
2701        const closeButton = Array.from(document.querySelectorAll('button'))
2702            .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия' || 
2703                         btn.getAttribute('aria-label')?.includes('закры'));
2704        
2705        if (closeButton) {
2706            closeButton.click();
2707        } else {
2708            // Альтернативный способ - нажатие ESC
2709            document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }));
2710        }
2711        
2712        await sleep(500);
2713    }
2714
2715    // Функция ожидания закрытия модального окна с отзывом
2716    function waitForReviewModalClose() {
2717        return new Promise((resolve) => {
2718            const checkModal = () => {
2719                const modal = document.querySelector('[role="dialog"]');
2720                if (!modal) {
2721                    resolve();
2722                    return;
2723                }
2724                setTimeout(checkModal, 100);
2725            };
2726            checkModal();
2727        });
2728    }
2729
2730    // Функция для обработки порции отзывов
2731    async function processReviewsBatch() {
2732        const statusDiv = document.querySelector('.reviews-generation-status');
2733        
2734        while (!reviewsProcessState.shouldStop) {
2735            // Получаем видимые отзывы
2736            const reviews = getVisibleReviews();
2737            
2738            if (reviews.length === 0) {
2739                statusDiv.textContent = `Завершено. Обработано отзывов: ${reviewsProcessState.processedCount}`;
2740                break;
2741            }
2742
2743            // Обрабатываем до 10 отзывов из текущей порции
2744            let processedInBatch = 0;
2745            
2746            for (let i = 0; i < reviews.length && processedInBatch < 10; i++) {
2747                if (reviewsProcessState.shouldStop) {
2748                    break;
2749                }
2750                
2751                const review = reviews[i];
2752                
2753                // Пропускаем отзывы, на которые уже есть ответы
2754                if (review.answersCount > 0) {
2755                    console.log(`Review ${i + 1} skipped: already has ${review.answersCount} answer(s)`);
2756                    continue;
2757                }
2758                
2759                try {
2760                    // Подсвечиваем текущий отзыв
2761                    review.row.classList.add('review-row-processing');
2762                    
2763                    statusDiv.textContent = `Обработка отзыва... Всего обработано: ${reviewsProcessState.processedCount}`;
2764
2765                    // Кликаем на строку чтобы открыть модальное окно
2766                    review.row.click();
2767                    
2768                    // Ждем открытия модального окна
2769                    await waitForReviewModal();
2770                    
2771                    // Генерируем ответ
2772                    const answer = await generateAnswerForReview(review.productName, review.rating, review.comment, review.sku);
2773                    
2774                    // Проверяем валидность ответа
2775                    if (!isValidAnswer(answer)) {
2776                        console.log(`Review ${i + 1} skipped: AI returned invalid or SKIP answer`);
2777                        review.row.classList.remove('review-row-processing');
2778                        review.row.classList.add('review-row-error');
2779                        
2780                        // Закрываем модальное окно
2781                        await closeReviewModal();
2782                        continue;
2783                    }
2784                    
2785                    // Вставляем ответ и отправляем
2786                    await submitReviewAnswer(answer);
2787                    
2788                    // Ждем закрытия модального окна
2789                    await waitForReviewModalClose();
2790                    
2791                    // Убираем подсветку обработки и добавляем подсветку завершения
2792                    review.row.classList.remove('review-row-processing');
2793                    review.row.classList.add('review-row-completed');
2794                    
2795                    reviewsProcessState.processedCount++;
2796                    processedInBatch++;
2797                    
2798                    console.log(`Review ${i + 1} processed successfully`);
2799
2800                } catch (error) {
2801                    console.error(`Error processing review ${i + 1}:`, error);
2802                    review.row.classList.remove('review-row-processing');
2803                    review.row.classList.add('review-row-error');
2804                    
2805                    // Пытаемся закрыть модальное окно при ошибке
2806                    try {
2807                        await closeReviewModal();
2808                    } catch (e) {
2809                        console.error('Error closing modal:', e);
2810                    }
2811                }
2812            }
2813            
2814            // Если обработали меньше 10 отзывов, значит нужно проскроллить
2815            if (processedInBatch < 10 && !reviewsProcessState.shouldStop) {
2816                console.log('Scrolling to load more reviews...');
2817                
2818                // Скроллим вниз
2819                const tableContainer = findReviewsTableContainer();
2820                if (tableContainer) {
2821                    tableContainer.scrollTop = tableContainer.scrollHeight;
2822                }
2823                
2824                // Ждем загрузки новых отзывов
2825                await sleep(2000);
2826                
2827                // Проверяем, появились ли новые отзывы
2828                const newReviews = getVisibleReviews();
2829                if (newReviews.length === reviews.length) {
2830                    // Новых отзывов не появилось, завершаем
2831                    statusDiv.textContent = `Завершено. Обработано отзывов: ${reviewsProcessState.processedCount}`;
2832                    break;
2833                }
2834            }
2835        }
2836        
2837        if (reviewsProcessState.shouldStop) {
2838            statusDiv.textContent = `Остановлено. Обработано отзывов: ${reviewsProcessState.processedCount}`;
2839        }
2840    }
2841
2842    // Функция для старта генерации ответов на отзывы
2843    async function startReviewsGeneration() {
2844        TM_log('startReviewsGeneration called');
2845        const generateBtn = document.querySelector('.reviews-generate-btn');
2846        const stopBtn = document.querySelector('.reviews-stop-btn');
2847        const statusDiv = document.querySelector('.reviews-generation-status');
2848
2849        // Если процесс уже запущен - игнорируем
2850        if (reviewsProcessState.isRunning) {
2851            TM_log('Process already running, ignoring');
2852            return;
2853        }
2854
2855        // Если это продолжение после остановки
2856        if (reviewsProcessState.shouldStop) {
2857            reviewsProcessState.shouldStop = false;
2858            generateBtn.textContent = 'Сгенерировать ответы на отзывы';
2859        }
2860
2861        reviewsProcessState.isRunning = true;
2862        reviewsProcessState.shouldStop = false;
2863        generateBtn.disabled = true;
2864        stopBtn.disabled = false;
2865
2866        TM_log('Starting reviews generation process...');
2867
2868        try {
2869            await processReviewsBatch();
2870        } catch (error) {
2871            console.error('Reviews generation error:', error);
2872            statusDiv.textContent = 'Ошибка при генерации ответов на отзывы';
2873        } finally {
2874            reviewsProcessState.isRunning = false;
2875            generateBtn.disabled = false;
2876            stopBtn.disabled = true;
2877        }
2878    }
2879
2880    // Функция для остановки генерации
2881    function stopReviewsGeneration() {
2882        TM_log('stopReviewsGeneration called');
2883        reviewsProcessState.shouldStop = true;
2884        const generateBtn = document.querySelector('.reviews-generate-btn');
2885        const stopBtn = document.querySelector('.reviews-stop-btn');
2886        
2887        generateBtn.textContent = 'Продолжить';
2888        generateBtn.disabled = false;
2889        stopBtn.disabled = true;
2890        
2891        const statusDiv = document.querySelector('.reviews-generation-status');
2892        statusDiv.textContent = `Остановлено. Обработано отзывов: ${reviewsProcessState.processedCount}`;
2893    }
2894
2895    // Функция для создания панели массовой генерации отзывов
2896    function createReviewsGenerationPanel() {
2897        console.log('createReviewsGenerationPanel called');
2898        const tableContainer = findReviewsTableContainer();
2899        console.log('Table container found:', !!tableContainer);
2900        if (!tableContainer) {
2901            console.error('Reviews table container not found');
2902            return;
2903        }
2904
2905        // Проверяем, не создана ли уже панель
2906        const existingPanel = document.querySelector('.reviews-generation-panel');
2907        console.log('Existing panel found:', !!existingPanel);
2908        if (existingPanel) {
2909            console.log('Reviews generation panel already exists');
2910            return;
2911        }
2912
2913        console.log('Creating reviews generation panel...');
2914        const panel = document.createElement('div');
2915        panel.className = 'reviews-generation-panel';
2916        
2917        panel.innerHTML = `
2918            <div class="reviews-generation-title">💬 Массовая генерация ответов на отзывы</div>
2919            <div class="reviews-generation-buttons">
2920                <button class="reviews-generate-btn">Сгенерировать ответы на отзывы</button>
2921                <button class="reviews-stop-btn" disabled>Остановить</button>
2922                <button class="reviews-prompt-edit-btn">✏️ Промпт для отзывов</button>
2923            </div>
2924            <div class="reviews-generation-status"></div>
2925        `;
2926
2927        console.log('Inserting panel before table container...');
2928        tableContainer.insertAdjacentElement('beforebegin', panel);
2929        console.log('Panel inserted');
2930
2931        // Добавляем обработчики
2932        const generateBtn = panel.querySelector('.reviews-generate-btn');
2933        const stopBtn = panel.querySelector('.reviews-stop-btn');
2934        const promptEditBtn = panel.querySelector('.reviews-prompt-edit-btn');
2935
2936        TM_log('Adding event listeners to reviews panel buttons...');
2937        
2938        generateBtn.addEventListener('click', () => {
2939            TM_log('Generate button clicked!');
2940            startReviewsGeneration();
2941        });
2942        
2943        stopBtn.addEventListener('click', () => {
2944            TM_log('Stop button clicked!');
2945            stopReviewsGeneration();
2946        });
2947        
2948        promptEditBtn.addEventListener('click', () => {
2949            TM_log('Prompt edit button clicked!');
2950            showReviewsPromptModal();
2951        });
2952
2953        TM_log('Reviews generation panel created with event listeners');
2954        
2955        // Загружаем промпт для отзывов из localStorage
2956        loadReviewsPrompt();
2957    }
2958
2959    // Инициализация массовой генерации отзывов
2960    function initReviewsGeneration() {
2961        // Проверяем, что мы на странице со списком отзывов
2962        if (!window.location.href.includes('/app/reviews') || 
2963            window.location.href.includes('/app/reviews/questions') ||
2964            window.location.href.includes('/app/reviews/auto-answer')) {
2965            return;
2966        }
2967
2968        console.log('Initializing reviews generation...');
2969        
2970        addReviewsGenerationStyles();
2971        
2972        // Ждем загрузки таблицы
2973        const observer = new MutationObserver(debounce(() => {
2974            const tableContainer = findReviewsTableContainer();
2975            const existingPanel = document.querySelector('.reviews-generation-panel');
2976            
2977            if (tableContainer) {
2978                // Если панель существует, но находится не перед контейнером таблицы
2979                if (existingPanel) {
2980                    // Проверяем, находится ли панель перед текущим контейнером таблицы
2981                    const panelNextSibling = existingPanel.nextElementSibling;
2982                    if (panelNextSibling !== tableContainer) {
2983                        console.log('Panel found but in wrong position, moving it...');
2984                        tableContainer.insertAdjacentElement('beforebegin', existingPanel);
2985                        console.log('Panel moved to correct position');
2986                    }
2987                } else {
2988                    console.log('Panel not found, creating new one...');
2989                    createReviewsGenerationPanel();
2990                }
2991            }
2992        }, 300));
2993
2994        observer.observe(document.body, {
2995            childList: true,
2996            subtree: true
2997        });
2998
2999        // Пробуем создать панель сразу
3000        const tableContainer = findReviewsTableContainer();
3001        if (tableContainer) {
3002            createReviewsGenerationPanel();
3003        }
3004    }
3005
3006    // Запускаем инициализацию массовой генерации отзывов
3007    initReviewsGeneration();
3008
3009})();