pygpt-net 2.6.56__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 (206) hide show
  1. pygpt_net/CHANGELOG.txt +6 -0
  2. pygpt_net/__init__.py +1 -1
  3. pygpt_net/config.py +44 -0
  4. pygpt_net/controller/chat/remote_tools.py +5 -3
  5. pygpt_net/controller/ui/mode.py +5 -3
  6. pygpt_net/core/models/models.py +2 -1
  7. pygpt_net/core/plugins/plugins.py +60 -0
  8. pygpt_net/data/config/config.json +3 -2
  9. pygpt_net/data/config/models.json +2 -2
  10. pygpt_net/data/config/settings.json +14 -0
  11. pygpt_net/data/locale/locale.de.ini +3 -1
  12. pygpt_net/data/locale/locale.en.ini +8 -7
  13. pygpt_net/data/locale/locale.es.ini +2 -0
  14. pygpt_net/data/locale/locale.fr.ini +2 -0
  15. pygpt_net/data/locale/locale.it.ini +2 -0
  16. pygpt_net/data/locale/locale.pl.ini +3 -1
  17. pygpt_net/data/locale/locale.uk.ini +3 -1
  18. pygpt_net/data/locale/locale.zh.ini +2 -0
  19. pygpt_net/data/locale/plugin.agent.de.ini +0 -0
  20. pygpt_net/data/locale/plugin.agent.en.ini +0 -0
  21. pygpt_net/data/locale/plugin.agent.es.ini +0 -0
  22. pygpt_net/data/locale/plugin.agent.fr.ini +0 -0
  23. pygpt_net/data/locale/plugin.agent.it.ini +0 -0
  24. pygpt_net/data/locale/plugin.agent.pl.ini +0 -0
  25. pygpt_net/data/locale/plugin.agent.uk.ini +0 -0
  26. pygpt_net/data/locale/plugin.agent.zh.ini +0 -0
  27. pygpt_net/data/locale/plugin.audio_input.de.ini +0 -0
  28. pygpt_net/data/locale/plugin.audio_input.en.ini +0 -0
  29. pygpt_net/data/locale/plugin.audio_input.es.ini +0 -0
  30. pygpt_net/data/locale/plugin.audio_input.fr.ini +0 -0
  31. pygpt_net/data/locale/plugin.audio_input.it.ini +0 -0
  32. pygpt_net/data/locale/plugin.audio_input.pl.ini +0 -0
  33. pygpt_net/data/locale/plugin.audio_input.uk.ini +0 -0
  34. pygpt_net/data/locale/plugin.audio_input.zh.ini +0 -0
  35. pygpt_net/data/locale/plugin.audio_output.de.ini +0 -0
  36. pygpt_net/data/locale/plugin.audio_output.en.ini +0 -0
  37. pygpt_net/data/locale/plugin.audio_output.es.ini +0 -0
  38. pygpt_net/data/locale/plugin.audio_output.fr.ini +0 -0
  39. pygpt_net/data/locale/plugin.audio_output.it.ini +0 -0
  40. pygpt_net/data/locale/plugin.audio_output.pl.ini +0 -0
  41. pygpt_net/data/locale/plugin.audio_output.uk.ini +0 -0
  42. pygpt_net/data/locale/plugin.audio_output.zh.ini +0 -0
  43. pygpt_net/data/locale/plugin.cmd_api.de.ini +0 -0
  44. pygpt_net/data/locale/plugin.cmd_api.en.ini +0 -0
  45. pygpt_net/data/locale/plugin.cmd_api.es.ini +0 -0
  46. pygpt_net/data/locale/plugin.cmd_api.fr.ini +0 -0
  47. pygpt_net/data/locale/plugin.cmd_api.it.ini +0 -0
  48. pygpt_net/data/locale/plugin.cmd_api.pl.ini +0 -0
  49. pygpt_net/data/locale/plugin.cmd_api.uk.ini +0 -0
  50. pygpt_net/data/locale/plugin.cmd_api.zh.ini +0 -0
  51. pygpt_net/data/locale/plugin.cmd_code_interpreter.de.ini +0 -0
  52. pygpt_net/data/locale/plugin.cmd_code_interpreter.en.ini +0 -0
  53. pygpt_net/data/locale/plugin.cmd_code_interpreter.es.ini +0 -0
  54. pygpt_net/data/locale/plugin.cmd_code_interpreter.fr.ini +0 -0
  55. pygpt_net/data/locale/plugin.cmd_code_interpreter.it.ini +0 -0
  56. pygpt_net/data/locale/plugin.cmd_code_interpreter.pl.ini +0 -0
  57. pygpt_net/data/locale/plugin.cmd_code_interpreter.uk.ini +0 -0
  58. pygpt_net/data/locale/plugin.cmd_code_interpreter.zh.ini +0 -0
  59. pygpt_net/data/locale/plugin.cmd_custom.de.ini +0 -0
  60. pygpt_net/data/locale/plugin.cmd_custom.en.ini +0 -0
  61. pygpt_net/data/locale/plugin.cmd_custom.es.ini +0 -0
  62. pygpt_net/data/locale/plugin.cmd_custom.fr.ini +0 -0
  63. pygpt_net/data/locale/plugin.cmd_custom.it.ini +0 -0
  64. pygpt_net/data/locale/plugin.cmd_custom.pl.ini +0 -0
  65. pygpt_net/data/locale/plugin.cmd_custom.uk.ini +0 -0
  66. pygpt_net/data/locale/plugin.cmd_custom.zh.ini +0 -0
  67. pygpt_net/data/locale/plugin.cmd_files.de.ini +0 -0
  68. pygpt_net/data/locale/plugin.cmd_files.en.ini +0 -0
  69. pygpt_net/data/locale/plugin.cmd_files.es.ini +0 -0
  70. pygpt_net/data/locale/plugin.cmd_files.fr.ini +0 -0
  71. pygpt_net/data/locale/plugin.cmd_files.it.ini +0 -0
  72. pygpt_net/data/locale/plugin.cmd_files.pl.ini +0 -0
  73. pygpt_net/data/locale/plugin.cmd_files.uk.ini +0 -0
  74. pygpt_net/data/locale/plugin.cmd_files.zh.ini +0 -0
  75. pygpt_net/data/locale/plugin.cmd_history.de.ini +0 -0
  76. pygpt_net/data/locale/plugin.cmd_history.en.ini +0 -0
  77. pygpt_net/data/locale/plugin.cmd_history.es.ini +0 -0
  78. pygpt_net/data/locale/plugin.cmd_history.fr.ini +0 -0
  79. pygpt_net/data/locale/plugin.cmd_history.it.ini +0 -0
  80. pygpt_net/data/locale/plugin.cmd_history.pl.ini +0 -0
  81. pygpt_net/data/locale/plugin.cmd_history.uk.ini +0 -0
  82. pygpt_net/data/locale/plugin.cmd_history.zh.ini +0 -0
  83. pygpt_net/data/locale/plugin.cmd_mouse_control.de.ini +0 -0
  84. pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +0 -0
  85. pygpt_net/data/locale/plugin.cmd_mouse_control.es.ini +0 -0
  86. pygpt_net/data/locale/plugin.cmd_mouse_control.fr.ini +0 -0
  87. pygpt_net/data/locale/plugin.cmd_mouse_control.it.ini +0 -0
  88. pygpt_net/data/locale/plugin.cmd_mouse_control.pl.ini +0 -0
  89. pygpt_net/data/locale/plugin.cmd_mouse_control.uk.ini +0 -0
  90. pygpt_net/data/locale/plugin.cmd_mouse_control.zh.ini +0 -0
  91. pygpt_net/data/locale/plugin.cmd_serial.de.ini +0 -0
  92. pygpt_net/data/locale/plugin.cmd_serial.en.ini +0 -0
  93. pygpt_net/data/locale/plugin.cmd_serial.es.ini +0 -0
  94. pygpt_net/data/locale/plugin.cmd_serial.fr.ini +0 -0
  95. pygpt_net/data/locale/plugin.cmd_serial.it.ini +0 -0
  96. pygpt_net/data/locale/plugin.cmd_serial.pl.ini +0 -0
  97. pygpt_net/data/locale/plugin.cmd_serial.uk.ini +0 -0
  98. pygpt_net/data/locale/plugin.cmd_serial.zh.ini +0 -0
  99. pygpt_net/data/locale/plugin.cmd_system.de.ini +0 -0
  100. pygpt_net/data/locale/plugin.cmd_system.en.ini +0 -0
  101. pygpt_net/data/locale/plugin.cmd_system.es.ini +0 -0
  102. pygpt_net/data/locale/plugin.cmd_system.fr.ini +0 -0
  103. pygpt_net/data/locale/plugin.cmd_system.it.ini +0 -0
  104. pygpt_net/data/locale/plugin.cmd_system.pl.ini +0 -0
  105. pygpt_net/data/locale/plugin.cmd_system.uk.ini +0 -0
  106. pygpt_net/data/locale/plugin.cmd_system.zh.ini +0 -0
  107. pygpt_net/data/locale/plugin.cmd_web.de.ini +0 -0
  108. pygpt_net/data/locale/plugin.cmd_web.en.ini +0 -0
  109. pygpt_net/data/locale/plugin.cmd_web.es.ini +0 -0
  110. pygpt_net/data/locale/plugin.cmd_web.fr.ini +0 -0
  111. pygpt_net/data/locale/plugin.cmd_web.it.ini +0 -0
  112. pygpt_net/data/locale/plugin.cmd_web.pl.ini +0 -0
  113. pygpt_net/data/locale/plugin.cmd_web.uk.ini +0 -0
  114. pygpt_net/data/locale/plugin.cmd_web.zh.ini +0 -0
  115. pygpt_net/data/locale/plugin.crontab.de.ini +0 -0
  116. pygpt_net/data/locale/plugin.crontab.en.ini +0 -0
  117. pygpt_net/data/locale/plugin.crontab.es.ini +0 -0
  118. pygpt_net/data/locale/plugin.crontab.fr.ini +0 -0
  119. pygpt_net/data/locale/plugin.crontab.it.ini +0 -0
  120. pygpt_net/data/locale/plugin.crontab.pl.ini +0 -0
  121. pygpt_net/data/locale/plugin.crontab.uk.ini +0 -0
  122. pygpt_net/data/locale/plugin.crontab.zh.ini +0 -0
  123. pygpt_net/data/locale/plugin.experts.de.ini +0 -0
  124. pygpt_net/data/locale/plugin.experts.en.ini +0 -0
  125. pygpt_net/data/locale/plugin.experts.es.ini +0 -0
  126. pygpt_net/data/locale/plugin.experts.fr.ini +0 -0
  127. pygpt_net/data/locale/plugin.experts.it.ini +0 -0
  128. pygpt_net/data/locale/plugin.experts.pl.ini +0 -0
  129. pygpt_net/data/locale/plugin.experts.uk.ini +0 -0
  130. pygpt_net/data/locale/plugin.experts.zh.ini +0 -0
  131. pygpt_net/data/locale/plugin.extra_prompt.de.ini +0 -0
  132. pygpt_net/data/locale/plugin.extra_prompt.en.ini +0 -0
  133. pygpt_net/data/locale/plugin.extra_prompt.es.ini +0 -0
  134. pygpt_net/data/locale/plugin.extra_prompt.fr.ini +0 -0
  135. pygpt_net/data/locale/plugin.extra_prompt.it.ini +0 -0
  136. pygpt_net/data/locale/plugin.extra_prompt.pl.ini +0 -0
  137. pygpt_net/data/locale/plugin.extra_prompt.uk.ini +0 -0
  138. pygpt_net/data/locale/plugin.extra_prompt.zh.ini +0 -0
  139. pygpt_net/data/locale/plugin.idx_llama_index.de.ini +0 -0
  140. pygpt_net/data/locale/plugin.idx_llama_index.en.ini +0 -0
  141. pygpt_net/data/locale/plugin.idx_llama_index.es.ini +0 -0
  142. pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +0 -0
  143. pygpt_net/data/locale/plugin.idx_llama_index.it.ini +0 -0
  144. pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +0 -0
  145. pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +0 -0
  146. pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +0 -0
  147. pygpt_net/data/locale/plugin.mailer.en.ini +0 -0
  148. pygpt_net/data/locale/plugin.mcp.en.ini +0 -0
  149. pygpt_net/data/locale/plugin.openai_dalle.de.ini +0 -0
  150. pygpt_net/data/locale/plugin.openai_dalle.en.ini +0 -0
  151. pygpt_net/data/locale/plugin.openai_dalle.es.ini +0 -0
  152. pygpt_net/data/locale/plugin.openai_dalle.fr.ini +0 -0
  153. pygpt_net/data/locale/plugin.openai_dalle.it.ini +0 -0
  154. pygpt_net/data/locale/plugin.openai_dalle.pl.ini +0 -0
  155. pygpt_net/data/locale/plugin.openai_dalle.uk.ini +0 -0
  156. pygpt_net/data/locale/plugin.openai_dalle.zh.ini +0 -0
  157. pygpt_net/data/locale/plugin.openai_vision.de.ini +0 -0
  158. pygpt_net/data/locale/plugin.openai_vision.en.ini +0 -0
  159. pygpt_net/data/locale/plugin.openai_vision.es.ini +0 -0
  160. pygpt_net/data/locale/plugin.openai_vision.fr.ini +0 -0
  161. pygpt_net/data/locale/plugin.openai_vision.it.ini +0 -0
  162. pygpt_net/data/locale/plugin.openai_vision.pl.ini +0 -0
  163. pygpt_net/data/locale/plugin.openai_vision.uk.ini +0 -0
  164. pygpt_net/data/locale/plugin.openai_vision.zh.ini +0 -0
  165. pygpt_net/data/locale/plugin.osm.en.ini +24 -24
  166. pygpt_net/data/locale/plugin.real_time.de.ini +0 -0
  167. pygpt_net/data/locale/plugin.real_time.en.ini +0 -0
  168. pygpt_net/data/locale/plugin.real_time.es.ini +0 -0
  169. pygpt_net/data/locale/plugin.real_time.fr.ini +0 -0
  170. pygpt_net/data/locale/plugin.real_time.it.ini +0 -0
  171. pygpt_net/data/locale/plugin.real_time.pl.ini +0 -0
  172. pygpt_net/data/locale/plugin.real_time.uk.ini +0 -0
  173. pygpt_net/data/locale/plugin.real_time.zh.ini +0 -0
  174. pygpt_net/data/locale/plugin.voice_control.de.ini +0 -0
  175. pygpt_net/data/locale/plugin.voice_control.en.ini +0 -0
  176. pygpt_net/data/locale/plugin.voice_control.es.ini +0 -0
  177. pygpt_net/data/locale/plugin.voice_control.fr.ini +0 -0
  178. pygpt_net/data/locale/plugin.voice_control.it.ini +0 -0
  179. pygpt_net/data/locale/plugin.voice_control.pl.ini +0 -0
  180. pygpt_net/data/locale/plugin.voice_control.uk.ini +0 -0
  181. pygpt_net/data/locale/plugin.voice_control.zh.ini +0 -0
  182. pygpt_net/data/locale/plugin.wolfram.en.ini +9 -9
  183. pygpt_net/plugin/cmd_web/config.py +17 -17
  184. pygpt_net/plugin/cmd_web/worker.py +325 -171
  185. pygpt_net/provider/api/x_ai/__init__.py +2 -0
  186. pygpt_net/provider/core/config/patch.py +15 -0
  187. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +1 -0
  188. pygpt_net/provider/llms/anthropic.py +4 -0
  189. pygpt_net/provider/llms/base.py +2 -0
  190. pygpt_net/provider/llms/deepseek_api.py +2 -0
  191. pygpt_net/provider/llms/google.py +2 -0
  192. pygpt_net/provider/llms/hugging_face_api.py +4 -0
  193. pygpt_net/provider/llms/hugging_face_router.py +2 -0
  194. pygpt_net/provider/llms/mistral.py +4 -0
  195. pygpt_net/provider/llms/perplexity.py +2 -0
  196. pygpt_net/provider/llms/x_ai.py +2 -0
  197. pygpt_net/ui/layout/chat/output.py +5 -5
  198. pygpt_net/ui/widget/dialog/base.py +4 -1
  199. pygpt_net/ui/widget/textarea/html.py +1 -0
  200. pygpt_net/ui/widget/textarea/input.py +19 -3
  201. pygpt_net/ui/widget/textarea/web.py +2 -1
  202. {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.57.dist-info}/METADATA +10 -2
  203. {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.57.dist-info}/RECORD +44 -44
  204. {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.57.dist-info}/LICENSE +0 -0
  205. {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.57.dist-info}/WHEEL +0 -0
  206. {pygpt_net-2.6.56.dist-info → pygpt_net-2.6.57.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: 2025.08.11 14:00:00 #
9
+ # Updated Date: 2025.09.22 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -32,6 +32,73 @@ class Worker(BaseWorker):
32
32
  self.ctx = None
33
33
  self.msg = None
34
34
 
35
+ # --- helpers ---
36
+
37
+ def _to_list(self, value):
38
+ """Normalize any value to a flat list of strings."""
39
+ # Accept list-like values
40
+ if value is None:
41
+ return []
42
+ if isinstance(value, list):
43
+ result = []
44
+ for v in value:
45
+ result.extend(self._to_list(v))
46
+ return result
47
+ if isinstance(value, (tuple, set)):
48
+ result = []
49
+ for v in value:
50
+ result.extend(self._to_list(v))
51
+ return result
52
+ # Accept string (also split by whitespace/newlines to support multiple URLs pasted together)
53
+ if isinstance(value, str):
54
+ # Split by any whitespace; do not split by comma/semicolon to avoid breaking data: URLs etc.
55
+ parts = value.split()
56
+ return [p.strip() for p in parts if p and isinstance(p, str)]
57
+ # Fallback: try to cast to str
58
+ try:
59
+ s = str(value).strip()
60
+ return [s] if s else []
61
+ except Exception:
62
+ return []
63
+
64
+ def _extract_urls(self, item: dict, allow_args: bool = True) -> list:
65
+ """Extract and normalize URLs from item (supports url, urls; also from args if present)."""
66
+ urls = []
67
+ try:
68
+ if self.has_param(item, "urls"):
69
+ urls.extend(self._to_list(self.get_param(item, "urls")))
70
+ if self.has_param(item, "url"):
71
+ urls.extend(self._to_list(self.get_param(item, "url")))
72
+ if allow_args and self.has_param(item, "args"):
73
+ args = self.get_param(item, "args")
74
+ if isinstance(args, dict):
75
+ if "urls" in args:
76
+ urls.extend(self._to_list(args.get("urls")))
77
+ if "url" in args:
78
+ urls.extend(self._to_list(args.get("url")))
79
+ except Exception:
80
+ pass
81
+
82
+ # Deduplicate and cleanup
83
+ cleaned = []
84
+ seen = set()
85
+ for u in urls:
86
+ if not u:
87
+ continue
88
+ if not isinstance(u, str):
89
+ try:
90
+ u = str(u)
91
+ except Exception:
92
+ continue
93
+ u = u.strip()
94
+ if not u:
95
+ continue
96
+ if u in seen:
97
+ continue
98
+ seen.add(u)
99
+ cleaned.append(u)
100
+ return cleaned
101
+
35
102
  @Slot()
36
103
  def run(self):
37
104
  try:
@@ -111,7 +178,7 @@ class Worker(BaseWorker):
111
178
  page,
112
179
  prompt,
113
180
  )
