pygpt-net 2.6.55__py3-none-any.whl → 2.6.57__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 (218) hide show
  1. pygpt_net/CHANGELOG.txt +12 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +26 -22
  4. pygpt_net/config.py +44 -0
  5. pygpt_net/controller/audio/audio.py +0 -0
  6. pygpt_net/controller/calendar/calendar.py +0 -0
  7. pygpt_net/controller/calendar/note.py +0 -0
  8. pygpt_net/controller/chat/chat.py +0 -0
  9. pygpt_net/controller/chat/handler/openai_stream.py +2 -1
  10. pygpt_net/controller/chat/handler/worker.py +0 -0
  11. pygpt_net/controller/chat/remote_tools.py +5 -3
  12. pygpt_net/controller/chat/render.py +0 -0
  13. pygpt_net/controller/chat/text.py +0 -0
  14. pygpt_net/controller/ctx/common.py +0 -0
  15. pygpt_net/controller/debug/debug.py +26 -2
  16. pygpt_net/controller/debug/fixtures.py +1 -1
  17. pygpt_net/controller/dialogs/confirm.py +15 -1
  18. pygpt_net/controller/dialogs/debug.py +2 -0
  19. pygpt_net/controller/lang/mapping.py +0 -0
  20. pygpt_net/controller/launcher/launcher.py +0 -0
  21. pygpt_net/controller/mode/mode.py +0 -0
  22. pygpt_net/controller/presets/presets.py +0 -0
  23. pygpt_net/controller/realtime/realtime.py +0 -0
  24. pygpt_net/controller/theme/theme.py +0 -0
  25. pygpt_net/controller/ui/mode.py +5 -3
  26. pygpt_net/controller/ui/tabs.py +0 -0
  27. pygpt_net/core/agents/agents.py +3 -1
  28. pygpt_net/core/agents/custom.py +150 -0
  29. pygpt_net/core/agents/provider.py +0 -0
  30. pygpt_net/core/builder/__init__.py +12 -0
  31. pygpt_net/core/builder/graph.py +478 -0
  32. pygpt_net/core/calendar/calendar.py +0 -0
  33. pygpt_net/core/ctx/ctx.py +0 -0
  34. pygpt_net/core/ctx/output.py +0 -0
  35. pygpt_net/core/debug/agent.py +0 -0
  36. pygpt_net/core/debug/agent_builder.py +29 -0
  37. pygpt_net/core/debug/console/console.py +0 -0
  38. pygpt_net/core/debug/db.py +0 -0
  39. pygpt_net/core/debug/debug.py +0 -0
  40. pygpt_net/core/debug/events.py +0 -0
  41. pygpt_net/core/debug/indexes.py +0 -0
  42. pygpt_net/core/debug/kernel.py +0 -0
  43. pygpt_net/core/debug/tabs.py +0 -0
  44. pygpt_net/core/filesystem/filesystem.py +0 -0
  45. pygpt_net/core/fixtures/__init__ +0 -0
  46. pygpt_net/core/fixtures/stream/__init__.py +0 -0
  47. pygpt_net/core/fixtures/stream/generator.py +0 -0
  48. pygpt_net/core/models/models.py +2 -1
  49. pygpt_net/core/plugins/plugins.py +60 -0
  50. pygpt_net/core/render/plain/pid.py +0 -0
  51. pygpt_net/core/render/plain/renderer.py +26 -4
  52. pygpt_net/core/render/web/body.py +46 -4
  53. pygpt_net/core/render/web/debug.py +0 -0
  54. pygpt_net/core/render/web/helpers.py +0 -0
  55. pygpt_net/core/render/web/pid.py +0 -0
  56. pygpt_net/core/render/web/renderer.py +15 -20
  57. pygpt_net/core/tabs/tab.py +0 -0
  58. pygpt_net/core/tabs/tabs.py +0 -0
  59. pygpt_net/core/text/utils.py +0 -0
  60. pygpt_net/css.qrc +0 -0
  61. pygpt_net/css_rc.py +0 -0
  62. pygpt_net/data/config/config.json +8 -7
  63. pygpt_net/data/config/models.json +3 -3
  64. pygpt_net/data/config/settings.json +14 -0
  65. pygpt_net/data/css/web-blocks.css +9 -0
  66. pygpt_net/data/css/web-blocks.dark.css +6 -0
  67. pygpt_net/data/css/web-blocks.darkest.css +6 -0
  68. pygpt_net/data/css/web-chatgpt.css +14 -6
  69. pygpt_net/data/css/web-chatgpt.dark.css +6 -0
  70. pygpt_net/data/css/web-chatgpt.darkest.css +6 -0
  71. pygpt_net/data/css/web-chatgpt.light.css +6 -0
  72. pygpt_net/data/css/web-chatgpt_wide.css +14 -6
  73. pygpt_net/data/css/web-chatgpt_wide.dark.css +6 -0
  74. pygpt_net/data/css/web-chatgpt_wide.darkest.css +6 -0
  75. pygpt_net/data/css/web-chatgpt_wide.light.css +6 -0
  76. pygpt_net/data/fixtures/fake_stream.txt +14 -1
  77. pygpt_net/data/icons/case.svg +0 -0
  78. pygpt_net/data/icons/chat1.svg +0 -0
  79. pygpt_net/data/icons/chat2.svg +0 -0
  80. pygpt_net/data/icons/chat3.svg +0 -0
  81. pygpt_net/data/icons/chat4.svg +0 -0
  82. pygpt_net/data/icons/fit.svg +0 -0
  83. pygpt_net/data/icons/note1.svg +0 -0
  84. pygpt_net/data/icons/note2.svg +0 -0
  85. pygpt_net/data/icons/note3.svg +0 -0
  86. pygpt_net/data/icons/stt.svg +0 -0
  87. pygpt_net/data/icons/translate.svg +0 -0
  88. pygpt_net/data/icons/tts.svg +0 -0
  89. pygpt_net/data/icons/url.svg +0 -0
  90. pygpt_net/data/icons/vision.svg +0 -0
  91. pygpt_net/data/icons/web_off.svg +0 -0
  92. pygpt_net/data/icons/web_on.svg +0 -0
  93. pygpt_net/data/js/app/async.js +166 -0
  94. pygpt_net/data/js/app/bridge.js +88 -0
  95. pygpt_net/data/js/app/common.js +212 -0
  96. pygpt_net/data/js/app/config.js +223 -0
  97. pygpt_net/data/js/app/custom.js +961 -0
  98. pygpt_net/data/js/app/data.js +84 -0
  99. pygpt_net/data/js/app/dom.js +322 -0
  100. pygpt_net/data/js/app/events.js +400 -0
  101. pygpt_net/data/js/app/highlight.js +542 -0
  102. pygpt_net/data/js/app/logger.js +305 -0
  103. pygpt_net/data/js/app/markdown.js +1137 -0
  104. pygpt_net/data/js/app/math.js +167 -0
  105. pygpt_net/data/js/app/nodes.js +395 -0
  106. pygpt_net/data/js/app/queue.js +260 -0
  107. pygpt_net/data/js/app/raf.js +250 -0
  108. pygpt_net/data/js/app/runtime.js +582 -0
  109. pygpt_net/data/js/app/scroll.js +433 -0
  110. pygpt_net/data/js/app/stream.js +2708 -0
  111. pygpt_net/data/js/app/template.js +287 -0
  112. pygpt_net/data/js/app/tool.js +87 -0
  113. pygpt_net/data/js/app/ui.js +86 -0
  114. pygpt_net/data/js/app/user.js +380 -0
  115. pygpt_net/data/js/app/utils.js +64 -0
  116. pygpt_net/data/js/app.min.js +880 -0
  117. pygpt_net/data/js/markdown-it/markdown-it-katex.min.js +1 -1
  118. pygpt_net/data/js/markdown-it/markdown-it.min.js +0 -0
  119. pygpt_net/data/locale/locale.de.ini +3 -1
  120. pygpt_net/data/locale/locale.en.ini +8 -0
  121. pygpt_net/data/locale/locale.es.ini +2 -0
  122. pygpt_net/data/locale/locale.fr.ini +2 -0
  123. pygpt_net/data/locale/locale.it.ini +2 -0
  124. pygpt_net/data/locale/locale.pl.ini +3 -1
  125. pygpt_net/data/locale/locale.uk.ini +3 -1
  126. pygpt_net/data/locale/locale.zh.ini +2 -0
  127. pygpt_net/data/locale/plugin.osm.en.ini +24 -24
  128. pygpt_net/data/locale/plugin.wolfram.en.ini +9 -9
  129. pygpt_net/fonts.qrc +0 -0
  130. pygpt_net/fonts_rc.py +0 -0
  131. pygpt_net/icons.qrc +0 -0
  132. pygpt_net/icons_rc.py +0 -0
  133. pygpt_net/item/agent.py +62 -0
  134. pygpt_net/item/builder_layout.py +62 -0
  135. pygpt_net/js.qrc +24 -1
  136. pygpt_net/js_rc.py +51394 -33687
  137. pygpt_net/plugin/base/worker.py +0 -0
  138. pygpt_net/plugin/cmd_web/config.py +17 -17
  139. pygpt_net/plugin/cmd_web/worker.py +325 -171
  140. pygpt_net/plugin/mcp/__init__.py +0 -0
  141. pygpt_net/plugin/mcp/config.py +0 -0
  142. pygpt_net/plugin/mcp/plugin.py +0 -0
  143. pygpt_net/plugin/mcp/worker.py +0 -0
  144. pygpt_net/plugin/osm/__init__.py +0 -0
  145. pygpt_net/plugin/osm/config.py +0 -0
  146. pygpt_net/plugin/osm/plugin.py +0 -0
  147. pygpt_net/plugin/osm/worker.py +0 -0
  148. pygpt_net/plugin/wolfram/__init__.py +0 -0
  149. pygpt_net/plugin/wolfram/config.py +0 -0
  150. pygpt_net/plugin/wolfram/plugin.py +0 -0
  151. pygpt_net/plugin/wolfram/worker.py +0 -0
  152. pygpt_net/provider/api/anthropic/tools.py +0 -0
  153. pygpt_net/provider/api/google/__init__.py +0 -0
  154. pygpt_net/provider/api/google/video.py +0 -0
  155. pygpt_net/provider/api/openai/agents/experts.py +0 -0
  156. pygpt_net/provider/api/openai/agents/remote_tools.py +0 -0
  157. pygpt_net/provider/api/openai/remote_tools.py +0 -0
  158. pygpt_net/provider/api/openai/responses.py +0 -0
  159. pygpt_net/provider/api/x_ai/__init__.py +2 -0
  160. pygpt_net/provider/api/x_ai/remote.py +0 -0
  161. pygpt_net/provider/core/agent/__init__.py +10 -0
  162. pygpt_net/provider/core/agent/base.py +51 -0
  163. pygpt_net/provider/core/agent/json_file.py +200 -0
  164. pygpt_net/provider/core/config/patch.py +33 -0
  165. pygpt_net/provider/core/config/patches/__init__.py +0 -0
  166. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +1 -0
  167. pygpt_net/provider/core/ctx/db_sqlite/storage.py +0 -0
  168. pygpt_net/provider/core/model/patches/__init__.py +0 -0
  169. pygpt_net/provider/core/model/patches/patch_before_2_6_42.py +0 -0
  170. pygpt_net/provider/core/preset/patch.py +0 -0
  171. pygpt_net/provider/core/preset/patches/__init__.py +0 -0
  172. pygpt_net/provider/core/preset/patches/patch_before_2_6_42.py +0 -0
  173. pygpt_net/provider/llms/anthropic.py +4 -0
  174. pygpt_net/provider/llms/base.py +2 -0
  175. pygpt_net/provider/llms/deepseek_api.py +2 -0
  176. pygpt_net/provider/llms/google.py +2 -0
  177. pygpt_net/provider/llms/hugging_face_api.py +4 -0
  178. pygpt_net/provider/llms/hugging_face_embedding.py +0 -0
  179. pygpt_net/provider/llms/hugging_face_router.py +2 -0
  180. pygpt_net/provider/llms/local.py +0 -0
  181. pygpt_net/provider/llms/mistral.py +4 -0
  182. pygpt_net/provider/llms/open_router.py +0 -0
  183. pygpt_net/provider/llms/perplexity.py +2 -0
  184. pygpt_net/provider/llms/utils.py +0 -0
  185. pygpt_net/provider/llms/voyage.py +0 -0
  186. pygpt_net/provider/llms/x_ai.py +2 -0
  187. pygpt_net/tools/agent_builder/__init__.py +12 -0
  188. pygpt_net/tools/agent_builder/tool.py +292 -0
  189. pygpt_net/tools/agent_builder/ui/__init__.py +0 -0
  190. pygpt_net/tools/agent_builder/ui/dialogs.py +152 -0
  191. pygpt_net/tools/agent_builder/ui/list.py +228 -0
  192. pygpt_net/tools/code_interpreter/ui/html.py +0 -0
  193. pygpt_net/tools/code_interpreter/ui/widgets.py +0 -0
  194. pygpt_net/tools/html_canvas/tool.py +23 -6
  195. pygpt_net/tools/html_canvas/ui/widgets.py +224 -2
  196. pygpt_net/ui/layout/chat/chat.py +0 -0
  197. pygpt_net/ui/layout/chat/output.py +5 -5
  198. pygpt_net/ui/main.py +10 -9
  199. pygpt_net/ui/menu/debug.py +39 -1
  200. pygpt_net/ui/widget/builder/__init__.py +12 -0
  201. pygpt_net/ui/widget/builder/editor.py +2001 -0
  202. pygpt_net/ui/widget/dialog/base.py +4 -1
  203. pygpt_net/ui/widget/draw/painter.py +0 -0
  204. pygpt_net/ui/widget/element/labels.py +9 -4
  205. pygpt_net/ui/widget/lists/db.py +0 -0
  206. pygpt_net/ui/widget/lists/debug.py +0 -0
  207. pygpt_net/ui/widget/tabs/body.py +0 -0
  208. pygpt_net/ui/widget/textarea/html.py +1 -0
  209. pygpt_net/ui/widget/textarea/input.py +28 -10
  210. pygpt_net/ui/widget/textarea/output.py +21 -1
  211. pygpt_net/ui/widget/textarea/web.py +31 -3
  212. pygpt_net/utils.py +40 -0
  213. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/METADATA +16 -2
  214. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/RECORD +116 -77
  215. pygpt_net/data/js/app.js +0 -5869
  216. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/LICENSE +0 -0
  217. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/WHEEL +0 -0
  218. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/entry_points.txt +0 -0
