abstractassistant 0.2.6__py3-none-any.whl → 0.3.0__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.
@@ -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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
110
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
131
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
152
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
176
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
197
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
263
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
277
+ font-family: "Helvetica Neue", "Helvetica", Arial, 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: "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
298
+ font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
299
299
  selection-background-color: rgba(34, 197, 94, 0.3);
300
300
  line-height: 1.5;
301
301
  }
@@ -506,11 +506,10 @@ class ToastManager:
506
506
  self.debug = debug
507
507
  self.current_toast: Optional[ToastWindow] = None
508
508
 
509
- # Ensure QApplication exists
510
- if not QApplication.instance():
511
- self.app = QApplication(sys.argv)
512
- else:
513
- self.app = QApplication.instance()
509
+ # Always use existing QApplication instance (never create a new one)
510
+ self.app = QApplication.instance()
511
+ if not self.app:
512
+ raise RuntimeError("No QApplication instance found. This should be created by the main app first.")
514
513
 
515
514
  if self.debug:
516
515
  print("✅ ToastManager initialized")
@@ -546,10 +545,10 @@ _active_toasts = []
546
545
  def show_toast_notification(message: str, debug: bool = False, voice_manager=None):
547
546
  """Standalone function to show a toast notification - stays visible until manually closed."""
548
547
  try:
549
- # Create a minimal QApplication if none exists
548
+ # Always use existing QApplication instance (never create a new one)
550
549
  app = QApplication.instance()
551
550
  if not app:
552
- app = QApplication(sys.argv)
551
+ raise RuntimeError("No QApplication instance found. This should be created by the main app first.")
553
552
 
554
553
  # Create and show toast (no auto-hide)
555
554
  toast = ToastWindow(message, debug=debug, voice_manager=voice_manager)
@@ -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', "Helvetica Neue", BlinkMacSystemFont, sans-serif;
279
+ font-family: 'Helvetica Neue', "Helvetica", Arial, 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', "Helvetica Neue", BlinkMacSystemFont, sans-serif;
294
+ font-family: 'Helvetica Neue', "Helvetica", Arial, sans-serif;
295
295
  max-height: 120px;
296
296
  min-height: 40px;
297
297
  }}
@@ -49,7 +49,7 @@ class IconGenerator:
49
49
  return img
50
50
 
51
51
  def _draw_gradient_circle(self, draw: ImageDraw.Draw, center: int, radius: int, color_scheme: str = "blue", animated: bool = False):
52
- """Draw a gradient circle background with color options."""
52
+
53
53
  # Color schemes - more vibrant and visible
