/
bytevalue
/
optimal
Обзор
Документация
Войти
/
bytevalue
/
optimal
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/main/resources/static/js/state.js
472 строки
15 KB
bytevalue
Edit chat title
08 дек 2025, 13:41
08 дек 2025, 13:41
768e132
Код
Авторство
О чём код?
/** * State Management System * Handles application state with pub/sub pattern */ class StateManager { constructor() { this.state = { userId: this.loadOrCreateUserId(), chats: [], activeChats: [], // Активные чаты deletedChats: [], // Удаленные чаты activeChatId: null, messages: {}, // {chatId: [messages]} loading: false, error: null, isConnected: false, messageStatus: {}, unreadChats: {}, lastSeenChats: {}, showWelcome: true, // Новые поля pipelineStages: {}, // {messageId: {stage: string, timestamp: string}} pendingAssistantMessages: {}, // {chatId: [tempIds]} }; this.subscribers = {}; this.history = []; this.maxHistorySize = 100; console.log('🎯 StateManager initialized with user:', this.state.userId); } /** * Load or create user ID */ loadOrCreateUserId() { let userId = localStorage.getItem(CONFIG.USER.STORAGE_KEY); if (!userId) { userId = CONFIG.USER.DEFAULT_ID(); localStorage.setItem(CONFIG.USER.STORAGE_KEY, userId); } return userId; } /** * Get current state or specific path */ getState(path = null) { if (!path) { return { ...this.state }; } const keys = path.split('.'); let current = this.state; for (const key of keys) { if (current?.[key] !== undefined) { current = current[key]; } else { return undefined; } } return current; } /** * Set state value */ setState(path, value) { const keys = path.split('.'); const lastKey = keys.pop(); let current = this.state; for (const key of keys) { if (!(key in current)) { current[key] = {}; } current = current[key]; } const oldValue = current[lastKey]; current[lastKey] = value; this.recordHistory(path, oldValue, value); this.notifySubscribers(path, value, oldValue); return this; } /** * Set all chats (legacy method) */ setChats(chats) { // Разделяем чаты по статусу const activeChats = Array.isArray(chats) ? chats.filter(chat => chat.status !== CONFIG.CHAT_STATUS.DELETED) : []; const deletedChats = Array.isArray(chats) ? chats.filter(chat => chat.status === CONFIG.CHAT_STATUS.DELETED) : []; this.state.chats = Array.isArray(chats) ? chats : []; this.state.activeChats = activeChats; this.state.deletedChats = deletedChats; this.notifySubscribers('chats', this.state.chats); this.notifySubscribers('activeChats', this.state.activeChats); this.notifySubscribers('deletedChats', this.state.deletedChats); } renameChat(chatId, newTitle) { // Update chat in all lists const updateChatInList = (list) => list.map(chat => chat.id === chatId ? { ...chat, title: newTitle } : chat); this.state.chats = updateChatInList(this.state.chats); this.state.activeChats = updateChatInList(this.state.activeChats); this.state.deletedChats = updateChatInList(this.state.deletedChats); // Update active chat if needed if (this.activeChat?.id === chatId) { this.activeChat.title = newTitle; } // Notify subscribers this.notifySubscribers('chats', this.state.chats); this.notifySubscribers('activeChats', this.state.activeChats); this.notifySubscribers('deletedChats', this.state.deletedChats); // Publish event eventBus.publish(CONFIG.EVENTS.CHAT_RENAMED, { chatId, newTitle }); return this; } /** * Set active chats */ setActiveChats(chats) { this.state.activeChats = Array.isArray(chats) ? chats : []; this.notifySubscribers('activeChats', this.state.activeChats); } /** * Set deleted chats */ setDeletedChats(chats) { this.state.deletedChats = Array.isArray(chats) ? chats : []; this.notifySubscribers('deletedChats', this.state.deletedChats); } /** * Add or update chat */ addOrUpdateChat(chat) { // Обновляем общий список const index = this.state.chats.findIndex((c) => c.id === chat.id); if (index >= 0) { this.state.chats[index] = { ...this.state.chats[index], ...chat }; } else { this.state.chats.unshift(chat); } // Обновляем активные/удаленные списки в зависимости от статуса if (chat.status === CONFIG.CHAT_STATUS.DELETED) { // Удаляем из активных, добавляем в удаленные this.state.activeChats = this.state.activeChats.filter(c => c.id !== chat.id); const deletedIndex = this.state.deletedChats.findIndex(c => c.id === chat.id); if (deletedIndex >= 0) { this.state.deletedChats[deletedIndex] = chat; } else { this.state.deletedChats.unshift(chat); } } else { // Добавляем/обновляем в активных, удаляем из удаленных const activeIndex = this.state.activeChats.findIndex(c => c.id === chat.id); if (activeIndex >= 0) { this.state.activeChats[activeIndex] = chat; } else { this.state.activeChats.unshift(chat); } this.state.deletedChats = this.state.deletedChats.filter(c => c.id !== chat.id); } this.notifySubscribers('chats', this.state.chats); this.notifySubscribers('activeChats', this.state.activeChats); this.notifySubscribers('deletedChats', this.state.deletedChats); } /** * Remove chat from all lists */ removeChat(chatId) { // Удаляем из всех списков this.state.chats = this.state.chats.filter((c) => c.id !== chatId); this.state.activeChats = this.state.activeChats.filter((c) => c.id !== chatId); this.state.deletedChats = this.state.deletedChats.filter((c) => c.id !== chatId); // Если это активный чат - сбрасываем if (this.state.activeChatId === chatId) { this.state.activeChatId = null; this.state.showWelcome = true; this.notifySubscribers('activeChatId', null); this.notifySubscribers('showWelcome', true); } // Удаляем сообщения чата delete this.state.messages[chatId]; this.notifySubscribers('chats', this.state.chats); this.notifySubscribers('activeChats', this.state.activeChats); this.notifySubscribers('deletedChats', this.state.deletedChats); this.notifySubscribers(`messages.${chatId}`, null); } /** * Set messages for chat */ setMessages(chatId, messages) { this.state.messages[chatId] = Array.isArray(messages) ? messages : []; this.notifySubscribers(`messages.${chatId}`, this.state.messages[chatId]); } /** * Add message to chat */ addMessage(chatId, message) { if (!this.state.messages[chatId]) { this.state.messages[chatId] = []; } // Check if message already exists const existingIndex = this.state.messages[chatId].findIndex(m => m.id === message.id); if (existingIndex >= 0) { // Update existing message this.state.messages[chatId][existingIndex] = message; } else { // Add new message this.state.messages[chatId].push(message); } // Sort by createdAt this.state.messages[chatId].sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt) ); // Если сообщение от ассистента и чат не активен - помечаем как непрочитанный if (message.role === 'ASSISTANT' && this.state.activeChatId !== chatId) { this.markChatAsUnread(chatId); } this.notifySubscribers(`messages.${chatId}`, this.state.messages[chatId]); this.notifySubscribers('message:updated', { chatId, message }); } findLastAssistantMessageId(chatId) { const messages = this.state.messages[chatId] || []; // Ищем последнее сообщение ассистента (с конца массива) for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].role === 'ASSISTANT') { return messages[i].id; } } return null; } /** * Найти ID сообщения ассистента с пустым контентом */ findEmptyAssistantMessageId(chatId) { const messages = this.state.messages[chatId] || []; // Ищем первое сообщение ассистента с пустым контентом for (let i = messages.length - 1; i >= 0; i--) { if (messages[i].role === 'ASSISTANT' && (!messages[i].content || messages[i].content.trim() === '')) { return messages[i].id; } } return null; } /** * Update message content */ updateMessageContent(chatId, messageId, content, status = null) { if (!this.state.messages[chatId]) return; const messages = [...this.state.messages[chatId]]; const messageIndex = messages.findIndex(m => m.id === messageId); if (messageIndex >= 0) { // Сохраняем все остальные поля сообщения messages[messageIndex] = { ...messages[messageIndex], content: content }; if (status !== null) { messages[messageIndex].status = status; } this.state.messages[chatId] = messages; this.notifySubscribers(`messages.${chatId}`, this.state.messages[chatId]); this.notifySubscribers('message:updated', { chatId, message: messages[messageIndex] }); } } /** * Get messages for active chat */ getActiveMessages() { return this.state.messages[this.state.activeChatId] || []; } /** * Пометить чат как непрочитанный */ markChatAsUnread(chatId) { this.state.unreadChats[chatId] = new Date().toISOString(); this.notifySubscribers('unreadChats', this.state.unreadChats); this.notifySubscribers(`unreadChats.${chatId}`, true); } /** * Пометить чат как прочитанный */ markChatAsRead(chatId) { if (this.state.unreadChats[chatId]) { delete this.state.unreadChats[chatId]; this.state.lastSeenChats[chatId] = new Date().toISOString(); this.notifySubscribers('unreadChats', this.state.unreadChats); this.notifySubscribers(`unreadChats.${chatId}`, false); } } /** * Проверить, непрочитан ли чат */ isChatUnread(chatId) { return !!this.state.unreadChats[chatId]; } /** * Установить этап пайплайна для сообщения */ setPipelineStage(messageId, stage) { this.state.pipelineStages[messageId] = { stage: stage, timestamp: new Date().toISOString() }; this.notifySubscribers(`pipelineStages.${messageId}`, stage); this.notifySubscribers('pipelineStages', this.state.pipelineStages); } /** * Получить этап пайплайна для сообщения */ getPipelineStage(messageId) { return this.state.pipelineStages[messageId]?.stage || null; } /** * Удалить этап пайплайна (при завершении) */ removePipelineStage(messageId) { delete this.state.pipelineStages[messageId]; this.notifySubscribers(`pipelineStages.${messageId}`, null); this.notifySubscribers('pipelineStages', this.state.pipelineStages); } /** * Добавить временный ID сообщения ассистента */ addPendingAssistantMessage(chatId, tempId) { if (!this.state.pendingAssistantMessages[chatId]) { this.state.pendingAssistantMessages[chatId] = []; } this.state.pendingAssistantMessages[chatId].push(tempId); this.notifySubscribers('pendingAssistantMessages', this.state.pendingAssistantMessages); } /** * Удалить временный ID (когда получили реальный ID) */ removePendingAssistantMessage(chatId, tempId) { if (this.state.pendingAssistantMessages[chatId]) { this.state.pendingAssistantMessages[chatId] = this.state.pendingAssistantMessages[chatId].filter(id => id !== tempId); this.notifySubscribers('pendingAssistantMessages', this.state.pendingAssistantMessages); } } /** * Показать/скрыть приветственный экран */ setWelcomeScreen(show) { this.state.showWelcome = show; this.notifySubscribers('showWelcome', show); } /** * Subscribe to state changes */ subscribe(path, callback) { if (!this.subscribers[path]) { this.subscribers[path] = []; } this.subscribers[path].push(callback); // Return unsubscribe function return () => { this.subscribers[path] = this.subscribers[path].filter((cb) => cb !== callback); }; } /** * Notify subscribers */ notifySubscribers(path, newValue, oldValue) { if (this.subscribers[path]) { this.subscribers[path].forEach((callback) => { try { callback(newValue, oldValue, path); } catch (error) { console.error('Error in subscriber:', error); } }); } } /** * Record history for debugging */ recordHistory(path, oldValue, newValue) { this.history.push({ timestamp: new Date().toISOString(), path, oldValue, newValue, }); if (this.history.length > this.maxHistorySize) { this.history.shift(); } } /** * Clear state */ clear() { this.state = { userId: this.state.userId, chats: [], activeChats: [], deletedChats: [], activeChatId: null, messages: {}, loading: false, error: null, isConnected: false, messageStatus: {}, unreadChats: {}, lastSeenChats: {}, pipelineStages: {}, pendingAssistantMessages: {}, showWelcome: true, }; } } // Global state manager instance const stateService = new StateManager(); console.log('📊 State management system ready');