@@ -112,6 +112,14 @@ class HtmlCanvas(BaseTool):
112
112
  self.signals.reload.emit(path)
113
113
  self.signals.update.emit(output)
114
114
 
115
+ def set_url(self, url: str):
116
+ """
117
+ Set output URL
118
+
119
+ :param url: URL to load
120
+ """
121
+ self.signals.url.emit(url)
122
+
115
123
  def reload_output(self):
116
124
  """Reload output data"""
117
125
  self.load_output()
@@ -172,18 +180,27 @@ class HtmlCanvas(BaseTool):
172
180
  """Clear input and output"""
173
181
  self.clear_output()
174
182
 
175
- def open(self):
176
- """Open HTML canvas dialog"""
183
+ def open(self, load: bool = True):
184
+ """
185
+ Open HTML canvas dialog
186
+
187
+ :param load: Load output data
188
+ """
177
189
  if not self.opened:
178
190
  self.opened = True
179
191
  self.auto_opened = False
180
- self.load_output()
192
+ if load:
193
+ self.load_output()
181
194
  self.window.ui.dialogs.open(self.dialog_id, width=800, height=600)
182
195
  self.dialog.widget.on_open()
183
196
  self.update()
184
197
 
185
- def auto_open(self):
186
- """Auto open canvas dialog or tab"""
198
+ def auto_open(self, load: bool = True):
199
+ """
200
+ Auto open canvas dialog or tab
201
+
202
+ :param load: Load output data
203
+ """
187
204
  if self.window.controller.ui.tabs.is_current_tool(self.id):
