678 lines
20 KiB
Markdown
678 lines
20 KiB
Markdown
# Руководство по миграции с 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 стили
|