Skip to content

Commit 5124a4d

Browse files
committed
refactor: ♻️ update 'src' files
1 parent d93db95 commit 5124a4d

File tree

3 files changed

+38
-163
lines changed

3 files changed

+38
-163
lines changed

gramma.py

Lines changed: 23 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,167 +1,29 @@
11
import os
2-
import time
3-
import asyncio
4-
import pyperclip
5-
from dotenv import load_dotenv
6-
from groq import AsyncGroq
7-
import pystray
8-
from pystray import MenuItem as item
9-
from PIL import Image
10-
from plyer import notification
112
import threading
12-
from typing import Dict
13-
from commands import commands
14-
import sys
15-
from src.config import (
16-
APP_NAME,
17-
APP_ICON,
18-
TRAY_ICON,
19-
NOTIFICATION_TIMEOUT_SHORT,
20-
NOTIFICATION_TIMEOUT_LONG,
21-
)
22-
23-
if sys.platform.startswith("win"):
24-
import win32event
25-
import win32api
26-
import winerror
27-
else:
28-
import fcntl
29-
30-
31-
class ClipboardListener:
32-
def __init__(self):
33-
self.load_env_vars()
34-
self.last_called = 0
35-
self.client = AsyncGroq(api_key=self.GROQ_API_KEY)
36-
self.commands = self.load_commands()
37-
self.ensure_single_instance()
38-
39-
def load_env_vars(self) -> None:
40-
load_dotenv()
41-
self.GROQ_API_KEY = os.getenv("GROQ_API_KEY")
42-
self.GROQ_MODEL = os.getenv("GROQ_MODEL")
43-
44-
def load_commands(self) -> Dict[str, str]:
45-
notification.notify(
46-
title=APP_NAME,
47-
message="Gramma is now running in the background!",
48-
app_name=APP_NAME,
49-
app_icon=APP_ICON,
50-
timeout=NOTIFICATION_TIMEOUT_LONG,
51-
)
52-
return commands
53-
54-
async def process_command(self, command: str, user_msg: str) -> None:
55-
if time.time() - self.last_called < 4:
56-
self.notify(
57-
"Rate Limit",
58-
"Please wait a few seconds before trying again.",
59-
NOTIFICATION_TIMEOUT_LONG,
60-
)
61-
return
62-
63-
prompt = self.commands.get(f"PROMPT_{command.upper()}")
64-
if not prompt:
65-
return
66-
67-
try:
68-
completion = await self.client.chat.completions.create(
69-
messages=[
70-
{"role": "system", "content": prompt},
71-
{"role": "user", "content": user_msg},
72-
],
73-
model=self.GROQ_MODEL,
74-
temperature=0.5,
75-
max_tokens=1024,
76-
top_p=1,
77-
stop=None,
78-
stream=False,
79-
)
80-
81-
response_msg = completion.choices[0].message.content
82-
pyperclip.copy(response_msg)
83-
self.notify(
84-
"Done!",
85-
"Text processed and copied to clipboard.",
86-
NOTIFICATION_TIMEOUT_SHORT,
87-
)
88-
self.last_called = time.time()
89-
except Exception as e:
90-
self.notify("Error", f"An error occurred: {e}", NOTIFICATION_TIMEOUT_LONG)
91-
92-
def process_command_sync(self, command: str, user_msg: str) -> None:
93-
asyncio.run(self.process_command(command, user_msg))
94-
95-
def notify(self, title: str, message: str, timeout: int) -> None:
96-
notification.notify(
97-
title=title,
98-
message=message,
99-
app_name=APP_NAME,
100-
app_icon=APP_ICON,
101-
timeout=timeout,
102-
)
103-
104-
def monitor_clipboard(self) -> None:
105-
recent_value = pyperclip.paste()
106-
while True:
107-
time.sleep(0.1)
108-
tmp_value = pyperclip.paste()
109-
if tmp_value != recent_value:
110-
recent_value = tmp_value
111-
for prefix in self.commands:
112-
if tmp_value.startswith(f"!{prefix.lower()[7:]}"):
113-
command = tmp_value.split(" ")[0][1:]
114-
user_msg = tmp_value[len(command) + 2 :].strip()
115-
self.process_command_sync(command, user_msg)
116-
117-
def setup_tray_icon(self) -> None:
118-
image = Image.open(TRAY_ICON)
119-
icon = pystray.Icon(
120-
APP_NAME,
121-
image,
122-
APP_NAME,
123-
menu=pystray.Menu(item("Exit", self.exit_program)),
124-
)
125-
icon.run()
126-
127-
def exit_program(self, icon) -> None:
128-
icon.stop()
3+
from src.data import SharedData
4+
from src.core.clipboard_listener import ClipboardListener
5+
from src.core.tray_icon import TrayIcon
6+
from src.managers.instance_manager import InstanceManager
7+
8+
9+
def main():
10+
instance_manager = InstanceManager()
11+
shared_data = SharedData()
12+
listener = ClipboardListener(shared_data)
13+
TrayIcon(listener)
14+
15+
try:
16+
listener_thread = threading.Thread(target=listener.monitor_clipboard)
17+
listener_thread.start()
18+
except KeyboardInterrupt:
19+
listener.stop_monitoring()
20+
except Exception as e:
21+
print(f"An error occurred: {e}")
22+
listener.stop_monitoring()
23+
finally:
24+
instance_manager.cleanup()
12925
os._exit(0)
13026

131-
def ensure_single_instance(self) -> None:
132-
if sys.platform.startswith("win"):
133-
self.handle_mutex()
134-
else:
135-
self.handle_lock_file()
136-
137-
def handle_mutex(self) -> None:
138-
self.mutex = None
139-
try:
140-
self.mutex = win32event.CreateMutex(None, False, APP_NAME)
141-
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
142-
self.notify_instance_already_running()
143-
except Exception as e:
144-
print(f"Error creating mutex: {e}")
145-
os._exit(1)
146-
147-
def handle_lock_file(self) -> None:
148-
lock_file = "/tmp/clipboardlistener.lock"
149-
try:
150-
self.lock_fd = os.open(lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
151-
except OSError:
152-
self.notify_instance_already_running()
153-
os._exit(1)
154-
else:
155-
fcntl.flock(self.lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
156-
15727

15828
if __name__ == "__main__":
159-
listener = ClipboardListener()
160-
threading.Thread(target=listener.setup_tray_icon).start()
161-
listener.monitor_clipboard()
162-
163-
if sys.platform.startswith("win"):
164-
win32event.ReleaseMutex(listener.mutex)
165-
else:
166-
os.close(listener.lock_fd)
167-
os.unlink("/tmp/clipboardlistener.lock")
29+
main()

src/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
APP_NAME = "Gramma"
22
APP_ICON = "src/images/app_icon.ico"
33
TRAY_ICON = "src/images/tray_icon.png"
4-
NOTIFICATION_TIMEOUT_SHORT = 2
5-
NOTIFICATION_TIMEOUT_LONG = 4
4+
5+
NOTIFICATION_TIMEOUT_SHORT = 3
6+
NOTIFICATION_TIMEOUT_LONG = 5

src/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from plyer import notification
2+
from src.config import APP_NAME, APP_ICON
3+
4+
5+
def notify(title: str, message: str, timeout: int) -> None:
6+
notification.notify(
7+
title=title,
8+
message=message,
9+
app_name=APP_NAME,
10+
app_icon=APP_ICON,
11+
timeout=timeout,
12+
)

0 commit comments

Comments
 (0)