This commit is contained in:
itqop 2025-12-25 11:39:58 +03:00
parent fc2eeb82ca
commit 2525fb910f
8 changed files with 1306 additions and 54 deletions

View File

@ -6,10 +6,10 @@
---
## 📊 Общий прогресс: 20%
## 📊 Общий прогресс: 55%
```
[████░░░░░░░░░░░░░░░░] 20% завершено
[███████████░░░░░░░░░] 55% завершено
```
---
@ -93,71 +93,90 @@
---
### 🟡 Этап 3: State Management (В ПРОЦЕССЕ)
### ✅ Этап 3: State Management (ЗАВЕРШЁН)
**Дата**: -
**Статус**: 🔲 Ожидает
**Дата**: 2025-12-25
**Статус**: ✅ Готово
#### 3.1. state/appState.js 🔲
#### 3.1. state/appState.js
**Строки из app.js**: 10-51
**Что нужно**:
- [ ] Создать класс AppState
- [ ] Геттеры: `getCurrentEnv()`, `getCurrentEnvSettings()`
- [ ] Сеттеры: `setCurrentEnvironment()`, `updateSettings()`
- [ ] Методы: `saveToLocalStorage()`, `loadFromLocalStorage()`
- [ ] Export: default export singleton
- [x] Создать класс AppState
- [x] Геттеры: `getCurrentEnv()`, `getCurrentEnvSettings()`
- [x] Сеттеры: `setCurrentEnvironment()`, `updateSettings()`
- [x] Методы: `saveToLocalStorage()`, `loadFromLocalStorage()`
- [x] Export: default export singleton
#### 3.2. data/storage.js 🔲
**Результат**: Singleton класс с полным управлением состоянием ✅
#### 3.2. data/storage.js ✅
**Строки из app.js**: 618-642
**Что нужно**:
- [ ] `saveEnvironmentData(env, data)` - сохранить данные окружения
- [ ] `loadEnvironmentData(env)` - загрузить данные окружения
- [ ] `clearEnvironmentData(env)` - очистить данные
- [ ] `clearAllData()` - очистить всё
- [x] `saveEnvironmentData(env, data)` - сохранить данные окружения
- [x] `loadEnvironmentData(env)` - загрузить данные окружения
- [x] `clearEnvironmentData(env)` - очистить данные
- [x] `clearAllData()` - очистить всё
- [x] Дополнительно: обертки для token, user, settings, annotations
#### 3.3. data/defaults.js 🔲
**Результат**: 19 функций для работы с localStorage ✅
#### 3.3. data/defaults.js ✅
**Файл**: settings.js (перенести)
**Что нужно**:
- [ ] Экспортировать `defaultSettings` из settings.js
- [ ] Адаптировать для использования в модулях
- [x] Экспортировать `defaultSettings` из settings.js
- [x] Адаптировать для использования в модулях
- [x] Добавить `defaultEnvironmentState`
**Результат**: ES6 экспорт дефолтных настроек ✅
---
### 🔲 Этап 4: Services (ОЖИДАЕТ)
### ✅ Этап 4: Services (ЗАВЕРШЁН)
**Дата**: -
**Статус**: 🔲 Ожидает
**Дата**: 2025-12-25
**Статус**: ✅ Готово
#### 4.1. services/api-client.js 🔲
#### 4.1. services/api-client.js
**Файл**: api-client.js (переместить)
- [ ] Переместить существующий `api-client.js` в `services/`
- [ ] Добавить ES6 экспорт: `export default new BriefBenchAPI()`
- [ ] Протестировать импорт
- [x] Переместить существующий `api-client.js` в `services/`
- [x] Добавить ES6 экспорт: `export default new BriefBenchAPI()`
- [x] Использовать storage.js для работы с токенами
- [x] Импортировать API_CONFIG из config.js
#### 4.2. services/auth.service.js 🔲
**Результат**: ES6 модуль с singleton экспортом ✅
#### 4.2. services/auth.service.js ✅
**Строки из app.js**: 60-140
**Функции**:
- [ ] `checkAuth()` - проверка авторизации
- [ ] `login(loginString)` - вход
- [ ] `logout()` - выход
- [ ] `isAuthenticated()` - проверка статуса
- [x] `checkAuth()` - проверка авторизации
- [x] `login(loginString)` - вход
- [x] `logout()` - выход
- [x] `isAuthenticated()` - проверка статуса
#### 4.3. services/settings.service.js 🔲
**Результат**: 4 функции для авторизации ✅
#### 4.3. services/settings.service.js ✅
**Строки из app.js**: 290-357
**Функции**:
- [ ] `loadFromServer()` - загрузить с сервера
- [ ] `saveToServer(settings)` - сохранить на сервер
- [ ] `extractEnvSettings(envSettings)` - извлечь настройки окружения
- [ ] `resetToDefaults()` - сброс к дефолтным
- [x] `loadFromServer()` - загрузить с сервера
- [x] `saveToServer(settings)` - сохранить на сервер
- [x] `extractEnvironmentSettings(envSettings)` - извлечь настройки окружения
- [x] `getCurrentEnvironmentSettings()` - получить настройки текущего окружения
- [x] `updateCurrentEnvironmentSettings()` - обновить настройки
#### 4.4. services/query.service.js 🔲
**Результат**: 5 функций для работы с настройками ✅
#### 4.4. services/query.service.js ✅
**Строки из app.js**: 861-1063
**Функции**:
- [ ] `buildRequestBody()` - построить тело запроса
- [ ] `sendQuery(env, apiMode, body)` - отправить запрос
- [ ] `extractQuestions()` - извлечь вопросы из textarea
- [ ] `loadRequestFromFile()` - загрузить запрос из файла
- [ ] `loadResponseFromFile()` - загрузить ответ из файла
- [x] `buildRequestBody()` - построить тело запроса
- [x] `sendQuery(env, apiMode, body)` - отправить запрос
- [x] `processQueryResponse()` - обработать ответ
- [x] `extractQuestions()` - извлечь вопросы из textarea
- [x] `loadRequestFromFile()` - загрузить запрос из файла
- [x] `loadResponseFromFile()` - загрузить ответ из файла
**Результат**: 6 функций для работы с запросами ✅
---
@ -280,36 +299,43 @@
## 📈 Статистика
### Создано файлов: 5/17
### Создано файлов: 12/17
| Категория | Создано | Всего | Прогресс |
|-----------|---------|-------|----------|
| Config | 1 | 1 | 100% ✅ |
| Utils | 4 | 4 | 100% ✅ |
| State | 0 | 1 | 0% 🔲 |
| Data | 0 | 2 | 0% 🔲 |
| Services | 0 | 4 | 0% 🔲 |
| State | 1 | 1 | 100% ✅ |
| Data | 2 | 2 | 100% ✅ |
| Services | 4 | 4 | 100% ✅ |
| UI | 0 | 7 | 0% 🔲 |
| Main | 0 | 1 | 0% 🔲 |
### Перенесено функций: ~36/~150
### Перенесено функций: ~70/~150
- ✅ Format utils: 11 функций
- ✅ File utils: 6 функций
- ✅ Validation utils: 4 функций
- ✅ DOM utils: 15 функций
- 🔲 Остальное: ~114 функций
- ✅ AppState class: ~15 методов
- ✅ Storage utils: 19 функций
- ✅ Services: ~15 функций (auth 4 + settings 5 + query 6)
- 🔲 Остальное: ~80 функций (в основном UI компоненты)
---
## 🎯 Следующий шаг
**Этап 3: State Management**
**Этап 5: UI Components**
Создать:
1. `state/appState.js` - глобальное состояние
2. `data/storage.js` - обёртка localStorage
3. `data/defaults.js` - дефолтные настройки
Создать UI компоненты (7 файлов):
1. `ui/auth.ui.js` - экран авторизации
2. `ui/loading.ui.js` - индикатор загрузки
3. `ui/settings.ui.js` - диалог настроек
4. `ui/query-builder.ui.js` - построитель запросов
5. `ui/questions-list.ui.js` - список вопросов
6. `ui/answer-viewer.ui.js` - просмотр ответов
7. `ui/annotations.ui.js` - интерфейс аннотаций
---
@ -322,4 +348,4 @@
---
**Последнее обновление**: 2025-12-25 (Этап 2 завершён)
**Последнее обновление**: 2025-12-25 (Этап 4 завершён)