54
54
  colors = {
55
55
  "blue": (64, 150, 255), # Brighter blue
@@ -87,172 +87,302 @@ class IconGenerator:
87
87
  intensity = 0.2
88
88
 
89
89
  elif color_scheme == "green":
90
- # Ready state: much more visible heartbeat
91
90
  base_color = colors["green"]
92
91
  if animated:
93
92
  import time
94
- # More noticeable pulse every 1.5 seconds
95
- pulse_cycle = (time.time() * 0.67) % 1 # Faster, 1.5-second cycle
96
- if pulse_cycle < 0.2: # Strong beat
97
- intensity = 2.0 # Much brighter
98
- elif pulse_cycle < 0.4: # Fade down
99
- intensity = 1.2
100
- else: # Rest period - much dimmer
101
- intensity = 0.4 # Much darker for contrast
93
+ # Gentle breathing for ready state
94
+ intensity = 0.8 + 0.4 * math.sin(time.time() * 0.5) # Slower breathing
102
95
  else:
103
96
  intensity = 1.0
104
97
  else:
105
98
  base_color = colors.get(color_scheme, colors["blue"])
106
99
  intensity = 1.0
107
- if animated:
108
- import time
109
- pulse = abs(math.sin(time.time() * 2)) * 0.2 + 0.9
110
- intensity *= pulse
111
-
112
- # Enhanced gradient effect - much more visible from center to edge
113
- for i in range(radius):
114
- # Create stronger gradient with more dramatic falloff
115
- gradient_factor = (1 - (i / radius) ** 0.5) # Square root for more dramatic gradient
116
- alpha = int(255 * gradient_factor * intensity)
117
-
118
- # Ensure alpha is within bounds
119
- alpha = max(0, min(255, alpha))
120
-
121
- # Create more vibrant color with better gradient
122
- color = (*base_color, alpha)
123
- draw.ellipse(
124
- [center - radius + i, center - radius + i,
125
- center + radius - i, center + radius - i],
126
- fill=color
127
- )
128
-
129
- # Add bright center core for more dramatic effect
130
- core_radius = max(1, radius // 4)
131
- core_alpha = int(255 * intensity * 1.2) # Extra bright center
132
- core_alpha = max(0, min(255, core_alpha))
133
- core_color = (*base_color, core_alpha)
134
- draw.ellipse(
135
- [center - core_radius, center - core_radius,
136
- center + core_radius, center + core_radius],
137
- fill=core_color
138
- )
100
+
101
+ # Apply intensity to color
102
+ final_color = tuple(int(c * intensity) for c in base_color)
103
+
104
+ # Draw main circle with gradient effect
105
+ for i in range(radius, 0, -2):
106
+ alpha = int(255 * (i / radius) * 0.8)
107
+ circle_color = final_color + (alpha,)
108
+ draw.ellipse([center-i, center-i, center+i, center+i], fill=circle_color)
139
109
 
140
110
  def _draw_neural_nodes(self, draw: ImageDraw.Draw, center: int, radius: int, animated: bool = False):
141
- """Draw neural network-style nodes."""
142
- # Animation effect for nodes - more visible
143
- node_alpha = 255 # Increased from 200 for full opacity
144
- small_node_alpha = 220 # Increased from 150 for better visibility
145
- if animated:
146
- import time
147
- pulse = abs(math.sin(time.time() * 3)) * 0.2 + 0.8 # Pulse between 0.8 and 1.0
148
- node_alpha = int(node_alpha * pulse)
149
- small_node_alpha = int(small_node_alpha * pulse)
150
-
151
- # Central node (larger)
152
- node_radius = int(radius * 0.15)
153
- draw.ellipse(
154
- [center - node_radius, center - node_radius,
155
- center + node_radius, center + node_radius],
156
- fill=(255, 255, 255, node_alpha)
157
- )
158
-
159
- # Surrounding nodes
160
- num_nodes = 6
161
- outer_radius = int(radius * 0.7)
162
-
163
- for i in range(num_nodes):
164
- angle = (2 * math.pi * i) / num_nodes
165
- x = center + int(outer_radius * math.cos(angle))
166
- y = center + int(outer_radius * math.sin(angle))
111
+ """Draw neural network nodes around the circle."""
112
+ node_positions = [
113
+ (center + radius * 0.6, center - radius * 0.3),
114
+ (center + radius * 0.3, center + radius * 0.6),
115
+ (center - radius * 0.4, center + radius * 0.4),
116
+ (center - radius * 0.6, center - radius * 0.2),
117
+ (center - radius * 0.1, center - radius * 0.7)
118
+ ]
119
+
120
+ for i, (x, y) in enumerate(node_positions):
121
+ node_radius = 3 + (i % 2) # Varying sizes
122
+ if animated:
123
+ import time
124
+ # Subtle pulsing
125
+ pulse = 1 + 0.3 * math.sin(time.time() * 2 + i)
126
+ node_radius *= pulse
167
127
 
168
- small_radius = int(radius * 0.08)
169
- draw.ellipse(
170
- [x - small_radius, y - small_radius,
171
- x + small_radius, y + small_radius],
172
- fill=(255, 255, 255, small_node_alpha)
173
- )
128
+ draw.ellipse([x-node_radius, y-node_radius, x+node_radius, y+node_radius],
129
+ fill=(255, 255, 255, 180))
174
130
 
175
131
  def _draw_neural_connections(self, draw: ImageDraw.Draw, center: int, radius: int, animated: bool = False):
176
- """Draw connections between neural nodes."""
177
- num_nodes = 6
178
- outer_radius = int(radius * 0.7)
179
-
180
- # Animation effect for connections - more visible
181
- line_alpha = 180 # Increased from 100 for better visibility
182
- connection_alpha = 120 # Increased from 60 for better visibility
183
- if animated:
184
- import time
185
- pulse = abs(math.sin(time.time() * 2.5)) * 0.3 + 0.7 # Pulse between 0.7 and 1.0
186
- line_alpha = int(line_alpha * pulse)
187
- connection_alpha = int(connection_alpha * pulse)
188
-
189
- # Draw lines from center to outer nodes
190
- for i in range(num_nodes):
191
- angle = (2 * math.pi * i) / num_nodes
192
- x = center + int(outer_radius * math.cos(angle))
193
- y = center + int(outer_radius * math.sin(angle))
194
-
195
- draw.line(
196
- [center, center, x, y],
197
- fill=(255, 255, 255, line_alpha),
198
- width=2
199
- )
200
-
201
- # Draw some connections between outer nodes
202
- for i in range(0, num_nodes, 2):
203
- angle1 = (2 * math.pi * i) / num_nodes
204
- angle2 = (2 * math.pi * ((i + 2) % num_nodes)) / num_nodes
205
-
206
- x1 = center + int(outer_radius * math.cos(angle1))
207
- y1 = center + int(outer_radius * math.sin(angle1))
208
- x2 = center + int(outer_radius * math.cos(angle2))
209
- y2 = center + int(outer_radius * math.sin(angle2))
210
-
211
- draw.line(
212
- [x1, y1, x2, y2],
213
- fill=(255, 255, 255, connection_alpha),
214
- width=1
215
- )
216
-
217
- def _add_glow_effect(self, img: Image.Image, color_scheme: str = "blue") -> Image.Image:
218
- """Add a subtle glow effect to the icon."""
219
- # Create a slightly larger version for the glow
220
- glow_size = self.size + 8
221
- glow_img = Image.new('RGBA', (glow_size, glow_size), (0, 0, 0, 0))
132
+ """Draw connecting lines between nodes."""
133
+ connections = [
134
+ ((center + radius * 0.6, center - radius * 0.3), (center + radius * 0.3, center + radius * 0.6)),
135
+ ((center + radius * 0.3, center + radius * 0.6), (center - radius * 0.4, center + radius * 0.4)),
136
+ ((center - radius * 0.4, center + radius * 0.4), (center - radius * 0.6, center - radius * 0.2)),
137
+ ((center - radius * 0.1, center - radius * 0.7), (center + radius * 0.6, center - radius * 0.3))
138
+ ]
222
139
 
223
- # Paste the original image in the center
224
- offset = 4
225
- glow_img.paste(img, (offset, offset), img)
226
-
227
- # Apply blur for glow effect
228
- glow_img = glow_img.filter(ImageFilter.GaussianBlur(radius=2))
140
+ for (x1, y1), (x2, y2) in connections:
141
+ draw.line([(x1, y1), (x2, y2)], fill=(255, 255, 255, 120), width=2)
142
+
143
+ def _add_glow_effect(self, img: Image.Image, color_scheme: str) -> Image.Image:
144
+ """Add a subtle glow effect around the icon."""
145
+ # Create glow layer
146
+ glow = img.filter(ImageFilter.GaussianBlur(radius=3))
229
147
 
230
- # Crop back to original size
231
- return glow_img.crop((offset, offset, offset + self.size, offset + self.size))
148
+ # Composite original on top of glow
149
+ result = Image.alpha_composite(glow, img)
150
+ return result
232
151
 
233
152
  def create_status_icon(self, status: str) -> Image.Image:
234
- """Create a status indicator icon.
153
+ """Create a simple status indicator icon.
235
154
 
236
155
  Args:
237
- status: Status string ('ready', 'generating', 'executing')
238
-
239
- Returns:
240
- Small status icon image
156
+ status: Status type ('ready', 'working', 'error', 'warning')
241
157
  """
242
- size = 16
158
+ size = self.size
243
159
  img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
244
160
  draw = ImageDraw.Draw(img)
245
161
 
162
+ # Status colors
246
163
  colors = {
247
- 'ready': (0, 255, 0, 200), # Green
248
- 'generating': (255, 165, 0, 200), # Orange
249
- 'executing': (255, 0, 0, 200), # Red
250
- 'error': (255, 0, 0, 200) # Red
164
+ 'ready': (52, 199, 89), # Green
165
+ 'working': (255, 149, 0), # Orange
166
+ 'error': (255, 59, 48), # Red
167
+ 'warning': (255, 204, 0), # Yellow
168
+ 'thinking': (175, 82, 222), # Purple
169
+ 'speaking': (0, 122, 255) # Blue
251
170
  }
252
171
 
253
- color = colors.get(status, (128, 128, 128, 200)) # Gray default
172
+ color = colors.get(status, colors['ready'])
254
173
 
255
174
  # Draw status circle
256
175
  draw.ellipse([2, 2, size-2, size-2], fill=color)
257
176
 
258
177
  return img
178
+
179
+ def apply_heartbeat_effect(self, base_icon: Image.Image, status: str = "ready") -> Image.Image:
180
+ """Apply DRAMATIC animated effect with solid colors and rotating elements.
181
+
182
+ Args:
183
+ base_icon: Base icon image to apply effect to
184
+ status: Status for animation type ('ready', 'thinking', 'speaking')
185
+
186
+ Returns:
187
+ Icon with dramatic animated effect applied
188
+ """
189
+ import time
190
+ import math
191
+ from PIL import ImageFilter, ImageEnhance, ImageDraw
192
+
193
+ # Debug: Print status occasionally (only in debug mode)
194
+ if hasattr(self, 'debug') and self.debug:
195
+ if hasattr(self, '_last_debug_time'):
196
+ if time.time() - self._last_debug_time > 3: # Every 3 seconds
197
+ print(f"🎨 Icon animation status: {status}")
198
+ self._last_debug_time = time.time()
199
+ else:
200
+ print(f"🎨 Icon animation status: {status}")
201
+ self._last_debug_time = time.time()
202
+
203
+ # Print status changes only in debug mode
204
+ if not hasattr(self, '_last_status') or self._last_status != status:
205
+ if hasattr(self, 'debug') and self.debug:
206
+ print(f"🔄 Icon status changed: {getattr(self, '_last_status', 'none')} → {status}")
207
+ self._last_status = status
208
+
209
+ # SOLID background colors for maximum visibility
210
+ solid_colors = {
211
+ 'ready': (0, 255, 80), # Bright green
212
+ 'thinking': (255, 60, 100), # Bright red
213
+ 'speaking': (60, 150, 255), # Bright blue
214
+ 'generating': (255, 160, 0) # Bright orange
215
+ }
216
+
217
+ # Create a new dramatic icon instead of modifying the base
218
+ size = base_icon.size[0]
219
+ center = size // 2
220
+
221
+ # Create new image with transparent background
222
+ result = Image.new('RGBA', (size, size), (0, 0, 0, 0))
223
+ draw = ImageDraw.Draw(result)
224
+
225
+ # Get current time for animation
226
+ current_time = time.time()
227
+
228
+ # Get base color for this status
229
+ base_color = solid_colors.get(status, solid_colors['ready'])
230
+
231
+ # Status-specific animation patterns with rotation
232
+ # Debug output disabled for clean terminal
233
+ # print(f"🎯 Animation logic: status='{status}', base_color={base_color}")
234
+
235
+ if status == 'thinking':
236
+ # print("🔴 THINKING: Drawing rotating red bars") # Debug disabled
237
+ # Fast rotating bars with red color - CLOCKWISE rotation
238
+ rotation_speed = 4.0 # 4 rotations per second
239
+ angle = -(current_time * rotation_speed * 360) % 360 # Negative for clockwise
240
+
241
+ # Double-heartbeat intensity
242
+ heartbeat = (current_time * 3.0) % 1 # 3Hz heartbeat
243
+ if heartbeat < 0.1:
244
+ intensity = 1.0
245
+ elif heartbeat < 0.2:
246
+ intensity = 0.3
247
+ elif heartbeat < 0.3:
248
+ intensity = 1.2
249
+ else:
250
+ intensity = 0.2
251
+
252
+ # Draw rotating bars
253
+ self._draw_rotating_bars(draw, center, size, angle, base_color, intensity)
254
+
255
+ elif status == 'speaking':
256
+ # print("🔵 SPEAKING: Drawing vibrating blue bars") # Debug disabled
257
+ # print(f"🔵 SPEAKING: Using color {base_color} (should be blue)") # Debug disabled
258
+
259
+ # Create voice frequency-like vibration pattern
260
+ freq1 = 8.0 # High frequency vibration
261
+ freq2 = 3.0 # Medium frequency modulation
262
+ freq3 = 1.5 # Low frequency envelope
263
+
264
+ # Complex vibration pattern mimicking voice
265
+ vibration = (math.sin(current_time * freq1 * 2 * math.pi) * 0.3 +
266
+ math.sin(current_time * freq2 * 2 * math.pi) * 0.4 +
267
+ math.sin(current_time * freq3 * 2 * math.pi) * 0.3)
268
+ intensity = 0.7 + vibration * 0.3
269
+
270
+ # Draw vibrating voice bars (vertical bars that vibrate)
271
+ self._draw_voice_bars(draw, center, size, base_color, intensity, current_time)
272
+
273
+ elif status == 'ready':
274
+ # print("🟢 READY: Drawing breathing green circle") # Debug disabled
275
+ # Slow breathing circle with green color
276
+ breath = 0.5 + 0.5 * math.sin(current_time * 0.6 * math.pi) # 0.3Hz breathing
277
+ intensity = 0.4 + breath * 0.3
278
+
279
+ # Draw breathing circle (no rotation)
280
+ self._draw_breathing_circle(draw, center, size, base_color, intensity)
281
+
282
+ else:
283
+ # print(f"❓ UNKNOWN STATUS: '{status}' - using default circle") # Debug disabled
284
+ # Default: static circle
285
+ self._draw_breathing_circle(draw, center, size, base_color, 0.5)
286
+
287
+ return result
288
+
289
+ def _draw_rotating_bars(self, draw, center, size, angle, color, intensity):
290
+ """Draw rotating bars for thinking status."""
291
+ # Adjust color intensity
292
+ r, g, b = color
293
+ r = int(min(255, r * intensity))
294
+ g = int(min(255, g * intensity))
295
+ b = int(min(255, b * intensity))
296
+ bar_color = (r, g, b, 255)
297
+
298
+ # Draw 4 bars rotating around center
299
+ bar_length = size * 0.3
300
+ bar_width = size * 0.08
301
+
302
+ for i in range(4):
303
+ bar_angle = angle + (i * 90)
304
+ rad = math.radians(bar_angle)
305
+
306
+ # Calculate bar endpoints
307
+ start_x = center + math.cos(rad) * (size * 0.15)
308
+ start_y = center + math.sin(rad) * (size * 0.15)
309
+ end_x = center + math.cos(rad) * (size * 0.35)
310
+ end_y = center + math.sin(rad) * (size * 0.35)
311
+
312
+ # Draw thick line as bar
313
+ self._draw_thick_line(draw, start_x, start_y, end_x, end_y, bar_width, bar_color)
314
+
315
+ def _draw_voice_bars(self, draw, center, size, color, intensity, current_time):
316
+ """Draw vibrating voice bars for speaking status."""
317
+ import math
318
+
319
+ # Adjust color intensity
320
+ r, g, b = color
321
+ r = int(min(255, r * intensity))
322
+ g = int(min(255, g * intensity))
323
+ b = int(min(255, b * intensity))
324
+ bar_color = (r, g, b, 255)
325
+
326
+ # Draw 5 vertical bars with different vibration frequencies (like voice visualizer)
327
+ bar_count = 5
328
+ bar_width = size * 0.08
329
+ bar_spacing = size * 0.12
330
+
331
+ for i in range(bar_count):
332
+ # Each bar has slightly different frequency for realistic voice effect
333
+ bar_freq = 6.0 + i * 1.5 # Different frequencies per bar
334
+ bar_vibration = math.sin(current_time * bar_freq * 2 * math.pi)
335
+
336
+ # Bar height varies with vibration (like audio visualizer)
337
+ base_height = size * 0.15
338
+ vibration_height = size * 0.25 * abs(bar_vibration)
339
+ total_height = base_height + vibration_height
340
+
341
+ # Position bars horizontally across the icon
342
+ x = center - (bar_count - 1) * bar_spacing / 2 + i * bar_spacing
343
+ y_top = center - total_height / 2
344
+ y_bottom = center + total_height / 2
345
+
346
+ # Draw vertical bar
347
+ bbox = [x - bar_width/2, y_top, x + bar_width/2, y_bottom]
348
+ draw.rectangle(bbox, fill=bar_color)
349
+
350
+ def _draw_breathing_circle(self, draw, center, size, color, intensity):
351
+ """Draw breathing circle for ready status."""
352
+ # Adjust color intensity
353
+ r, g, b = color
354
+ r = int(min(255, r * intensity))
355
+ g = int(min(255, g * intensity))
356
+ b = int(min(255, b * intensity))
357
+ circle_color = (r, g, b, 255)
358
+
359
+ # Draw MUCH LARGER pulsing circle to match menu bar icon size
360
+ base_radius = size * 0.35 # Much larger base size
361
+ radius = base_radius * (0.8 + 0.4 * intensity)
362
+ bbox = [center - radius, center - radius, center + radius, center + radius]
363
+ draw.ellipse(bbox, fill=circle_color)
364
+
365
+ def _draw_thick_line(self, draw, x1, y1, x2, y2, width, color):
366
+ """Draw a thick line between two points."""
367
+ import math
368
+ # Calculate perpendicular offset for thickness
369
+ dx = x2 - x1
370
+ dy = y2 - y1
371
+ length = math.sqrt(dx*dx + dy*dy)
372
+ if length == 0:
373
+ return
374
+
375
+ # Normalize and get perpendicular
376
+ dx /= length
377
+ dy /= length
378
+ px = -dy * width / 2
379
+ py = dx * width / 2
380
+
381
+ # Draw polygon for thick line
382
+ points = [
383
+ (x1 + px, y1 + py),
384
+ (x1 - px, y1 - py),
385
+ (x2 - px, y2 - py),
386
+ (x2 + px, y2 + py)
387
+ ]
388
+ draw.polygon(points, fill=color)
@@ -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: "SF Pro Text", "Helvetica Neue", BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
106
+ font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif;
107
107
  font-size: 14px; /* Base font size */
108
108
  line-height: 1.6;
109
109
  color: #e2e8f0;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractassistant
3
- Version: 0.2.6
3
+ Version: 0.3.0
4
4
  Summary: A sleek (macOS) system tray application providing instant access to LLMs
5
5
  Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
6
6
  License-Expression: MIT
@@ -21,7 +21,7 @@ Classifier: Topic :: Desktop Environment
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: abstractcore[all]>=2.4.5
24
+ Requires-Dist: abstractcore[all]>=2.5.0
25
25
  Requires-Dist: pystray>=0.19.4
26
26
  Requires-Dist: Pillow>=10.0.0
27
27
  Requires-Dist: PyQt5>=5.15.0
@@ -69,18 +69,22 @@ A sleek macOS system tray application providing instant access to Large Language
69
69
 
70
70
  #### 🍎 macOS Users (Recommended)
71
71
  ```bash
72
- # Enhanced installation with Dock integration
73
- python3 install.py
72
+ # Install AbstractAssistant
73
+ pip install abstractassistant
74
+
75
+ # Create native macOS app bundle
76
+ create-app-bundle
74
77
  ```
75
78
 
76
79
  This will:
77
- - Install AbstractAssistant from PyPI
78
- - Create a macOS app bundle in `/Applications`
79
- - Add AbstractAssistant to your Dock for easy access
80
+ - Install AbstractAssistant from PyPI with all dependencies
81
+ - Create a native macOS app bundle in `/Applications`
82
+ - Add AbstractAssistant to your Dock with a beautiful neural network icon
83
+ - Enable launch from Spotlight, Finder, and Dock
80
84
 
81
85
  #### 🔧 Standard Installation
82
86
  ```bash
83
- # Install from PyPI
87
+ # Install from PyPI (terminal access only)
84
88
  pip install abstractassistant
85
89
  ```
86
90
 
@@ -98,7 +102,7 @@ For detailed installation instructions including prerequisites and voice setup,
98
102
  # Launch the assistant
99
103
  assistant
100
104
 
101
- # Or create macOS app bundle after installation
105
+ # Create macOS app bundle after installation
102
106
  create-app-bundle
103
107
  ```
104
108
 
@@ -0,0 +1,28 @@
1
+ setup_macos_app.py,sha256=9dIPr9TipjtgdIhd0MnR2syRNoFyBVMnRsWDW0UCT3A,10736
2
+ abstractassistant/__init__.py,sha256=homfqMDh6sX2nBROtk6-y72jnrStPph8gEOeT0OjKyU,35
3
+ abstractassistant/app.py,sha256=9Dn_2ShT6O5sE3Qf09oh-wKQpLFzKTNVJzZbB0POOYI,40078
4
+ abstractassistant/cli.py,sha256=SQPxQCLjX-LOlhSEvG302D0AOyxlxo5QM2imxr9wxmc,4385
5
+ abstractassistant/config.py,sha256=KodfPYTpHtavJyne-h-B-r3kbEt1uusSY8GknGLtDL8,5809
6
+ abstractassistant/create_app_bundle.py,sha256=LAZdp2C90ikMVd3KPdwNYBYUASbHpypOJIwvx6fQyXM,1698
7
+ abstractassistant/web_server.py,sha256=_pqMzy13qfim9BMBqQJQifWyX7UQXFD_sZeiu4ZBt40,12816
8
+ abstractassistant/core/__init__.py,sha256=TETStgToTe7QSsCZgRHDk2oSErlLJoeGN0sFg4Yx2_c,15
9
+ abstractassistant/core/llm_manager.py,sha256=hJun-nDfRv9zxv_3tfrHAmVYSYT96E-0zDJB2TiaSeQ,19226
10
+ abstractassistant/core/tts_manager.py,sha256=Cxh302EgIycwkWxe7XntmLW-j_WusbJOYRCs3Jms3CU,9892
11
+ abstractassistant/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
12
+ abstractassistant/ui/chat_bubble.py,sha256=TE6zPtQ46I9grKGAb744wHqk4yO6-und3iif8_33XGk,11357
13
+ abstractassistant/ui/history_dialog.py,sha256=25EVyf3-8Kaw1bZPTZe8G-uqw_KnP2t--OgAjsxC06w,18548
14
+ abstractassistant/ui/provider_manager.py,sha256=9IM-BxIs6lUlk6cDCBi7oZFMXmn4CFMlxh0s-_vhzXY,8403
15
+ abstractassistant/ui/qt_bubble.py,sha256=GO0IvM_04h9obRV6WbFKpJsoAwuftYkkljLtmw5XCYU,96483
16
+ abstractassistant/ui/toast_manager.py,sha256=1aU4DPo-J45bC61gTEctHq98ZrHIFxRfZa_9Q8KF588,13721
17
+ abstractassistant/ui/toast_window.py,sha256=BRSwEBlaND5LLipn1HOX0ISWxVH-zOHsYplFkiPaj_g,21727
18
+ abstractassistant/ui/tts_state_manager.py,sha256=UF_zrfl9wf0hNHBGxevcoKxW5Dh7zXibUSVoSSjGP4o,10565
19
+ abstractassistant/ui/ui_styles.py,sha256=FvE2CVUbHmHu1PKVTBBGyhbt781qh4WjLMrHviln39s,13120
20
+ abstractassistant/utils/__init__.py,sha256=7Q3BxyXETkt3tm5trhuLTyL8PoECOK0QiK-0KUVAR2Q,16
21
+ abstractassistant/utils/icon_generator.py,sha256=BLL0ULngPA3QGz_jJga491Fo3tnNi0fx5CcIzvLoVxg,16203
22
+ abstractassistant/utils/markdown_renderer.py,sha256=u5tVIhulSwRYADiqJcZNoHhU8e6pJVgzrwZRd61Bov0,12585
23
+ abstractassistant-0.3.0.dist-info/licenses/LICENSE,sha256=QUjFNAE-0yOkW9-Rle2axkpkt9H7xiZ2VbN-VeONhxc,1106
24
+ abstractassistant-0.3.0.dist-info/METADATA,sha256=noWV-vlbNqvER5tPC8K2Ky1gl8V0qyGYdqpCkwX54sI,11320
25
+ abstractassistant-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
+ abstractassistant-0.3.0.dist-info/entry_points.txt,sha256=MIzeCh0XG6MbhIzBHtkdEjmjxYBsQrGFevq8Y1L8Jkc,118
27
+ abstractassistant-0.3.0.dist-info/top_level.txt,sha256=oEcSXZAqbflTfZRfF4dogUq6TC1Nqyplq4JgC0CZnLI,34
28
+ abstractassistant-0.3.0.dist-info/RECORD,,