add chat list

This commit is contained in:
unknown 2025-09-29 02:06:20 +03:00
parent 99029541d3
commit 21e8bb0ad0
5 changed files with 176 additions and 37 deletions

View File

@ -8,11 +8,15 @@ from threading import Thread
import time import time
import asyncio import asyncio
from app.core.database import get_last_login, get_session, set_last_login from app.core.database import get_last_login, get_session, set_last_login
# Импортируем сервис чатов
from app.core.services.chat_service import get_private_chats
class MainController(QStackedWidget): class MainController(QStackedWidget):
# Сигнал для показа уведомлений из любого потока # Сигнал для показа уведомлений из любого потока
# (message, is_error) # (message, is_error)
notification_requested = Signal(str, bool) notification_requested = Signal(str, bool)
# Сигнал для передачи загруженных данных чата в основной поток
chats_loaded = Signal(object)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -49,8 +53,9 @@ class MainController(QStackedWidget):
self.yobble_home_view = YobbleHomeView(username=username) self.yobble_home_view = YobbleHomeView(username=username)
# Подключаем сигнал к слоту в YobbleHomeView # Подключаем сигналы к слотам в YobbleHomeView
self.notification_requested.connect(self.yobble_home_view.show_notification) self.notification_requested.connect(self.yobble_home_view.show_notification)
self.chats_loaded.connect(self.yobble_home_view.update_chat_list)
self.addWidget(self.yobble_home_view) self.addWidget(self.yobble_home_view)
self.setCurrentWidget(self.yobble_home_view) self.setCurrentWidget(self.yobble_home_view)
@ -72,8 +77,32 @@ class MainController(QStackedWidget):
# asyncio.run() выполняет async функцию и возвращает её результат # asyncio.run() выполняет async функцию и возвращает её результат
asyncio.run(self.yobble_home_view.preload_permissions()) asyncio.run(self.yobble_home_view.preload_permissions())
# Загружаем список чатов
print("[Sync] Загружаем список чатов...")
asyncio.run(self.load_chats(username))
except Exception as e: except Exception as e:
error_message = f"Ошибка предзагрузки: {e}" error_message = f"Ошибка предзагрузки: {e}"
print(f"[Sync] {error_message}") print(f"[Sync] {error_message}")
# Отправляем сигнал вместо прямого вызова # Отправляем сигнал вместо прямого вызова
self.notification_requested.emit(error_message, True) self.notification_requested.emit(error_message, True)
async def load_chats(self, username: str):
"""
Загружает список чатов для пользователя.
"""
session = get_session(username)
# print("debug session", session)
# if not session or 'access_token' not in session:
# self.notification_requested.emit("Сессия не найдена, не могу загрузить чаты.", True)
# return
token = session['access_token']
success, data = await get_private_chats(token=token, offset=0, limit=50)
if success:
# Отправляем данные в основной поток через сигнал
self.chats_loaded.emit(data)
else:
# Отправляем ошибку в основной поток через сигнал
self.notification_requested.emit(f"Не удалось загрузить чаты: {data}", True)

View File

