Skip to content

Commit 6efd499

Browse files
committed
feat: tray for showing and hiding kratt.
1 parent a0ba7da commit 6efd499

5 files changed

Lines changed: 72 additions & 15 deletions

File tree

kratt/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
- Use code blocks with language identifiers.
3535
""".strip()
3636

37-
# Global Hotkey (L Control + R Alt)
38-
HOTKEY = {keyboard.Key.ctrl_l, keyboard.Key.alt_r}
37+
# Global Hotkey
38+
HOTKEY = {keyboard.Key.ctrl_l, keyboard.Key.shift_l}
3939

4040
# RAG Settings
4141
RAG_CHUNK_SIZE = 500
@@ -90,4 +90,4 @@ def save_settings(settings: dict) -> None:
9090
with open(SETTINGS_FILE, "w", encoding="utf-8") as f:
9191
json.dump(settings, f, indent=4)
9292
except IOError as e:
93-
print(f"Error saving settings: {e}")
93+
print(f"Error saving settings: {e}")

kratt/core/hotkey_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,4 @@ def stop(self) -> None:
9393
if self._hotkey_id is not None:
9494
keyboard.remove_hotkey(self._hotkey_id)
9595
except Exception:
96-
pass
96+
pass

kratt/core/web_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,4 @@ def scrape_urls(self, urls: list[str]) -> dict[str, str]:
327327
except Exception as e:
328328
print(f"Playwright critical error: {e}")
329329

330-
return self.results
330+
return self.results

kratt/core/worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,4 @@ def _emit_completion(self, start_time: float) -> None:
271271
start_time: Timestamp when generation began.
272272
"""
273273
duration = time.time() - start_time
274-
self.finished.emit(duration, self.token_count)
274+
self.finished.emit(duration, self.token_count)

kratt/ui/main_window.py

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import os
99
from pathlib import Path
10-
from PySide6.QtCore import Qt, QPoint, Signal, QTimer, QSize
10+
from PySide6.QtCore import Qt, Signal, QTimer, QSize
1111
from PySide6.QtGui import QColor, QIcon
12-
from PySide6.QtGui import QPixmap, QPainter
12+
from PySide6.QtGui import QPixmap, QPainter, QFont
1313
from PySide6.QtWidgets import (
1414
QWidget,
1515
QVBoxLayout,
@@ -22,8 +22,9 @@
2222
QApplication,
2323
QGraphicsDropShadowEffect,
2424
QFileDialog,
25+
QSystemTrayIcon,
26+
QMenu,
2527
)
26-
2728
from kratt.config import (
2829
load_settings,
2930
save_settings,
@@ -38,7 +39,8 @@ class MainWindow(QWidget):
3839
Main draggable, frameless chat window.
3940
4041
Manages user input, chat history, worker thread execution,
41-
and UI state during message generation.
42+
and UI state during message generation. Supports system tray
43+
integration for minimized state.
4244
"""
4345

4446
toggle_signal = Signal()
@@ -59,6 +61,7 @@ def __init__(self) -> None:
5961
self.current_model_used = ""
6062

6163
self._setup_ui()
64+
self._setup_tray()
6265
self._setup_hotkey()
6366
self.new_chat()
6467

@@ -96,6 +99,63 @@ def _setup_ui(self) -> None:
9699
self._setup_input_area()
97100
self._center_window()
98101

102+
def _setup_tray(self) -> None:
103+
"""Set up system tray icon with context menu."""
104+
self.tray_icon = QSystemTrayIcon(self)
105+
106+
# Create tray menu
107+
tray_menu = QMenu()
108+
109+
action_show = tray_menu.addAction("Show")
110+
action_show.triggered.connect(self.show_window)
111+
112+
action_hide = tray_menu.addAction("Hide")
113+
action_hide.triggered.connect(self.hide)
114+
115+
tray_menu.addSeparator()
116+
117+
action_quit = tray_menu.addAction("Quit")
118+
action_quit.triggered.connect(QApplication.quit)
119+
120+
self.tray_icon.setContextMenu(tray_menu)
121+
122+
self._create_tray_icon_pixmap()
123+
124+
self.tray_icon.activated.connect(self._on_tray_icon_activated)
125+
126+
self.tray_icon.show()
127+
128+
def _create_tray_icon_pixmap(self) -> None:
129+
"""Create a simple tray icon pixmap with 'K' letter."""
130+
pixmap = QPixmap(64, 64)
131+
pixmap.fill(QColor(230, 126, 34, 255))
132+
133+
painter = QPainter(pixmap)
134+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
135+
136+
font = QFont("Sans Serif", 32, QFont.Weight.Bold)
137+
painter.setFont(font)
138+
painter.setPen(QColor(255, 255, 255))
139+
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "K")
140+
141+
painter.end()
142+
143+
self.tray_icon.setIcon(QIcon(pixmap))
144+
145+
def _on_tray_icon_activated(self, reason) -> None:
146+
"""Handle tray icon activation (click)."""
147+
if reason in (
148+
QSystemTrayIcon.ActivationReason.Trigger,
149+
QSystemTrayIcon.ActivationReason.DoubleClick,
150+
):
151+
self.show_window()
152+
153+
def show_window(self) -> None:
154+
"""Show the window and bring it to focus."""
155+
self.show()
156+
self.activateWindow()
157+
self.txt_input.setFocus()
158+
99159
def _setup_header(self) -> None:
100160
"""Set up the top bar with title, new chat, settings, and close buttons."""
101161
self.header = QFrame()
@@ -125,7 +185,7 @@ def _setup_header(self) -> None:
125185
btn_close.setObjectName("CloseBtn")
126186
btn_close.setFixedSize(28, 28)
127187
btn_close.setCursor(Qt.CursorShape.PointingHandCursor)
128-
btn_close.clicked.connect(self.close)
188+
btn_close.clicked.connect(self.toggle_visibility)
129189

130190
self.header_layout.addWidget(title)
131191
self.header_layout.addStretch()
@@ -146,7 +206,6 @@ def _setup_chat_area(self) -> None:
146206

147207
self.chat_widget = QWidget()
148208
self.chat_widget.setObjectName("ChatWidget")
149-
# Important: Allow the chat_widget to be styled via CSS background-color rules
150209
self.chat_widget.setAttribute(Qt.WidgetAttribute.WA_StyledBackground)
151210

152211
self.chat_layout = QVBoxLayout()
@@ -500,6 +559,4 @@ def toggle_visibility(self) -> None:
500559
if self.isVisible():
501560
self.hide()
502561
else:
503-
self.show()
504-
self.activateWindow()
505-
self.txt_input.setFocus()
562+
self.show_window()

0 commit comments

Comments
 (0)