20 KiB
Руководство по миграции с 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 Тестовые сценарии
- Login screen должен показаться
- Ввести 8-значный логин → успешный вход
- Настройки должны загрузиться с сервера
- Изменить настройки → сохранить → перезагрузить → настройки сохранились
- Отправить bench query → получить ответ
- Отправить backend query → получить ответ
- Нажать 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 стили