pygpt-net 2.7.8__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 (112) hide show
  1. pygpt_net/CHANGELOG.txt +14 -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/realtime/realtime.py +13 -1
  19. pygpt_net/controller/theme/theme.py +11 -2
  20. pygpt_net/controller/ui/tabs.py +1 -1
  21. pygpt_net/core/ctx/output.py +38 -12
  22. pygpt_net/core/db/database.py +4 -2
  23. pygpt_net/core/debug/console/console.py +30 -2
  24. pygpt_net/core/debug/context.py +2 -1
  25. pygpt_net/core/debug/ui.py +26 -4
  26. pygpt_net/core/filesystem/filesystem.py +6 -2
  27. pygpt_net/core/notepad/notepad.py +2 -2
  28. pygpt_net/core/tabs/tabs.py +79 -19
  29. pygpt_net/data/config/config.json +4 -3
  30. pygpt_net/data/config/models.json +37 -22
  31. pygpt_net/data/config/settings.json +12 -0
  32. pygpt_net/data/locale/locale.ar.ini +1833 -0
  33. pygpt_net/data/locale/locale.bg.ini +1833 -0
  34. pygpt_net/data/locale/locale.cs.ini +1833 -0
  35. pygpt_net/data/locale/locale.da.ini +1833 -0
  36. pygpt_net/data/locale/locale.de.ini +4 -1
  37. pygpt_net/data/locale/locale.en.ini +70 -67
  38. pygpt_net/data/locale/locale.es.ini +4 -1
  39. pygpt_net/data/locale/locale.fi.ini +1833 -0
  40. pygpt_net/data/locale/locale.fr.ini +4 -1
  41. pygpt_net/data/locale/locale.he.ini +1833 -0
  42. pygpt_net/data/locale/locale.hi.ini +1833 -0
  43. pygpt_net/data/locale/locale.hu.ini +1833 -0
  44. pygpt_net/data/locale/locale.it.ini +4 -1
  45. pygpt_net/data/locale/locale.ja.ini +1833 -0
  46. pygpt_net/data/locale/locale.ko.ini +1833 -0
  47. pygpt_net/data/locale/locale.nl.ini +1833 -0
  48. pygpt_net/data/locale/locale.no.ini +1833 -0
  49. pygpt_net/data/locale/locale.pl.ini +5 -2
  50. pygpt_net/data/locale/locale.pt.ini +1833 -0
  51. pygpt_net/data/locale/locale.ro.ini +1833 -0
  52. pygpt_net/data/locale/locale.ru.ini +1833 -0
  53. pygpt_net/data/locale/locale.sk.ini +1833 -0
  54. pygpt_net/data/locale/locale.sv.ini +1833 -0
  55. pygpt_net/data/locale/locale.tr.ini +1833 -0
  56. pygpt_net/data/locale/locale.uk.ini +4 -1
  57. pygpt_net/data/locale/locale.zh.ini +4 -1
  58. pygpt_net/item/notepad.py +8 -2
  59. pygpt_net/migrations/Version20260121190000.py +25 -0
  60. pygpt_net/migrations/Version20260122140000.py +25 -0
  61. pygpt_net/migrations/__init__.py +5 -1
  62. pygpt_net/preload.py +246 -3
  63. pygpt_net/provider/api/__init__.py +16 -2
  64. pygpt_net/provider/api/anthropic/__init__.py +21 -7
  65. pygpt_net/provider/api/google/__init__.py +21 -7
  66. pygpt_net/provider/api/google/image.py +89 -2
  67. pygpt_net/provider/api/google/realtime/client.py +70 -24
  68. pygpt_net/provider/api/google/realtime/realtime.py +48 -12
  69. pygpt_net/provider/api/google/video.py +2 -2
  70. pygpt_net/provider/api/openai/__init__.py +26 -11
  71. pygpt_net/provider/api/openai/image.py +79 -3
  72. pygpt_net/provider/api/openai/realtime/realtime.py +26 -6
  73. pygpt_net/provider/api/openai/responses.py +11 -31
  74. pygpt_net/provider/api/openai/video.py +2 -2
  75. pygpt_net/provider/api/x_ai/__init__.py +21 -10
  76. pygpt_net/provider/api/x_ai/realtime/client.py +185 -146
  77. pygpt_net/provider/api/x_ai/realtime/realtime.py +30 -15
  78. pygpt_net/provider/api/x_ai/remote_tools.py +83 -0
  79. pygpt_net/provider/api/x_ai/tools.py +51 -0
  80. pygpt_net/provider/core/config/patch.py +12 -1
  81. pygpt_net/provider/core/model/patch.py +36 -1
  82. pygpt_net/provider/core/notepad/db_sqlite/storage.py +53 -10
  83. pygpt_net/tools/agent_builder/ui/dialogs.py +2 -1
  84. pygpt_net/tools/audio_transcriber/ui/dialogs.py +2 -1
  85. pygpt_net/tools/code_interpreter/ui/dialogs.py +2 -1
  86. pygpt_net/tools/html_canvas/ui/dialogs.py +2 -1
  87. pygpt_net/tools/image_viewer/ui/dialogs.py +3 -5
  88. pygpt_net/tools/indexer/ui/dialogs.py +2 -1
  89. pygpt_net/tools/media_player/ui/dialogs.py +2 -1
  90. pygpt_net/tools/translator/ui/dialogs.py +2 -1
  91. pygpt_net/tools/translator/ui/widgets.py +6 -2
  92. pygpt_net/ui/dialog/about.py +2 -2
  93. pygpt_net/ui/dialog/db.py +2 -1
  94. pygpt_net/ui/dialog/debug.py +169 -6
  95. pygpt_net/ui/dialog/logger.py +6 -2
  96. pygpt_net/ui/dialog/models.py +36 -3
  97. pygpt_net/ui/dialog/preset.py +5 -1
  98. pygpt_net/ui/dialog/remote_store.py +2 -1
  99. pygpt_net/ui/main.py +3 -2
  100. pygpt_net/ui/widget/dialog/editor_file.py +2 -1
  101. pygpt_net/ui/widget/lists/debug.py +12 -7
  102. pygpt_net/ui/widget/option/checkbox.py +2 -8
  103. pygpt_net/ui/widget/option/combo.py +10 -2
  104. pygpt_net/ui/widget/textarea/console.py +156 -7
  105. pygpt_net/ui/widget/textarea/highlight.py +66 -0
  106. pygpt_net/ui/widget/textarea/input.py +624 -57
  107. pygpt_net/ui/widget/textarea/notepad.py +294 -27
  108. {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/LICENSE +1 -1
  109. {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/METADATA +16 -64
  110. {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/RECORD +112 -91
  111. {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/WHEEL +0 -0
  112. {pygpt_net-2.7.8.dist-info → pygpt_net-2.7.10.dist-info}/entry_points.txt +0 -0
@@ -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.06 20:00:00 #
9
+ # Updated Date: 2026.01.07 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -27,7 +27,7 @@ class Realtime:
27
27
 
28
28
  def __init__(self, window=None):
29
29
  """
30
- OpenAI API realtime controller
30
+ xAI API realtime controller
31
31
 
32
32
  :param window: Window instance
33
33
  """
@@ -68,15 +68,11 @@ class Realtime:
68
68
  self.handler.set_debug(is_debug)
69
69
 
70
70
  # tools
71
- tools = []
72
- '''
73
- tools = self.window.core.api.xai.tools.prepare(model, context.external_functions)
74
- '''
71
+ tools = self.window.core.api.xai.tools.prepare_realtime(context.external_functions)
75
72
 
76
73
  # remote tools
77
74
  remote_tools = []
78
- '''
79
- remote_tools = self.window.core.api.openai.remote_tools.append_to_tools(
75
+ remote_tools = self.window.core.api.xai.remote.append_to_tools(
80
76
  mode=context.mode,
81
77
  model=model,
82
78
  stream=context.stream,
@@ -84,7 +80,6 @@ class Realtime:
84
80
  tools=remote_tools,
85
81
  preset=context.preset,
86
82
  )
87
- '''
88
83
 
89
84
  # handle sub-reply (tool results from tool calls)
90
85
  if context.ctx.internal:
@@ -107,6 +102,31 @@ class Realtime:
107
102
  self.handler.update_ctx(context.ctx)
108
103
  return True # do not start new session, just send tool results
109
104
 
105
+ # Resolve last session ID from history only (no fallback)
106
+ last_session_id = extract_last_session_id(context.history) if context.history else None
107
+ if is_debug:
108
+ print("[realtime session] Last ID", last_session_id)
109
+
110
+ # Enforce clean state rules prior to any live updates:
111
+ # - If there is no history: always reset live session to start fresh.
112
+ # - If history exists but has no resumable handle: close any active session to avoid continuation.
113
+ try:
114
+ history_len = len(context.history) if context.history else 0
115
+ except Exception:
116
+ history_len = 0
117
+
118
+ if history_len == 0:
119
+ if self.handler.is_session_active():
120
+ self.handler.close_session_sync()
121
+ try:
122
+ if context.ctx and isinstance(context.ctx.extra, dict):
123
+ context.ctx.extra.pop("rt_session_id", None)
124
+ except Exception:
125
+ pass
126
+ last_session_id = None # force new session
127
+ elif not last_session_id and self.handler.is_session_active():
128
+ self.handler.close_session_sync()
129
+
110
130
  # update auto-turn in active session
111
131
  if (self.handler.is_session_active()
112
132
  and (auto_turn != self.prev_auto_turn
@@ -121,13 +141,8 @@ class Realtime:
121
141
  self.window.update_status(trans("speech.listening"))
122
142
  return True # do not send new request if session is active
123
143
 
124
- # Last session ID
125
- last_session_id = extract_last_session_id(context.history)
126
- if is_debug:
127
- print("[realtime session] Last ID", last_session_id)
128
-
129
144
  # Voice
130
- voice = "ara"
145
+ voice = "Ara"
131
146
  try:
132
147
  v = self.window.core.plugins.get_option("audio_output", "xai_tts_voice")
133
148
  if v:
@@ -28,6 +28,7 @@ except Exception:
28
28
  x_x_search = None
29
29
  x_code_execution = None
30
30
  x_mcp = None
31
+ x_collections_search = None
31
32
 
32
33
 
33
34
  class Remote:
@@ -42,6 +43,9 @@ class Remote:
42
43
  - Builds xAI SDK tool objects for web_search, x_search, code_execution, MCP.
43
44
  - Returns include flags, max_turns and use_encrypted_content settings.
44
45
 
46
+ Realtime (WebSocket) tools builder for xAI:
47
+ - Produces Realtime-compatible tool descriptors to be attached to session.update/response.create.
48
+
45
49
  :param window: Window instance
46
50
  """
47
51
  self.window = window
@@ -269,6 +273,85 @@ class Remote:
269
273
  "reason": http_reason,
270
274
  }
271
275
 
276
+ # ---------- Realtime tools (WebSocket) ----------
277
+
278
+ def append_to_tools(
279
+ self,
280
+ mode: str,
281
+ model: ModelItem,
282
+ stream: bool,
283
+ is_expert_call: bool,
284
+ tools: List[dict],
285
+ preset=None
286
+ ) -> List[dict]:
287
+ """
288
+ Prepare remote tools for xAI Realtime sessions.
289
+
290
+ The returned list contains Realtime/WebSocket tool descriptors understood by xAI’s Grok Voice Agent API.
291
+ Server-side tools are executed by xAI; client-side tools are added separately in the caller.
292
+
293
+ :param mode: Agent mode (unused here, kept for signature compatibility)
294
+ :param model: Model item
295
+ :param stream: Streaming flag
296
+ :param is_expert_call: Expert call flag (unused here for xAI)
297
+ :param tools: Current tools list to extend
298
+ :param preset: Preset item (unused here)
299
+ :return: Extended tools list
300
+ """
301
+ cfg = self.window.core.config
302
+ enabled_global = self.window.controller.chat.remote_tools.enabled
303
+
304
+ # Toggles
305
+ is_web_enabled = enabled_global(model, "web_search")
306
+ is_x_enabled = bool(cfg.get("remote_tools.xai.x_search", False))
307
+ is_code_enabled = bool(cfg.get("remote_tools.xai.code_execution", False))
308
+ is_mcp_enabled = bool(cfg.get("remote_tools.xai.mcp", False))
309
+ is_collections_enabled = bool(cfg.get("remote_tools.xai.collections", False))
310
+
311
+ # Web search
312
+ if is_web_enabled:
313
+ tools.append({"type": "web_search"})
314
+
315
+ # X search
316
+ if is_x_enabled:
317
+ tools.append({"type": "x_search"})
318
+
319
+ # Code execution (code interpreter)
320
+ if is_code_enabled:
321
+ # Container is optional here; xAI executes code server-side
322
+ tools.append({"type": "code_interpreter"})
323
+
324
+ # Collections search
325
+ if is_collections_enabled:
326
+ ids = cfg.get("remote_tools.xai.collections.args", "")
327
+ ids_list: List[str] = []
328
+ if ids:
329
+ try:
330
+ ids_list = [s.strip() for s in ids.split(",") if s.strip()]
331
+ except Exception:
332
+ ids_list = []
333
+ if ids_list:
334
+ tools.append({
335
+ "type": "collections_search",
336
+ "collection_ids": ids_list,
337
+ })
338
+
339
+ # MCP
340
+ if is_mcp_enabled:
341
+ mcp_tool = cfg.get("remote_tools.xai.mcp.args", "")
342
+ if mcp_tool:
343
+ try:
344
+ parsed = json.loads(mcp_tool)
345
+ # Accept either full dict or minimal {"type":"mcp"}
346
+ if isinstance(parsed, dict):
347
+ tools.append(parsed)
348
+ except Exception:
349
+ tools.append({"type": "mcp"})
350
+ else:
351
+ tools.append({"type": "mcp"})
352
+
353
+ return tools
354
+
272
355
  # ---------- helpers ----------
273
356
 
274
357
  def _build_http_params(
@@ -26,6 +26,7 @@ class Tools:
26
26
 
27
27
  - prepare(): legacy OpenAI-compatible dicts (kept for compatibility if needed).
28
28
  - prepare_sdk_tools(): xAI SDK client-side tool descriptors for Chat Responses.
29
+ - prepare_realtime(): Realtime/WebSocket-compatible function tools.
29
30
 
30
31
  :param window: Window instance
31
32
  """
@@ -168,4 +169,54 @@ class Tools:
168
169
  ))
169
170
  except Exception:
170
171
  continue
172
+ return tools
173
+
174
+ def prepare_realtime(self, functions: list) -> List[dict]:
175
+ """
176
+ Prepare function tools for Realtime/WebSocket sessions.
177
+
178
+ The returned structure matches the Realtime "tools" schema:
179
+ [
180
+ {
181
+ "type": "function",
182
+ "function": {
183
+ "name": "...",
184
+ "description": "...",
185
+ "parameters": { ... JSON Schema ... }
186
+ }
187
+ }
188
+ ]
189
+
190
+ :param functions: List of functions with keys: name (str), desc (str), params (JSON Schema str)
191
+ :return: List of function tool descriptors
192
+ """
193
+ if not functions or not isinstance(functions, list):
194
+ return []
195
+
196
+ tools: List[dict] = []
197
+ for fn in functions:
198
+ name = str(fn.get("name") or "").strip()
199
+ if not name:
200
+ continue
201
+ desc = fn.get("desc") or ""
202
+ params: Optional[dict] = {}
203
+ if fn.get("params"):
204
+ try:
205
+ params = json.loads(fn["params"])
206
+ except Exception:
207
+ params = {}
208
+ params = self._sanitize_schema(params or {})
209
+ if not params.get("type"):
210
+ params["type"] = "object"
211
+ else:
212
+ params = {"type": "object"}
213
+
214
+ tools.append({
215
+ "type": "function",
216
+ "function": {
217
+ "name": name,
218
+ "description": desc,
219
+ "parameters": params,
220
+ }
221
+ })
171
222
  return tools
@@ -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.06 06:00:00 #
9
+ # Updated Date: 2026.01.07 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -274,6 +274,17 @@ class Patch:
274
274
  data[key] = cfg_get_base(key)
275
275
  updated = True
276
276
 
277
+ # < 2.7.9
278
+ if old < parse_version("2.7.9"):
279
+ print("Migrating config from < 2.7.9...")
280
+ to_add = [
281
+ "api_key_management_xai",
282
+ ]
283
+ for key in to_add:
284
+ if key not in data or data[key] is None or data[key] == "None":
285
+ data[key] = ""
286
+ updated = True
287
+
277
288
  # update file
278
289
  migrated = False
279
290
  if updated:
@@ -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.04 19:00:00 #
9
+ # Updated Date: 2026.01.07 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from packaging.version import parse as parse_version, Version
@@ -154,6 +154,41 @@ class Patch:
154
154
  m.input.append("image")
155
155
  updated = True
156
156
 
157
+ # < 2.7.9 <--- add missing audio input
158
+ if old < parse_version("2.7.9"):
159
+ print("Migrating models from < 2.7.9...")
160
+ models_to_update = [
161
+ "grok-4",
162
+ "grok-4-fast-non-reasoning",
163
+ "grok-4-fast-reasoning",
164
+ "grok-4-1-fast-non-reasoning",
165
+ "grok-4-1-fast-reasoning",
166
+ ]
167
+ for model in models_to_update:
168
+ if model in data:
169
+ m = data[model]
170
+ if not m.is_audio_input():
171
+ m.input.append("audio")
172
+ if not m.is_audio_output():
173
+ m.output.append("audio")
174
+ if not m.has_mode("audio"):
175
+ m.mode.append("audio")
176
+ models_to_remove = [
177
+ "gemini-2.5-flash-preview-native-audio-dialog",
178
+ ]
179
+ for model in models_to_remove:
180
+ if model in data:
181
+ del data[model]
182
+ models_to_add = [
183
+ "gemini-2.5-flash-native-audio-latest",
184
+ ]
185
+ for model in models_to_add:
186
+ if model not in data:
187
+ base_model = from_base(model)
188
+ if base_model:
189
+ data[model] = base_model
190
+ updated = True
191
+
157
192
  # update file
158
193
  if updated:
159
194
  # fix empty/broken data
@@ -6,9 +6,9 @@
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 22:00:00 #
9
+ # Updated Date: 2026.01.22 16:00:00 #
10
10
  # ================================================== #
11
-
11
+ import json
12
12
  import time
13
13
  from typing import Dict, Any
14
14
 
@@ -116,14 +116,18 @@ class Storage:
116
116
  title = :title,
117
117
  content = :content,
118
118
  is_initialized = :is_initialized,
119
- updated_ts = :updated_ts
119
+ updated_ts = :updated_ts,
120
+ highlights_json = :highlights_json,
121
+ scroll_pos = :scroll_pos
120
122
  WHERE idx = :idx
121
123
  """).bindparams(
122
124
  idx=int(notepad.idx or 0),
123
125
  title=notepad.title,
124
126
  content=notepad.content,
125
127
  is_initialized=int(notepad.initialized),
126
- updated_ts=ts
128
+ updated_ts=ts,
129
+ highlights_json=self.pack_item_value(notepad.highlights),
130
+ scroll_pos=int(notepad.scroll_pos)
127
131
  )
128
132
  else:
129
133
  stmt = text("""
@@ -136,7 +140,9 @@ class Storage:
136
140
  created_ts,
137
141
  updated_ts,
138
142
  is_deleted,
139
- is_initialized
143
+ is_initialized,
144
+ highlights_json,
145
+ scroll_pos
140
146
  )
141
147
  VALUES
142
148
  (
@@ -147,7 +153,9 @@ class Storage:
147
153
  :created_ts,
148
154
  :updated_ts,
149
155
  :is_deleted,
150
- :is_initialized
156
+ :is_initialized,
157
+ :highlights_json,
158
+ :scroll_pos
151
159
  )
152
160
  """).bindparams(
153
161
  idx=notepad.idx,
@@ -157,7 +165,9 @@ class Storage:
157
165
  created_ts=ts,
158
166
  updated_ts=ts,
159
167
  is_deleted=int(notepad.deleted),
160
- is_initialized=int(notepad.initialized)
168
+ is_initialized=int(notepad.initialized),
169
+ highlights_json=self.pack_item_value(notepad.highlights),
170
+ scroll_pos=int(notepad.scroll_pos)
161
171
  )
162
172
  conn.execute(stmt)
163
173
 
@@ -180,7 +190,9 @@ class Storage:
180
190
  created_ts,
181
191
  updated_ts,
182
192
  is_deleted,
183
- is_initialized
193
+ is_initialized,
194
+ highlights_json,
195
+ scroll_pos
184
196
  )
185
197
  VALUES
186
198
  (
@@ -191,7 +203,9 @@ class Storage:
191
203
  :created_ts,
192
204
  :updated_ts,
193
205
  :is_deleted,
194
- :is_initialized
206
+ :is_initialized,
207
+ :highlights_json,
208
+ :scroll_pos
195
209
  )
196
210
  """).bindparams(
197
211
  idx=int(notepad.idx or 0),
@@ -201,7 +215,9 @@ class Storage:
201
215
  created_ts=ts,
202
216
  updated_ts=ts,
203
217
  is_deleted=int(notepad.deleted),
204
- is_initialized=int(notepad.initialized)
218
+ is_initialized=int(notepad.initialized),
219
+ highlights_json=self.pack_item_value(notepad.highlights),
220
+ scroll_pos=int(notepad.scroll_pos)
205
221
  )
206
222
  with db.begin() as conn:
207
223
  result = conn.execute(stmt)
@@ -225,4 +241,31 @@ class Storage:
225
241
  notepad.content = row['content']
226
242
  notepad.deleted = bool(row['is_deleted'])
227
243
  notepad.initialized = bool(row['is_initialized'])
244
+ notepad.highlights = self.unpack_item_value(row['highlights_json'])
245
+ notepad.scroll_pos = int(row.get('scroll_pos', -1))
228
246
  return notepad
247
+
248
+ def pack_item_value(self, value: Any) -> str:
249
+ """
250
+ Pack item value to JSON
251
+
252
+ :param value: Value to pack
253
+ :return: JSON string or value itself
254
+ """
255
+ if isinstance(value, (list, dict)):
256
+ return json.dumps(value)
257
+ return value
258
+
259
+ def unpack_item_value(self, value: Any) -> Any:
260
+ """
261
+ Unpack item value from JSON
262
+
263
+ :param value: Value to unpack
264
+ :return: Unpacked value
265
+ """
266
+ if value is None:
267
+ return []
268
+ try:
269
+ return json.loads(value)
270
+ except:
271
+ return value
@@ -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.24 00:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, QEvent
@@ -38,6 +38,7 @@ class Builder:
38
38
  def setup_menu(self, parent=None) -> QMenuBar:
39
39
  """Setup agent_builder dialog menu"""
40
40
  self.menu_bar = QMenuBar(parent)
41
+ self.menu_bar.setNativeMenuBar(False)
41
42
  self.file_menu = self.menu_bar.addMenu(trans("menu.file"))
42
43
  t = self.tool
43
44
 
@@ -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 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -35,6 +35,7 @@ class AudioTranscribe:
35
35
  def setup_menu(self, parent=None) -> QMenuBar:
36
36
  """Setup audio transcriber dialog menu"""
37
37
  self.menu_bar = QMenuBar(parent)
38
+ self.menu_bar.setNativeMenuBar(False)
38
39
  self.file_menu = self.menu_bar.addMenu(trans("menu.file"))
39
40
  t = self.window.tools.get("transcriber")
40
41
 
@@ -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.05 21:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -75,6 +75,7 @@ class Tool:
75
75
  """
76
76
  # create menu bar
77
77
  self.menu_bar = QMenuBar()
78
+ self.menu_bar.setNativeMenuBar(False)
78
79
  self.menu["file"] = self.menu_bar.addMenu(trans("interpreter.menu.file"))
79
80
  self.menu["kernel"] = self.menu_bar.addMenu(trans("interpreter.menu.kernel"))
80
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.05 21:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -53,6 +53,7 @@ class Tool:
53
53
  """
54
54
  # create menu bar
55
55
  self.menu_bar = QMenuBar()
56
+ self.menu_bar.setNativeMenuBar(False)
56
57
  self.menu["file"] = self.menu_bar.addMenu(trans("menu.file"))
57
58
 
58
59
  self.actions["file.open"] = QAction(QIcon(":/icons/folder.svg"), trans("tool.html_canvas.menu.file.open"))
@@ -1,7 +1,3 @@
1
- # dialog.py
2
-
3
- # dialog.py
4
-
5
1
  #!/usr/bin/env python3
6
2
  # -*- coding: utf-8 -*-
7
3
  # ================================================== #
@@ -10,7 +6,7 @@
10
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
11
7
  # MIT License #
12
8
  # Created By : Marcin Szczygliński #
13
- # Updated Date: 2026.01.06 19:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
14
10
  # ================================================== #
15
11
 
16
12
  from PySide6.QtCore import Qt, QPoint, QSize, QEvent
@@ -61,6 +57,7 @@ class DialogSpawner:
61
57
  source.setVisible(False)
62
58
  pixmap = ImageLabel(dialog, self.path)
63
59
  pixmap.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored))
