abstractassistant 0.2.0__py3-none-any.whl → 0.2.6__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/create_app_bundle.py +48 -0
- 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.2.0.dist-info → abstractassistant-0.2.6.dist-info}/METADATA +28 -5
- {abstractassistant-0.2.0.dist-info → abstractassistant-0.2.6.dist-info}/RECORD +14 -12
- abstractassistant-0.2.6.dist-info/entry_points.txt +3 -0
- {abstractassistant-0.2.0.dist-info → abstractassistant-0.2.6.dist-info}/top_level.txt +1 -0
- setup_macos_app.py +269 -0
- abstractassistant-0.2.0.dist-info/entry_points.txt +0 -2
- {abstractassistant-0.2.0.dist-info → abstractassistant-0.2.6.dist-info}/WHEEL +0 -0
- {abstractassistant-0.2.0.dist-info → abstractassistant-0.2.6.dist-info}/licenses/LICENSE +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
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Create macOS App Bundle for AbstractAssistant.
|
|
4
|
+
|
|
5
|
+
This script can be run after installation to create a macOS app bundle
|
|
6
|
+
that allows launching AbstractAssistant from the Dock.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
"""Create macOS app bundle for AbstractAssistant."""
|
|
15
|
+
try:
|
|
16
|
+
# Import the app bundle generator
|
|
17
|
+
from abstractassistant.setup_macos_app import MacOSAppBundleGenerator
|
|
18
|
+
|
|
19
|
+
# Find the package directory
|
|
20
|
+
import abstractassistant
|
|
21
|
+
package_dir = Path(abstractassistant.__file__).parent
|
|
22
|
+
|
|
23
|
+
# Create the generator and build the app bundle
|
|
24
|
+
generator = MacOSAppBundleGenerator(package_dir)
|
|
25
|
+
|
|
26
|
+
print("🍎 Creating macOS app bundle for AbstractAssistant...")
|
|
27
|
+
success = generator.generate_app_bundle()
|
|
28
|
+
|
|
29
|
+
if success:
|
|
30
|
+
print("\n🎉 Success!")
|
|
31
|
+
print(" AbstractAssistant is now available in your Applications folder")
|
|
32
|
+
print(" You can launch it from the Dock or Spotlight!")
|
|
33
|
+
return 0
|
|
34
|
+
else:
|
|
35
|
+
print("\n❌ Failed to create app bundle")
|
|
36
|
+
return 1
|
|
37
|
+
|
|
38
|
+
except ImportError as e:
|
|
39
|
+
print(f"❌ Error: {e}")
|
|
40
|
+
print(" Make sure AbstractAssistant is properly installed")
|
|
41
|
+
return 1
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"❌ Unexpected error: {e}")
|
|
44
|
+
return 1
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
sys.exit(main())
|
|
@@ -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:
|