/
Naxxato
/
VIS
Обзор
Документация
Войти
/
Naxxato
/
VIS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
modules/scripts.txt
836 строк
32 KB
Naxxato
upload files
15 ноя 2025, 15:02
15 ноя 2025, 15:02
a25c946
Код
Авторство
О чём код?
class ScriptsModule { constructor(container, savedData) { this.container = container; this.data = savedData && Array.isArray(savedData.products) ? savedData : { products: [] }; if (!Array.isArray(this.data.products)) this.data.products = []; this.selected = { product: 0, category: 0 }; // selected.category will now represent the *active* category for editing purposes, but all will be rendered if (savedData && savedData.selected) { this.selected = savedData.selected; } // Drag & Drop state this.draggedElement = null; this.dragOverCategory = null; // AI Integration this.aiIntegrationEnabled = true; this.autoLearnThreshold = 0.7; // Порог для автоматического обучения ИИ this.settings = this._resolveSettings(); this._handleSettingsUpdate = (event) => { if (!event || !event.detail) return; const { module, settings } = event.detail; if (module && module !== 'scripts') return; if (settings && settings.scripts) { this.settings = this._resolveSettings(settings); } else { this.settings = this._resolveSettings(); } this.render(); }; this._handleApplyColor = (event) => { if (!event || !event.detail || !event.detail.color) return; this._applyColorToAllScripts(event.detail.color); }; document.addEventListener('vis-settings-updated', this._handleSettingsUpdate); document.addEventListener('vis-apply-scripts-color', this._handleApplyColor); this.render(); } destroy() { document.removeEventListener('vis-settings-updated', this._handleSettingsUpdate); document.removeEventListener('vis-apply-scripts-color', this._handleApplyColor); } /** * Поиск по скриптам для ИИ-помощника */ async search(query) { const results = []; const lowerQuery = query.toLowerCase(); this.data.products.forEach((product, productIndex) => { product.categories.forEach((category, categoryIndex) => { category.scripts.forEach((script, scriptIndex) => { const title = script.title || ''; const text = script.text || ''; const combinedText = `${title} ${text}`.toLowerCase(); if (combinedText.includes(lowerQuery)) { results.push({ title: `${product.name} > ${category.name} > ${title}`, context: text.substring(0, 200) + (text.length > 200 ? '...' : ''), moduleName: 'scripts', productIndex, categoryIndex, scriptIndex, focus: () => { this.selected.product = productIndex; this.selected.category = categoryIndex; this.render(); // Подсвечиваем найденный скрипт setTimeout(() => { const scriptElement = this.container.querySelector(`[data-script-index="${scriptIndex}"]`); if (scriptElement) { scriptElement.classList.add('search-highlight'); scriptElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => scriptElement.classList.remove('search-highlight'), 3000); } }, 100); } }); } }); }); }); return results; } /** * Интеграция с ИИ-помощником: автоматическое обучение на основе скриптов */ async syncWithAI() { if (!this.aiIntegrationEnabled) return; try { // Получаем все скрипты для обучения ИИ const allScripts = this._getAllScripts(); // Отправляем скрипты в ИИ-помощник для обучения for (const script of allScripts) { await this._teachAIScript(script); } showGlobalToast('🤖 Скрипты синхронизированы с ИИ-помощником'); } catch (error) { console.error('Ошибка синхронизации с ИИ:', error); showGlobalToast('❌ Ошибка синхронизации с ИИ-помощником'); } } /** * Получение всех скриптов для обучения ИИ */ _getAllScripts() { const scripts = []; this.data.products.forEach((product, productIndex) => { product.categories.forEach((category, categoryIndex) => { category.scripts.forEach((script, scriptIndex) => { if (script.text && script.text.trim()) { scripts.push({ question: this._generateQuestionFromScript(script, product, category), answer: script.text, source: `Скрипт: ${product.name} > ${category.name}`, scriptId: `${productIndex}_${categoryIndex}_${scriptIndex}`, productIndex, categoryIndex, scriptIndex }); } }); }); }); return scripts; } /** * Генерация вопроса для ИИ на основе скрипта */ _generateQuestionFromScript(script, product, category) { const productName = product.name || 'продукт'; const categoryName = category.name || 'категория'; // Извлекаем ключевые слова из скрипта const keywords = this._extractKeywords(script.text); if (keywords.length > 0) { return `Как работать с ${keywords[0]} в ${productName} (${categoryName})?`; } return `Как использовать скрипт для ${productName} в категории ${categoryName}?`; } /** * Извлечение ключевых слов из текста скрипта */ _extractKeywords(text) { const words = text.toLowerCase() .replace(/[^\w\s]/g, ' ') .split(/\s+/) .filter(word => word.length > 3) .filter(word => !['это', 'что', 'как', 'для', 'при', 'или', 'но', 'если', 'когда', 'где'].includes(word)); // Подсчитываем частоту слов const wordCount = {}; words.forEach(word => { wordCount[word] = (wordCount[word] || 0) + 1; }); // Возвращаем топ-3 самых частых слов return Object.entries(wordCount) .sort(([,a], [,b]) => b - a) .slice(0, 3) .map(([word]) => word); } /** * Обучение ИИ конкретным скриптом */ async _teachAIScript(scriptData) { try { // Ищем ИИ-помощник в открытых окнах const aiWindows = document.querySelectorAll('.module-window'); for (const window of aiWindows) { if (window.moduleInstances && window.moduleInstances.aiAssistant) { const aiInstance = window.moduleInstances.aiAssistant; // Проверяем, есть ли уже такой скрипт в базе знаний ИИ const existingMatch = aiInstance._findBestAnswer(scriptData.question); if (!existingMatch || existingMatch.score < this.autoLearnThreshold) { // Добавляем новый скрипт в базу знаний ИИ aiInstance.kb.push({ id: 'script_' + scriptData.scriptId + '_' + Date.now(), question: scriptData.question, answer: scriptData.answer, source: scriptData.source, createdAt: Date.now(), usage: 0, fromScripts: true // Флаг, что это из модуля Скрипты }); aiInstance._saveKB(); } } } } catch (error) { console.error('Ошибка обучения ИИ скриптом:', error); } } /** * Получение статистики модуля */ getStats() { let totalScripts = 0; this.data.products.forEach(product => { product.categories.forEach(category => { totalScripts += (category.scripts || []).length; }); }); return { items: totalScripts, products: this.data.products.length, categories: this.data.products.reduce((total, product) => total + (product.categories || []).length, 0) }; } async render() { this.container.innerHTML = ''; const isEdit = typeof editMode !== 'undefined' ? editMode : true; this.settings = this._resolveSettings(); // Render Product Tabs (Horizontal) const prodTabs = document.createElement('div'); prodTabs.className = 'scripts-prod-tabs'; for (let i = 0; i < this.data.products.length; i++) { const p = this.data.products[i]; const tab = document.createElement('button'); tab.className = 'scripts-tab' + (this.selected.product === i ? ' active' : ''); tab.textContent = p.name || `Продукт ${i+1}`; tab.onclick = async (e) => { if (isEdit) { const res = await showModal({ title: 'Редактировать продукт', fields: [{ name: 'name', value: p.name }], // onDelete: true // Удаляем возможность удаления продукта }); if (res.action === 'save') { p.name = res.values.name; this.render(); } // if (res.action === 'delete') { // Удаляем блок удаления продукта // this.data.products.splice(i, 1); // if (this.selected.product >= this.data.products.length) this.selected.product = Math.max(0, this.data.products.length-1); // this.selected.category = 0; // this.render(); // } } else { this.selected.product = i; this.selected.category = 0; // Reset category selection when product changes this.render(); } }; prodTabs.appendChild(tab); } if (isEdit) { const addBtn = document.createElement('button'); addBtn.className = 'scripts-tab-add'; addBtn.innerHTML = '+ Продукт'; addBtn.onclick = async () => { const res = await showModal({ title: 'Добавить продукт', fields: [{ name: 'name', placeholder: 'Название продукта' }] }); if (res.action === 'save' && res.values.name.trim()) { this.data.products.push({ name: res.values.name.trim(), categories: [] }); this.selected.product = this.data.products.length-1; this.selected.category = 0; this.render(); } }; prodTabs.appendChild(addBtn); } this.container.appendChild(prodTabs); if (!this.data.products.length) { this.container.appendChild(this._empty('Нет продуктов. Включите режим редактирования.')); return; } const prod = this.data.products[this.selected.product]; prod.categories = prod.categories || []; // Main container for all category sections (Vertical) const scriptsCategoriesContainer = document.createElement('div'); scriptsCategoriesContainer.className = 'scripts-all-categories-container'; // New class for this container if (!prod.categories.length && !isEdit) { scriptsCategoriesContainer.appendChild(this._empty('Нет категорий в этом продукте.')); } else if (!prod.categories.length && isEdit) { scriptsCategoriesContainer.appendChild(this._empty('Нет категорий в этом продукте. Создайте первую категорию.')); } // Render Category Sections (Vertical) for (let i = 0; i < prod.categories.length; i++) { const c = prod.categories[i]; const categorySection = document.createElement('div'); categorySection.className = 'scripts-category-section'; const categoryHeader = document.createElement('button'); // Changed from div to button for consistency/clickability categoryHeader.className = 'scripts-category-header'; categoryHeader.textContent = c.name || `Категория ${i+1}`; // Добавляем подсказку о Drag & Drop в режиме редактирования if (isEdit) { categoryHeader.title = 'Перетащите скрипты сюда для перемещения между категориями'; } // Add edit/delete functionality to category header if in edit mode if (isEdit) { categoryHeader.onclick = async () => { const res = await showModal({ title: 'Редактировать категорию', fields: [{ name: 'name', value: c.name }], onDelete: true // Allow deleting categories }); if (res.action === 'save') { c.name = res.values.name; this.render(); } if (res.action === 'delete') { prod.categories.splice(i, 1); if (this.selected.category >= prod.categories.length) this.selected.category = Math.max(0, prod.categories.length - 1); this.render(); } }; } categorySection.appendChild(categoryHeader); // Scripts view for this category c.scripts = c.scripts || []; c.scripts.forEach(script => this._applyDefaultColor(script)); const layout = this.settings.layout || 'grid'; const density = this.settings.density || 'comfortable'; const columns = Math.max(1, parseInt(this.settings.columns, 10) || 4); const appendScriptControls = (targetElement, script, scriptIndex) => { if (!isEdit) return; const editBtn = document.createElement('button'); editBtn.className = 'scripts-card-edit'; editBtn.innerHTML = '✏️'; editBtn.title = 'Редактировать скрипт'; editBtn.onclick = async (e) => { e.stopPropagation(); const res = await showModal({ title: 'Редактировать скрипт', fields: [ { name: 'text', value: script.text, type: 'textarea' }, { name: 'color', value: script.color || this.settings.globalColor, label: 'Цвет (HEX)', placeholder: '#rrggbb', type: 'color' } ], onDelete: true }); if (res.action === 'save') { script.text = res.values.text; script.color = res.values.color; this._applyDefaultColor(script); this.render(); } if (res.action === 'delete') { c.scripts.splice(scriptIndex, 1); this.render(); } }; targetElement.appendChild(editBtn); const delBtn = document.createElement('button'); delBtn.className = 'scripts-card-del'; delBtn.textContent = '×'; delBtn.title = 'Удалить скрипт'; delBtn.onclick = (e) => { e.stopPropagation(); if (confirm('Вы уверены, что хотите удалить этот скрипт?')) { c.scripts.splice(scriptIndex, 1); this.render(); } }; targetElement.appendChild(delBtn); }; let layoutContainer; const cardMinWidth = this._resolveCardMinWidth(columns); if (layout === 'labels') { const labelsContainer = document.createElement('div'); labelsContainer.className = 'scripts-labels'; if (!c.scripts.length && !isEdit) { const emptyScripts = document.createElement('div'); emptyScripts.className = 'scripts-empty'; emptyScripts.textContent = 'Нет скриптов в этой категории.'; labelsContainer.appendChild(emptyScripts); } c.scripts.forEach((s, sIdx) => { const label = document.createElement('div'); label.className = 'scripts-label'; if (s.color) label.style.backgroundColor = s.color; const text = document.createElement('span'); text.className = 'scripts-label-text'; text.textContent = this._truncateText(s.text || '', 120); label.appendChild(text); if (isEdit) { const actions = document.createElement('div'); actions.className = 'scripts-label-actions'; appendScriptControls(actions, s, sIdx); label.appendChild(actions); } label.onclick = (e) => { if (isEdit && (e.target.closest('button'))) return; if (!isEdit) { this._copyScriptText(s); } }; labelsContainer.appendChild(label); }); layoutContainer = labelsContainer; } else { const scriptsGrid = document.createElement('div'); scriptsGrid.className = 'scripts-grid'; scriptsGrid.dataset.layout = layout === 'list' ? 'list' : 'grid'; scriptsGrid.dataset.density = density; scriptsGrid.dataset.columns = layout === 'grid' ? columns : ''; scriptsGrid.style.setProperty('--scripts-card-min-width', `${cardMinWidth}px`); if (!c.scripts.length && !isEdit) { const emptyScripts = document.createElement('div'); emptyScripts.className = 'scripts-empty'; emptyScripts.textContent = 'Нет скриптов в этой категории.'; scriptsGrid.appendChild(emptyScripts); } for (let sIdx = 0; sIdx < c.scripts.length; sIdx++) { const s = c.scripts[sIdx]; const card = document.createElement('div'); card.className = 'scripts-card'; if (s.color) card.style.backgroundColor = s.color; card.title = s.text || ''; const allowDrag = isEdit && layout === 'grid'; card.draggable = allowDrag; if (allowDrag) { card.dataset.scriptIndex = sIdx; card.dataset.categoryIndex = i; card.dataset.productIndex = this.selected.product; } appendScriptControls(card, s, sIdx); const text = document.createElement('div'); text.className = 'scripts-card-text'; text.textContent = this._truncateText(s.text || '', 300); card.appendChild(text); card.onclick = () => { if (!isEdit) { this._copyScriptText(s); } }; scriptsGrid.appendChild(card); } layoutContainer = scriptsGrid; } let addScriptBtn = null; if (isEdit) { addScriptBtn = document.createElement('button'); addScriptBtn.className = 'scripts-card-add'; addScriptBtn.textContent = '+ Добавить скрипт'; addScriptBtn.onclick = async () => { const res = await showModal({ title: 'Добавить скрипт', fields: [ { name: 'text', placeholder: 'Текст скрипта...', type: 'textarea' }, { name: 'color', label: 'Цвет (HEX)', value: this.settings.globalColor, placeholder: '#rrggbb', type: 'color' } ] }); if (res.action === 'save') { const newScript = { text: res.values.text, color: res.values.color }; this._applyDefaultColor(newScript); c.scripts.push(newScript); this.render(); } }; } categorySection.appendChild(layoutContainer); if (addScriptBtn) { layoutContainer.appendChild(addScriptBtn); } scriptsCategoriesContainer.appendChild(categorySection); } // Add Category button at the bottom of the categories container if (isEdit) { const addCategoryBtn = document.createElement('button'); addCategoryBtn.className = 'scripts-tab-add scripts-add-category-button-bottom'; // New class for bottom button addCategoryBtn.innerHTML = '+ Категория'; addCategoryBtn.onclick = async () => { const res = await showModal({ title: 'Добавить категорию', fields: [{ name: 'name', placeholder: 'Название категории' }] }); if (res.action === 'save' && res.values.name.trim()) { prod.categories.push({ name: res.values.name.trim(), scripts: [] }); this.render(); } }; scriptsCategoriesContainer.appendChild(addCategoryBtn); } this.container.appendChild(scriptsCategoriesContainer); // Append the main categories container // Добавляем обработчики Drag & Drop this._setupDragAndDrop(); } save() { return { ...this.data, selected: this.selected }; } /** * Настройка Drag & Drop функциональности */ _setupDragAndDrop() { const isEdit = typeof editMode !== 'undefined' ? editMode : true; // Drag & Drop работает только в режиме редактирования if (!isEdit) return; const layout = this.settings && this.settings.layout ? this.settings.layout : 'grid'; if (layout !== 'grid') return; const scriptCards = this.container.querySelectorAll('.scripts-card'); const categorySections = this.container.querySelectorAll('.scripts-category-section'); // Обработчики для карточек скриптов scriptCards.forEach(card => { card.addEventListener('dragstart', (e) => this._handleDragStart(e)); card.addEventListener('dragend', (e) => this._handleDragEnd(e)); }); // Обработчики для категорий (drop zones) categorySections.forEach(section => { section.addEventListener('dragover', (e) => this._handleDragOver(e)); section.addEventListener('dragenter', (e) => this._handleDragEnter(e)); section.addEventListener('dragleave', (e) => this._handleDragLeave(e)); section.addEventListener('drop', (e) => this._handleDrop(e)); }); } /** * Начало перетаскивания */ _handleDragStart(e) { const isEdit = typeof editMode !== 'undefined' ? editMode : true; // Проверяем, что мы в режиме редактирования if (!isEdit) { e.preventDefault(); return; } this.draggedElement = e.target; e.target.style.opacity = '0.5'; e.target.classList.add('dragging'); // Сохраняем данные о перетаскиваемом элементе e.dataTransfer.setData('text/plain', JSON.stringify({ scriptIndex: parseInt(e.target.dataset.scriptIndex), categoryIndex: parseInt(e.target.dataset.categoryIndex), productIndex: parseInt(e.target.dataset.productIndex) })); e.dataTransfer.effectAllowed = 'move'; } /** * Конец перетаскивания */ _handleDragEnd(e) { e.target.style.opacity = ''; e.target.classList.remove('dragging'); this.draggedElement = null; this.dragOverCategory = null; // Убираем подсветку со всех категорий this.container.querySelectorAll('.scripts-category-section').forEach(section => { section.classList.remove('drag-over'); }); } /** * Перетаскивание над элементом */ _handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } /** * Вход в зону перетаскивания */ _handleDragEnter(e) { e.preventDefault(); const categorySection = e.target.closest('.scripts-category-section'); if (categorySection) { this.dragOverCategory = categorySection; categorySection.classList.add('drag-over'); } } /** * Выход из зоны перетаскивания */ _handleDragLeave(e) { const categorySection = e.target.closest('.scripts-category-section'); if (categorySection && !categorySection.contains(e.relatedTarget)) { categorySection.classList.remove('drag-over'); } } /** * Сброс элемента */ _handleDrop(e) { e.preventDefault(); const isEdit = typeof editMode !== 'undefined' ? editMode : true; // Проверяем, что мы в режиме редактирования if (!isEdit) { return; } try { const dragData = JSON.parse(e.dataTransfer.getData('text/plain')); const targetCategorySection = e.target.closest('.scripts-category-section'); if (!targetCategorySection || !dragData) return; // Получаем индекс целевой категории const targetCategoryIndex = Array.from(this.container.querySelectorAll('.scripts-category-section')) .indexOf(targetCategorySection); // Проверяем, что скрипт не перемещается в ту же категорию if (dragData.categoryIndex === targetCategoryIndex) { return; } // Получаем данные о скрипте const product = this.data.products[dragData.productIndex]; const sourceCategory = product.categories[dragData.categoryIndex]; const script = sourceCategory.scripts[dragData.scriptIndex]; // Удаляем скрипт из исходной категории sourceCategory.scripts.splice(dragData.scriptIndex, 1); // Добавляем скрипт в целевую категорию const targetCategory = product.categories[targetCategoryIndex]; targetCategory.scripts.push(script); // Перерисовываем интерфейс this.render(); // Показываем уведомление showGlobalToast(`📋 Скрипт перемещен в категорию "${targetCategory.name}"`); // Автоматическая синхронизация с ИИ при перемещении if (this.aiIntegrationEnabled) { setTimeout(() => this.syncWithAI(), 500); } } catch (error) { console.error('Ошибка при перемещении скрипта:', error); showGlobalToast('❌ Ошибка при перемещении скрипта'); } } _resolveSettings(external) { const base = { layout: 'grid', columns: 4, density: 'comfortable', globalColor: '#f48fb1', useGlobalColor: false }; const source = external && external.scripts ? external.scripts : (typeof window !== 'undefined' && window.getVisSettings ? window.getVisSettings().scripts : null); if (!source) return { ...base }; return { ...base, ...source }; } _resolveCardMinWidth(columns) { const presets = { 1: 420, 2: 320, 3: 260, 4: 220, 5: 200, 6: 180 }; const normalized = Math.max(1, Math.min(Number.isFinite(columns) ? columns : 4, 6)); return presets[normalized] || 220; } _applyDefaultColor(script) { if (!script) return; if (script.color && script.color.trim()) return; if (this.settings.useGlobalColor && this.settings.globalColor) { script.color = this.settings.globalColor; } else { script.color = this._getRandomColor(); } } _applyColorToAllScripts(color) { if (!color) return; this.data.products.forEach(product => { (product.categories || []).forEach(category => { (category.scripts || []).forEach(script => { script.color = color; }); }); }); this.render(); if (typeof saveAll === 'function') { try { saveAll(); } catch (err) { console.warn('Не удалось сохранить состояние после изменения цвета', err); } } } _truncateText(text, limit = 300) { if (!text) return ''; const trimmed = text.trim(); if (trimmed.length <= limit) return trimmed; return trimmed.slice(0, limit) + '...'; } _copyScriptText(script) { if (!navigator.clipboard) return; const text = script && script.text ? script.text : ''; navigator.clipboard.writeText(text).then(() => { let toastText = text; if (toastText.length > 100) toastText = `${toastText.slice(0, 100)}...`; showGlobalToast(`Скопировано:\n${toastText}`); }); } _getRandomColor() { const colors = [ '#ffab91', '#ffcc80', '#e6ee9b', '#80deea', '#8c9eff', '#b39ddb', '#f48fb1', '#ff8a65', '#ffd54f', '#dce775', '#4dd0e1', '#7986cb', '#ce93d8', '#f06292', '#ff7043', '#ffca28', '#cddc39', '#26c6da' ]; return colors[Math.floor(Math.random() * colors.length)]; } search(query) { const results = []; const queryLower = query.toLowerCase(); this.data.products.forEach((p, pIndex) => { p.categories.forEach((c, cIndex) => { c.scripts.forEach((s, sIndex) => { if (s.text.toLowerCase().includes(queryLower)) { results.push({ title: `${p.name || `Продукт ${pIndex + 1}`} > ${c.name || `Категория ${cIndex + 1}`}`, context: `В скрипте: "...${s.text.substring(0, 100)}..."`, focus: () => { const win = this.container.closest('.module-window'); if (win) { const allWindows = document.querySelectorAll('.module-window'); let maxZ = 0; allWindows.forEach(w => { const z = parseInt(w.style.zIndex, 10) || 0; if (z > maxZ) maxZ = z; }); win.style.zIndex = maxZ + 1; win.winConfig.activeModule = 'scripts'; this.selected.product = pIndex; renderWindowContent(win); setTimeout(() => { const categorySections = this.container.querySelectorAll('.scripts-category-section'); if (categorySections[cIndex]) { categorySections[cIndex].scrollIntoView({ behavior: 'smooth', block: 'start' }); const card = categorySections[cIndex].querySelectorAll('.scripts-card')[sIndex]; if(card) { card.style.outline = '2px solid var(--color-accent)'; card.style.background = 'rgba(79,140,255,0.08)'; setTimeout(() => { card.style.outline = ''; card.style.background = ''; }, 2000); } } }, 100); } } }); } }); }); }); return results; } _empty(msg) { const d = document.createElement('div'); d.className = 'scripts-empty'; d.textContent = msg; return d; } }