pygpt-net 2.6.20__py3-none-any.whl → 2.6.22__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 (221) hide show
  1. pygpt_net/CHANGELOG.txt +13 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +3 -1
  4. pygpt_net/controller/__init__.py +4 -8
  5. pygpt_net/controller/access/voice.py +2 -2
  6. pygpt_net/controller/agent/agent.py +130 -2
  7. pygpt_net/controller/agent/experts.py +93 -96
  8. pygpt_net/controller/agent/llama.py +2 -1
  9. pygpt_net/controller/assistant/assistant.py +18 -1
  10. pygpt_net/controller/assistant/batch.py +2 -3
  11. pygpt_net/controller/assistant/editor.py +2 -2
  12. pygpt_net/controller/assistant/files.py +2 -3
  13. pygpt_net/controller/assistant/store.py +2 -2
  14. pygpt_net/controller/attachment/attachment.py +17 -1
  15. pygpt_net/controller/audio/audio.py +2 -2
  16. pygpt_net/controller/camera/camera.py +15 -7
  17. pygpt_net/controller/chat/chat.py +2 -2
  18. pygpt_net/controller/chat/common.py +50 -33
  19. pygpt_net/controller/chat/image.py +67 -77
  20. pygpt_net/controller/chat/input.py +94 -166
  21. pygpt_net/controller/chat/output.py +83 -140
  22. pygpt_net/controller/chat/response.py +83 -102
  23. pygpt_net/controller/chat/text.py +116 -149
  24. pygpt_net/controller/ctx/common.py +2 -1
  25. pygpt_net/controller/ctx/ctx.py +87 -6
  26. pygpt_net/controller/files/files.py +13 -1
  27. pygpt_net/controller/idx/idx.py +26 -2
  28. pygpt_net/controller/idx/indexer.py +85 -76
  29. pygpt_net/controller/kernel/reply.py +53 -66
  30. pygpt_net/controller/kernel/stack.py +16 -16
  31. pygpt_net/controller/lang/lang.py +52 -34
  32. pygpt_net/controller/model/importer.py +3 -2
  33. pygpt_net/controller/model/model.py +62 -3
  34. pygpt_net/controller/notepad/notepad.py +86 -84
  35. pygpt_net/controller/plugins/settings.py +3 -4
  36. pygpt_net/controller/settings/editor.py +4 -4
  37. pygpt_net/controller/settings/profile.py +105 -124
  38. pygpt_net/controller/theme/menu.py +154 -57
  39. pygpt_net/controller/theme/nodes.py +51 -44
  40. pygpt_net/controller/theme/theme.py +33 -9
  41. pygpt_net/controller/tools/tools.py +2 -2
  42. pygpt_net/controller/ui/tabs.py +2 -3
  43. pygpt_net/controller/ui/ui.py +16 -2
  44. pygpt_net/core/agents/observer/evaluation.py +3 -3
  45. pygpt_net/core/agents/provider.py +25 -3
  46. pygpt_net/core/agents/runner.py +4 -1
  47. pygpt_net/core/agents/runners/llama_workflow.py +19 -7
  48. pygpt_net/core/agents/runners/loop.py +3 -1
  49. pygpt_net/core/agents/runners/openai_workflow.py +17 -3
  50. pygpt_net/core/agents/tools.py +4 -1
  51. pygpt_net/core/bridge/context.py +34 -37
  52. pygpt_net/core/ctx/container.py +13 -12
  53. pygpt_net/core/ctx/ctx.py +1 -1
  54. pygpt_net/core/ctx/output.py +7 -4
  55. pygpt_net/core/db/database.py +2 -2
  56. pygpt_net/core/debug/console/console.py +2 -2
  57. pygpt_net/core/debug/debug.py +12 -1
  58. pygpt_net/core/dispatcher/dispatcher.py +24 -1
  59. pygpt_net/core/events/app.py +7 -7
  60. pygpt_net/core/events/control.py +26 -26
  61. pygpt_net/core/events/event.py +6 -3
  62. pygpt_net/core/events/kernel.py +2 -2
  63. pygpt_net/core/events/render.py +13 -13
  64. pygpt_net/core/experts/experts.py +76 -82
  65. pygpt_net/core/experts/worker.py +12 -12
  66. pygpt_net/core/filesystem/actions.py +1 -2
  67. pygpt_net/core/models/models.py +5 -1
  68. pygpt_net/core/models/ollama.py +14 -5
  69. pygpt_net/core/render/plain/helpers.py +2 -5
  70. pygpt_net/core/render/plain/renderer.py +26 -30
  71. pygpt_net/core/render/web/body.py +1 -1
  72. pygpt_net/core/render/web/helpers.py +2 -2
  73. pygpt_net/core/render/web/renderer.py +4 -4
  74. pygpt_net/core/settings/settings.py +43 -13
  75. pygpt_net/core/tabs/tabs.py +20 -13
  76. pygpt_net/core/types/__init__.py +2 -1
  77. pygpt_net/core/types/agent.py +4 -4
  78. pygpt_net/core/types/base.py +19 -0
  79. pygpt_net/core/types/console.py +6 -6
  80. pygpt_net/core/types/mode.py +8 -8
  81. pygpt_net/core/types/multimodal.py +3 -3
  82. pygpt_net/core/types/openai.py +2 -1
  83. pygpt_net/data/config/config.json +5 -5
  84. pygpt_net/data/config/models.json +19 -3
  85. pygpt_net/data/config/settings.json +14 -14
  86. pygpt_net/data/locale/locale.de.ini +4 -1
  87. pygpt_net/data/locale/locale.en.ini +6 -3
  88. pygpt_net/data/locale/locale.es.ini +4 -1
  89. pygpt_net/data/locale/locale.fr.ini +4 -1
  90. pygpt_net/data/locale/locale.it.ini +4 -1
  91. pygpt_net/data/locale/locale.pl.ini +5 -4
  92. pygpt_net/data/locale/locale.uk.ini +4 -1
  93. pygpt_net/data/locale/locale.zh.ini +4 -1
  94. pygpt_net/item/ctx.py +256 -240
  95. pygpt_net/item/model.py +59 -116
  96. pygpt_net/item/preset.py +122 -105
  97. pygpt_net/plugin/twitter/plugin.py +2 -2
  98. pygpt_net/provider/agents/llama_index/workflow/planner.py +3 -3
  99. pygpt_net/provider/agents/openai/agent.py +4 -12
  100. pygpt_net/provider/agents/openai/agent_b2b.py +10 -15
  101. pygpt_net/provider/agents/openai/agent_planner.py +4 -4
  102. pygpt_net/provider/agents/openai/agent_with_experts.py +3 -7
  103. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -8
  104. pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -8
  105. pygpt_net/provider/agents/openai/bot_researcher.py +2 -18
  106. pygpt_net/provider/agents/openai/bots/__init__.py +0 -0
  107. pygpt_net/provider/agents/openai/bots/research_bot/__init__.py +0 -0
  108. pygpt_net/provider/agents/openai/bots/research_bot/agents/__init__.py +0 -0
  109. pygpt_net/provider/agents/openai/bots/research_bot/agents/planner_agent.py +1 -1
  110. pygpt_net/provider/agents/openai/bots/research_bot/agents/search_agent.py +1 -0
  111. pygpt_net/provider/agents/openai/bots/research_bot/agents/writer_agent.py +1 -1
  112. pygpt_net/provider/agents/openai/bots/research_bot/manager.py +1 -10
  113. pygpt_net/provider/agents/openai/evolve.py +5 -9
  114. pygpt_net/provider/agents/openai/supervisor.py +4 -8
  115. pygpt_net/provider/core/config/patch.py +10 -3
  116. pygpt_net/provider/core/ctx/db_sqlite/utils.py +43 -43
  117. pygpt_net/provider/core/model/patch.py +11 -1
  118. pygpt_net/provider/core/preset/json_file.py +47 -49
  119. pygpt_net/provider/gpt/agents/experts.py +2 -2
  120. pygpt_net/tools/audio_transcriber/ui/dialogs.py +44 -54
  121. pygpt_net/tools/code_interpreter/body.py +1 -2
  122. pygpt_net/tools/code_interpreter/tool.py +7 -4
  123. pygpt_net/tools/code_interpreter/ui/html.py +1 -3
  124. pygpt_net/tools/code_interpreter/ui/widgets.py +2 -3
  125. pygpt_net/tools/html_canvas/ui/widgets.py +1 -3
  126. pygpt_net/tools/image_viewer/ui/dialogs.py +40 -37
  127. pygpt_net/tools/indexer/ui/widgets.py +2 -4
  128. pygpt_net/tools/media_player/tool.py +2 -5
  129. pygpt_net/tools/media_player/ui/widgets.py +60 -36
  130. pygpt_net/tools/text_editor/ui/widgets.py +18 -19
  131. pygpt_net/tools/translator/ui/widgets.py +39 -35
  132. pygpt_net/ui/base/context_menu.py +9 -4
  133. pygpt_net/ui/dialog/db.py +1 -3
  134. pygpt_net/ui/dialog/models.py +1 -3
  135. pygpt_net/ui/dialog/models_importer.py +2 -4
  136. pygpt_net/ui/dialogs.py +34 -30
  137. pygpt_net/ui/layout/chat/attachments.py +72 -84
  138. pygpt_net/ui/layout/chat/attachments_ctx.py +40 -44
  139. pygpt_net/ui/layout/chat/attachments_uploaded.py +36 -39
  140. pygpt_net/ui/layout/chat/calendar.py +100 -70
  141. pygpt_net/ui/layout/chat/chat.py +23 -17
  142. pygpt_net/ui/layout/chat/input.py +95 -118
  143. pygpt_net/ui/layout/chat/output.py +100 -162
  144. pygpt_net/ui/layout/chat/painter.py +89 -61
  145. pygpt_net/ui/layout/ctx/ctx_list.py +43 -52
  146. pygpt_net/ui/layout/status.py +23 -14
  147. pygpt_net/ui/layout/toolbox/agent.py +27 -38
  148. pygpt_net/ui/layout/toolbox/agent_llama.py +42 -45
  149. pygpt_net/ui/layout/toolbox/assistants.py +42 -38
  150. pygpt_net/ui/layout/toolbox/computer_env.py +32 -23
  151. pygpt_net/ui/layout/toolbox/footer.py +13 -16
  152. pygpt_net/ui/layout/toolbox/image.py +18 -21
  153. pygpt_net/ui/layout/toolbox/indexes.py +46 -89
  154. pygpt_net/ui/layout/toolbox/mode.py +20 -7
  155. pygpt_net/ui/layout/toolbox/model.py +12 -10
  156. pygpt_net/ui/layout/toolbox/presets.py +68 -52
  157. pygpt_net/ui/layout/toolbox/prompt.py +31 -58
  158. pygpt_net/ui/layout/toolbox/toolbox.py +25 -21
  159. pygpt_net/ui/layout/toolbox/vision.py +20 -22
  160. pygpt_net/ui/main.py +2 -4
  161. pygpt_net/ui/menu/about.py +64 -84
  162. pygpt_net/ui/menu/audio.py +87 -63
  163. pygpt_net/ui/menu/config.py +121 -127
  164. pygpt_net/ui/menu/debug.py +69 -76
  165. pygpt_net/ui/menu/file.py +32 -35
  166. pygpt_net/ui/menu/menu.py +2 -3
  167. pygpt_net/ui/menu/plugins.py +69 -33
  168. pygpt_net/ui/menu/theme.py +45 -46
  169. pygpt_net/ui/menu/tools.py +56 -60
  170. pygpt_net/ui/menu/video.py +20 -25
  171. pygpt_net/ui/tray.py +1 -2
  172. pygpt_net/ui/widget/audio/bar.py +1 -3
  173. pygpt_net/ui/widget/audio/input_button.py +3 -4
  174. pygpt_net/ui/widget/calendar/select.py +1 -2
  175. pygpt_net/ui/widget/dialog/base.py +12 -9
  176. pygpt_net/ui/widget/dialog/editor_file.py +20 -23
  177. pygpt_net/ui/widget/dialog/find.py +25 -24
  178. pygpt_net/ui/widget/dialog/profile.py +57 -53
  179. pygpt_net/ui/widget/draw/painter.py +62 -93
  180. pygpt_net/ui/widget/element/button.py +42 -30
  181. pygpt_net/ui/widget/element/checkbox.py +23 -15
  182. pygpt_net/ui/widget/element/group.py +6 -5
  183. pygpt_net/ui/widget/element/labels.py +1 -2
  184. pygpt_net/ui/widget/filesystem/explorer.py +93 -102
  185. pygpt_net/ui/widget/image/display.py +1 -2
  186. pygpt_net/ui/widget/lists/assistant.py +1 -2
  187. pygpt_net/ui/widget/lists/attachment.py +1 -2
  188. pygpt_net/ui/widget/lists/attachment_ctx.py +1 -2
  189. pygpt_net/ui/widget/lists/context.py +2 -4
  190. pygpt_net/ui/widget/lists/index.py +1 -2
  191. pygpt_net/ui/widget/lists/model.py +1 -2
  192. pygpt_net/ui/widget/lists/model_editor.py +1 -2
  193. pygpt_net/ui/widget/lists/model_importer.py +1 -2
  194. pygpt_net/ui/widget/lists/preset.py +1 -2
  195. pygpt_net/ui/widget/lists/preset_plugins.py +1 -2
  196. pygpt_net/ui/widget/lists/profile.py +1 -2
  197. pygpt_net/ui/widget/lists/uploaded.py +1 -2
  198. pygpt_net/ui/widget/option/checkbox.py +2 -4
  199. pygpt_net/ui/widget/option/checkbox_list.py +1 -4
  200. pygpt_net/ui/widget/option/cmd.py +1 -4
  201. pygpt_net/ui/widget/option/dictionary.py +25 -28
  202. pygpt_net/ui/widget/option/input.py +1 -3
  203. pygpt_net/ui/widget/tabs/Input.py +16 -12
  204. pygpt_net/ui/widget/tabs/body.py +5 -3
  205. pygpt_net/ui/widget/tabs/layout.py +36 -25
  206. pygpt_net/ui/widget/tabs/output.py +96 -74
  207. pygpt_net/ui/widget/textarea/calendar_note.py +1 -2
  208. pygpt_net/ui/widget/textarea/editor.py +41 -73
  209. pygpt_net/ui/widget/textarea/find.py +11 -10
  210. pygpt_net/ui/widget/textarea/html.py +3 -6
  211. pygpt_net/ui/widget/textarea/input.py +63 -64
  212. pygpt_net/ui/widget/textarea/notepad.py +54 -38
  213. pygpt_net/ui/widget/textarea/output.py +65 -54
  214. pygpt_net/ui/widget/textarea/search_input.py +5 -4
  215. pygpt_net/ui/widget/textarea/web.py +2 -4
  216. pygpt_net/ui/widget/vision/camera.py +2 -31
  217. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/METADATA +25 -154
  218. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/RECORD +218 -217
  219. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/LICENSE +0 -0
  220. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/WHEEL +0 -0
  221. {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/entry_points.txt +0 -0
@@ -6,11 +6,11 @@
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.21 07:00:00 #
9
+ # Updated Date: 2025.08.23 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
13
- from typing import Dict, List
13
+ from typing import Dict, List, Optional
14
14
 
15
15
  from PySide6.QtCore import Slot
16
16
 
@@ -114,16 +114,17 @@ class Experts:
114
114
  :return: experts dict
115
115
  """
116
116
  experts = {}
117
- presets = self.window.core.presets.get_by_mode(MODE_EXPERT)
117
+ core = self.window.core
118
+ presets = core.presets.get_by_mode(MODE_EXPERT)
118
119
 
119
120
  # mode: agent
120
121
  if self.agent_enabled():
121
- agents = self.window.core.presets.get_by_mode(MODE_AGENT)
122
- agent = self.window.core.config.get('preset')
122
+ agents = core.presets.get_by_mode(MODE_AGENT)
123
+ agent = core.config.get('preset')
123
124
  if agent is not None:
124
125
  if agent in agents:
125
126
  for uuid in agents[agent].experts:
126
- expert = self.window.core.presets.get_by_uuid(uuid)
127
+ expert = core.presets.get_by_uuid(uuid)
127
128
  if expert is not None:
128
129
  id = expert.filename
129
130
  experts[id] = expert
@@ -137,12 +138,12 @@ class Experts:
137
138
  experts[k] = presets[k]
138
139
  return experts
139
140
 
140
- def get_expert_name_by_id(self, id: str) -> str:
141
+ def get_expert_name_by_id(self, id: str) -> Optional[str]:
141
142
  """
142
143
  Get expert name by id
143
144
 
144
145
  :param id: expert id
145
- :return: expert name
146
+ :return: expert name or None if not found
146
147
  """
147
148
  experts = self.get_experts()
148
149
  if id in experts:
@@ -156,10 +157,11 @@ class Experts:
156
157
  :return: number of experts
157
158
  """
158
159
  i = 0
159
- agents = self.window.core.presets.get_by_mode(MODE_AGENT)
160
+ core = self.window.core
161
+ agents = core.presets.get_by_mode(MODE_AGENT)
160
162
  if uuid in agents:
161
163
  for expert_uuid in agents[uuid].experts:
162
- expert = self.window.core.presets.get_by_uuid(expert_uuid)
164
+ expert = core.presets.get_by_uuid(expert_uuid)
163
165
  if expert is not None:
164
166
  i += 1
165
167
  return i
@@ -189,15 +191,16 @@ class Experts:
189
191
  :param ctx: context item
190
192
  :return: dict with calls
191
193
  """
194
+ core = self.window.core
192
195
  ids = self.get_experts().keys()
193
196
  if not ids: # abort if no experts
194
197
  return {}
195
- cmds = self.window.core.command.extract_cmds(ctx.output)
198
+ cmds = core.command.extract_cmds(ctx.output)
196
199
  if len(cmds) > 0:
197
200
  ctx.cmds = cmds # append commands to ctx
198
201
  else: # abort if no cmds
199
202
  return {}
200
- commands = self.window.core.command.from_commands(cmds) # pack to execution list
203
+ commands = core.command.from_commands(cmds) # pack to execution list
201
204
  is_cmd = False
202
205
  my_commands = []
203
206
  calls = {}
@@ -220,7 +223,7 @@ class Experts:
220
223
  query = item["params"]["query"]
221
224
  calls[id] = query
222
225
  except Exception as e:
223
- self.window.core.debug.log(e)
226
+ core.debug.log(e)
224
227
  return {}
225
228
  return calls
226
229
 
@@ -272,9 +275,6 @@ class Experts:
272
275
  reply_ctx.output_name = ""
273
276
  reply_ctx.sub_call = True # this flag is not copied in to_dict
274
277
 
275
- internal = False
276
- if self.agent_enabled(): # hide in agent mode
277
- internal = True
278
278
  if ctx.output.strip() != "":
279
279
  response = reply_ctx.output
280
280
  else:
@@ -290,16 +290,15 @@ class Experts:
290
290
  if ctx.sub_reply:
291
291
  reply_ctx.extra["sub_reply"] = True # mark as sub-reply
292
292
  context.prompt = json.dumps(result, ensure_ascii=False, indent=2) # to master
293
- extra = {
294
- "force": True,
295
- "reply": True,
296
- "internal": internal,
297
- }
298
- event = KernelEvent(KernelEvent.INPUT_SYSTEM, {
293
+
294
+ self.window.dispatch(KernelEvent(KernelEvent.INPUT_SYSTEM, {
299
295
  'context': context,
300
- 'extra': extra,
301
- })
302
- self.window.dispatch(event)
296
+ 'extra': {
297
+ "force": True,
298
+ "reply": True,
299
+ "internal": self.agent_enabled(),
300
+ },
301
+ }))
303
302
 
304
303
  def call(
305
304
  self,
@@ -317,26 +316,27 @@ class Experts:
317
316
  if self.stopped():
318
317
  return
319
318
 
320
- self.worker = ExpertWorker(
319
+ worker = ExpertWorker(
321
320
  window=self.window,
322
321
  master_ctx=master_ctx,
323
322
  expert_id=expert_id,
324
- query=query
323
+ query=query,
325
324
  )
326
- self.worker.signals.response.connect(self.handle_response) # connect to finished signal
327
- self.worker.signals.finished.connect(self.handle_finished) # connect to finished signal
328
- self.worker.signals.error.connect(self.handle_error) # connect to error signal
329
- self.worker.signals.event.connect(self.handle_event) # connect to event signal
330
- self.worker.signals.output.connect(self.handle_output) # connect to output signal
331
- self.worker.signals.lock_input.connect(self.handle_input_locked) # connect to lock input signal
332
- self.worker.signals.cmd.connect(self.handle_cmd) # connect to cmd signal
333
-
334
- # start worker in thread pool
325
+ worker.signals.response.connect(self.handle_response) # connect to finished signal
326
+ worker.signals.finished.connect(self.handle_finished) # connect to finished signal
327
+ worker.signals.error.connect(self.handle_error) # connect to error signal
328
+ worker.signals.event.connect(self.handle_event) # connect to event signal
329
+ worker.signals.output.connect(self.handle_output) # connect to output signal
330
+ worker.signals.lock_input.connect(self.handle_input_locked) # connect to lock input signal
331
+ worker.signals.cmd.connect(self.handle_cmd) # connect to cmd signal
332
+
333
+ # start worker in threadpool
334
+ self.worker = worker
335
335
  self.last_expert_id = expert_id # store last expert id
336
336
  self.master_ctx = master_ctx
337
- expert_name = self.get_expert_name_by_id(expert_id)
337
+ name = self.get_expert_name_by_id(expert_id)
338
338
  event = KernelEvent(KernelEvent.STATE_BUSY, {
339
- "msg": f"{trans('expert.wait.status')} ({expert_name})",
339
+ "msg": f"{trans('expert.wait.status')} ({name})",
340
340
  })
341
341
  self.window.dispatch(event) # dispatch busy event
342
342
  self.window.threadpool.start(self.worker)
@@ -355,7 +355,7 @@ class Experts:
355
355
  self.window.controller.chat.output.handle(
356
356
  ctx=ctx,
357
357
  mode=mode,
358
- stream_mode=False,
358
+ stream=False,
359
359
  )
360
360
 
361
361
  @Slot(CtxItem, CtxItem, str, str, str)
@@ -379,31 +379,33 @@ class Experts:
379
379
  if self.stopped():
380
380
  return
381
381
 
382
+ core = self.window.core
383
+ update_status = self.window.update_status
384
+
382
385
  # extract native tool calls if provided
383
386
  if ctx.tool_calls:
384
387
  # if not internal commands in a text body then append tool calls as commands (prevent double commands)
385
- if not self.window.core.command.has_cmds(ctx.output):
386
- self.window.core.command.append_tool_calls(ctx) # append tool calls as commands
388
+ if not core.command.has_cmds(ctx.output):
389
+ core.command.append_tool_calls(ctx) # append tool calls as commands
387
390
  if not isinstance(ctx.extra, dict):
388
391
  ctx.extra = {}
389
392
  ctx.extra["tool_calls"] = ctx.tool_calls
390
393
 
391
394
  # if 'get_context' tool is used then force call, and append idx
392
395
  self.extract_tool_calls(ctx) # extract tool calls from ctx
393
-
394
396
  self.window.controller.chat.command.handle(ctx, internal=True) # handle cmds sync
395
- if ctx.reply:
396
- self.window.update_status("") # clear status
397
397
 
398
+ if ctx.reply:
399
+ update_status("") # clear status
398
400
  # prepare data to send as reply
399
401
  tool_data = json.dumps(ctx.results)
400
402
  # if "tool_output" in ctx.extra and ctx.extra["tool_output"]:
401
403
  # tool_data = str(ctx.extra["tool_output"])
402
404
 
403
- self.window.core.ctx.update_item(ctx) # update context in db
404
- self.window.update_status('...')
405
+ core.ctx.update_item(ctx) # update context in db
406
+ update_status('...')
405
407
  ctx.output = f"<tool>{ctx.cmds}</tool>"
406
- self.window.core.ctx.update_item(ctx) # update ctx in DB
408
+ core.ctx.update_item(ctx) # update ctx in DB
407
409
  self.handle_finished()
408
410
  self.call(
409
411
  master_ctx=self.master_ctx,
@@ -461,34 +463,29 @@ class Experts:
461
463
 
462
464
  :param error: error message
463
465
  """
466
+ dispatch = self.window.dispatch
467
+
464
468
  if self.stopped():
465
- event = KernelEvent(KernelEvent.STATE_IDLE, {})
466
- self.window.dispatch(event) # dispatch idle event
469
+ dispatch(KernelEvent(KernelEvent.STATE_IDLE, {})) # dispatch idle event
467
470
  return
468
471
 
469
472
  # handle error from worker
470
473
  context = BridgeContext()
471
474
  context.prompt = f"{trans('expert.wait.failed')}: {error}"
472
- extra = {
473
- "force": True,
474
- "reply": False,
475
- "internal": False,
476
- }
477
- # reply to master
478
- event = KernelEvent(KernelEvent.INPUT_SYSTEM, {
475
+ dispatch(KernelEvent(KernelEvent.INPUT_SYSTEM, {
479
476
  'context': context,
480
- 'extra': extra,
481
- })
482
- self.window.dispatch(event)
483
- event = KernelEvent(KernelEvent.STATE_IDLE, {})
484
- self.window.dispatch(event) # dispatch idle event
477
+ 'extra': {
478
+ "force": True,
479
+ "reply": False,
480
+ "internal": False,
481
+ },
482
+ })) # reply to master
483
+ dispatch(KernelEvent(KernelEvent.STATE_IDLE, {})) # dispatch idle event
485
484
 
486
485
  @Slot()
487
486
  def handle_finished(self):
488
487
  """Handle worker finished signal"""
489
- event = KernelEvent(KernelEvent.STATE_IDLE, {})
490
- self.window.dispatch(event) # dispatch idle event
491
-
488
+ self.window.dispatch(KernelEvent(KernelEvent.STATE_IDLE, {})) # dispatch idle event
492
489
 
493
490
  @Slot(CtxItem, str)
494
491
  def handle_response(self, ctx: CtxItem, expert_id: str):
@@ -498,9 +495,10 @@ class Experts:
498
495
  :param ctx: CtxItem
499
496
  :param expert_id: expert id
500
497
  """
498
+ dispatch = self.window.dispatch
499
+
501
500
  if self.stopped():
502
- event = KernelEvent(KernelEvent.STATE_IDLE, {})
503
- self.window.dispatch(event) # dispatch idle event
501
+ dispatch(KernelEvent(KernelEvent.STATE_IDLE, {})) # dispatch idle event
504
502
  return
505
503
 
506
504
  # handle reply from worker
@@ -510,20 +508,17 @@ class Experts:
510
508
  "expert_id": expert_id,
511
509
  "result": str(ctx.output),
512
510
  }
511
+ # TODO: clear ctx.output here?
513
512
  context.prompt = json.dumps(result, ensure_ascii=False, indent=2) # prepare prompt for reply
514
- extra = {
515
- "force": True,
516
- "reply": True,
517
- "internal": False,
518
- }
519
- # reply to master
520
- event = KernelEvent(KernelEvent.INPUT_SYSTEM, {
513
+ dispatch(KernelEvent(KernelEvent.INPUT_SYSTEM, {
521
514
  'context': context,
522
- 'extra': extra,
523
- })
524
- self.window.dispatch(event)
525
- event = KernelEvent(KernelEvent.STATE_IDLE, {})
526
- self.window.dispatch(event) # dispatch idle event
515
+ 'extra': {
516
+ "force": True,
517
+ "reply": True,
518
+ "internal": False,
519
+ },
520
+ })) # reply to master
521
+ dispatch(KernelEvent(KernelEvent.STATE_IDLE, {})) # dispatch idle event
527
522
 
528
523
  def get_functions(self) -> List[Dict[str, str]]:
529
524
  """
@@ -531,7 +526,7 @@ class Experts:
531
526
 
532
527
  :return: call the expert commands
533
528
  """
534
- cmds = [
529
+ return [
535
530
  {
536
531
  "cmd": TOOL_EXPERT_CALL_NAME,
537
532
  "instruction": TOOL_EXPERT_CALL_DESCRIPTION,
@@ -551,7 +546,6 @@ class Experts:
551
546
  ]
552
547
  }
553
548
  ]
554
- return cmds
555
549
 
556
550
  def get_retriever_tool(self) -> Dict[str, str]:
557
551
  """
@@ -580,10 +574,10 @@ class Experts:
580
574
  :return: True if expert calls found
581
575
  """
582
576
  if not ctx.sub_reply and not ctx.reply:
583
- mentions = self.window.core.experts.extract_calls(ctx)
577
+ mentions = self.extract_calls(ctx)
584
578
  if mentions:
585
579
  for expert_id in mentions:
586
- if not self.window.core.experts.exists(expert_id):
580
+ if not self.exists(expert_id):
587
581
  continue
588
582
  return True
589
583
  return False
@@ -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.21 07:00:00 #
9
+ # Updated Date: 2025.08.23 15:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import List, Optional
@@ -303,16 +303,6 @@ class ExpertWorker(QRunnable):
303
303
  self.signals.finished.emit()
304
304
  self.cleanup()
305
305
 
306
- def cleanup(self):
307
- """Cleanup resources after worker execution."""
308
- sig = self.signals
309
- self.signals = None
310
- if sig is not None:
311
- try:
312
- sig.deleteLater()
313
- except RuntimeError:
314
- pass
315
-
316
306
  def call_agent(
317
307
  self,
318
308
  context: BridgeContext,
@@ -359,4 +349,14 @@ class ExpertWorker(QRunnable):
359
349
  if response_ctx:
360
350
  return str(response_ctx.output)
361
351
  else:
362
- return "No response from expert."
352
+ return "No response from expert."
353
+
354
+ def cleanup(self):
355
+ """Cleanup resources after worker execution."""
356
+ sig = self.signals
357
+ self.signals = None
358
+ if sig is not None:
359
+ try:
360
+ sig.deleteLater()
361
+ except RuntimeError:
362
+ pass
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 08:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -16,7 +16,6 @@ from PySide6.QtGui import QAction, QIcon
16
16
  from PySide6.QtWidgets import QWidget
17
17
 
18
18
  from pygpt_net.utils import trans
19
- import pygpt_net.icons_rc
20
19
 
21
20
 
22
21
  class Actions:
@@ -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.15 23:00:00 #
9
+ # Updated Date: 2025.08.24 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -498,6 +498,10 @@ class Models:
498
498
  args["api_key"] = cfg.get('api_key_hugging_face', "")
499
499
  args["base_url"] = cfg.get('api_endpoint_hugging_face', "")
500
500
  self.window.core.debug.info("[api] Using client: HuggingFace Router API")
501
+ elif model.provider == "ollama":
502
+ args["api_key"] = "ollama"
503
+ args["base_url"] = self.window.core.models.ollama.get_base_url() + "/v1"
504
+ self.window.core.debug.info("[api] Using client: Ollama")
501
505
  else:
502
506
  self.window.core.debug.info("[api] Using client: OpenAI (default)")
503
507
 
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.09 21:00:00 #
9
+ # Updated Date: 2025.08.24 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -22,16 +22,25 @@ class Ollama:
22
22
  self.window = window
23
23
  self.available_models = []
24
24
 
25
- def get_status(self) -> dict:
25
+ def get_base_url(self) -> str:
26
26
  """
27
- Check Ollama status
27
+ Get Ollama API base URL
28
28
 
29
- :return: dict
29
+ :return: Ollama API base URL
30
30
  """
31
31
  api_base = "http://localhost:11434"
32
32
  if 'OLLAMA_API_BASE' in os.environ:
33
33
  api_base = os.environ['OLLAMA_API_BASE']
34
- self.window.core.idx.log("Using Ollama base URL: {}".format(api_base))
34
+ return api_base
35
+
36
+ def get_status(self) -> dict:
37
+ """
38
+ Check Ollama status
39
+
40
+ :return: dict
41
+ """
42
+ api_base = self.get_base_url()
43
+ self.window.core.idx.log(f"Using Ollama base URL: {api_base}")
35
44
 
36
45
  url = api_base + "/api/tags"
37
46
  try:
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.14 00:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
 
@@ -27,10 +27,7 @@ class Helpers:
27
27
  :param text: text to format
28
28
  :return: formatted text
29
29
  """
30
- text = text.strip()
31
- #text = text.replace("#~###~", "~###~") # fix for #~###~ in text (previous versions)
32
- #text = text.replace("# ~###~", "~###~") # fix for # ~###~ in text (previous versions)
33
- return text
30
+ return text.strip()
34
31
 
35
32
  def post_format_text(self, text: str) -> str:
36
33
  """
@@ -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.21 00:00:00 #
9
+ # Updated Date: 2025.08.24 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from datetime import datetime
@@ -178,11 +178,9 @@ class Renderer(BaseRenderer):
178
178
  if clear:
179
179
  self.clear_output(meta)
180
180
 
181
- i = 0
182
- for item in items:
181
+ for i, item in enumerate(items):
183
182
  item.idx = i
184
183
  self.append_context_item(meta, item)
185
- i += 1
186
184
 
187
185
  def append_input(
188
186
  self,
@@ -251,20 +249,23 @@ class Renderer(BaseRenderer):
251
249
  :param item: context item
252
250
  :param footer: True if it is a footer
253
251
  """
254
- appended = []
252
+ appended = set()
255
253
  pid = self.get_or_create_pid(meta)
256
254
 
257
255
  # images
258
256
  c = len(item.images)
259
257
  if c > 0:
260
258
  n = 1
259
+ pd = self.pids[pid]
260
+ already = set(pd.images_appended)
261
261
  for image in item.images:
262
- if image in appended or image in self.pids[pid].images_appended:
262
+ if image in appended or image in already:
263
263
  continue
264
264
  try:
265
- appended.append(image)
265
+ appended.add(image)
266
266
  self.append_raw(meta, item, self.body.get_image_html(image, n, c))
267
- self.pids[pid].images_appended.append(image)
267
+ pd.images_appended.append(image)
268
+ already.add(image)
268
269
  n += 1
269
270
  except Exception as e:
270
271
  pass
@@ -277,7 +278,7 @@ class Renderer(BaseRenderer):
277
278
  if file in appended:
278
279
  continue
279
280
  try:
280
- appended.append(file)
281
+ appended.add(file)
281
282
  self.append_raw(meta, item, self.body.get_file_html(file, n, c))
282
283
  n += 1
283
284
  except Exception as e:
@@ -288,13 +289,16 @@ class Renderer(BaseRenderer):
288
289
  if c > 0:
289
290
  urls_str = []
290
291
  n = 1
292
+ pd = self.pids[pid]
293
+ already = set(pd.urls_appended)
291
294
  for url in item.urls:
292
- if url in appended or url in self.pids[pid].urls_appended:
295
+ if url in appended or url in already:
293
296
  continue
294
297
  try:
295
- appended.append(url)
298
+ appended.add(url)
296
299
  urls_str.append(self.body.get_url_html(url, n, c))
297
- self.pids[pid].urls_appended.append(url)
300
+ pd.urls_appended.append(url)
301
+ already.add(url)
298
302
  n += 1
299
303
  except Exception as e:
300
304
  pass
@@ -337,8 +341,9 @@ class Renderer(BaseRenderer):
337
341
  raw_chunk = str(text_chunk)
338
342
 
339
343
  if begin:
340
- self.pids[pid].buffer = "" # reset buffer
341
- self.pids[pid].is_cmd = False # reset command flag
344
+ pd = self.pids[pid]
345
+ pd.buffer = ""
346
+ pd.is_cmd = False
342
347
 
343
348
  if self.is_timestamp_enabled() and item.output_timestamp is not None:
344
349
  name = ""
@@ -386,13 +391,12 @@ class Renderer(BaseRenderer):
386
391
  :param text: text to append
387
392
  """
388
393
  node = self.get_output_node(meta)
389
- prev_text = node.toPlainText()
390
- if prev_text != "":
391
- prev_text += "\n\n"
392
- new_text = f"{prev_text}{text.strip()}"
393
- node.setPlainText(new_text)
394
- cur = node.textCursor() # Move cursor to end of text
394
+ cur = node.textCursor()
395
395
  cur.movePosition(QTextCursor.End)
396
+ if not node.document().isEmpty():
397
+ cur.insertText("\n\n")
398
+ cur.insertText(text.strip())
399
+ node.setTextCursor(cur)
396
400
 
397
401
  def append_chunk_start(self, meta: CtxMeta, ctx: CtxItem):
398
402
  """
@@ -437,14 +441,9 @@ class Renderer(BaseRenderer):
437
441
  :param end: end of the line character
438
442
  """
439
443
  node = self.get_output_node(meta)
440
- cur = node.textCursor() # Move cursor to end of text
444
+ cur = node.textCursor()
441
445
  cur.movePosition(QTextCursor.End)
442
- s = f"{str(text)}{end}"
443
- while s:
444
- head, sep, s = s.partition("\n") # Split line at LF
445
- cur.insertText(head)
446
- if sep:
447
- cur.insertText("\n")
446
+ cur.insertText(f"{str(text)}{end}")
448
447
  node.setTextCursor(cur)
449
448
 
450
449
  def append_timestamp(
@@ -549,9 +548,6 @@ class Renderer(BaseRenderer):
549
548
  for node in self.get_all_nodes():
550
549
  try:
551
550
  node.clear()
552
- node.document().setMarkdown("")
553
- node.document().setHtml("")
554
- node.setPlainText("")
555
551
  except Exception as e:
556
552
  pass
557
553
 
@@ -881,7 +881,7 @@ class Body:
881
881
  }
882
882
  });
883
883
  });
884
- setTimeout(cycleTips, 60000); // after 60 seconds
884
+ setTimeout(cycleTips, 10000); // after 10 seconds
885
885
  </script>
886
886
  </head>
887
887
  <body """
@@ -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 00:00:00 #
9
+ # Updated Date: 2025.08.24 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -136,7 +136,7 @@ class Helpers:
136
136
  :return: formatted text
137
137
  """
138
138
  s = text
139
- if self.window.core.config.get("llama.idx.chat.agent.render.all", False):
139
+ if self.window.core.config.get("agent.output.render.all", False):
140
140
  if "__agent_begin__" in s or "__agent_end__" in s:
141
141
  s = s.replace("__agent_begin__", '<div class="msg-agent">').replace("__agent_end__", "</div>")
142
142
  return s.strip()
@@ -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.19 07:00:00 #
9
+ # Updated Date: 2025.08.24 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -594,10 +594,10 @@ class Renderer(BaseRenderer):
594
594
  """
595
595
  output = ctx.output
596
596
  if isinstance(ctx.extra, dict) and ctx.extra.get("output"):
597
- if self.window.core.config.get("llama.idx.chat.agent.render.all", False):
598
- output = f"__agent_begin__{ctx.output}__agent_end__{ctx.extra['output']}"
597
+ if self.window.core.config.get("agent.output.render.all", False):
598
+ output = ctx.output # full agent output
599
599
  else:
600
- output = ctx.extra["output"]
600
+ output = ctx.extra["output"] # final output only
601
601
  else:
602
602
  if not output:
603
603
  return