pygpt-net 2.6.33__py3-none-any.whl → 2.6.34__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 (42) hide show
  1. pygpt_net/CHANGELOG.txt +7 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/assistant/batch.py +14 -4
  4. pygpt_net/controller/assistant/files.py +1 -0
  5. pygpt_net/controller/assistant/store.py +195 -1
  6. pygpt_net/controller/camera/camera.py +1 -1
  7. pygpt_net/controller/chat/common.py +50 -46
  8. pygpt_net/controller/config/placeholder.py +95 -75
  9. pygpt_net/controller/dialogs/confirm.py +3 -1
  10. pygpt_net/controller/media/media.py +11 -3
  11. pygpt_net/controller/painter/common.py +231 -13
  12. pygpt_net/core/assistants/files.py +18 -0
  13. pygpt_net/core/camera/camera.py +31 -402
  14. pygpt_net/core/camera/worker.py +430 -0
  15. pygpt_net/core/filesystem/url.py +3 -0
  16. pygpt_net/core/render/web/body.py +65 -9
  17. pygpt_net/core/text/utils.py +3 -0
  18. pygpt_net/data/config/config.json +3 -3
  19. pygpt_net/data/config/models.json +3 -3
  20. pygpt_net/data/config/settings.json +10 -5
  21. pygpt_net/data/locale/locale.de.ini +8 -7
  22. pygpt_net/data/locale/locale.en.ini +9 -6
  23. pygpt_net/data/locale/locale.es.ini +8 -7
  24. pygpt_net/data/locale/locale.fr.ini +8 -7
  25. pygpt_net/data/locale/locale.it.ini +8 -7
  26. pygpt_net/data/locale/locale.pl.ini +8 -7
  27. pygpt_net/data/locale/locale.uk.ini +8 -7
  28. pygpt_net/data/locale/locale.zh.ini +8 -7
  29. pygpt_net/item/assistant.py +13 -1
  30. pygpt_net/provider/api/google/__init__.py +32 -23
  31. pygpt_net/provider/api/openai/store.py +45 -1
  32. pygpt_net/provider/llms/google.py +4 -0
  33. pygpt_net/ui/dialog/assistant_store.py +213 -203
  34. pygpt_net/ui/layout/chat/input.py +3 -3
  35. pygpt_net/ui/widget/draw/painter.py +16 -1
  36. pygpt_net/ui/widget/option/combo.py +5 -1
  37. pygpt_net/ui/widget/textarea/input.py +273 -3
  38. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.34.dist-info}/METADATA +9 -2
  39. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.34.dist-info}/RECORD +42 -41
  40. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.34.dist-info}/LICENSE +0 -0
  41. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.34.dist-info}/WHEEL +0 -0
  42. {pygpt_net-2.6.33.dist-info → pygpt_net-2.6.34.dist-info}/entry_points.txt +0 -0
@@ -1,9 +1,19 @@
1
- # controller/painter/common.py
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.02 20:00:00 #
10
+ # ================================================== #
2
11
 
3
12
  from typing import Tuple, Optional, Dict, List
4
13
 
5
14
  from PySide6.QtCore import Qt, QSize
6
15
  from PySide6.QtGui import QColor
16
+ from PySide6.QtWidgets import QComboBox
7
17
 
8
18
 
9
19
  class Common:
@@ -14,6 +24,12 @@ class Common:
14
24
  :param window: Window instance
15
25
  """
16
26
  self.window = window
27
+ # Guard to prevent re-entrancy when programmatically changing the combo/size
28
+ self._changing_canvas_size = False
29
+ # Cached set for predefined canvas sizes
30
+ self._predef_canvas_sizes_set = None
31
+ # Sticky custom value derived from the current "source" image (kept at index 0 when present)
32
+ self._sticky_custom_value: Optional[str] = None
17
33
 
18
34
  def convert_to_size(self, canvas_size: str) -> Tuple[int, int]:
19
35
  """
