/** * =============================================================================== * FILE: modules/web_api/static/js/sidebar.js * ROLE: Контроллер левого сайдбара с 5-хабовой навигацией, реестрами, * подробным описанием управления контекстом и интеграцией чата. * =============================================================================== */ // Глобальная функция безопасного экранирования HTML window.escapeHtml = function(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; window.SidebarManager = { currentHub: 'TASKS', currentSubTab: { 'REGISTRIES': 'REMOTE' }, hubs: [ { id: 'TASKS', label: 'Задачи', icon: 'fa-list-check' }, { id: 'SNAPSHOTS', label: 'Срезы', icon: 'fa-camera' }, { id: 'REGISTRIES', label: 'Реестры', icon: 'fa-address-book' }, { id: 'PROMPT', label: 'Промпт', icon: 'fa-terminal' }, { id: 'CONTEXT', label: 'Контекст', icon: 'fa-comments' } ], subTabs: { 'REGISTRIES': [ { id: 'REMOTE', label: 'Удаленщики', icon: 'fa-house-laptop' }, { id: 'EXCEPTIONS', label: 'Исключения', icon: 'fa-user-shield' } ] }, init() { this.renderHeader(); this.renderContent(); }, setHub(hubId) { this.currentHub = hubId; this.renderHeader(); this.renderContent(); }, setSubTab(subTabId) { this.currentSubTab[this.currentHub] = subTabId; this.renderHeader(); this.renderContent(); }, renderHeader() { const headerContainer = document.getElementById("sidebar-dynamic-header"); if (!headerContainer) return; // 1. Основные 5 Хабов const hubsHtml = `
${this.hubs.map(h => { const isActive = this.currentHub === h.id; return ` `; }).join('')}
`; // 2. Подвкладки (только для хабов, где они требуются, например Реестры) let subTabsHtml = ''; if (this.subTabs[this.currentHub]) { const currentActiveSub = this.currentSubTab[this.currentHub] || this.subTabs[this.currentHub][0].id; subTabsHtml = `
${this.subTabs[this.currentHub].map(st => { const isSubActive = currentActiveSub === st.id; return ` `; }).join('')}
`; } headerContainer.innerHTML = hubsHtml + subTabsHtml; }, renderContent() { const contentContainer = document.getElementById("sidebar-dynamic-content"); if (!contentContainer) return; contentContainer.scrollTop = 0; switch (this.currentHub) { case 'TASKS': this.renderTasksView(contentContainer); break; case 'SNAPSHOTS': this.renderSnapshotsView(contentContainer); break; case 'REGISTRIES': this.renderRegistriesView(contentContainer); break; case 'PROMPT': this.renderPromptView(contentContainer); break; case 'CONTEXT': this.renderContextView(contentContainer); break; default: contentContainer.innerHTML = `
Раздел в разработке
`; } }, // ========================================================================= // ХАБ 1: ЗАДАЧИ // ========================================================================= renderTasksView(container) { container.innerHTML = `
Загрузка задач...
`; if (window.loadTasks) { window.loadTasks(); } }, // ========================================================================= // ХАБ 2: СРЕЗЫ СКУД // ========================================================================= async renderSnapshotsView(container) { container.innerHTML = `
Загрузка срезов...
`; try { const res = await fetch("/api/v1/snapshots", { headers: AuthManager.getAuthHeaders() }); const data = res.ok ? await res.json() : { snapshots: [] }; const snaps = data.snapshots || []; if (snaps.length === 0) { container.innerHTML = `
Срезы СКУД не найдены
`; return; } const itemsHtml = snaps.map(s => `
${escapeHtml(s.snapshot_id)} ${s.is_final ? 'Финал Y' : ''}
${escapeHtml(s.snapshot_time)} · ${s.record_count || 0} зап.
`).join(''); container.innerHTML = `
Всего срезов: ${snaps.length}
${itemsHtml}
`; } catch (e) { container.innerHTML = `
Ошибка загрузки срезов
`; } }, // ========================================================================= // ХАБ 3: РЕЕСТРЫ (УДАЛЕНЩИКИ + ИСКЛЮЧЕНИЯ) // ========================================================================= renderRegistriesView(container) { const subTab = this.currentSubTab['REGISTRIES'] || 'REMOTE'; if (subTab === 'REMOTE') { this.renderRemoteWorkersView(container); } else { this.renderExceptionsView(container); } }, async renderRemoteWorkersView(container) { container.innerHTML = `
Загрузка удаленщиков...
`; try { const res = await fetch("/api/v1/remote-workers", { headers: AuthManager.getAuthHeaders() }); const data = res.ok ? await res.json() : { workers: [] }; const workers = data.workers || []; const listHtml = workers.map(w => { const dFrom = w.date_from ? w.date_from : 'сегодня'; const dTo = w.date_to ? w.date_to : 'бессрочно'; const periodLabel = (!w.date_to) ? `с ${dFrom} (бессрочно)` : `${dFrom} — ${dTo}`; return `
${escapeHtml(w.fio)}
${escapeHtml(w.department || 'Все')}
${escapeHtml(periodLabel)}
`; }).join(''); container.innerHTML = `
В реестре: ${workers.length} чел.
${workers.length > 0 ? listHtml : '
Список удаленщиков пуст
'}
`; } catch (e) { container.innerHTML = `
Ошибка загрузки реестра удаленщиков
`; } }, async deleteRemoteWorker(fio) { if (!confirm(`Удалить сотрудника ${fio} из реестра удаленщиков?`)) return; try { const res = await fetch(`/api/v1/remote-workers?fio=${encodeURIComponent(fio)}`, { method: "DELETE", headers: AuthManager.getAuthHeaders() }); if (res.ok) { this.renderContent(); } else { alert("Ошибка удаления"); } } catch (e) { alert("Ошибка сети"); } }, async renderExceptionsView(container) { container.innerHTML = `
Загрузка исключений...
`; try { const res = await fetch("/api/v1/exceptions/", { headers: AuthManager.getAuthHeaders() }); const data = res.ok ? await res.json() : {}; const categories = [ { key: 'include_fio', title: 'Белый список (ФИО)' }, { key: 'fio', title: 'Исключенные сотрудники (ФИО)' }, { key: 'departments', title: 'Исключенные отделы' }, { key: 'positions', title: 'Исключенные должности' } ]; const html = categories.map(cat => { const items = data[cat.key] || []; return `
${cat.title} (${items.length})
${items.map(it => ` ${escapeHtml(it)} `).join('') || 'Пусто'}
`; }).join(''); container.innerHTML = `
${html}
`; } catch (e) { container.innerHTML = `
Ошибка загрузки исключений
`; } }, async addExceptionPrompt(category) { const val = prompt(`Введите значение для категории [${category}]:`); if (!val || !val.trim()) return; try { const res = await fetch("/api/v1/exceptions/", { method: "POST", headers: AuthManager.getAuthHeaders(), body: JSON.stringify({ category: category, value: val.trim() }) }); if (res.ok) this.renderContent(); else alert("Ошибка добавления"); } catch (e) { alert("Ошибка сети"); } }, async deleteExceptionItem(category, value) { if (!confirm(`Удалить "${value}" из ${category}?`)) return; try { const res = await fetch(`/api/v1/exceptions/?category=${encodeURIComponent(category)}&value=${encodeURIComponent(value)}`, { method: "DELETE", headers: AuthManager.getAuthHeaders() }); if (res.ok) this.renderContent(); else alert("Ошибка удаления"); } catch (e) { alert("Ошибка сети"); } }, // ========================================================================= // ХАБ 4: СИСТЕМНЫЙ ПРОМПТ И БАЗА ЗНАНИЙ // ========================================================================= renderPromptView(container) { container.innerHTML = `
Инструкции и регламенты ИИ Управление системными директивами, базой знаний и правилами арбитража кадровых аномалий СКУД и 1С.
`; }, // ========================================================================= // ХАБ 5: УПРАВЛЕНИЕ КОНТЕКСТОМ СЕССИИ (С ПОДРОБНЫМ ОПИСАНИЕМ) // ========================================================================= renderContextView(container) { container.innerHTML = `
Управление памятью чата
Мягкая очистка: удаляет только служебные транзакции (карточки срезов, временные превью промпта, промежуточные подтверждения). Смысловой диалог пользователя сохраняется.
Полный сброс: полностью стирает контекст активной сессии из базы данных и очищает окно чата. Используется при переходе к новой дате или новой теме анализа.
`; }, async handleFullSessionReset() { if (!confirm("Вы действительно хотите полностью очистить историю диалога и сбросить сессию чата?")) { return; } // Очищаем локальное окно чата до стартового приветствия const chatContainer = document.getElementById("chat-messages-container"); if (chatContainer) { chatContainer.innerHTML = `
ИИ-ассистент SCUD Orion AI
Сессия чата очищена. Память ассистента сброшена. Задайте новый вопрос или команду.
`; } try { await fetch("/api/v1/chat", { method: "POST", headers: AuthManager.getAuthHeaders(), body: JSON.stringify({ message: "сбрось сессию полностью", session_id: "web_session_main" }) }); } catch (e) { console.error("Ошибка запроса сброса сессии:", e); } } }; document.addEventListener("DOMContentLoaded", () => { if (window.AuthManager && AuthManager.isAuthenticated()) { SidebarManager.init(); } });