View File

@ -0,0 +1,84 @@
/**
* Default Settings
*
* Дефолтные настройки приложения.
*/
/**
* Default settings structure for Brief Bench
* User-editable fields only - server configuration managed by FastAPI backend
*/
export const defaultSettings = {
// Active environment
activeEnvironment: 'ift', // 'ift', 'psi', or 'prod'
// Environment-specific settings
environments: {
ift: {
name: 'ИФТ',
apiMode: 'bench', // 'bench' or 'backend'
// Optional headers for RAG backend
bearerToken: '', // Bearer token for authorization (optional)
systemPlatform: '', // System-Platform header (optional)
systemPlatformUser: '', // System-Platform-User header (optional)
// Backend mode settings
platformUserId: '',
platformId: '',
withClassify: false,
resetSessionMode: true // Reset session after each question
},
psi: {
name: 'ПСИ',
apiMode: 'bench', // 'bench' or 'backend'
// Optional headers for RAG backend
bearerToken: '',
systemPlatform: '',
systemPlatformUser: '',
// Backend mode settings
platformUserId: '',
platformId: '',
withClassify: false,
resetSessionMode: true
},
prod: {
name: 'ПРОМ',
apiMode: 'bench', // 'bench' or 'backend'
// Optional headers for RAG backend
bearerToken: '',
systemPlatform: '',
systemPlatformUser: '',
// Backend mode settings
platformUserId: '',
platformId: '',
withClassify: false,
resetSessionMode: true
}
},
// UI settings
theme: 'light',
autoSaveDrafts: true,
requestTimeout: 1800000, // 30 minutes in milliseconds
// Query settings
defaultWithDocs: true,
defaultQueryMode: 'questions' // 'questions' or 'raw-json'
}
/**
* Default environment state structure
*/
export const defaultEnvironmentState = {
currentRequest: null,
currentResponse: null,
currentAnswerIndex: 0,
annotations: {},
requestTimestamp: null,
requestId: null
}