@ -1,20 +1,47 @@
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any, List, Literal
from uuid import UUID from uuid import UUID
from datetime import datetime from datetime import datetime
from pydantic import BaseModel, Field
from typing import Optional, Literal, List, Any, Dict
class MessageForward(BaseModel):
forward_type: Optional[Literal["chat_private_messages", "chat_group_messages",
"chat_public_messages", "reply"]] = Field(None, description="Тип пересылаемого контента")
forward_sender_id: Optional[UUID] = Field(None, description="ID чата, откуда переслано сообщение")
forward_message_id: Optional[int] = Field(None, description="Данные внутренний ид сообщения в forward_chat")
forward_chat_data: Optional[Any] = Field(default=None, description="Данные о чате пересылаемом (беседы и паблики)")
class MessageItem(BaseModel):
message_id: int = Field(..., description="внутренний ID сообщения")
message_type: List[Literal["text", "media", "circle", "voice", "system", "forward",
"reply", "poll"]] = Field(..., alias="message_type", description="Типы сообщения")
forward_metadata: Optional[MessageForward]
chat_id: UUID = Field(..., description="Чат ID")
sender_id: UUID = Field(..., description="Кто отправил")
sender_data: Optional[Any] = Field(default=None, description="Данные о пользователе")
content: Optional[str] = Field(None, description="Текст сообщения")
media_link: Optional[Any] = Field(None, description="Ссылка на медиа (заглушка)")
is_viewed: bool = Field(..., description="Флаг просмотра")
created_at: datetime = Field(..., description="Дата и время создания сообщения")
updated_at: Optional[datetime] = Field(None, description="Дата и время обновления сообщения")
class LastMessage(BaseModel):
message_id: int
message_type: List[Literal["text", "media", "circle", "voice", "system", "forward", "reply", "poll"]]
context: str
created_at: datetime
class PrivateChatListItem(BaseModel): class PrivateChatListItem(BaseModel):
chat_name: Optional[str] chat_id: UUID = Field(..., description="ID чата")
chat_type: Literal["self", "private"] chat_type: Literal["self", "private"] = Field(..., description="Тип чата")
chat_id: UUID chat_data: Optional[Dict[str, Any]] = Field(default=None, description="Данные о чате")
chat_data: Optional[Dict[str, Any]] = None last_message: Optional[MessageItem] = Field(None, description="Последнее сообщение в чате")
companion_id: Optional[UUID] = None created_at: datetime = Field(..., description="Дата создания чата")
companion_data: Optional[Dict[str, Any]] = None # Типизируй как нужно
last_message: Optional[LastMessage] = None
created_at: datetime class PrivateChatListData(BaseModel):
items: List[PrivateChatListItem]
has_more: bool
class PrivateChatListResponse(BaseModel):
status: str
data: PrivateChatListData

View File

@ -0,0 +1,48 @@
import httpx
from app.core import config
from app.core.localizer import localizer
from app.core.models.chat_models import PrivateChatListResponse, PrivateChatListData
async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
"""
Получает список приватных чатов пользователя.
:param token: Токен доступа пользователя
:param offset: Смещение для пагинации
:param limit: Количество чатов для загрузки
:return: Кортеж (успех: bool, данные: PrivateChatListData | str)
"""
# TODO: Добавить логику обновления токена, как в auth_service.py
url = f"{config.BASE_URL}/v1/chat/private/list"
headers = {"Authorization": f"Bearer {token}"}
params = {"offset": offset, "limit": limit}
try:
async with httpx.AsyncClient(http2=True) as client:
response = await client.get(url, headers=headers, params=params)
print("response.status_code", response.status_code)
if response.status_code == 200:
data = response.json()
if data.get("status") == "fine":
# Используем Pydantic модель для парсинга ответа
response_model = PrivateChatListResponse(**data)
print("response_model.data", response_model.data)
return True, response_model.data
else:
return False, data.get("detail", localizer.translate("Неизвестная ошибка ответа"))
elif response.status_code in [401, 403]:
error_data = response.json()
return False, error_data.get("detail", localizer.translate("Ошибка аутентификации или авторизации"))
elif response.status_code == 422:
return False, localizer.translate("Некорректные параметры запроса")
else:
return False, f"{localizer.translate('Ошибка сервера')}: {response.status_code}"
except httpx.RequestError as e:
return False, f"{localizer.translate('Ошибка сети')}: {e}"
except Exception as e:
return False, f"{localizer.translate('Произошла ошибка')}: {e}"

View File

