/** * Query Builder UI * * UI компонент для построителя запросов. */ import appState from '../state/appState.js' import queryService from '../services/query.service.js' import loadingUI from './loading.ui.js' import { validateJSON } from '../utils/validation.utils.js' import { addClass, removeClass, hideElement, showElement, getInputValue, setElementText } from '../utils/dom.utils.js' import { showToast } from '../utils/dom.utils.js' /** * Показать построитель запросов */ export function show() { const queryBuilder = document.getElementById('query-builder') const answerViewer = document.getElementById('answer-viewer') if (queryBuilder) { removeClass(queryBuilder, 'hidden') } if (answerViewer) { addClass(answerViewer, 'hidden') } } /** * Переключить режим запроса (questions / raw-json) * @param {string} mode - Режим ('questions' или 'raw-json') */ export function switchMode(mode) { // Update toggle buttons const toggleButtons = document.querySelectorAll('.toggle-option') toggleButtons.forEach(btn => { removeClass(btn, 'active') if (btn.dataset.mode === mode) { addClass(btn, 'active') } }) // Show/hide mode panels const questionsMode = document.getElementById('questions-mode') const rawJsonMode = document.getElementById('raw-json-mode') if (mode === 'questions') { showElement(questionsMode) hideElement(rawJsonMode) } else if (mode === 'raw-json') { hideElement(questionsMode) showElement(rawJsonMode) } } /** * Валидация JSON в raw-json режиме * @returns {boolean} True если JSON валиден */ export function validateJSONMode() { const textarea = document.getElementById('json-textarea') const message = document.getElementById('json-validation-message') if (!textarea || !message) { console.error('JSON textarea or validation message not found') return false } const jsonText = getInputValue(textarea) const validation = validateJSON(jsonText) if (validation.valid) { removeClass(textarea, 'error') removeClass(message, 'error') addClass(message, 'color-success') const count = Array.isArray(validation.data) ? validation.data.length : 0 setElementText(message, `✓ JSON валиден (${count} вопросов)`) return true } else { addClass(textarea, 'error') addClass(message, 'error') removeClass(message, 'color-success') setElementText(message, `✗ Ошибка: ${validation.error}`) return false } } /** * Обработать отправку запроса * @param {Function} onSuccess - Callback при успешной отправке */ export async function handleSendQuery(onSuccess) { try { const envSettings = appState.getCurrentEnvSettings() const currentEnvKey = appState.getCurrentEnvironment() const apiMode = envSettings?.apiMode || 'bench' // Get current mode from toggle const activeToggle = document.querySelector('.toggle-option.active') const mode = activeToggle?.dataset.mode || 'questions' // Get form values const questionsText = getInputValue('questions-textarea') const jsonText = getInputValue('json-textarea') // Build request body const requestBody = queryService.buildRequestBody(mode, questionsText, jsonText) // Show loading const loadingMsg = apiMode === 'backend' ? 'Отправка запроса к Backend API...' : 'Отправка запроса к Bench API...' loadingUI.show(loadingMsg) // Send query const resetSession = envSettings?.resetSessionMode !== false const apiResponse = await queryService.sendQuery( currentEnvKey, apiMode, requestBody, resetSession ) // Hide loading loadingUI.hide() // Process response queryService.processQueryResponse(currentEnvKey, requestBody, apiResponse) // Call success callback if (typeof onSuccess === 'function') { onSuccess() } } catch (error) { console.error('Query failed:', error) loadingUI.hide() showToast(`Ошибка запроса: ${error.message}`, 'error') } } /** * Переключить между табами * @param {HTMLElement} tabButton - Кнопка таба * @param {string} tabId - ID контента таба */ export function switchTab(tabButton, tabId) { if (!tabButton || !tabId) { return } // Get all tabs in the same group const tabsContainer = tabButton.parentElement const tabs = tabsContainer.querySelectorAll('.tab') // Deactivate all tabs tabs.forEach(tab => removeClass(tab, 'active')) // Activate clicked tab addClass(tabButton, 'active') // Find and show corresponding content const contentContainer = tabsContainer.nextElementSibling if (contentContainer && contentContainer.classList.contains('tab-content')) { // Handle nested tabs const parent = tabsContainer.parentElement const allContents = parent.querySelectorAll('.tab-content') allContents.forEach(content => { if (content.id === tabId) { addClass(content, 'active') } else if (!content.contains(tabsContainer)) { removeClass(content, 'active') } }) } else { // Handle top-level tabs const parent = tabsContainer.parentElement const allContents = parent.querySelectorAll(':scope > .tab-content') allContents.forEach(content => { if (content.id === tabId) { addClass(content, 'active') } else { removeClass(content, 'active') } }) // If activated content has nested tabs, ensure first nested tab-content is shown const activatedContent = document.getElementById(tabId) if (activatedContent) { const nestedTabsContainer = activatedContent.querySelector('.tabs') if (nestedTabsContainer) { // Activate first nested tab button const nestedTabs = nestedTabsContainer.querySelectorAll('.tab') nestedTabs.forEach((tab, index) => { if (index === 0) { addClass(tab, 'active') } else { removeClass(tab, 'active') } }) // Find nested tab-content elements const children = Array.from(activatedContent.children) const nestedContents = children.filter(el => el.classList.contains('tab-content') && el !== nestedTabsContainer ) // Deactivate all first, then activate first one nestedContents.forEach(content => removeClass(content, 'active')) if (nestedContents.length > 0) { addClass(nestedContents[0], 'active') } } } } } /** * Инициализация обработчиков событий * @param {Function} onQuerySuccess - Callback при успешной отправке запроса */ export function setupListeners(onQuerySuccess) { // Toggle mode buttons const toggleButtons = document.querySelectorAll('.toggle-option') toggleButtons.forEach(btn => { btn.addEventListener('click', () => { const mode = btn.dataset.mode if (mode) { switchMode(mode) } }) }) // JSON validation const jsonTextarea = document.getElementById('json-textarea') if (jsonTextarea) { jsonTextarea.addEventListener('input', validateJSONMode) } // Send query button const sendQueryBtn = document.getElementById('send-query-btn') if (sendQueryBtn) { sendQueryBtn.addEventListener('click', () => handleSendQuery(onQuerySuccess)) } } // Export as default object export default { show, switchMode, validateJSONMode, handleSendQuery, switchTab, setupListeners }