pygpt-net 2.7.9__py3-none-any.whl → 2.7.10__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 (102) hide show
  1. pygpt_net/CHANGELOG.txt +9 -0
  2. pygpt_net/LICENSE +1 -1
  3. pygpt_net/__init__.py +3 -3
  4. pygpt_net/config.py +15 -1
  5. pygpt_net/controller/chat/common.py +5 -4
  6. pygpt_net/controller/chat/image.py +3 -3
  7. pygpt_net/controller/chat/stream.py +76 -41
  8. pygpt_net/controller/chat/stream_worker.py +3 -3
  9. pygpt_net/controller/ctx/extra.py +3 -1
  10. pygpt_net/controller/dialogs/debug.py +37 -8
  11. pygpt_net/controller/kernel/kernel.py +3 -7
  12. pygpt_net/controller/lang/custom.py +25 -12
  13. pygpt_net/controller/lang/lang.py +45 -3
  14. pygpt_net/controller/lang/mapping.py +15 -2
  15. pygpt_net/controller/notepad/notepad.py +68 -25
  16. pygpt_net/controller/presets/editor.py +5 -1
  17. pygpt_net/controller/presets/presets.py +17 -5
  18. pygpt_net/controller/theme/theme.py +11 -2
  19. pygpt_net/controller/ui/tabs.py +1 -1
  20. pygpt_net/core/ctx/output.py +38 -12
  21. pygpt_net/core/db/database.py +4 -2
  22. pygpt_net/core/debug/console/console.py +30 -2
  23. pygpt_net/core/debug/context.py +2 -1
  24. pygpt_net/core/debug/ui.py +26 -4
  25. pygpt_net/core/filesystem/filesystem.py +6 -2
  26. pygpt_net/core/notepad/notepad.py +2 -2
  27. pygpt_net/core/tabs/tabs.py +79 -19
  28. pygpt_net/data/config/config.json +3 -3
  29. pygpt_net/data/config/models.json +3 -3
  30. pygpt_net/data/config/settings.json +12 -0
  31. pygpt_net/data/locale/locale.ar.ini +1833 -0
  32. pygpt_net/data/locale/locale.bg.ini +1833 -0
  33. pygpt_net/data/locale/locale.cs.ini +1833 -0
  34. pygpt_net/data/locale/locale.da.ini +1833 -0
  35. pygpt_net/data/locale/locale.de.ini +4 -1
  36. pygpt_net/data/locale/locale.en.ini +70 -67
  37. pygpt_net/data/locale/locale.es.ini +4 -1
  38. pygpt_net/data/locale/locale.fi.ini +1833 -0
  39. pygpt_net/data/locale/locale.fr.ini +4 -1
  40. pygpt_net/data/locale/locale.he.ini +1833 -0
  41. pygpt_net/data/locale/locale.hi.ini +1833 -0
  42. pygpt_net/data/locale/locale.hu.ini +1833 -0
  43. pygpt_net/data/locale/locale.it.ini +4 -1
  44. pygpt_net/data/locale/locale.ja.ini +1833 -0
  45. pygpt_net/data/locale/locale.ko.ini +1833 -0
  46. pygpt_net/data/locale/locale.nl.ini +1833 -0
  47. pygpt_net/data/locale/locale.no.ini +1833 -0
  48. pygpt_net/data/locale/locale.pl.ini +5 -2
  49. pygpt_net/data/locale/locale.pt.ini +1833 -0
  50. pygpt_net/data/locale/locale.ro.ini +1833 -0
  51. pygpt_net/data/locale/locale.ru.ini +1833 -0
  52. pygpt_net/data/locale/locale.sk.ini +1833 -0
  53. pygpt_net/data/locale/locale.sv.ini +1833 -0
  54. pygpt_net/data/locale/locale.tr.ini +1833 -0
  55. pygpt_net/data/locale/locale.uk.ini +4 -1
  56. pygpt_net/data/locale/locale.zh.ini +4 -1
  57. pygpt_net/item/notepad.py +8 -2
  58. pygpt_net/migrations/Version20260121190000.py +25 -0
  59. pygpt_net/migrations/Version20260122140000.py +25 -0
  60. pygpt_net/migrations/__init__.py +5 -1
  61. pygpt_net/preload.py +246 -3
  62. pygpt_net/provider/api/__init__.py +16 -2
  63. pygpt_net/provider/api/anthropic/__init__.py +21 -7
  64. pygpt_net/provider/api/google/__init__.py +21 -7
  65. pygpt_net/provider/api/google/image.py +89 -2
  66. pygpt_net/provider/api/google/video.py +2 -2
  67. pygpt_net/provider/api/openai/__init__.py +26 -11
  68. pygpt_net/provider/api/openai/image.py +79 -3
  69. pygpt_net/provider/api/openai/responses.py +11 -31
  70. pygpt_net/provider/api/openai/video.py +2 -2
  71. pygpt_net/provider/api/x_ai/__init__.py +21 -7
  72. pygpt_net/provider/core/notepad/db_sqlite/storage.py +53 -10
  73. pygpt_net/tools/agent_builder/ui/dialogs.py +2 -1
  74. pygpt_net/tools/audio_transcriber/ui/dialogs.py +2 -1
  75. pygpt_net/tools/code_interpreter/ui/dialogs.py +2 -1
  76. pygpt_net/tools/html_canvas/ui/dialogs.py +2 -1
  77. pygpt_net/tools/image_viewer/ui/dialogs.py +3 -5
  78. pygpt_net/tools/indexer/ui/dialogs.py +2 -1
  79. pygpt_net/tools/media_player/ui/dialogs.py +2 -1
  80. pygpt_net/tools/translator/ui/dialogs.py +2 -1
  81. pygpt_net/tools/translator/ui/widgets.py +6 -2
  82. pygpt_net/ui/dialog/about.py +2 -2
  83. pygpt_net/ui/dialog/db.py +2 -1
  84. pygpt_net/ui/dialog/debug.py +169 -6
  85. pygpt_net/ui/dialog/logger.py +6 -2
  86. pygpt_net/ui/dialog/models.py +36 -3
  87. pygpt_net/ui/dialog/preset.py +5 -1
  88. pygpt_net/ui/dialog/remote_store.py +2 -1
  89. pygpt_net/ui/main.py +3 -2
  90. pygpt_net/ui/widget/dialog/editor_file.py +2 -1
  91. pygpt_net/ui/widget/lists/debug.py +12 -7
  92. pygpt_net/ui/widget/option/checkbox.py +2 -8
  93. pygpt_net/ui/widget/option/combo.py +10 -2
  94. pygpt_net/ui/widget/textarea/console.py +156 -7
  95. pygpt_net/ui/widget/textarea/highlight.py +66 -0
  96. pygpt_net/ui/widget/textarea/input.py +624 -57
  97. pygpt_net/ui/widget/textarea/notepad.py +294 -27
  98. {pygpt_net-2.7.9.dist-info → pygpt_net-2.7.10.dist-info}/LICENSE +1 -1
  99. {pygpt_net-2.7.9.dist-info → pygpt_net-2.7.10.dist-info}/METADATA +11 -64
  100. {pygpt_net-2.7.9.dist-info → pygpt_net-2.7.10.dist-info}/RECORD +102 -81
  101. {pygpt_net-2.7.9.dist-info → pygpt_net-2.7.10.dist-info}/WHEEL +0 -0
  102. {pygpt_net-2.7.9.dist-info → pygpt_net-2.7.10.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,12 @@
1
+ 2.7.10 (2026-02-03)
2
+
3
+ - Fixed an issue where an avatar could be overwritten when creating a new preset.
4
+ - Fixed an issue where a new context was not created when opening a new tab in the second column.
5
+ - Added prompt history navigation to the input field (Ctrl + Up/Down arrow keys).
6
+ - Added initial image centering when loading the Image Viewer.
7
+ - Added a Mark/Unmark feature to the Notepad widget.
8
+ - Added 18 new languages: Arabic (ar), Bulgarian (bg), Czech (cs), Danish (da), Finnish (fi), Hebrew (he), Hindi (hi), Hungarian (hu), Japanese (ja), Korean (ko), Dutch (nl), Norwegian (no), Portuguese (pt), Romanian (ro), Russian (ru), Slovak (sk), Swedish (sv), Turkish (tr).
9
+
1
10
  2.7.9 (2026-01-08)
2
11
 
3
12
  - Improved realtime audio mode.
pygpt_net/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Marcin Szczygliński
3
+ Copyright (c) 2026 Marcin Szczygliński
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2026-01-08 00:00:00 #
9
+ # Updated Date: 2026-02-03 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2026, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.7.9"
17
- __build__ = "2026-01-08"
16
+ __version__ = "2.7.10"
17
+ __build__ = "2026-02-03"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/config.py CHANGED
@@ -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.09.01 23:00:00 #
9
+ # Updated Date: 2026.01.20 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -25,6 +25,20 @@ from pygpt_net.core.types.console import Color
25
25
  _RE_VERSION = re.compile(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]')
26
26
  _RE_BUILD = re.compile(r'__build__\s*=\s*[\'"]([^\'"]*)[\'"]')
27
27
 
28
+ def quick_get_config_value(key: str, default: any = None) -> any:
29
+ """
30
+ Quick get config value without initializing the whole app
31
+
32
+ :param key: key
33
+ :param default: default value
34
+ :return: value
35
+ """
36
+ config = Config()
37
+ workdir = config.prepare_workdir()
38
+ config.set_workdir(workdir)
39
+ config.load_config(all=False)
40
+ return config.get(key, default)
41
+
28
42
 
29
43
  class Config:
30
44
  CONFIG_DIR = 'pygpt-net'
@@ -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.09.25 12:00:00 #
9
+ # Updated Date: 2026.01.21 13:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -322,9 +322,10 @@ class Common:
322
322
 
323
323
  def stop_client(self):
324
324
  """Stop all clients"""
325
- return # TODO: make it work without connection error after close
326
- self.window.core.api.openai.safe_close()
327
- self.window.core.api.google.safe_close()
325
+ try:
326
+ self.window.core.api.stop()
327
+ except Exception:
328
+ pass
328
329
 
329
330
  def check_api_key(
330
331
  self,
@@ -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: 2026.01.01 15:00:00 #
9
+ # Updated Date: 2026.01.23 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -84,11 +84,11 @@ class Image:
84
84
  if core.config.get("video.remix"):
85
85
  tmp_video_id = item.extra.get('video_id')
86
86
  if video_id is None and tmp_video_id:
87
- video_id = tmp_video_id
87
+ video_id = core.filesystem.to_workdir(tmp_video_id, auto_prefix=False)
88
88
  if core.config.get("img.remix"):
89
89
  tmp_image_id = item.extra.get('image_id')
90
90
  if image_id is None and tmp_image_id:
91
- image_id = tmp_image_id
91
+ image_id = core.filesystem.to_workdir(tmp_image_id)
92
92
  if image_id and video_id:
93
93
  break
94
94
 
@@ -6,10 +6,10 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2026.01.05 20:00:00 #
9
+ # Updated Date: 2026.01.22 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from typing import Optional
12
+ from typing import Optional, Any
13
13
 
14
14
  from PySide6.QtCore import Slot, QObject
15
15
 
@@ -29,16 +29,27 @@ class Stream(QObject):
29
29
  """
30
30
  super().__init__()
31
31
  self.window = window
32
- self.ctx = None
33
- self.mode = None
34
- self.thread = None
35
- self.worker = None
36
- self.is_response = False
37
- self.reply = False
38
- self.internal = False
39
- self.context = None
40
- self.extra = {}
41
- self.instance = None
32
+ self.instance = None # cached get renderer instance method
33
+ self.pids = {} # {pid -> {data}}, workers are tracked per PID
34
+
35
+ def get_pid_by_ctx(self, ctx: CtxItem) -> Optional[int]:
36
+ """
37
+ Get process ID by context item
38
+
39
+ :param ctx: Context item
40
+ :return: Process ID or None
41
+ """
42
+ if ctx and ctx.meta:
43
+ return self.window.core.ctx.output.get_pid(ctx.meta)
44
+ return None
45
+
46
+ def get_pid_ids(self) -> list[int]:
47
+ """
48
+ Get all active process IDs
49
+
50
+ :return: List of process IDs
51
+ """
52
+ return list(self.pids.keys())
42
53
 
43
54
  def append(
44
55
  self,
@@ -61,13 +72,20 @@ class Stream(QObject):
61
72
  :param context: Optional BridgeContext for additional context
62
73
  :param extra: Additional data to pass to the stream
63
74
  """
64
- self.ctx = ctx
65
- self.mode = mode
66
- self.is_response = is_response
67
- self.reply = reply
68
- self.internal = internal
69
- self.context = context
70
- self.extra = extra if extra is not None else {}
75
+ pid = self.get_pid_by_ctx(ctx)
76
+ if pid is None:
77
+ return # abort streaming if no PID found
78
+ if pid not in self.pids:
79
+ self.pids[pid] = {}
80
+
81
+ pid_data = self.pids[pid]
82
+ pid_data["ctx"] = ctx
83
+ pid_data["mode"] = mode
84
+ pid_data["is_response"] = is_response
85
+ pid_data["reply"] = reply
86
+ pid_data["internal"] = internal
87
+ pid_data["context"] = context
88
+ pid_data["extra"] = extra if extra is not None else {}
71
89
 
72
90
  # cache the get renderer instance method
73
91
  if self.instance is None:
@@ -79,10 +97,10 @@ class Stream(QObject):
79
97
  worker.signals.errorOccurred.connect(self.handleError)
80
98
  worker.signals.end.connect(self.handleEnd)
81
99
  worker.signals.chunk.connect(self.handleChunk)
82
- ctx.stream = None
83
- self.worker = worker
100
+ ctx.stream = None # clear reference to generator
84
101
 
85
- self.window.core.debug.info("[chat] Stream begin...")
102
+ pid_data["worker"] = worker # keep reference to avoid GC, per PID
103
+ self.window.core.debug.info(f"[chat] Stream begin... PID={pid}")
86
104
  self.window.threadpool.start(worker)
87
105
 
88
106
  @Slot(object)
@@ -92,31 +110,41 @@ class Stream(QObject):
92
110
 
93
111
  :param ctx: Context item
94
112
  """
113
+ pid = self.get_pid_by_ctx(ctx)
114
+ if pid is None or pid not in self.pids:
115
+ return # abort if no PID found or not tracked
116
+ pid_data = self.pids[pid]
117
+
95
118
  controller = self.window.controller
96
119
  controller.ui.update_tokens()
120
+ mode = pid_data["mode"]
97
121
 
98
- data = {"meta": self.ctx.meta, "ctx": self.ctx}
122
+ data = {
123
+ "meta": pid_data["ctx"].meta,
124
+ "ctx": pid_data["ctx"]
125
+ }
99
126
  event = RenderEvent(RenderEvent.STREAM_END, data)
100
127
  self.window.dispatch(event)
101
128
  controller.chat.output.handle_after(
102
129
  ctx=ctx,
103
- mode=self.mode,
130
+ mode=mode,
104
131
  stream=True,
105
132
  )
106
133
 
107
- if self.mode == MODE_ASSISTANT:
134
+ if mode == MODE_ASSISTANT:
108
135
  controller.assistant.threads.handle_output_message_after_stream(ctx)
109
136
  else:
110
- if self.is_response:
137
+ if pid_data["is_response"]:
111
138
  controller.chat.response.post_handle(
112
139
  ctx=ctx,
113
- mode=self.mode,
140
+ mode=mode,
114
141
  stream=True,
115
- reply=self.reply,
116
- internal=self.internal,
142
+ reply=pid_data["reply"],
143
+ internal=pid_data["internal"],
117
144
  )
118
145
 
119
- self.worker = None
146
+ pid_data["worker"] = None # release worker reference
147
+ del self.pids[pid] # remove PID tracking
120
148
 
121
149
  @Slot(object, str, bool)
122
150
  def handleChunk(
@@ -150,26 +178,33 @@ class Stream(QObject):
150
178
  """
151
179
  self.window.dispatch(event)
152
180
 
153
- @Slot(object)
154
- def handleError(self, error):
181
+ @Slot(object, object)
182
+ def handleError(self, ctx: CtxItem, error: Any):
155
183
  """
156
184
  Slot for handling stream errors
157
185
 
186
+ :param ctx: Context item
158
187
  :param error: Exception or error message
159
188
  """
189
+ pid = self.get_pid_by_ctx(ctx)
190
+ if pid is None or pid not in self.pids:
191
+ return # abort if no PID found or not tracked
192
+ pid_data = self.pids[pid]
193
+
160
194
  self.window.core.debug.log(error)
161
- if self.is_response:
162
- if not isinstance(self.extra, dict):
163
- self.extra = {}
164
- self.extra["error"] = error
165
- self.window.controller.chat.response.failed(self.context, self.extra)
195
+ if pid_data["is_response"]:
196
+ if not isinstance(pid_data["extra"], dict):
197
+ pid_data["extra"] = {}
198
+ pid_data["extra"]["error"] = error
199
+ self.window.controller.chat.response.failed(pid_data["context"], pid_data["extra"])
166
200
  self.window.controller.chat.response.post_handle(
167
- ctx=self.ctx,
168
- mode=self.mode,
201
+ ctx=pid_data["ctx"],
202
+ mode=pid_data["mode"],
169
203
  stream=True,
170
- reply=self.reply,
171
- internal=self.internal,
204
+ reply=pid_data["reply"],
205
+ internal=pid_data["internal"],
172
206
  )
207
+ # TODO: remove PID from tracking on error?
173
208
 
174
209
  def log(self, data: object):
175
210
  """
@@ -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: 2026.01.05 20:00:00 #
9
+ # Updated Date: 2026.01.22 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import io
@@ -39,7 +39,7 @@ class WorkerSignals(QObject):
39
39
  - `chunk`: CtxItem, chunk str, begin bool
40
40
  """
41
41
  end = Signal(object)
42
- errorOccurred = Signal(Exception)
42
+ errorOccurred = Signal(object, Exception)
43
43
  eventReady = Signal(object)
44
44
  chunk = Signal(object, str, bool) # CtxItem, chunk, begin
45
45
 
@@ -423,7 +423,7 @@ class StreamWorker(QRunnable):
423
423
 
424
424
  # Emit error and end
425
425
  if state.error:
426
- emit_error(state.error)
426
+ emit_error(ctx, state.error)
427
427
  ctx.msg_id = None
428
428
  # clear response_id on error - this prevents no response_id in API on next call
429
429
  # prev messages will be sent again, new response_id will be generated
@@ -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.09.15 22:00:00 #
9
+ # Updated Date: 2026.01.22 06:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtWidgets import QApplication
@@ -245,6 +245,8 @@ class Extra:
245
245
  current_item = self.window.core.ctx.get_item_by_id(item_id)
246
246
  if prev_item is not None and current_item is not None:
247
247
  prev_item.output += "\n\n" + current_item.output
248
+ if current_item.msg_id is not None:
249
+ prev_item.msg_id = current_item.msg_id # update msg_id to the latest
248
250
  self.window.core.ctx.update_item(prev_item)
249
251
  self.window.core.ctx.remove_item(current_item.id)
250
252
  self.window.controller.ctx.refresh()
@@ -6,9 +6,10 @@
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.09.14 20:00:00 #
9
+ # Updated Date: 2026.01.21 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ import time
12
13
  from typing import Any
13
14
 
14
15
  from PySide6.QtCore import Qt
@@ -62,6 +63,8 @@ class Debug:
62
63
  'ui': UIDebug(self.window)
63
64
  }
64
65
 
66
+ self.is_realtime = False # real-time data update
67
+
65
68
  # prepare debug ids
66
69
  self.ids = self.workers.keys()
67
70
  self.models = {}
@@ -77,12 +80,30 @@ class Debug:
77
80
  self.active[id] = False
78
81
  self.idx[id] = 0
79
82
 
83
+ def refresh(self, id: str):
84
+ """
85
+ Refresh debug data
86
+
87
+ :param id: debug id
88
+ """
89
+ if id in self.initialized:
90
+ self.initialized[id] = False
91
+
80
92
  def begin(self, id: str):
81
93
  """
82
94
  Begin debug data
83
95
 
84
96
  :param id: debug id
85
97
  """
98
+ if self.initialized[id] and not self.is_realtime:
99
+ if self.is_active(id):
100
+ return # skip if already initialized and realtime disabled
101
+
102
+ upd_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
103
+ self.window.ui.debug[id].last_update_label.setText(
104
+ f"Last update: {upd_time}"
105
+ )
106
+
86
107
  model = self.models.get(id, None)
87
108
  dialog = self.window.ui.debug[id]
88
109
  dialog.setModel(model)
@@ -92,18 +113,28 @@ class Debug:
92
113
  if id not in self.counters or self.counters[id] != model.rowCount():
93
114
  model.removeRows(0, model.rowCount())
94
115
  self.initialized[id] = False
95
- dialog.setUpdatesEnabled(True)
96
116
  self.idx[id] = 0
97
117
 
118
+ def set_realtime(self, id: str, enabled: bool):
119
+ """
120
+ Set realtime mode
121
+
122
+ :param id: debug id
123
+ :param enabled: enable realtime
124
+ """
125
+ self.is_realtime = enabled
126
+
98
127
  def end(self, id: str):
99
128
  """
100
129
  End debug data
101
130
 
102
131
  :param id: debug id
103
132
  """
133
+ dialog = self.window.ui.debug[id]
104
134
  self.counters[id] = self.idx[id]
105
135
  self.initialized[id] = True
106
136
  self.window.ui.debug[id].on_data_end()
137
+ dialog.setUpdatesEnabled(True)
107
138
 
108
139
  def add(self, id: str, k: str, v: Any):
109
140
  """
@@ -113,23 +144,21 @@ class Debug:
113
144
  :param k: key
114
145
  :param v: value
115
146
  """
116
- dialog = self.window.ui.debug[id]
117
- dialog.setUpdatesEnabled(False)
118
147
  model = self.models.get(id, None)
119
148
  if self.initialized[id] is False:
120
149
  idx = model.rowCount()
121
150
  model.insertRow(idx)
122
151
  model.setData(model.index(idx, self.DBG_KEY), k)
123
152
  model.setData(model.index(idx, self.DBG_VALUE), v)
153
+ self.idx[id] += 1
124
154
  else:
125
155
  for idx in range(0, model.rowCount()):
156
+ self.idx[id] += 1
126
157
  if model.index(idx, self.DBG_KEY).data() == k:
158
+ if model.index(idx, self.DBG_VALUE).data() == v:
159
+ continue # no changes, skip update
127
160
  model.setData(model.index(idx, self.DBG_VALUE), v)
128
- self.idx[id] += 1
129
- dialog.setUpdatesEnabled(True)
130
161
  return
131
- self.idx[id] += 1
132
- dialog.setUpdatesEnabled(True)
133
162
 
134
163
  def get_ids(self) -> list:
135
164
  """
@@ -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.12.28 00:00:00 #
9
+ # Updated Date: 2026.01.21 13:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import threading
@@ -404,12 +404,8 @@ class Kernel:
404
404
  return threading.current_thread() is threading.main_thread()
405
405
 
406
406
  def close_clients(self):
407
- """
408
- Close all active clients associated with the kernel.
409
- """
410
- w = self.window
407
+ """Safe close all active clients"""
411
408
  try:
412
- w.core.api.openai.safe_close()
413
- w.core.api.google.safe_close()
409
+ self.window.core.api.close()
414
410
  except Exception:
415
411
  pass
@@ -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.21 21:00:00 #
9
+ # Updated Date: 2026.01.21 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -23,6 +23,8 @@ from pygpt_net.core.types import (
23
23
  MODE_LLAMA_INDEX,
24
24
  MODE_VISION,
25
25
  MODE_RESEARCH,
26
+ MODE_AGENT_OPENAI,
27
+ MODE_COMPUTER,
26
28
  )
27
29
  from pygpt_net.utils import trans
28
30
 
@@ -52,17 +54,28 @@ class Custom:
52
54
  self.window.ui.config['assistant']['tool.code_interpreter'].box.setText(
53
55
  trans('assistant.tool.code_interpreter')
54
56
  )
55
- self.window.ui.config['preset'][MODE_CHAT].box.setText(trans("preset.chat"))
56
- self.window.ui.config['preset'][MODE_COMPLETION].box.setText(trans("preset.completion"))
57
- self.window.ui.config['preset'][MODE_IMAGE].box.setText(trans("preset.img"))
58
- # self.window.ui.config['preset'][MODE_VISION].box.setText(trans("preset.vision"))
59
- # self.window.ui.config['preset'][MODE_LANGCHAIN].box.setText(trans("preset.langchain"))
60
- self.window.ui.config['preset'][MODE_LLAMA_INDEX].box.setText(trans("preset.llama_index"))
61
- self.window.ui.config['preset'][MODE_AGENT].box.setText(trans("preset.agent"))
62
- self.window.ui.config['preset'][MODE_AGENT_LLAMA].box.setText(trans("preset.agent_llama"))
63
- self.window.ui.config['preset'][MODE_EXPERT].box.setText(trans("preset.expert"))
64
- self.window.ui.config['preset'][MODE_AUDIO].box.setText(trans("preset.audio"))
65
- self.window.ui.config['preset'][MODE_RESEARCH].box.setText(trans("preset.research"))
57
+
58
+ # preset editor
59
+ self.window.ui.config['preset'][MODE_CHAT].setText(trans("preset.chat"))
60
+ self.window.ui.config['preset'][MODE_COMPLETION].setText(trans("preset.completion"))
61
+ self.window.ui.config['preset'][MODE_IMAGE].setText(trans("preset.img"))
62
+ # self.window.ui.config['preset'][MODE_VISION].setText(trans("preset.vision"))
63
+ # self.window.ui.config['preset'][MODE_LANGCHAIN].setText(trans("preset.langchain"))
64
+ self.window.ui.config['preset'][MODE_LLAMA_INDEX].setText(trans("preset.llama_index"))
65
+ self.window.ui.config['preset'][MODE_AGENT].setText(trans("preset.agent"))
66
+ self.window.ui.config['preset'][MODE_AGENT_LLAMA].setText(trans("preset.agent_llama"))
67
+ self.window.ui.config['preset'][MODE_AGENT_OPENAI].setText(trans("preset.agent_openai"))
68
+ self.window.ui.config['preset'][MODE_EXPERT].setText(trans("preset.expert"))
69
+ self.window.ui.config['preset'][MODE_AUDIO].setText(trans("preset.audio"))
70
+ self.window.ui.config['preset'][MODE_RESEARCH].setText(trans("preset.research"))
71
+ self.window.ui.config['preset'][MODE_COMPUTER].setText(trans("preset.computer"))
72
+ self.window.ui.config['preset']["ai_personalize"].setText(trans("preset.ai_personalize"))
73
+
74
+ self.window.ui.tabs['preset.editor.tabs'].setTabText(0, trans("preset.tab.general"))
75
+ self.window.ui.tabs['preset.editor.tabs'].setTabText(1, trans("preset.tab.personalize"))
76
+ self.window.ui.tabs['preset.editor.tabs'].setTabText(2, trans("preset.tab.experts"))
77
+ self.window.ui.tabs['preset.editor.tabs'].setTabText(3, trans("preset.tab.remote_tools"))
78
+
66
79
 
67
80
  self.window.ui.config['global']['img_raw'].setText(trans("img.raw"))
68
81
 
@@ -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.24 23:00:00 #
9
+ # Updated Date: 2026.01.22 18:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtGui import QAction, QActionGroup
@@ -20,6 +20,36 @@ from .settings import Settings
20
20
 
21
21
 
22
22
  class Lang:
23
+
24
+ LANG_MAPPING = {
25
+ "en": "English (default)",
26
+ "pl": "Polski",
27
+ "de": "Deutsch",
28
+ "es": "Español",
29
+ "fr": "Français",
30
+ "it": "Italiano",
31
+ "uk": "Українська",
32
+ "ru": "Русский",
33
+ "zh": "中文",
34
+ "ja": "日本語",
35
+ "ar": "العربية",
36
+ "pt": "Português",
37
+ "hi": "हिन्दी",
38
+ "ko": "한국어",
39
+ "tr": "Türkçe",
40
+ "he": "עברית",
41
+ "nl": "Nederlands",
42
+ "sv": "Svenska",
43
+ "fi": "Suomi",
44
+ "no": "Norsk",
45
+ "da": "Dansk",
46
+ "cs": "Čeština",
47
+ "sk": "Slovenčina",
48
+ "bg": "Български",
49
+ "hu": "Magyar",
50
+ "ro": "Română",
51
+ }
52
+
23
53
  def __init__(self, window=None):
24
54
  """
25
55
  Language switch controller
@@ -56,11 +86,21 @@ class Lang:
56
86
  menu_lang = menu['lang']
57
87
  menu_root = menu['menu.lang']
58
88
  for lang in langs:
59
- act = QAction(lang.upper(), w, checkable=True)
89
+ title = self.LANG_MAPPING.get(lang, "")
90
+ act_title = lang.upper() + (" - " + title if title else "")
91
+ act = QAction(act_title, w, checkable=True)
60
92
  act.setData(lang)
61
93
  menu_lang[lang] = act
94
+
95
+ # sort by code, but put 'en' first
96
+ sorted_langs = sorted(menu_lang.items(), key=lambda x: (x[0] != 'en', x[0]))
97
+ menu_lang = dict(sorted_langs)
98
+
99
+ for act in menu_lang.values():
62
100
  self._lang_group.addAction(act)
63
101
  menu_root.addAction(act)
102
+ if act.data() == 'en':
103
+ menu_root.addSeparator()
64
104
  self.loaded = True
65
105
  self.update()
66
106
 
@@ -115,4 +155,6 @@ class Lang:
115
155
  def reload(self):
116
156
  """Reload language"""
117
157
  self.reload_config()
118
- self.setup()
158
+ self.setup()
159
+ current = self.window.core.config.get('lang')
160
+ self.toggle(current)