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

247 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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
}