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 asyncio
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):
# Сигнал для показа уведомлений из любого потока
# (message, is_error)
notification_requested = Signal(str, bool)
# Сигнал для передачи загруженных данных чата в основной поток
chats_loaded = Signal(object)
def __init__(self):
super().__init__()
@ -49,8 +53,9 @@ class MainController(QStackedWidget):
self.yobble_home_view = YobbleHomeView(username=username)
# Подключаем сигнал к слоту в YobbleHomeView
# Подключаем сигналы к слотам в YobbleHomeView
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.setCurrentWidget(self.yobble_home_view)
@ -72,8 +77,32 @@ class MainController(QStackedWidget):
# asyncio.run() выполняет async функцию и возвращает её результат
asyncio.run(self.yobble_home_view.preload_permissions())
# Загружаем список чатов
print("[Sync] Загружаем список чатов...")
asyncio.run(self.load_chats(username))
except Exception as e:
error_message = f"Ошибка предзагрузки: {e}"
print(f"[Sync] {error_message}")
# Отправляем сигнал вместо прямого вызова
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 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):
chat_name: Optional[str]
chat_type: Literal["self", "private"]
chat_id: UUID
chat_data: Optional[Dict[str, Any]] = None
companion_id: Optional[UUID] = None
companion_data: Optional[Dict[str, Any]] = None # Типизируй как нужно
last_message: Optional[LastMessage] = None
created_at: datetime
chat_id: UUID = Field(..., description="ID чата")
chat_type: Literal["self", "private"] = Field(..., description="Тип чата")
chat_data: Optional[Dict[str, Any]] = Field(default=None, description="Данные о чате")
last_message: Optional[MessageItem] = Field(None, description="Последнее сообщение в чате")
created_at: datetime = Field(..., description="Дата создания чата")
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.QtCore import Qt
from typing import List
from app.core.models.chat_models import PrivateChatListItem
class ChatListView(QWidget):
def __init__(self, username, chat_items: list[PrivateChatListItem]):
def __init__(self):
super().__init__()
self.setWindowTitle(f"Чаты — {username}")
self.setMinimumSize(400, 500)
self.chat_items = chat_items
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
self.label = QLabel("Список чатов:")
layout.addWidget(self.label)
"""Инициализирует пользовательский интерфейс."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.chat_list = QListWidget()
self.render_chat_items()
self.chat_list.setStyleSheet("QListWidget { border: none; }")
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()
for chat in self.chat_items:
companion_name = chat.companion_data.get("username", "Без имени") if chat.companion_data else "Неизвестный"
last_msg = chat.last_message.context if chat.last_message else "Нет сообщений"
item_text = f"{companion_name}{last_msg}"
self.chat_list.addItem(QListWidgetItem(item_text))
item = QListWidgetItem(text)
item.setTextAlignment(Qt.AlignCenter)
self.chat_list.addItem(item)
def update_chat_items(self, new_items: list[PrivateChatListItem]):
self.chat_items = new_items
self.render_chat_items()
def populate_chats(self, chat_items: List[PrivateChatListItem]):
"""
Заполняет список чатов данными, полученными от сервера.
"""
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.dialogs import show_themed_messagebox
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.database import get_current_access_token
from app.core.localizer import localizer
@ -366,11 +367,21 @@ class YobbleHomeView(QWidget):
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("Контент Профиля"))
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):
"""Обрабатывает нажатие на кнопку вкладки, проверяя права доступа."""
if index in self.REQUIRED_PERMISSIONS: