Ozon AI Answer Generator

AI-powered answer generator for Ozon seller questions

Size

92.0 KB

Version

1.3.59

Created

Mar 19, 2026

Updated

23 days ago

1// ==UserScript==
2// @name		Ozon AI Answer Generator
3// @description		AI-powered answer generator for Ozon seller questions
4// @version		1.3.59
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 modelSettings = {
452        provider: 'rmcall', // 'rmcall' или 'openrouter'
453        model: 'google/gemini-2.0-flash-exp:free',
454        apiKey: '',
455        customModels: [] // Список кастомных моделей
456    };
457
458    // Дефолтный промпт
459    const DEFAULT_PROMPT = 'Ты - профессиональный менеджер по работе с клиентами на маркетплейсе Ozon.\n\n' +
460        'Продукт: {productName}\n\n' +
461        'Вопрос покупателя: {questionText}{knowledgeBaseInfo}\n\n' +
462        'Задача: Сгенерируй профессиональный, вежливый и информативный ответ на вопрос покупателя о товаре.\n\n' +
463        'Требования к ответу:\n' +
464        '- Будь вежливым и дружелюбным\n' +
465        '- Отвечай по существу вопроса\n' +
466        '- Используй информацию о продукте\n' +
467        '- Ответ должен быть кратким (2-4 предложения)\n' +
468        '- Не придумывай характеристики, которых нет в названии продукта\n' +
469        '- Если нужна дополнительная информация, вежливо предложи обратиться к описанию товара\n' +
470        '- Учитывай при ответе Дополнительные инструкции {additionalPrompt} если они есть\n' +
471        '- ВАЖНО: Если у тебя недостаточно информации для ответа или ты не знаешь что ответить, напиши ТОЛЬКО слово "SKIP" без дополнительных объяснений';
472
473    // Функция для загрузки базы знаний из localStorage
474    async function loadKnowledgeBase() {
475        try {
476            const stored = localStorage.getItem('ozon_knowledge_base');
477            if (stored) {
478                knowledgeBase = JSON.parse(stored);
479                TM_log('Knowledge base loaded:', knowledgeBase.length, 'products');
480                updateKnowledgeBaseStatus();
481            }
482        } catch (error) {
483            console.error('Error loading knowledge base:', error);
484        }
485    }
486
487    // Функция для сохранения базы знаний в localStorage
488    function saveKnowledgeBase(data) {
489        try {
490            localStorage.setItem('ozon_knowledge_base', JSON.stringify(data));
491            knowledgeBase = data;
492            TM_log('Knowledge base saved:', data.length, 'products');
493            updateKnowledgeBaseStatus();
494        } catch (error) {
495            console.error('Error saving knowledge base:', error);
496            alert('Ошибка при сохранении базы знаний: ' + error.message);
497        }
498    }
499
500    // Функция для обновления статуса базы знаний
501    function updateKnowledgeBaseStatus() {
502        const statusDiv = document.querySelector('.kb-status');
503        if (statusDiv) {
504            if (knowledgeBase && knowledgeBase.length > 0) {
505                statusDiv.textContent = `База знаний загружена: ${knowledgeBase.length} товаров`;
506                statusDiv.style.color = '#28a745';
507            } else {
508                statusDiv.textContent = 'База знаний не загружена';
509                statusDiv.style.color = '#666';
510            }
511        }
512    }
513
514    // Функция для поиска товара в базе знаний по SKU
515    function findProductInKnowledgeBase(sellerArticle) {
516        if (!knowledgeBase || knowledgeBase.length === 0) {
517            TM_log('Knowledge base is empty');
518            return null;
519        }
520
521        if (!sellerArticle) {
522            TM_log('Seller article is empty');
523            return null;
524        }
525
526        TM_log('Searching for seller article:', sellerArticle);
527
528        // Ищем товар по артикулу - проверяем несколько возможных названий колонок
529        const possibleColumns = [
530            'Штрихкод (Серийный номер / EAN)',
531            'SKU',
532            'Артикул',
533            'Штрихкод',
534            'Серийный номер',
535            'EAN',
536            'Артикул продавца'
537        ];
538        
539        let foundProduct = null;
540        let foundColumn = '';
541        
542        for (const item of knowledgeBase) {
543            for (const column of possibleColumns) {
544                const itemSku = item[column];
545                if (itemSku && itemSku.toString().trim() === sellerArticle) {
546                    foundProduct = item;
547                    foundColumn = column;
548                    break;
549                }
550            }
551            if (foundProduct) break;
552        }
553
554        if (foundProduct) {
555            TM_log(`Product found in knowledge base using column "${foundColumn}"`);
556            TM_log('Product data:', foundProduct);
557        } else {
558            TM_log('Product not found in knowledge base for article:', sellerArticle);
559            TM_log('Available columns in first product:', knowledgeBase[0] ? Object.keys(knowledgeBase[0]) : 'No products');
560        }
561
562        return foundProduct;
563    }
564
565    // Функция для форматирования информации о товаре из базы знаний
566    function formatProductInfo(product) {
567        const info = [];
568        
569        if (product['Название товара']) info.push(`Название: ${product['Название товара']}`);
570        if (product['Бренд*']) info.push(`Бренд: ${product['Бренд*']}`);
571        if (product['Тип*']) info.push(`Тип: ${product['Тип*']}`);
572        if (product['Состав*']) info.push(`Состав: ${product['Состав*']}`);
573        if (product['Аннотация']) info.push(`Описание: ${product['Аннотация']}`);
574        if (product['Целевая аудитория']) info.push(`Целевая аудитория: ${product['Целевая аудитория']}`);
575        if (product['Направление БАД']) info.push(`Направление: ${product['Направление БАД']}`);
576        if (product['Вкусовой акцент (вкус)']) info.push(`Вкус: ${product['Вкусовой акцент (вкус)']}`);
577        if (product['Форма выпуска продукта']) info.push(`Форма выпуска: ${product['Форма выпуска продукта']}`);
578        if (product['Способ применения']) info.push(`Способ применения: ${product['Способ применения']}`);
579        if (product['Срок годности в днях']) info.push(`Срок годности: ${product['Срок годности в днях']} дней`);
580        if (product['Страна-изготовитель']) info.push(`Страна-изготовитель: ${product['Страна-изготовитель']}`);
581        if (product['Для детей']) info.push(`Для детей: ${product['Для детей']}`);
582        if (product['Минимальный возраст от']) info.push(`Минимальный возраст: ${product['Минимальный возраст от']}`);
583        
584        return info.join('\n');
585    }
586
587    // Функция для обработки загрузки XLS файла
588    function handleFileUpload(event) {
589        const file = event.target.files[0];
590        if (!file) return;
591
592        const reader = new FileReader();
593        
594        reader.onload = function(e) {
595            try {
596                const data = new Uint8Array(e.target.result);
597                const workbook = XLSX.read(data, { type: 'array' });
598                
599                // Берем первый лист
600                const firstSheetName = workbook.SheetNames[0];
601                const worksheet = workbook.Sheets[firstSheetName];
602                
603                // Конвертируем в JSON
604                const jsonData = XLSX.utils.sheet_to_json(worksheet);
605                
606                TM_log('XLS parsed:', jsonData.length, 'rows');
607                
608                if (jsonData.length === 0) {
609                    alert('Файл пустой или не содержит данных');
610                    return;
611                }
612                
613                // Сохраняем в localStorage
614                saveKnowledgeBase(jsonData);
615                alert(`База знаний загружена успешно!\nЗагружено товаров: ${jsonData.length}`);
616                
617            } catch (error) {
618                console.error('Error parsing XLS:', error);
619                alert('Ошибка при чтении файла: ' + error.message);
620            }
621        };
622        
623        reader.onerror = function(error) {
624            console.error('Error reading file:', error);
625            alert('Ошибка при чтении файла');
626        };
627        
628        reader.readAsArrayBuffer(file);
629    }
630
631    // Функция для добавления стилей массовой генерации
632    function addBulkGenerationStyles() {
633        const styles = `
634            .bulk-generation-panel {
635                margin: 20px 0;
636                padding: 20px;
637                background: #ffffff;
638                border-radius: 8px;
639                border: 2px solid #005bff;
640                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
641            }
642            
643            .bulk-generation-title {
644                font-size: 16px;
645                font-weight: 600;
646                margin-bottom: 16px;
647                color: #1a1a1a;
648            }
649            
650            .bulk-generation-buttons {
651                display: flex;
652                gap: 12px;
653                margin-bottom: 12px;
654            }
655            
656            .bulk-generate-btn, .bulk-answer-all-btn {
657                background: #005bff;
658                color: white;
659                border: none;
660                padding: 12px 24px;
661                border-radius: 6px;
662                font-size: 14px;
663                font-weight: 500;
664                cursor: pointer;
665                transition: background 0.2s;
666            }
667            
668            .bulk-generate-btn:hover, .bulk-answer-all-btn:hover {
669                background: #0047cc;
670            }
671            
672            .bulk-generate-btn:disabled, .bulk-answer-all-btn:disabled {
673                background: #d1d1d6;
674                cursor: not-allowed;
675            }
676            
677            .bulk-answer-all-btn {
678                background: #28a745;
679            }
680            
681            .bulk-answer-all-btn:hover {
682                background: #218838;
683            }
684            
685            .kb-upload-btn {
686                background: #6f42c1;
687                color: white;
688                border: none;
689                padding: 12px 24px;
690                border-radius: 6px;
691                font-size: 14px;
692                font-weight: 500;
693                cursor: pointer;
694                transition: background 0.2s;
695            }
696            
697            .kb-upload-btn:hover {
698                background: #5a32a3;
699            }
700            
701            .prompt-edit-btn {
702                background: #fd7e14;
703                color: white;
704                border: none;
705                padding: 12px 24px;
706                border-radius: 6px;
707                font-size: 14px;
708                font-weight: 500;
709                cursor: pointer;
710                transition: background 0.2s;
711            }
712            
713            .prompt-edit-btn:hover {
714                background: #e8590c;
715            }
716            
717            .model-select-btn {
718                background: #17a2b8;
719                color: white;
720                border: none;
721                padding: 12px 24px;
722                border-radius: 6px;
723                font-size: 14px;
724                font-weight: 500;
725                cursor: pointer;
726                transition: background 0.2s;
727            }
728            
729            .model-select-btn:hover {
730                background: #138496;
731            }
732            
733            .kb-file-input {
734                display: none;
735            }
736            
737            .prompt-modal-overlay {
738                position: fixed;
739                top: 0;
740                left: 0;
741                right: 0;
742                bottom: 0;
743                background: rgba(0, 0, 0, 0.5);
744                display: flex;
745                align-items: center;
746                justify-content: center;
747                z-index: 10000;
748            }
749            
750            .prompt-modal {
751                background: white;
752                border-radius: 8px;
753                width: 90%;
754                max-width: 800px;
755                max-height: 90vh;
756                display: flex;
757                flex-direction: column;
758                box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
759            }
760            
761            .prompt-modal-header {
762                display: flex;
763                justify-content: space-between;
764                align-items: center;
765                padding: 20px;
766                border-bottom: 1px solid #e0e0e0;
767            }
768            
769            .prompt-modal-title {
770                margin: 0;
771                font-size: 18px;
772                font-weight: 600;
773                color: #1a1a1a;
774            }
775            
776            .prompt-modal-close {
777                background: none;
778                border: none;
779                font-size: 24px;
780                color: #666;
781                cursor: pointer;
782                padding: 0;
783                width: 30px;
784                height: 30px;
785                display: flex;
786                align-items: center;
787                justify-content: center;
788                border-radius: 4px;
789                transition: background 0.2s;
790            }
791            
792            .prompt-modal-close:hover {
793                background: #f0f0f0;
794            }
795            
796            .prompt-modal-body {
797                padding: 20px;
798                flex: 1;
799                overflow-y: auto;
800            }
801            
802            .prompt-modal-hint {
803                font-size: 13px;
804                color: #666;
805                margin-bottom: 12px;
806                padding: 10px;
807                background: #f8f9fa;
808                border-radius: 4px;
809                border-left: 3px solid #fd7e14;
810            }
811            
812            .prompt-modal-textarea {
813                width: 100%;
814                min-height: 300px;
815                padding: 12px;
816                border: 1px solid #d1d1d6;
817                border-radius: 6px;
818                font-size: 14px;
819                font-family: 'Courier New', monospace;
820                resize: vertical;
821                box-sizing: border-box;
822            }
823            
824            .prompt-modal-textarea:focus {
825                outline: none;
826                border-color: #fd7e14;
827                box-shadow: 0 0 0 3px rgba(253, 126, 20, 0.1);
828            }
829            
830            .prompt-modal-footer {
831                display: flex;
832                justify-content: space-between;
833                align-items: center;
834                padding: 20px;
835                border-top: 1px solid #e0e0e0;
836            }
837            
838            .prompt-modal-actions {
839                display: flex;
840                gap: 12px;
841            }
842            
843            .prompt-modal-reset {
844                background: #6c757d;
845                color: white;
846                border: none;
847                padding: 10px 20px;
848                border-radius: 6px;
849                font-size: 14px;
850                font-weight: 500;
851                cursor: pointer;
852                transition: background 0.2s;
853            }
854            
855            .prompt-modal-reset:hover {
856                background: #5a6268;
857            }
858            
859            .prompt-modal-cancel {
860                background: #f0f0f0;
861                color: #333;
862                border: none;
863                padding: 10px 20px;
864                border-radius: 6px;
865                font-size: 14px;
866                font-weight: 500;
867                cursor: pointer;
868                transition: background 0.2s;
869            }
870            
871            .prompt-modal-cancel:hover {
872                background: #e0e0e0;
873            }
874            
875            .prompt-modal-save {
876                background: #fd7e14;
877                color: white;
878                border: none;
879                padding: 10px 20px;
880                border-radius: 6px;
881                font-size: 14px;
882                font-weight: 500;
883                cursor: pointer;
884                transition: background 0.2s;
885            }
886            
887            .prompt-modal-save:hover {
888                background: #e8590c;
889            }
890            
891            .model-modal-form-group {
892                margin-bottom: 20px;
893            }
894            
895            .model-modal-label {
896                display: block;
897                font-size: 14px;
898                font-weight: 600;
899                color: #1a1a1a;
900                margin-bottom: 8px;
901            }
902            
903            .model-modal-select {
904                width: 100%;
905                padding: 10px 12px;
906                border: 1px solid #d1d1d6;
907                border-radius: 6px;
908                font-size: 14px;
909                font-family: inherit;
910                box-sizing: border-box;
911                background: white;
912            }
913            
914            .model-modal-select:focus {
915                outline: none;
916                border-color: #17a2b8;
917                box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.1);
918            }
919            
920            .model-modal-input {
921                width: 100%;
922                padding: 10px 12px;
923                border: 1px solid #d1d1d6;
924                border-radius: 6px;
925                font-size: 14px;
926                font-family: inherit;
927                box-sizing: border-box;
928            }
929            
930            .model-modal-input:focus {
931                outline: none;
932                border-color: #17a2b8;
933                box-shadow: 0 0 0 3px rgba(23, 162, 184, 0.1);
934            }
935            
936            .model-modal-input:disabled {
937                background: #f5f5f5;
938                cursor: not-allowed;
939            }
940        `;
941        
942        TM_addStyle(styles);
943    }
944
945    // Универсальная функция для поиска контейнера таблицы с вопросами
946    function findQuestionsTableContainer() {
947        console.log('Searching for questions table container...');
948        
949        // Стратегия 1: Ищем таблицу по заголовку колонки "Вопрос"
950        const tables = document.querySelectorAll('table');
951        for (const table of tables) {
952            const headers = table.querySelectorAll('th');
953            for (const header of headers) {
954                if (header.textContent.trim() === 'Вопрос') {
955                    console.log('Found table by "Вопрос" header');
956                    // Ищем самый внешний контейнер с overflow или специфичным классом
957                    let container = table;
958                    let bestContainer = table.parentElement;
959                    
960                    while (container.parentElement && container.parentElement.tagName !== 'BODY') {
961                        container = container.parentElement;
962                        const style = window.getComputedStyle(container);
963                        
964                        // Ищем контейнер с overflow: auto или класс, начинающийся с n1d-
965                        if ((style.overflow === 'auto' || style.overflowX === 'auto') || 
966                            (container.className && container.className.includes('n1d-d') && container.className.includes('k'))) {
967                            bestContainer = container;
968                        }
969                    }
970                    
971                    console.log('Found container:', bestContainer.className);
972                    return bestContainer;
973                }
974            }
975        }
976        
977        // Стратегия 2: Ищем по data-widget атрибуту
978        const widget = document.querySelector('[data-widget="@seller-ui/reviews"]');
979        if (widget) {
980            const table = widget.querySelector('table');
981            if (table) {
982                console.log('Found table by data-widget');
983                // Ищем ближайший контейнер с классом n1d-
984                let container = table.parentElement;
985                while (container && container !== widget) {
986                    if (container.className && container.className.includes('n1d-d') && container.className.includes('k')) {
987                        return container;
988                    }
989                    container = container.parentElement;
990                }
991                return table.parentElement;
992            }
993        }
994        
995        // Стратегия 3: Ищем таблицу с tbody, содержащим кнопки с вопросами
996        for (const table of tables) {
997            const questionButtons = table.querySelectorAll('tbody button');
998            if (questionButtons.length > 0) {
999                console.log('Found table by question buttons');
1000                // Ищем родительский контейнер с классом n1d-
1001                let container = table.parentElement;
1002                while (container && container.tagName !== 'BODY') {
1003                    if (container.className && container.className.includes('n1d-d') && container.className.includes('k')) {
1004                        return container;
1005                    }
1006                    container = container.parentElement;
1007                }
1008                return table.parentElement;
1009            }
1010        }
1011        
1012        // Стратегия 4: Старый способ - по классу (fallback)
1013        const oldContainer = document.querySelector('.n1d-dk9d');
1014        if (oldContainer) {
1015            console.log('Found container by old class selector');
1016            return oldContainer;
1017        }
1018        
1019        console.error('Table container not found with any strategy');
1020        return null;
1021    }
1022
1023    // Функция для создания панели массовой генерации
1024    function createBulkGenerationPanel() {
1025        console.log('createBulkGenerationPanel called');
1026        const tableContainer = findQuestionsTableContainer();
1027        console.log('Table container found:', !!tableContainer);
1028        if (!tableContainer) {
1029            console.error('Table container not found');
1030            return;
1031        }
1032
1033        // Проверяем, не создана ли уже панель
1034        const existingPanel = document.querySelector('.bulk-generation-panel');
1035        console.log('Existing panel found:', !!existingPanel);
1036        if (existingPanel) {
1037            console.log('Bulk generation panel already exists');
1038            return;
1039        }
1040
1041        console.log('Creating bulk generation panel...');
1042        const panel = document.createElement('div');
1043        panel.className = 'bulk-generation-panel';
1044        
1045        panel.innerHTML = `
1046            <div class="bulk-generation-title">🤖 Массовая генерация ответов</div>
1047            <div class="bulk-generation-buttons">
1048                <button class="bulk-generate-btn">Сгенерировать ответы</button>
1049                <button class="bulk-answer-all-btn" disabled>Ответить всем</button>
1050                <button class="kb-upload-btn">📚 Загрузить базу знаний</button>
1051                <button class="prompt-edit-btn">✏️ Промпт</button>
1052                <button class="model-select-btn">⚙️ Модель</button>
1053                <input type="file" class="kb-file-input" accept=".xls,.xlsx" />
1054            </div>
1055            <div class="bulk-generation-status"></div>
1056            <div class="kb-status">База знаний не загружена</div>
1057        `;
1058
1059        console.log('Inserting panel before table container...');
1060        tableContainer.insertAdjacentElement('beforebegin', panel);
1061        console.log('Panel inserted');
1062
1063        // Добавляем обработчики
1064        const generateBtn = panel.querySelector('.bulk-generate-btn');
1065        const answerAllBtn = panel.querySelector('.bulk-answer-all-btn');
1066        const kbUploadBtn = panel.querySelector('.kb-upload-btn');
1067        const promptEditBtn = panel.querySelector('.prompt-edit-btn');
1068        const kbFileInput = panel.querySelector('.kb-file-input');
1069
1070        generateBtn.addEventListener('click', () => bulkGenerateAnswers());
1071        answerAllBtn.addEventListener('click', () => bulkAnswerAll());
1072        
1073        kbUploadBtn.addEventListener('click', () => {
1074            kbFileInput.click();
1075        });
1076        
1077        promptEditBtn.addEventListener('click', () => {
1078            showPromptModal();
1079        });
1080        
1081        const modelSelectBtn = panel.querySelector('.model-select-btn');
1082        
1083        modelSelectBtn.addEventListener('click', () => {
1084            showModelModal();
1085        });
1086        
1087        kbFileInput.addEventListener('change', handleFileUpload);
1088
1089        console.log('Bulk generation panel created');
1090        
1091        // Загружаем базу знаний из localStorage
1092        loadKnowledgeBase();
1093        
1094        // Загружаем кастомный промпт из localStorage
1095        loadCustomPrompt();
1096    }
1097
1098    // Функция для получения всех видимых вопросов
1099    function getVisibleQuestions() {
1100        // Ищем все строки в tbody, которые содержат ссылку на товар и кнопку с вопросом
1101        const allRows = document.querySelectorAll('tbody tr');
1102        const questions = [];
1103
1104        allRows.forEach((row, index) => {
1105            // Ищем ссылку на товар в 3-й колонке
1106            const productLink = row.querySelector('td:nth-child(3) a');
1107            // Ищем кнопку с вопросом в 4-й колонке
1108            const questionButton = row.querySelector('td:nth-child(4) button');
1109            
1110            // Ищем SKU - это div с классом body-400 в 3-й колонке
1111            const skuElements = row.querySelectorAll('td:nth-child(3) div');
1112            let sku = '';
1113            for (const el of skuElements) {
1114                const text = el.textContent.trim();
1115                // SKU обычно числовой и длиной 8-12 символов
1116                if (text.length >= 8 && text.length <= 12 && /^\d+$/.test(text)) {
1117                    sku = text;
1118                    break;
1119                }
1120            }
1121            
1122            // Получаем количество ответов из 5-й колонки
1123            const answersCountElement = row.querySelector('td:nth-child(5)');
1124            const answersCount = answersCountElement ? parseInt(answersCountElement.textContent.trim()) : 0;
1125            
1126            if (productLink && questionButton) {
1127                questions.push({
1128                    index: index,
1129                    row: row,
1130                    productName: productLink.textContent.trim(),
1131                    questionText: questionButton.textContent.trim(),
1132                    questionButton: questionButton,
1133                    sku: sku,
1134                    answersCount: answersCount
1135                });
1136            }
1137        });
1138
1139        return questions;
1140    }
1141
1142    // Функция для массовой генерации ответов
1143    async function bulkGenerateAnswers() {
1144        const generateBtn = document.querySelector('.bulk-generate-btn');
1145        const answerAllBtn = document.querySelector('.bulk-answer-all-btn');
1146        const statusDiv = document.querySelector('.bulk-generation-status');
1147
1148        try {
1149            generateBtn.disabled = true;
1150            answerAllBtn.disabled = true;
1151            generatedAnswers.clear();
1152
1153            const questions = getVisibleQuestions();
1154            
1155            if (questions.length === 0) {
1156                statusDiv.textContent = 'Нет вопросов для обработки';
1157                return;
1158            }
1159
1160            statusDiv.textContent = `Генерация ответов: 0 из ${questions.length}`;
1161
1162            let skippedCount = 0;
1163            let alreadyAnsweredCount = 0;
1164
1165            for (let i = 0; i < questions.length; i++) {
1166                const question = questions[i];
1167                
1168                try {
1169                    // Пропускаем вопросы, на которые уже есть ответы
1170                    if (question.answersCount > 0) {
1171                        console.log(`Question ${i + 1} skipped: already has ${question.answersCount} answer(s)`);
1172                        alreadyAnsweredCount++;
1173                        continue;
1174                    }
1175                    
1176                    // Подсвечиваем текущий вопрос
1177                    question.row.classList.add('question-row-processing');
1178                    
1179                    statusDiv.textContent = `Генерация ответов: ${i + 1} из ${questions.length}`;
1180
1181                    // Генерируем ответ
1182                    const answer = await generateAnswerForQuestion(question.productName, question.questionText, question.sku);
1183                    
1184                    // Проверяем валидность ответа
1185                    if (!isValidAnswer(answer)) {
1186                        console.log(`Question ${i + 1} skipped: AI returned invalid or SKIP answer`);
1187                        question.row.classList.remove('question-row-processing');
1188                        question.row.classList.add('question-row-error');
1189                        skippedCount++;
1190                        continue;
1191                    }
1192                    
1193                    // Сохраняем ответ
1194                    generatedAnswers.set(i, answer);
1195                    
1196                    // Добавляем textarea с ответом
1197                    addAnswerTextarea(question.row, answer, i);
1198                    
1199                    // Убираем подсветку обработки и добавляем подсветку завершения
1200                    question.row.classList.remove('question-row-processing');
1201                    question.row.classList.add('question-row-completed');
1202                    
1203                    console.log(`Answer generated for question ${i + 1}:`, answer);
1204
1205                } catch (error) {
1206                    console.error(`Error generating answer for question ${i + 1}:`, error);
1207                    TM_log(`ERROR: Error generating answer for question ${i + 1}:`, error.message || error.toString());
1208                    question.row.classList.remove('question-row-processing');
1209                    question.row.classList.add('question-row-error');
1210                    skippedCount++;
1211                    // Пропускаем вопрос при ошибке
1212                }
1213            }
1214
1215            let statusText = `Генерация завершена: ${generatedAnswers.size} из ${questions.length} ответов`;
1216            if (alreadyAnsweredCount > 0) {
1217                statusText += ` (уже отвечено: ${alreadyAnsweredCount})`;
1218            }
1219            if (skippedCount > 0) {
1220                statusText += ` (пропущено: ${skippedCount})`;
1221            }
1222            statusDiv.textContent = statusText;
1223            answerAllBtn.disabled = generatedAnswers.size === 0;
1224
1225        } catch (error) {
1226            console.error('Bulk generation error:', error);
1227            statusDiv.textContent = 'Ошибка при массовой генерации';
1228        } finally {
1229            generateBtn.disabled = false;
1230        }
1231    }
1232
1233    // Функция для генерации ответа на один вопрос
1234    async function generateAnswerForQuestion(productName, questionText, sku) {
1235        // Ищем товар в базе знаний по SKU
1236        const productInfo = findProductInKnowledgeBase(sku);
1237        let knowledgeBaseInfo = '';
1238        
1239        if (productInfo) {
1240            knowledgeBaseInfo = '\n\nДополнительная информация о товаре из базы знаний:\n' + formatProductInfo(productInfo);
1241            console.log('Using knowledge base info for bulk generation');
1242        }
1243        
1244        // Используем кастомный промпт или дефолтный
1245        const promptTemplate = getCurrentPrompt();
1246        
1247        // Заменяем переменные в промпте
1248        const prompt = promptTemplate
1249            .replace('{productName}', productName)
1250            .replace('{questionText}', questionText)
1251            .replace('{knowledgeBaseInfo}', knowledgeBaseInfo)
1252            .replace('{additionalPrompt}', '');
1253
1254        const answer = await callAI(prompt);
1255        return answer;
1256    }
1257
1258    // Функция для добавления textarea с ответом
1259    function addAnswerTextarea(row, answer, index) {
1260        // Проверяем, не добавлен ли уже textarea
1261        const existingContainer = row.querySelector('.answer-textarea-container');
1262        if (existingContainer) {
1263            const textarea = existingContainer.querySelector('.answer-textarea');
1264            textarea.value = answer;
1265            autoResizeTextarea(textarea);
1266            return;
1267        }
1268
1269        const questionCell = row.querySelector('td:nth-child(4)');
1270        if (!questionCell) return;
1271
1272        const container = document.createElement('div');
1273        container.className = 'answer-textarea-container';
1274        
1275        container.innerHTML = `
1276            <div class="answer-label">Сгенерированный ответ:</div>
1277            <textarea class="answer-textarea" data-question-index="${index}">${answer}</textarea>
1278        `;
1279
1280        questionCell.appendChild(container);
1281
1282        // Обновляем значение в Map при редактировании
1283        const textarea = container.querySelector('.answer-textarea');
1284        
1285        // Автоматически изменяем размер textarea
1286        autoResizeTextarea(textarea);
1287        
1288        // Предотвращаем открытие модального окна при клике на textarea или контейнер
1289        container.addEventListener('click', (e) => {
1290            e.stopPropagation();
1291        });
1292        
1293        textarea.addEventListener('click', (e) => {
1294            e.stopPropagation();
1295        });
1296        
1297        textarea.addEventListener('input', () => {
1298            generatedAnswers.set(index, textarea.value);
1299            autoResizeTextarea(textarea);
1300            updateAnswerAllButton();
1301        });
1302        
1303        // Активируем кнопку "Ответить всем"
1304        updateAnswerAllButton();
1305    }
1306    
1307    // Функция для обновления состояния кнопки "Ответить всем"
1308    function updateAnswerAllButton() {
1309        const answerAllBtn = document.querySelector('.bulk-answer-all-btn');
1310        if (!answerAllBtn) return;
1311        
1312        // Проверяем есть ли хотя бы один ответ
1313        const questions = getVisibleQuestions();
1314        let hasAnswers = false;
1315        
1316        for (const question of questions) {
1317            const answerTextarea = question.row.querySelector('.answer-textarea');
1318            if (answerTextarea && answerTextarea.value.trim()) {
1319                hasAnswers = true;
1320                break;
1321            }
1322        }
1323        
1324        answerAllBtn.disabled = !hasAnswers;
1325    }
1326    
1327    // Функция для автоматического изменения размера textarea
1328    function autoResizeTextarea(textarea) {
1329        // Сбрасываем высоту для правильного расчета
1330        textarea.style.height = 'auto';
1331        
1332        // Устанавливаем высоту на основе scrollHeight
1333        const newHeight = Math.max(150, textarea.scrollHeight);
1334        textarea.style.height = newHeight + 'px';
1335        
1336        // Также адаптируем ширину, если текст очень длинный
1337        const lineLength = textarea.value.split('\n').reduce((max, line) => Math.max(max, line.length), 0);
1338        if (lineLength > 100) {
1339            textarea.style.width = '100%';
1340        }
1341    }
1342
1343    // Функция для автоматической отправки всех ответов
1344    async function bulkAnswerAll() {
1345        TM_log('=== BULK ANSWER ALL STARTED ===');
1346        const answerAllBtn = document.querySelector('.bulk-answer-all-btn');
1347        const statusDiv = document.querySelector('.bulk-generation-status');
1348        
1349        TM_log('Button found:', !!answerAllBtn);
1350        TM_log('Status div found:', !!statusDiv);
1351
1352        try {
1353            answerAllBtn.disabled = true;
1354            TM_log('Button disabled');
1355            
1356            const questions = getVisibleQuestions();
1357            TM_log('Questions found:', questions.length);
1358            let successCount = 0;
1359            let totalToSend = 0;
1360
1361            // Сначала подсчитываем сколько ответов нужно отправить
1362            for (let i = 0; i < questions.length; i++) {
1363                const question = questions[i];
1364                const answerTextarea = question.row.querySelector('.answer-textarea');
1365                if (answerTextarea && answerTextarea.value.trim()) {
1366                    totalToSend++;
1367                    TM_log(`Question ${i}: has answer, length: ${answerTextarea.value.trim().length}`);
1368                }
1369            }
1370
1371            TM_log('Total answers to send:', totalToSend);
1372
1373            if (totalToSend === 0) {
1374                statusDiv.textContent = 'Нет ответов для отправки';
1375                answerAllBtn.disabled = false;
1376                TM_log('No answers to send, exiting');
1377                return;
1378            }
1379
1380            statusDiv.textContent = `Отправка ответов: 0 из ${totalToSend}`;
1381
1382            for (let i = 0; i < questions.length; i++) {
1383                const question = questions[i];
1384                
1385                // Получаем ответ из textarea на странице (не в модальном окне!)
1386                const answerTextareaOnPage = question.row.querySelector('.answer-textarea');
1387                if (!answerTextareaOnPage || !answerTextareaOnPage.value.trim()) {
1388                    TM_log(`Question ${i + 1} skipped: no answer`);
1389                    continue;
1390                }
1391                
1392                const answer = answerTextareaOnPage.value.trim();
1393
1394                try {
1395                    TM_log(`Processing question ${i + 1}, answer length:`, answer.length);
1396                    statusDiv.textContent = `Отправка ответов: ${successCount + 1} из ${totalToSend}`;
1397                    
1398                    // Кликаем на вопрос
1399                    TM_log('About to click question button');
1400                    question.questionButton.click();
1401                    TM_log('Clicked on question button');
1402                    
1403                    // Ждем открытия модального окна
1404                    TM_log('Waiting for modal to open...');
1405                    await waitForModal();
1406                    TM_log('Modal opened');
1407                    
1408                    // Находим модальное окно - ищем div с текстом "Ответ на вопрос"
1409                    const answerSectionTitle = Array.from(document.querySelectorAll('*'))
1410                        .find(el => el.textContent.trim() === 'Ответ на вопрос' && el.tagName === 'DIV');
1411                    
1412                    if (!answerSectionTitle) {
1413                        throw new Error('Answer section not found');
1414                    }
1415                    TM_log('Answer section title found');
1416                    
1417                    // Находим родительский контейнер с классом mt7
1418                    let answerContainer = answerSectionTitle.parentElement;
1419                    if (!answerContainer || !answerContainer.classList.contains('mt7')) {
1420                        throw new Error('Answer container not found');
1421                    }
1422                    TM_log('Answer container found');
1423
1424                    // Находим textarea внутри этого контейнера - ищем по label "Ваш ответ"
1425                    const label = Array.from(answerContainer.querySelectorAll('label'))
1426                        .find(l => l.textContent.trim() === 'Ваш ответ');
1427                    
1428                    if (!label) {
1429                        throw new Error('Label "Ваш ответ" not found');
1430                    }
1431                    
1432                    // Получаем id из атрибута for
1433                    const textareaId = label.getAttribute('for');
1434                    if (!textareaId) {
1435                        throw new Error('Textarea id not found in label');
1436                    }
1437                    TM_log('Textarea id from label:', textareaId);
1438                    
1439                    // Находим textarea по id
1440                    const textareaInModal = document.getElementById(textareaId);
1441                    if (!textareaInModal) {
1442                        throw new Error('Textarea not found by id: ' + textareaId);
1443                    }
1444                    TM_log('Textarea found in modal by id');
1445
1446                    // Вставляем ответ, который мы взяли СО СТРАНИЦЫ
1447                    textareaInModal.value = answer;
1448                    TM_log('Answer set to modal textarea, length:', textareaInModal.value.length);
1449                    
1450                    // Генерируем события для обновления состояния
1451                    const inputEvent = new Event('input', { bubbles: true });
1452                    textareaInModal.dispatchEvent(inputEvent);
1453                    
1454                    const changeEvent = new Event('change', { bubbles: true });
1455                    textareaInModal.dispatchEvent(changeEvent);
1456                    
1457                    // Также пробуем установить фокус и снять его для активации валидации
1458                    textareaInModal.focus();
1459                    textareaInModal.blur();
1460                    
1461                    TM_log('Events dispatched');
1462
1463                    // Ждем немного для обновления UI
1464                    await sleep(1000);
1465
1466                    // Находим кнопку "Отправить ответ" - ищем по тексту внутри кнопки
1467                    const submitButton = Array.from(answerContainer.querySelectorAll('button[type="submit"]'))
1468                        .find(btn => btn.textContent.includes('Отправить ответ'));
1469                    
1470                    if (!submitButton) {
1471                        TM_log('Submit button not found, available buttons:', 
1472                            Array.from(answerContainer.querySelectorAll('button')).map(b => b.textContent));
1473                        throw new Error('Submit button not found');
1474                    }
1475                    TM_log('Submit button found, text:', submitButton.textContent);
1476                    TM_log('Submit button disabled:', submitButton.disabled);
1477
1478                    // Если кнопка все еще disabled, пробуем еще раз обновить textarea
1479                    if (submitButton.disabled) {
1480                        TM_log('Button still disabled, trying to re-trigger events...');
1481                        textareaInModal.value = answer;
1482                        textareaInModal.dispatchEvent(new Event('input', { bubbles: true }));
1483                        textareaInModal.dispatchEvent(new Event('change', { bubbles: true }));
1484                        await sleep(500);
1485                        TM_log('Submit button disabled after retry:', submitButton.disabled);
1486                    }
1487
1488                    submitButton.click();
1489                    TM_log('Submit button clicked');
1490                    
1491                    // Ждем немного после отправки
1492                    await sleep(1500);
1493                    
1494                    // Закрываем модальное окно - ищем кнопку с aria-label "Крестик для закрытия"
1495                    const closeButton = Array.from(document.querySelectorAll('button'))
1496                        .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия');
1497                    
1498                    if (closeButton) {
1499                        closeButton.click();
1500                        TM_log('Close button clicked');
1501                    } else {
1502                        TM_log('Close button not found, trying ESC key');
1503                        // Альтернативный способ - нажатие ESC
1504                        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }));
1505                    }
1506                    
1507                    // Ждем закрытия модального окна
1508                    await waitForModalClose();
1509                    TM_log('Modal closed');
1510                    
1511                    successCount++;
1512                    TM_log(`Answer ${successCount} sent successfully`);
1513
1514                } catch (error) {
1515                    TM_log(`ERROR: Error sending answer for question ${i + 1}:`, error.message);
1516                    console.error('Full error:', error);
1517                    // Закрываем модальное окно при ошибке
1518                    const closeButton = Array.from(document.querySelectorAll('button'))
1519                        .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия');
1520                    if (closeButton) {
1521                        closeButton.click();
1522                    } else {
1523                        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27 }));
1524                    }
1525                    await sleep(500);
1526                }
1527            }
1528
1529            statusDiv.textContent = `Отправка завершена: ${successCount} из ${totalToSend} ответов отправлено`;
1530            TM_log('=== BULK ANSWER ALL COMPLETED ===');
1531
1532        } catch (error) {
1533            TM_log('ERROR: Bulk answer error:', error.message);
1534            console.error('Full bulk answer error:', error);
1535            statusDiv.textContent = 'Ошибка при отправке ответов';
1536        } finally {
1537            answerAllBtn.disabled = false;
1538            TM_log('Button re-enabled');
1539        }
1540    }
1541
1542    // Вспомогательная функция ожидания модального окна
1543    function waitForModal() {
1544        return new Promise((resolve) => {
1545            const checkModal = () => {
1546                // Ищем div с текстом "Ответ на вопрос"
1547                const answerSection = Array.from(document.querySelectorAll('*'))
1548                    .find(el => el.textContent.trim() === 'Ответ на вопрос' && el.tagName === 'DIV');
1549                
1550                if (answerSection) {
1551                    // Проверяем, что есть textarea с label "Ваш ответ"
1552                    const container = answerSection.parentElement;
1553                    if (container) {
1554                        const label = Array.from(container.querySelectorAll('label'))
1555                            .find(l => l.textContent.trim() === 'Ваш ответ');
1556                        if (label) {
1557                            resolve();
1558                            return;
1559                        }
1560                    }
1561                }
1562                setTimeout(checkModal, 100);
1563            };
1564            checkModal();
1565        });
1566    }
1567
1568    // Вспомогательная функция ожидания закрытия модального окна
1569    function waitForModalClose() {
1570        return new Promise((resolve) => {
1571            const checkModal = () => {
1572                // Проверяем, что модальное окно с вопросом закрыто
1573                const answerSection = Array.from(document.querySelectorAll('*'))
1574                    .find(el => el.textContent.trim() === 'Ответ на вопрос' && el.tagName === 'DIV');
1575                
1576                if (!answerSection) {
1577                    resolve();
1578                    return;
1579                }
1580                
1581                // Проверяем, что есть кнопка закрытия
1582                const closeButton = Array.from(document.querySelectorAll('button'))
1583                    .find(btn => btn.getAttribute('aria-label') === 'Крестик для закрытия');
1584                
1585                if (closeButton) {
1586                    resolve();
1587                    return;
1588                }
1589                
1590                setTimeout(checkModal, 100);
1591            };
1592            checkModal();
1593        });
1594    }
1595
1596    // Вспомогательная функция задержки
1597    function sleep(ms) {
1598        return new Promise(resolve => setTimeout(resolve, ms));
1599    }
1600
1601    // Функция для загрузки промпта из localStorage
1602    async function loadCustomPrompt() {
1603        try {
1604            const stored = localStorage.getItem('ozon_custom_prompt');
1605            if (stored) {
1606                customPrompt = stored;
1607                console.log('Custom prompt loaded');
1608            }
1609        } catch (error) {
1610            console.error('Error loading custom prompt:', error);
1611        }
1612    }
1613
1614    // Функция для сохранения промпта в localStorage
1615    function saveCustomPrompt(prompt) {
1616        try {
1617            localStorage.setItem('ozon_custom_prompt', prompt);
1618            customPrompt = prompt;
1619            console.log('Custom prompt saved');
1620        } catch (error) {
1621            console.error('Error saving custom prompt:', error);
1622            alert('Ошибка при сохранении промпта: ' + error.message);
1623        }
1624    }
1625
1626    // Функция для получения текущего промпта
1627    function getCurrentPrompt() {
1628        return customPrompt || DEFAULT_PROMPT;
1629    }
1630
1631    // Функция для показа модального окна с промптом
1632    function showPromptModal() {
1633        // Проверяем, не открыто ли уже модальное окно
1634        if (document.querySelector('.prompt-modal-overlay')) {
1635            return;
1636        }
1637
1638        const overlay = document.createElement('div');
1639        overlay.className = 'prompt-modal-overlay';
1640        
1641        const modal = document.createElement('div');
1642        modal.className = 'prompt-modal';
1643        
1644        const currentPrompt = getCurrentPrompt();
1645        
1646        modal.innerHTML = `
1647            <div class="prompt-modal-header">
1648                <h3 class="prompt-modal-title">Редактирование промпта</h3>
1649                <button class="prompt-modal-close"></button>
1650            </div>
1651            <div class="prompt-modal-body">
1652                <div class="prompt-modal-hint">
1653                    Используйте переменные: {productName}, {questionText}, {knowledgeBaseInfo}
1654                </div>
1655                <textarea class="prompt-modal-textarea">${currentPrompt}</textarea>
1656            </div>
1657            <div class="prompt-modal-footer">
1658                <button class="prompt-modal-reset">Сбросить на дефолтный</button>
1659                <div class="prompt-modal-actions">
1660                    <button class="prompt-modal-cancel">Отмена</button>
1661                    <button class="prompt-modal-save">Сохранить</button>
1662                </div>
1663            </div>
1664        `;
1665        
1666        overlay.appendChild(modal);
1667        document.body.appendChild(overlay);
1668        
1669        // Обработчики
1670        const closeBtn = modal.querySelector('.prompt-modal-close');
1671        const cancelBtn = modal.querySelector('.prompt-modal-cancel');
1672        const saveBtn = modal.querySelector('.prompt-modal-save');
1673        const resetBtn = modal.querySelector('.prompt-modal-reset');
1674        const textarea = modal.querySelector('.prompt-modal-textarea');
1675        
1676        const closeModal = () => {
1677            overlay.remove();
1678        };
1679        
1680        closeBtn.addEventListener('click', closeModal);
1681        cancelBtn.addEventListener('click', closeModal);
1682        overlay.addEventListener('click', (e) => {
1683            if (e.target === overlay) closeModal();
1684        });
1685        
1686        saveBtn.addEventListener('click', () => {
1687            const newPrompt = textarea.value.trim();
1688            if (newPrompt) {
1689                saveCustomPrompt(newPrompt);
1690                alert('Промпт сохранен успешно!');
1691                closeModal();
1692            } else {
1693                alert('Промпт не может быть пустым');
1694            }
1695        });
1696        
1697        resetBtn.addEventListener('click', () => {
1698            if (confirm('Вы уверены, что хотите сбросить промпт на дефолтный?')) {
1699                textarea.value = DEFAULT_PROMPT;
1700                localStorage.removeItem('ozon_custom_prompt');
1701                customPrompt = null;
1702                alert('Промпт сброшен на дефолтный');
1703            }
1704        });
1705    }
1706
1707    // Функция для показа модального окна выбора модели
1708    function showModelModal() {
1709        // Проверяем, не открыто ли уже модальное окно
1710        if (document.querySelector('.prompt-modal-overlay')) {
1711            return;
1712        }
1713
1714        // Загружаем настройки модели из localStorage
1715        loadModelSettings();
1716
1717        const overlay = document.createElement('div');
1718        overlay.className = 'prompt-modal-overlay';
1719        
1720        const modal = document.createElement('div');
1721        modal.className = 'prompt-modal';
1722        
1723        modal.innerHTML = `
1724            <div class="prompt-modal-header">
1725                <h3 class="prompt-modal-title">Настройки AI модели</h3>
1726                <button class="prompt-modal-close"></button>
1727            </div>
1728            <div class="prompt-modal-body">
1729                <div class="model-modal-form-group">
1730                    <label class="model-modal-label">Провайдер:</label>
1731                    <select class="model-modal-select" id="model-provider-select">
1732                        <option value="rmcall" ${modelSettings.provider === 'rmcall' ? 'selected' : ''}>RM Call (стандартный)</option>
1733                        <option value="openrouter" ${modelSettings.provider === 'openrouter' ? 'selected' : ''}>OpenRouter</option>
1734                    </select>
1735                </div>
1736                
1737                <div class="model-modal-form-group" id="model-select-group" style="display: ${modelSettings.provider === 'openrouter' ? 'block' : 'none'};">
1738                    <label class="model-modal-label">
1739                        Модель:
1740                        <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;">
1741                            🔍 Проверить модели
1742                        </button>
1743                    </label>
1744                    <select class="model-modal-select" id="model-name-select" size="8" style="height: auto;">
1745                        ${getModelOptions()}
1746                    </select>
1747                    <div id="model-test-status" style="margin-top: 8px; font-size: 12px; color: #666;"></div>
1748                    <div style="margin-top: 12px; display: flex; gap: 8px;">
1749                        <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;" />
1750                        <button id="add-model-btn" style="padding: 8px 16px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">➕ Добавить</button>
1751                        <button id="remove-model-btn" style="padding: 8px 16px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px;">🗑️ Удалить</button>
1752                    </div>
1753                </div>
1754                
1755                <div class="model-modal-form-group" id="api-key-group" style="display: ${modelSettings.provider === 'openrouter' ? 'block' : 'none'};">
1756                    <label class="model-modal-label">API ключ OpenRouter:</label>
1757                    <input type="password" class="model-modal-input" id="api-key-input" placeholder="Введите API ключ" value="${modelSettings.apiKey || ''}" />
1758                    <div class="prompt-modal-hint" style="margin-top: 8px;">
1759                        Получите бесплатный API ключ на <a href="https://openrouter.ai/keys" target="_blank" style="color: #17a2b8;">openrouter.ai/keys</a>
1760                    </div>
1761                </div>
1762            </div>
1763            <div class="prompt-modal-footer">
1764                <div></div>
1765                <div class="prompt-modal-actions">
1766                    <button class="prompt-modal-cancel">Отмена</button>
1767                    <button class="prompt-modal-save">Сохранить</button>
1768                </div>
1769            </div>
1770        `;
1771        
1772        overlay.appendChild(modal);
1773        document.body.appendChild(overlay);
1774        
1775        // Обработчики
1776        const closeBtn = modal.querySelector('.prompt-modal-close');
1777        const cancelBtn = modal.querySelector('.prompt-modal-cancel');
1778        const saveBtn = modal.querySelector('.prompt-modal-save');
1779        const providerSelect = modal.querySelector('#model-provider-select');
1780        const modelNameSelect = modal.querySelector('#model-name-select');
1781        const apiKeyInput = modal.querySelector('#api-key-input');
1782        const modelSelectGroup = modal.querySelector('#model-select-group');
1783        const apiKeyGroup = modal.querySelector('#api-key-group');
1784        const testModelsBtn = modal.querySelector('#test-models-btn');
1785        
1786        // Устанавливаем текущую модель
1787        if (modelSettings.model) {
1788            modelNameSelect.value = modelSettings.model;
1789        }
1790        
1791        const closeModal = () => {
1792            overlay.remove();
1793        };
1794        
1795        // Обработчик изменения провайдера
1796        providerSelect.addEventListener('change', () => {
1797            const isOpenRouter = providerSelect.value === 'openrouter';
1798            modelSelectGroup.style.display = isOpenRouter ? 'block' : 'none';
1799            apiKeyGroup.style.display = isOpenRouter ? 'block' : 'none';
1800        });
1801        
1802        // Обработчик проверки моделей
1803        testModelsBtn.addEventListener('click', async () => {
1804            await testAllModels(modelNameSelect, apiKeyInput.value.trim());
1805        });
1806        
1807        // Обработчик добавления модели
1808        const addModelBtn = modal.querySelector('#add-model-btn');
1809        const removeModelBtn = modal.querySelector('#remove-model-btn');
1810        const customModelInput = modal.querySelector('#custom-model-input');
1811        
1812        addModelBtn.addEventListener('click', () => {
1813            const modelId = customModelInput.value.trim();
1814            if (!modelId) {
1815                alert('Пожалуйста, введите ID модели');
1816                return;
1817            }
1818            
1819            // Проверяем, не добавлена ли уже эта модель
1820            if (!modelSettings.customModels) {
1821                modelSettings.customModels = [];
1822            }
1823            
1824            if (modelSettings.customModels.includes(modelId)) {
1825                alert('Эта модель уже добавлена');
1826                return;
1827            }
1828            
1829            // Добавляем модель
1830            modelSettings.customModels.push(modelId);
1831            saveModelSettings();
1832            
1833            // Обновляем список моделей
1834            modelNameSelect.innerHTML = getModelOptions();
1835            modelNameSelect.value = modelId;
1836            
1837            customModelInput.value = '';
1838            alert('Модель добавлена успешно!');
1839        });
1840        
1841        removeModelBtn.addEventListener('click', () => {
1842            const selectedModel = modelNameSelect.value;
1843            if (!selectedModel) {
1844                alert('Пожалуйста, выберите модель для удаления');
1845                return;
1846            }
1847            
1848            // Проверяем, является ли модель кастомной
1849            if (!modelSettings.customModels || !modelSettings.customModels.includes(selectedModel)) {
1850                alert('Можно удалять только добавленные вами модели');
1851                return;
1852            }
1853            
1854            if (!confirm(`Вы уверены, что хотите удалить модель "${selectedModel}"?`)) {
1855                return;
1856            }
1857            
1858            // Удаляем модель
1859            modelSettings.customModels = modelSettings.customModels.filter(m => m !== selectedModel);
1860            saveModelSettings();
1861            
1862            // Обновляем список моделей
1863            modelNameSelect.innerHTML = getModelOptions();
1864            
1865            alert('Модель удалена успешно!');
1866        });
1867        
1868        closeBtn.addEventListener('click', closeModal);
1869        cancelBtn.addEventListener('click', closeModal);
1870        overlay.addEventListener('click', (e) => {
1871            if (e.target === overlay) closeModal();
1872        });
1873        
1874        saveBtn.addEventListener('click', () => {
1875            const provider = providerSelect.value;
1876            const model = modelNameSelect.value;
1877            const apiKey = apiKeyInput.value.trim();
1878            
1879            // Валидация
1880            if (provider === 'openrouter' && !apiKey) {
1881                alert('Пожалуйста, введите API ключ для OpenRouter');
1882                return;
1883            }
1884            
1885            // Сохраняем настройки
1886            modelSettings.provider = provider;
1887            modelSettings.model = model;
1888            modelSettings.apiKey = apiKey;
1889            
1890            saveModelSettings();
1891            alert('Настройки модели сохранены успешно!');
1892            closeModal();
1893        });
1894    }
1895
1896    // Функция для тестирования всех моделей
1897    async function testAllModels(selectElement, apiKey) {
1898        if (!apiKey) {
1899            alert('Пожалуйста, введите API ключ для проверки моделей');
1900            return;
1901        }
1902
1903        const statusDiv = document.querySelector('#model-test-status');
1904        const testBtn = document.querySelector('#test-models-btn');
1905        
1906        testBtn.disabled = true;
1907        testBtn.textContent = '⏳ Проверка...';
1908        statusDiv.textContent = 'Проверка моделей...';
1909        
1910        const models = [];
1911        for (let i = 0; i < selectElement.options.length; i++) {
1912            models.push({
1913                value: selectElement.options[i].value,
1914                text: selectElement.options[i].text,
1915                option: selectElement.options[i]
1916            });
1917        }
1918        
1919        let workingCount = 0;
1920        let failedCount = 0;
1921        
1922        for (let i = 0; i < models.length; i++) {
1923            const model = models[i];
1924            statusDiv.textContent = `Проверка ${i + 1} из ${models.length}: ${model.text}`;
1925            
1926            try {
1927                const isWorking = await testModel(model.value, apiKey);
1928                
1929                if (isWorking) {
1930                    model.option.text = '✅ ' + model.text.replace('✅ ', '').replace('❌ ', '');
1931                    workingCount++;
1932                } else {
1933                    model.option.text = '❌ ' + model.text.replace('✅ ', '').replace('❌ ', '');
1934                    failedCount++;
1935                }
1936            } catch (error) {
1937                console.error(`Model ${model.value} test failed:`, error);
1938                model.option.text = '❌ ' + model.text.replace('✅ ', '').replace('❌ ', '');
1939                failedCount++;
1940            }
1941            
1942            // Небольшая задержка между запросами
1943            await sleep(500);
1944        }
1945        
1946        testBtn.disabled = false;
1947        testBtn.textContent = '🔍 Проверить модели';
1948        statusDiv.innerHTML = `<span style="color: #28a745;">✅ Работает: ${workingCount}</span> | <span style="color: #dc3545;">❌ Не работает: ${failedCount}</span>`;
1949    }
1950
1951    // Функция для тестирования одной модели
1952    async function testModel(modelName, apiKey) {
1953        try {
1954            const response = await GM.xmlhttpRequest({
1955                method: 'POST',
1956                url: 'https://openrouter.ai/api/v1/chat/completions',
1957                headers: {
1958                    'Content-Type': 'application/json',
1959                    'Authorization': `Bearer ${apiKey}`,
1960                    'HTTP-Referer': window.location.href,
1961                    'X-Title': 'Ozon AI Answer Generator'
1962                },
1963                data: JSON.stringify({
1964                    model: modelName,
1965                    messages: [
1966                        {
1967                            role: 'user',
1968                            content: 'Ответь одним словом: привет'
1969                        }
1970                    ],
1971                    max_tokens: 10
1972                }),
1973                timeout: 10000
1974            });
1975            
1976            if (response.status === 200) {
1977                const data = JSON.parse(response.responseText);
1978                return !!(data.choices && data.choices[0] && data.choices[0].message);
1979            }
1980            
1981            return false;
1982        } catch (error) {
1983            console.error(`Model ${modelName} test failed:`, error);
1984            return false;
1985        }
1986    }
1987
1988    // Функция для загрузки настроек модели из localStorage
1989    function loadModelSettings() {
1990        try {
1991            const stored = localStorage.getItem('ozon_model_settings');
1992            if (stored) {
1993                const settings = JSON.parse(stored);
1994                modelSettings.provider = settings.provider || 'rmcall';
1995                modelSettings.model = settings.model || 'google/gemini-2.0-flash-exp:free';
1996                modelSettings.apiKey = settings.apiKey || '';
1997                modelSettings.customModels = settings.customModels || [];
1998                console.log('Model settings loaded:', modelSettings);
1999            }
2000        } catch (error) {
2001            console.error('Error loading model settings:', error);
2002        }
2003    }
2004
2005    // Функция для генерации списка моделей
2006    function getModelOptions() {
2007        const defaultModels = [
2008            { value: 'arcee-ai/trinity-large-preview:free', label: 'Arcee Trinity Large (бесплатно)' },
2009            { value: 'openrouter/free', label: 'OpenRouter Free' }
2010        ];
2011        
2012        let options = '';
2013        
2014        // Добавляем дефолтные модели
2015        for (const model of defaultModels) {
2016            options += `<option value="${model.value}">${model.label}</option>`;
2017        }
2018        
2019        // Добавляем кастомные модели
2020        if (modelSettings.customModels && modelSettings.customModels.length > 0) {
2021            options += '<option disabled>──────────</option>';
2022            for (const modelId of modelSettings.customModels) {
2023                options += `<option value="${modelId}">🔧 ${modelId}</option>`;
2024            }
2025        }
2026        
2027        return options;
2028    }
2029
2030    // Функция для сохранения настроек модели в localStorage
2031    function saveModelSettings() {
2032        try {
2033            localStorage.setItem('ozon_model_settings', JSON.stringify(modelSettings));
2034            console.log('Model settings saved:', modelSettings);
2035        } catch (error) {
2036            console.error('Error saving model settings:', error);
2037            alert('Ошибка при сохранении настроек модели: ' + error.message);
2038        }
2039    }
2040
2041    // Универсальная функция для вызова AI
2042    async function callAI(prompt) {
2043        // Загружаем настройки модели
2044        loadModelSettings();
2045        
2046        console.log('Using AI provider:', modelSettings.provider);
2047        console.log('Prompt being sent to AI:', prompt);
2048        
2049        if (modelSettings.provider === 'openrouter') {
2050            // Используем OpenRouter API
2051            if (!modelSettings.apiKey) {
2052                throw new Error('API ключ OpenRouter не настроен. Откройте настройки модели и введите API ключ.');
2053            }
2054            
2055            console.log('Calling OpenRouter API with model:', modelSettings.model);
2056            
2057            try {
2058                const response = await GM.xmlhttpRequest({
2059                    method: 'POST',
2060                    url: 'https://openrouter.ai/api/v1/chat/completions',
2061                    headers: {
2062                        'Content-Type': 'application/json',
2063                        'Authorization': `Bearer ${modelSettings.apiKey}`,
2064                        'HTTP-Referer': window.location.href,
2065                        'X-Title': 'Ozon AI Answer Generator'
2066                    },
2067                    data: JSON.stringify({
2068                        model: modelSettings.model,
2069                        messages: [
2070                            {
2071                                role: 'user',
2072                                content: prompt
2073                            }
2074                        ]
2075                    })
2076                });
2077                
2078                console.log('OpenRouter response status:', response.status);
2079                console.log('OpenRouter response:', response.responseText);
2080                
2081                if (response.status !== 200) {
2082                    console.error('OpenRouter API error:', response);
2083                    throw new Error(`OpenRouter API error: ${response.status} - ${response.statusText}`);
2084                }
2085                
2086                const data = JSON.parse(response.responseText);
2087                
2088                if (!data.choices || !data.choices[0] || !data.choices[0].message) {
2089                    console.error('Invalid OpenRouter response:', data);
2090                    throw new Error('Неверный формат ответа от OpenRouter API');
2091                }
2092                
2093                return data.choices[0].message.content;
2094                
2095            } catch (error) {
2096                console.error('OpenRouter API call failed:', error);
2097                throw new Error(`Ошибка вызова OpenRouter API: ${error.message}`);
2098            }
2099        } else {
2100            // Используем стандартный RM.aiCall
2101            console.log('Calling RM.aiCall');
2102            return await RM.aiCall(prompt);
2103        }
2104    }
2105
2106    // Инициализация массовой генерации
2107    function initBulkGeneration() {
2108        // Проверяем, что мы на странице со списком вопросов
2109        if (!window.location.href.includes('/app/reviews/questions')) {
2110            return;
2111        }
2112
2113        console.log('Initializing bulk generation...');
2114        
2115        addBulkGenerationStyles();
2116        
2117        // Ждем загрузки таблицы
2118        const observer = new MutationObserver(debounce(() => {
2119            const tableContainer = findQuestionsTableContainer();
2120            const existingPanel = document.querySelector('.bulk-generation-panel');
2121            
2122            if (tableContainer) {
2123                // Если панель существует, но находится не перед контейнером таблицы
2124                if (existingPanel) {
2125                    // Проверяем, находится ли панель перед текущим контейнером таблицы
2126                    const panelNextSibling = existingPanel.nextElementSibling;
2127                    if (panelNextSibling !== tableContainer) {
2128                        console.log('Panel found but in wrong position, moving it...');
2129                        tableContainer.insertAdjacentElement('beforebegin', existingPanel);
2130                        console.log('Panel moved to correct position');
2131                    }
2132                } else {
2133                    // Панели нет, создаем новую
2134                    console.log('Panel not found, creating new one...');
2135                    createBulkGenerationPanel();
2136                }
2137            }
2138        }, 300));
2139
2140        observer.observe(document.body, {
2141            childList: true,
2142            subtree: true
2143        });
2144
2145        // Пробуем создать панель сразу
2146        const tableContainer = findQuestionsTableContainer();
2147        if (tableContainer) {
2148            createBulkGenerationPanel();
2149        }
2150    }
2151
2152    // Запускаем инициализацию массовой генерации
2153    initBulkGeneration();
2154
2155})();