brief-rags-bench/MIGRATION_GUIDE.md

20 KiB
Raw Permalink Blame History

Руководство по миграции с 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: Копирование статических файлов

# Из корня проекта 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

Изменить:

<!-- Scripts -->
<script src="settings.js"></script>
<script src="app.js"></script>

На:

<!-- Scripts -->
<script src="settings.js"></script>
<script src="api-client.js"></script>
<script src="app.js"></script>

3.2 Добавить Login Screen

Добавить ПЕРЕД <div id="app">:

<!-- Login Screen -->
<div id="login-screen" class="login-container" style="display: none;">
  <div class="login-card">
    <h2 style="text-align: center; margin-bottom: var(--md-spacing-xl);">Brief Bench</h2>
    <div class="form-group">
      <label class="form-label" for="login-input">8-значный логин</label>
      <input
        type="text"
        class="form-input"
        id="login-input"
        placeholder="12345678"
        maxlength="8"
        pattern="[0-9]{8}"
        autocomplete="off"
      >
      <div class="form-helper-text" id="login-error" style="color: var(--md-error); display: none;"></div>
    </div>
    <button class="btn btn-filled" id="login-submit-btn" style="width: 100%;">
      Войти
    </button>
  </div>
</div>

3.3 Добавить стили для Login Screen

Добавить в конец styles.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:

<h6 class="mt-lg mb-md">Сертификаты (для прокси)</h6>
<div class="form-helper-text mb-md">...</div>
<div class="form-group">
  <label class="form-label">CA Certificate Path</label>
  ...
</div>
<!-- И все остальные поля сертификатов -->

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

Удалить:

<div class="form-group">
  <label class="form-label">Полный URL (переопределяет host/port/endpoint)</label>
  <input type="text" class="form-input" id="setting-full-url" ...>
</div>

<div class="form-group">
  <label class="form-label">Host</label>
  <input type="text" class="form-input" id="setting-host" ...>
</div>

<div class="form-group">
  <label class="form-label">Port</label>
  <input type="text" class="form-input" id="setting-port" ...>
</div>

<div class="form-group">
  <label class="form-label">Endpoint</label>
  <input type="text" class="form-input" id="setting-endpoint" ...>
</div>

<div class="form-group">
  <label class="form-label">System ID</label>
  <input type="text" class="form-input" id="setting-system-id" ...>
</div>

<div class="form-group">
  <label class="form-label">Request ID Template</label>
  <input type="text" class="form-input" id="setting-request-id" ...>
</div>

3.6 Добавить кнопку Logout

Добавить в App Bar Actions:

<button class="btn-icon" id="logout-btn" title="Выход">
  <span class="material-icons">logout</span>
</button>

Шаг 4: Модификация app.js

4.1 Удалить старые API функции

Удалить функции:

  • buildApiUrl()
  • sendQuery()
  • buildBackendApiUrl()
  • buildBackendHeaders()
  • sendBackendAsk()
  • sendBackendReset()
  • sendBackendSequential()

4.2 Добавить Login Flow

Добавить в начало файла (после объявления AppState):

// ============================================
// 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 Изменить управление настройками

Заменить функции:

/**
 * Загрузить настройки с сервера (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

Было:

// Старый код
async function handleSendQuery() {
  // ...
  const response = await sendQuery(requestBody)  // Прямой fetch к RAG
  // ...
}

Стало:

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

Заменить обработчик:

// Было
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()

Удалить строки для полей, которые больше не редактируются:

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()

Изменить:

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:

// 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 Изменить инициализацию приложения

Было:

// Initialize app on load
document.addEventListener('DOMContentLoaded', () => {
  AppState.settings = loadSettings()
  // ...
  initApp()
})

Стало:

// 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).

Оставить только дефолты для тех полей, которые пользователь может редактировать:

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:

# Serve static files (frontend)
app.mount("/static", StaticFiles(directory="static"), name="static")

Раскомментировать эту строку.


Шаг 7: Тестирование

7.1 Запустить FastAPI локально

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 стили