262 lines
7.6 KiB
JavaScript
262 lines
7.6 KiB
JavaScript
/**
|
|
* 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
|
|
}
|