pygpt-net 2.6.2__py3-none-any.whl → 2.6.3__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.
Files changed (35) hide show
  1. pygpt_net/CHANGELOG.txt +5 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/controller/calendar/note.py +101 -126
  4. pygpt_net/controller/chat/render.py +20 -7
  5. pygpt_net/controller/ctx/common.py +3 -2
  6. pygpt_net/controller/ctx/ctx.py +68 -129
  7. pygpt_net/controller/layout/layout.py +166 -108
  8. pygpt_net/controller/mode/mode.py +88 -85
  9. pygpt_net/controller/model/model.py +73 -73
  10. pygpt_net/controller/plugins/plugins.py +186 -243
  11. pygpt_net/controller/plugins/presets.py +54 -55
  12. pygpt_net/controller/plugins/settings.py +54 -69
  13. pygpt_net/controller/presets/presets.py +292 -297
  14. pygpt_net/controller/theme/theme.py +72 -81
  15. pygpt_net/controller/ui/mode.py +2 -3
  16. pygpt_net/controller/ui/tabs.py +69 -90
  17. pygpt_net/controller/ui/ui.py +47 -56
  18. pygpt_net/controller/ui/vision.py +24 -23
  19. pygpt_net/core/render/web/body.py +183 -192
  20. pygpt_net/core/render/web/renderer.py +323 -370
  21. pygpt_net/data/config/config.json +2 -2
  22. pygpt_net/data/config/models.json +2 -2
  23. pygpt_net/ui/base/config_dialog.py +83 -101
  24. pygpt_net/ui/base/context_menu.py +48 -46
  25. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  26. pygpt_net/ui/widget/calendar/select.py +86 -70
  27. pygpt_net/ui/widget/lists/attachment.py +86 -44
  28. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  29. pygpt_net/ui/widget/lists/context.py +135 -188
  30. pygpt_net/ui/widget/lists/preset.py +59 -61
  31. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.3.dist-info}/METADATA +8 -3
  32. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.3.dist-info}/RECORD +35 -35
  33. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.3.dist-info}/LICENSE +0 -0
  34. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.3.dist-info}/WHEEL +0 -0
  35. {pygpt_net-2.6.2.dist-info → pygpt_net-2.6.3.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,8 @@
1
+ 2.6.3 (2025-08-15)
2
+
3
+ - Optimized streaming and CPU usage.
4
+ - Fixed crash on set label color and ctx duplicate.
5
+
1
6
  2.6.2 (2025-08-15)
2
7
 
3
8
  - Added plugins (beta): Google, Facebook, X/Twitter, Telegram, Slack, GitHub, Bitbucket.
pygpt_net/__init__.py CHANGED
@@ -13,7 +13,7 @@ __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.2"
16
+ __version__ = "2.6.3"
17
17
  __build__ = "2025-08-15"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 08:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
@@ -32,29 +32,47 @@ class Note:
32
32
  """Setup calendar notes"""
33
33
  self.counters_all = self.window.core.config.get("ctx.counters.all", True)
34
34
 
35
+ def _adjacent_months(self, year: int, month: int):
36
+ if month == 1:
37
+ py, pm = year - 1, 12
38
+ else:
39
+ py, pm = year, month - 1
40
+ if month == 12:
41
+ ny, nm = year + 1, 1
42
+ else:
43
+ ny, nm = year, month + 1
44
+ return (py, pm), (ny, nm)
45
+
35
46
  def update(self):
36
47
  """Update on content change"""
37
- year = self.window.controller.calendar.selected_year
38
- month = self.window.controller.calendar.selected_month
39
- day = self.window.controller.calendar.selected_day
48
+ ctrl_cal = self.window.controller.calendar
49
+ year = ctrl_cal.selected_year
50
+ month = ctrl_cal.selected_month
51
+ day = ctrl_cal.selected_day
40
52
 
41
53
  if year is None or month is None or day is None:
42
54
  return
43
55
 
44
- content = self.window.ui.calendar['note'].toPlainText()
45
- note = self.window.core.calendar.get_by_date(year, month, day)
56
+ ui_note = self.window.ui.calendar['note']
57
+ content = ui_note.toPlainText()
58
+ cal = self.window.core.calendar
59
+ note = cal.get_by_date(year, month, day)
46
60
 
47
- # update or create note
61
+ changed = False
48
62
  if note is None:
49
- if content.strip() != "":
63
+ if content.strip():
50
64
  note = self.create(year, month, day)
51
65
  note.content = content
52
- self.window.core.calendar.add(note)
66
+ cal.add(note)
67
+ changed = True
53
68
  else:
54
- note.content = content
55
- self.window.core.calendar.update(note)
69
+ if note.content != content:
70
+ note.content = content
71
+ cal.update(note)
72
+ changed = True
56
73
 
57
- self.refresh_num(year, month) # update note cells when note is changed
74
+ if changed:
75
+ self.refresh_num(year, month)
58
76
 
59
77
  def update_content(
60
78
  self,
@@ -69,13 +87,12 @@ class Note:
69
87
  :param month: month
70
88
  :param day: day
71
89
  """
90
+ ui_note = self.window.ui.calendar['note']
72
91
  note = self.window.core.calendar.get_by_date(year, month, day)
73
- if note is None:
74
- self.window.ui.calendar['note'].setPlainText("")
75
- self.window.ui.calendar['note'].on_update()
76
- else:
77
- self.window.ui.calendar['note'].setPlainText(note.content)
78
- self.window.ui.calendar['note'].on_update()
92
+ new_text = "" if note is None else note.content
93
+ if ui_note.toPlainText() != new_text:
94
+ ui_note.setPlainText(new_text)
95
+ ui_note.on_update()
79
96
 
80
97
  def update_label(
81
98
  self,
@@ -90,15 +107,13 @@ class Note:
90
107
  :param month: month
91
108
  :param day: day
92
109
  """
93
- suffix = datetime.datetime(year, month, day).strftime("%Y-%m-%d")
94
- self.window.ui.calendar['note.label'].setText(trans('calendar.note.label') + " (" + suffix + ")")
110
+ suffix = f"{year:04d}-{month:02d}-{day:02d}"
111
+ self.window.ui.calendar['note.label'].setText(f"{trans('calendar.note.label')} ({suffix})")
95
112
 
96
113
  def update_current(self):
97
114
  """Update label to current selected date"""
98
- year = self.window.ui.calendar['select'].currentYear
99
- month = self.window.ui.calendar['select'].currentMonth
100
- day = self.window.ui.calendar['select'].currentDay
101
- self.update_label(year, month, day)
115
+ select = self.window.ui.calendar['select']
116
+ self.update_label(select.currentYear, select.currentMonth, select.currentDay)
102
117
 
103
118
  def update_status(
104
119
  self,
@@ -115,16 +130,22 @@ class Note:
115
130
  :param month: month
116
131
  :param day: day
117
132
  """
118
- note = self.window.core.calendar.get_by_date(year, month, day)
133
+ cal = self.window.core.calendar
134
+ note = cal.get_by_date(year, month, day)
135
+ changed = False
119
136
  if note is None:
120
137
  note = self.create(year, month, day)
121
138
  note.status = status
122
- self.window.core.calendar.add(note)
139
+ cal.add(note)
140
+ changed = True
123
141
  else:
124
- note.status = status
125
- self.window.core.calendar.update(note)
142
+ if note.status != status:
143
+ note.status = status
144
+ cal.update(note)
145
+ changed = True
126
146
 
127
- self.refresh_num(year, month) # update note cells when note is changed
147
+ if changed:
148
+ self.refresh_num(year, month)
128
149
 
129
150
  def get_counts_around_month(
130
151
  self,
@@ -138,27 +159,12 @@ class Note:
138
159
  :param month: month
139
160
  :return: combined counters
140
161
  """
141
- current_month_start = datetime.datetime(year, month, 1)
142
- last_month_start = (current_month_start - datetime.timedelta(days=1)).replace(day=1)
143
-
144
- if month == 12:
145
- next_month_start = datetime.datetime(year + 1, 1, 1)
146
- else:
147
- next_month_start = datetime.datetime(year, month + 1, 1)
148
-
149
- current = self.get_ctx_counters(
150
- year,
151
- month,
152
- )
153
- last = self.get_ctx_counters(
154
- last_month_start.year,
155
- last_month_start.month,
156
- )
157
- next = self.get_ctx_counters(
158
- next_month_start.year,
159
- next_month_start.month,
160
- )
161
- return {**last, **current, **next} # combine counters
162
+ (ly, lm), (ny, nm) = self._adjacent_months(year, month)
163
+ result: Dict[str, int] = {}
164
+ result.update(self.get_ctx_counters(ly, lm))
165
+ result.update(self.get_ctx_counters(year, month))
166
+ result.update(self.get_ctx_counters(ny, nm))
167
+ return result
162
168
 
163
169
  def get_labels_counts_around_month(
164
170
  self,
@@ -172,27 +178,12 @@ class Note:
172
178
  :param month: month
173
179
  :return: combined counters
174
180
  """
175
- current_month_start = datetime.datetime(year, month, 1)
176
- last_month_start = (current_month_start - datetime.timedelta(days=1)).replace(day=1)
177
-
178
- if month == 12:
179
- next_month_start = datetime.datetime(year + 1, 1, 1)
180
- else:
181
- next_month_start = datetime.datetime(year, month + 1, 1)
182
-
183
- current = self.get_ctx_labels_counters(
184
- year,
185
- month,
186
- )
187
- last = self.get_ctx_labels_counters(
188
- last_month_start.year,
189
- last_month_start.month,
190
- )
191
- next = self.get_ctx_labels_counters(
192
- next_month_start.year,
193
- next_month_start.month,
194
- )
195
- return {**last, **current, **next} # combine counters
181
+ (ly, lm), (ny, nm) = self._adjacent_months(year, month)
182
+ result: Dict[str, Dict[int, int]] = {}
183
+ result.update(self.get_ctx_labels_counters(ly, lm))
184
+ result.update(self.get_ctx_labels_counters(year, month))
185
+ result.update(self.get_ctx_labels_counters(ny, nm))
186
+ return result
196
187
 
197
188
  def get_ctx_counters(
198
189
  self,
@@ -206,18 +197,17 @@ class Note:
206
197
  :param month: month
207
198
  :return: ctx counters
208
199
  """
209
- # default values (no filters)
210
- search_string = None
211
- search_content = False
212
- filters = None
213
-
214
- # + filters
215
- if not self.counters_all:
216
- search_string = self.window.core.ctx.get_search_string()
217
- search_content = self.window.core.ctx.is_search_content()
218
- filters = self.window.core.ctx.get_parsed_filters()
219
-
220
- return self.window.core.ctx.provider.get_ctx_count_by_day(
200
+ ctx = self.window.core.ctx
201
+ if self.counters_all:
202
+ search_string = None
203
+ search_content = False
204
+ filters = None
205
+ else:
206
+ search_string = ctx.get_search_string()
207
+ search_content = ctx.is_search_content()
208
+ filters = ctx.get_parsed_filters()
209
+
210
+ return ctx.provider.get_ctx_count_by_day(
221
211
  year=year,
222
212
  month=month,
223
213
  day=None,
@@ -238,18 +228,17 @@ class Note:
238
228
  :param month: month
239
229
  :return: ctx counters
240
230
  """
241
- # default values (no filters)
242
- search_string = None
243
- search_content = False
244
- filters = None
245
-
246
- # + filters
247
- if not self.counters_all:
248
- search_string = self.window.core.ctx.get_search_string()
249
- search_content = self.window.core.ctx.is_search_content()
250
- filters = self.window.core.ctx.get_parsed_filters()
251
-
252
- return self.window.core.ctx.provider.get_ctx_labels_count_by_day(
231
+ ctx = self.window.core.ctx
232
+ if self.counters_all:
233
+ search_string = None
234
+ search_content = False
235
+ filters = None
236
+ else:
237
+ search_string = ctx.get_search_string()
238
+ search_content = ctx.is_search_content()
239
+ filters = ctx.get_parsed_filters()
240
+
241
+ return ctx.provider.get_ctx_labels_count_by_day(
253
242
  year=year,
254
243
  month=month,
255
244
  day=None,
@@ -305,26 +294,13 @@ class Note:
305
294
  :param month: month
306
295
  :return: combined notes existence
307
296
  """
308
- current_month_start = datetime.datetime(year, month, 1)
309
- last_month_start = (current_month_start - datetime.timedelta(days=1)).replace(day=1)
310
- if month == 12:
311
- next_month_start = datetime.datetime(year + 1, 1, 1)
312
- else:
313
- next_month_start = datetime.datetime(year, month + 1, 1)
314
-
315
- current = self.window.core.calendar.get_notes_existence_by_day(
316
- year,
317
- month,
318
- )
319
- last = self.window.core.calendar.get_notes_existence_by_day(
320
- last_month_start.year,
321
- last_month_start.month,
322
- )
323
- next = self.window.core.calendar.get_notes_existence_by_day(
324
- next_month_start.year,
325
- next_month_start.month,
326
- )
327
- return {**last, **current, **next} # combine notes existence
297
+ (ly, lm), (ny, nm) = self._adjacent_months(year, month)
298
+ cal = self.window.core.calendar
299
+ result: Dict[str, Dict[int, int]] = {}
300
+ result.update(cal.get_notes_existence_by_day(ly, lm))
301
+ result.update(cal.get_notes_existence_by_day(year, month))
302
+ result.update(cal.get_notes_existence_by_day(ny, nm))
303
+ return result
328
304
 
329
305
  def refresh_num(self, year: int, month: int):
330
306
  """
@@ -342,6 +318,8 @@ class Note:
342
318
 
343
319
  :param state: state
344
320
  """
321
+ if self.counters_all == state:
322
+ return
345
323
  self.counters_all = state
346
324
  self.window.core.config.set("ctx.counters.all", state)
347
325
  self.window.core.config.save()
@@ -355,17 +333,14 @@ class Note:
355
333
  """
356
334
  dt = "" # TODO: add to config append date/time
357
335
  # dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ":\n--------------------------\n"
358
- prev_text = self.window.ui.calendar['note'].toPlainText()
359
- if prev_text != "":
360
- prev_text += "\n\n"
361
- new_text = prev_text + dt + text.strip()
362
- self.window.ui.calendar['note'].setText(new_text)
363
- self.update()
364
-
365
- # move cursor to end
366
- cursor = self.window.ui.calendar['note'].textCursor()
336
+ editor = self.window.ui.calendar['note']
337
+ cursor = editor.textCursor()
367
338
  cursor.movePosition(QTextCursor.End)
368
- self.window.ui.calendar['note'].setTextCursor(cursor)
339
+ if not editor.document().isEmpty():
340
+ cursor.insertText("\n\n")
341
+ cursor.insertText(dt + text.strip())
342
+ editor.setTextCursor(cursor)
343
+ self.update()
369
344
 
370
345
  def clear_note(self):
371
346
  """Clear note"""
@@ -379,4 +354,4 @@ class Note:
379
354
 
380
355
  :return: notepad text
381
356
  """
382
- return self.window.ui.calendar['note'].toPlainText()
357
+ return self.window.ui.calendar['note'].toPlainText()
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.01 19:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List
@@ -36,6 +36,7 @@ class Render:
36
36
  self.web_renderer = WebRenderer(window)
37
37
  self.engine = None
38
38
  self.scroll = 0
39
+ self.renderer = None
39
40
 
40
41
  def setup(self):
41
42
  """Setup render"""
@@ -86,7 +87,7 @@ class Render:
86
87
  elif name == RenderEvent.STREAM_BEGIN:
87
88
  self.stream_begin(meta, ctx)
88
89
  elif name == RenderEvent.STREAM_APPEND:
89
- self.append_chunk(meta, ctx, chunk, begin)
90
+ self.instance().append_chunk(meta, ctx, chunk, begin)
90
91
  elif name == RenderEvent.STREAM_NEXT:
91
92
  self.next_chunk(meta, ctx)
92
93
  elif name == RenderEvent.STREAM_END:
@@ -179,7 +180,6 @@ class Render:
179
180
  :param begin: if it is the beginning of the stream
180
181
  """
181
182
  self.instance().append_live(meta, ctx, text_chunk, begin)
182
- self.update()
183
183
 
184
184
  def clear_live(self, meta: CtxMeta, ctx: CtxItem):
185
185
  """
@@ -413,7 +413,6 @@ class Render:
413
413
  :param begin: if it is the beginning of the stream
414
414
  """
415
415
  self.instance().append_chunk(meta, ctx, text_chunk, begin)
416
- self.update()
417
416
 
418
417
  def next_chunk(
419
418
  self,
@@ -551,8 +550,8 @@ class Render:
551
550
 
552
551
  def update(self):
553
552
  """On update - active"""
554
- for pin in self.window.ui.nodes['output']:
555
- self.window.ui.nodes['output'][pin].on_update()
553
+ for pid in self.window.ui.nodes['output']:
554
+ self.window.ui.nodes['output'][pid].on_update()
556
555
 
557
556
  def clear(self, meta: CtxMeta):
558
557
  """
@@ -658,7 +657,7 @@ class Render:
658
657
  pass
659
658
  else:
660
659
  self.window.ui.nodes['output.timestamp'].setVisible(False)
661
- self.window.controller.ctx.refresh() # TODO: move to on_switch
660
+
662
661
  self.window.controller.theme.markdown.update(force=True)
663
662
  for pid in self.window.ui.nodes['output']:
664
663
  try:
@@ -668,12 +667,26 @@ class Render:
668
667
  except Exception as e:
669
668
  pass
670
669
 
670
+ # cache renderer instance
671
+ if self.window.core.config.get('render.plain'):
672
+ self.renderer = self.plaintext_renderer
673
+ else:
674
+ if self.engine == "web":
675
+ self.renderer = self.web_renderer
676
+ else:
677
+ self.renderer = self.markdown_renderer
678
+
679
+ self.window.controller.ctx.refresh()
680
+
671
681
  def instance(self) -> BaseRenderer:
672
682
  """
673
683
  Get instance of current renderer
674
684
 
675
685
  :return: Renderer instance
676
686
  """
687
+ if self.renderer:
688
+ return self.renderer # return cached renderer instance
689
+
677
690
  # get selected renderer
678
691
  if self.window.core.config.get('render.plain'):
679
692
  return self.plaintext_renderer
@@ -6,11 +6,12 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.19 17:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional
13
13
 
14
+ from PySide6.QtCore import QTimer
14
15
  from PySide6.QtWidgets import QApplication
15
16
 
16
17
  from pygpt_net.core.tabs.tab import Tab
@@ -90,7 +91,7 @@ class Common:
90
91
  self.window.update_status(
91
92
  "Context duplicated, new ctx id: {}".format(new_id)
92
93
  )
93
- self.window.controller.ctx.update(no_scroll=True)
94
+ QTimer.singleShot(100, lambda: self.window.controller.ctx.update(no_scroll=True))
94
95
 
95
96
  def dismiss_rename(self):
96
97
  """Dismiss rename dialog"""