brief-rags-bench/static/js/ui/answer-viewer.ui.js

247 lines
8.4 KiB
JavaScript
Raw Normal View History

2025-12-25 09:47:11 +01:00
/**
* Answer Viewer UI
*
* UI компонент для просмотра ответов.
*/
import appState from '../state/appState.js'
import { escapeHtml, formatTime, formatTimestamp, isTableText, parseTextTable } from '../utils/format.utils.js'
import { setElementText, setElementHTML, addClass, removeClass, hideElement, showElement } from '../utils/dom.utils.js'
/**
* Показать просмотрщик ответов, скрыть построитель запросов
*/
export function show() {
const queryBuilder = document.getElementById('query-builder')
const answerViewer = document.getElementById('answer-viewer')
if (queryBuilder) {
addClass(queryBuilder, 'hidden')
}
if (answerViewer) {
removeClass(answerViewer, 'hidden')
}
}
/**
* Отрендерить ответ по индексу
* @param {number} index - Индекс ответа
* @param {Function} onLoadAnnotations - Callback для загрузки аннотаций
*/
export function render(index, onLoadAnnotations) {
const env = appState.getCurrentEnv()
const answer = env.currentResponse?.answers[index]
if (!answer) {
console.error('Answer not found at index:', index)
return
}
const isBackendMode = answer.backend_mode === true
// Show answer viewer
show()
// Render question header
setElementText('current-question-number', index + 1)
setElementText('current-question-text', answer.question)
// Render metadata
setElementText('processing-time', isBackendMode ? 'N/A' : formatTime(answer.processing_time_sec))
setElementText('request-id', env.requestId || '-')
setElementText('request-timestamp', env.requestTimestamp ? formatTimestamp(env.requestTimestamp) : '-')
// Render answer bodies
renderBody('body-research-text', answer.body_research)
renderBody('body-analytical-text', answer.body_analytical_hub)
// Show/hide documents sections based on mode
const docsSection = document.querySelector('.answer-section:has(#docs-tabs)')
if (docsSection) {
if (isBackendMode) {
hideElement(docsSection)
} else {
showElement(docsSection)
}
}
if (!isBackendMode) {
// Render documents (only in bench mode)
renderDocuments('vectorstore-research-docs', answer.docs_from_vectorstore?.research, 'docs_from_vectorstore', 'research', index)
renderDocuments('vectorstore-analytical-docs', answer.docs_from_vectorstore?.analytical_hub, 'docs_from_vectorstore', 'analytical_hub', index)
renderDocuments('llm-research-docs', answer.docs_to_llm?.research, 'docs_to_llm', 'research', index)
renderDocuments('llm-analytical-docs', answer.docs_to_llm?.analytical_hub, 'docs_to_llm', 'analytical_hub', index)
}
// Load annotations
if (typeof onLoadAnnotations === 'function') {
onLoadAnnotations(index)
}
}
/**
* Отрендерить тело ответа
* @param {string} elementId - ID элемента
* @param {string} text - Текст ответа
*/
export function renderBody(elementId, text) {
const container = document.getElementById(elementId)
if (!container) {
console.warn(`Element ${elementId} not found`)
return
}
if (!text) {
setElementHTML(container, '<p class="color-warning">Нет данных</p>')
return
}
if (isTableText(text)) {
const table = parseTextTable(text)
if (table) {
setElementHTML(container, `<div class="table-container">${table}</div>`)
return
}
}
// Render as plain text with line breaks
const html = `<p>${escapeHtml(text).replace(/\n/g, '<br>')}</p>`
setElementHTML(container, html)
}
/**
* Отрендерить документы
* @param {string} containerId - ID контейнера
* @param {Array} docs - Массив документов
* @param {string} section - Секция (docs_from_vectorstore, docs_to_llm)
* @param {string} subsection - Подсекция (research, analytical_hub)
* @param {number} answerIndex - Индекс ответа
*/
export function renderDocuments(containerId, docs, section, subsection, answerIndex) {
const container = document.getElementById(containerId)
if (!container) {
console.warn(`Container ${containerId} not found`)
return
}
if (!docs || docs.length === 0) {
setElementHTML(container, `
<div class="empty-state">
<div class="empty-state-text">Нет документов</div>
</div>
`)
return
}
const html = docs.map((doc, docIndex) => {
const docId = `doc-${section}-${subsection}-${docIndex}`
let docContent = ''
if (typeof doc === 'string') {
if (isTableText(doc)) {
const table = parseTextTable(doc)
docContent = table || `<pre class="text-table">${escapeHtml(doc)}</pre>`
} else {
docContent = `<p>${escapeHtml(doc).replace(/\n/g, '<br>')}</p>`
}
} else {
docContent = `<pre class="text-table">${escapeHtml(JSON.stringify(doc, null, 2))}</pre>`
}
return `
<div class="expansion-panel" id="${docId}">
<div class="expansion-header" onclick="window.toggleExpansion('${docId}')">
<span class="expansion-header-title">Документ #${docIndex + 1}</span>
<span class="material-icons expansion-icon">expand_more</span>
</div>
<div class="expansion-content">
<div class="expansion-body">
${docContent}
<div class="annotation-section mt-md">
<h6 class="mb-sm">Пометки</h6>
<div class="annotation-issues mb-md">
<label class="issue-checkbox">
<input type="checkbox" class="checkbox"
data-section="${section}"
data-subsection="${subsection}"
data-doc-index="${docIndex}"
data-issue="factual_error">
<span>Факт. ошибка</span>
</label>
<label class="issue-checkbox">
<input type="checkbox" class="checkbox"
data-section="${section}"
data-subsection="${subsection}"
data-doc-index="${docIndex}"
data-issue="inaccurate_wording">
<span>Неточность формулировки</span>
</label>
<label class="issue-checkbox">
<input type="checkbox" class="checkbox"
data-section="${section}"
data-subsection="${subsection}"
data-doc-index="${docIndex}"
data-issue="insufficient_context">
<span>Недостаточно контекста</span>
</label>
<label class="issue-checkbox">
<input type="checkbox" class="checkbox"
data-section="${section}"
data-subsection="${subsection}"
data-doc-index="${docIndex}"
data-issue="offtopic">
<span>Не по теме</span>
</label>
<label class="issue-checkbox">
<input type="checkbox" class="checkbox"
data-section="${section}"
data-subsection="${subsection}"
data-doc-index="${docIndex}"
data-issue="technical_answer">
<span>Технический ответ</span>
</label>
</div>
<div class="form-group">
<label>Комментарий</label>
<textarea class="textarea"
data-section="${section}"
data-subsection="${subsection}"
data-doc-index="${docIndex}"
data-field="comment"
placeholder="Комментарий к документу..."></textarea>
</div>
</div>
</div>
</div>
</div>
`
}).join('')
setElementHTML(container, html)
}
/**
* Переключить раскрытие expansion panel
* @param {string} id - ID панели
*/
export function toggleExpansion(id) {
const panel = document.getElementById(id)
if (panel) {
panel.classList.toggle('expanded')
}
}
// Export as default object
export default {
show,
render,
renderBody,
renderDocuments,
toggleExpansion
}