brief-rags-bench/static/js/ui/query-builder.ui.js

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
}