patch chat

This commit is contained in:
unknown 2025-09-29 02:53:52 +03:00
parent 96fe4d0b91
commit 229cab0dde
5 changed files with 139 additions and 5 deletions

View File

@ -2,7 +2,7 @@ DEBUG = True
API_SCHEME = "https" API_SCHEME = "https"
API_HOST = "api.yobble.org" API_HOST = "api.yobble.org"
BASE_URL = f"{API_SCHEME}://{API_HOST}" BASE_URL = f"{API_SCHEME}://{API_HOST}"
APP_VERSION = "0.2_home_screen" APP_VERSION = "0.3_chat"
APP_NAME = "yobble messenger" APP_NAME = "yobble messenger"
APP_HEADER = f"{APP_NAME}" APP_HEADER = f"{APP_NAME}"
if DEBUG: APP_HEADER=f"{APP_HEADER} ({APP_VERSION})" if DEBUG: APP_HEADER=f"{APP_HEADER} ({APP_VERSION})"

View File

@ -56,3 +56,23 @@ class PrivateChatHistoryData(BaseModel):
class PrivateChatHistoryResponse(BaseModel): class PrivateChatHistoryResponse(BaseModel):
status: str status: str
data: PrivateChatHistoryData data: PrivateChatHistoryData
# send
class PrivateMessageSendRequest(BaseModel):
chat_id: UUID
content: Optional[str] = Field(None, description="Содержимое сообщения (макс. 4096 символов)", max_length=4096)
message_type: List[Literal["text"]] = Field(
..., description="Один или несколько типов сообщения"
)
class PrivateMessageSendData(BaseModel):
message_id: int
chat_id: UUID
created_at: datetime
class PrivateMessageSendResponse(BaseModel):
status: str
data: PrivateMessageSendData

View File

