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
|
@@ -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;
|