167 lines
5.0 KiB
JavaScript
167 lines
5.0 KiB
JavaScript
/**
|
|
* Questions List UI
|
|
*
|
|
* UI компонент для списка вопросов.
|
|
*/
|
|
|
|
import appState from '../state/appState.js'
|
|
import { escapeHtml, formatTime, pluralize } from '../utils/format.utils.js'
|
|
import { setElementText, setElementHTML } from '../utils/dom.utils.js'
|
|
|
|
/**
|
|
* Проверить наличие аннотаций в секции документов
|
|
* @param {object} docsSection - Секция документов
|
|
* @returns {boolean} True если есть аннотации
|
|
*/
|
|
export function hasAnnotationsInDocs(docsSection) {
|
|
if (!docsSection) return false
|
|
|
|
// Check research documents
|
|
if (docsSection.research) {
|
|
for (const docIndex in docsSection.research) {
|
|
const doc = docsSection.research[docIndex]
|
|
if (doc.issues?.length > 0 || doc.comment) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check analytical_hub documents
|
|
if (docsSection.analytical_hub) {
|
|
for (const docIndex in docsSection.analytical_hub) {
|
|
const doc = docsSection.analytical_hub[docIndex]
|
|
if (doc.issues?.length > 0 || doc.comment) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Отрендерить список вопросов
|
|
*/
|
|
export function render() {
|
|
const container = document.getElementById('questions-list')
|
|
const countElement = document.getElementById('questions-count')
|
|
|
|
if (!container) {
|
|
console.error('Questions list container not found')
|
|
return
|
|
}
|
|
|
|
const env = appState.getCurrentEnv()
|
|
const response = env.currentResponse
|
|
|
|
if (!response || !response.answers || response.answers.length === 0) {
|
|
setElementHTML(container, `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">
|
|
<span class="material-icons" style="font-size: inherit;">question_answer</span>
|
|
</div>
|
|
<div class="empty-state-text">Нет данных</div>
|
|
<div class="empty-state-subtext">Отправьте запрос к RAG бэкенду</div>
|
|
</div>
|
|
`)
|
|
|
|
if (countElement) {
|
|
setElementText(countElement, '0 вопросов')
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// Update count
|
|
if (countElement) {
|
|
const count = response.answers.length
|
|
const text = `${count} ${pluralize(count, 'вопрос', 'вопроса', 'вопросов')}`
|
|
setElementText(countElement, text)
|
|
}
|
|
|
|
// Render questions
|
|
const html = response.answers.map((answer, index) => {
|
|
const isActive = index === env.currentAnswerIndex
|
|
const annotation = env.annotations[index]
|
|
|
|
// Check for annotations in body sections
|
|
const hasBodyAnnotations = annotation && (
|
|
annotation.overall?.comment ||
|
|
annotation.body_research?.issues?.length > 0 ||
|
|
annotation.body_analytical_hub?.issues?.length > 0
|
|
)
|
|
|
|
// Check for annotations in documents
|
|
const hasDocAnnotations = annotation && (
|
|
hasAnnotationsInDocs(annotation.docs_from_vectorstore) ||
|
|
hasAnnotationsInDocs(annotation.docs_to_llm)
|
|
)
|
|
|
|
const hasAnyAnnotations = hasBodyAnnotations || hasDocAnnotations
|
|
|
|
// Get rating indicator
|
|
const rating = annotation?.overall?.rating
|
|
let ratingIndicator = ''
|
|
|
|
if (rating === 'correct') {
|
|
ratingIndicator = '<span class="material-icons" style="font-size: 18px; color: #4caf50;">check_circle</span>'
|
|
} else if (rating === 'partial') {
|
|
ratingIndicator = '<span class="material-icons" style="font-size: 18px; color: #ff9800;">error</span>'
|
|
} else if (rating === 'incorrect') {
|
|
ratingIndicator = '<span class="material-icons" style="font-size: 18px; color: #f44336;">cancel</span>'
|
|
}
|
|
|
|
// Get annotation bookmark indicator (separate from rating)
|
|
const annotationIndicator = hasAnyAnnotations
|
|
? '<span class="material-icons color-warning" style="font-size: 18px;">bookmark</span>'
|
|
: ''
|
|
|
|
return `
|
|
<div class="card card-clickable question-item ${isActive ? 'active' : ''}"
|
|
data-index="${index}"
|
|
onclick="window.selectAnswer(${index})">
|
|
<div class="card-content">
|
|
<div class="question-item-header">
|
|
<div class="text-overline">#${index + 1}</div>
|
|
<div style="display: flex; gap: 4px;">
|
|
${ratingIndicator}
|
|
${annotationIndicator}
|
|
</div>
|
|
</div>
|
|
<div class="question-text">${escapeHtml(answer.question)}</div>
|
|
<div class="question-meta">
|
|
<span>${formatTime(answer.processing_time_sec)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
}).join('')
|
|
|
|
setElementHTML(container, html)
|
|
}
|
|
|
|
/**
|
|
* Выбрать ответ по индексу
|
|
* @param {number} index - Индекс ответа
|
|
* @param {Function} onSelect - Callback при выборе
|
|
*/
|
|
export function selectAnswer(index, onSelect) {
|
|
const env = appState.getCurrentEnv()
|
|
env.currentAnswerIndex = index
|
|
|
|
// Re-render questions list to update active state
|
|
render()
|
|
|
|
// Call callback if provided
|
|
if (typeof onSelect === 'function') {
|
|
onSelect(index)
|
|
}
|
|
}
|
|
|
|
// Export as default object
|
|
export default {
|
|
render,
|
|
selectAnswer,
|
|
hasAnnotationsInDocs
|
|
}
|