diff --git a/generations/library_rag/templates/chat.html b/generations/library_rag/templates/chat.html index b083c3c..fd42165 100644 --- a/generations/library_rag/templates/chat.html +++ b/generations/library_rag/templates/chat.html @@ -645,6 +645,187 @@ height: 15px; flex-shrink: 0; } + + /* Sidebar panel - container for both works filter and context */ + .sidebar-panel { + display: flex; + flex-direction: column; + height: 100%; + gap: 1rem; + } + + /* Works filter section */ + .works-filter-section { + background-color: rgba(255, 255, 255, 0.06); + border-radius: 12px; + border: 1px solid rgba(125, 110, 88, 0.25); + overflow: hidden; + flex-shrink: 0; + } + + .works-filter-content { + padding: 0.75rem 1rem 1rem 1rem; + max-height: 250px; + overflow-y: auto; + } + + .works-filter-content.collapsed { + display: none; + } + + .works-filter-actions { + display: flex; + gap: 0.5rem; + margin-bottom: 0.75rem; + } + + .btn-mini { + padding: 0.35rem 0.75rem; + font-family: var(--font-body); + font-size: 0.8rem; + font-weight: 500; + border-radius: 4px; + border: 1px solid rgba(125, 110, 88, 0.3); + background-color: rgba(255, 255, 255, 0.5); + color: var(--color-text-main); + cursor: pointer; + transition: all 0.2s ease; + } + + .btn-mini:hover { + background-color: var(--color-accent); + color: white; + border-color: var(--color-accent); + } + + .works-count-badge { + font-family: var(--font-body); + font-size: 0.75rem; + font-weight: 600; + background-color: var(--color-accent); + color: white; + padding: 0.15rem 0.5rem; + border-radius: 10px; + margin-left: 0.5rem; + } + + .works-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .work-item { + display: flex; + align-items: flex-start; + gap: 0.6rem; + padding: 0.6rem 0.75rem; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 6px; + border: 1px solid rgba(125, 110, 88, 0.15); + cursor: pointer; + transition: all 0.2s ease; + } + + .work-item:hover { + background-color: rgba(125, 110, 88, 0.08); + border-color: rgba(125, 110, 88, 0.25); + } + + .work-item.selected { + background-color: rgba(125, 110, 88, 0.1); + border-color: var(--color-accent); + } + + .work-checkbox { + width: 16px; + height: 16px; + margin-top: 2px; + cursor: pointer; + accent-color: var(--color-accent); + } + + .work-info { + flex: 1; + min-width: 0; + } + + .work-title { + font-family: var(--font-body); + font-size: 0.85rem; + font-weight: 500; + color: var(--color-text-strong); + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .work-author { + font-family: var(--font-body); + font-size: 0.75rem; + color: var(--color-accent-alt); + margin-top: 0.15rem; + } + + .work-count { + font-family: var(--font-body); + font-size: 0.7rem; + font-weight: 600; + color: var(--color-accent); + background-color: rgba(125, 110, 88, 0.1); + padding: 0.15rem 0.4rem; + border-radius: 4px; + white-space: nowrap; + flex-shrink: 0; + } + + /* Works filter scrollbar */ + .works-filter-content::-webkit-scrollbar { + width: 6px; + } + + .works-filter-content::-webkit-scrollbar-track { + background: rgba(125, 110, 88, 0.05); + border-radius: 3px; + } + + .works-filter-content::-webkit-scrollbar-thumb { + background: rgba(125, 110, 88, 0.3); + border-radius: 3px; + } + + .works-filter-content::-webkit-scrollbar-thumb:hover { + background: rgba(125, 110, 88, 0.5); + } + + /* Update context sidebar to fill remaining space */ + .sidebar-panel .context-sidebar { + flex: 1; + min-height: 0; + } + + /* Responsive - Mobile */ + @media (max-width: 992px) { + .sidebar-panel { + flex-direction: column; + height: auto; + } + + .works-filter-section { + order: -1; + max-height: none; + } + + .works-filter-content { + max-height: 150px; + } + + .sidebar-panel .context-sidebar { + order: -1; + max-height: 300px; + } + }
@@ -707,8 +888,31 @@
- -
+ +
@@ -741,10 +946,159 @@ const collapseBtn = document.getElementById('collapse-btn'); const contextSidebar = document.getElementById('context-sidebar'); + // Works filter DOM elements + const worksFilterContent = document.getElementById('works-filter-content'); + const worksCollapseBtn = document.getElementById('works-collapse-btn'); + const worksCountBadge = document.getElementById('works-count-badge'); + const worksList = document.getElementById('works-list'); + const selectAllWorksBtn = document.getElementById('select-all-works'); + const selectNoneWorksBtn = document.getElementById('select-none-works'); + // State let isGenerating = false; let currentEventSource = null; + // Works filter state + let availableWorks = []; + let selectedWorks = []; + + // ========== WORKS FILTER FUNCTIONS ========== + + async function loadAvailableWorks() { + try { + const response = await fetch('/api/get-works'); + if (!response.ok) { + throw new Error('Failed to load works'); + } + availableWorks = await response.json(); + + // Load saved selection from localStorage or select all by default + const savedSelection = localStorage.getItem('selectedWorks'); + if (savedSelection) { + try { + const parsed = JSON.parse(savedSelection); + // Filter to only keep works that still exist + const existingTitles = availableWorks.map(w => w.title); + selectedWorks = parsed.filter(title => existingTitles.includes(title)); + } catch (e) { + console.error('Error parsing saved selection:', e); + selectedWorks = availableWorks.map(w => w.title); + } + } else { + // Default: all works selected + selectedWorks = availableWorks.map(w => w.title); + } + + // If savedSelection resulted in empty (all works removed), select all + if (selectedWorks.length === 0 && availableWorks.length > 0) { + selectedWorks = availableWorks.map(w => w.title); + } + + renderWorksList(); + updateWorksCount(); + saveSelectedWorksToStorage(); + + } catch (error) { + console.error('Error loading works:', error); + worksList.innerHTML = ''; + } + } + + function renderWorksList() { + worksList.innerHTML = ''; + + if (availableWorks.length === 0) { + worksList.innerHTML = ''; + return; + } + + availableWorks.forEach(work => { + const isSelected = selectedWorks.includes(work.title); + + const workItem = document.createElement('div'); + workItem.className = `work-item${isSelected ? ' selected' : ''}`; + workItem.innerHTML = ` + +
+
${work.title}
+
${work.author}
+
+
${work.chunks_count} passages
+ `; + + // Click on work item toggles checkbox + workItem.addEventListener('click', (e) => { + if (e.target.classList.contains('work-checkbox')) return; // Let checkbox handle itself + const checkbox = workItem.querySelector('.work-checkbox'); + checkbox.checked = !checkbox.checked; + toggleWorkSelection(work.title, checkbox.checked); + workItem.classList.toggle('selected', checkbox.checked); + }); + + // Checkbox change event + const checkbox = workItem.querySelector('.work-checkbox'); + checkbox.addEventListener('change', (e) => { + toggleWorkSelection(work.title, e.target.checked); + workItem.classList.toggle('selected', e.target.checked); + }); + + worksList.appendChild(workItem); + }); + } + + function toggleWorkSelection(title, isSelected) { + if (isSelected) { + if (!selectedWorks.includes(title)) { + selectedWorks.push(title); + } + } else { + selectedWorks = selectedWorks.filter(t => t !== title); + } + updateWorksCount(); + saveSelectedWorksToStorage(); + } + + function updateWorksCount() { + worksCountBadge.textContent = `${selectedWorks.length}/${availableWorks.length}`; + } + + function saveSelectedWorksToStorage() { + try { + localStorage.setItem('selectedWorks', JSON.stringify(selectedWorks)); + } catch (e) { + console.error('Error saving to localStorage:', e); + } + } + + // Select All button + selectAllWorksBtn.addEventListener('click', () => { + selectedWorks = availableWorks.map(w => w.title); + renderWorksList(); + updateWorksCount(); + saveSelectedWorksToStorage(); + }); + + // Select None button + selectNoneWorksBtn.addEventListener('click', () => { + selectedWorks = []; + renderWorksList(); + updateWorksCount(); + saveSelectedWorksToStorage(); + }); + + // Works filter collapse button + worksCollapseBtn.addEventListener('click', () => { + const isCollapsed = worksFilterContent.classList.contains('collapsed'); + worksFilterContent.classList.toggle('collapsed'); + worksCollapseBtn.textContent = isCollapsed ? '▼' : '▲'; + worksCollapseBtn.title = isCollapsed ? 'Réduire' : 'Développer'; + }); + + // Load works on page load + loadAvailableWorks(); + + // ========== END WORKS FILTER FUNCTIONS ========== + // Configure marked for markdown rendering marked.setOptions({ breaks: true, @@ -950,7 +1304,7 @@ const typingId = addTypingIndicator(); try { - // POST /chat/send with chosen question + // POST /chat/send with chosen question and selected works filter const response = await fetch('/chat/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -959,7 +1313,8 @@ provider: provider, model: model, limit: 5, - use_reformulation: false // Reformulation already done + use_reformulation: false, // Reformulation already done + selected_works: selectedWorks // Filter by selected works }) });