brief-rags-bench/MIGRATION_GUIDE.md

678 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Руководство по миграции с 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
<!-- Scripts -->
<script src="settings.js"></script>
<script src="app.js"></script>
```
**На:**
```html
<!-- Scripts -->
<script src="settings.js"></script>
<script src="api-client.js"></script>
<script src="app.js"></script>
```
### 3.2 Добавить Login Screen
**Добавить ПЕРЕД `<div id="app">`:**
```html
<!-- 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`:**
```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
<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
**Удалить:**
```html
<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:**
```html
<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):**
```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 стили