188
205
  tool_col = self.window.controller.ui.tabs.get_tool_column(self.id)
189
206
  current_col = self.window.controller.ui.tabs.column_idx
@@ -201,7 +218,7 @@ class HtmlCanvas(BaseTool):
201
218
  return
202
219
  if not self.auto_opened:
203
220
  self.auto_opened = True
204
- self.open()
221
+ self.open(load=load)
205
222
 
206
223
  def open_file(self):
207
224
  """Open file dialog"""
@@ -9,8 +9,17 @@
9
9
  # Updated Date: 2025.09.16 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from PySide6.QtCore import Qt, Slot, QUrl, QObject, Signal
13
- from PySide6.QtWidgets import QVBoxLayout, QCheckBox, QHBoxLayout
12
+ from PySide6.QtCore import Qt, Slot, QUrl, QObject, Signal, QSize
13
+ from PySide6.QtGui import QIcon
14
+ from PySide6.QtWidgets import (
15
+ QVBoxLayout,
16
+ QCheckBox,
17
+ QHBoxLayout,
18
+ QLineEdit,
19
+ QPushButton,
20
+ QWidget,
21
+ QSizePolicy,
22
+ )
14
23
 
15
24
  from pygpt_net.ui.widget.element.labels import HelpLabel
16
25
  from pygpt_net.ui.widget.textarea.editor import BaseCodeEditor