60
+ pixmap.setAlignment(Qt.AlignCenter)
64
61
 
65
62
  # scrollable container for pixmap
66
63
  scroll = QScrollArea(dialog)
@@ -212,6 +209,7 @@ class ImageViewerDialog(BaseDialog):
212
209
  :return: QMenuBar
213
210
  """
214
211
  self.menu_bar = QMenuBar(self)
212
+ self.menu_bar.setNativeMenuBar(False)
215
213
  self.file_menu = self.menu_bar.addMenu(trans("menu.file"))
216
214
 
217
215
  self.actions["new"] = QAction(self._icon_add, trans("action.new"), 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: 2024.04.17 01:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -49,6 +49,7 @@ class DialogBuilder:
49
49
  """
50
50
  # create menu bar
51
51
  self.menu_bar = QMenuBar()
52
+ self.menu_bar.setNativeMenuBar(False)
52
53
  self.menu["file"] = self.menu_bar.addMenu(trans("menu.file"))
53
54
  self.menu["config"] = self.menu_bar.addMenu(trans("menu.config"))
54
55
 
@@ -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.03.26 15:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -34,6 +34,7 @@ class VideoPlayer:
34
34
  def setup_menu(self) -> QMenuBar:
35
35
  """Setup video dialog menu"""
36
36
  self.menu_bar = QMenuBar()
37
+ self.menu_bar.setNativeMenuBar(False)
37
38
  self.file_menu = self.menu_bar.addMenu(trans("menu.file"))
38
39
 
39
40
  # open
@@ -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.08 05:00:00 #
9
+ # Updated Date: 2026.01.22 17:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -51,6 +51,7 @@ class Tool:
51
51
  """
52
52
  # create menu bar
53
53
  self.menu_bar = QMenuBar()
54
+ self.menu_bar.setNativeMenuBar(False)
54
55
  self.menu["file"] = self.menu_bar.addMenu(trans("menu.file"))
55
56
  self.actions["file.open"] = QAction(QIcon(":/icons/folder.svg"), trans("tool.html_canvas.menu.file.open"))
56
57
  self.actions["file.open"].triggered.connect(
@@ -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.20 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt, Slot, QObject, Signal
@@ -14,6 +14,7 @@ from PySide6.QtGui import QAction, QIcon, QKeySequence, QFontMetrics
14
14
  from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QTextEdit, QWidget, QSplitter, QPushButton, QLabel
15
15
 
16
16
  from pygpt_net.core.text.finder import Finder
17
+ from pygpt_net.core.types import MODE_CHAT
17
18
  from pygpt_net.ui.widget.option.combo import OptionCombo
18
19
 
19
20
  from pygpt_net.ui.widget.textarea.search_input import SearchInput
@@ -67,6 +68,9 @@ class ToolWidget:
67
68
  "type": "combo",
68
69
  "label": "menu.tools.translator.model",
69
70
  "use": "models",
71
+ "use_params": {
72
+ "mode": [MODE_CHAT]
73
+ },
70
74
  "value": "gpt-4o-mini",
71
75
  }
72
76
  self.model_select = OptionCombo(
@@ -319,7 +323,7 @@ class TextColumn(QWidget):
319
323
  :param search_text: Text to search in languages
320
324
  """
321
325
  key = self.window.core.text.find_lang_id_by_search_string(search_text)
322
- if key:
326
+ if key and key != "-":
323
327
  self.lang_select.set_value(key)
324
328
 
325
329
  def on_clear(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: 2025.06.28 16:00:00 #
9
+ # Updated Date: 2026.01.21 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -20,7 +20,7 @@ from pygpt_net.utils import trans
20
20
 
21
21
  class About:
22
22
 
23
- RELEASE_YEAR = 2025
23
+ RELEASE_YEAR = 2026
24
24
 
25
25
  def __init__(self, window=None):
26
26
  """