Skip to content

Commit a5c79b6

Browse files
committed
better handling of chat messages
adds copy / delete controls to each message
1 parent 98ca98a commit a5c79b6

File tree

8 files changed

+285
-123
lines changed

8 files changed

+285
-123
lines changed

src/airunner/styles/dark_theme/styles.qss

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -560,13 +560,6 @@ QTableWidget::item:selected {
560560
border: 1px solid rgba(0, 132, 185, 50);
561561
}
562562

563-
QWidget#message QPlainTextEdit {
564-
border-radius: 5px;
565-
border: 5px solid #1f1f1f;
566-
background-color: #1f1f1f;
567-
color: #ffffff;
568-
}
569-
570563
QSplitter::handle::vertical {
571564
border-top: 4px solid rgba(0, 132, 185, 50);
572565
}
@@ -734,3 +727,28 @@ QMenu {
734727
SwitchWidget:enabled[checked="true"] {
735728
background-color: rgba(0, 132, 185, 100);
736729
}
730+
731+
QScrollArea#chat_container #message_container {
732+
background-color: #1f1f1f;
733+
}
734+
735+
QScrollArea#chat_container #message_container[class="alternate"] {
736+
background-color: #000;
737+
}
738+
739+
QScrollArea#chat_container #message_container QTextEdit {
740+
border: none;
741+
background-color: transparent;
742+
color: #ffffff;
743+
}
744+
745+
QScrollArea#chat_container #message_container QPushButton {
746+
border: none;
747+
background-color: transparent;
748+
color: #ffffff;
749+
}
750+
751+
QScrollArea#chat_container #message_container QPushButton:hover {
752+
background-color: #000;
753+
border: 1px solid #1f1f1f;
754+
}

src/airunner/widgets/llm/chat_prompt_widget.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ def _set_conversation_widgets(self, messages):
9696
name=message["name"],
9797
message=message["content"],
9898
is_bot=message["is_bot"],
99+
message_id=message["id"],
99100
first_message=True
100101
)
102+
QTimer.singleShot(100, self.scroll_to_bottom)
101103