@@ -28,8 +44,8 @@ class Common:
28
44
  """
29
45
  Set canvas size
30
46
 
31
- :param width: int
32
- :param height: int
47
+ :param width: Canvas width
48
+ :param height: Canvas height
33
49
  """
34
50
  self.window.ui.painter.setFixedSize(QSize(width, height))
35
51
 
@@ -69,16 +85,64 @@ class Common:
69
85
 
70
86
  :param selected: Selected size
71
87
  """
88
+ # Re-entrancy guard to avoid loops when we adjust the combo programmatically
89
+ if self._changing_canvas_size:
90
+ return
91
+
92
+ combo: QComboBox = self.window.ui.nodes['painter.select.canvas.size']
93
+ painter = self.window.ui.painter
94
+
95
+ # Heuristic to detect manual UI change vs programmatic call
96
+ # - manual if: no arg, or int index (Qt int overload), or arg equals currentText/currentData
97
+ raw_arg = selected
98
+ current_text = combo.currentText()
99
+ current_data = combo.currentData()
100
+ current_data_str = current_data if isinstance(current_data, str) else None
101
+ is_manual = (
102
+ raw_arg is None
103
+ or isinstance(raw_arg, int)
104
+ or (isinstance(raw_arg, str) and (raw_arg == current_text or (current_data_str and raw_arg == current_data_str)))
105
+ )
106
+
107
+ # Resolve selection if not passed explicitly; fallback to currentText if userData is missing
72
108
  if not selected:
73
- selected = self.window.ui.nodes['painter.select.canvas.size'].currentData()
74
- if selected:
75
- size = self.convert_to_size(selected)
76
- # setCurrentText might not exist in the combo's items for custom sizes; harmless if it doesn't match
77
- self.window.ui.nodes['painter.select.canvas.size'].setCurrentText(selected)
78
- self.set_canvas_size(size[0], size[1])
79
- # resizing the widget triggers automatic image rescale in PainterWidget.resizeEvent
80
- self.window.core.config.set('painter.canvas.size', selected)
109
+ selected = current_data_str or current_text
110
+
111
+ # Normalize to "WxH" strictly; if invalid, do nothing
112
+ selected_norm = self._normalize_canvas_value(selected)
113
+ if not selected_norm:
114
+ return
115
+
116
+ # Save undo only for manual changes and only if size will change
117
+ will_change = selected_norm != f"{painter.width()}x{painter.height()}"
118
+ if is_manual and will_change:
119
+ painter.saveForUndo()
120
+
121
+ try:
122
+ self._changing_canvas_size = True
123
+
124
+ predef = self._get_predef_canvas_set()
125
+
126
+ # Sticky custom update only for programmatic (source-driven) changes
127
+ programmatic = not is_manual
128
+ if programmatic:
129
+ if selected_norm in predef:
130
+ self._sticky_custom_value = None
131
+ else:
132
+ self._sticky_custom_value = selected_norm
133
+
134
+ # Ensure combo reflects single custom at index 0 (sticky respected), then select current value
135
+ self._sync_canvas_size_combo(combo, selected_norm, sticky_to_keep=self._sticky_custom_value)
136
+
137
+ # Apply canvas size; PainterWidget handles rescaling in resizeEvent
138
+ w, h = self.convert_to_size(selected_norm)
139
+ self.set_canvas_size(w, h)
140
+
141
+ # Persist normalized value
142
+ self.window.core.config.set('painter.canvas.size', selected_norm)
81
143
  self.window.core.config.save()
144
+ finally:
145
+ self._changing_canvas_size = False
82
146
 
83
147
  def change_brush_size(self, size: int):
84
148
  """
@@ -141,7 +205,7 @@ class Common:
141
205
  if self.window.core.config.has('painter.brush.size'):
142
206
  size = int(self.window.core.config.get('painter.brush.size', 3))
143
207
  self.window.ui.nodes['painter.select.brush.size'].setCurrentIndex(
144
- self.window.ui.nodes['painter.select.brush.size'].findText(str(size))
208
+ self.window.ui.nodes['painter.select.brush.size'].findText(str(size))
145
209
  )