225
static/js/data/storage.js Normal file
View File

@ -0,0 +1,225 @@
/**
* Storage Service
*
* Обертка над localStorage для работы с данными приложения.
*/
import { STORAGE_KEYS } from '../config.js'
/**
* Save environment data to localStorage
* @param {string} env - Environment name ('ift', 'psi', 'prod')
* @param {object} data - Environment data to save
*/
export function saveEnvironmentData(env, data) {
try {
const key = STORAGE_KEYS.envData(env)
localStorage.setItem(key, JSON.stringify(data))
} catch (e) {
console.error(`Failed to save data for environment ${env}:`, e)
}
}
/**
* Load environment data from localStorage
* @param {string} env - Environment name ('ift', 'psi', 'prod')
* @returns {object|null} Environment data or null if not found
*/
export function loadEnvironmentData(env) {
try {
const key = STORAGE_KEYS.envData(env)
const savedData = localStorage.getItem(key)
if (!savedData) {
return null
}
return JSON.parse(savedData)
} catch (e) {
console.error(`Failed to load data for environment ${env}:`, e)
return null
}
}
/**
* Clear environment data from localStorage
* @param {string} env - Environment name ('ift', 'psi', 'prod')
*/
export function clearEnvironmentData(env) {
try {
const key = STORAGE_KEYS.envData(env)
localStorage.removeItem(key)
} catch (e) {
console.error(`Failed to clear data for environment ${env}:`, e)
}
}
/**
* Clear all environment data
*/
export function clearAllEnvironmentData() {
const environments = ['ift', 'psi', 'prod']
environments.forEach(env => clearEnvironmentData(env))
}
/**
* Save token to localStorage
* @param {string} token - JWT token
*/
export function saveToken(token) {
try {
localStorage.setItem(STORAGE_KEYS.token, token)
} catch (e) {
console.error('Failed to save token:', e)
}
}
/**
* Load token from localStorage
* @returns {string|null} JWT token or null if not found
*/
export function loadToken() {
try {
return localStorage.getItem(STORAGE_KEYS.token)
} catch (e) {
console.error('Failed to load token:', e)
return null
}
}
/**
* Clear token from localStorage
*/
export function clearToken() {
try {
localStorage.removeItem(STORAGE_KEYS.token)
} catch (e) {
console.error('Failed to clear token:', e)
}
}
/**
* Save user info to localStorage
* @param {object} user - User object
*/
export function saveUser(user) {
try {
localStorage.setItem(STORAGE_KEYS.user, JSON.stringify(user))
} catch (e) {
console.error('Failed to save user:', e)
}
}
/**
* Load user info from localStorage
* @returns {object|null} User object or null if not found
*/
export function loadUser() {
try {
const userData = localStorage.getItem(STORAGE_KEYS.user)
return userData ? JSON.parse(userData) : null
} catch (e) {
console.error('Failed to load user:', e)
return null
}
}
/**
* Clear user info from localStorage
*/
export function clearUser() {
try {
localStorage.removeItem(STORAGE_KEYS.user)
} catch (e) {
console.error('Failed to clear user:', e)
}
}
/**
* Save settings to localStorage
* @param {object} settings - Settings object
*/
export function saveSettings(settings) {
try {
localStorage.setItem(STORAGE_KEYS.settings, JSON.stringify(settings))
} catch (e) {
console.error('Failed to save settings:', e)
}
}
/**
* Load settings from localStorage
* @returns {object|null} Settings object or null if not found
*/
export function loadSettings() {
try {
const settingsData = localStorage.getItem(STORAGE_KEYS.settings)
return settingsData ? JSON.parse(settingsData) : null
} catch (e) {
console.error('Failed to load settings:', e)
return null
}
}
/**
* Clear settings from localStorage
*/
export function clearSettings() {
try {
localStorage.removeItem(STORAGE_KEYS.settings)
} catch (e) {
console.error('Failed to clear settings:', e)
}
}
/**
* Save annotation draft to localStorage
* @param {object} draft - Annotation draft object
*/
export function saveAnnotationDraft(draft) {
try {
localStorage.setItem(STORAGE_KEYS.annotations, JSON.stringify(draft))
} catch (e) {
console.error('Failed to save annotation draft:', e)
}
}
/**
* Load annotation draft from localStorage
* @returns {object|null} Annotation draft or null if not found
*/
export function loadAnnotationDraft() {
try {
const draftData = localStorage.getItem(STORAGE_KEYS.annotations)
return draftData ? JSON.parse(draftData) : null
} catch (e) {
console.error('Failed to load annotation draft:', e)
return null
}
}
/**
* Clear annotation draft from localStorage
*/
export function clearAnnotationDraft() {
try {
localStorage.removeItem(STORAGE_KEYS.annotations)
} catch (e) {
console.error('Failed to clear annotation draft:', e)
}
}
/**
* Clear all data from localStorage
*/
export function clearAllData() {
try {
clearToken()
clearUser()
clearSettings()
clearAllEnvironmentData()
clearAnnotationDraft()
} catch (e) {
console.error('Failed to clear all data:', e)
}
}

