abstractassistant 0.1.1__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- abstractassistant/core/llm_manager.py +15 -11
- abstractassistant/ui/history_dialog.py +142 -42
- abstractassistant/ui/qt_bubble.py +231 -58
- abstractassistant/ui/toast_window.py +8 -8
- abstractassistant/ui/ui_styles.py +2 -2
- abstractassistant/utils/markdown_renderer.py +1 -1
- {abstractassistant-0.1.1.dist-info → abstractassistant-0.2.5.dist-info}/METADATA +4 -3
- {abstractassistant-0.1.1.dist-info → abstractassistant-0.2.5.dist-info}/RECORD +12 -12
- {abstractassistant-0.1.1.dist-info → abstractassistant-0.2.5.dist-info}/WHEEL +0 -0
- {abstractassistant-0.1.1.dist-info → abstractassistant-0.2.5.dist-info}/entry_points.txt +0 -0
- {abstractassistant-0.1.1.dist-info → abstractassistant-0.2.5.dist-info}/licenses/LICENSE +0 -0
- {abstractassistant-0.1.1.dist-info → abstractassistant-0.2.5.dist-info}/top_level.txt +0 -0
|
@@ -242,14 +242,15 @@ class LLMManager:
|
|
|
242
242
|
self.current_model = model
|
|
243
243
|
self._initialize_llm()
|
|
244
244
|
|
|
245
|
-
def generate_response(self, message: str, provider: str = None, model: str = None) -> str:
|
|
245
|
+
def generate_response(self, message: str, provider: str = None, model: str = None, media: Optional[List[str]] = None) -> str:
|
|
246
246
|
"""Generate a response using the session for context persistence.
|
|
247
|
-
|
|
247
|
+
|
|
248
248
|
Args:
|
|
249
249
|
message: User message
|
|
250
250
|
provider: Optional provider override
|
|
251
251
|
model: Optional model override
|
|
252
|
-
|
|
252
|
+
media: Optional list of file paths to attach (images, PDFs, Office docs, etc.)
|
|
253
|
+
|
|
253
254
|
Returns:
|
|
254
255
|
Generated response text
|
|
255
256
|
"""
|
|
@@ -258,24 +259,27 @@ class LLMManager:
|
|
|
258
259
|
self.set_provider(provider, model)
|
|
259
260
|
elif model and model != self.current_model:
|
|
260
261
|
self.set_model(model)
|
|
261
|
-
|
|
262
|
+
|
|
262
263
|
try:
|
|
263
264
|
# Ensure we have a session
|
|
264
265
|
if self.current_session is None:
|
|
265
266
|
self.create_new_session()
|
|
266
|
-
|
|
267
|
-
# Generate response using session
|
|
268
|
-
#
|
|
269
|
-
|
|
270
|
-
|
|
267
|
+
|
|
268
|
+
# Generate response using session with optional media files
|
|
269
|
+
# AbstractCore 2.4.5+ supports media=[] parameter for file attachments
|
|
270
|
+
if media and len(media) > 0:
|
|
271
|
+
response = self.current_session.generate(message, media=media)
|
|
272
|
+
else:
|
|
273
|
+
response = self.current_session.generate(message)
|
|
274
|
+
|
|
271
275
|
# Handle response format
|
|
272
276
|
if hasattr(response, 'content'):
|
|
273
277
|
response_text = response.content
|
|
274
278
|
else:
|
|
275
279
|
response_text = str(response)
|
|
276
|
-
|
|
280
|
+
|
|
277
281
|
return response_text
|
|
278
|
-
|
|
282
|
+
|
|
279
283
|
except Exception as e:
|
|
280
284
|
return f"Error generating response: {str(e)}"
|
|
281
285
|
|
|
@@ -6,23 +6,99 @@ This module provides an authentic iPhone Messages UI for displaying chat history
|
|
|
6
6
|
import re
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from typing import Dict, List
|
|
9
|
+
import markdown
|
|
10
|
+
from markdown.extensions.fenced_code import FencedCodeExtension
|
|
11
|
+
from markdown.extensions.tables import TableExtension
|
|
12
|
+
from markdown.extensions.nl2br import Nl2BrExtension
|
|
13
|
+
from pygments import highlight
|
|
14
|
+
from pygments.lexers import get_lexer_by_name, TextLexer
|
|
15
|
+
from pygments.formatters import HtmlFormatter
|
|
9
16
|
|
|
10
17
|
try:
|
|
11
18
|
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QScrollArea,
|
|
12
|
-
QWidget, QLabel, QFrame, QPushButton)
|
|
13
|
-
from PyQt6.QtCore import Qt
|
|
14
|
-
from PyQt6.QtGui import QFont
|
|
19
|
+
QWidget, QLabel, QFrame, QPushButton, QApplication)
|
|
20
|
+
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
|
21
|
+
from PyQt6.QtGui import QFont, QCursor
|
|
15
22
|
except ImportError:
|
|
16
23
|
try:
|
|
17
24
|
from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QScrollArea,
|
|
18
|
-
QWidget, QLabel, QFrame, QPushButton)
|
|
19
|
-
from PyQt5.QtCore import Qt
|
|
20
|
-
from PyQt5.QtGui import QFont
|
|
25
|
+
QWidget, QLabel, QFrame, QPushButton, QApplication)
|
|
26
|
+
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
|
|
27
|
+
from PyQt5.QtGui import QFont, QCursor
|
|
21
28
|
except ImportError:
|
|
22
29
|
from PySide2.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QScrollArea,
|
|
23
|
-
QWidget, QLabel, QFrame, QPushButton)
|
|
24
|
-
from PySide2.QtCore import Qt
|
|
25
|
-
from PySide2.QtGui import QFont
|
|
30
|
+
QWidget, QLabel, QFrame, QPushButton, QApplication)
|
|
31
|
+
from PySide2.QtCore import Qt, QTimer, Signal as pyqtSignal
|
|
32
|
+
from PySide2.QtGui import QFont, QCursor
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ClickableBubble(QFrame):
|
|
36
|
+
"""Clickable message bubble that copies content to clipboard."""
|
|
37
|
+
|
|
38
|
+
clicked = pyqtSignal()
|
|
39
|
+
|
|
40
|
+
def __init__(self, content: str, is_user: bool, parent=None):
|
|
41
|
+
super().__init__(parent)
|
|
42
|
+
self.content = content
|
|
43
|
+
self.is_user = is_user
|
|
44
|
+
self.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
|
|
45
|
+
|
|
46
|
+
# Store original colors for animation
|
|
47
|
+
if is_user:
|
|
48
|
+
self.normal_bg = "#007AFF"
|
|
49
|
+
self.clicked_bg = "#0066CC"
|
|
50
|
+
else:
|
|
51
|
+
self.normal_bg = "#3a3a3c"
|
|
52
|
+
self.clicked_bg = "#4a4a4c"
|
|
53
|
+
|
|
54
|
+
def mousePressEvent(self, event):
|
|
55
|
+
"""Handle mouse press with visual feedback."""
|
|
56
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
57
|
+
# Apply clicked style (darker)
|
|
58
|
+
self.setStyleSheet(f"""
|
|
59
|
+
QFrame {{
|
|
60
|
+
background: {self.clicked_bg};
|
|
61
|
+
border: none;
|
|
62
|
+
border-radius: 18px;
|
|
63
|
+
max-width: 400px;
|
|
64
|
+
}}
|
|
65
|
+
""")
|
|
66
|
+
super().mousePressEvent(event)
|
|
67
|
+
|
|
68
|
+
def mouseReleaseEvent(self, event):
|
|
69
|
+
"""Handle mouse release - copy to clipboard and restore style."""
|
|
70
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
|
71
|
+
# Copy to clipboard
|
|
72
|
+
clipboard = QApplication.clipboard()
|
|
73
|
+
clipboard.setText(self.content)
|
|
74
|
+
|
|
75
|
+
# Visual feedback: glossy effect (lighter color briefly)
|
|
76
|
+
glossy_color = "#0080FF" if self.is_user else "#5a5a5c"
|
|
77
|
+
self.setStyleSheet(f"""
|
|
78
|
+
QFrame {{
|
|
79
|
+
background: {glossy_color};
|
|
80
|
+
border: none;
|
|
81
|
+
border-radius: 18px;
|
|
82
|
+
max-width: 400px;
|
|
83
|
+
}}
|
|
84
|
+
""")
|
|
85
|
+
|
|
86
|
+
# Restore normal color after brief delay
|
|
87
|
+
QTimer.singleShot(200, self._restore_normal_style)
|
|
88
|
+
|
|
89
|
+
self.clicked.emit()
|
|
90
|
+
super().mouseReleaseEvent(event)
|
|
91
|
+
|
|
92
|
+
def _restore_normal_style(self):
|
|
93
|
+
"""Restore normal bubble style."""
|
|
94
|
+
self.setStyleSheet(f"""
|
|
95
|
+
QFrame {{
|
|
96
|
+
background: {self.normal_bg};
|
|
97
|
+
border: none;
|
|
98
|
+
border-radius: 18px;
|
|
99
|
+
max-width: 400px;
|
|
100
|
+
}}
|
|
101
|
+
""")
|
|
26
102
|
|
|
27
103
|
|
|
28
104
|
class SafeDialog(QDialog):
|
|
@@ -97,6 +173,9 @@ class iPhoneMessagesDialog:
|
|
|
97
173
|
# Apply authentic iPhone styling
|
|
98
174
|
dialog.setStyleSheet(iPhoneMessagesDialog._get_authentic_iphone_styles())
|
|
99
175
|
|
|
176
|
+
# Auto-scroll to bottom to show the latest messages
|
|
177
|
+
QTimer.singleShot(100, lambda: scroll_area.verticalScrollBar().setValue(scroll_area.verticalScrollBar().maximum()))
|
|
178
|
+
|
|
100
179
|
return dialog
|
|
101
180
|
|
|
102
181
|
@staticmethod
|
|
@@ -161,7 +240,7 @@ class iPhoneMessagesDialog:
|
|
|
161
240
|
background: transparent;
|
|
162
241
|
border: none;
|
|
163
242
|
text-align: left;
|
|
164
|
-
font-family: -
|
|
243
|
+
font-family: "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
165
244
|
}
|
|
166
245
|
""")
|
|
167
246
|
nav_layout.addWidget(back_btn)
|
|
@@ -175,7 +254,7 @@ class iPhoneMessagesDialog:
|
|
|
175
254
|
color: #ffffff;
|
|
176
255
|
font-size: 17px;
|
|
177
256
|
font-weight: 600;
|
|
178
|
-
font-family: -
|
|
257
|
+
font-family: "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
179
258
|
}
|
|
180
259
|
""")
|
|
181
260
|
nav_layout.addWidget(title)
|
|
@@ -216,20 +295,20 @@ class iPhoneMessagesDialog:
|
|
|
216
295
|
container = QFrame()
|
|
217
296
|
container.setStyleSheet("background: transparent; border: none;")
|
|
218
297
|
layout = QHBoxLayout(container)
|
|
219
|
-
layout.setContentsMargins(
|
|
298
|
+
layout.setContentsMargins(12, 0, 12, 0) # Tighter margins for more width
|
|
220
299
|
layout.setSpacing(0)
|
|
221
300
|
|
|
222
|
-
# Create bubble
|
|
223
|
-
bubble =
|
|
301
|
+
# Create clickable bubble
|
|
302
|
+
bubble = ClickableBubble(msg['content'], is_user)
|
|
224
303
|
bubble_layout = QVBoxLayout(bubble)
|
|
225
|
-
bubble_layout.setContentsMargins(
|
|
304
|
+
bubble_layout.setContentsMargins(12, 7, 12, 7) # More compact padding
|
|
226
305
|
bubble_layout.setSpacing(0)
|
|
227
306
|
|
|
228
307
|
# Process content with FULL markdown support
|
|
229
308
|
content = iPhoneMessagesDialog._process_full_markdown(msg['content'])
|
|
230
309
|
content_label = QLabel(content)
|
|
231
310
|
content_label.setWordWrap(True)
|
|
232
|
-
content_label.setTextInteractionFlags(Qt.TextInteractionFlag.
|
|
311
|
+
content_label.setTextInteractionFlags(Qt.TextInteractionFlag.NoTextInteraction) # No text selection, bubble handles clicks
|
|
233
312
|
content_label.setTextFormat(Qt.TextFormat.RichText)
|
|
234
313
|
|
|
235
314
|
if is_user:
|
|
@@ -239,17 +318,17 @@ class iPhoneMessagesDialog:
|
|
|
239
318
|
background: #007AFF;
|
|
240
319
|
border: none;
|
|
241
320
|
border-radius: 18px;
|
|
242
|
-
max-width:
|
|
321
|
+
max-width: 400px;
|
|
243
322
|
}
|
|
244
323
|
""")
|
|
245
324
|
content_label.setStyleSheet("""
|
|
246
325
|
QLabel {
|
|
247
326
|
background: transparent;
|
|
248
327
|
color: #FFFFFF;
|
|
249
|
-
font-size:
|
|
328
|
+
font-size: 14px;
|
|
250
329
|
font-weight: 400;
|
|
251
|
-
line-height:
|
|
252
|
-
font-family: -
|
|
330
|
+
line-height: 18px;
|
|
331
|
+
font-family: "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
253
332
|
}
|
|
254
333
|
""")
|
|
255
334
|
# Right align
|
|
@@ -262,17 +341,17 @@ class iPhoneMessagesDialog:
|
|
|
262
341
|
background: #3a3a3c;
|
|
263
342
|
border: none;
|
|
264
343
|
border-radius: 18px;
|
|
265
|
-
max-width:
|
|
344
|
+
max-width: 400px;
|
|
266
345
|
}
|
|
267
346
|
""")
|
|
268
347
|
content_label.setStyleSheet("""
|
|
269
348
|
QLabel {
|
|
270
349
|
background: transparent;
|
|
271
350
|
color: #ffffff;
|
|
272
|
-
font-size:
|
|
351
|
+
font-size: 14px;
|
|
273
352
|
font-weight: 400;
|
|
274
|
-
line-height:
|
|
275
|
-
font-family: -
|
|
353
|
+
line-height: 18px;
|
|
354
|
+
font-family: "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
276
355
|
}
|
|
277
356
|
""")
|
|
278
357
|
# Left align
|
|
@@ -315,7 +394,7 @@ class iPhoneMessagesDialog:
|
|
|
315
394
|
font-size: 13px;
|
|
316
395
|
font-weight: 400;
|
|
317
396
|
color: rgba(255, 255, 255, 0.6);
|
|
318
|
-
font-family: -
|
|
397
|
+
font-family: "SF Pro Text", "Helvetica Neue", sans-serif;
|
|
319
398
|
padding: 0px;
|
|
320
399
|
}
|
|
321
400
|
""")
|
|
@@ -340,29 +419,50 @@ class iPhoneMessagesDialog:
|
|
|
340
419
|
|
|
341
420
|
@staticmethod
|
|
342
421
|
def _process_full_markdown(text: str) -> str:
|
|
343
|
-
"""Process markdown
|
|
344
|
-
#
|
|
345
|
-
|
|
422
|
+
"""Process markdown using proper markdown library with syntax highlighting."""
|
|
423
|
+
# Configure markdown with extensions
|
|
424
|
+
md = markdown.Markdown(
|
|
425
|
+
extensions=[
|
|
426
|
+
FencedCodeExtension(),
|
|
427
|
+
TableExtension(),
|
|
428
|
+
'nl2br', # Convert newlines to <br>
|
|
429
|
+
],
|
|
430
|
+
extension_configs={
|
|
431
|
+
'fenced_code': {
|
|
432
|
+
'lang_prefix': 'language-',
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Convert markdown to HTML
|
|
438
|
+
html = md.convert(text)
|
|
346
439
|
|
|
347
|
-
#
|
|
348
|
-
|
|
440
|
+
# Apply custom styling to the generated HTML
|
|
441
|
+
# Style code blocks
|
|
442
|
+
html = html.replace('<pre>', '<pre style="margin: 6px 0; background: rgba(0,0,0,0.3); border-radius: 6px; padding: 8px; overflow-x: auto;">')
|
|
443
|
+
html = html.replace('<code>', '<code style="font-family: \'SF Mono\', \'Menlo\', \'Monaco\', \'Courier New\', monospace; font-size: 12px; line-height: 1.4; color: #e8e8e8;">')
|
|
349
444
|
|
|
350
|
-
#
|
|
351
|
-
|
|
445
|
+
# Style tables
|
|
446
|
+
html = html.replace('<table>', '<table style="margin: 6px 0; border-collapse: collapse; width: 100%; font-size: 12px;">')
|
|
447
|
+
html = html.replace('<thead>', '<thead style="background: rgba(0,0,0,0.2);">')
|
|
448
|
+
html = html.replace('<th>', '<th style="padding: 4px 8px; text-align: left; font-weight: 600; border-bottom: 1px solid rgba(255,255,255,0.2);">')
|
|
449
|
+
html = html.replace('<td>', '<td style="padding: 4px 8px; border-bottom: 1px solid rgba(255,255,255,0.1);">')
|
|
352
450
|
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
451
|
+
# Style headers with minimal spacing
|
|
452
|
+
html = html.replace('<h1>', '<h1 style="margin: 6px 0 2px 0; font-weight: 600; font-size: 17px;">')
|
|
453
|
+
html = html.replace('<h2>', '<h2 style="margin: 5px 0 2px 0; font-weight: 600; font-size: 16px;">')
|
|
454
|
+
html = html.replace('<h3>', '<h3 style="margin: 4px 0 1px 0; font-weight: 600; font-size: 15px;">')
|
|
455
|
+
html = html.replace('<h4>', '<h4 style="margin: 3px 0 1px 0; font-weight: 600; font-size: 14px;">')
|
|
358
456
|
|
|
359
|
-
#
|
|
360
|
-
|
|
457
|
+
# Style lists with minimal spacing
|
|
458
|
+
html = html.replace('<ul>', '<ul style="margin: 4px 0; padding-left: 20px;">')
|
|
459
|
+
html = html.replace('<ol>', '<ol style="margin: 4px 0; padding-left: 20px;">')
|
|
460
|
+
html = html.replace('<li>', '<li style="margin: 1px 0; line-height: 1.3;">')
|
|
361
461
|
|
|
362
|
-
#
|
|
363
|
-
|
|
462
|
+
# Style paragraphs with minimal spacing
|
|
463
|
+
html = html.replace('<p>', '<p style="margin: 2px 0; line-height: 1.3;">')
|
|
364
464
|
|
|
365
|
-
return
|
|
465
|
+
return html
|
|
366
466
|
|
|
367
467
|
@staticmethod
|
|
368
468
|
def _get_authentic_iphone_styles() -> str:
|
|
@@ -145,7 +145,7 @@ class TTSToggle(QPushButton):
|
|
|
145
145
|
border-radius: 12px;
|
|
146
146
|
font-size: 12px;
|
|
147
147
|
color: {text_color};
|
|
148
|
-
font-family:
|
|
148
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
149
149
|
font-weight: 600;
|
|
150
150
|
}}
|
|
151
151
|
QPushButton:hover {{
|
|
@@ -212,7 +212,7 @@ class FullVoiceToggle(QPushButton):
|
|
|
212
212
|
border-radius: 12px;
|
|
213
213
|
font-size: 12px;
|
|
214
214
|
color: {text_color};
|
|
215
|
-
font-family:
|
|
215
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
216
216
|
font-weight: 600;
|
|
217
217
|
}}
|
|
218
218
|
QPushButton:hover {{
|
|
@@ -228,28 +228,34 @@ class FullVoiceToggle(QPushButton):
|
|
|
228
228
|
|
|
229
229
|
class LLMWorker(QThread):
|
|
230
230
|
"""Worker thread for LLM processing."""
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
response_ready = pyqtSignal(str)
|
|
233
233
|
error_occurred = pyqtSignal(str)
|
|
234
|
-
|
|
235
|
-
def __init__(self, llm_manager, message, provider, model):
|
|
234
|
+
|
|
235
|
+
def __init__(self, llm_manager, message, provider, model, media=None):
|
|
236
236
|
super().__init__()
|
|
237
237
|
self.llm_manager = llm_manager
|
|
238
238
|
self.message = message
|
|
239
239
|
self.provider = provider
|
|
240
240
|
self.model = model
|
|
241
|
-
|
|
241
|
+
self.media = media or []
|
|
242
|
+
|
|
242
243
|
def run(self):
|
|
243
244
|
"""Run LLM processing in background."""
|
|
244
245
|
try:
|
|
245
|
-
# Use LLMManager session for context persistence
|
|
246
|
-
response = self.llm_manager.generate_response(
|
|
247
|
-
|
|
246
|
+
# Use LLMManager session for context persistence with optional media files
|
|
247
|
+
response = self.llm_manager.generate_response(
|
|
248
|
+
self.message,
|
|
249
|
+
self.provider,
|
|
250
|
+
self.model,
|
|
251
|
+
media=self.media if self.media else None
|
|
252
|
+
)
|
|
253
|
+
|
|
248
254
|
# Response is already a string from LLMManager
|
|
249
255
|
response_text = str(response)
|
|
250
|
-
|
|
256
|
+
|
|
251
257
|
self.response_ready.emit(response_text)
|
|
252
|
-
|
|
258
|
+
|
|
253
259
|
except Exception as e:
|
|
254
260
|
print(f"❌ LLM Error: {e}")
|
|
255
261
|
import traceback
|
|
@@ -278,6 +284,9 @@ class QtChatBubble(QWidget):
|
|
|
278
284
|
|
|
279
285
|
# History dialog instance for toggle behavior
|
|
280
286
|
self.history_dialog = None
|
|
287
|
+
|
|
288
|
+
# Attached files for media handling (AbstractCore 2.4.5+)
|
|
289
|
+
self.attached_files: List[str] = []
|
|
281
290
|
|
|
282
291
|
# Initialize new manager classes
|
|
283
292
|
self.provider_manager = None
|
|
@@ -357,7 +366,7 @@ class QtChatBubble(QWidget):
|
|
|
357
366
|
font-size: 14px;
|
|
358
367
|
font-weight: 600;
|
|
359
368
|
color: rgba(255, 255, 255, 0.9);
|
|
360
|
-
font-family:
|
|
369
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
361
370
|
}
|
|
362
371
|
QPushButton:hover {
|
|
363
372
|
background: rgba(255, 60, 60, 0.8);
|
|
@@ -389,7 +398,7 @@ class QtChatBubble(QWidget):
|
|
|
389
398
|
border-radius: 11px;
|
|
390
399
|
font-size: 10px;
|
|
391
400
|
color: rgba(255, 255, 255, 0.7);
|
|
392
|
-
font-family:
|
|
401
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
393
402
|
padding: 0 10px;
|
|
394
403
|
}
|
|
395
404
|
QPushButton:hover {
|
|
@@ -431,7 +440,7 @@ class QtChatBubble(QWidget):
|
|
|
431
440
|
font-size: 10px;
|
|
432
441
|
font-weight: 600;
|
|
433
442
|
color: #ffffff;
|
|
434
|
-
font-family:
|
|
443
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
435
444
|
}
|
|
436
445
|
""")
|
|
437
446
|
header_layout.addWidget(self.status_label)
|
|
@@ -442,7 +451,7 @@ class QtChatBubble(QWidget):
|
|
|
442
451
|
self.input_container = QFrame()
|
|
443
452
|
self.input_container.setStyleSheet("""
|
|
444
453
|
QFrame {
|
|
445
|
-
background: #
|
|
454
|
+
background: #2a2a2a;
|
|
446
455
|
border: 1px solid #404040;
|
|
447
456
|
border-radius: 8px;
|
|
448
457
|
padding: 4px;
|
|
@@ -455,14 +464,42 @@ class QtChatBubble(QWidget):
|
|
|
455
464
|
# Input field with inline send button
|
|
456
465
|
input_row = QHBoxLayout()
|
|
457
466
|
input_row.setSpacing(4)
|
|
458
|
-
|
|
467
|
+
|
|
468
|
+
# File attachment button - modern paperclip icon
|
|
469
|
+
self.attach_button = QPushButton("📎")
|
|
470
|
+
self.attach_button.clicked.connect(self.attach_files)
|
|
471
|
+
self.attach_button.setFixedSize(36, 36)
|
|
472
|
+
self.attach_button.setToolTip("Attach files (images, PDFs, Office docs, etc.)")
|
|
473
|
+
self.attach_button.setStyleSheet("""
|
|
474
|
+
QPushButton {
|
|
475
|
+
background: rgba(255, 255, 255, 0.08);
|
|
476
|
+
border: 1px solid #404040;
|
|
477
|
+
border-radius: 18px;
|
|
478
|
+
font-size: 14px;
|
|
479
|
+
color: rgba(255, 255, 255, 0.7);
|
|
480
|
+
text-align: center;
|
|
481
|
+
padding: 0px;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
QPushButton:hover {
|
|
485
|
+
background: rgba(255, 255, 255, 0.12);
|
|
486
|
+
border: 1px solid #0066cc;
|
|
487
|
+
color: rgba(255, 255, 255, 0.9);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
QPushButton:pressed {
|
|
491
|
+
background: rgba(255, 255, 255, 0.06);
|
|
492
|
+
}
|
|
493
|
+
""")
|
|
494
|
+
input_row.addWidget(self.attach_button)
|
|
495
|
+
|
|
459
496
|
# Text input - larger, primary focus
|
|
460
497
|
self.input_text = QTextEdit()
|
|
461
498
|
self.input_text.setPlaceholderText("Ask me anything... (Shift+Enter to send)")
|
|
462
499
|
self.input_text.setMaximumHeight(100) # Increased to better use available space
|
|
463
500
|
self.input_text.setMinimumHeight(70) # Increased to better use available space
|
|
464
501
|
input_row.addWidget(self.input_text)
|
|
465
|
-
|
|
502
|
+
|
|
466
503
|
# Send button - primary action with special styling
|
|
467
504
|
self.send_button = QPushButton("→")
|
|
468
505
|
self.send_button.clicked.connect(self.send_message)
|
|
@@ -478,16 +515,16 @@ class QtChatBubble(QWidget):
|
|
|
478
515
|
text-align: center;
|
|
479
516
|
padding: 0px;
|
|
480
517
|
}
|
|
481
|
-
|
|
518
|
+
|
|
482
519
|
QPushButton:hover {
|
|
483
520
|
background: #0080ff;
|
|
484
521
|
border: 1px solid #0099ff;
|
|
485
522
|
}
|
|
486
|
-
|
|
523
|
+
|
|
487
524
|
QPushButton:pressed {
|
|
488
525
|
background: #0052a3;
|
|
489
526
|
}
|
|
490
|
-
|
|
527
|
+
|
|
491
528
|
QPushButton:disabled {
|
|
492
529
|
background: #404040;
|
|
493
530
|
color: #666666;
|
|
@@ -495,14 +532,30 @@ class QtChatBubble(QWidget):
|
|
|
495
532
|
}
|
|
496
533
|
""")
|
|
497
534
|
input_row.addWidget(self.send_button)
|
|
498
|
-
|
|
535
|
+
|
|
499
536
|
input_layout.addLayout(input_row)
|
|
537
|
+
|
|
538
|
+
# Attached files display area (initially hidden)
|
|
539
|
+
self.attached_files_container = QFrame()
|
|
540
|
+
self.attached_files_container.setStyleSheet("""
|
|
541
|
+
QFrame {
|
|
542
|
+
background: rgba(255, 255, 255, 0.04);
|
|
543
|
+
border: 1px solid #404040;
|
|
544
|
+
border-radius: 6px;
|
|
545
|
+
padding: 4px;
|
|
546
|
+
}
|
|
547
|
+
""")
|
|
548
|
+
self.attached_files_layout = QHBoxLayout(self.attached_files_container)
|
|
549
|
+
self.attached_files_layout.setContentsMargins(4, 4, 4, 4)
|
|
550
|
+
self.attached_files_layout.setSpacing(4)
|
|
551
|
+
self.attached_files_container.hide() # Initially hidden
|
|
552
|
+
input_layout.addWidget(self.attached_files_container)
|
|
500
553
|
layout.addWidget(self.input_container)
|
|
501
554
|
|
|
502
555
|
# Bottom controls - Cursor style (minimal, clean)
|
|
503
556
|
controls_layout = QHBoxLayout()
|
|
504
|
-
controls_layout.setContentsMargins(
|
|
505
|
-
controls_layout.setSpacing(
|
|
557
|
+
controls_layout.setContentsMargins(8, 2, 8, 2)
|
|
558
|
+
controls_layout.setSpacing(4)
|
|
506
559
|
|
|
507
560
|
# Provider dropdown (rounded, clean)
|
|
508
561
|
self.provider_combo = QComboBox()
|
|
@@ -514,10 +567,10 @@ class QtChatBubble(QWidget):
|
|
|
514
567
|
background: rgba(255, 255, 255, 0.08);
|
|
515
568
|
border: none;
|
|
516
569
|
border-radius: 14px;
|
|
517
|
-
padding: 0
|
|
570
|
+
padding: 0 8px;
|
|
518
571
|
font-size: 11px;
|
|
519
572
|
color: rgba(255, 255, 255, 0.9);
|
|
520
|
-
font-family:
|
|
573
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
521
574
|
}
|
|
522
575
|
QComboBox:hover {
|
|
523
576
|
background: rgba(255, 255, 255, 0.12);
|
|
@@ -544,10 +597,10 @@ class QtChatBubble(QWidget):
|
|
|
544
597
|
background: rgba(255, 255, 255, 0.08);
|
|
545
598
|
border: none;
|
|
546
599
|
border-radius: 14px;
|
|
547
|
-
padding: 0
|
|
600
|
+
padding: 0 8px;
|
|
548
601
|
font-size: 11px;
|
|
549
602
|
color: rgba(255, 255, 255, 0.9);
|
|
550
|
-
font-family:
|
|
603
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
551
604
|
}
|
|
552
605
|
QComboBox:hover {
|
|
553
606
|
background: rgba(255, 255, 255, 0.12);
|
|
@@ -568,7 +621,7 @@ class QtChatBubble(QWidget):
|
|
|
568
621
|
|
|
569
622
|
# Token counter (minimal)
|
|
570
623
|
self.token_label = QLabel("0 / 128k")
|
|
571
|
-
self.token_label.setFixedHeight(
|
|
624
|
+
self.token_label.setFixedHeight(28) # Match provider and model dropdown height
|
|
572
625
|
self.token_label.setMinimumWidth(104) # Increased by 30% (80 * 1.3 = 104)
|
|
573
626
|
self.token_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
574
627
|
self.token_label.setStyleSheet("""
|
|
@@ -578,7 +631,7 @@ class QtChatBubble(QWidget):
|
|
|
578
631
|
border-radius: 14px;
|
|
579
632
|
font-size: 12px;
|
|
580
633
|
color: rgba(255, 255, 255, 0.6);
|
|
581
|
-
font-family:
|
|
634
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
582
635
|
}
|
|
583
636
|
""")
|
|
584
637
|
controls_layout.addWidget(self.token_label)
|
|
@@ -612,21 +665,21 @@ class QtChatBubble(QWidget):
|
|
|
612
665
|
|
|
613
666
|
/* Input Field - Modern Grey Design */
|
|
614
667
|
QTextEdit {
|
|
615
|
-
background: #
|
|
668
|
+
background: #2a2a2a;
|
|
616
669
|
border: 1px solid #404040;
|
|
617
670
|
border-radius: 8px;
|
|
618
671
|
padding: 12px 16px;
|
|
619
672
|
font-size: 14px;
|
|
620
673
|
font-weight: 400;
|
|
621
674
|
color: #ffffff;
|
|
622
|
-
font-family: system-ui,
|
|
675
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
623
676
|
selection-background-color: #0066cc;
|
|
624
677
|
line-height: 1.4;
|
|
625
678
|
}
|
|
626
679
|
|
|
627
680
|
QTextEdit:focus {
|
|
628
681
|
border: 1px solid #0066cc;
|
|
629
|
-
background: #
|
|
682
|
+
background: #333333;
|
|
630
683
|
}
|
|
631
684
|
|
|
632
685
|
QTextEdit::placeholder {
|
|
@@ -642,7 +695,7 @@ class QtChatBubble(QWidget):
|
|
|
642
695
|
font-size: 11px;
|
|
643
696
|
font-weight: 500;
|
|
644
697
|
color: #ffffff;
|
|
645
|
-
font-family: system-ui,
|
|
698
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
646
699
|
}
|
|
647
700
|
|
|
648
701
|
QPushButton:hover {
|
|
@@ -670,7 +723,7 @@ class QtChatBubble(QWidget):
|
|
|
670
723
|
font-size: 12px;
|
|
671
724
|
font-weight: 400;
|
|
672
725
|
color: #ffffff;
|
|
673
|
-
font-family: system-ui,
|
|
726
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
674
727
|
letter-spacing: 0.01em;
|
|
675
728
|
}
|
|
676
729
|
|
|
@@ -700,7 +753,7 @@ class QtChatBubble(QWidget):
|
|
|
700
753
|
selection-background-color: #4299e1;
|
|
701
754
|
color: #e2e8f0;
|
|
702
755
|
padding: 4px;
|
|
703
|
-
font-family:
|
|
756
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, sans-serif;
|
|
704
757
|
}
|
|
705
758
|
|
|
706
759
|
QComboBox QAbstractItemView::item {
|
|
@@ -732,7 +785,7 @@ class QtChatBubble(QWidget):
|
|
|
732
785
|
color: rgba(255, 255, 255, 0.8);
|
|
733
786
|
font-size: 12px;
|
|
734
787
|
font-weight: 500;
|
|
735
|
-
font-family:
|
|
788
|
+
font-family: "SF Pro Text", "Helvetica Neue", 'Segoe UI', Roboto, sans-serif;
|
|
736
789
|
letter-spacing: 0.3px;
|
|
737
790
|
}
|
|
738
791
|
|
|
@@ -747,7 +800,7 @@ class QtChatBubble(QWidget):
|
|
|
747
800
|
text-transform: uppercase;
|
|
748
801
|
letter-spacing: 0.5px;
|
|
749
802
|
color: #a6e3a1;
|
|
750
|
-
font-family:
|
|
803
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, sans-serif;
|
|
751
804
|
}
|
|
752
805
|
|
|
753
806
|
QLabel#status_generating {
|
|
@@ -760,7 +813,7 @@ class QtChatBubble(QWidget):
|
|
|
760
813
|
text-transform: uppercase;
|
|
761
814
|
letter-spacing: 0.5px;
|
|
762
815
|
color: #fab387;
|
|
763
|
-
font-family:
|
|
816
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, sans-serif;
|
|
764
817
|
}
|
|
765
818
|
|
|
766
819
|
QLabel#status_error {
|
|
@@ -773,7 +826,7 @@ class QtChatBubble(QWidget):
|
|
|
773
826
|
text-transform: uppercase;
|
|
774
827
|
letter-spacing: 0.5px;
|
|
775
828
|
color: #f38ba8;
|
|
776
|
-
font-family:
|
|
829
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, sans-serif;
|
|
777
830
|
}
|
|
778
831
|
|
|
779
832
|
QLabel#token_label {
|
|
@@ -781,7 +834,7 @@ class QtChatBubble(QWidget):
|
|
|
781
834
|
border: 1px solid #4a5568;
|
|
782
835
|
border-radius: 8px;
|
|
783
836
|
padding: 10px 12px;
|
|
784
|
-
font-family:
|
|
837
|
+
font-family: "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, sans-serif;
|
|
785
838
|
font-size: 11px;
|
|
786
839
|
font-weight: 500;
|
|
787
840
|
color: #cbd5e0;
|
|
@@ -1023,22 +1076,136 @@ class QtChatBubble(QWidget):
|
|
|
1023
1076
|
print(f"Model changed to: {self.current_model}")
|
|
1024
1077
|
|
|
1025
1078
|
|
|
1079
|
+
def attach_files(self):
|
|
1080
|
+
"""Open file dialog to attach files (AbstractCore 2.4.5+ media handling)."""
|
|
1081
|
+
file_dialog = QFileDialog(self)
|
|
1082
|
+
file_dialog.setFileMode(QFileDialog.FileMode.ExistingFiles)
|
|
1083
|
+
file_dialog.setNameFilter(
|
|
1084
|
+
"All supported files (*.png *.jpg *.jpeg *.gif *.webp *.bmp *.tiff "
|
|
1085
|
+
"*.pdf *.docx *.xlsx *.pptx *.txt *.md *.csv *.tsv *.json);;"
|
|
1086
|
+
"Images (*.png *.jpg *.jpeg *.gif *.webp *.bmp *.tiff);;"
|
|
1087
|
+
"Documents (*.pdf *.docx *.xlsx *.pptx *.txt *.md);;"
|
|
1088
|
+
"Data files (*.csv *.tsv *.json);;"
|
|
1089
|
+
"All files (*.*)"
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
if file_dialog.exec():
|
|
1093
|
+
selected_files = file_dialog.selectedFiles()
|
|
1094
|
+
for file_path in selected_files:
|
|
1095
|
+
if file_path not in self.attached_files:
|
|
1096
|
+
self.attached_files.append(file_path)
|
|
1097
|
+
if self.debug:
|
|
1098
|
+
print(f"📎 Attached file: {file_path}")
|
|
1099
|
+
|
|
1100
|
+
self.update_attached_files_display()
|
|
1101
|
+
|
|
1102
|
+
def update_attached_files_display(self):
|
|
1103
|
+
"""Update the visual display of attached files."""
|
|
1104
|
+
# Clear existing file chips
|
|
1105
|
+
while self.attached_files_layout.count():
|
|
1106
|
+
child = self.attached_files_layout.takeAt(0)
|
|
1107
|
+
if child.widget():
|
|
1108
|
+
child.widget().deleteLater()
|
|
1109
|
+
|
|
1110
|
+
if not self.attached_files:
|
|
1111
|
+
self.attached_files_container.hide()
|
|
1112
|
+
return
|
|
1113
|
+
|
|
1114
|
+
# Show container and add file chips
|
|
1115
|
+
self.attached_files_container.show()
|
|
1116
|
+
|
|
1117
|
+
for file_path in self.attached_files:
|
|
1118
|
+
import os
|
|
1119
|
+
file_name = os.path.basename(file_path)
|
|
1120
|
+
|
|
1121
|
+
# Create file chip
|
|
1122
|
+
file_chip = QFrame()
|
|
1123
|
+
file_chip.setStyleSheet("""
|
|
1124
|
+
QFrame {
|
|
1125
|
+
background: rgba(0, 102, 204, 0.2);
|
|
1126
|
+
border: 1px solid rgba(0, 102, 204, 0.4);
|
|
1127
|
+
border-radius: 10px;
|
|
1128
|
+
padding: 2px 8px;
|
|
1129
|
+
}
|
|
1130
|
+
""")
|
|
1131
|
+
|
|
1132
|
+
chip_layout = QHBoxLayout(file_chip)
|
|
1133
|
+
chip_layout.setContentsMargins(4, 2, 4, 2)
|
|
1134
|
+
chip_layout.setSpacing(4)
|
|
1135
|
+
|
|
1136
|
+
# File icon based on type
|
|
1137
|
+
ext = os.path.splitext(file_name)[1].lower()
|
|
1138
|
+
if ext in ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.tiff']:
|
|
1139
|
+
icon = "🖼️"
|
|
1140
|
+
elif ext == '.pdf':
|
|
1141
|
+
icon = "📄"
|
|
1142
|
+
elif ext in ['.docx', '.doc']:
|
|
1143
|
+
icon = "📝"
|
|
1144
|
+
elif ext in ['.xlsx', '.xls']:
|
|
1145
|
+
icon = "📊"
|
|
1146
|
+
elif ext in ['.pptx', '.ppt']:
|
|
1147
|
+
icon = "📊"
|
|
1148
|
+
elif ext in ['.csv', '.tsv']:
|
|
1149
|
+
icon = "📋"
|
|
1150
|
+
else:
|
|
1151
|
+
icon = "📎"
|
|
1152
|
+
|
|
1153
|
+
file_label = QLabel(f"{icon} {file_name[:20]}{'...' if len(file_name) > 20 else ''}")
|
|
1154
|
+
file_label.setStyleSheet("background: transparent; border: none; color: rgba(255, 255, 255, 0.9); font-size: 10px;")
|
|
1155
|
+
chip_layout.addWidget(file_label)
|
|
1156
|
+
|
|
1157
|
+
# Remove button
|
|
1158
|
+
remove_btn = QPushButton("✕")
|
|
1159
|
+
remove_btn.setFixedSize(16, 16)
|
|
1160
|
+
remove_btn.setStyleSheet("""
|
|
1161
|
+
QPushButton {
|
|
1162
|
+
background: transparent;
|
|
1163
|
+
border: none;
|
|
1164
|
+
color: rgba(255, 255, 255, 0.6);
|
|
1165
|
+
font-size: 10px;
|
|
1166
|
+
padding: 0px;
|
|
1167
|
+
}
|
|
1168
|
+
QPushButton:hover {
|
|
1169
|
+
color: rgba(255, 60, 60, 0.9);
|
|
1170
|
+
}
|
|
1171
|
+
""")
|
|
1172
|
+
remove_btn.clicked.connect(lambda checked, fp=file_path: self.remove_attached_file(fp))
|
|
1173
|
+
chip_layout.addWidget(remove_btn)
|
|
1174
|
+
|
|
1175
|
+
self.attached_files_layout.addWidget(file_chip)
|
|
1176
|
+
|
|
1177
|
+
self.attached_files_layout.addStretch()
|
|
1178
|
+
|
|
1179
|
+
def remove_attached_file(self, file_path):
|
|
1180
|
+
"""Remove a file from the attached files list."""
|
|
1181
|
+
if file_path in self.attached_files:
|
|
1182
|
+
self.attached_files.remove(file_path)
|
|
1183
|
+
if self.debug:
|
|
1184
|
+
print(f"🗑️ Removed attached file: {file_path}")
|
|
1185
|
+
self.update_attached_files_display()
|
|
1186
|
+
|
|
1026
1187
|
def send_message(self):
|
|
1027
|
-
"""Send message to LLM."""
|
|
1188
|
+
"""Send message to LLM with optional media attachments."""
|
|
1028
1189
|
message = self.input_text.toPlainText().strip()
|
|
1029
1190
|
if not message:
|
|
1030
1191
|
return
|
|
1031
|
-
|
|
1192
|
+
|
|
1032
1193
|
if self.debug:
|
|
1033
1194
|
print(f"💬 Sending message: '{message[:50]}...' to {self.current_provider}/{self.current_model}")
|
|
1034
|
-
|
|
1195
|
+
if self.attached_files:
|
|
1196
|
+
print(f"📎 With {len(self.attached_files)} attached file(s)")
|
|
1197
|
+
|
|
1035
1198
|
# 1. Clear input immediately
|
|
1036
1199
|
self.input_text.clear()
|
|
1037
|
-
|
|
1038
|
-
# 2.
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
# 3.
|
|
1200
|
+
|
|
1201
|
+
# 2. Capture attached files before clearing
|
|
1202
|
+
media_files = self.attached_files.copy()
|
|
1203
|
+
|
|
1204
|
+
# 3. Clear attached files display
|
|
1205
|
+
self.attached_files.clear()
|
|
1206
|
+
self.update_attached_files_display()
|
|
1207
|
+
|
|
1208
|
+
# 4. Update UI for sending state
|
|
1042
1209
|
self.send_button.setEnabled(False)
|
|
1043
1210
|
self.send_button.setText("⏳")
|
|
1044
1211
|
self.status_label.setText("generating")
|
|
@@ -1056,21 +1223,27 @@ class QtChatBubble(QWidget):
|
|
|
1056
1223
|
color: #fab387;
|
|
1057
1224
|
}
|
|
1058
1225
|
""")
|
|
1059
|
-
|
|
1226
|
+
|
|
1060
1227
|
# Notify main app about status change (for icon animation)
|
|
1061
1228
|
if self.status_callback:
|
|
1062
1229
|
self.status_callback("generating")
|
|
1063
|
-
|
|
1230
|
+
|
|
1064
1231
|
print("🔄 QtChatBubble: UI updated, creating worker thread...")
|
|
1065
|
-
|
|
1066
|
-
#
|
|
1067
|
-
self.worker = LLMWorker(
|
|
1232
|
+
|
|
1233
|
+
# 5. Start worker thread to send request with optional media files
|
|
1234
|
+
self.worker = LLMWorker(
|
|
1235
|
+
self.llm_manager,
|
|
1236
|
+
message,
|
|
1237
|
+
self.current_provider,
|
|
1238
|
+
self.current_model,
|
|
1239
|
+
media=media_files if media_files else None
|
|
1240
|
+
)
|
|
1068
1241
|
self.worker.response_ready.connect(self.on_response_ready)
|
|
1069
1242
|
self.worker.error_occurred.connect(self.on_error_occurred)
|
|
1070
|
-
|
|
1243
|
+
|
|
1071
1244
|
print("🔄 QtChatBubble: Starting worker thread...")
|
|
1072
1245
|
self.worker.start()
|
|
1073
|
-
|
|
1246
|
+
|
|
1074
1247
|
print("🔄 QtChatBubble: Worker thread started, hiding bubble...")
|
|
1075
1248
|
# Hide bubble after sending (like the original design)
|
|
1076
1249
|
QTimer.singleShot(500, self.hide)
|
|
@@ -1450,7 +1623,7 @@ class QtChatBubble(QWidget):
|
|
|
1450
1623
|
font-size: 10px;
|
|
1451
1624
|
font-weight: 600;
|
|
1452
1625
|
color: #ffffff;
|
|
1453
|
-
font-family:
|
|
1626
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
1454
1627
|
}}
|
|
1455
1628
|
""")
|
|
1456
1629
|
|
|
@@ -1952,7 +2125,7 @@ class QtChatBubble(QWidget):
|
|
|
1952
2125
|
border-radius: 11px;
|
|
1953
2126
|
font-size: 10px;
|
|
1954
2127
|
color: #ffffff;
|
|
1955
|
-
font-family:
|
|
2128
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
1956
2129
|
padding: 0 10px;
|
|
1957
2130
|
font-weight: 600;
|
|
1958
2131
|
}
|
|
@@ -1969,7 +2142,7 @@ class QtChatBubble(QWidget):
|
|
|
1969
2142
|
border-radius: 11px;
|
|
1970
2143
|
font-size: 10px;
|
|
1971
2144
|
color: rgba(255, 255, 255, 0.7);
|
|
1972
|
-
font-family:
|
|
2145
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
1973
2146
|
padding: 0 10px;
|
|
1974
2147
|
}
|
|
1975
2148
|
QPushButton:hover {
|
|
@@ -107,7 +107,7 @@ class ToastWindow(QWidget):
|
|
|
107
107
|
color: rgba(255, 255, 255, 0.9);
|
|
108
108
|
background: transparent;
|
|
109
109
|
border: none;
|
|
110
|
-
font-family:
|
|
110
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
111
111
|
}
|
|
112
112
|
""")
|
|
113
113
|
header_layout.addWidget(title_label)
|
|
@@ -128,7 +128,7 @@ class ToastWindow(QWidget):
|
|
|
128
128
|
border-radius: 12px;
|
|
129
129
|
font-size: 11px;
|
|
130
130
|
color: rgba(255, 255, 255, 0.7);
|
|
131
|
-
font-family:
|
|
131
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
132
132
|
}
|
|
133
133
|
QPushButton:hover {
|
|
134
134
|
background: rgba(255, 255, 255, 0.15);
|
|
@@ -149,7 +149,7 @@ class ToastWindow(QWidget):
|
|
|
149
149
|
border-radius: 12px;
|
|
150
150
|
font-size: 11px;
|
|
151
151
|
color: rgba(255, 255, 255, 0.7);
|
|
152
|
-
font-family:
|
|
152
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
153
153
|
}
|
|
154
154
|
QPushButton:hover {
|
|
155
155
|
background: rgba(255, 255, 255, 0.15);
|
|
@@ -173,7 +173,7 @@ class ToastWindow(QWidget):
|
|
|
173
173
|
border-radius: 12px;
|
|
174
174
|
font-size: 11px;
|
|
175
175
|
color: rgba(255, 255, 255, 0.7);
|
|
176
|
-
font-family:
|
|
176
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
177
177
|
}
|
|
178
178
|
QPushButton:hover {
|
|
179
179
|
background: rgba(255, 255, 255, 0.15);
|
|
@@ -194,7 +194,7 @@ class ToastWindow(QWidget):
|
|
|
194
194
|
border-radius: 12px;
|
|
195
195
|
font-size: 11px;
|
|
196
196
|
color: rgba(255, 255, 255, 0.7);
|
|
197
|
-
font-family:
|
|
197
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
198
198
|
}
|
|
199
199
|
QPushButton:hover {
|
|
200
200
|
background: rgba(255, 255, 255, 0.15);
|
|
@@ -260,7 +260,7 @@ class ToastWindow(QWidget):
|
|
|
260
260
|
color: rgba(255, 255, 255, 0.9);
|
|
261
261
|
background: transparent;
|
|
262
262
|
border: none;
|
|
263
|
-
font-family:
|
|
263
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
264
264
|
font-size: 11px;
|
|
265
265
|
font-weight: 500;
|
|
266
266
|
}
|
|
@@ -274,7 +274,7 @@ class ToastWindow(QWidget):
|
|
|
274
274
|
font-size: 10px;
|
|
275
275
|
font-weight: 500;
|
|
276
276
|
color: rgba(255, 255, 255, 0.8);
|
|
277
|
-
font-family:
|
|
277
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
QPushButton:hover {
|
|
@@ -295,7 +295,7 @@ class ToastWindow(QWidget):
|
|
|
295
295
|
font-size: 13px;
|
|
296
296
|
font-weight: 400;
|
|
297
297
|
color: rgba(255, 255, 255, 0.95);
|
|
298
|
-
font-family:
|
|
298
|
+
font-family: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
|
|
299
299
|
selection-background-color: rgba(34, 197, 94, 0.3);
|
|
300
300
|
line-height: 1.5;
|
|
301
301
|
}
|
|
@@ -276,7 +276,7 @@ class UIStyles:
|
|
|
276
276
|
padding: 8px;
|
|
277
277
|
background: {COLORS['surface']};
|
|
278
278
|
font-size: 13px;
|
|
279
|
-
font-family: 'SF Pro Text',
|
|
279
|
+
font-family: 'SF Pro Text', "Helvetica Neue", BlinkMacSystemFont, sans-serif;
|
|
280
280
|
}}
|
|
281
281
|
QTextEdit:focus {{
|
|
282
282
|
border-color: {COLORS['primary']};
|
|
@@ -291,7 +291,7 @@ class UIStyles:
|
|
|
291
291
|
padding: 12px 16px;
|
|
292
292
|
background: {COLORS['surface']};
|
|
293
293
|
font-size: 14px;
|
|
294
|
-
font-family: 'SF Pro Text',
|
|
294
|
+
font-family: 'SF Pro Text', "Helvetica Neue", BlinkMacSystemFont, sans-serif;
|
|
295
295
|
max-height: 120px;
|
|
296
296
|
min-height: 40px;
|
|
297
297
|
}}
|
|
@@ -103,7 +103,7 @@ class MarkdownRenderer:
|
|
|
103
103
|
"""Get base CSS styles for markdown content."""
|
|
104
104
|
return """
|
|
105
105
|
.markdown-content {
|
|
106
|
-
font-family:
|
|
106
|
+
font-family: "SF Pro Text", "Helvetica Neue", BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
107
107
|
font-size: 14px; /* Base font size */
|
|
108
108
|
line-height: 1.6;
|
|
109
109
|
color: #e2e8f0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: abstractassistant
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: A sleek (macOS) system tray application providing instant access to LLMs
|
|
5
5
|
Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
|
|
6
6
|
License: MIT
|
|
@@ -22,13 +22,14 @@ Classifier: Topic :: Desktop Environment
|
|
|
22
22
|
Requires-Python: >=3.9
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
|
-
Requires-Dist: abstractcore[all]>=2.4.
|
|
25
|
+
Requires-Dist: abstractcore[all]>=2.4.5
|
|
26
26
|
Requires-Dist: pystray>=0.19.4
|
|
27
27
|
Requires-Dist: Pillow>=10.0.0
|
|
28
28
|
Requires-Dist: PyQt5>=5.15.0
|
|
29
29
|
Requires-Dist: markdown>=3.5.0
|
|
30
30
|
Requires-Dist: pygments>=2.16.0
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: pymdown-extensions>=10.0
|
|
32
|
+
Requires-Dist: abstractvoice>=0.5.0
|
|
32
33
|
Requires-Dist: pyperclip>=1.8.2
|
|
33
34
|
Requires-Dist: plyer>=2.1.0
|
|
34
35
|
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
@@ -4,23 +4,23 @@ abstractassistant/cli.py,sha256=SQPxQCLjX-LOlhSEvG302D0AOyxlxo5QM2imxr9wxmc,4385
|
|
|
4
4
|
abstractassistant/config.py,sha256=KodfPYTpHtavJyne-h-B-r3kbEt1uusSY8GknGLtDL8,5809
|
|
5
5
|
abstractassistant/web_server.py,sha256=_pqMzy13qfim9BMBqQJQifWyX7UQXFD_sZeiu4ZBt40,12816
|
|
6
6
|
abstractassistant/core/__init__.py,sha256=TETStgToTe7QSsCZgRHDk2oSErlLJoeGN0sFg4Yx2_c,15
|
|
7
|
-
abstractassistant/core/llm_manager.py,sha256=
|
|
7
|
+
abstractassistant/core/llm_manager.py,sha256=yIvFj0pSLeC22PsaD0a3I4PjylO5Qvju8P_1e9iyXAM,11376
|
|
8
8
|
abstractassistant/core/tts_manager.py,sha256=vE5SOh16CmeWp_ml_tehmSZxj503KEjz0uCQPXpWOpQ,7819
|
|
9
9
|
abstractassistant/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
|
|
10
10
|
abstractassistant/ui/chat_bubble.py,sha256=TE6zPtQ46I9grKGAb744wHqk4yO6-und3iif8_33XGk,11357
|
|
11
|
-
abstractassistant/ui/history_dialog.py,sha256=
|
|
11
|
+
abstractassistant/ui/history_dialog.py,sha256=jLi8JV6z_yN6yXBsRAF6cPfyPjwsUB-FYlBaInQRF3U,18523
|
|
12
12
|
abstractassistant/ui/provider_manager.py,sha256=9IM-BxIs6lUlk6cDCBi7oZFMXmn4CFMlxh0s-_vhzXY,8403
|
|
13
|
-
abstractassistant/ui/qt_bubble.py,sha256=
|
|
13
|
+
abstractassistant/ui/qt_bubble.py,sha256=X9cgvV-s1V_kFQNCB9lr9ODc-raeFoYTIFYepz_uCRc,89142
|
|
14
14
|
abstractassistant/ui/toast_manager.py,sha256=1aU4DPo-J45bC61gTEctHq98ZrHIFxRfZa_9Q8KF588,13721
|
|
15
|
-
abstractassistant/ui/toast_window.py,sha256=
|
|
15
|
+
abstractassistant/ui/toast_window.py,sha256=EYGRQOzQeR9MYrfdzkxN6FH-y7CaW8Noy5kw3CpQlmw,21609
|
|
16
16
|
abstractassistant/ui/tts_state_manager.py,sha256=UF_zrfl9wf0hNHBGxevcoKxW5Dh7zXibUSVoSSjGP4o,10565
|
|
17
|
-
abstractassistant/ui/ui_styles.py,sha256=
|
|
17
|
+
abstractassistant/ui/ui_styles.py,sha256=VoGlrZI3YJzDKPv9LWz39W3XcFI4N952rkzXb8Musfw,13150
|
|
18
18
|
abstractassistant/utils/__init__.py,sha256=7Q3BxyXETkt3tm5trhuLTyL8PoECOK0QiK-0KUVAR2Q,16
|
|
19
19
|
abstractassistant/utils/icon_generator.py,sha256=MH3giercjE6Dh100EZ_8kw5WC0PVwd5F4inKfRDLp2w,10455
|
|
20
|
-
abstractassistant/utils/markdown_renderer.py,sha256=
|
|
21
|
-
abstractassistant-0.
|
|
22
|
-
abstractassistant-0.
|
|
23
|
-
abstractassistant-0.
|
|
24
|
-
abstractassistant-0.
|
|
25
|
-
abstractassistant-0.
|
|
26
|
-
abstractassistant-0.
|
|
20
|
+
abstractassistant/utils/markdown_renderer.py,sha256=sTn045glqxh5QU3LIuYp7zQpBIuwf5e3cWZzDz5i0Yw,12620
|
|
21
|
+
abstractassistant-0.2.5.dist-info/licenses/LICENSE,sha256=QUjFNAE-0yOkW9-Rle2axkpkt9H7xiZ2VbN-VeONhxc,1106
|
|
22
|
+
abstractassistant-0.2.5.dist-info/METADATA,sha256=0Zo6V7ql9bNfWcExMQSoqyNh6g1xGfwOqcYMU4bNYT4,10607
|
|
23
|
+
abstractassistant-0.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
abstractassistant-0.2.5.dist-info/entry_points.txt,sha256=hSNmsf88uDC7eDA2yeTK17IFz5MtaPZEx5_0jQJ2aAY,57
|
|
25
|
+
abstractassistant-0.2.5.dist-info/top_level.txt,sha256=qZc_LQH3CBxLq2P4B1aHayzkj8hn0euR31edkXQVzDA,18
|
|
26
|
+
abstractassistant-0.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|