146
210
 
147
211
  def get_colors(self) -> Dict[str, QColor]:
@@ -191,4 +255,158 @@ class Common:
191
255
 
192
256
  :return: path to capture directory
193
257
  """
194
- return self.window.core.config.get_user_dir('capture')
258
+ return self.window.core.config.get_user_dir('capture')
259
+
260
+ # ---------- Public sync helper (used by PainterWidget undo/redo) ----------
261
+
262
+ def sync_canvas_combo_from_widget(self):
263
+ """
264
+ Sync the size combobox with current PainterWidget canvas size.
265
+ Also derive sticky custom from the current source image if it is custom.
266
+ This method does not change the canvas size (UI-only sync).
267
+ """
268
+ if self._changing_canvas_size:
269
+ return
270
+
271
+ combo: QComboBox = self.window.ui.nodes['painter.select.canvas.size']
272
+ painter = self.window.ui.painter
273
+
274
+ canvas_value = f"{painter.width()}x{painter.height()}"
275
+ canvas_norm = self._normalize_canvas_value(canvas_value)
276
+ if not canvas_norm:
277
+ return
278
+
279
+ # Derive sticky from current source image (if custom)
280
+ predef = self._get_predef_canvas_set()
281
+ sticky = None
282
+ if painter.sourceImageOriginal is not None and not painter.sourceImageOriginal.isNull():
283
+ src_val = f"{painter.sourceImageOriginal.width()}x{painter.sourceImageOriginal.height()}"
284
+ src_val = self._normalize_canvas_value(src_val)
285
+ if src_val and src_val not in predef:
286
+ sticky = src_val
287
+
288
+ try:
289
+ self._changing_canvas_size = True
290
+ self._sticky_custom_value = sticky
291
+ self._sync_canvas_size_combo(combo, canvas_norm, sticky_to_keep=sticky)
292
+
293
+ # Persist canvas size only (do not change sticky config-scope)
294
+ self.window.core.config.set('painter.canvas.size', canvas_norm)
295
+ self.window.core.config.save()
296
+ finally:
297
+ self._changing_canvas_size = False
298
+
299
+ # ---------- Internal helpers ----------
300
+
301
+ def _normalize_canvas_value(self, value: Optional[str]) -> Optional[str]:
302
+ """
303
+ Normalize arbitrary canvas string to canonical 'WxH'. Returns None if invalid.
304
+ Accepts variants like ' 1024 x 768 ', '1024×768', etc.
305
+
306
+ :param value: input value
307
+ :return: normalized value or None
308
+ """
309
+ if not value:
310
+ return None
311
+ s = str(value).strip().lower().replace(' ', '').replace('×', 'x')
312
+ if 'x' not in s:
313
+ return None
314
+ parts = s.split('x', 1)
315
+ try:
316
+ w = int(parts[0])
317
+ h = int(parts[1])
318
+ except Exception:
319
+ return None
320
+ if w <= 0 or h <= 0:
321
+ return None
322
+ return f"{w}x{h}"
323
+
324
+ def _get_predef_canvas_set(self) -> set:
325
+ """
326
+ Return cached set of predefined sizes for O(1) lookups.
327
+
328
+ :return: set of predefined sizes
329
+ """
330
+ if self._predef_canvas_sizes_set is None:
331
+ self._predef_canvas_sizes_set = set(self.get_canvas_sizes())
332
+ return self._predef_canvas_sizes_set
333
+
334
+ def _find_index_for_value(self, combo: QComboBox, value: str) -> int:
335
+ """
336
+ Find index by userData first, then by text. Returns -1 if not found.
337
+
338
+ :param combo: QComboBox
339
+ :param value: value to find
340
+ :return: index or -1
341
+ """
342
+ idx = combo.findData(value)
343
+ if idx == -1:
344
+ idx = combo.findText(value, Qt.MatchFixedString)
345
+ return idx
346
+
347
+ def _remove_extra_custom_items(self, combo: QComboBox, predef: set, keep_index: int = -1):
348
+ """
349
+ Remove all non-predefined items except one at keep_index (if set).
350
+
351
+ :param combo: QComboBox
352
+ :param predef: set of predefined values
353
+ :param keep_index: index to keep even if custom, or -1 to remove all custom
354
+ """
355
+ for i in range(combo.count() - 1, -1, -1):
356
+ if i == keep_index:
357
+ continue
358
+ txt = combo.itemText(i)
359
+ if txt not in predef:
360
+ combo.removeItem(i)
361
+
362
+ def _ensure_custom_index0(self, combo: QComboBox, custom_value: str, predef: set):
363
+ """
364
+ Ensure exactly one custom item exists at index 0 with given value.
365
+
366
+ :param combo: QComboBox
367
+ :param custom_value: custom value to set at index 0
368
+ :param predef: set of predefined values
369
+ """
370
+ if combo.count() > 0 and combo.itemText(0) not in predef:
371
+ if combo.itemText(0) != custom_value:
372
+ combo.setItemText(0, custom_value)
373
+ combo.setItemData(0, custom_value)
374
+ else:
375
+ combo.insertItem(0, custom_value, custom_value)
376
+ self._remove_extra_custom_items(combo, predef, keep_index=0)
377
+
378
+ def _sync_canvas_size_combo(self, combo: QComboBox, value: str, sticky_to_keep: Optional[str]):
379
+ """
380
+ Enforce invariant and selection:
381
+ - If sticky_to_keep is a custom value -> keep it as single custom item at index 0.
382
+ - If sticky_to_keep is None -> remove all custom items.
383
+ - Select 'value' in the combo. If value is custom and sticky_to_keep differs or is None,
384
+ ensure index 0 matches 'value' and select it.
385
+
386
+ :param combo: QComboBox
387
+ :param value: current canvas size value to select
388
+ :param sticky_to_keep: sticky custom value to keep at index 0, or None
389
+ """
390
+ predef = self._get_predef_canvas_set()
391
+
392
+ # Maintain sticky custom slot (index 0) if provided
393
+ if sticky_to_keep and sticky_to_keep not in predef:
394
+ self._ensure_custom_index0(combo, sticky_to_keep, predef)
395
+ else:
396
+ self._remove_extra_custom_items(combo, predef, keep_index=-1)
397
+
398
+ # Select the current canvas value
399
+ if value in predef:
400
+ idx = self._find_index_for_value(combo, value)
401
+ if idx != -1 and idx != combo.currentIndex():
402
+ combo.setCurrentIndex(idx)
403
+ elif idx == -1:
404
+ # Fallback: set text (should not normally happen if combo prepopulates predefined sizes)
405
+ combo.setCurrentText(value)
406
+ else:
407
+ # Current value is custom: ensure it exists at index 0 and select it
408
+ # If sticky differs or is None, overwrite/create the custom at index 0 to reflect true current value.
409
+ if not sticky_to_keep or sticky_to_keep != value:
410
+ self._ensure_custom_index0(combo, value, predef)
411
+ if combo.currentIndex() != 0:
412
+ combo.setCurrentIndex(0)
@@ -257,6 +257,24 @@ class Files:
257
257
  del self.items[file.record_id]
258
258
  return True
259
259
 
260
+ def delete_by_file_id(self, file_id: str) -> bool:
261
+ """
262
+ Delete file by file ID
263
+
264
+ :param file_id: file ID
265
+ :return: True if deleted
266
+ """
267
+ res = self.provider.delete_by_file_id(file_id)
268
+ if res:
269
+ # remove from items
270
+ to_delete = []
271
+ for id in self.items:
272
+ if self.items[id].file_id == file_id:
273
+ to_delete.append(id)
274
+ for id in to_delete:
275
+ del self.items[id]
276
+ return res
277
+
260
278
  def on_store_deleted(self, store_id: str):
261
279
  """
262
280
  Clear deleted store from files