View File

@ -0,0 +1,257 @@
/**
* Brief Bench API Client
*
* Взаимодействие с FastAPI backend.
*/
import { saveToken, loadToken, clearToken } from '../data/storage.js'
import { API_CONFIG } from '../config.js'
/**
* BriefBenchAPI class - handles all API communication with FastAPI backend
*/
class BriefBenchAPI {
constructor() {
this.baseURL = API_CONFIG.baseURL
}
// ============================================
// Internal Helpers
// ============================================
_getToken() {
return loadToken()
}
_setToken(token) {
saveToken(token)
}
_clearToken() {
clearToken()
}
_getHeaders(includeAuth = true) {
const headers = {
'Content-Type': 'application/json'
}
if (includeAuth) {
const token = this._getToken()
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
}
return headers
}
async _handleResponse(response) {
// Handle 401 Unauthorized
if (response.status === 401) {
this._clearToken()
throw new Error('Сессия истекла. Пожалуйста, войдите снова.')
}
// Handle 502 Bad Gateway (RAG backend error)
if (response.status === 502) {
throw new Error('RAG backend недоступен или вернул ошибку')
}
// Handle other errors
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`)
}
// Handle 204 No Content
if (response.status === 204) {
return null
}
return await response.json()
}
async _request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`
try {
const response = await fetch(url, options)
return await this._handleResponse(response)
} catch (error) {
console.error(`API request failed: ${endpoint}`, error)
throw error
}
}
// ============================================
// Auth API
// ============================================
/**
* Авторизация с 8-значным логином
* @param {string} login - 8-значный логин
* @returns {Promise<{access_token: string, user: object}>}
*/
async login(login) {
const response = await this._request(`/auth/login?login=${login}`, {
method: 'POST',
headers: this._getHeaders(false)
})
// Сохранить токен
this._setToken(response.access_token)
return response
}
/**
* Выход (очистка токена)
*/
logout() {
this._clearToken()
window.location.reload()
}
/**
* Проверка авторизации
* @returns {boolean}
*/
isAuthenticated() {
return !!this._getToken()
}
// ============================================
// Settings API
// ============================================
/**
* Получить настройки пользователя для всех окружений
* @returns {Promise<{user_id: string, settings: object, updated_at: string}>}
*/
async getSettings() {
return await this._request('/settings', {
method: 'GET',
headers: this._getHeaders()
})
}
/**
* Обновить настройки пользователя
* @param {object} settings - Объект с настройками для окружений
* @returns {Promise<{user_id: string, settings: object, updated_at: string}>}
*/
async updateSettings(settings) {
return await this._request('/settings', {
method: 'PATCH',
headers: this._getHeaders(),
body: JSON.stringify({ settings })
})
}
// ============================================
// Query API
// ============================================
/**
* Отправить batch запрос (Bench mode)
* @param {string} environment - Окружение (ift/psi/prod)
* @param {Array<{body: string, with_docs: boolean}>} questions - Массив вопросов
* @returns {Promise<{request_id: string, timestamp: string, environment: string, response: object}>}
*/
async benchQuery(environment, questions) {
return await this._request('/query/bench', {
method: 'POST',
headers: this._getHeaders(),
body: JSON.stringify({
environment,
questions
})
})
}
/**
* Отправить последовательные запросы (Backend mode)
* @param {string} environment - Окружение (ift/psi/prod)
* @param {Array<{body: string, with_docs: boolean}>} questions - Массив вопросов
* @param {boolean} resetSession - Сбрасывать ли сессию после каждого вопроса
* @returns {Promise<{request_id: string, timestamp: string, environment: string, response: object}>}
*/
async backendQuery(environment, questions, resetSession = true) {
return await this._request('/query/backend', {
method: 'POST',
headers: this._getHeaders(),
body: JSON.stringify({
environment,
questions,
reset_session: resetSession
})
})
}
// ============================================
// Analysis Sessions API
// ============================================
/**
* Сохранить сессию анализа
* @param {object} sessionData - Данные сессии
* @returns {Promise<object>}
*/
async saveSession(sessionData) {
return await this._request('/analysis/sessions', {
method: 'POST',
headers: this._getHeaders(),
body: JSON.stringify(sessionData)
})
}
/**
* Получить список сессий
* @param {string|null} environment - Фильтр по окружению (опционально)
* @param {number} limit - Лимит результатов
* @param {number} offset - Смещение для пагинации
* @returns {Promise<{sessions: Array, total: number}>}
*/
async getSessions(environment = null, limit = 50, offset = 0) {
const params = new URLSearchParams({ limit, offset })
if (environment) {
params.append('environment', environment)
}
return await this._request(`/analysis/sessions?${params}`, {
method: 'GET',
headers: this._getHeaders()
})
}
/**
* Получить конкретную сессию
* @param {string} sessionId - ID сессии
* @returns {Promise<object>}
*/
async getSession(sessionId) {
return await this._request(`/analysis/sessions/${sessionId}`, {
method: 'GET',
headers: this._getHeaders()
})
}
/**
* Удалить сессию
* @param {string} sessionId - ID сессии
* @returns {Promise<null>}
*/
async deleteSession(sessionId) {
return await this._request(`/analysis/sessions/${sessionId}`, {
method: 'DELETE',
headers: this._getHeaders()
})
}
}
// Export singleton instance as default export
export default new BriefBenchAPI()
// Export class for testing purposes
export { BriefBenchAPI }

View File

@ -0,0 +1,67 @@
/**
* Auth Service
*
* Сервис авторизации пользователей.
*/
import api from './api-client.js'
import settingsService from './settings.service.js'
/**
* Проверить авторизацию при загрузке страницы
* @returns {Promise<boolean>} True если авторизован
*/
export async function checkAuth() {
if (!api.isAuthenticated()) {
return false
}
// Попробовать загрузить настройки (валидация токена)
try {
await settingsService.loadFromServer()
return true
} catch (error) {
console.error('Token validation failed:', error)
return false
}
}
/**
* Авторизация пользователя
* @param {string} login - 8-значный логин
* @returns {Promise<object>} User info
*/
export async function login(login) {
// Валидация формата логина
if (!/^[0-9]{8}$/.test(login)) {
throw new Error('Логин должен состоять из 8 цифр')
}
try {
const response = await api.login(login)
console.log('Login successful:', response.user)
// Загрузить настройки с сервера
await settingsService.loadFromServer()
return response.user
} catch (error) {
console.error('Login failed:', error)
throw error
}
}
/**
* Выход из системы
*/
export function logout() {
api.logout()
}
/**
* Проверить авторизован ли пользователь
* @returns {boolean}
*/
export function isAuthenticated() {
return api.isAuthenticated()
}

View File

@ -0,0 +1,186 @@
/**
* Query Service
*
* Сервис отправки запросов к RAG backend.
*/
import api from './api-client.js'
import appState from '../state/appState.js'
import { validateJSON } from '../utils/validation.utils.js'
import { generateUUID } from '../utils/format.utils.js'
import { loadFileAsJSON, loadFileAsText } from '../utils/file.utils.js'
/**
* Построить тело запроса из UI
* @param {string} mode - Режим ('questions' или 'raw-json')
* @param {string} questionsText - Текст вопросов (для режима questions)
* @param {string} jsonText - JSON текст (для режима raw-json)
* @returns {Array<{body: string, with_docs: boolean}>} Массив вопросов
*/
export function buildRequestBody(mode, questionsText, jsonText) {
if (mode === 'questions') {
const questions = questionsText
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
if (questions.length === 0) {
throw new Error('Введите хотя бы один вопрос')
}
const settings = appState.settings || {}
const defaultWithDocs = settings.defaultWithDocs !== undefined
? settings.defaultWithDocs
: true
return questions.map(q => ({
body: q,
with_docs: defaultWithDocs
}))
} else if (mode === 'raw-json') {
const validation = validateJSON(jsonText)
if (!validation.valid) {
throw new Error(validation.error)
}
return validation.data
} else {
throw new Error(`Неизвестный режим: ${mode}`)
}
}
/**
* Отправить запрос к RAG backend
* @param {string} environment - Окружение (ift/psi/prod)
* @param {string} apiMode - Режим API ('bench' или 'backend')
* @param {Array} questions - Массив вопросов
* @param {boolean} resetSession - Сбрасывать ли сессию (только для backend mode)
* @returns {Promise<object>} API response
*/
export async function sendQuery(environment, apiMode, questions, resetSession = true) {
let apiResponse
if (apiMode === 'bench') {
apiResponse = await api.benchQuery(environment, questions)
} else if (apiMode === 'backend') {
apiResponse = await api.backendQuery(environment, questions, resetSession)
} else {
throw new Error(`Неизвестный режим API: ${apiMode}`)
}
// Validate response format
if (!apiResponse.response ||
!apiResponse.response.answers ||
!Array.isArray(apiResponse.response.answers)) {
throw new Error('Некорректный формат ответа: отсутствует поле "answers"')
}
return apiResponse
}
/**
* Обработать результат запроса и обновить AppState
* @param {string} environment - Окружение
* @param {Array} requestBody - Тело запроса
* @param {object} apiResponse - Ответ от API
*/
export function processQueryResponse(environment, requestBody, apiResponse) {
const env = appState.getEnvironment(environment)
// Update environment state
env.currentRequest = requestBody
env.currentResponse = apiResponse.response
env.requestId = apiResponse.request_id
env.requestTimestamp = apiResponse.timestamp
env.currentAnswerIndex = 0
env.annotations = {}
// Save to localStorage
appState.saveEnvironmentToStorage(environment)
return env
}
/**
* Загрузить запрос из файла
* @param {File} file - JSON файл с запросом
* @returns {Promise<Array>} Массив вопросов
*/
export async function loadRequestFromFile(file) {
try {
const data = await loadFileAsJSON(file)
// Validate it's an array
if (!Array.isArray(data)) {
throw new Error('Файл должен содержать JSON массив')
}
return data
} catch (error) {
console.error('Error loading request from file:', error)
throw new Error(`Ошибка загрузки запроса: ${error.message}`)
}
}
/**
* Загрузить ответ из файла
* @param {File} file - JSON файл с ответом
* @param {string} environment - Текущее окружение
* @returns {Promise<object>} Загруженный ответ
*/
export async function loadResponseFromFile(file, environment) {
try {
const data = await loadFileAsJSON(file)
// Validate response format
if (!data.answers || !Array.isArray(data.answers)) {
throw new Error('Файл должен содержать объект с полем "answers" (массив)')
}
const env = appState.getEnvironment(environment)
// Set response
env.currentResponse = data
env.currentAnswerIndex = 0
env.requestTimestamp = new Date().toISOString()
env.requestId = 'loaded-' + generateUUID()
env.annotations = {}
// Try to reconstruct request from questions in response
env.currentRequest = data.answers.map(answer => ({
body: answer.question,
with_docs: true
}))
// Save to localStorage
appState.saveEnvironmentToStorage(environment)
return data
} catch (error) {
console.error('Error loading response from file:', error)
throw new Error(`Ошибка загрузки ответа: ${error.message}`)
}
}
/**
* Извлечь вопросы из textarea
* @param {string} text - Текст из textarea
* @returns {Array<string>} Массив вопросов
*/
export function extractQuestions(text) {
return text
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
}
// Export as default object
export default {
buildRequestBody,
sendQuery,
processQueryResponse,
loadRequestFromFile,
loadResponseFromFile,
extractQuestions
}

