diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..a4c9117
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,17 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-added-large-files
+ - id: debug-statements
+ - id: check-ast
+
+ - repo: https://github.com/psf/black
+ rev: 23.3.0
+ hooks:
+ - id: black
+ language_version: python3
+ args: [--line-length=100]
diff --git a/llama_assistant/global_hotkey.py b/llama_assistant/global_hotkey.py
index 707e8ed..3c97a98 100644
--- a/llama_assistant/global_hotkey.py
+++ b/llama_assistant/global_hotkey.py
@@ -7,9 +7,7 @@ class GlobalHotkey(QObject):
def __init__(self, hotkey):
super().__init__()
- self.hotkey = keyboard.HotKey(
- keyboard.HotKey.parse(hotkey), self.on_activate
- )
+ self.hotkey = keyboard.HotKey(keyboard.HotKey.parse(hotkey), self.on_activate)
self.listener = keyboard.Listener(
on_press=self.for_canonical(self.hotkey.press),
on_release=self.for_canonical(self.hotkey.release),
diff --git a/llama_assistant/icons.py b/llama_assistant/icons.py
new file mode 100644
index 0000000..17fa01b
--- /dev/null
+++ b/llama_assistant/icons.py
@@ -0,0 +1,32 @@
+from PyQt6.QtCore import Qt
+from PyQt6.QtGui import QIcon
+from PyQt6.QtSvg import QSvgRenderer
+from PyQt6.QtCore import QByteArray
+from PyQt6.QtGui import QPixmap, QPainter
+
+# Updated SVG icons with white fill and stroke
+copy_icon_svg = """
+
+"""
+
+clear_icon_svg = """
+
+"""
+
+
+def create_icon_from_svg(svg_string):
+ svg_bytes = QByteArray(svg_string.encode("utf-8"))
+ renderer = QSvgRenderer(svg_bytes)
+ pixmap = QPixmap(24, 24) # Size of the icon
+ pixmap.fill(Qt.GlobalColor.transparent)
+ painter = QPainter(pixmap)
+ renderer.render(painter)
+ painter.end()
+ return QIcon(pixmap)
diff --git a/llama_assistant/llama_assistant.py b/llama_assistant/llama_assistant.py
index 80ce1d9..6ca83e9 100644
--- a/llama_assistant/llama_assistant.py
+++ b/llama_assistant/llama_assistant.py
@@ -44,6 +44,11 @@
from llama_assistant.speech_recognition import SpeechRecognitionThread
from llama_assistant.utils import image_to_base64_data_uri
from llama_assistant.model_handler import handler as model_handler
+from llama_assistant.icons import (
+ create_icon_from_svg,
+ copy_icon_svg,
+ clear_icon_svg,
+)
class LlamaAssistant(QMainWindow):
@@ -106,10 +111,7 @@ def save_settings(self):
def init_ui(self):
self.setWindowTitle("AI Assistant")
self.setFixedSize(600, 200)
- self.setWindowFlags(
- Qt.WindowType.FramelessWindowHint
- | Qt.WindowType.WindowStaysOnTopHint
- )
+ self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
central_widget = QWidget(self)
@@ -147,9 +149,7 @@ def init_ui(self):
top_layout.addWidget(self.input_field)
# Load the mic icon from resources
- with resources.path(
- "llama_assistant.resources", "mic_icon.png"
- ) as path:
+ with resources.path("llama_assistant.resources", "mic_icon.png") as path:
mic_icon = QIcon(str(path))
self.mic_button = QPushButton(self)
@@ -206,12 +206,33 @@ def init_ui(self):
# Add new buttons to layout
result_layout = QHBoxLayout()
result_layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
+
+ # Create and set up the Copy Result button
self.copy_button = QPushButton("Copy Result", self)
+ self.copy_button.setIcon(create_icon_from_svg(copy_icon_svg))
+ self.copy_button.setIconSize(QSize(18, 18))
+ self.copy_button.setStyleSheet(
+ """
+ QPushButton { padding-left: 4px; padding-right: 8px; }
+ QPushButton::icon { margin-right: 4px; }
+ """
+ )
self.copy_button.clicked.connect(self.copy_result)
self.copy_button.hide()
+
+ # Create and set up the Clear button
self.clear_button = QPushButton("Clear", self)
+ self.clear_button.setIcon(create_icon_from_svg(clear_icon_svg))
+ self.clear_button.setIconSize(QSize(18, 18))
+ self.clear_button.setStyleSheet(
+ """
+ QPushButton { padding-left: 4px; padding-right: 8px; }
+ QPushButton::icon { margin-right: 4px; }
+ """
+ )
self.clear_button.clicked.connect(self.clear_chat)
self.clear_button.hide()
+
result_layout.addWidget(self.copy_button)
result_layout.addWidget(self.clear_button)
@@ -231,9 +252,7 @@ def init_ui(self):
# Create a scroll area for the chat box
self.scroll_area = QScrollArea(self)
self.scroll_area.setWidgetResizable(True)
- self.scroll_area.setHorizontalScrollBarPolicy(
- Qt.ScrollBarPolicy.ScrollBarAlwaysOff
- )
+ self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.scroll_area.setStyleSheet(
"""
QScrollArea {
@@ -285,7 +304,6 @@ def on_task_button_clicked(self):
def update_styles(self):
opacity = self.settings.get("transparency", 90) / 100
base_style = f"""
- background-color: rgba{QColor(self.settings["color"]).getRgb()[:3] + (opacity,)};
border: none;
border-radius: 20px;
color: white;
@@ -295,16 +313,23 @@ def update_styles(self):
self.input_field.setStyleSheet(
f"""
QPlainTextEdit {{
+ background-color: rgba{QColor(self.settings["color"]).getRgb()[:3] + (opacity,)};
{base_style}
}}
"""
)
- self.chat_box.setStyleSheet(f"QTextBrowser {{ {base_style} }}")
+ self.chat_box.setStyleSheet(
+ f"""QTextBrowser {{ {base_style}
+ background-color: rgba{QColor(self.settings["color"]).lighter(120).getRgb()[:3] + (opacity,)};
+ border-radius: 5px;
+ }}"""
+ )
button_style = f"""
QPushButton {{
{base_style}
padding: 2.5px 5px;
border-radius: 5px;
+ background-color: rgba{QColor(self.settings["color"]).getRgb()[:3] + (opacity,)};
}}
QPushButton:hover {{
background-color: rgba{QColor(self.settings["color"]).lighter(120).getRgb()[:3] + (opacity,)};
@@ -324,9 +349,10 @@ def update_styles(self):
{base_style}
padding: 2.5px 5px;
border-radius: 5px;
+ background-color: rgba{QColor(self.settings["color"]).lighter(120).getRgb()[:3] + (opacity,)};
}}
QPushButton:hover {{
- background-color: rgba(200, 200, 200, 0.8);
+ background-color: rgba{QColor(self.settings["color"]).lighter(150).getRgb()[:3] + (opacity,)};
}}
"""
for button in [self.copy_button, self.clear_button]:
@@ -382,9 +408,7 @@ def toggle_visibility(self):
def on_submit(self):
message = self.input_field.toPlainText()
self.input_field.clear()
- self.loading_animation.move(
- self.width() // 2 - 25, self.height() // 2 - 25
- )
+ self.loading_animation.move(self.width() // 2 - 25, self.height() // 2 - 25)
self.loading_animation.start_animation()
if self.dropped_image:
@@ -412,9 +436,7 @@ def process_text(self, message, task="chat"):
self.last_response = response
self.chat_box.append(f"You: {message}")
- self.chat_box.append(
- f"AI ({task}): {markdown.markdown(response)}"
- )
+ self.chat_box.append(f"AI ({task}): {markdown.markdown(response)}")
self.loading_animation.stop_animation()
self.show_chat_box()
@@ -425,9 +447,7 @@ def process_image_with_prompt(self, image_path, prompt):
self.chat_box.append(f"You: [Uploaded an image: {image_path}]")
self.chat_box.append(f"You: {prompt}")
self.chat_box.append(
- f"AI: {markdown.markdown(response)}"
- if response
- else "No response"
+ f"AI: {markdown.markdown(response)}" if response else "No response"
)
self.loading_animation.stop_animation()
self.show_chat_box()
@@ -438,9 +458,7 @@ def show_chat_box(self):
self.copy_button.show()
self.clear_button.show()
self.setFixedHeight(600) # Increase this value if needed
- self.chat_box.verticalScrollBar().setValue(
- self.chat_box.verticalScrollBar().maximum()
- )
+ self.chat_box.verticalScrollBar().setValue(self.chat_box.verticalScrollBar().maximum())
def copy_result(self):
self.hide()
@@ -451,6 +469,7 @@ def copy_result(self):
def clear_chat(self):
self.chat_box.clear()
self.last_response = ""
+ self.scroll_area.hide()
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
@@ -461,13 +480,9 @@ def dragEnterEvent(self, event: QDragEnterEvent):
def dropEvent(self, event: QDropEvent):
files = [u.toLocalFile() for u in event.mimeData().urls()]
for file_path in files:
- if file_path.lower().endswith(
- (".png", ".jpg", ".jpeg", ".gif", ".bmp")
- ):
+ if file_path.lower().endswith((".png", ".jpg", ".jpeg", ".gif", ".bmp")):
self.dropped_image = file_path
- self.input_field.setPlaceholderText(
- "Enter a prompt for the image..."
- )
+ self.input_field.setPlaceholderText("Enter a prompt for the image...")
self.show_image_thumbnail(file_path)
break
@@ -531,9 +546,7 @@ def show_image_thumbnail(self, image_path):
# Add new image to layout
self.image_layout.addWidget(self.image_label)
- self.setFixedHeight(
- self.height() + 110
- ) # Increase height to accommodate larger image
+ self.setFixedHeight(self.height() + 110) # Increase height to accommodate larger image
def remove_image_thumbnail(self):
if self.image_label:
@@ -541,9 +554,7 @@ def remove_image_thumbnail(self):
self.image_label = None
self.dropped_image = None
self.input_field.setPlaceholderText("Ask me anything...")
- self.setFixedHeight(
- self.height() - 110
- ) # Decrease height after removing image
+ self.setFixedHeight(self.height() - 110) # Decrease height after removing image
def mousePressEvent(self, event):
self.oldPos = event.globalPosition().toPoint()
diff --git a/llama_assistant/loading_animation.py b/llama_assistant/loading_animation.py
index 5f93a1f..6832fa8 100644
--- a/llama_assistant/loading_animation.py
+++ b/llama_assistant/loading_animation.py
@@ -56,9 +56,7 @@ def paintEvent(self, event):
painter.setBrush(color)
painter.setPen(Qt.PenStyle.NoPen)
- painter.drawEllipse(
- QPointF(x, y), self.dot_radius, self.dot_radius
- )
+ painter.drawEllipse(QPointF(x, y), self.dot_radius, self.dot_radius)
@property
def rotation(self):
diff --git a/llama_assistant/model_handler.py b/llama_assistant/model_handler.py
index 475615b..757009d 100644
--- a/llama_assistant/model_handler.py
+++ b/llama_assistant/model_handler.py
@@ -39,16 +39,12 @@ def add_supported_model(self, model: Model):
self.supported_models.append(model)
def remove_supported_model(self, model_id: str):
- self.supported_models = [
- m for m in self.supported_models if m.model_id != model_id
- ]
+ self.supported_models = [m for m in self.supported_models if m.model_id != model_id]
if model_id in self.loaded_models:
self.unload_model(model_id)
def load_model(self, model_id: str) -> Optional[Dict]:
- model = next(
- (m for m in self.supported_models if m.model_id == model_id), None
- )
+ model = next((m for m in self.supported_models if m.model_id == model_id), None)
if not model:
print(f"Model with ID {model_id} not found.")
return None
@@ -123,9 +119,7 @@ def chat_completion(
]
)
else:
- response = model.create_chat_completion(
- messages=[{"role": "user", "content": message}]
- )
+ response = model.create_chat_completion(messages=[{"role": "user", "content": message}])
return response["choices"][0]["message"]["content"]
@@ -181,9 +175,7 @@ def _schedule_unload(self, model_id: str):
# Use image model
image_url = "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
- result = handler.chat_completion(
- "moondream", "What's in this image?", image=image_url
- )
+ result = handler.chat_completion("moondream", "What's in this image?", image=image_url)
print(result)
# Use local model
diff --git a/llama_assistant/setting_dialog.py b/llama_assistant/setting_dialog.py
index e351680..2220ffd 100644
--- a/llama_assistant/setting_dialog.py
+++ b/llama_assistant/setting_dialog.py
@@ -40,9 +40,7 @@ def __init__(self, parent=None):
self.ai_model_combo.addItems(["Llama 1B + Moondream2"])
self.layout.addRow("AI Model:", self.ai_model_combo)
- self.label = QLabel(
- "Note: Changing AI model will be supported in the future."
- )
+ self.label = QLabel("Note: Changing AI model will be supported in the future.")
self.layout.addRow(self.label)
self.save_button = QPushButton("Save")
@@ -66,13 +64,9 @@ def load_settings(self):
if settings_file.exists():
with open(settings_file, "r") as f:
settings = json.load(f)
- self.shortcut_recorder.setText(
- settings.get("shortcut", "++")
- )
+ self.shortcut_recorder.setText(settings.get("shortcut", "++"))
self.color = QColor(settings.get("color", "#1E1E1E"))
- self.transparency_slider.setValue(
- int(settings.get("transparency", 90))
- )
+ self.transparency_slider.setValue(int(settings.get("transparency", 90)))
# self.ai_model_combo.setCurrentText(
# settings.get("ai_model", "Llama 1B")
# ) # TODO: Implement this feature
diff --git a/llama_assistant/speech_recognition.py b/llama_assistant/speech_recognition.py
index 4573ece..2a0c0c0 100644
--- a/llama_assistant/speech_recognition.py
+++ b/llama_assistant/speech_recognition.py
@@ -17,9 +17,7 @@ def run(self):
self.recognizer.adjust_for_ambient_noise(source)
while not self.stop_listening:
try:
- audio = self.recognizer.listen(
- source, timeout=1, phrase_time_limit=10
- )
+ audio = self.recognizer.listen(source, timeout=1, phrase_time_limit=10)
text = self.recognizer.recognize_google(audio)
self.finished.emit(text)
except sr.WaitTimeoutError:
diff --git a/pyproject.toml b/pyproject.toml
index 13c8d6f..0e87b97 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,4 +51,47 @@ include = ["llama_assistant*"]
exclude = ["tests*"]
[tool.setuptools.package-data]
-"llama_assistant.resources" = ["*.png"]
\ No newline at end of file
+"llama_assistant.resources" = ["*.png"]
+
+
+[tool.black]
+line-length = 100
+target-version = ['py37']
+include = '\.pyi?$'
+extend-exclude = '''
+/(
+ # directories
+ \.eggs
+ | \.git
+ | \.hg
+ | \.mypy_cache
+ | \.tox
+ | \.venv
+ | build
+ | dist
+)/
+'''
+
+[tool.pylint.master]
+ignore-patterns = ["test_.*?py"]
+
+[tool.pylint.format]
+max-line-length = 100
+
+[tool.pylint.messages_control]
+disable = [
+ "C0114", # missing-module-docstring
+ "C0116", # missing-function-docstring
+ "C0103", # invalid-name
+ "W0611", # unused-import
+ "W0612", # unused-variable
+ "W0613", # unused-argument
+ "W0621", # redefined-outer-name
+ "W0622", # redefined-builtin
+ "W0703", # broad-except
+ "R0801", # duplicate-code
+ "R0902", # too-many-instance-attributes
+ "R0903", # too-few-public-methods
+ "R0904", # too-many-public-methods
+ "R0913", # too-many-arguments
+]