@ -1,7 +1,11 @@
import httpx import httpx
from app.core import config from app.core import config
from app.core.localizer import localizer from app.core.localizer import localizer
from app.core.models.chat_models import PrivateChatListResponse, PrivateChatListData, PrivateChatHistoryResponse, PrivateChatHistoryData from app.core.models.chat_models import (
PrivateChatListResponse, PrivateChatListData,
PrivateChatHistoryResponse, PrivateChatHistoryData,
PrivateMessageSendRequest, PrivateMessageSendResponse
)
from uuid import UUID from uuid import UUID
async def get_private_chats(token: str, offset: int = 0, limit: int = 20): async def get_private_chats(token: str, offset: int = 0, limit: int = 20):
@ -90,3 +94,48 @@ async def get_chat_history(token: str, chat_id: UUID, before_message_id: int = N
return False, f"Ошибка сети: {e}" return False, f"Ошибка сети: {e}"
except Exception as e: except Exception as e:
return False, f"Произошла ошибка: {e}" return False, f"Произошла ошибка: {e}"
async def send_private_message(token: str, payload: "PrivateMessageSendRequest"):
"""
Отправляет приватное сообщение в чат.
:param token: Токен доступа
:param payload: Данные сообщения (Pydantic модель PrivateMessageSendRequest)
:return: Кортеж (успех: bool, данные: PrivateMessageSendData | str)
"""
url = f"{config.BASE_URL}/v1/chat/private/send"
headers = {"Authorization": f"Bearer {token}"}
try:
async with httpx.AsyncClient(http2=True) as client:
response = await client.post(url, headers=headers, json=payload.model_dump(mode='json'))
if response.status_code == 200:
data = response.json()
if data.get("status") == "fine":
response_model = PrivateMessageSendResponse(**data)
return True, response_model.data
else:
return False, data.get("detail", "Неизвестная ошибка ответа")
elif response.status_code in [401, 403, 404]:
error_data = response.json()
print("error_data", error_data)
return False, error_data.get("detail", "Ошибка доступа или чат не найден")
elif response.status_code == 422:
error_data = response.json()
# Может быть список ошибок
detail = error_data.get("detail")
if isinstance(detail, list):
return False, ", ".join([e.get("msg", "Неизвестная ошибка валидации") for e in detail])
return False, detail or "Некорректные данные"
else:
return False, f"Ошибка сервера: {response.status_code}"
except httpx.RequestError as e:
return False, f"Ошибка сети: {e}"
except Exception as e:
return False, f"Произошла ошибка: {e}"

View File

@ -1,11 +1,14 @@
from PySide6.QtWidgets import QWidget, QVBoxLayout, QListWidget, QLineEdit, QPushButton, QHBoxLayout, QListWidgetItem from PySide6.QtWidgets import QWidget, QVBoxLayout, QListWidget, QLineEdit, QPushButton, QHBoxLayout, QListWidgetItem
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, Signal
from app.core.models.chat_models import MessageItem from app.core.models.chat_models import MessageItem
from app.ui.widgets.message_bubble_widget import MessageBubbleWidget from app.ui.widgets.message_bubble_widget import MessageBubbleWidget
from app.core.theme import theme_manager from app.core.theme import theme_manager
from uuid import UUID from uuid import UUID
class ChatView(QWidget): class ChatView(QWidget):
# Сигнал, который отправляет текст сообщения для отправки
send_message_requested = Signal(str)
def __init__(self, chat_id: UUID, current_user_id: UUID): def __init__(self, chat_id: UUID, current_user_id: UUID):
super().__init__() super().__init__()
self.chat_id = chat_id self.chat_id = chat_id
@ -39,6 +42,20 @@ class ChatView(QWidget):
main_layout.addWidget(self.message_list) main_layout.addWidget(self.message_list)
main_layout.addLayout(input_layout) main_layout.addLayout(input_layout)
# --- Подключение сигналов ---
self.send_button.clicked.connect(self._on_send)
self.message_input.returnPressed.connect(self._on_send)
def _on_send(self):
"""Обработчик нажатия кнопки отправки."""
message_text = self.message_input.text().strip()
if message_text:
self.send_message_requested.emit(message_text)
def clear_input(self):
"""Очищает поле ввода."""
self.message_input.clear()
def update_theme(self): def update_theme(self):
"""Обновляет стили в соответствии с темой.""" """Обновляет стили в соответствии с темой."""
palette = theme_manager.get_current_palette() palette = theme_manager.get_current_palette()

View File

@ -16,7 +16,8 @@ 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
from app.ui.views.chat_view import ChatView from app.ui.views.chat_view import ChatView
from app.core.services.chat_service import get_chat_history from app.core.services.chat_service import get_chat_history, send_private_message
from app.core.models.chat_models import PrivateMessageSendRequest, MessageItem
from uuid import UUID from uuid import UUID
class YobbleHomeView(QWidget): class YobbleHomeView(QWidget):
@ -497,6 +498,8 @@ class YobbleHomeView(QWidget):
self.chat_view = ChatView(chat_id, self.current_user_id) self.chat_view = ChatView(chat_id, self.current_user_id)
self.chat_view.populate_history(data.items) self.chat_view.populate_history(data.items)
# Подключаем сигнал отправки сообщения к нашему новому методу
self.chat_view.send_message_requested.connect(self.send_message)
# Заменяем плейсхолдер на реальный виджет # Заменяем плейсхолдер на реальный виджет
self.content_stack.removeWidget(self.chat_view_container) self.content_stack.removeWidget(self.chat_view_container)
@ -509,6 +512,51 @@ class YobbleHomeView(QWidget):
else: else:
self.show_notification(f"Не удалось загрузить историю: {data}", is_error=True) self.show_notification(f"Не удалось загрузить историю: {data}", is_error=True)
def send_message(self, content: str):
"""Слот для отправки сообщения. Запускает асинхронную задачу."""
if not self.chat_view:
return
chat_id = self.chat_view.chat_id
payload = PrivateMessageSendRequest(
chat_id=chat_id,
content=content,
message_type=["text"]
)
asyncio.create_task(self.do_send_message(payload))
async def do_send_message(self, payload: PrivateMessageSendRequest):
"""Асинхронно отправляет сообщение и обновляет UI."""
token = await get_current_access_token()
if not token:
self.show_notification("Ошибка: сессия не найдена.", is_error=True)
return
success, data = await send_private_message(token, payload)
if success:
# В случае успеха, создаем объект сообщения и добавляем в чат
# Используем текущее время, т.к. сервер возвращает время с UTC
from datetime import datetime
new_message = MessageItem(
message_id=data.message_id,
chat_id=data.chat_id,
sender_id=self.current_user_id,
content=payload.content,
message_type=['text'],
is_viewed=True, # Свои сообщения считаем просмотренными
created_at=datetime.now(),
forward_metadata=None,
sender_data=None,
media_link=None,
updated_at=None
)
self.chat_view.add_message(new_message)
self.chat_view.clear_input() # Очищаем поле ввода
else:
self.show_notification(f"Ошибка отправки: {data}", is_error=True)
def close_chat_view(self): def close_chat_view(self):
"""Закрывает виджет чата и возвращается к списку.""" """Закрывает виджет чата и возвращается к списку."""
self.content_stack.setCurrentWidget(self.chat_list_view) self.content_stack.setCurrentWidget(self.chat_list_view)