View File

@ -0,0 +1,117 @@
/**
* Settings Service
*
* Сервис управления настройками пользователя.
*/
import api from './api-client.js'
import appState from '../state/appState.js'
import { ENV_NAMES } from '../config.js'
/**
* Загрузить настройки с сервера
* @returns {Promise<object>} Загруженные настройки
*/
export async function loadFromServer() {
try {
const response = await api.getSettings()
// Преобразовать в формат AppState.settings
const settings = {
activeEnvironment: appState.getCurrentEnvironment(),
environments: {
ift: {
name: ENV_NAMES.ift,
...response.settings.ift
},
psi: {
name: ENV_NAMES.psi,
...response.settings.psi
},
prod: {
name: ENV_NAMES.prod,
...response.settings.prod
}
},
requestTimeout: 1800000 // 30 минут (фиксировано)
}
// Обновить AppState
appState.setSettings(settings)
console.log('Settings loaded from server:', settings)
return settings
} catch (error) {
console.error('Failed to load settings from server:', error)
throw error
}
}
/**
* Сохранить настройки на сервер
* @param {object} settings - Настройки для сохранения
* @returns {Promise<object>} Сохранённые настройки
*/
export async function saveToServer(settings) {
try {
// Извлечь только поля, которые сервер ожидает
const settingsToSave = {
ift: extractEnvironmentSettings(settings.environments.ift),
psi: extractEnvironmentSettings(settings.environments.psi),
prod: extractEnvironmentSettings(settings.environments.prod)
}
await api.updateSettings(settingsToSave)
appState.setSettings(settings)
console.log('Settings saved to server')
return settings
} catch (error) {
console.error('Failed to save settings to server:', error)
throw error
}
}
/**
* Извлечь только нужные поля для сервера
* @param {object} envSettings - Настройки окружения
* @returns {object} Очищенные настройки для API
*/
export function extractEnvironmentSettings(envSettings) {
return {
apiMode: envSettings.apiMode,
bearerToken: envSettings.bearerToken || null,
systemPlatform: envSettings.systemPlatform || null,
systemPlatformUser: envSettings.systemPlatformUser || null,
platformUserId: envSettings.platformUserId || null,
platformId: envSettings.platformId || null,
withClassify: envSettings.withClassify || false,
resetSessionMode: envSettings.resetSessionMode !== false
}
}
/**
* Получить настройки текущего окружения
* @returns {object} Настройки текущего окружения
*/
export function getCurrentEnvironmentSettings() {
return appState.getCurrentEnvSettings()
}
/**
* Обновить настройки текущего окружения
* @param {object} envSettings - Новые настройки окружения
*/
export function updateCurrentEnvironmentSettings(envSettings) {
const env = appState.getCurrentEnvironment()
appState.updateEnvironmentSettings(env, envSettings)
}
// Export as default object
export default {
loadFromServer,
saveToServer,
extractEnvironmentSettings,
getCurrentEnvironmentSettings,
updateCurrentEnvironmentSettings
}

290
static/js/state/appState.js Normal file
View File