@ -1,35 +1,59 @@
from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QLabel, QListWidgetItem from PySide6.QtWidgets import QWidget, QListWidget, QVBoxLayout, QLabel, QListWidgetItem
from PySide6.QtCore import Qt
from typing import List
from app.core.models.chat_models import PrivateChatListItem from app.core.models.chat_models import PrivateChatListItem
class ChatListView(QWidget): class ChatListView(QWidget):
def __init__(self, username, chat_items: list[PrivateChatListItem]): def __init__(self):
super().__init__() super().__init__()
self.setWindowTitle(f"Чаты — {username}")
self.setMinimumSize(400, 500)
self.chat_items = chat_items
self.init_ui() self.init_ui()
def init_ui(self): def init_ui(self):
layout = QVBoxLayout() """Инициализирует пользовательский интерфейс."""
layout = QVBoxLayout(self)
self.label = QLabel("Список чатов:") layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.label)
self.chat_list = QListWidget() self.chat_list = QListWidget()
self.render_chat_items() self.chat_list.setStyleSheet("QListWidget { border: none; }")
layout.addWidget(self.chat_list) layout.addWidget(self.chat_list)
self.setLayout(layout) # Изначальное состояние
self.show_placeholder_message("Загрузка чатов...")
def render_chat_items(self): def show_placeholder_message(self, text):
"""Очищает список и показывает одно сообщение (например, "Загрузка..." или "Чатов нет")."""
self.chat_list.clear() self.chat_list.clear()
for chat in self.chat_items: item = QListWidgetItem(text)
companion_name = chat.companion_data.get("username", "Без имени") if chat.companion_data else "Неизвестный" item.setTextAlignment(Qt.AlignCenter)
last_msg = chat.last_message.context if chat.last_message else "Нет сообщений" self.chat_list.addItem(item)
item_text = f"{companion_name}{last_msg}"
self.chat_list.addItem(QListWidgetItem(item_text))
def update_chat_items(self, new_items: list[PrivateChatListItem]): def populate_chats(self, chat_items: List[PrivateChatListItem]):
self.chat_items = new_items """
self.render_chat_items() Заполняет список чатов данными, полученными от сервера.
"""
self.chat_list.clear()
if not chat_items:
self.show_placeholder_message("У вас пока нет чатов")
return
for chat in chat_items:
# Определяем имя собеседника
if chat.chat_type == "self":
companion_name = "Избранное"
elif chat.chat_data and 'login' in chat.chat_data:
companion_name = chat.chat_data['login']
else:
companion_name = "Неизвестный"
# Получаем текст последнего сообщения
if chat.last_message and chat.last_message.content:
last_msg = chat.last_message.content
else:
last_msg = "Нет сообщений"
# Создаем кастомный виджет для элемента списка (можно будет улучшить)
# Пока просто текстом
item_text = f"{companion_name}\n{last_msg}"
list_item = QListWidgetItem(item_text)
self.chat_list.addItem(list_item)

View File

@ -11,6 +11,7 @@ from PySide6.QtGui import QColor
from app.core.theme import theme_manager from app.core.theme import theme_manager
from app.core.dialogs import show_themed_messagebox from app.core.dialogs import show_themed_messagebox
from app.ui.views.side_menu_view import SideMenuView from app.ui.views.side_menu_view import SideMenuView
from app.ui.views.chat_list_view import ChatListView
from app.core.services.auth_service import get_user_role from app.core.services.auth_service import get_user_role
from app.core.database import get_current_access_token from app.core.database import get_current_access_token
from app.core.localizer import localizer from app.core.localizer import localizer
@ -366,11 +367,21 @@ class YobbleHomeView(QWidget):
self.content_stack.addWidget(self.music_label) self.content_stack.addWidget(self.music_label)
# Чаты # Чаты
self.content_stack.addWidget(QLabel("Контент Чатов")) self.chat_list_view = ChatListView()
self.content_stack.addWidget(self.chat_list_view)
# Профиль # Профиль
self.content_stack.addWidget(QLabel("Контент Профиля")) self.content_stack.addWidget(QLabel("Контент Профиля"))
def update_chat_list(self, chat_data):
"""
Слот для обновления списка чатов.
Получает данные из сигнала контроллера.
"""
print(f"[UI] Получены данные для обновления чатов: {len(chat_data.items)} элементов.")
# Передаем данные в виджет списка чатов для отображения
self.chat_list_view.populate_chats(chat_data.items)
def on_tab_button_clicked(self, index): def on_tab_button_clicked(self, index):
"""Обрабатывает нажатие на кнопку вкладки, проверяя права доступа.""" """Обрабатывает нажатие на кнопку вкладки, проверяя права доступа."""
if index in self.REQUIRED_PERMISSIONS: if index in self.REQUIRED_PERMISSIONS: