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,4 +1,13 @@
1
- # ui/widget.painer.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
  import datetime
4
13
  from collections import deque
@@ -364,6 +373,9 @@ class PainterWidget(QWidget):
364
373
  self.redoStack.append(current)
365
374
  state = self.undoStack.pop()
366
375
  self._apply_state(state)
376
+ # Keep size combo in sync with restored canvas and source (handles sticky custom)
377
+ if self.window and hasattr(self.window, "controller"):
378
+ self.window.controller.painter.common.sync_canvas_combo_from_widget()
367
379
 
368
380
  def redo(self):
369
381
  """Redo the last undo action"""
@@ -372,6 +384,9 @@ class PainterWidget(QWidget):
372
384
  self.undoStack.append(current)
373
385
  state = self.redoStack.pop()
374
386
  self._apply_state(state)
387
+ # Keep size combo in sync with restored canvas and source (handles sticky custom)
388
+ if self.window and hasattr(self.window, "controller"):
389
+ self.window.controller.painter.common.sync_canvas_combo_from_widget()
375
390
 
376
391
  def has_undo(self) -> bool:
377
392
  """Check if undo is available"""
@@ -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.07.12 19:00:00 #
9
+ # Updated Date: 2025.09.02 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -109,6 +109,8 @@ class OptionCombo(QWidget):
109
109
  for item in self.keys:
110
110
  if type(item) is dict:
111
111
  for key, value in item.items():
112
+ if not isinstance(key, str):
113
+ key = str(key)
112
114
  if key.startswith("separator::"):
113
115
  self.combo.addSeparator(value)
114
116
  else:
@@ -117,6 +119,8 @@ class OptionCombo(QWidget):
117
119
  self.combo.addItem(item, item)
118
120
  elif type(self.keys) is dict:
119
121
  for key, value in self.keys.items():
122
+ if not isinstance(key, str):
123
+ key = str(key)
120
124
  if key.startswith("separator::"):
121
125
  self.combo.addSeparator(value)
122
126
  else:
@@ -6,12 +6,13 @@
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.27 07:00:00 #
9
+ # Updated Date: 2025.09.03 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional
13
+ import math
13
14
 
14
- from PySide6.QtCore import Qt, QSize
15
+ from PySide6.QtCore import Qt, QSize, QTimer, QEvent
15
16
  from PySide6.QtGui import QAction, QIcon, QImage
