Мощный AI-аналитик продаж на Ozon с автоматическим выявлением проблемных товаров и рекомендациями
Size
46.9 KB
Version
1.1.1
Created
Dec 4, 2025
Updated
9 days ago
1// ==UserScript==
2// @name Ozon AI Аналитика (OpenRouter, advanced)
3// @description Мощный AI-аналитик продаж на Ozon с автоматическим выявлением проблемных товаров и рекомендациями
4// @version 1.1.1
5// @match https://*.seller.ozon.ru/*
6// @icon https://st.ozone.ru/s3/seller-ui-static/icon/favicon32.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('🚀 Ozon AI Аналитика запущена');
12
13 // Утилита для задержки
14 function delay(ms) {
15 return new Promise(resolve => setTimeout(resolve, ms));
16 }
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 parseNumber(str) {
33 if (!str || str === '—' || str === '') return null;
34 const cleaned = str.replace(/[^\d.,-]/g, '').replace(',', '.');
35 const num = parseFloat(cleaned);
36 return isNaN(num) ? null : num;
37 }
38
39 // Парсинг процента изменения
40 function parsePercentChange(str) {
41 if (!str || str === '—') return null;
42 const match = str.match(/([+-]?\d+)%/);
43 return match ? parseInt(match[1]) : null;
44 }
45
46 // Извлечение значения и процента изменения
47 function parseValueWithChange(text) {
48 if (!text || text === '—') return { value: null, change: null };
49
50 // Разделяем значение и процент
51 const parts = text.split(/([+-]\d+%)/);
52 const value = parseNumber(parts[0]);
53 const change = parts[1] ? parsePercentChange(parts[1]) : null;
54
55 return { value, change };
56 }
57
58 // Функция автоматической загрузки всех товаров
59 async function loadAllProducts() {
60 console.log('📦 Начинаем загрузку всех товаров...');
61
62 let clickCount = 0;
63 const maxClicks = 100; // Защита от бесконечного цикла
64
65 while (clickCount < maxClicks) {
66 const loadMoreButton = document.querySelector('button.styles_loadMoreButton_2RI3D');
67
68 if (!loadMoreButton) {
69 console.log('✅ Все товары загружены! Всего кликов:', clickCount);
70 break;
71 }
72
73 console.log(`🔄 Загружаем еще товары... (клик ${clickCount + 1})`);
74 loadMoreButton.click();
75 clickCount++;
76
77 // Ждем загрузки новых товаров
78 await delay(2000);
79 }
80
81 return clickCount;
82 }
83
84 // Парсинг данных товаров из таблицы
85 function parseProductsData() {
86 console.log('📊 Парсим данные товаров...');
87
88 const products = [];
89 const rows = document.querySelectorAll('tr.ct590-c0.ct590-b9');
90
91 console.log(`Найдено строк товаров: ${rows.length}`);
92
93 rows.forEach((row, index) => {
94 try {
95 const cells = row.querySelectorAll('td');
96
97 // Название товара и артикул
98 const productCell = cells[0];
99 const productLink = productCell.querySelector('a[href*="ozon.ru/product"]');
100 const productName = productLink ? productLink.getAttribute('title') : '';
101 const articleMatch = productCell.textContent.match(/Арт\.\s*(\d+)/);
102 const article = articleMatch ? articleMatch[1] : '';
103
104 // Получаем текст из всех ячеек
105 const cellTexts = Array.from(cells).map(cell => cell.textContent.trim());
106
107 // Парсим основные показатели
108 const revenue = parseValueWithChange(cellTexts[2]); // Заказано на сумму
109 const ordersCount = parseValueWithChange(cellTexts[4]); // Количество заказов
110 const views = parseValueWithChange(cellTexts[5]); // Показы
111 const uniqueVisitors = parseValueWithChange(cellTexts[6]); // Уникальные посетители
112 const cardViews = parseValueWithChange(cellTexts[8]); // Просмотры карточки
113
114 // Конверсии
115 const conversionSearchToCard = parseValueWithChange(cellTexts[12]); // Конверсия из поиска в карточку
116 const stock = parseValueWithChange(cellTexts[13]); // Остаток
117 const conversionCardToCart = parseValueWithChange(cellTexts[15]); // Конверсия из карточки в корзину
118 const addToCart = parseValueWithChange(cellTexts[16]); // Добавлено в корзину
119 const cartViews = parseValueWithChange(cellTexts[18]); // Просмотры корзины
120 const ordersFromCart = parseValueWithChange(cellTexts[20]); // Заказы из корзины
121 const ordersTotal = parseValueWithChange(cellTexts[23]); // Всего заказов
122
123 // Цена и ДРР
124 const avgPrice = parseValueWithChange(cellTexts[28]); // Средняя цена
125
126 // Ищем ДРР в других ячейках
127 let drr = { value: null, change: null };
128 for (let i = 30; i < cellTexts.length; i++) {
129 if (cellTexts[i].includes('%') && !cellTexts[i].includes('₽')) {
130 const parsed = parseValueWithChange(cellTexts[i]);
131 if (parsed.value !== null && parsed.value > 0 && parsed.value < 100) {
132 drr = parsed;
133 break;
134 }
135 }
136 }
137
138 const product = {
139 name: productName,
140 article: article,
141 revenue: revenue.value,
142 revenueChange: revenue.change,
143 ordersCount: ordersCount.value,
144 ordersCountChange: ordersCount.change,
145 views: views.value,
146 viewsChange: views.change,
147 uniqueVisitors: uniqueVisitors.value,
148 uniqueVisitorsChange: uniqueVisitors.change,
149 cardViews: cardViews.value,
150 cardViewsChange: cardViews.change,
151 conversionSearchToCard: conversionSearchToCard.value,
152 conversionSearchToCardChange: conversionSearchToCard.change,
153 conversionCardToCart: conversionCardToCart.value,
154 conversionCardToCartChange: conversionCardToCart.change,
155 stock: stock.value,
156 stockChange: stock.change,
157 avgPrice: avgPrice.value,
158 avgPriceChange: avgPrice.change,
159 drr: drr.value,
160 drrChange: drr.change,
161 addToCart: addToCart.value,
162 addToCartChange: addToCart.change,
163 ordersTotal: ordersTotal.value,
164 ordersTotalChange: ordersTotal.change
165 };
166
167 products.push(product);
168
169 } catch (error) {
170 console.error(`Ошибка парсинга товара ${index}:`, error);
171 }
172 });
173
174 console.log(`✅ Распарсено товаров: ${products.length}`);
175 return products;
176 }
177
178 // AI анализ товаров
179 async function analyzeProductsWithAI(products) {
180 console.log('🤖 Запускаем AI анализ товаров...');
181
182 // Фильтруем товары с проблемами
183 const problematicProducts = products.filter(p => {
184 // Проверяем критерии проблем
185 const hasRevenueDrop = p.revenueChange !== null && p.revenueChange < -20;
186 const hasOrdersDrop = p.ordersCountChange !== null && p.ordersCountChange < -20;
187 const hasViewsDrop = p.viewsChange !== null && p.viewsChange < -30;
188 const hasConversionDrop = p.conversionCardToCartChange !== null && p.conversionCardToCartChange < -15;
189 const hasHighDRR = p.drr !== null && p.drr > 30;
190 const hasStockIssue = p.stock !== null && p.ordersCount !== null && (p.ordersCount * 7 < p.stock);
191 const hasLowStock = p.stock !== null && p.stock < 10;
192
193 return hasRevenueDrop || hasOrdersDrop || hasViewsDrop || hasConversionDrop || hasHighDRR || hasStockIssue || hasLowStock;
194 });
195
196 // Фильтруем успешные товары (значительный рост)
197 const successfulProducts = products.filter(p => {
198 const hasRevenueGrowth = p.revenueChange !== null && p.revenueChange > 30;
199 const hasOrdersGrowth = p.ordersCountChange !== null && p.ordersCountChange > 30;
200 const hasViewsGrowth = p.viewsChange !== null && p.viewsChange > 40;
201
202 return hasRevenueGrowth || hasOrdersGrowth || hasViewsGrowth;
203 });
204
205 console.log(`⚠️ Найдено проблемных товаров: ${problematicProducts.length} из ${products.length}`);
206 console.log(`✅ Найдено успешных товаров: ${successfulProducts.length} из ${products.length}`);
207
208 if (problematicProducts.length === 0 && successfulProducts.length === 0) {
209 return {
210 summary: 'Все товары показывают стабильные показатели. Нет критических проблем и значительных изменений.',
211 problems: [],
212 successes: []
213 };
214 }
215
216 // Берем топ-20 самых проблемных товаров для AI анализа
217 const topProblematic = problematicProducts.slice(0, 20);
218
219 // Берем топ-10 самых успешных товаров для AI анализа
220 const topSuccessful = successfulProducts.slice(0, 10);
221
222 // Формируем промпт для AI
223 const prompt = `Ты - эксперт по аналитике продаж на маркетплейсе Ozon. Проанализируй данные по товарам.
224
225${topProblematic.length > 0 ? `ПРОБЛЕМНЫЕ ТОВАРЫ:
226${topProblematic.map((p, i) => `
227${i + 1}. ${p.name} (Арт. ${p.article})
228 - Выручка: ${p.revenue ? p.revenue.toLocaleString() + ' ₽' : 'н/д'} (${p.revenueChange !== null ? (p.revenueChange > 0 ? '+' : '') + p.revenueChange + '%' : 'н/д'})
229 - Заказы: ${p.ordersCount || 'н/д'} (${p.ordersCountChange !== null ? (p.ordersCountChange > 0 ? '+' : '') + p.ordersCountChange + '%' : 'н/д'})
230 - Показы: ${p.views || 'н/д'} (${p.viewsChange !== null ? (p.viewsChange > 0 ? '+' : '') + p.viewsChange + '%' : 'н/д'})
231 - Просмотры карточки: ${p.cardViews || 'н/д'} (${p.cardViewsChange !== null ? (p.cardViewsChange > 0 ? '+' : '') + p.cardViewsChange + '%' : 'н/д'})
232 - Конверсия поиск→карточка: ${p.conversionSearchToCard !== null ? p.conversionSearchToCard + '%' : 'н/д'} (${p.conversionSearchToCardChange !== null ? (p.conversionSearchToCardChange > 0 ? '+' : '') + p.conversionSearchToCardChange + 'п.п.' : 'н/д'})
233 - Конверсия карточка→корзина: ${p.conversionCardToCart !== null ? p.conversionCardToCart + '%' : 'н/д'} (${p.conversionCardToCartChange !== null ? (p.conversionCardToCartChange > 0 ? '+' : '') + p.conversionCardToCartChange + 'п.п.' : 'н/д'})
234 - ДРР: ${p.drr !== null ? p.drr + '%' : 'н/д'} (${p.drrChange !== null ? (p.drrChange > 0 ? '+' : '') + p.drrChange + 'п.п.' : 'н/д'})
235 - Средняя цена: ${p.avgPrice ? p.avgPrice.toLocaleString() + ' ₽' : 'н/д'} (${p.avgPriceChange !== null ? (p.avgPriceChange > 0 ? '+' : '') + p.avgPriceChange + '%' : 'н/д'})
236`).join('\n')}` : ''}
237
238${topSuccessful.length > 0 ? `
239УСПЕШНЫЕ ТОВАРЫ (значительный рост):
240${topSuccessful.map((p, i) => `
241${i + 1}. ${p.name} (Арт. ${p.article})
242 - Выручка: ${p.revenue ? p.revenue.toLocaleString() + ' ₽' : 'н/д'} (${p.revenueChange !== null ? (p.revenueChange > 0 ? '+' : '') + p.revenueChange + '%' : 'н/д'})
243 - Заказы: ${p.ordersCount || 'н/д'} (${p.ordersCountChange !== null ? (p.ordersCountChange > 0 ? '+' : '') + p.ordersCountChange + '%' : 'н/д'})
244 - Показы: ${p.views || 'н/д'} (${p.viewsChange !== null ? (p.viewsChange > 0 ? '+' : '') + p.viewsChange + '%' : 'н/д'})
245 - Просмотры карточки: ${p.cardViews || 'н/д'} (${p.cardViewsChange !== null ? (p.cardViewsChange > 0 ? '+' : '') + p.cardViewsChange + '%' : 'н/д'})
246 - Конверсия поиск→карточка: ${p.conversionSearchToCard !== null ? p.conversionSearchToCard + '%' : 'н/д'} (${p.conversionSearchToCardChange !== null ? (p.conversionSearchToCardChange > 0 ? '+' : '') + p.conversionSearchToCardChange + 'п.п.' : 'н/д'})
247 - Конверсия карточка→корзина: ${p.conversionCardToCart !== null ? p.conversionCardToCart + '%' : 'н/д'} (${p.conversionCardToCartChange !== null ? (p.conversionCardToCartChange > 0 ? '+' : '') + p.conversionCardToCartChange + 'п.п.' : 'н/д'})
248 - ДРР: ${p.drr !== null ? p.drr + '%' : 'н/д'} (${p.drrChange !== null ? (p.drrChange > 0 ? '+' : '') + p.drrChange + 'п.п.' : 'н/д'})
249 - Средняя цена: ${p.avgPrice ? p.avgPrice.toLocaleString() + ' ₽' : 'н/д'} (${p.avgPriceChange !== null ? (p.avgPriceChange > 0 ? '+' : '') + p.avgPriceChange + '%' : 'н/д'})
250`).join('\n')}` : ''}
251
252Проанализируй:
2531. Для проблемных товаров: уровень критичности (critical/high/medium/low), проблемы и рекомендации
2542. Для успешных товаров: причины роста и как масштабировать успех на другие товары
2553. Общий вывод`;
256
257 try {
258 const aiResponse = await RM.aiCall(prompt, {
259 type: 'json_schema',
260 json_schema: {
261 name: 'products_analysis',
262 schema: {
263 type: 'object',
264 properties: {
265 summary: {
266 type: 'string',
267 description: 'Общий вывод по всем товарам'
268 },
269 problems: {
270 type: 'array',
271 items: {
272 type: 'object',
273 properties: {
274 article: { type: 'string' },
275 severity: {
276 type: 'string',
277 enum: ['critical', 'high', 'medium', 'low']
278 },
279 problems: {
280 type: 'array',
281 items: { type: 'string' }
282 },
283 recommendations: {
284 type: 'array',
285 items: { type: 'string' }
286 }
287 },
288 required: ['article', 'severity', 'problems', 'recommendations']
289 }
290 },
291 successes: {
292 type: 'array',
293 items: {
294 type: 'object',
295 properties: {
296 article: { type: 'string' },
297 reasons: {
298 type: 'array',
299 items: { type: 'string' },
300 description: 'Причины успеха товара'
301 },
302 scalingTips: {
303 type: 'array',
304 items: { type: 'string' },
305 description: 'Как масштабировать успех на другие товары'
306 }
307 },
308 required: ['article', 'reasons', 'scalingTips']
309 }
310 }
311 },
312 required: ['summary', 'problems', 'successes']
313 }
314 }
315 });
316
317 console.log('✅ AI анализ завершен');
318 return aiResponse;
319
320 } catch (error) {
321 console.error('❌ Ошибка AI анализа:', error);
322 return {
323 summary: 'Не удалось выполнить AI анализ. Попробуйте позже.',
324 problems: [],
325 successes: []
326 };
327 }
328 }
329
330 // Создание UI панели с результатами
331 function createAnalysisPanel(productsData, aiAnalysis) {
332 console.log('🎨 Создаем панель с результатами...');
333
334 // Удаляем старую панель если есть
335 const oldPanel = document.getElementById('ozon-ai-analysis-panel');
336 if (oldPanel) oldPanel.remove();
337
338 // Состояние фильтра
339 let currentFilter = 'all'; // all, critical, high, medium, low
340
341 // Создаем панель
342 const panel = document.createElement('div');
343 panel.id = 'ozon-ai-analysis-panel';
344 panel.style.cssText = `
345 position: fixed;
346 top: 80px;
347 right: 20px;
348 width: 450px;
349 max-height: 80vh;
350 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
351 border-radius: 16px;
352 box-shadow: 0 20px 60px rgba(0,0,0,0.3);
353 z-index: 10000;
354 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
355 overflow: hidden;
356 animation: slideIn 0.4s ease-out;
357 `;
358
359 // Заголовок
360 const header = document.createElement('div');
361 header.style.cssText = `
362 background: rgba(255,255,255,0.15);
363 backdrop-filter: blur(10px);
364 padding: 20px;
365 display: flex;
366 justify-content: space-between;
367 align-items: center;
368 border-bottom: 1px solid rgba(255,255,255,0.2);
369 `;
370 header.innerHTML = `
371 <div style="display: flex; align-items: center; gap: 12px;">
372 <span style="font-size: 28px;">🤖</span>
373 <div>
374 <h3 style="margin: 0; color: white; font-size: 18px; font-weight: 600;">AI Аналитика Ozon</h3>
375 <p style="margin: 4px 0 0 0; color: rgba(255,255,255,0.8); font-size: 12px;">Проанализировано товаров: ${productsData.length}</p>
376 </div>
377 </div>
378 <button id="close-analysis-panel" style="
379 background: rgba(255,255,255,0.2);
380 border: none;
381 color: white;
382 width: 32px;
383 height: 32px;
384 border-radius: 8px;
385 cursor: pointer;
386 font-size: 20px;
387 display: flex;
388 align-items: center;
389 justify-content: center;
390 transition: all 0.2s;
391 ">×</button>
392 `;
393
394 // Контент
395 const content = document.createElement('div');
396 content.style.cssText = `
397 padding: 20px;
398 max-height: calc(80vh - 100px);
399 overflow-y: auto;
400 background: white;
401 `;
402
403 // Общий вывод
404 const summary = document.createElement('div');
405 summary.style.cssText = `
406 background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
407 color: white;
408 padding: 16px;
409 border-radius: 12px;
410 margin-bottom: 20px;
411 font-size: 14px;
412 line-height: 1.6;
413 box-shadow: 0 4px 12px rgba(245,87,108,0.3);
414 `;
415 summary.innerHTML = `
416 <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
417 <span style="font-size: 20px;">📊</span>
418 <strong style="font-size: 15px;">Общий вывод</strong>
419 </div>
420 <p style="margin: 0;">${aiAnalysis.summary}</p>
421 `;
422 content.appendChild(summary);
423
424 // Статистика
425 const stats = document.createElement('div');
426 stats.style.cssText = `
427 display: grid;
428 grid-template-columns: repeat(2, 1fr);
429 gap: 12px;
430 margin-bottom: 20px;
431 `;
432
433 const criticalCount = aiAnalysis.products.filter(p => p.severity === 'critical').length;
434 const highCount = aiAnalysis.products.filter(p => p.severity === 'high').length;
435 const mediumCount = aiAnalysis.products.filter(p => p.severity === 'medium').length;
436 const lowCount = aiAnalysis.products.filter(p => p.severity === 'low').length;
437
438 stats.innerHTML = `
439 <div data-filter="critical" style="background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
440 <div style="font-size: 24px; font-weight: bold;">${criticalCount}</div>
441 <div style="font-size: 12px; opacity: 0.9;">Критичных</div>
442 </div>
443 <div data-filter="high" style="background: linear-gradient(135deg, #ffa502 0%, #ff7f50 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
444 <div style="font-size: 24px; font-weight: bold;">${highCount}</div>
445 <div style="font-size: 12px; opacity: 0.9;">Высокий приоритет</div>
446 </div>
447 <div data-filter="medium" style="background: linear-gradient(135deg, #ffd32a 0%, #ffb142 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
448 <div style="font-size: 24px; font-weight: bold;">${mediumCount}</div>
449 <div style="font-size: 12px; opacity: 0.9;">Средний приоритет</div>
450 </div>
451 <div data-filter="all" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); padding: 12px; border-radius: 10px; color: white; text-align: center; cursor: pointer; transition: all 0.3s;">
452 <div style="font-size: 24px; font-weight: bold;">${productsData.length}</div>
453 <div style="font-size: 12px; opacity: 0.9;">Всего товаров</div>
454 </div>
455 `;
456 content.appendChild(stats);
457
458 // Контейнер для списка товаров
459 const productsContainer = document.createElement('div');
460 productsContainer.id = 'products-container';
461
462 // Функция отрисовки товаров
463 function renderProducts(filter) {
464 currentFilter = filter;
465 productsContainer.innerHTML = '';
466
467 // Обновляем стили кнопок фильтра
468 stats.querySelectorAll('[data-filter]').forEach(btn => {
469 if (btn.dataset.filter === filter) {
470 btn.style.transform = 'scale(1.05)';
471 btn.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
472 } else {
473 btn.style.transform = 'scale(1)';
474 btn.style.boxShadow = 'none';
475 }
476 });
477
478 // Фильтруем товары
479 let filteredProducts = aiAnalysis.products;
480 if (filter !== 'all') {
481 filteredProducts = aiAnalysis.products.filter(p => p.severity === filter);
482 }
483
484 if (filteredProducts.length === 0) {
485 productsContainer.innerHTML = '<p style="text-align: center; color: #666; padding: 20px;">Нет товаров с таким уровнем проблем</p>';
486 return;
487 }
488
489 const productsTitle = document.createElement('h4');
490 productsTitle.textContent = '⚠️ Проблемные товары';
491 productsTitle.style.cssText = `
492 margin: 0 0 16px 0;
493 color: #333;
494 font-size: 16px;
495 font-weight: 600;
496 `;
497 productsContainer.appendChild(productsTitle);
498
499 filteredProducts.forEach(aiProduct => {
500 const productData = productsData.find(p => p.article === aiProduct.article);
501 if (!productData) return;
502
503 const severityColors = {
504 critical: { bg: '#ffe0e0', border: '#ff6b6b', icon: '🔴' },
505 high: { bg: '#fff4e0', border: '#ffa502', icon: '🟠' },
506 medium: { bg: '#fff9e0', border: '#ffd32a', icon: '🟡' },
507 low: { bg: '#e0f7ff', border: '#4facfe', icon: '🔵' }
508 };
509
510 const colors = severityColors[aiProduct.severity];
511
512 // Расчет дополнительных метрик
513 const daysOfStock = productData.ordersCount > 0 ? Math.round(productData.stock / (productData.ordersCount / 7)) : 0;
514 const ctr = productData.views > 0 && productData.cardViews > 0 ? ((productData.cardViews / productData.views) * 100).toFixed(2) : 0;
515 const crl = productData.cardViews > 0 && productData.addToCart > 0 ? ((productData.addToCart / productData.cardViews) * 100).toFixed(2) : 0;
516 const cr = productData.cardViews > 0 && productData.ordersCount > 0 ? ((productData.ordersCount / productData.cardViews) * 100).toFixed(2) : 0;
517
518 const productCard = document.createElement('div');
519 productCard.style.cssText = `
520 background: ${colors.bg};
521 border-left: 4px solid ${colors.border};
522 padding: 16px;
523 border-radius: 10px;
524 margin-bottom: 12px;
525 transition: all 0.3s;
526 cursor: pointer;
527 `;
528
529 // Улучшенное отображение проблем с процентами
530 const problemsWithPercents = aiProduct.problems.map(problem => {
531 let problemText = problem;
532
533 // Добавляем проценты изменения к проблемам
534 if (problem.toLowerCase().includes('выручк') && productData.revenueChange !== null) {
535 problemText += ` (${productData.revenueChange > 0 ? '+' : ''}${productData.revenueChange}%)`;
536 }
537 if (problem.toLowerCase().includes('заказ') && productData.ordersCountChange !== null) {
538 problemText += ` (${productData.ordersCountChange > 0 ? '+' : ''}${productData.ordersCountChange}%)`;
539 }
540 if (problem.toLowerCase().includes('показ') && productData.viewsChange !== null) {
541 problemText += ` (${productData.viewsChange > 0 ? '+' : ''}${productData.viewsChange}%)`;
542 }
543 if (problem.toLowerCase().includes('конверси') && productData.conversionCardToCartChange !== null) {
544 problemText += ` (${productData.conversionCardToCartChange > 0 ? '+' : ''}${productData.conversionCardToCartChange}%)`;
545 }
546 if (problem.toLowerCase().includes('дрр') && productData.drrChange !== null) {
547 problemText += ` (${productData.drrChange > 0 ? '+' : ''}${productData.drrChange}%)`;
548 }
549 if (problem.toLowerCase().includes('цен') && productData.avgPriceChange !== null) {
550 problemText += ` (${productData.avgPriceChange > 0 ? '+' : ''}${productData.avgPriceChange}%)`;
551 }
552
553 return problemText;
554 });
555
556 productCard.innerHTML = `
557 <div style="display: flex; align-items: start; gap: 10px; margin-bottom: 12px;">
558 <span style="font-size: 20px;">${colors.icon}</span>
559 <div style="flex: 1;">
560 <div style="font-weight: 600; color: #333; font-size: 14px; margin-bottom: 4px;">
561 ${productData.name.substring(0, 60)}${productData.name.length > 60 ? '...' : ''}
562 </div>
563 <div style="color: #666; font-size: 12px;">Арт. ${productData.article}</div>
564 </div>
565 </div>
566
567 <div style="background: white; padding: 12px; border-radius: 8px; margin-bottom: 12px;">
568 <div style="font-size: 12px; color: #666; margin-bottom: 8px; font-weight: 600;">📉 Проблемы:</div>
569 ${problemsWithPercents.map(p => `
570 <div style="font-size: 12px; color: #333; margin-bottom: 4px; padding-left: 8px; border-left: 2px solid ${colors.border};">
571 • ${p}
572 </div>
573 `).join('')}
574 </div>
575
576 <div style="background: white; padding: 12px; border-radius: 8px; margin-bottom: 12px;">
577 <div style="font-size: 12px; color: #666; margin-bottom: 8px; font-weight: 600;">💡 Рекомендации:</div>
578 ${aiProduct.recommendations.map(r => `
579 <div style="font-size: 12px; color: #333; margin-bottom: 4px; padding-left: 8px; border-left: 2px solid #4caf50;">
580 ✓ ${r}
581 </div>
582 `).join('')}
583 </div>
584
585 <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; font-size: 11px;">
586 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
587 <div style="color: #666;">Заказы</div>
588 <div style="font-weight: 600; color: ${productData.ordersCountChange < 0 ? '#ff6b6b' : '#4caf50'};">
589 ${productData.ordersCount || 'н/д'} ${productData.ordersCountChange !== null ? (productData.ordersCountChange > 0 ? '↑' : '↓') + Math.abs(productData.ordersCountChange) + '%' : ''}
590 </div>
591 </div>
592 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
593 <div style="color: #666;">Показы</div>
594 <div style="font-weight: 600; color: ${productData.viewsChange < 0 ? '#ff6b6b' : '#4caf50'};">
595 ${productData.views || 'н/д'} ${productData.viewsChange !== null ? (productData.viewsChange > 0 ? '↑' : '↓') + Math.abs(productData.viewsChange) + '%' : ''}
596 </div>
597 </div>
598 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
599 <div style="color: #666;">Остаток</div>
600 <div style="font-weight: 600; color: #333;">
601 ${productData.stock || 'н/д'}
602 </div>
603 </div>
604 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
605 <div style="color: #666;">Дней остатка</div>
606 <div style="font-weight: 600; color: ${daysOfStock < 7 ? '#ff6b6b' : daysOfStock < 14 ? '#ffa502' : '#4caf50'};">
607 ${daysOfStock > 0 ? daysOfStock : 'н/д'}
608 </div>
609 </div>
610 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
611 <div style="color: #666;">Клики</div>
612 <div style="font-weight: 600; color: #333;">
613 ${productData.cardViews || 'н/д'}
614 </div>
615 </div>
616 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
617 <div style="color: #666;">CTR</div>
618 <div style="font-weight: 600; color: #333;">
619 ${ctr}%
620 </div>
621 </div>
622 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
623 <div style="color: #666;">CRL</div>
624 <div style="font-weight: 600; color: #333;">
625 ${crl}%
626 </div>
627 </div>
628 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
629 <div style="color: #666;">CR</div>
630 <div style="font-weight: 600; color: #333;">
631 ${cr}%
632 </div>
633 </div>
634 <div style="background: white; padding: 8px; border-radius: 6px; text-align: center;">
635 <div style="color: #666;">ДРР</div>
636 <div style="font-weight: 600; color: ${productData.drr > 30 ? '#ff6b6b' : productData.drr > 20 ? '#ffa502' : '#4caf50'};">
637 ${productData.drr !== null ? productData.drr + '%' : 'н/д'}
638 </div>
639 </div>
640 </div>
641 `;
642
643 // Hover эффект
644 productCard.addEventListener('mouseenter', () => {
645 productCard.style.transform = 'translateX(-4px)';
646 productCard.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
647 });
648 productCard.addEventListener('mouseleave', () => {
649 productCard.style.transform = 'translateX(0)';
650 productCard.style.boxShadow = 'none';
651 });
652
653 // Клик по товару - фильтрация по артикулу
654 productCard.addEventListener('click', () => {
655 console.log('🔍 Фильтруем по артикулу:', productData.article);
656 filterByArticle(productData.article);
657 });
658
659 productsContainer.appendChild(productCard);
660 });
661 }
662
663 // Добавляем обработчики на кнопки фильтра
664 stats.querySelectorAll('[data-filter]').forEach(btn => {
665 btn.addEventListener('click', () => {
666 renderProducts(btn.dataset.filter);
667 });
668
669 // Hover эффект
670 btn.addEventListener('mouseenter', () => {
671 if (btn.dataset.filter !== currentFilter) {
672 btn.style.transform = 'scale(1.05)';
673 }
674 });
675 btn.addEventListener('mouseleave', () => {
676 if (btn.dataset.filter !== currentFilter) {
677 btn.style.transform = 'scale(1)';
678 }
679 });
680 });
681
682 content.appendChild(productsContainer);
683
684 // Изначально показываем все товары
685 renderProducts('all');
686
687 panel.appendChild(header);
688 panel.appendChild(content);
689 document.body.appendChild(panel);
690
691 // Обработчик закрытия
692 document.getElementById('close-analysis-panel').addEventListener('click', () => {
693 panel.style.animation = 'slideOut 0.3s ease-in';
694 setTimeout(() => panel.remove(), 300);
695 });
696
697 // Стили для анимации
698 if (!document.getElementById('ozon-ai-styles')) {
699 const styles = document.createElement('style');
700 styles.id = 'ozon-ai-styles';
701 styles.textContent = `
702 @keyframes slideIn {
703 from {
704 transform: translateX(500px);
705 opacity: 0;
706 }
707 to {
708 transform: translateX(0);
709 opacity: 1;
710 }
711 }
712 @keyframes slideOut {
713 from {
714 transform: translateX(0);
715 opacity: 1;
716 }
717 to {
718 transform: translateX(500px);
719 opacity: 0;
720 }
721 }
722 #close-analysis-panel:hover {
723 background: rgba(255,255,255,0.3) !important;
724 transform: scale(1.1);
725 }
726 #ozon-ai-analysis-panel::-webkit-scrollbar {
727 width: 8px;
728 }
729 #ozon-ai-analysis-panel::-webkit-scrollbar-track {
730 background: #f1f1f1;
731 border-radius: 10px;
732 }
733 #ozon-ai-analysis-panel::-webkit-scrollbar-thumb {
734 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
735 border-radius: 10px;
736 }
737 `;
738 document.head.appendChild(styles);
739 }
740
741 console.log('✅ Панель создана');
742 }
743
744 // Функция фильтрации по артикулу на странице
745 function filterByArticle(article) {
746 console.log('🔍 Применяем фильтр по артикулу:', article);
747
748 // Ищем кнопку "Артикул" в фильтрах
749 const filterButtons = document.querySelectorAll('button');
750 let articleFilterButton = null;
751
752 filterButtons.forEach(btn => {
753 if (btn.textContent.trim().toLowerCase().includes('артикул')) {
754 articleFilterButton = btn;
755 }
756 });
757
758 if (articleFilterButton) {
759 console.log('✅ Найдена кнопка фильтра "Артикул"');
760
761 // Кликаем на кнопку "Артикул"
762 articleFilterButton.click();
763
764 // Ждем появления поля ввода
765 setTimeout(() => {
766 // Ищем поле ввода артикула
767 const articleInput = document.querySelector('input[placeholder*="артикул" i], input[name*="article" i], input[placeholder*="Артикул" i]');
768
769 if (articleInput) {
770 console.log('✅ Найдено поле ввода артикула');
771
772 // Очищаем и вводим артикул
773 articleInput.value = '';
774 articleInput.focus();
775 articleInput.value = article;
776
777 // Триггерим события
778 articleInput.dispatchEvent(new Event('input', { bubbles: true }));
779 articleInput.dispatchEvent(new Event('change', { bubbles: true }));
780 articleInput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
781
782 // Ищем кнопку "Применить"
783 setTimeout(() => {
784 const applyButtons = document.querySelectorAll('button');
785 let applyButton = null;
786
787 applyButtons.forEach(btn => {
788 const text = btn.textContent.trim().toLowerCase();
789 if (text === 'применить' || text === 'apply') {
790 applyButton = btn;
791 }
792 });
793
794 if (applyButton) {
795 console.log('✅ Найдена кнопка "Применить"');
796 applyButton.click();
797 console.log('✅ Фильтр применен');
798 } else {
799 console.log('⚠️ Кнопка "Применить" не найдена');
800 }
801 }, 500);
802 } else {
803 console.log('⚠️ Поле ввода артикула не найдено');
804 }
805 }, 500);
806 } else {
807 console.log('⚠️ Кнопка фильтра "Артикул" не найдена');
808 }
809 }
810
811 // Создание кнопки запуска анализа
812 function createAnalyzeButton() {
813 // Удаляем старую кнопку если есть
814 const oldButton = document.getElementById('ozon-ai-analyze-btn');
815 if (oldButton) oldButton.remove();
816
817 const button = document.createElement('button');
818 button.id = 'ozon-ai-analyze-btn';
819 button.innerHTML = '🤖 AI Анализ';
820 button.style.cssText = `
821 position: fixed;
822 top: 20px;
823 right: 20px;
824 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
825 color: white;
826 border: none;
827 padding: 14px 24px;
828 border-radius: 12px;
829 font-size: 15px;
830 font-weight: 600;
831 cursor: pointer;
832 z-index: 9999;
833 box-shadow: 0 8px 24px rgba(102,126,234,0.4);
834 transition: all 0.3s;
835 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
836 `;
837
838 button.addEventListener('mouseenter', () => {
839 button.style.transform = 'translateY(-2px)';
840 button.style.boxShadow = '0 12px 32px rgba(102,126,234,0.5)';
841 });
842
843 button.addEventListener('mouseleave', () => {
844 button.style.transform = 'translateY(0)';
845 button.style.boxShadow = '0 8px 24px rgba(102,126,234,0.4)';
846 });
847
848 button.addEventListener('click', async () => {
849 button.disabled = true;
850 button.innerHTML = '⏳ Загрузка...';
851
852 try {
853 // Загружаем все товары
854 await loadAllProducts();
855
856 button.innerHTML = '📊 Парсинг...';
857 await delay(500);
858
859 // Парсим данные
860 const products = parseProductsData();
861
862 if (products.length === 0) {
863 alert('Не удалось найти товары на странице');
864 button.disabled = false;
865 button.innerHTML = '🤖 AI Анализ';
866 return;
867 }
868
869 button.innerHTML = '🤖 AI анализ...';
870
871 // AI анализ
872 const aiAnalysis = await analyzeProductsWithAI(products);
873
874 // Создаем панель с результатами
875 createAnalysisPanel(products, aiAnalysis);
876
877 button.innerHTML = '✅ Готово!';
878 await delay(2000);
879 button.innerHTML = '🤖 AI Анализ';
880 button.disabled = false;
881
882 } catch (error) {
883 console.error('Ошибка анализа:', error);
884 alert('Произошла ошибка при анализе. Проверьте консоль.');
885 button.innerHTML = '🤖 AI Анализ';
886 button.disabled = false;
887 }
888 });
889
890 document.body.appendChild(button);
891 }
892
893 // Инициализация
894 function init() {
895 console.log('🎯 Инициализация Ozon AI Аналитика...');
896
897 // Ждем загрузки страницы
898 if (document.readyState === 'loading') {
899 document.addEventListener('DOMContentLoaded', () => {
900 setTimeout(createAnalyzeButton, 1000);
901 });
902 } else {
903 setTimeout(createAnalyzeButton, 1000);
904 }
905
906 // Пересоздаем кнопку при изменении DOM (навигация в SPA)
907 const observer = new MutationObserver(debounce(() => {
908 if (!document.getElementById('ozon-ai-analyze-btn')) {
909 createAnalyzeButton();
910 }
911 }, 1000));
912
913 observer.observe(document.body, {
914 childList: true,
915 subtree: true
916 });
917 }
918
919 init();
920
921})();