# План улучшения чатов /messages **Дата:** 25 февраля 2026 **Цель:** Улучшить приложение /messages, используя лучшие практики из /messages-chatscope --- ## Текущие проблемы /messages ### ❌ Нет WebSocket real-time обновлений - Сообщения обновляются только при ручном обновлении страницы - Нет синхронизации между вкладками - Редактирование/удаление не отображается в реальном времени ### ❌ Нет индикатора "печатает..." - Непонятно, печатает ли собеседник ответ ### ❌ Оптимистичные обновления без подтверждения - Добавляет сообщение в UI сразу после отправки - Если запрос упадет, сообщение останется в UI (нет rollback) ### ✅ Что работает хорошо - Реакции на сообщения - Редактирование сообщений - Цитирование (reply_to) - Прикрепление файлов - Предпросмотр медиа - Эмодзи пикер - Бесконечная прокрутка (pagination) --- ## Что есть в /messages-chatscope ### ✅ WebSocket real-time обновления ```typescript const ws = new WebSocket('ws://localhost:9000/ws/'); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'chat_message') { setMessages(prev => [...prev, data.payload]); } else if (data.type === 'chat_message_edited') { setMessages(prev => prev.map(m => m.id === data.payload.id ? { ...m, ...data.payload } : m )); } else if (data.type === 'chat_message_deleted') { setMessages(prev => prev.filter(m => m.id !== data.message_id)); } else if (data.type === 'chat_user_typing') { setTyping(true); setTimeout(() => setTyping(false), 3000); } }; ``` ### ✅ Индикатор "печатает..." ```typescript const handleTyping = () => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify({ action: 'typing' })); } }; // В UI: {typing && } ``` ### ✅ WebSocket-first подход - Отправляет через REST API - Получает подтверждение через WebSocket - Нет оптимистичных обновлений (надежнее) --- ## План улучшений ### Этап 1: Добавить WebSocket (HIGH PRIORITY) 🔥 **Цель:** Добавить real-time обновления без удаления существующего функционала **Задачи:** 1. ✅ Создать хук `useWebSocket` для переиспользования 2. ✅ Подключить WebSocket к чату при открытии 3. ✅ Обрабатывать события: - `chat_message` - новое сообщение - `chat_message_edited` - редактирование - `chat_message_deleted` - удаление - `chat_user_typing` - печатает 4. ✅ Отключать WebSocket при закрытии чата 5. ✅ Переподключение при разрыве соединения **Файлы для изменения:** - `frontend/src/hooks/useWebSocket.ts` (новый) - `frontend/src/app/messages/[chatId]/page.tsx` (добавить хук) **Время:** ~2-3 часа --- ### Этап 2: Индикатор "печатает..." (MEDIUM PRIORITY) **Цель:** Показывать когда собеседник печатает **Задачи:** 1. ✅ Отправлять событие `typing` при вводе текста 2. ✅ Обрабатывать `chat_user_typing` от других пользователей 3. ✅ Добавить UI компонент "печатает..." 4. ✅ Таймаут 3 секунды без активности **Файлы для изменения:** - `frontend/src/app/messages/[chatId]/page.tsx` (UI + логика) - `frontend/src/components/TypingIndicator.tsx` (новый) **Время:** ~1 час --- ### Этап 3: Улучшить отправку сообщений (LOW PRIORITY) **Цель:** Более надежная доставка **Задачи:** 1. ✅ Убрать оптимистичное добавление сообщения 2. ✅ Добавить loading состояние для отправляемого сообщения 3. ✅ Показывать "Отправка..." пока сообщение не подтверждено 4. ✅ Добавлять сообщение только после WebSocket события **Альтернатива:** Оставить оптимистичное + добавить rollback при ошибке **Файлы для изменения:** - `frontend/src/app/messages/[chatId]/page.tsx` **Время:** ~1-2 часа --- ### Этап 4: Синхронизация между вкладками (NICE TO HAVE) **Цель:** Все открытые вкладки с одним чатом синхронизированы **Решение:** WebSocket автоматически решает эту проблему ✅ --- ## Архитектура решения ### useWebSocket Hook ```typescript // frontend/src/hooks/useWebSocket.ts export function useWebSocket(chatId: number | null) { const [isConnected, setIsConnected] = useState(false); const wsRef = useRef(null); const handlers = useRef({ onMessage: (data: any) => {}, onTyping: (userId: number) => {}, onError: (error: Event) => {}, }); useEffect(() => { if (!chatId) return; const ws = new WebSocket(`ws://localhost:9000/ws/`); wsRef.current = ws; ws.onopen = () => { setIsConnected(true); ws.send(JSON.stringify({ action: 'open_chat', chat_id: chatId, load_history: false })); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); handlers.current.onMessage(data); }; ws.onclose = () => setIsConnected(false); ws.onerror = handlers.current.onError; return () => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ action: 'close_chat', chat_id: chatId })); } ws.close(); }; }, [chatId]); const sendTyping = useCallback(() => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify({ action: 'typing' })); } }, []); return { isConnected, sendTyping, handlers }; } ``` ### Использование в /messages ```typescript export default function MessageDialogPage() { const { isConnected, sendTyping, handlers } = useWebSocket(chatId); const [typing, setTyping] = useState(false); // Обработка WebSocket событий useEffect(() => { handlers.current.onMessage = (data) => { if (data.type === 'chat_message') { setMessages(prev => uniqueMessagesById([...prev, data.payload])); } else if (data.type === 'chat_message_edited') { setMessages(prev => prev.map(m => m.id === data.payload.id ? { ...m, ...data.payload } : m )); } else if (data.type === 'chat_message_deleted') { setMessages(prev => prev.filter(m => m.id !== data.message_id)); } else if (data.type === 'chat_user_typing') { if (data.user_id !== user?.id) { setTyping(true); setTimeout(() => setTyping(false), 3000); } } }; }, [user?.id]); // Отправка "печатает..." const handleInputChange = (e: React.ChangeEvent) => { setMessageText(e.target.value); sendTyping(); // Отправляем индикатор }; // UI индикатора {typing && (
Собеседник печатает...
)} } ``` --- ## Риски и меры предосторожности ### ⚠️ Риск: Дублирование сообщений **Решение:** Использовать `uniqueMessagesById()` при добавлении ### ⚠️ Риск: Потеря соединения **Решение:** Добавить автоматическое переподключение ### ⚠️ Риск: Конфликт с существующей логикой **Решение:** Постепенное внедрение, тестирование на каждом этапе ### ⚠️ Риск: Backend не поддерживает все события **Решение:** Проверить backend WebSocket endpoints перед началом --- ## Тестирование ### Тест-кейсы: 1. ✅ Отправка сообщения отображается у обоих пользователей 2. ✅ Редактирование сообщения обновляется в реальном времени 3. ✅ Удаление сообщения исчезает у всех 4. ✅ Индикатор "печатает..." появляется при вводе 5. ✅ WebSocket переподключается при разрыве 6. ✅ Нет дублирования сообщений 7. ✅ Работает в нескольких вкладках одновременно --- ## Приоритезация **Неделя 1:** Этап 1 (WebSocket) + Этап 2 (Typing indicator) **Неделя 2:** Этап 3 (Улучшение отправки) + Тестирование **Неделя 3:** Bugfixes + Оптимизация --- ## Дополнительные улучшения (будущее) - 🔄 Показывать статус доставки сообщения (отправлено/доставлено/прочитано) - 📎 Drag & drop для файлов - 🖼️ Галерея медиа из чата - 🔍 Поиск по сообщениям - 📌 Закрепленные сообщения - 🔊 Звуковые уведомления о новых сообщениях - 💾 Офлайн режим с кешированием - ⚡ Оптимизация производительности для больших чатов (виртуализация списка)