@@ -32,6 +41,17 @@ class ToolWidget:
32
41
  self.edit = None # canvas edit
33
42
  self.btn_edit = None # edit checkbox
34
43
 
44
+ # --- Navigation bar state (added) ---
45
+ # This toolbar is shown only when opening via URL (open_url),
46
+ # and hidden when using set_output/load_output.
47
+ self.nav_bar = None
48
+ self.nav_layout = None
49
+ self.address_bar = None
50
+ self.btn_back = None
51
+ self.btn_next = None
52
+ self.btn_reload = None
53
+ self.btn_go = None # "Go" button (text)
54
+
35
55
  def on_open(self):
36
56
  """On open"""
37
57
  pass
@@ -41,6 +61,7 @@ class ToolWidget:
41
61
  if self.tool:
42
62
  self.tool.signals.update.disconnect(self.set_output)
43
63
  self.tool.signals.reload.disconnect(self.load_output)
64
+ self.tool.signals.url.disconnect(self.open_url) # keep connections clean
44
65
  if self.output:
45
66
  self.output.on_delete()
46
67
 
@@ -67,6 +88,86 @@ class ToolWidget:
67
88
  lambda: self.tool.save_output()
68
89
  )
69
90
 
91
+ # ---- Navigation bar (added) ----
92
+ # Visible only when navigating URLs via open_url or address bar.
93
+ self.nav_bar = QWidget()
94
+ self.nav_layout = QHBoxLayout(self.nav_bar)
95
+ self.nav_layout.setContentsMargins(6, 4, 6, 4) # small margins for compact look
96
+ self.nav_layout.setSpacing(6)
97
+ self.nav_bar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
98
+
99
+ # Icon size and fixed navbar height (to prevent taking too much space)
100
+ icon_size_px = 20 # base icon size for QPushButtons
101
+ nav_height = max(32, min(44, icon_size_px + 16)) # compact, never half-screen
102
+ self.nav_bar.setFixedHeight(nav_height)
103
+
104
+ # Buttons (use QPushButton with icons from resources)
105
+ self.btn_back = QPushButton()
106
+ self.btn_back.setToolTip("Back")
107
+ self.btn_back.setIcon(QIcon(":/icons/back.svg"))
108
+ self.btn_back.setIconSize(QSize(icon_size_px, icon_size_px))
109
+ self.btn_back.setFixedHeight(nav_height - 8)
110
+ self.btn_back.setEnabled(False)
111
+ self.btn_back.setAutoDefault(False) # prevent Enter from triggering this (added)
112
+ try:
113
+ self.btn_back.setDefault(False)
114
+ except Exception:
115
+ pass
116
+
117
+ self.btn_next = QPushButton()
118
+ self.btn_next.setToolTip("Next")
119
+ self.btn_next.setIcon(QIcon(":/icons/redo.svg")) # use redo.svg as "next"
120
+ self.btn_next.setIconSize(QSize(icon_size_px, icon_size_px))
121
+ self.btn_next.setFixedHeight(nav_height - 8)
122
+ self.btn_next.setEnabled(False)
123
+ self.btn_next.setAutoDefault(False) # (added)
124
+ try:
125
+ self.btn_next.setDefault(False)
126
+ except Exception:
127
+ pass
128
+
129
+ self.btn_reload = QPushButton()
130
+ self.btn_reload.setToolTip("Reload")
131
+ self.btn_reload.setIcon(QIcon(":/icons/reload.svg"))
132
+ self.btn_reload.setIconSize(QSize(icon_size_px, icon_size_px))
133
+ self.btn_reload.setFixedHeight(nav_height - 8)
134
+ self.btn_reload.setAutoDefault(False) # (added)
135
+ try:
136
+ self.btn_reload.setDefault(False)
137
+ except Exception:
138
+ pass
139
+
140
+ # "Go" button is text-only as requested
141
+ self.btn_go = QPushButton("GO")
142
+ self.btn_go.setToolTip("Open URL")
143
+ self.btn_go.setFixedHeight(nav_height - 8)
144
+ self.btn_go.setAutoDefault(False) # avoid stealing Enter (added)
145
+ try:
146
+ self.btn_go.setDefault(False)
147
+ except Exception:
148
+ pass
149
+
150
+ # Address bar (custom line edit to capture Enter and avoid default button reload)
151
+ self.address_bar = AddressLineEdit(on_return_callback=self._on_address_enter)
152
+ self.address_bar.setPlaceholderText("Enter URL and press Enter")
153
+ self.address_bar.setFixedHeight(nav_height - 8)
154
+ # Still keep the Qt signal for completeness (won't fire because we intercept the key)
155
+ self.address_bar.returnPressed.connect(self._on_address_enter)
156
+
157
+ # Hook up button actions
158
+ self.btn_back.clicked.connect(lambda: self._navigate('back'))
159
+ self.btn_next.clicked.connect(lambda: self._navigate('forward'))
160
+ self.btn_reload.clicked.connect(lambda: self._navigate('reload'))
161
+ self.btn_go.clicked.connect(self._on_address_enter) # "Go" triggers address open
162
+
163
+ # Build nav layout
164
+ self.nav_layout.addWidget(self.btn_back)
165
+ self.nav_layout.addWidget(self.btn_next)
166
+ self.nav_layout.addWidget(self.btn_reload)
167
+ self.nav_layout.addWidget(self.address_bar, 1)
168
+ self.nav_layout.addWidget(self.btn_go)
169
+ self.nav_bar.setVisible(False) # hidden by default
170
+
70
171
  # edit checkbox
