From a042a4ce59a5a64c8b273f7271db1e69e855b459 Mon Sep 17 00:00:00 2001 From: Siddhant Sharma Date: Mon, 30 Mar 2026 10:09:51 +0530 Subject: [PATCH] enh: add lazy loading of texts and make them scrollable. --- CHANGELOG.md | 2 +- src/memorytext/models/message_list.py | 52 +++++++++++++++++++--- src/memorytext/services/message_service.py | 26 +++++++++-- src/memorytext/views/chat_view.py | 21 +++++++-- src/memorytext/windows/main_window.py | 6 +-- 5 files changed, 90 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c455976..5b812b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ### Added - +1. Add lazy loading of texts and make them scrollable. ### Changed diff --git a/src/memorytext/models/message_list.py b/src/memorytext/models/message_list.py index d4e7c28..88c0225 100644 --- a/src/memorytext/models/message_list.py +++ b/src/memorytext/models/message_list.py @@ -1,5 +1,11 @@ from __future__ import annotations -from PySide6.QtCore import QAbstractListModel, Qt, QModelIndex +from typing import TYPE_CHECKING + +from PySide6.QtCore import QAbstractListModel, QPersistentModelIndex, Qt, QModelIndex + +if TYPE_CHECKING: + from collections.abc import Callable + from memorytext.models.core import Message class MessageList(QAbstractListModel): @@ -7,9 +13,17 @@ class MessageList(QAbstractListModel): SenderRole = Qt.ItemDataRole.UserRole + 2 TimestampRole = Qt.ItemDataRole.UserRole + 3 - def __init__(self, message_list, parent=None): + def __init__( + self, + conversation_id: int | None, + message_service: Callable[[int | None, int, int], tuple[list[Message], int]], + limit: int = 10, + parent=None, + ): super().__init__(parent) - self._items = message_list + self._conversation_id = conversation_id + self._message_service = message_service + self._items, self._total = self._message_service(conversation_id, limit, 0) def rowCount(self, parent=QModelIndex()): # pyright: ignore[reportIncompatibleMethodOverride] return len(self._items) @@ -47,7 +61,35 @@ def data(self, index, role=Qt.ItemDataRole.DisplayRole): # pyright: ignore[repo def flags(self, index): return Qt.ItemFlag.ItemIsEnabled - def set_messages(self, message_list): + def set_messages( + self, + conversation_id: int | None, + message_service: Callable[[int | None, int, int], tuple[list, int]], + limit: int = 10, + offset: int = 0, + ): self.beginResetModel() - self._items = message_list + self._conversation_id = conversation_id + self._message_service = message_service + self._items, self._total = self._message_service(conversation_id, limit, offset) self.endResetModel() + + def canFetchMore(self, parent: QModelIndex | QPersistentModelIndex) -> bool: + return self.rowCount() < self._total + + def fetchMore( + self, + parent: QModelIndex | QPersistentModelIndex = QModelIndex(), + ) -> None: + offset = self.rowCount() + remaining = self._total - offset + limit = 10 + to_fetch = min(limit, remaining) + if to_fetch > 0: + new_messages, count = self._message_service( + self._conversation_id, to_fetch, offset + ) + if count > 0: + self.beginInsertRows(QModelIndex(), offset, offset + count - 1) + self._items.extend(new_messages) + self.endInsertRows() diff --git a/src/memorytext/services/message_service.py b/src/memorytext/services/message_service.py index b9b7db1..c7ed925 100644 --- a/src/memorytext/services/message_service.py +++ b/src/memorytext/services/message_service.py @@ -6,9 +6,27 @@ from memorytext.db import get_db -def get_messages(conversation_id, count: int = 10): +def get_messages( + conversation_id: int | None, limit: int = 10, offset: int = 0 +) -> tuple[list, int]: engine = get_db() + count = limit + offset with Session(engine) as session: - stmt = select(Message).where(Message.conversation_id == conversation_id) - message_list = session.scalars(stmt).fetchmany(count) - return message_list + stmt = ( + select(Message) + .where(Message.conversation_id == conversation_id) + .order_by(Message.timestamp.asc()) + ) + message_list = list(session.scalars(stmt).fetchall()) + if message_list is not None: + total = len(message_list) + else: + total = 0 + if message_list is not None and offset >= total: + return [], total + elif message_list is not None and count >= total: + return message_list[offset:], total + elif message_list is not None: + return message_list[offset:count], total + else: + return [], total diff --git a/src/memorytext/views/chat_view.py b/src/memorytext/views/chat_view.py index 8b8864c..308032b 100644 --- a/src/memorytext/views/chat_view.py +++ b/src/memorytext/views/chat_view.py @@ -1,11 +1,18 @@ +from __future__ import annotations +from typing import TYPE_CHECKING from PySide6.QtWidgets import QListView from PySide6.QtCore import Qt from PySide6.QtGui import QFont from memorytext.models.message_list import MessageList +from memorytext.services import message_service from memorytext.delegates.message_list import MessageDelegate +if TYPE_CHECKING: + from collections.abc import Callable + + class ChatView(QListView): def __init__(self): super().__init__() @@ -14,15 +21,23 @@ def __init__(self): font.setPointSize(14) self.setFont(font) self.setSpacing(10) - self.message_model = MessageList([]) + self.message_model = MessageList(None, message_service.get_messages) self.setModel(self.message_model) self.setItemDelegate(MessageDelegate()) self.setWordWrap(True) self.setUniformItemSizes(False) self.setFocusPolicy(Qt.NoFocus) # pyright: ignore[reportAttributeAccessIssue] - def set_messages(self, message_list): - self.message_model.set_messages(message_list) + def set_messages( + self, + conversation_id: int | None, + message_service: Callable[[int | None, int, int], tuple[list, int]], + limit: int = 10, + offset: int = 0, + ): + self.message_model.set_messages( + conversation_id, message_service, limit=limit, offset=offset + ) def resizeEvent(self, event): super().resizeEvent(event) diff --git a/src/memorytext/windows/main_window.py b/src/memorytext/windows/main_window.py index ec472d1..dfcac2f 100644 --- a/src/memorytext/windows/main_window.py +++ b/src/memorytext/windows/main_window.py @@ -8,8 +8,8 @@ from PySide6.QtCore import QModelIndex from memorytext.views.chat_view import ChatView from memorytext.views.chat_list_view import ChatListView -import memorytext.services.message_service as msg_service from memorytext.windows.import_window import ImportWindow +from memorytext.services import message_service class MainWindow(QMainWindow): @@ -41,9 +41,7 @@ def __init__(self): def load_messages(self, index: QModelIndex): convo_id = self.sidebar.model().get_id(index) # pyright: ignore[reportAttributeAccessIssue] - - message_list = msg_service.get_messages(convo_id, count=20) - self.chat_area.set_messages(message_list) + self.chat_area.set_messages(convo_id, message_service.get_messages) def import_new_chat(self): if self.import_window is None: