# Руководство по миграции с rag-bench-old-version на FastAPI Этот документ содержит детальные инструкции по переносу статического SPA на новую архитектуру с FastAPI backend. --- ## Основные изменения в архитектуре ### Старая версия (rag-bench-old-version) ``` Browser (index.html + app.js) ↓ fetch (HTTPS + mTLS) RAG Backend (IFT/PSI/PROD) Настройки: localStorage Авторизация: нет ``` ### Новая версия (brief-bench-fastapi) ``` Browser (index.html + app.js + api-client.js) ↓ fetch (HTTP, JWT token) FastAPI Backend ↓ httpx (HTTPS + mTLS) RAG Backend (IFT/PSI/PROD) Настройки: DB API (per-user) Авторизация: JWT (8-значный логин) ``` --- ## Шаг 1: Копирование статических файлов ```bash # Из корня проекта brief-bench-fastapi cp rag-bench-old-version/index.html static/ cp rag-bench-old-version/styles.css static/ cp rag-bench-old-version/app.js static/ cp rag-bench-old-version/settings.js static/ ``` --- ## Шаг 2: Создание api-client.js См. полную реализацию в DEVELOPMENT_PLAN.md (раздел "Создать API client для frontend"). Создать файл `static/api-client.js` с классом `BriefBenchAPI`. --- ## Шаг 3: Модификация index.html ### 3.1 Добавить подключение api-client.js **Изменить:** ```html ``` **На:** ```html ``` ### 3.2 Добавить Login Screen **Добавить ПЕРЕД `
`:** ```html ``` ### 3.3 Добавить стили для Login Screen **Добавить в конец `styles.css`:** ```css /* Login Screen */ .login-container { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; z-index: 9999; } .login-card { background: white; padding: var(--md-spacing-xxl); border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); max-width: 400px; width: 90%; } .login-card h2 { color: #667eea; font-weight: 500; } .login-card .form-input:focus { border-color: #667eea; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2); } .login-card .btn-filled { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; margin-top: var(--md-spacing-md); } .login-card .btn-filled:hover { opacity: 0.9; } ``` ### 3.4 Удалить настройки сертификатов из UI Сертификаты теперь настраиваются в `.env` на сервере, поэтому пользователь не должен их редактировать. **Удалить из Settings Dialog:** ```html
Сертификаты (для прокси)
...
...
``` ### 3.5 Убрать поля host/port/endpoint из Settings Dialog Эти поля теперь тоже настраиваются на сервере. Пользователь редактирует только: - API Mode (bench/backend) - Bearer Token - System Platform / System Platform User - Platform User ID / Platform ID - With Classify / Reset Session Mode **Удалить:** ```html
``` ### 3.6 Добавить кнопку Logout **Добавить в App Bar Actions:** ```html ``` --- ## Шаг 4: Модификация app.js ### 4.1 Удалить старые API функции **Удалить функции:** - `buildApiUrl()` - `sendQuery()` - `buildBackendApiUrl()` - `buildBackendHeaders()` - `sendBackendAsk()` - `sendBackendReset()` - `sendBackendSequential()` ### 4.2 Добавить Login Flow **Добавить в начало файла (после объявления AppState):** ```javascript // ============================================ // Authentication // ============================================ /** * Проверить авторизацию при загрузке страницы */ async function checkAuth() { if (!api.isAuthenticated()) { showLoginScreen() return false } // Попробовать загрузить настройки (валидация токена) try { await loadSettingsFromServer() return true } catch (error) { console.error('Token validation failed:', error) showLoginScreen() return false } } /** * Показать экран авторизации */ function showLoginScreen() { document.getElementById('login-screen').style.display = 'flex' document.getElementById('app').style.display = 'none' } /** * Скрыть экран авторизации и показать приложение */ function hideLoginScreen() { document.getElementById('login-screen').style.display = 'none' document.getElementById('app').style.display = 'block' } /** * Обработка авторизации */ async function handleLogin() { const loginInput = document.getElementById('login-input') const loginError = document.getElementById('login-error') const loginBtn = document.getElementById('login-submit-btn') const login = loginInput.value.trim() // Валидация if (!/^[0-9]{8}$/.test(login)) { loginError.textContent = 'Логин должен состоять из 8 цифр' loginError.style.display = 'block' return } loginError.style.display = 'none' loginBtn.disabled = true loginBtn.textContent = 'Вход...' try { const response = await api.login(login) console.log('Login successful:', response.user) // Загрузить настройки с сервера await loadSettingsFromServer() // Скрыть login screen, показать приложение hideLoginScreen() loginInput.value = '' } catch (error) { console.error('Login failed:', error) loginError.textContent = error.message || 'Ошибка авторизации' loginError.style.display = 'block' } finally { loginBtn.disabled = false loginBtn.textContent = 'Войти' } } /** * Выход из системы */ function handleLogout() { if (confirm('Вы уверены, что хотите выйти?')) { api.logout() } } ``` ### 4.3 Изменить управление настройками **Заменить функции:** ```javascript /** * Загрузить настройки с сервера (DB API) */ async function loadSettingsFromServer() { try { const response = await api.getSettings() // Преобразовать в формат AppState.settings AppState.settings = { activeEnvironment: AppState.currentEnvironment, environments: { ift: { name: 'ИФТ', ...response.settings.ift }, psi: { name: 'ПСИ', ...response.settings.psi }, prod: { name: 'ПРОМ', ...response.settings.prod } }, requestTimeout: 1800000, // 30 минут (фиксировано) } console.log('Settings loaded from server:', AppState.settings) } catch (error) { console.error('Failed to load settings from server:', error) throw error } } /** * Сохранить настройки на сервер (DB API) */ async function saveSettingsToServer(settings) { try { // Извлечь только поля, которые сервер ожидает const settingsToSave = { ift: extractEnvironmentSettings(settings.environments.ift), psi: extractEnvironmentSettings(settings.environments.psi), prod: extractEnvironmentSettings(settings.environments.prod) } await api.updateSettings(settingsToSave) AppState.settings = settings console.log('Settings saved to server') } catch (error) { console.error('Failed to save settings to server:', error) throw error } } /** * Извлечь только нужные поля для сервера */ function extractEnvironmentSettings(envSettings) { return { apiMode: envSettings.apiMode, bearerToken: envSettings.bearerToken || '', systemPlatform: envSettings.systemPlatform || '', systemPlatformUser: envSettings.systemPlatformUser || '', platformUserId: envSettings.platformUserId || '', platformId: envSettings.platformId || '', withClassify: envSettings.withClassify || false, resetSessionMode: envSettings.resetSessionMode !== false } } // УДАЛИТЬ старые функции loadSettings() и saveSettings() ``` ### 4.4 Заменить вызовы API для Query **Было:** ```javascript // Старый код async function handleSendQuery() { // ... const response = await sendQuery(requestBody) // Прямой fetch к RAG // ... } ``` **Стало:** ```javascript async function handleSendQuery() { // Получить текущие настройки окружения const envSettings = getCurrentEnvSettings() const env = AppState.currentEnvironment // Проверить режим API if (envSettings.apiMode === 'bench') { // Batch mode const response = await api.benchQuery(env, requestBody) processResponse(response.response) } else if (envSettings.apiMode === 'backend') { // Sequential mode const resetSession = envSettings.resetSessionMode !== false const response = await api.backendQuery(env, requestBody, resetSession) processResponse(response.response) } } ``` ### 4.5 Изменить Save Settings в Settings Dialog **Заменить обработчик:** ```javascript // Было document.getElementById('save-settings-btn').addEventListener('click', () => { const updatedSettings = readSettingsFromDialog() saveSettings(updatedSettings) // localStorage showToast('Настройки сохранены') closeSettingsDialog() }) // Стало document.getElementById('save-settings-btn').addEventListener('click', async () => { const saveBtn = document.getElementById('save-settings-btn') saveBtn.disabled = true saveBtn.textContent = 'Сохранение...' try { const updatedSettings = readSettingsFromDialog() await saveSettingsToServer(updatedSettings) showToast('Настройки сохранены на сервере') closeSettingsDialog() } catch (error) { console.error('Failed to save settings:', error) showToast(`Ошибка сохранения: ${error.message}`, 'error') } finally { saveBtn.disabled = false saveBtn.textContent = 'Сохранить' } }) ``` ### 4.6 Обновить populateSettingsDialog() **Удалить строки для полей, которые больше не редактируются:** ```javascript function populateSettingsDialog() { const env = AppState.currentEnvironment const envSettings = AppState.settings.environments[env] // Set environment selector document.getElementById('settings-env-selector').value = env // API Mode const apiMode = envSettings.apiMode || 'bench' document.getElementById('setting-api-mode').value = apiMode toggleBackendSettings(apiMode === 'backend') // Populate environment-specific fields (только те что остались!) document.getElementById('setting-bearer-token').value = envSettings.bearerToken || '' document.getElementById('setting-system-platform').value = envSettings.systemPlatform || '' document.getElementById('setting-system-platform-user').value = envSettings.systemPlatformUser || '' // Backend mode fields document.getElementById('setting-platform-user-id').value = envSettings.platformUserId || '' document.getElementById('setting-platform-id').value = envSettings.platformId || '' document.getElementById('setting-with-classify').checked = envSettings.withClassify || false document.getElementById('setting-reset-session-mode').checked = envSettings.resetSessionMode !== false // Убрать global timeout (теперь не редактируется) } ``` ### 4.7 Обновить readSettingsFromDialog() **Изменить:** ```javascript function readSettingsFromDialog() { const env = document.getElementById('settings-env-selector').value // Update environment-specific settings const updatedSettings = JSON.parse(JSON.stringify(AppState.settings)) // Deep copy updatedSettings.environments[env] = { name: updatedSettings.environments[env].name, apiMode: document.getElementById('setting-api-mode').value, bearerToken: document.getElementById('setting-bearer-token').value.trim(), systemPlatform: document.getElementById('setting-system-platform').value.trim(), systemPlatformUser: document.getElementById('setting-system-platform-user').value.trim(), platformUserId: document.getElementById('setting-platform-user-id').value.trim(), platformId: document.getElementById('setting-platform-id').value.trim(), withClassify: document.getElementById('setting-with-classify').checked, resetSessionMode: document.getElementById('setting-reset-session-mode').checked, } return updatedSettings } ``` ### 4.8 Добавить event listeners **Добавить в секцию Event Listeners:** ```javascript // Login document.getElementById('login-submit-btn').addEventListener('click', handleLogin) document.getElementById('login-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleLogin() } }) // Logout document.getElementById('logout-btn').addEventListener('click', handleLogout) ``` ### 4.9 Изменить инициализацию приложения **Было:** ```javascript // Initialize app on load document.addEventListener('DOMContentLoaded', () => { AppState.settings = loadSettings() // ... initApp() }) ``` **Стало:** ```javascript // Initialize app on load document.addEventListener('DOMContentLoaded', async () => { // Проверить авторизацию const isAuthenticated = await checkAuth() if (isAuthenticated) { // Пользователь авторизован, инициализировать приложение initApp() } }) ``` --- ## Шаг 5: Изменения в settings.js **Упростить defaultSettings:** Удалить поля, которые теперь настраиваются на сервере (host, port, endpoint, certPaths, systemId, requestIdTemplate). Оставить только дефолты для тех полей, которые пользователь может редактировать: ```javascript const defaultSettings = { activeEnvironment: 'ift', environments: { ift: { name: 'ИФТ', apiMode: 'bench', bearerToken: '', systemPlatform: '', systemPlatformUser: '', platformUserId: '', platformId: '', withClassify: false, resetSessionMode: true, }, psi: { name: 'ПСИ', apiMode: 'bench', bearerToken: '', systemPlatform: '', systemPlatformUser: '', platformUserId: '', platformId: '', withClassify: false, resetSessionMode: true, }, prod: { name: 'ПРОМ', apiMode: 'bench', bearerToken: '', systemPlatform: '', systemPlatformUser: '', platformUserId: '', platformId: '', withClassify: false, resetSessionMode: true, } }, requestTimeout: 1800000, // 30 minutes (не редактируется) } ``` --- ## Шаг 6: Раскомментировать static files в main.py **В `app/main.py`:** ```python # Serve static files (frontend) app.mount("/static", StaticFiles(directory="static"), name="static") ``` Раскомментировать эту строку. --- ## Шаг 7: Тестирование ### 7.1 Запустить FastAPI локально ```bash uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` ### 7.2 Открыть в браузере ``` http://localhost:8000/static/index.html ``` ### 7.3 Тестовые сценарии 1. Login screen должен показаться 2. Ввести 8-значный логин → успешный вход 3. Настройки должны загрузиться с сервера 4. Изменить настройки → сохранить → перезагрузить → настройки сохранились 5. Отправить bench query → получить ответ 6. Отправить backend query → получить ответ 7. Нажать Logout → вернуться на login screen --- ## Ключевые отличия для разработчика | Аспект | Старая версия | Новая версия | |--------|--------------|--------------| | Авторизация | Нет | JWT (8 цифр) | | Настройки | localStorage | DB API (per-user) | | API вызовы | fetch → RAG напрямую | fetch → FastAPI → RAG | | mTLS | В браузере (невозможно) | На FastAPI сервере | | Endpoints | RAG endpoints | FastAPI endpoints | | Пользователи | Один (все настройки общие) | Множество (настройки персональные) | --- ## Что НЕ нужно менять - Весь UI (остаётся как есть) - Логика отображения ответов - Аннотации - Экспорт/импорт анализа (работает через сохранение сессий на сервер) - Material Design стили