Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
## [v0.7.0]

### Added

1. Add a simple toolbar at the top.
2. Add a context menu to the chat list sidebar with options:
- Edit Chat Title
- Delete

### Changed

1. Move the import button to the toolbar.
2. Move helper windows to separate module.


## [v0.6.0]

## Added
### Added

1. Add a common engine for the app.
2. Add storage modules to keep the services layer lighter.

## Changed
### Changed

1. Use a session factory for getting db sessions.
2. Reduce the maximum width of message bubbles.
Expand Down
10 changes: 10 additions & 0 deletions src/memorytext/services/chat_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,13 @@ def delete_chat_by_title(title: str):
except SQLAlchemyError:
session.rollback()
raise


def edit_chat_title(conversation_id: int, new_title: str):
with get_db_session() as session:
try:
chrepo.edit_chat_title(session, conversation_id, new_title)
session.commit()
except SQLAlchemyError:
session.rollback()
raise
10 changes: 9 additions & 1 deletion src/memorytext/storage/chats_repo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from sqlalchemy import delete, select
from sqlalchemy import delete, select, update
from sqlalchemy.orm import selectinload

from memorytext.models.core import Conversation
Expand Down Expand Up @@ -45,3 +45,11 @@ def delete_chat_by_title(session: Session, title: str):
select(Conversation.id).where(Conversation.title == title)
)
delete_chat(session, conversation_id)


def edit_chat_title(session: Session, conversation_id: int | None, new_title: str):
session.execute(
update(Conversation)
.where(Conversation.id == conversation_id)
.values(title=new_title)
)
69 changes: 65 additions & 4 deletions src/memorytext/views/chat_list_view.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,77 @@
from PySide6.QtWidgets import QListView
from PySide6.QtWidgets import (
QListView,
QMenu,
)
from PySide6.QtCore import Qt, QPoint, Signal
from PySide6.QtGui import QAction, QIcon

from memorytext.models.chat_list import ChatList
from memorytext.services.chat_service import get_chats
from memorytext.services import chat_service
from memorytext.windows.helper_windows import ConfirmationWindow, EditFieldWindow

DELETE_ICON = QIcon.fromTheme("edit-delete")
EDIT_TITLE_ICON = QIcon.fromTheme("document-edit")


class ChatListView(QListView):
chat_deleted = Signal(int)

def __init__(self):
super().__init__()

chat_list = get_chats()
chat_list = chat_service.get_chats()
self.chats = ChatList(chat_list)
self.setModel(self.chats)
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_context_menu)

def update_data(self):
chat_list = get_chats()
chat_list = chat_service.get_chats()
self.chats.set_data(chat_list)

def show_context_menu(self, position: QPoint):
menu = QMenu(self)
index = self.indexAt(position)
if index.isValid():
delete_action = QAction(DELETE_ICON, "Delete Chat", self)
edit_title_action = QAction(EDIT_TITLE_ICON, "Edit Chat Title", self)
menu.addAction(edit_title_action)
menu.addAction(delete_action)

action = menu.exec(self.mapToGlobal(position))

if action == delete_action:
self.delete_conversation_id = index.data(role=Qt.ItemDataRole.UserRole)
text = "Are you sure you want to delete the chat?"
self.confirm = ConfirmationWindow(text, self.delete_chat_action)
self.confirm.show()

if action == edit_title_action:
self.edit_conversation_id = index.data(role=Qt.ItemDataRole.UserRole)
text = "Please enter a new title."
self.edit_window = EditFieldWindow(
"Edit Chat Title", "New Title", text, self.edit_chat_title_action
)
self.edit_window.show()

def edit_chat_title_action(self):
new_title = self.edit_window.field_box.text()
if new_title and self.edit_conversation_id:
try:
chat_service.edit_chat_title(self.edit_conversation_id, new_title)
except Exception as e:
print(e)
raise
self.edit_window.close()
self.update_data()

def delete_chat_action(self):
if self.delete_conversation_id:
try:
chat_service.delete_chat(self.delete_conversation_id)
self.chat_deleted.emit(self.delete_conversation_id)
except Exception as e:
print(e)
raise
self.confirm.close()
self.update_data()
164 changes: 164 additions & 0 deletions src/memorytext/windows/helper_windows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from __future__ import annotations
from typing import TYPE_CHECKING

