lemonade-sdk 8.1.4__py3-none-any.whl → 8.2.2__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.

Potentially problematic release.


This version of lemonade-sdk might be problematic. Click here for more details.

Files changed (53) hide show
  1. lemonade/cache.py +6 -1
  2. lemonade/cli.py +47 -5
  3. lemonade/common/inference_engines.py +13 -4
  4. lemonade/common/status.py +4 -4
  5. lemonade/common/system_info.py +544 -1
  6. lemonade/profilers/agt_power.py +437 -0
  7. lemonade/profilers/hwinfo_power.py +429 -0
  8. lemonade/tools/accuracy.py +143 -48
  9. lemonade/tools/adapter.py +6 -1
  10. lemonade/tools/bench.py +26 -8
  11. lemonade/tools/flm/__init__.py +1 -0
  12. lemonade/tools/flm/utils.py +303 -0
  13. lemonade/tools/huggingface/bench.py +6 -1
  14. lemonade/tools/llamacpp/bench.py +146 -27
  15. lemonade/tools/llamacpp/load.py +30 -2
  16. lemonade/tools/llamacpp/utils.py +393 -33
  17. lemonade/tools/oga/bench.py +5 -26
  18. lemonade/tools/oga/load.py +60 -121
  19. lemonade/tools/oga/migration.py +403 -0
  20. lemonade/tools/report/table.py +76 -8
  21. lemonade/tools/server/flm.py +133 -0
  22. lemonade/tools/server/llamacpp.py +220 -553
  23. lemonade/tools/server/serve.py +684 -168
  24. lemonade/tools/server/static/js/chat.js +666 -342
  25. lemonade/tools/server/static/js/model-settings.js +24 -3
  26. lemonade/tools/server/static/js/models.js +597 -73
  27. lemonade/tools/server/static/js/shared.js +79 -14
  28. lemonade/tools/server/static/logs.html +191 -0
  29. lemonade/tools/server/static/styles.css +491 -66
  30. lemonade/tools/server/static/webapp.html +83 -31
  31. lemonade/tools/server/tray.py +158 -38
  32. lemonade/tools/server/utils/macos_tray.py +226 -0
  33. lemonade/tools/server/utils/{system_tray.py → windows_tray.py} +13 -0
  34. lemonade/tools/server/webapp.py +4 -1
  35. lemonade/tools/server/wrapped_server.py +559 -0
  36. lemonade/version.py +1 -1
  37. lemonade_install/install.py +54 -611
  38. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/METADATA +29 -72
  39. lemonade_sdk-8.2.2.dist-info/RECORD +83 -0
  40. lemonade_server/cli.py +145 -37
  41. lemonade_server/model_manager.py +521 -37
  42. lemonade_server/pydantic_models.py +28 -1
  43. lemonade_server/server_models.json +246 -92
  44. lemonade_server/settings.py +39 -39
  45. lemonade/tools/quark/__init__.py +0 -0
  46. lemonade/tools/quark/quark_load.py +0 -173
  47. lemonade/tools/quark/quark_quantize.py +0 -439
  48. lemonade_sdk-8.1.4.dist-info/RECORD +0 -77
  49. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/WHEEL +0 -0
  50. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/entry_points.txt +0 -0
  51. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/LICENSE +0 -0
  52. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/NOTICE.md +0 -0
  53. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,226 @@
