Wildberries Review Reply Generator

AI-powered reply generator for Wildberries seller feedback with customizable tone and style settings

Size

24.1 KB

Version

1.1.1

Created

Dec 2, 2025

Updated

11 days ago

1// ==UserScript==
2// @name		Wildberries Review Reply Generator
3// @description		AI-powered reply generator for Wildberries seller feedback with customizable tone and style settings
4// @version		1.1.1
5// @match		https://*.seller.wildberries.ru/*
6// @icon		https://static-basket-02.wbbasket.ru/vol20/root-monorepo/latest/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10    
11    console.log('Wildberries Review Reply Generator - Extension Loaded');
12    
13    // ============================================
14    // CONFIGURATION & CONSTANTS
15    // ============================================
16    
17    const STORAGE_KEYS = {
18        TONE_SETTINGS: 'wb_reply_tone_settings',
19        ADDITIONAL_INSTRUCTIONS: 'wb_reply_additional_instructions'
20    };
21    
22    const DEFAULT_TONE = 'Friendly, professional, short, always thank the customer, do not use emojis, пиши на русском, учитывай контекст для ответа';
23    const DEFAULT_INSTRUCTIONS = `* Если нет имени - просто здоровайся
24* Если отзыв негативный, но нет текста - пиши например "жаль что вам не понравилось, хотелось бы больше информации, напишите нам"
25* Если отзыв положительный, но нет текста - пиши например "спасибо за высокую оценку, рады что вам всё понравилось"
26* Если отзыв про доставку - пиши что мы не влияем на доставку, потому что товар хранится на складах wildberries
27* Учитывай при ответе указанные достоинства, недостатки и комментарий
28* Если указано, что нет эффекта или результата - пиши, что каждый организм индивидуален, у всех разные дефициты, рекомендуем пропить курс и после делать выводы`;
29    
30    // ============================================
31    // REVIEW DATA EXTRACTION
32    // ============================================
33    
34    /**
35     * Extracts the currently visible/expanded review text from the page
36     * @returns {Object} Object containing review text, customer name, and product info
37     */
38    function extractReviewData() {
39        console.log('Extracting review data from page...');
40        
41        // Find the review text - look for the expanded feedback item
42        const reviewTextElement = document.querySelector('.Feedback-text-block__che\\+hHALlA span.Text--body-l__rcq6CWuqor');
43        const reviewText = reviewTextElement ? reviewTextElement.textContent.trim() : null;
44        
45        // Find customer name
46        const customerNameElement = document.querySelector('.Extended-feedback-item-info-content__aeFOe4qZKw span.Text--h4-bold__zMXtJ5k8XU');
47        const customerName = customerNameElement ? customerNameElement.textContent.trim() : null;
48        
49        // Find product name
50        const productNameElement = document.querySelector('.Extended-article-info-card__p99vZY6cjs a.Browser-link--h3__hnyowivo\\+s');
51        const productName = productNameElement ? productNameElement.textContent.trim().replace(/\s+/g, ' ') : null;
52        
53        // Find rating
54        const ratingElement = document.querySelector('.Rating__d1dZ\\+mT4Fv');
55        let rating = null;
56        if (ratingElement) {
57            const ratingText = ratingElement.getAttribute('aria-label') || ratingElement.textContent;
58            rating = ratingText;
59        }
60        
61        console.log('Extracted review data:', {
62            reviewText,
63            customerName,
64            productName,
65            rating
66        });
67        
68        return {
69            reviewText,
70            customerName,
71            productName,
72            rating,
73            success: reviewText !== null
74        };
75    }
76    
77    /**
78     * Inserts the generated reply text into the reply textarea on the page
79     * @param {string} replyText - The generated reply text to insert
80     */
81    function insertReplyIntoPage(replyText) {
82        console.log('Inserting reply into page textarea...');
83        
84        // Find the reply textarea
85        const replyTextarea = document.querySelector('textarea#answerText[name="answerText"]');
86        
87        if (replyTextarea) {
88            // Set the value
89            replyTextarea.value = replyText;
90            
91            // Trigger input event so the page knows the value changed
92            const inputEvent = new Event('input', { bubbles: true });
93            replyTextarea.dispatchEvent(inputEvent);
94            
95            // Also trigger change event
96            const changeEvent = new Event('change', { bubbles: true });
97            replyTextarea.dispatchEvent(changeEvent);
98            
99            // Focus the textarea so user can see it
100            replyTextarea.focus();
101            
102            console.log('Reply inserted successfully');
103            return true;
104        } else {
105            console.error('Reply textarea not found on page');
106            return false;
107        }
108    }
109    
110    // ============================================
111    // AI REPLY GENERATION (MOCK)
112    // ============================================
113    
114    /**
115     * Generates a reply based on review data and settings
116     * This is a MOCK function - replace with actual AI API call
117     * 
118     * TO INTEGRATE YOUR AI API:
119     * 1. Replace the mock response with your API call
120     * 2. Use the 'prompt' variable which contains the full context
121     * 3. Return the AI-generated reply text
122     * 
123     * Example with fetch:
124     * const response = await fetch('YOUR_AI_API_ENDPOINT', {
125     *     method: 'POST',
126     *     headers: { 'Content-Type': 'application/json' },
127     *     body: JSON.stringify({ prompt: prompt })
128     * });
129     * const data = await response.json();
130     * return data.reply;
131     */
132    async function generateReply(reviewData, toneSettings, additionalInstructions) {
133        console.log('Generating reply with settings:', { toneSettings, additionalInstructions });
134        
135        // Build the prompt that will be sent to AI API
136        const prompt = buildPrompt(reviewData, toneSettings, additionalInstructions);
137        
138        console.log('Generated prompt for AI:', prompt);
139        
140        // ============================================
141        // MOCK AI RESPONSE - REPLACE THIS SECTION
142        // ============================================
143        
144        // Simulate API delay
145        await new Promise(resolve => setTimeout(resolve, 1000));
146        
147        // Mock response - generate a realistic reply based on the review
148        let mockReply = '';
149        
150        // Generate different responses based on review content
151        const reviewLower = reviewData.reviewText.toLowerCase();
152        
153        if (reviewLower.includes('не помог') || reviewLower.includes('не работает') || reviewLower.includes('не эффективн')) {
154            mockReply = `Здравствуйте, ${reviewData.customerName}! Благодарим вас за отзыв. Нам очень жаль, что продукт не оправдал ваших ожиданий. Эффективность может зависеть от индивидуальных особенностей организма. Рекомендуем проконсультироваться со специалистом по поводу применения. Будем рады помочь вам с выбором другого продукта.`;
155        } else if (reviewLower.includes('долго') || reviewLower.includes('доставк') || reviewLower.includes('ждал')) {
156            mockReply = `Здравствуйте, ${reviewData.customerName}! Благодарим за ваш отзыв. Нам очень жаль, что вы долго ждали ваш заказ. К сожалению, мы не можем повлиять на скорость работы доставки, так как товар хранится на складах Wildberries. Надеемся, что наш продукт вам понравится!`;
157        } else if (reviewLower.includes('отличн') || reviewLower.includes('хорош') || reviewLower.includes('понравил')) {
158            mockReply = `Здравствуйте, ${reviewData.customerName}! Благодарим вас за положительный отзыв! Очень рады, что наш продукт вам понравился. Желаем вам здоровья и хорошего настроения!`;
159        } else {
160            mockReply = `Здравствуйте, ${reviewData.customerName}! Благодарим вас за отзыв о товаре "${reviewData.productName}". Ваше мнение очень важно для нас. Если у вас есть вопросы по использованию продукта, мы всегда готовы помочь.`;
161        }
162        
163        return mockReply;
164        
165        // ============================================
166        // TO USE YOUR AI API, REPLACE THE ABOVE CODE WITH:
167        // ============================================
168        // const response = await fetch('YOUR_AI_API_ENDPOINT', {
169        //     method: 'POST',
170        //     headers: { 
171        //         'Content-Type': 'application/json',
172        //         'Authorization': 'Bearer YOUR_API_KEY'
173        //     },
174        //     body: JSON.stringify({ prompt: prompt })
175        // });
176        // const data = await response.json();
177        // return data.reply;
178        // ============================================
179    }
180    
181    /**
182     * Builds the prompt string for AI API
183     * @param {Object} reviewData - The extracted review data
184     * @param {string} toneSettings - Tone and style settings
185     * @param {string} additionalInstructions - Additional instructions
186     * @returns {string} The complete prompt
187     */
188    function buildPrompt(reviewData, toneSettings, additionalInstructions) {
189        let prompt = 'Ты - представитель службы поддержки продавца на маркетплейсе Wildberries. Твоя задача - написать ответ на отзыв покупателя.\n\n';
190        
191        prompt += 'ИНФОРМАЦИЯ О ТОВАРЕ И ОТЗЫВЕ:\n';
192        prompt += `Товар: ${reviewData.productName || 'Не указан'}\n`;
193        if (reviewData.rating) {
194            prompt += `Оценка: ${reviewData.rating}\n`;
195        }
196        prompt += `Имя покупателя: ${reviewData.customerName || 'Покупатель'}\n`;
197        prompt += `Текст отзыва: "${reviewData.reviewText}"\n\n`;
198        
199        prompt += `ТРЕБОВАНИЯ К ТОНУ И СТИЛЮ ОТВЕТА:\n${toneSettings}\n\n`;
200        
201        if (additionalInstructions && additionalInstructions.trim()) {
202            prompt += `ДОПОЛНИТЕЛЬНЫЕ ИНСТРУКЦИИ:\n${additionalInstructions}\n\n`;
203        }
204        
205        prompt += 'ВАЖНО:\n';
206        prompt += '- Напиши ТОЛЬКО текст ответа, без дополнительных пояснений\n';
207        prompt += '- Обращайся к покупателю по имени\n';
208        prompt += '- Будь вежливым и профессиональным\n';
209        prompt += '- Учитывай контекст отзыва и оценку\n';
210        prompt += '- Не используй markdown форматирование\n\n';
211        
212        prompt += 'Напиши ответ на отзыв:';
213        
214        return prompt;
215    }
216    
217    // ============================================
218    // UI CREATION
219    // ============================================
220    
221    /**
222     * Creates and injects the extension UI panel
223     */
224    async function createUI() {
225        console.log('Creating extension UI...');
226        
227        // Load saved settings
228        const savedTone = await GM.getValue(STORAGE_KEYS.TONE_SETTINGS, DEFAULT_TONE);
229        const savedInstructions = await GM.getValue(STORAGE_KEYS.ADDITIONAL_INSTRUCTIONS, DEFAULT_INSTRUCTIONS);
230        
231        // Create the UI container
232        const panel = document.createElement('div');
233        panel.id = 'wb-reply-generator-panel';
234        panel.innerHTML = `
235            <div class="wb-rg-header">
236                <h3>AI Reply Generator</h3>
237                <button id="wb-rg-toggle" class="wb-rg-toggle-btn"></button>
238            </div>
239            <div class="wb-rg-content">
240                <div class="wb-rg-section">
241                    <label for="wb-rg-tone">Tone & Style Settings:</label>
242                    <textarea id="wb-rg-tone" rows="3" placeholder="e.g., Friendly, professional, short, always thank the customer, do not use emojis">${savedTone}</textarea>
243                </div>
244                
245                <div class="wb-rg-section">
246                    <label for="wb-rg-instructions">Additional Instructions:</label>
247                    <textarea id="wb-rg-instructions" rows="3" placeholder="Add any templates or special instructions here...">${savedInstructions}</textarea>
248                </div>
249                
250                <div class="wb-rg-section">
251                    <button id="wb-rg-generate" class="wb-rg-btn-primary">Generate Reply</button>
252                </div>
253                
254                <div class="wb-rg-section" id="wb-rg-result-section" style="display: none;">
255                    <label for="wb-rg-result">Generated Reply:</label>
256                    <textarea id="wb-rg-result" rows="6" readonly></textarea>
257                    <div class="wb-rg-status" id="wb-rg-status"></div>
258                </div>
259            </div>
260        `;
261        
262        // Add styles
263        const styles = `
264            #wb-reply-generator-panel {
265                position: fixed;
266                top: 80px;
267                right: 20px;
268                width: 400px;
269                background: white;
270                border: 1px solid #ddd;
271                border-radius: 8px;
272                box-shadow: 0 4px 12px rgba(0,0,0,0.15);
273                z-index: 10000;
274                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
275            }
276            
277            .wb-rg-header {
278                display: flex;
279                justify-content: space-between;
280                align-items: center;
281                padding: 16px;
282                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
283                color: white;
284                border-radius: 8px 8px 0 0;
285                cursor: move;
286            }
287            
288            .wb-rg-header h3 {
289                margin: 0;
290                font-size: 16px;
291                font-weight: 600;
292            }
293            
294            .wb-rg-toggle-btn {
295                background: rgba(255,255,255,0.2);
296                border: none;
297                color: white;
298                width: 28px;
299                height: 28px;
300                border-radius: 4px;
301                cursor: pointer;
302                font-size: 20px;
303                line-height: 1;
304                transition: background 0.2s;
305            }
306            
307            .wb-rg-toggle-btn:hover {
308                background: rgba(255,255,255,0.3);
309            }
310            
311            .wb-rg-content {
312                padding: 16px;
313                max-height: 600px;
314                overflow-y: auto;
315            }
316            
317            .wb-rg-content.collapsed {
318                display: none;
319            }
320            
321            .wb-rg-section {
322                margin-bottom: 16px;
323            }
324            
325            .wb-rg-section label {
326                display: block;
327                margin-bottom: 6px;
328                font-weight: 500;
329                font-size: 13px;
330                color: #333;
331            }
332            
333            .wb-rg-section textarea {
334                width: 100%;
335                padding: 10px;
336                border: 1px solid #ddd;
337                border-radius: 6px;
338                font-size: 13px;
339                font-family: inherit;
340                resize: vertical;
341                box-sizing: border-box;
342                transition: border-color 0.2s;
343            }
344            
345            .wb-rg-section textarea:focus {
346                outline: none;
347                border-color: #667eea;
348            }
349            
350            .wb-rg-section textarea[readonly] {
351                background: #f8f9fa;
352                color: #333;
353            }
354            
355            .wb-rg-btn-primary {
356                width: 100%;
357                padding: 12px;
358                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
359                color: white;
360                border: none;
361                border-radius: 6px;
362                font-size: 14px;
363                font-weight: 600;
364                cursor: pointer;
365                transition: transform 0.2s, box-shadow 0.2s;
366            }
367            
368            .wb-rg-btn-primary:hover {
369                transform: translateY(-1px);
370                box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
371            }
372            
373            .wb-rg-btn-primary:active {
374                transform: translateY(0);
375            }
376            
377            .wb-rg-btn-primary:disabled {
378                opacity: 0.6;
379                cursor: not-allowed;
380                transform: none;
381            }
382            
383            .wb-rg-status {
384                margin-top: 8px;
385                padding: 8px;
386                border-radius: 4px;
387                font-size: 12px;
388                text-align: center;
389            }
390            
391            .wb-rg-status.success {
392                background: #d4edda;
393                color: #155724;
394                border: 1px solid #c3e6cb;
395            }
396            
397            .wb-rg-status.error {
398                background: #f8d7da;
399                color: #721c24;
400                border: 1px solid #f5c6cb;
401            }
402            
403            .wb-rg-status.loading {
404                background: #d1ecf1;
405                color: #0c5460;
406                border: 1px solid #bee5eb;
407            }
408        `;
409        
410        TM_addStyle(styles);
411        
412        // Insert panel into page
413        document.body.appendChild(panel);
414        
415        // Make panel draggable
416        makeDraggable(panel);
417        
418        // Add event listeners
419        setupEventListeners();
420        
421        console.log('Extension UI created successfully');
422    }
423    
424    /**
425     * Makes the panel draggable
426     */
427    function makeDraggable(element) {
428        const header = element.querySelector('.wb-rg-header');
429        let isDragging = false;
430        let currentX;
431        let currentY;
432        let initialX;
433        let initialY;
434        
435        header.addEventListener('mousedown', (e) => {
436            if (e.target.id === 'wb-rg-toggle') return;
437            isDragging = true;
438            initialX = e.clientX - element.offsetLeft;
439            initialY = e.clientY - element.offsetTop;
440        });
441        
442        document.addEventListener('mousemove', (e) => {
443            if (!isDragging) return;
444            e.preventDefault();
445            currentX = e.clientX - initialX;
446            currentY = e.clientY - initialY;
447            element.style.left = currentX + 'px';
448            element.style.top = currentY + 'px';
449            element.style.right = 'auto';
450        });
451        
452        document.addEventListener('mouseup', () => {
453            isDragging = false;
454        });
455    }
456    
457    /**
458     * Sets up event listeners for UI interactions
459     */
460    function setupEventListeners() {
461        // Toggle panel collapse/expand
462        const toggleBtn = document.getElementById('wb-rg-toggle');
463        const content = document.querySelector('.wb-rg-content');
464        
465        toggleBtn.addEventListener('click', () => {
466            content.classList.toggle('collapsed');
467            toggleBtn.textContent = content.classList.contains('collapsed') ? '+' : '−';
468        });
469        
470        // Generate reply button
471        const generateBtn = document.getElementById('wb-rg-generate');
472        generateBtn.addEventListener('click', handleGenerateReply);
473        
474        // Auto-save settings on change
475        const toneInput = document.getElementById('wb-rg-tone');
476        const instructionsInput = document.getElementById('wb-rg-instructions');
477        
478        toneInput.addEventListener('change', async () => {
479            await GM.setValue(STORAGE_KEYS.TONE_SETTINGS, toneInput.value);
480            console.log('Tone settings saved');
481        });
482        
483        instructionsInput.addEventListener('change', async () => {
484            await GM.setValue(STORAGE_KEYS.ADDITIONAL_INSTRUCTIONS, instructionsInput.value);
485            console.log('Additional instructions saved');
486        });
487    }
488    
489    /**
490     * Handles the generate reply button click
491     */
492    async function handleGenerateReply() {
493        console.log('Generate reply button clicked');
494        
495        const generateBtn = document.getElementById('wb-rg-generate');
496        const resultSection = document.getElementById('wb-rg-result-section');
497        const resultTextarea = document.getElementById('wb-rg-result');
498        const statusDiv = document.getElementById('wb-rg-status');
499        const toneInput = document.getElementById('wb-rg-tone');
500        const instructionsInput = document.getElementById('wb-rg-instructions');
501        
502        // Disable button during generation
503        generateBtn.disabled = true;
504        generateBtn.textContent = 'Generating...';
505        
506        // Show loading status
507        resultSection.style.display = 'block';
508        statusDiv.className = 'wb-rg-status loading';
509        statusDiv.textContent = 'Generating reply...';
510        resultTextarea.value = '';
511        
512        try {
513            // Extract review data from page
514            const reviewData = extractReviewData();
515            
516            if (!reviewData.success || !reviewData.reviewText) {
517                throw new Error('Could not find review text on the page. Please make sure a review is opened/expanded.');
518            }
519            
520            // Get settings
521            const toneSettings = toneInput.value.trim() || DEFAULT_TONE;
522            const additionalInstructions = instructionsInput.value.trim();
523            
524            // Save settings
525            await GM.setValue(STORAGE_KEYS.TONE_SETTINGS, toneSettings);
526            await GM.setValue(STORAGE_KEYS.ADDITIONAL_INSTRUCTIONS, additionalInstructions);
527            
528            // Generate reply (mock for now)
529            const generatedReply = await generateReply(reviewData, toneSettings, additionalInstructions);
530            
531            // Display result
532            resultTextarea.value = generatedReply;
533            
534            // Insert into page
535            const inserted = insertReplyIntoPage(generatedReply);
536            
537            if (inserted) {
538                statusDiv.className = 'wb-rg-status success';
539                statusDiv.textContent = '✓ Reply generated and inserted into the page!';
540            } else {
541                statusDiv.className = 'wb-rg-status error';
542                statusDiv.textContent = '⚠ Reply generated but could not insert into page. Copy manually.';
543            }
544            
545        } catch (error) {
546            console.error('Error generating reply:', error);
547            statusDiv.className = 'wb-rg-status error';
548            statusDiv.textContent = '✗ Error: ' + error.message;
549        } finally {
550            // Re-enable button
551            generateBtn.disabled = false;
552            generateBtn.textContent = 'Generate Reply';
553        }
554    }
555    
556    // ============================================
557    // INITIALIZATION
558    // ============================================
559    
560    /**
561     * Initialize the extension
562     */
563    function init() {
564        console.log('Initializing Wildberries Review Reply Generator...');
565        
566        // Wait for page to be ready
567        if (document.readyState === 'loading') {
568            document.addEventListener('DOMContentLoaded', createUI);
569        } else {
570            // DOM is already ready
571            createUI();
572        }
573    }
574    
575    // Start the extension
576    init();
577    
578})();
Wildberries Review Reply Generator | Robomonkey