@ -0,0 +1,290 @@
/**
* Application State
*
* Singleton класс для управления глобальным состоянием приложения.
*/
import { defaultSettings, defaultEnvironmentState } from '../data/defaults.js'
import {
saveEnvironmentData,
loadEnvironmentData,
saveSettings,
loadSettings
} from '../data/storage.js'
/**
* AppState class - manages global application state
* Implements Singleton pattern
*/
class AppState {
constructor() {
if (AppState.instance) {
return AppState.instance
}
// Settings from server
this.settings = null
// Current active environment: 'ift', 'psi', or 'prod'
this.currentEnvironment = 'ift'
// Environment-specific runtime data
this.environments = {
ift: { ...defaultEnvironmentState },
psi: { ...defaultEnvironmentState },
prod: { ...defaultEnvironmentState }
}
AppState.instance = this
}
/**
* Get current environment
* @returns {string} Current environment name
*/
getCurrentEnvironment() {
return this.currentEnvironment
}
/**
* Set current environment
* @param {string} env - Environment name ('ift', 'psi', 'prod')
*/
setCurrentEnvironment(env) {
if (!['ift', 'psi', 'prod'].includes(env)) {
console.error(`Invalid environment: ${env}`)
return
}
this.currentEnvironment = env
}
/**
* Get current environment state
* @returns {object} Current environment state
*/
getCurrentEnv() {
return this.environments[this.currentEnvironment]
}
/**
* Get current environment settings
* @returns {object} Current environment settings
*/
getCurrentEnvSettings() {
if (!this.settings) {
return null
}
return this.settings.environments[this.currentEnvironment]
}
/**
* Get environment state by name
* @param {string} env - Environment name
* @returns {object} Environment state
*/
getEnvironment(env) {
return this.environments[env]
}
/**
* Set settings
* @param {object} settings - Settings object from server
*/
setSettings(settings) {
this.settings = settings
// Update current environment from settings
if (settings.activeEnvironment) {
this.setCurrentEnvironment(settings.activeEnvironment)
}
}
/**
* Update settings
* @param {object} updates - Settings updates
*/
updateSettings(updates) {
if (!this.settings) {
this.settings = { ...defaultSettings }
}
this.settings = {
...this.settings,
...updates
}
// Update current environment if changed
if (updates.activeEnvironment) {
this.setCurrentEnvironment(updates.activeEnvironment)
}
}
/**
* Update environment-specific settings
* @param {string} env - Environment name
* @param {object} envSettings - Environment settings updates
*/
updateEnvironmentSettings(env, envSettings) {
if (!this.settings) {
this.settings = { ...defaultSettings }
}
if (!this.settings.environments) {
this.settings.environments = {}
}
this.settings.environments[env] = {
...this.settings.environments[env],
...envSettings
}
}
/**
* Set current request for environment
* @param {string} env - Environment name
* @param {object} request - Request object
*/
setRequest(env, request) {
this.environments[env].currentRequest = request
}
/**
* Set current response for environment
* @param {string} env - Environment name
* @param {object} response - Response object
*/
setResponse(env, response) {
this.environments[env].currentResponse = response
}
/**
* Set request metadata
* @param {string} env - Environment name
* @param {string} requestId - Request ID
* @param {string} timestamp - Request timestamp
*/
setRequestMetadata(env, requestId, timestamp) {
this.environments[env].requestId = requestId
this.environments[env].requestTimestamp = timestamp
}
/**
* Set current answer index
* @param {string} env - Environment name
* @param {number} index - Answer index
*/
setCurrentAnswerIndex(env, index) {
this.environments[env].currentAnswerIndex = index
}
/**
* Set annotations for environment
* @param {string} env - Environment name
* @param {object} annotations - Annotations object
*/
setAnnotations(env, annotations) {
this.environments[env].annotations = annotations
}
/**
* Get annotations for environment
* @param {string} env - Environment name
* @returns {object} Annotations object
*/
getAnnotations(env) {
return this.environments[env].annotations || {}
}
/**
* Clear environment data
* @param {string} env - Environment name
*/
clearEnvironment(env) {
this.environments[env] = { ...defaultEnvironmentState }
}
/**
* Save environment data to localStorage
* @param {string} env - Environment name
*/
saveEnvironmentToStorage(env) {
const data = this.environments[env]
saveEnvironmentData(env, data)
}
/**
* Load environment data from localStorage
* @param {string} env - Environment name
*/
loadEnvironmentFromStorage(env) {
const data = loadEnvironmentData(env)
if (data) {
this.environments[env] = data
}
}
/**
* Save settings to localStorage
*/
saveSettingsToStorage() {
if (this.settings) {
saveSettings(this.settings)
}
}
/**
* Load settings from localStorage
*/
loadSettingsFromStorage() {
const settings = loadSettings()
if (settings) {
this.setSettings(settings)
}
}
/**
* Load all data from localStorage
*/
loadAllFromStorage() {
this.loadSettingsFromStorage()
const environments = ['ift', 'psi', 'prod']
environments.forEach(env => this.loadEnvironmentFromStorage(env))
}
/**
* Save all data to localStorage
*/
saveAllToStorage() {
this.saveSettingsToStorage()
const environments = ['ift', 'psi', 'prod']
environments.forEach(env => this.saveEnvironmentToStorage(env))
}
/**
* Reset to default state
*/
reset() {
this.settings = null
this.currentEnvironment = 'ift'
this.environments = {
ift: { ...defaultEnvironmentState },
psi: { ...defaultEnvironmentState },
prod: { ...defaultEnvironmentState }
}
}
}
// Create singleton instance
const appState = new AppState()
// Export singleton instance as default export
export default appState
// Export class for testing purposes
export { AppState }