16
17
  from PySide6.QtWidgets import (
17
18
  QTextEdit,
@@ -90,6 +91,24 @@ class ChatInput(QTextEdit):
90
91
  # Apply initial margins (top padding + left space for icons)
91
92
  self._apply_margins()
92
93
 
94
+ # ---- Auto-resize config (input in splitter) ----
95
+ self._auto_max_lines = 10 # max lines for auto-expansion
96
+ self._auto_max_ratio = 0.25 # max fraction of main window height
97
+ self._auto_debounce_ms = 0 # coalesce updates in next event loop turn
98
+ self._auto_updating = False # reentrancy guard
99
+ self._splitter_resize_in_progress = False
100
+ self._splitter_connected = False
101
+ self._user_adjusting_splitter = False
102
+ self._auto_pause_ms_after_user_drag = 350
103
+ self._last_target_container_h = None
104
+
105
+ self._auto_timer = QTimer(self)
106
+ self._auto_timer.setSingleShot(True)
107
+ self._auto_timer.timeout.connect(self._auto_resize_tick)
108
+
109
+ # Trigger auto-resize on text changes (tokens update stays intact)
110
+ self.textChanged.connect(self._schedule_auto_resize)
111
+
93
112
  def _apply_text_top_padding(self):
94
113
  """Apply extra top padding inside the text area by using viewport margins."""
95
114
  # Left margin is computed in _apply_margins()
@@ -213,6 +232,9 @@ class ChatInput(QTextEdit):
213
232
  self.window.controller.chat.input.send_input()
214
233
  handled = True
215
234
  self.setFocus()
235
+ # Collapse to minimum after sending
236
+ if handled:
237
+ QTimer.singleShot(0, self.collapse_to_min)
216
238
  elif key == Qt.Key_Escape and self.window.controller.ctx.extra.is_editing():
217
239
  self.window.controller.ctx.extra.edit_cancel()
218
240
  handled = True
@@ -240,10 +262,17 @@ class ChatInput(QTextEdit):
240
262
  self.window.core.config.data['font_size.input'] = self.value
241
263
  self.window.core.config.save()
242
264
  self.window.controller.ui.update_font_size()
265
+ # Reflow may change number of lines; adjust auto-height next tick
266
+ QTimer.singleShot(0, self._schedule_auto_resize)
243
267
  event.accept()
244
268
  return
245
269
  super().wheelEvent(event)
246
270
 
271
+ def changeEvent(self, event):
272
+ super().changeEvent(event)
273
+ if event.type() == QEvent.FontChange:
274
+ self._schedule_auto_resize()
275
+
247
276
  def action_add_attachment(self):
248
277
  """Add attachment (button click)."""
249
278
  self.window.controller.attachment.open_add()
@@ -672,4 +701,245 @@ class ChatInput(QTextEdit):
672
701
  try:
673
702
  self._reposition_icon_bar()
674
703
  except Exception:
675
- pass
704
+ pass
705
+ # Recompute on width changes (word wrap may change line count)
706
+ if not self._splitter_resize_in_progress:
707
+ if self.hasFocus():
708
+ self._schedule_auto_resize()
709
+ else:
710
+ # Allow shrinking to minimum when content is single line
711
+ self._schedule_auto_resize(enforce_minimize_if_single=True)
712
+
713
+ # ================== Auto-resize inside QSplitter ==================
714
+
715
+ def _ensure_splitter_hook(self):
716
+ """Lazy-connect to main splitter to detect manual drags."""
717
+ if self._splitter_connected:
718
+ return
719
+ splitter = self._get_main_splitter()
720
+ if splitter is not None:
721
+ try:
722
+ splitter.splitterMoved.connect(self._on_splitter_moved_by_user)
723
+ self._splitter_connected = True
724
+ except Exception:
725
+ pass
726
+
727
+ def _on_splitter_moved_by_user(self, pos, index):
728
+ """Pause auto-resize briefly while the user drags the splitter."""
729
+ self._user_adjusting_splitter = True
730
+ QTimer.singleShot(self._auto_pause_ms_after_user_drag, self._reset_user_adjusting_flag)
731
+
732
+ def _reset_user_adjusting_flag(self):
733
+ self._user_adjusting_splitter = False
734
+
735
+ def _get_main_splitter(self):
736
+ """Get main vertical splitter from window registry."""
737
+ try:
738
+ return self.window.ui.splitters.get('main.output')
739
+ except Exception:
740
+ return None
741
+
742
+ def _find_container_in_splitter(self, splitter):
743
+ """Find the direct child of splitter that contains this ChatInput."""
744
+ if splitter is None:
745
+ return None, -1
746
+ for i in range(splitter.count()):
747
+ w = splitter.widget(i)
748
+ if w and w.isAncestorOf(self):
749
+ return w, i
750
+ return None, -1
751
+
752
+ def _schedule_auto_resize(self, force: bool = False, enforce_minimize_if_single: bool = False):
753
+ """Schedule auto-resize; multiple calls are coalesced."""
754
+ # Store flags for the next tick
755
+ self._pending_force = getattr(self, "_pending_force", False) or bool(force)
756
+ self._pending_minimize_if_single = getattr(self, "_pending_minimize_if_single", False) or bool(
757
+ enforce_minimize_if_single)
758
+ # Avoid scheduling when splitter drag in progress
759
+ if self._user_adjusting_splitter or self._splitter_resize_in_progress:
760
+ return
761
+ self._ensure_splitter_hook()
762
+ # Debounce to next event loop to ensure document layout is up to date
763
+ if not self._auto_timer.isActive():
764
+ self._auto_timer.start(self._auto_debounce_ms)
765
+
766
+ def _auto_resize_tick(self):
767
+ """Execute auto-resize once after debounce."""
768
+ force = getattr(self, "_pending_force", False)
769
+ minimize_if_single = getattr(self, "_pending_minimize_if_single", False)
770
+ self._pending_force = False
771
+ self._pending_minimize_if_single = False
772
+ try:
773
+ self._update_auto_height(force=force, minimize_if_single=minimize_if_single)
774
+ except Exception:
775
+ # Never break input pipeline on errors
776
+ pass
777
+
778
+ def _document_content_height(self) -> int:
779
+ """Return QTextDocument layout height in pixels."""
780
+ doc = self.document()
781
+ layout = doc.documentLayout()
782
+ if layout is not None:
783
+ h = layout.documentSize().height()
784
+ else:
785
+ h = doc.size().height()
786
+ return int(math.ceil(h))
787
+
788
+ def _line_spacing(self) -> int:
789
+ """Return current line spacing for font."""
790
+ return int(math.ceil(self.fontMetrics().lineSpacing()))
791
+
792
+ def _effective_lines(self, doc_h: int, line_h: int, doc_margin: float) -> float:
793
+ """Rough estimate of visible line count from document height."""
794
+ base = max(0.0, doc_h - 2.0 * float(doc_margin))
795
+ if line_h <= 0:
796
+ return 1.0
797
+ return max(1.0, base / float(line_h))
798
+
799
+ def _min_input_widget_height(self, non_viewport_h: int) -> int:
800
+ """Height of QTextEdit widget required to fit a single line without scrollbars."""
801
+ line_h = self._line_spacing()
802
+ doc_margin = float(self.document().documentMargin())
803
+ min_viewport_h = int(math.ceil(2.0 * doc_margin + line_h))
804
+ # Respect current minimum size hint to avoid jitter on some styles
805
+ min_hint = max(self.minimumSizeHint().height(), 0)
806
+ return max(min_hint, min_viewport_h + non_viewport_h)
807
+
808
+ def _max_input_widget_height_by_lines(self, non_viewport_h: int) -> int:
809
+ """Max widget height allowed by line count cap."""
810
+ line_h = self._line_spacing()
811
+ doc_margin = float(self.document().documentMargin())
812
+ max_viewport_h = int(math.ceil(2.0 * doc_margin + self._auto_max_lines * line_h))
813
+ return max_viewport_h + non_viewport_h
814
+
815
+ def _should_shrink_to_min(self, doc_h: int) -> bool:
816
+ """Decide if we should collapse to minimum (single line or empty)."""
817
+ line_h = self._line_spacing()
818
+ doc_margin = float(self.document().documentMargin())
819
+ threshold = 2.0 * doc_margin + 1.25 * line_h # small slack for layout rounding
820
+ return doc_h <= threshold
821
+
822
+ def _update_auto_height(self, force: bool = False, minimize_if_single: bool = False):
823
+ """
824
+ Core auto-resize routine:
825
+ - expand only when the input has focus (unless force=True),
826
+ - cap by max lines and 1/4 of main window height,
827
+ - shrink back to minimal only after send or when text is effectively one line.
828
+ """
829
+ if self._auto_updating or self._splitter_resize_in_progress:
830
+ return
831
+
832
+ splitter = self._get_main_splitter()
833
+ container, idx = self._find_container_in_splitter(splitter)
834
+ if splitter is None or container is None or idx < 0:
835
+ return # Not yet attached to the splitter
836
+
837
+ # Expansion only with focus unless forced
838
+ has_focus = self.hasFocus()
839
+ can_expand = force or has_focus
840
+ if self._user_adjusting_splitter and not force:
841
+ return
842
+
843
+ # Measure current layout and targets
844
+ doc_h = self._document_content_height()
845
+ non_viewport_h = self.height() - self.viewport().height()
846
+ needed_input_h = int(math.ceil(doc_h + non_viewport_h))
847
+ min_input_h = self._min_input_widget_height(non_viewport_h)
848
+ max_input_by_lines = self._max_input_widget_height_by_lines(non_viewport_h)
849
+
850
+ # Container overhead above the inner QTextEdit
851
+ container_overhead = max(0, container.height() - self.height())
852
+ needed_container_h = needed_input_h + container_overhead
853
+ min_container_h = min_input_h + container_overhead
854
+
855
+ # Max cap by window fraction
856
+ try:
857
+ max_container_by_ratio = int(self.window.height() * self._auto_max_ratio)
858
+ except Exception:
859
+ max_container_by_ratio = 0 # fallback disables ratio cap if window unavailable
860
+
861
+ max_container_by_lines = max_input_by_lines + container_overhead
862
+ cap_container_max = max_container_by_lines
863
+ if max_container_by_ratio > 0:
864
+ cap_container_max = min(cap_container_max, max_container_by_ratio)
865
+
866
+ current_sizes = splitter.sizes()
867
+ if idx >= len(current_sizes):
868
+ return
869
+ current_container_h = current_sizes[idx]
870
+
871
+ # Decide on action
872
+ target_container_h = None
873
+
874
+ # Shrink only when requested or effectively single line
875
+ if minimize_if_single or self._should_shrink_to_min(doc_h):
876
+ if current_container_h > min_container_h + 1:
877
+ target_container_h = min_container_h
878
+
879
+ # Expand if focused (or forced), but only up to caps
880
+ elif can_expand:
881
+ desired = min(needed_container_h, cap_container_max)
882
+ if desired > current_container_h + 1:
883
+ target_container_h = desired
884
+
885
+ # Apply if needed
886
+ if target_container_h is None:
887
+ return
888
+
889
+ total = sum(current_sizes)
890
+ # Clamp to splitter total height
891
+ target_container_h = max(0, min(target_container_h, total))
892
+
893
+ if abs(target_container_h - current_container_h) <= 1:
894
+ return
895
+
896
+ # Prepare new sizes (2 widgets expected: output at 0, input at 1)
897
+ new_sizes = list(current_sizes)
898
+ # Distribute delta to other panes; here we have exactly 2
899
+ other_total = total - current_container_h
900
+ new_other_total = total - target_container_h
901
+ if other_total <= 0:
902
+ # degenerate case; just set directly
903
+ pass
904
+ else:
905
+ # Scale other widgets proportionally
906
+ scale = new_other_total / float(other_total) if other_total > 0 else 1.0
907
+ for i in range(len(new_sizes)):
908
+ if i != idx:
909
+ new_sizes[i] = int(round(new_sizes[i] * scale))
910
+ new_sizes[idx] = int(target_container_h)
911
+
912
+ # Final clamp to preserve sum
913
+ diff = total - sum(new_sizes)
914
+ if diff != 0 and len(new_sizes) > 0:
915
+ # Adjust the first non-target pane to fix rounding
916
+ for i in range(len(new_sizes)):
917
+ if i != idx:
918
+ new_sizes[i] += diff
919
+ break
920
+
921
+ self._splitter_resize_in_progress = True
922
+ try:
923
+ old_block = splitter.blockSignals(True)
924
+ splitter.setSizes(new_sizes)
925
+ splitter.blockSignals(old_block)
926
+ finally:
927
+ self._splitter_resize_in_progress = False
928
+
929
+ # Keep stored sizes in sync with app expectations (mirrors ChatMain.on_splitter_moved)
930
+ try:
931
+ tabs = self.window.ui.tabs
932
+ if "input" in tabs:
933
+ t_idx = tabs['input'].currentIndex()
934
+ if t_idx != 0:
935
+ self.window.controller.ui.splitter_output_size_files = new_sizes
936
+ else:
937
+ self.window.controller.ui.splitter_output_size_input = new_sizes
938
+ except Exception:
939
+ pass
940
+
941
+ self._last_target_container_h = target_container_h
942
+
943
+ def collapse_to_min(self):
944
+ """Public helper to collapse input area to minimal height."""
945
+ self._schedule_auto_resize(force=True, enforce_minimize_if_single=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygpt-net
3
- Version: 2.6.33
3
+ Version: 2.6.34
4
4
  Summary: Desktop AI Assistant powered by: OpenAI GPT-5, GPT-4, o1, o3, Gemini, Claude, Grok, DeepSeek, and other models supported by Llama Index, and Ollama. Chatbot, agents, completion, image generation, vision analysis, speech-to-text, plugins, internet access, file handling, command execution and more.
5
5
  License: MIT
6
6
  Keywords: ai,api,api key,app,assistant,bielik,chat,chatbot,chatgpt,claude,dall-e,deepseek,desktop,gemini,gpt,gpt-3.5,gpt-4,gpt-4-vision,gpt-4o,gpt-5,gpt-oss,gpt3.5,gpt4,grok,langchain,llama-index,llama3,mistral,o1,o3,ollama,openai,presets,py-gpt,py_gpt,pygpt,pyside,qt,text completion,tts,ui,vision,whisper
@@ -118,7 +118,7 @@ Description-Content-Type: text/markdown
118
118
 
119
119
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
120
120
 
121
- Release: **2.6.33** | build: **2025-09-02** | Python: **>=3.10, <3.14**
121
+ Release: **2.6.34** | build: **2025-09-03** | Python: **>=3.10, <3.14**
122
122
 
123
123
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
124
124
  >
@@ -3565,6 +3565,13 @@ may consume additional tokens that are not displayed in the main window.
3565
3565
 
3566
3566
  ## Recent changes:
3567
3567
 
3568
+ **2.6.34 (2025-09-03)**
3569
+
3570
+ - Added auto-resizing for input.
3571
+ - Added file management to the OpenAI Vector Stores tool.
3572
+ - Implemented removal of script tags when exporting HTML.
3573
+ - Enabled maintaining custom resolution in Painter.
3574
+
3568
3575
  **2.6.33 (2025-09-02)**
3569
3576
 
3570
3577
  - Added a "crop" option, auto-resize canvas, and layer support to Painter.