102104
def on_hear_signal(self, data: dict):
103105
transcription = data["transcription"]
@@ -189,11 +191,14 @@ def do_generate(self, image_override=None, prompt_override=None, callback=None,
189191
return
190192
self.generating = True
191193

192-
self.add_message_to_conversation(
194+
widget = self.add_message_to_conversation(
193195
name=self.chatbot.username,
194196
message=self.prompt,
195197
is_bot=False
196198
)
199+
if widget is not None:
200+
QTimer.singleShot(100, widget.set_content_size)
201+
QTimer.singleShot(100, self.scroll_to_bottom)
197202

198203
self.clear_prompt()
199204
self.start_progress_bar()
@@ -312,7 +317,8 @@ def add_message_to_conversation(
312317
name,
313318
message,
314319
is_bot,
315-
first_message=True
320+
first_message=True,
321+
message_id=None
316322
):
317323
if not first_message:
318324
# get the last widget from the scrollAreaWidgetContents.layout()
@@ -330,15 +336,18 @@ def add_message_to_conversation(
330336

331337
self.remove_spacer()
332338

339+
widget = None
333340
if message != "":
334-
widget = MessageWidget(name=name, message=message, is_bot=is_bot)
341+
widget = MessageWidget(name=name, message=message, is_bot=is_bot, message_id=message_id, conversation_id=self.conversation_id)
335342
self.ui.scrollAreaWidgetContents.layout().addWidget(widget)
336343

337344
self.add_spacer()
338345

339346
# automatically scroll to the bottom of the scrollAreaWidgetContents
340347
self.scroll_to_bottom()
341348

349+
return widget
350+
342351
def remove_spacer(self):
343352
if self.spacer is not None:
344353
self.ui.scrollAreaWidgetContents.layout().removeItem(self.spacer)

src/airunner/widgets/llm/message_widget.py

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
from airunner.data.models.settings_models import Message, Conversation
12
from airunner.enums import SignalCode
23
from airunner.widgets.base_widget import BaseWidget
34
from airunner.widgets.llm.templates.message_ui import Ui_message
45

56
from PySide6.QtGui import QTextCursor, QFontDatabase, QFont
6-
from PySide6.QtWidgets import QTextEdit
7+
from PySide6.QtWidgets import QTextEdit, QApplication, QWidget
78
from PySide6.QtGui import QFontMetrics
8-
from PySide6.QtCore import Qt, QSize
9+
from PySide6.QtCore import Qt, QSize, Slot, QEvent, QTimer
910
from PySide6.QtCore import Signal
1011

1112

@@ -22,30 +23,48 @@ class MessageWidget(BaseWidget):
2223
def __init__(self, *args, **kwargs):
2324
self.name = kwargs.pop("name")
2425
self.message = kwargs.pop("message")
26+
self.conversation_id = kwargs.pop("conversation_id")
27+
self.message_id = kwargs.pop("message_id")
28+
if self.message_id is None:
29+
session = self.session
30+
message = session.query(Message).filter(Message.conversation_id == self.conversation_id).order_by(
31+
Message.id.desc()).first()
32+
if message is not None:
33+
self.message_id = message.id
2534
self.is_bot = kwargs.pop("is_bot")
2635
super().__init__(*args, **kwargs)
2736
self.ui.content.setReadOnly(True)
2837
self.ui.content.insertPlainText(self.message)
2938
self.ui.content.document().contentsChanged.connect(self.sizeChange)
30-
name = self.name
31-
if self.is_bot:
32-
self.ui.bot_name.show()
33-
self.ui.bot_name.setText(f"{name}")
34-
self.ui.bot_name.setStyleSheet("font-weight: normal;")
35-
self.ui.user_name.hide()
36-
else:
37-
self.ui.user_name.show()
38-
self.ui.user_name.setText(f"{name}")
39-
self.ui.user_name.setStyleSheet("font-weight: normal;")
40-
self.ui.bot_name.hide()
41-
42-
self.ui.content.setStyleSheet("border-radius: 5px; border: 5px solid #1f1f1f; background-color: #1f1f1f; color: #ffffff;")
43-
39+
self.ui.user_name.setText(f"{self.name}")
4440
self.register(SignalCode.APPLICATION_SETTINGS_CHANGED_SIGNAL, self.on_application_settings_changed_signal)
4541
self.font_family = None
4642
self.font_size = None
4743
self.set_chat_font()
4844

45+
if self.is_bot:
46+
self.ui.message_container.setProperty("class", "alternate")
47+
48+
self.ui.copy_button.setVisible(False)
49+
self.ui.delete_button.setVisible(False)
50+
self.ui.message_container.installEventFilter(self)
51+
self.set_cursor(Qt.CursorShape.ArrowCursor)
52+
53+
def set_cursor(self, cursor_type):
54+
self.ui.message_container.setCursor(cursor_type)
55+
for child in self.ui.message_container.findChildren(QWidget):
56+
child.setCursor(cursor_type)
57+
58+
def eventFilter(self, obj, event):
59+
if obj == self.ui.message_container:
60+
if event.type() == QEvent.Type.Enter:
61+
self.ui.copy_button.setVisible(True)
62+
self.ui.delete_button.setVisible(True)
63+
elif event.type() == QEvent.Type.Leave:
64+
self.ui.copy_button.setVisible(False)
65+
self.ui.delete_button.setVisible(False)
66+
return super().eventFilter(obj, event)
67+
4968
def on_application_settings_changed_signal(self):
5069
self.set_chat_font()
5170

@@ -71,7 +90,7 @@ def set_chat_font(self):
7190
def set_content_size(self):
7291
doc_height = self.ui.content.document().size().height()
7392
doc_width = self.ui.content.document().size().width()
74-
self.setMinimumHeight(int(doc_height) + 25)
93+
self.setMinimumHeight(int(doc_height) + 45)
7594
self.setMinimumWidth(int(doc_width))
7695

7796
def sizeChange(self):
@@ -98,3 +117,16 @@ def update_message(self, text):
98117

99118
self.ui.content.setPlainText(self.message)
100119

120+
@Slot()
121+
def delete(self):
122+
session = self.session
123+
message = session.query(Message).filter(Message.id == self.message_id).first()
124+
session.delete(message)
125+
session.commit()
126+
self.setParent(None)
127+
self.deleteLater()
128+
129+
@Slot()
130+
def copy(self):
131+
clipboard = QApplication.clipboard()
132+
clipboard.setText(self.message)

src/airunner/widgets/llm/templates/chat_prompt.ui

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,9 @@
225225
</property>
226226
<widget class="QWidget" name="verticalLayoutWidget">
227227
<layout class="QVBoxLayout" name="verticalLayout">
228+
<property name="spacing">
229+
<number>0</number>
230+
</property>
228231
<item>
229232
<widget class="QScrollArea" name="chat_container">
230233
<property name="frameShape">
@@ -255,6 +258,9 @@
255258
<property name="bottomMargin">
256259
<number>0</number>
257260
</property>
261+
<property name="spacing">
262+
<number>0</number>
263+
</property>
258264
<item row="0" column="0">
259265
<widget class="QTextEdit" name="conversation">
260266
<property name="font">

src/airunner/widgets/llm/templates/chat_prompt_ui.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def setupUi(self, chat_prompt):
115115
self.verticalLayoutWidget = QWidget(self.chat_prompt_splitter)
116116
self.verticalLayoutWidget.setObjectName(u"verticalLayoutWidget")
117117
self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)
118+
self.verticalLayout.setSpacing(0)
118119
self.verticalLayout.setObjectName(u"verticalLayout")
119120
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
120121
self.chat_container = QScrollArea(self.verticalLayoutWidget)
@@ -125,6 +126,7 @@ def setupUi(self, chat_prompt):
125126
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
126127
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 593, 321))
127128
self.gridLayout_2 = QGridLayout(self.scrollAreaWidgetContents)
129+
self.gridLayout_2.setSpacing(0)
128130
self.gridLayout_2.setObjectName(u"gridLayout_2")
129131
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
130132
self.conversation = QTextEdit(self.scrollAreaWidgetContents)

0 commit comments

Comments
 (0)