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
})
});