from PySide6.QtWidgets import (
QComboBox,
QFormLayout,
QMainWindow,
QHBoxLayout,
QVBoxLayout,
QLineEdit,
QPushButton,
QWidget,
QLabel,
)
from PySide6.QtCore import Slot
from memorytext.services import chat_service

if TYPE_CHECKING:
from collections.abc import Callable


class EditFieldWindow(QMainWindow):
def __init__(
self, window_title: str, field_name: str, text: str, ok_action: Callable
):
super().__init__()
self.setWindowTitle(window_title)
self.setFixedSize(500, 200)

button_layout = QHBoxLayout()
form_layout = QFormLayout()
layout = QVBoxLayout()

self.label = QLabel(text)
layout.addWidget(self.label)

self.field_box = QLineEdit()
self.field_box.setMaximumWidth(150)

self.ok_button = QPushButton("Ok")
button_layout.addWidget(self.ok_button)

self.cancel_button = QPushButton("Cancel")
button_layout.addWidget(self.cancel_button)

form_layout.addRow(f"{field_name}:", self.field_box)

layout.addLayout(form_layout)
layout.addLayout(button_layout)

self.cancel_button.clicked.connect(self.close)
self.ok_button.clicked.connect(ok_action)

container = QWidget()
container.setLayout(layout)

self.setCentralWidget(container)


class ErrorWindow(QMainWindow):
def __init__(self, error: str):
super().__init__()
self.setWindowTitle("Error")
self.setFixedSize(500, 200)

self.error_text = QLabel(text=error)
self.ok_button = QPushButton("Ok")

self.ok_button.clicked.connect(self.close)

layout = QVBoxLayout()
layout.addWidget(self.error_text)
layout.addWidget(self.ok_button)

container = QWidget()
container.setLayout(layout)

self.setCentralWidget(container)


class SelectUserWindow(QMainWindow):
def __init__(self, service: Callable[[str], set[str]], title: str):
super().__init__()
self.service = service
self.chat_title = title
self.initUI()

def initUI(self):
self.participants = self.service(self.chat_title)
self.setWindowTitle("Select your username")
self.setGeometry(100, 100, 300, 200)

button_layout = QHBoxLayout()
layout = QVBoxLayout()

self.comboBox = QComboBox()
self.comboBox.addItems(self.participants) # pyright: ignore[reportArgumentType]
layout.addWidget(self.comboBox)

self.default_select = str(next((name for name in self.participants), None))
self.comboBox.setCurrentText(self.default_select)

self.label = QLabel(f"Selected username: {self.default_select}")
layout.addWidget(self.label)

self.ok_button = QPushButton("Ok")
button_layout.addWidget(self.ok_button)

self.cancel_button = QPushButton("Cancel")
button_layout.addWidget(self.cancel_button)

layout.addLayout(button_layout)

self.cancel_button.clicked.connect(self.close)
self.ok_button.clicked.connect(self.set_username)
self.comboBox.currentTextChanged.connect(self.on_selection_changed)

container = QWidget()
container.setLayout(layout)

self.setCentralWidget(container)

@Slot(str)
def on_selection_changed(self, text):
self.label.setText(f"Selected username: {text}")

@Slot()
def set_username(self):
try:
username = self.comboBox.currentText()
chat_service.set_username(self.chat_title, username)
except Exception as e:
self.error_window = ErrorWindow(error=f"Error: {e}")
self.error_window.show()
self.close()


class ConfirmationWindow(QMainWindow):
def __init__(self, text: str, ok_action: Callable):
super().__init__()
self.setWindowTitle("Confirm Delete")
self.setFixedSize(500, 200)

button_layout = QHBoxLayout()
layout = QVBoxLayout()

self.label = QLabel(text)
layout.addWidget(self.label)

self.ok_button = QPushButton("Ok")
button_layout.addWidget(self.ok_button)

self.cancel_button = QPushButton("Cancel")
button_layout.addWidget(self.cancel_button)

layout.addLayout(button_layout)

self.cancel_button.clicked.connect(self.close)
self.ok_button.clicked.connect(ok_action)

container = QWidget()
container.setLayout(layout)

self.setCentralWidget(container)
Loading
Loading