71
172
  self.btn_edit = QCheckBox(trans("html_canvas.btn.edit"))
72
173
  self.btn_edit.stateChanged.connect(
@@ -83,6 +184,8 @@ class ToolWidget:
83
184
  bottom_layout.addWidget(path_label)
84
185
 
85
186
  output_layout = QVBoxLayout()
187
+ # put navigation bar above the web output
188
+ output_layout.addWidget(self.nav_bar) # added
86
189
  output_layout.addWidget(self.output)
87
190
  output_layout.addWidget(self.edit)
88
191
  output_layout.setContentsMargins(0, 0, 0, 0)
@@ -93,8 +196,24 @@ class ToolWidget:
93
196
  self.output.signals.audio_read.connect(
94
197
  self.window.controller.chat.render.handle_audio_read)
95
198
 
199
+ # Keep nav bar in sync with the underlying view if signals are available
200
+ # (urlChanged/loadStarted/loadFinished are part of QWebEngineView)
201
+ if hasattr(self.output, 'urlChanged'):
202
+ self.output.urlChanged.connect(self._on_url_changed)
203
+ if hasattr(self.output, 'loadStarted'):
204
+ try:
205
+ self.output.loadStarted.connect(lambda: self._update_nav_controls())
206
+ except TypeError:
207
+ pass
208
+ if hasattr(self.output, 'loadFinished'):
209
+ try:
210
+ self.output.loadFinished.connect(lambda ok: self._update_nav_controls())
211
+ except TypeError:
212
+ pass
213
+
96
214
  self.tool.signals.update.connect(self.set_output)
97
215
  self.tool.signals.reload.connect(self.load_output)
216
+ self.tool.signals.url.connect(self.open_url)
98
217
 
99
218
  layout = QVBoxLayout()
100
219
  layout.addLayout(output_layout)
@@ -109,6 +228,22 @@ class ToolWidget:
109
228
  :param content: Content
110
229
  """
111
230
  self.edit.setPlainText(content)
231
+ # Hide navigation bar when content is set via set_output (not a URL)
232
+ self._show_navbar(False)
233
+
234
+ @Slot(str)
235
+ def open_url(self, url: str):
236
+ """
237
+ Open URL in output
238
+
239
+ :param url: URL
240
+ """
241
+ print("Opening URL:", url)
242
+ self._show_navbar(True) # Show navigation bar for URL navigation
243
+ if self.address_bar:
244
+ self.address_bar.setText(url)
245
+ self.output.setUrl(QUrl(url))
246
+ self._update_nav_controls()
112
247
 
113
248
  @Slot(str)
114
249
  def load_output(self, path: str):
@@ -118,6 +253,71 @@ class ToolWidget:
118
253
  :param path: Content
119
254
  """
120
255
  self.output.setUrl(QUrl().fromLocalFile(path))
256
+ # Hide navigation bar when loading from local file/path
257
+ self._show_navbar(False)
258
+
259
+ # ----------------------
260
+ # Navigation helpers (added)
261
+ # ----------------------
262
+ def _show_navbar(self, show: bool):
263
+ """Show/hide the navigation bar."""
264
+ if self.nav_bar:
265
+ self.nav_bar.setVisible(show)
266
+ # Keep controls state up-to-date
267
+ self._update_nav_controls()
268
+
269
+ def _navigate(self, action: str):
270
+ """Perform navigation action on the output view."""
271
+ if not self.output:
272
+ return
273
+ try:
274
+ if action == 'back' and hasattr(self.output, 'back'):
275
+ self.output.back()
276
+ elif action == 'forward' and hasattr(self.output, 'forward'):
277
+ self.output.forward()
278
+ elif action == 'reload' and hasattr(self.output, 'reload'):
279
+ self.output.reload()
280
+ finally:
281
+ self._update_nav_controls()
282
+
283
+ def _on_address_enter(self):
284
+ """Handle Enter pressed in the address bar or clicking GO."""
285
+ if not self.output or not self.address_bar:
286
+ return
287
+ text = self.address_bar.text().strip()
288
+ if not text:
289
+ return
290
+ # Use fromUserInput to accept entries like 'example.com'
291
+ url = QUrl.fromUserInput(text)
292
+ if url.isValid():
293
+ self._show_navbar(True)
294
+ self.output.setUrl(url)
295
+ self._update_nav_controls()
296
+
297
+ def _on_url_changed(self, url: QUrl):
298
+ """Keep address bar in sync with the view URL."""
299
+ if self.address_bar:
300
+ self.address_bar.setText(url.toString())
301
+ self._update_nav_controls()
302
+
303
+ def _update_nav_controls(self):
304
+ """Enable/disable back/forward buttons based on history."""
305
+ if not self.output:
306
+ return
307
+ # Reload is generally available when navbar is visible
308
+ if self.btn_reload:
309
+ self.btn_reload.setEnabled(True)
310
+ # Back/forward depend on history if available
311
+ if self.btn_back and self.btn_next:
312
+ try:
313
+ hist = self.output.history()
314
+ self.btn_back.setEnabled(bool(hist and hist.canGoBack()))
315
+ self.btn_next.setEnabled(bool(hist and hist.canGoForward()))
316
+ except Exception:
317
+ # If history is not available, keep safe defaults
318
+ self.btn_back.setEnabled(False)
319
+ self.btn_next.setEnabled(False)
320
+
121
321
 
122
322
  class CanvasOutput(HtmlOutput):
123
323
  def __init__(self, window=None):
@@ -129,6 +329,7 @@ class CanvasOutput(HtmlOutput):
129
329
  super(CanvasOutput, self).__init__(window)
130
330
  self.window = window
131
331
 
332
+
132
333
  class CanvasEdit(BaseCodeEditor):
133
334
  def __init__(self, window=None):
134
335
  """
@@ -170,6 +371,27 @@ class CanvasEdit(BaseCodeEditor):
170
371
  return super().eventFilter(source, event)
171
372
 
172
373
 
374
+ # --- Address bar input widget (added) ---
375
+ class AddressLineEdit(QLineEdit):
376
+ """
377
+ Custom QLineEdit that ensures Enter triggers opening the typed URL
378
+ and prevents default buttons (e.g., Reload) from consuming the key.
379
+ """
380
+ def __init__(self, on_return_callback=None, *args, **kwargs):
381
+ super().__init__(*args, **kwargs)
382
+ self._on_return_callback = on_return_callback
383
+
384
+ def keyPressEvent(self, event):
385
+ if event.key() in (Qt.Key_Return, Qt.Key_Enter):
386
+ # Call the assigned handler and stop propagation so default buttons won't trigger.
387
+ if callable(self._on_return_callback):
388
+ self._on_return_callback()
389
+ event.accept()
390
+ return
391
+ super().keyPressEvent(event)
392
+
393
+
173
394
  class ToolSignals(QObject):
174
395
  update = Signal(str) # data
175
396
  reload = Signal(str) # path
397
+ url = Signal(str) # url
File without changes
@@ -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.17 07:00:00 #
9
+ # Updated Date: 2025.09.22 12:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -86,9 +86,9 @@ class Output:
86
86
  nodes['icon.indexer'].setToolTip("Indexer")
87
87
  nodes['icon.indexer'].clicked.connect(lambda: tools.get("indexer").toggle())
88
88
 
89
- nodes['icon.remote_tool.web'] = IconLabel(":/icons/web_on.svg", window=self.window)
90
- nodes['icon.remote_tool.web'].setToolTip(trans("icon.remote_tool.web"))
91
- nodes['icon.remote_tool.web'].clicked.connect(lambda: ctrl.chat.remote_tools.toggle('web_search'))
89
+ # nodes['icon.remote_tool.web'] = IconLabel(":/icons/web_on.svg", window=self.window)
90
+ # nodes['icon.remote_tool.web'].setToolTip(trans("icon.remote_tool.web"))
91
+ # nodes['icon.remote_tool.web'].clicked.connect(lambda: ctrl.chat.remote_tools.toggle('web_search'))
92
92
 
93
93
  min_policy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
94
94
 
@@ -139,7 +139,7 @@ class Output:
139
139
  nodes['anim.loading'].hide()
140
140
 
141
141
  right_bar_layout = QHBoxLayout()
142
- right_bar_layout.addWidget(nodes['icon.remote_tool.web'])
142
+ # right_bar_layout.addWidget(nodes['icon.remote_tool.web'])
143
143
  right_bar_layout.addWidget(nodes['icon.video.capture'])
144
144
  right_bar_layout.addWidget(nodes['icon.audio.input'])
145
145
  right_bar_layout.addWidget(nodes['icon.audio.output'])
pygpt_net/ui/main.py CHANGED
@@ -22,7 +22,7 @@ from pygpt_net.controller import Controller
22
22
  from pygpt_net.tools import Tools
23
23
  from pygpt_net.ui import UI
24
24
  from pygpt_net.ui.widget.textarea.web import ChatWebOutput
25
- from pygpt_net.utils import get_app_meta, freeze_updates
25
+ from pygpt_net.utils import get_app_meta, freeze_updates, set_env, has_env, get_env
26
26
 
27
27
 
28
28
  class MainWindow(QMainWindow, QtStyleTools):
@@ -112,20 +112,21 @@ class MainWindow(QMainWindow, QtStyleTools):
112
112
  render_debug = True
113
113
 
114
114
  # disable/enable logging web engine context
115
+ CHROMIUM_FLAGS = "QTWEBENGINE_CHROMIUM_FLAGS"
116
+
115
117
  if not render_debug:
116
118
  QLoggingCategory.setFilterRules("*.info=false")
117
119
  else:
118
- if "QTWEBENGINE_CHROMIUM_FLAGS" in os.environ:
119
- os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] += " --enable-logging --log-level=0"
120
- else:
121
- os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging --log-level=0"
120
+ set_env(CHROMIUM_FLAGS, "--enable-logging", True)
121
+ set_env(CHROMIUM_FLAGS, "--log-level=0", True)
122
122
 
123
123
  # OpenGL disable
124
124
  if self.core.config.get("render.open_gl") is False:
125
- if "QTWEBENGINE_CHROMIUM_FLAGS" in os.environ:
126
- os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] += " --disable-gpu"
127
- else:
128
- os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu"
125
+ set_env(CHROMIUM_FLAGS, "--disable-gpu", True)
126
+
127
+ # log chromium flags
128
+ if (has_env(CHROMIUM_FLAGS) and get_env("PYGPT_APP_ENV") == "dev") or render_debug:
129
+ print("Running with Chromium flags:", get_env(CHROMIUM_FLAGS))
129
130
 
130
131
  def add_plugin(self, plugin):
131
132
  """
@@ -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.12 20:00:00 #
9
+ # Updated Date: 2025.09.19 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtGui import QAction
@@ -39,6 +39,7 @@ class Debug:
39
39
  'attachments',
40
40
  'assistants',
41
41
  'agent',
42
+ # 'agent_builder',
42
43
  'events',
43
44
  'indexes',
44
45
  'ui',
@@ -61,6 +62,7 @@ class Debug:
61
62
  'attachments',
62
63
  'assistants',
63
64
  'agent',
65
+ # 'agent_builder',
64
66
  'events',
65
67
  'indexes',
66
68
  'ui',
@@ -98,6 +100,7 @@ class Debug:
98
100
  menu.addActions(
99
101
  [
100
102
  m['debug.agent'],
103
+ # m['debug.agent_builder'],
101
104
  m['debug.assistants'],
102
105
  m['debug.attachments'],
103
106
  m['debug.config'],
@@ -113,4 +116,39 @@ class Debug:
113
116
  ]
114
117
  )
115
118
 
119
+ # add separator and sub-menu with Chrome debug
120
+ menu.addSeparator()
121
+
122
+ # Sub-menu "Chrome"
123
+ m['menu.debug.chrome'] = menu.addMenu("Chromium")
124
+ chrome_urls = {
125
+ 'sandbox': ('Sandbox', 'chrome://sandbox'),
126
+ 'gpu': ('GPU', 'chrome://gpu'),
127
+ 'version': ('Version', 'chrome://version'),
128
+ 'flags': ('Flags', 'chrome://flags'),
129
+ 'components': ('Components', 'chrome://components'),
130
+ 'policy': ('Policy', 'chrome://policy'),
131
+ 'dns': ('DNS', 'chrome://dns'),
132
+ 'net_export': ('Net Export', 'chrome://net-export'),
133
+ 'media_internals': ('Media Internals', 'chrome://media-internals'),
134
+ 'webrtc_internals': ('WebRTC Internals', 'chrome://webrtc-internals'),
135
+ 'discards': ('Discards (Tab Discarding)', 'chrome://discards'),
136
+ 'crashes': ('Crashes', 'chrome://crashes'),
137
+ 'histograms': ('Histograms', 'chrome://histograms'),
138
+ 'process_internals': ('Process Internals', 'chrome://process-internals'),
139
+ 'tracing': ('Tracing', 'chrome://tracing'),
140
+ 'chrome_urls': ('All Chrome URLs', 'chrome://chrome-urls'),
141
+ }
142
+
143
+ for key, (label, url) in chrome_urls.items():
144
+ action_key = f"debug.chrome.{key}"
145
+ m[action_key] = QAction(label, win)
146
+ m[action_key].triggered.connect(lambda _=False, u=url: dbg.open_chrome_debug(u))
147
+ m['menu.debug.chrome'].addAction(m[action_key])
148
+
149
+ # add open devtools action
150
+ m['debug.chrome.devtools'] = QAction("Open DevTools", win)
151
+ m['debug.chrome.devtools'].triggered.connect(dbg.open_dev_tools)
152
+ menu.addAction(m['debug.chrome.devtools'])
153
+
116
154
  m['debug.render'].setChecked(bool(win.core.config.get('debug.render')))
@@ -0,0 +1,12 @@
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.19 00:00:00 #
10
+ # ================================================== #
11
+
12
+ from .editor import NodeEditor