1
+ import platform
2
+ import subprocess
3
+ from typing import Callable, Optional
4
+
5
+ # Check if we're on macOS and import accordingly
6
+ if platform.system() == "Darwin":
7
+ try:
8
+ import rumps
9
+
10
+ RUMPS_AVAILABLE = True
11
+ except ImportError:
12
+ RUMPS_AVAILABLE = False
13
+ print("Warning: rumps not available. Install with: pip install rumps")
14
+ else:
15
+ RUMPS_AVAILABLE = False
16
+
17
+
18
+ class MenuItem:
19
+ """
20
+ Cross-platform menu item representation.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ text: str,
26
+ callback: Optional[Callable] = None,
27
+ enabled: bool = True,
28
+ submenu=None,
29
+ checked: bool = False,
30
+ ):
31
+ self.text = text
32
+ self.callback = callback
33
+ self.enabled = enabled
34
+ self.submenu = submenu
35
+ self.checked = checked
36
+
37
+
38
+ class Menu:
39
+ """
40
+ Cross-platform menu representation.
41
+ """
42
+
43
+ SEPARATOR = "SEPARATOR"
44
+
45
+ def __init__(self, *items):
46
+ self.items = list(items)
47
+
48
+
49
+ class MacOSSystemTray:
50
+ """
51
+ macOS-specific system tray implementation using rumps.
52
+ """
53
+
54
+ def __init__(self, app_name: str, icon_path: str):
55
+ self._check_rumps_availability()
56
+
57
+ self.app_name = app_name
58
+ self.icon_path = icon_path
59
+ self.app = None
60
+ self.menu_callbacks = {}
61
+ self._menu_update_timer = None
62
+
63
+ def _check_rumps_availability(self):
64
+ """Check if rumps is available and raise error if not."""
65
+ if not RUMPS_AVAILABLE:
66
+ raise ImportError("rumps library is required for macOS tray support")
67
+
68
+ def create_menu(self):
69
+ """
70
+ Create the context menu based on current state. Override in subclass.
71
+ """
72
+ return Menu(MenuItem("Exit", self.exit_app))
73
+
74
+ def build_rumps_menu(self, menu_items):
75
+ """
76
+ Convert our menu structure to rumps menu items.
77
+ """
78
+ rumps_items = []
79
+
80
+ for item in menu_items:
81
+ if item == Menu.SEPARATOR:
82
+ rumps_items.append(rumps.separator)
83
+ elif isinstance(item, MenuItem):
84
+ if item.submenu:
85
+ # Create submenu
86
+ submenu_items = self.build_rumps_menu(item.submenu.items)
87
+ submenu = rumps.MenuItem(item.text)
88
+ for sub_item in submenu_items:
89
+ submenu.add(sub_item)
90
+ rumps_items.append(submenu)
91
+ else:
92
+ # Create regular menu item
93
+ menu_item = rumps.MenuItem(
94
+ item.text,
95
+ callback=(
96
+ self._create_callback_wrapper(item)
97
+ if item.callback
98
+ else None
99
+ ),
100
+ )
101
+
102
+ # Set enabled state
103
+ if not item.enabled:
104
+ menu_item.set_callback(None)
105
+
106
+ # Set checked state
107
+ if item.checked:
108
+ menu_item.state = 1
109
+ else:
110
+ menu_item.state = 0
111
+
112
+ rumps_items.append(menu_item)
113
+
114
+ return rumps_items
115
+
116
+ def _create_callback_wrapper(self, item):
117
+ """Create a callback wrapper that matches our interface."""
118
+
119
+ def wrapper(sender): # pylint: disable=unused-argument
120
+ if item.callback:
121
+ item.callback(None, item)
122
+
123
+ return wrapper
124
+
125
+ def show_balloon_notification(
126
+ self, title, message, timeout=5000
127
+ ): # pylint: disable=unused-argument
128
+ """
129
+ Show a notification on macOS using the Notification Center.
130
+ Falls back to console output if AppleScript fails.
131
+ """
132
+ try:
133
+ # Escape quotes in message and title for AppleScript
134
+ escaped_title = title.replace('"', '\\"')
135
+ escaped_message = message.replace('"', '\\"')
136
+ escaped_app_name = self.app_name.replace('"', '\\"')
137
+
138
+ # Use AppleScript to show notification
139
+ script = (
140
+ f'display notification "{escaped_message}" '
141
+ f'with title "{escaped_title}" subtitle "{escaped_app_name}"'
142
+ )
143
+ subprocess.run(
144
+ ["osascript", "-e", script], check=True, capture_output=True, text=True
145
+ )
146
+ except FileNotFoundError:
147
+ # osascript not available, fallback to console
148
+ print(f"[{self.app_name}] {title}: {message}")
149
+ except subprocess.CalledProcessError as e:
150
+ # AppleScript failed, fallback to console
151
+ print(f"[{self.app_name}] {title}: {message}")
152
+ print(f"Warning: Failed to show notification via AppleScript: {e}")
153
+ except Exception as e: # pylint: disable=broad-exception-caught
154
+ # Any other error, fallback to console
155
+ print(f"[{self.app_name}] {title}: {message}")
156
+ print(f"Warning: Failed to show notification: {e}")
157
+
158
+ def exit_app(self, _, __):
159
+ """Exit the application."""
160
+ if self.app:
161
+ rumps.quit_application()
162
+
163
+ def run(self):
164
+ """
165
+ Run the tray application.
166
+ """
167
+ self._check_rumps_availability()
168
+
169
+ try:
170
+ # Create the rumps app
171
+ self.app = rumps.App(self.app_name, icon=self.icon_path, quit_button=None)
172
+
173
+ # Build the initial menu
174
+ self.refresh_menu()
175
+
176
+ # Set up a timer to refresh menu periodically (every 3 seconds)
177
+ # This provides a good balance between responsiveness and performance
178
+ self._setup_menu_refresh_timer()
179
+
180
+ # Call the on_ready hook if available (for compatibility with tray.py)
181
+ if hasattr(self, "on_ready") and callable(getattr(self, "on_ready", None)):
182
+ getattr(self, "on_ready")()
183
+
184
+ # Start the app
185
+ self.app.run()
186
+ except Exception as e:
187
+ raise RuntimeError(f"Failed to start macOS tray application: {e}") from e
188
+
189
+ def refresh_menu(self):
190
+ """
191
+ Refresh the menu by rebuilding it with current state.
192
+ """
193
+ if not self.app:
194
+ return
195
+
196
+ # Clear existing menu
197
+ self.app.menu.clear()
198
+
199
+ # Build fresh menu with current state
200
+ menu = self.create_menu()
201
+ menu_items = self.build_rumps_menu(menu.items)
202
+
203
+ # Add updated menu items
204
+ for item in menu_items:
205
+ self.app.menu.add(item)
206
+
207
+ def _setup_menu_refresh_timer(self):
208
+ """
209
+ Set up a timer to periodically refresh the menu.
210
+ """
211
+ if not self.app:
212
+ return
213
+
214
+ # Create a timer that refreshes the menu every 3 seconds
215
+ @rumps.timer(3)
216
+ def refresh_menu_timer(sender): # pylint: disable=unused-argument
217
+ self.refresh_menu()
218
+
219
+ # Store reference to prevent garbage collection
220
+ self._menu_update_timer = refresh_menu_timer
221
+
222
+ def update_menu(self):
223
+ """
224
+ Update the menu by rebuilding it.
225
+ """
226
+ self.refresh_menu()
@@ -25,12 +25,14 @@ class MenuItem:
25
25
  enabled: bool = True,
26
26
  submenu=None,
27
27
  bitmap_path: Optional[str] = None,
28
+ checked: bool = False,
28
29
  ):
29
30
  self.text = text
30
31
  self.callback = callback
31
32
  self.enabled = enabled
32
33
  self.submenu = submenu
33
34
  self.bitmap_path = bitmap_path
35
+ self.checked = checked
34
36
  self.id = None # Will be set when menu is created
35
37
  self.bitmap_handle = None
36
38
 
@@ -358,9 +360,20 @@ class SystemTray:
358
360
  self.register_window_class()
359
361
  self.create_window()
360
362
 
363
+ # Set up Windows console control handler for CTRL+C
364
+ self.console_handler = self.setup_console_control_handler(self.logger)
365
+
361
366
  # Add tray icon
362
367
  self.add_tray_icon()
363
368
 
369
+ # Notify subclasses that the tray is ready (hwnd and icon created)
370
+ # Allows showing initial notifications after initialization
371
+ try:
372
+ if hasattr(self, "on_ready") and callable(getattr(self, "on_ready")):
373
+ self.on_ready()
374
+ except Exception:
375
+ pass
376
+
364
377
  # Run the message loop in the main thread
365
378
  self.message_loop()
366
379
 
@@ -1,5 +1,6 @@
1
1
  from pathlib import Path
2
2
  import json
3
+ import platform
3
4
  from fastapi.responses import HTMLResponse
4
5
  from lemonade_server.model_manager import ModelManager
5
6
 
@@ -14,10 +15,11 @@ def get_webapp_html(port=8000):
14
15
  # Use shared filter function from model_manager.py
15
16
  filtered_models = ModelManager().filter_models_by_backend(server_models)
16
17
 
17
- # Pass filtered server_models to JS
18
+ # Pass filtered server_models and platform info to JS
18
19
  server_models_js = (
19
20
  f"<script>window.SERVER_MODELS = {json.dumps(filtered_models)};</script>"
20
21
  )
22
+ platform_js = f"<script>window.PLATFORM = '{platform.system()}';</script>"
21
23
 
22
24
  # Load HTML template
23
25
  template_path = Path(__file__).parent / "static" / "webapp.html"
@@ -27,5 +29,6 @@ def get_webapp_html(port=8000):
27
29
  # Replace template variables
28
30
  html_content = html_template.replace("{{SERVER_PORT}}", str(port))
29
31
  html_content = html_content.replace("{{SERVER_MODELS_JS}}", server_models_js)
32
+ html_content = html_content.replace("{{PLATFORM_JS}}", platform_js)
30
33
 
31
34
  return HTMLResponse(content=html_content)