114
- self.msg = "Web search finished: '{}'".format(query )
181
+ self.msg = f"Web search finished: '{query}'"
115
182
  result = {
116
183
  'query': query,
117
184
  'content': content,
@@ -123,7 +190,8 @@ class Worker(BaseWorker):
123
190
  self.ctx.urls_before.append(url)
124
191
  if img:
125
192
  result["thumb_img"] = img
126
- self.ctx.images_before.append(img)
193
+ if img not in self.ctx.images_before:
194
+ self.ctx.images_before.append(img)
127
195
 
128
196
  self.ctx.save_reply = True # leave links in context
129
197
  return self.make_response(item, result)
@@ -138,28 +206,43 @@ class Worker(BaseWorker):
138
206
  prompt = None
139
207
  if self.has_param(item, "summarize_prompt"):
140
208
  prompt = self.get_param(item, "summarize_prompt")
141
- url = self.get_param(item, "url", "")
142
209
 
143
- self.msg = "Opening Web URL: '{}'".format(url)
144
- content, url, img = self.websearch.open_url(
145
- url,
146
- prompt,
147
- )
148
- result = {
149
- 'url': url,
150
- 'content': content,
151
- }
152
- context = "From: " + url + ":\n--------------------------------\n" + str(content)
153
- if url:
154
- self.ctx.urls_before.append(url)
155
- if img:
156
- result["thumb_img"] = img
157
- self.ctx.images_before.append(img)
210
+ # Get URLs with robust fallback (urls/url; also supports lists)
211
+ urls = self._extract_urls(item, allow_args=False)
212
+
213
+ results = []
214
+ context = ""
215
+ for url in urls:
216
+ try:
217
+ self.msg = f"Opening Web URL: '{url}'"
218
+ content, url_resolved, img = self.websearch.open_url(
219
+ url,
220
+ prompt,
221
+ )
222
+ # Always include the URL the result refers to
223
+ result = {
224
+ 'url': url_resolved or url,
225
+ 'content': content,
226
+ }
227
+ context += f"From: {url_resolved or url} + :\n--------------------------------\n{content}"
228
+ if url_resolved or url:
229
+ self.ctx.urls_before.append(url_resolved or url)
230
+ if img:
231
+ result["thumb_img"] = img
232
+ if img not in self.ctx.images_before:
233
+ self.ctx.images_before.append(img)
234
+ results.append(result)
235
+ except Exception as e:
236
+ results.append({
237
+ 'url': url,
238
+ 'content': f"Error: {e}",
239
+ })
240
+ self.log(f"Error opening URL '{url}': {e}")
158
241
 
159
242
  extra = {
160
243
  "context": context,
161
244
  }
162
- return self.make_response(item, result, extra=extra)
245
+ return self.make_response(item, results, extra=extra)
163
246
 
164
247
  def cmd_web_url_raw(self, item: dict) -> dict:
165
248
  """
@@ -168,26 +251,40 @@ class Worker(BaseWorker):
168
251
  :param item: command item
169
252
  :return: response item
170
253
  """
171
- url = self.get_param(item, "url", "")
172
- self.msg = "Opening Web URL: '{}'".format(url)
173
- content, url, img = self.websearch.open_url_raw(
174
- url,
175
- )
176
- result = {
177
- 'url': url,
178
- 'content': content,
179
- }
180
- context = "From: " + url + ":\n--------------------------------\n" + str(content)
181
- if url:
182
- self.ctx.urls_before.append(url)
183
- if img:
184
- result["thumb_img"] = img
185
- self.ctx.images_before.append(img)
254
+ # Get URLs with robust fallback (urls/url; also supports lists)
255
+ urls = self._extract_urls(item, allow_args=False)
256
+
257
+ results = []
258
+ context = ""
259
+ for url in urls:
260
+ try:
261
+ self.msg = f"Opening Web URL: '{url}'"
262
+ content, url_resolved, img = self.websearch.open_url_raw(
263
+ url,
264
+ )
265
+ result = {
266
+ 'url': url_resolved or url,
267
+ 'content': content,
268
+ }
269
+ context += f"From: {url_resolved or url} + :\n--------------------------------\n{content}"
270
+ if url_resolved or url:
271
+ self.ctx.urls_before.append(url_resolved or url)
272
+ if img:
273
+ result["thumb_img"] = img
274
+ if img not in self.ctx.images_before:
275
+ self.ctx.images_before.append(img)
276
+ results.append(result)
277
+ except Exception as e:
278
+ results.append({
279
+ 'url': url,
280
+ 'content': f"Error: {e}",
281
+ })
282
+ self.log(f"Error opening URL '{url}': {e}")
186
283
 
187
284
  extra = {
188
285
  "context": context,
189
286
  }
190
- return self.make_response(item, result, extra=extra)
287
+ return self.make_response(item, results, extra=extra)
191
288
 
192
289
  def cmd_web_urls(self, item: dict) -> dict:
193
290
  """
@@ -216,7 +313,7 @@ class Worker(BaseWorker):
216
313
  num,
217
314
  offset,
218
315
  )
219
- self.msg = "Web search finished: '{}'".format(query)
316
+ self.msg = f"Web search finished: '{query}'"
220
317
  result = {
221
318
  'query': query,
222
319
  'urls': json.dumps(urls),
@@ -226,7 +323,8 @@ class Worker(BaseWorker):
226
323
  }
227
324
  if urls:
228
325
  for url in urls:
229
- self.ctx.urls_before.append(url)
326
+ if url not in self.ctx.urls_before:
327
+ self.ctx.urls_before.append(url)
230
328
 
231
329
  return self.make_response(item, result)
232
330
 
@@ -245,39 +343,38 @@ class Worker(BaseWorker):
245
343
  if self.has_param(item, "args"):
246
344
  args = self.get_param(item, "args")
247
345
 
248
- url = ""
249
- if self.has_param(item, "url"):
250
- url = self.get_param(item, "url") # from default param
251
- if "url" in args:
252
- url = args["url"] # override from args
253
-
254
- if not url:
346
+ # Extract URLs with fallback: url(s) from item and args
347
+ urls = self._extract_urls(item, allow_args=True)
348
+ if not urls:
255
349
  return self.make_response(item, "No URL provided")
256
350
 
257
- self.msg = "Indexing URL: '{}'".format(url)
258
351
  idx_name = self.plugin.get_option_value("idx")
259
- self.status("Please wait... indexing: {}...".format(url))
260
-
261
- # index URL via Llama-index
262
- num, errors = self.plugin.window.core.idx.index_urls(
263
- idx=idx_name,
264
- urls=[url],
265
- type=type,
266
- extra_args=args,
267
- )
268
- result = {
269
- 'url': url,
270
- 'num_indexed': num,
271
- 'index': idx_name,
272
- 'errors': errors,
273
- }
274
- if url and (url.startswith("http://") or url.startswith("https://")):
275
- self.ctx.urls_before.append(url)
352
+ results = []
276
353
 
277
- extra = {
278
- "url": url,
279
- }
280
- return self.make_response(item, result, extra=extra)
354
+ for url in urls:
355
+ self.msg = f"Indexing URL: '{url}'"
356
+ self.status(f"Please wait... indexing: {url}...")
357
+
358
+ # index URL via Llama-index per URL to keep per-URL result
359
+ num, errors = self.plugin.window.core.idx.index_urls(
360
+ idx=idx_name,
361
+ urls=[url],
362
+ type=type,
363
+ extra_args=args,
364
+ )
365
+ result = {
366
+ 'url': url,
367
+ 'num_indexed': num,
368
+ 'index': idx_name,
369
+ 'errors': errors,
370
+ }
371
+ if url and (url.startswith("http://") or url.startswith("https://")):
372
+ if url not in self.ctx.urls_before:
373
+ self.ctx.urls_before.append(url)
374
+ results.append(result)
375
+
376
+ # Return list of results with URLs included
377
+ return self.make_response(item, results)
281
378
 
282
379
  def cmd_web_index_query(self, item: dict) -> dict:
283
380
  """
@@ -295,69 +392,85 @@ class Worker(BaseWorker):
295
392
  if self.has_param(item, "args"):
296
393
  args = self.get_param(item, "args")
297
394
 
298
- url = ""
299
- if self.has_param(item, "url"):
300
- url = self.get_param(item, "url") # from default param
301
- if isinstance(args, dict) and "url" in args:
302
- url = args["url"] # override from args
303
-
304
- if not url:
395
+ # Extract URLs with fallback: url(s) from item and args
396
+ urls = self._extract_urls(item, allow_args=True)
397
+ if not urls:
305
398
  result = "No URL provided"
306
399
  return self.make_response(item, result)
307
400
 
308
401
  if self.has_param(item, "query"):
309
402
  query = self.get_param(item, "query")
310
403
 
311
- result = "No data"
312
- context = None
313
- if query is not None:
314
- # query file using temp index (created on the fly)
315
- self.log("Querying web: {}".format(url))
404
+ results = []
405
+ context = ""
316
406
 
317
- # get tmp query model
318
- model = self.plugin.window.core.models.from_defaults()
319
- tmp_model = self.plugin.get_option_value("model_tmp_query")
320
- if self.plugin.window.core.models.has(tmp_model):
321
- model = self.plugin.window.core.models.get(tmp_model)
407
+ # get tmp query model (shared for all URLs)
408
+ model = self.plugin.window.core.models.from_defaults()
409
+ tmp_model = self.plugin.get_option_value("model_tmp_query")
410
+ if self.plugin.window.core.models.has(tmp_model):
411
+ model = self.plugin.window.core.models.get(tmp_model)
322
412
 
323
- answer = self.plugin.window.core.idx.chat.query_web(
324
- ctx=self.ctx,
325
- type=type,
326
- url=url,
327
- args=args,
328
- query=query,
329
- model=model,
330
- )
331
- self.log("Response from temporary in-memory index: {}".format(answer))
332
- if answer:
333
- from_str = type
334
- if url:
335
- from_str += ", URL: " + url
336
- result = answer
337
- context = "From: " + from_str + ":\n--------------------------------\n" + answer
338
-
339
- # + auto-index to main index using Llama-index
340
- if self.plugin.get_option_value("auto_index"):
341
- self.msg = "Indexing URL: '{}'".format(url)
342
- idx_name = self.plugin.get_option_value("idx")
343
- # show status
344
- self.status("Please wait... indexing: {}...".format(url))
345
- # index URL via Llama-index
346
- num, errors = self.plugin.window.core.idx.index_urls(
347
- idx=idx_name,
348
- urls=[url],
349
- type=type,
350
- extra_args=args,
351
- )
413
+ if query is not None:
414
+ for url in urls:
415
+ try:
416
+ # query file using temp index (created on the fly)
417
+ self.log(f"Querying web: {url}")
418
+
419
+ answer = self.plugin.window.core.idx.chat.query_web(
420
+ ctx=self.ctx,
421
+ type=type,
422
+ url=url,
423
+ args=args,
424
+ query=query,
425
+ model=model,
426
+ )
427
+ self.log(f"Response from temporary in-memory index: {answer}")
428
+ content = answer if answer else "No data"
429
+ from_str = type
430
+ if url:
431
+ from_str += ", URL: " + url
432
+ context += "From: " + from_str + ":\n--------------------------------\n" + content
433
+
434
+ # + auto-index to main index using Llama-index
435
+ if self.plugin.get_option_value("auto_index"):
436
+ self.msg = f"Indexing URL: '{url}'"
437
+ idx_name = self.plugin.get_option_value("idx")
438
+ # show status
439
+ self.status(f"Please wait... indexing: {url}...")
440
+ # index URL via Llama-index
441
+ self.plugin.window.core.idx.index_urls(
442
+ idx=idx_name,
443
+ urls=[url],
444
+ type=type,
445
+ extra_args=args,
446
+ )
352
447
 
353
- # add URL to context
354
- if url and (url.startswith("http://") or url.startswith("https://")):
355
- self.ctx.urls_before.append(url)
448
+ # add URL to context
449
+ if url and (url.startswith("http://") or url.startswith("https://")):
450
+ self.ctx.urls_before.append(url)
451
+
452
+ results.append({
453
+ 'url': url,
454
+ 'content': content,
455
+ })
456
+ except Exception as e:
457
+ results.append({
458
+ 'url': url,
459
+ 'content': f"Error: {e}",
460
+ })
461
+ self.log(f"Error querying/indexing URL '{url}': {e}")
462
+ else:
463
+ # No query provided - return info per URL to keep URL in response
464
+ for url in urls:
465
+ results.append({
466
+ 'url': url,
467
+ 'content': "No data",
468
+ })
356
469
 
357
470
  extra = {
358
- "context": context,
471
+ "context": context if context else None,
359
472
  }
360
- return self.make_response(item, result, extra=extra)
473
+ return self.make_response(item, results, extra=extra)
361
474
 
362
475
  def cmd_web_extract_links(self, item: dict) -> dict:
363
476
  """
@@ -366,17 +479,30 @@ class Worker(BaseWorker):
366
479
  :param item: command item
367
480
  :return: response item
368
481
  """
369
- url = ""
370
- if self.has_param(item, "url"):
371
- url = self.get_param(item, "url")
372
- if not url:
482
+ urls = self._extract_urls(item, allow_args=False)
483
+ if not urls:
373
484
  return self.make_response(item, "No URL provided")
374
- links = self.plugin.window.core.web.helpers.get_links(url)
375
- result = {
376
- 'links': links,
377
- }
378
- self.ctx.urls_before.append(url)
379
- return self.make_response(item, result)
485
+
486
+ results = []
487
+ for url in urls:
488
+ try:
489
+ links = self.plugin.window.core.web.helpers.get_links(url)
490
+ result = {
491
+ 'url': url,
492
+ 'links': links,
493
+ }
494
+ if url not in self.ctx.urls_before:
495
+ self.ctx.urls_before.append(url)
496
+ results.append(result)
497
+ except Exception as e:
498
+ results.append({
499
+ 'url': url,
500
+ 'links': [],
501
+ 'content': f"Error: {e}",
502
+ })
503
+ self.log(f"Error extracting links from '{url}': {e}")
504
+
505
+ return self.make_response(item, results)
380
506
 
381
507
  def cmd_web_extract_images(self, item: dict) -> dict:
382
508
  """
@@ -386,27 +512,43 @@ class Worker(BaseWorker):
386
512
  :return: response item
387
513
  """
388
514
  download = False
389
- url = ""
390
- if self.has_param(item, "url"):
391
- url = self.get_param(item, "url")
392
515
  if self.has_param(item, "download"):
393
516
  download = bool(self.get_param(item, "download"))
394
- if not url:
517
+
518
+ urls = self._extract_urls(item, allow_args=False)
519
+ if not urls:
395
520
  return self.make_response(item, "No URL provided")
396
- images = self.plugin.window.core.web.helpers.get_images(url)
397
- result = {
398
- 'images': images,
399
- }
400
- if images and download:
401
- for img in images:
402
- try:
403
- path = self.plugin.window.core.web.helpers.download_image(img)
404
- if path:
405
- self.ctx.images_before.append(path)
406
- except Exception as e:
407
- print(e)
408
- self.ctx.urls_before.append(url)
409
- return self.make_response(item, result)
521
+
522
+ results = []
523
+ for url in urls:
524
+ try:
525
+ images = self.plugin.window.core.web.helpers.get_images(url)
526
+ result = {
527
+ 'url': url,
528
+ 'images': images,
529
+ }
530
+ if images and download:
531
+ for img in images:
532
+ try:
533
+ path = self.plugin.window.core.web.helpers.download_image(img)
534
+ if path:
535
+ if path not in self.ctx.images_before:
536
+ self.ctx.images_before.append(path)
537
+ except Exception as e:
538
+ # Keep downloading independent per image
539
+ print(e)
540
+ if url not in self.ctx.urls_before:
541
+ self.ctx.urls_before.append(url)
542
+ results.append(result)
543
+ except Exception as e:
544
+ results.append({
545
+ 'url': url,
546
+ 'images': [],
547
+ 'content': f"Error: {e}",
548
+ })
549
+ self.log(f"Error extracting images from '{url}': {e}")
550
+
551
+ return self.make_response(item, results)
410
552
 
411
553
  def cmd_web_request(self, item: dict) -> dict:
412
554
  """
@@ -415,7 +557,6 @@ class Worker(BaseWorker):
415
557
  :param item: command item
416
558
  :return: response item
417
559
  """
418
- url = ""
419
560
  method = "GET"
420
561
  data = None
421
562
  raw = None
@@ -425,10 +566,10 @@ class Worker(BaseWorker):
425
566
  cookies = None
426
567
  files = None
427
568
 
428
- if self.has_param(item, "url"):
429
- url = self.get_param(item, "url")
430
- if not url:
569
+ urls = self._extract_urls(item, allow_args=False)
570
+ if not urls:
431
571
  return self.make_response(item, "No URL provided")
572
+
432
573
  if self.has_param(item, "method"):
433
574
  method = self.get_param(item, "method")
434
575
  if self.has_param(item, "data_form"):
@@ -446,24 +587,37 @@ class Worker(BaseWorker):
446
587
  if self.has_param(item, "files"):
447
588
  files = self.get_param(item, "files")
448
589
 
449
- code, response = self.window.core.web.helpers.request(
450
- url=url,
451
- method=method,
452
- headers=headers,
453
- params=params,
454
- data=data if data else raw,
455
- json=json,
456
- cookies=cookies,
457
- files=files,
458
- timeout=self.plugin.get_option_value("timeout"),
459
- disable_ssl_verify=self.plugin.get_option_value("disable_ssl"),
460
- allow_redirects=True,
461
- stream=False,
462
- user_agent=self.plugin.get_option_value("user_agent"))
463
- result = {
464
- 'url': url,
465
- 'code': code,
466
- 'response': response,
467
- }
468
- self.ctx.urls_before.append(url)
469
- return self.make_response(item, result)
590
+ results = []
591
+ for url in urls:
592
+ try:
593
+ code, response = self.window.core.web.helpers.request(
594
+ url=url,
595
+ method=method,
596
+ headers=headers,
597
+ params=params,
598
+ data=data if data else raw,
599
+ json=json,
600
+ cookies=cookies,
601
+ files=files,
602
+ timeout=self.plugin.get_option_value("timeout"),
603
+ disable_ssl_verify=self.plugin.get_option_value("disable_ssl"),
604
+ allow_redirects=True,
605
+ stream=False,
606
+ user_agent=self.plugin.get_option_value("user_agent"))
607
+ result = {
608
+ 'url': url,
609
+ 'code': code,
610
+ 'response': response,
611
+ }
612
+ if url not in self.ctx.urls_before:
613
+ self.ctx.urls_before.append(url)
614
+ results.append(result)
615
+ except Exception as e:
616
+ results.append({
617
+ 'url': url,
618
+ 'code': None,
619
+ 'response': f"Error: {e}",
620
+ })
621
+ self.log(f"Error during web request to '{url}': {e}")
622
+
623
+ return self.make_response(item, results)
@@ -75,6 +75,8 @@ class ApiXAI:
75
75
  api_key = cfg.get("api_key_xai") or os.environ.get("XAI_API_KEY") or ""
76
76
  timeout = cfg.get("api_native_xai.timeout") # optional
77
77
  proxy = cfg.get("api_proxy") or ""
78
+ if not cfg.get("api_proxy.enabled"):
79
+ proxy = ""
78
80
 
79
81
  kwargs: Dict[str, Any] = {}
80
82
  if api_key:
@@ -31,6 +31,7 @@ class Patch:
31
31
  """
32
32
  data = self.window.core.config.all()
33
33
  cfg_get_base = self.window.core.config.get_base
34
+ remove_plugin_config = self.window.core.config.remove_plugin_config
34
35
  patch_css = self.window.core.updater.patch_css
35
36
  current = "0.0.0"
36
37
  updated = False
@@ -129,6 +130,20 @@ class Patch:
129
130
  patch_css('web-chatgpt_wide.darkest.css', True)
130
131
  updated = True
131
132
 
133
+ # < 2.6.57
134
+ if old < parse_version("2.6.57"):
135
+ print("Migrating config from < 2.6.57...")
136
+ remove_plugin_config("cmd_web", "max_open_urls")
137
+ remove_plugin_config("cmd_web", "cmd.web_url_open")
138
+ remove_plugin_config("cmd_web", "cmd.web_url_raw")
139
+ remove_plugin_config("cmd_web", "cmd.web_extract_links")
140
+ remove_plugin_config("cmd_web", "cmd.web_extract_images")
141
+ if "api_proxy.enabled" not in data:
142
+ data["api_proxy.enabled"] = False
143
+ if "api_proxy" in data and data["api_proxy"]:
144
+ data["api_proxy.enabled"] = True
145
+ updated = True
146
+
132
147
  # update file
133
148
  migrated = False
134
149
  if updated:
@@ -29,6 +29,7 @@ class Patch:
29
29
  """
30
30
  data = self.window.core.config.all()
31
31
  cfg_get_base = self.window.core.config.get_base
32
+ remove_plugin_config = self.window.core.config.remove_plugin_config
32
33
  patch_css = self.window.core.updater.patch_css
33
34
  current = "0.0.0"
34
35
  updated = False