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
@@ -0,0 +1,478 @@
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 __future__ import annotations
13
+ from typing import Dict, List, Optional, Tuple, Any
14
+ from dataclasses import dataclass, field
15
+ from uuid import uuid4
16
+ from PySide6.QtCore import QObject, Signal
17
+ import re
18
+
19
+
20
+ def gen_uuid() -> str:
21
+ return str(uuid4())
22
+
23
+
24
+ # ------------------------ Data models (pure data) ------------------------
25
+
26
+ @dataclass
27
+ class PropertyModel:
28
+ uuid: str
29
+ id: str
30
+ type: str # "slot", "str", "int", "float", "bool", "combo", "text"
31
+ name: str
32
+ editable: bool = True
33
+ value: Any = None
34
+ allowed_inputs: int = 0 # 0 none, -1 unlimited, >0 limit
35
+ allowed_outputs: int = 0 # 0 none, -1 unlimited, >0 limit
36
+ options: Optional[List[str]] = None # for combo
37
+
38
+ def to_dict(self) -> dict:
39
+ return {
40
+ "uuid": self.uuid,
41
+ "id": self.id,
42
+ "type": self.type,
43
+ "name": self.name,
44
+ "editable": self.editable,
45
+ "value": self.value,
46
+ "allowed_inputs": self.allowed_inputs,
47
+ "allowed_outputs": self.allowed_outputs,
48
+ "options": self.options or [],
49
+ }
50
+
51
+ @staticmethod
52
+ def from_dict(d: dict) -> "PropertyModel":
53
+ return PropertyModel(
54
+ uuid=d.get("uuid", gen_uuid()),
55
+ id=d["id"],
56
+ type=d["type"],
57
+ name=d.get("name", d["id"]),
58
+ editable=d.get("editable", True),
59
+ value=d.get("value"),
60
+ allowed_inputs=d.get("allowed_inputs", 0),
61
+ allowed_outputs=d.get("allowed_outputs", 0),
62
+ options=d.get("options") or None,
63
+ )
64
+
65
+
66
+ @dataclass
67
+ class NodeModel:
68
+ uuid: str
69
+ id: str
70
+ name: str
71
+ type: str
72
+ properties: Dict[str, PropertyModel] = field(default_factory=dict)
73
+
74
+ def to_dict(self) -> dict:
75
+ return {
76
+ "uuid": self.uuid,
77
+ "id": self.id,
78
+ "name": self.name,
79
+ "type": self.type,
80
+ "properties": {pid: p.to_dict() for pid, p in self.properties.items()},
81
+ }
82
+
83
+ @staticmethod
84
+ def from_dict(d: dict) -> "NodeModel":
85
+ props = {pid: PropertyModel.from_dict(pd) for pid, pd in d.get("properties", {}).items()}
86
+ return NodeModel(
87
+ uuid=d.get("uuid", gen_uuid()),
88
+ id=d["id"],
89
+ name=d.get("name", d["id"]),
90
+ type=d["type"],
91
+ properties=props,
92
+ )
93
+
94
+
95
+ @dataclass
96
+ class ConnectionModel:
97
+ uuid: str
98
+ src_node: str
99
+ src_prop: str
100
+ dst_node: str
101
+ dst_prop: str
102
+
103
+ def to_dict(self) -> dict:
104
+ return {
105
+ "uuid": self.uuid,
106
+ "src_node": self.src_node, "src_prop": self.src_prop,
107
+ "dst_node": self.dst_node, "dst_prop": self.dst_prop,
108
+ }
109
+
110
+ @staticmethod
111
+ def from_dict(d: dict) -> "ConnectionModel":
112
+ return ConnectionModel(
113
+ uuid=d.get("uuid", gen_uuid()),
114
+ src_node=d["src_node"], src_prop=d["src_prop"],
115
+ dst_node=d["dst_node"], dst_prop=d["dst_prop"],
116
+ )
117
+
118
+
119
+ # ------------------------ Types registry (templates) ------------------------
120
+
121
+ @dataclass
122
+ class PropertySpec:
123
+ id: str
124
+ type: str
125
+ name: Optional[str] = None
126
+ editable: bool = True
127
+ value: Any = None
128
+ allowed_inputs: int = 0
129
+ allowed_outputs: int = 0
130
+ options: Optional[List[str]] = None
131
+
132
+
133
+ @dataclass
134
+ class NodeTypeSpec:
135
+ type_name: str
136
+ title: Optional[str] = None
137
+ properties: List[PropertySpec] = field(default_factory=list)
138
+ # Below are optional extensions for agent-flow needs:
139
+ base_id: Optional[str] = None # base prefix for friendly ids, e.g. "agent"
140
+ export_kind: Optional[str] = None # short kind for export, e.g. "agent", "start"
141
+ bg_color: Optional[str] = None # optional per-type background color (CSS/hex)
142
+
143
+ class NodeTypeRegistry:
144
+ """Registry for node type specifications. Extend/override in subclasses."""
145
+ def __init__(self):
146
+ self._types: Dict[str, NodeTypeSpec] = {}
147
+ self._install_default_types()
148
+
149
+ def register(self, spec: NodeTypeSpec):
150
+ self._types[spec.type_name] = spec
151
+
152
+ def types(self) -> List[str]:
153
+ return list(self._types.keys())
154
+
155
+ def get(self, type_name: str) -> Optional[NodeTypeSpec]:
156
+ return self._types.get(type_name)
157
+
158
+ def _install_default_types(self):
159
+ # Example/basic nodes kept intact
160
+ self.register(NodeTypeSpec(
161
+ type_name="Value/Float",
162
+ title="Float",
163
+ properties=[
164
+ PropertySpec(id="value", type="float", name="Value", editable=True, value=0.0,
165
+ allowed_inputs=0, allowed_outputs=1),
166
+ ]
167
+ ))
168
+ self.register(NodeTypeSpec(
169
+ type_name="Math/Add",
170
+ title="Add",
171
+ properties=[
172
+ PropertySpec(id="A", type="float", name="A", editable=True, value=0.0, allowed_inputs=1, allowed_outputs=0),
173
+ PropertySpec(id="B", type="float", name="B", editable=True, value=0.0, allowed_inputs=1, allowed_outputs=0),
174
+ PropertySpec(id="result", type="float", name="Result", editable=False, value=None, allowed_inputs=0, allowed_outputs=1),
175
+ ]
176
+ ))
177
+ # Tip: to allow multiple connections to an input or output, set allowed_inputs/allowed_outputs to -1.
178
+
179
+ # Agent-flow nodes
180
+ # Start
181
+ self.register(NodeTypeSpec(
182
+ type_name="Flow/Start",
183
+ title="Start",
184
+ base_id="start",
185
+ export_kind="start",
186
+ bg_color="#2D5A27",
187
+ properties=[
188
+ PropertySpec(id="output", type="flow", name="Output", editable=False, allowed_inputs=0, allowed_outputs=1),
189
+ # base_id will be auto-injected as read-only property at creation
190
+ ],
191
+ ))
192
+ # Agent
193
+ self.register(NodeTypeSpec(
194
+ type_name="Flow/Agent",
195
+ title="Agent",
196
+ base_id="agent",
197
+ export_kind="agent",
198
+ bg_color="#304A6E",
199
+ properties=[
200
+ PropertySpec(id="name", type="str", name="Name", editable=True, value=""),
201
+ PropertySpec(id="instruction", type="text", name="Instruction", editable=True, value=""),
202
+ PropertySpec(id="remote_tools", type="bool", name="Remote tools", editable=True, value=True),
203
+ PropertySpec(id="local_tools", type="bool", name="Local tools", editable=True, value=True),
204
+ PropertySpec(id="input", type="flow", name="Input", editable=False, allowed_inputs=-1, allowed_outputs=0),
205
+ PropertySpec(id="output", type="flow", name="Output", editable=False, allowed_inputs=0, allowed_outputs=1),
206
+ PropertySpec(id="memory", type="memory", name="Memory", editable=False, allowed_inputs=0, allowed_outputs=1),
207
+ ],
208
+ ))
209
+ # Memory
210
+ self.register(NodeTypeSpec(
211
+ type_name="Flow/Memory",
212
+ title="Memory",
213
+ base_id="mem",
214
+ export_kind="memory",
215
+ bg_color="#593E78",
216
+ properties=[
217
+ PropertySpec(id="name", type="str", name="Name", editable=True, value=""),
218
+ PropertySpec(id="input", type="memory", name="Input", editable=False, allowed_inputs=-1, allowed_outputs=0),
219
+ ],
220
+ ))
221
+ # End
222
+ self.register(NodeTypeSpec(
223
+ type_name="Flow/End",
224
+ title="End",
225
+ base_id="end",
226
+ export_kind="end",
227
+ bg_color="#6B2E2E",
228
+ properties=[
229
+ PropertySpec(id="input", type="flow", name="Input", editable=False, allowed_inputs=1, allowed_outputs=0),
230
+ ],
231
+ ))
232
+
233
+
234
+ # ------------------------ Graph (Qt QObject + signals) ------------------------
235
+
236
+ class NodeGraph(QObject):
237
+ """In-memory node graph with Qt signals, easily serializable."""
238
+ nodeAdded = Signal(object) # NodeModel
239
+ nodeRemoved = Signal(str) # node uuid
240
+ connectionAdded = Signal(object) # ConnectionModel
241
+ connectionRemoved = Signal(str) # connection uuid
242
+ cleared = Signal()
243
+ propertyValueChanged = Signal(str, str, object) # node_uuid, prop_id, new_value
244
+
245
+ def __init__(self, registry: Optional[NodeTypeRegistry] = None):
246
+ super().__init__()
247
+ self.registry = registry or NodeTypeRegistry()
248
+ self.nodes: Dict[str, NodeModel] = {}
249
+ self.connections: Dict[str, ConnectionModel] = {}
250
+ self._node_counter = 1
251
+ # Per-base_id counters for friendly IDs (persisted with layout)
252
+ self._id_counters: Dict[str, int] = {}
253
+
254
+ # -------- ID helpers (friendly unique IDs per base prefix) --------
255
+
256
+ @staticmethod
257
+ def _slug_from_type_name(type_name: str) -> str:
258
+ # fallback base id from type name: last segment lowercased, non-words to underscore
259
+ base = type_name.split("/")[-1].lower()
260
+ base = re.sub(r"\W+", "_", base).strip("_")
261
+ return base or "node"
262
+
263
+ def _next_id_for_base(self, base: str) -> str:
264
+ n = self._id_counters.get(base, 0) + 1
265
+ self._id_counters[base] = n
266
+ return f"{base}_{n}"
267
+
268
+ def _seed_counters_from_existing(self):
269
+ # Scan existing node ids like 'agent_12' and seed counters to max per base
270
+ for node in self.nodes.values():
271
+ m = re.match(r"^([a-zA-Z0-9]+)_(\d+)$", node.id)
272
+ if not m:
273
+ continue
274
+ base, num = m.group(1), int(m.group(2))
275
+ cur = self._id_counters.get(base, 0)
276
+ if num > cur:
277
+ self._id_counters[base] = num
278
+
279
+ # -------- Creation / mutation --------
280
+
281
+ def create_node_from_type(self, type_name: str, name: Optional[str] = None) -> NodeModel:
282
+ spec = self.registry.get(type_name)
283
+ if not spec:
284
+ raise ValueError(f"Unknown node type: {type_name}")
285
+
286
+ base_id = spec.base_id or self._slug_from_type_name(type_name)
287
+ # Generate friendly id if base configured; otherwise fallback to generic Node-#
288
+ if spec.base_id:
289
+ nid = self._next_id_for_base(base_id)
290
+ else:
291
+ nid = f"Node-{self._node_counter}"
292
+ self._node_counter += 1
293
+
294
+ props: Dict[str, PropertyModel] = {}
295
+ for ps in spec.properties:
296
+ props[ps.id] = PropertyModel(
297
+ uuid=gen_uuid(), id=ps.id, type=ps.type, name=ps.name or ps.id,
298
+ editable=ps.editable, value=ps.value,
299
+ allowed_inputs=ps.allowed_inputs, allowed_outputs=ps.allowed_outputs,
300
+ options=ps.options
301
+ )
302
+ # Auto inject read-only 'base_id' property for visibility if base_id defined and not present
303
+ if spec.base_id and "base_id" not in props:
304
+ props["base_id"] = PropertyModel(
305
+ uuid=gen_uuid(), id="base_id", type="str", name="Base ID",
306
+ editable=False, value=base_id, allowed_inputs=0, allowed_outputs=0
307
+ )
308
+
309
+ node = NodeModel(uuid=gen_uuid(), id=nid, name=name or spec.title or nid, type=type_name, properties=props)
310
+ return node
311
+
312
+ def add_node(self, node: NodeModel):
313
+ self.nodes[node.uuid] = node
314
+ # Ensure counters are aware of externally provided nodes
315
+ m = re.match(r"^([a-zA-Z0-9]+)_(\d+)$", node.id)
316
+ if m:
317
+ base, num = m.group(1), int(m.group(2))
318
+ if self._id_counters.get(base, 0) < num:
319
+ self._id_counters[base] = num
320
+ self.nodeAdded.emit(node)
321
+
322
+ def remove_node(self, node_uuid: str):
323
+ to_remove = [cid for cid, c in self.connections.items()
324
+ if c.src_node == node_uuid or c.dst_node == node_uuid]
325
+ for cid in to_remove:
326
+ self.remove_connection(cid)
327
+ if node_uuid in self.nodes:
328
+ del self.nodes[node_uuid]
329
+ self.nodeRemoved.emit(node_uuid)
330
+
331
+ def can_connect(self, src: Tuple[str, str], dst: Tuple[str, str]) -> Tuple[bool, str]:
332
+ src_node_uuid, src_prop_id = src
333
+ dst_node_uuid, dst_prop_id = dst
334
+ if src_node_uuid == dst_node_uuid and src_prop_id == dst_prop_id:
335
+ return False, "Cannot connect a port to itself."
336
+ src_node = self.nodes.get(src_node_uuid)
337
+ dst_node = self.nodes.get(dst_node_uuid)
338
+ if not src_node or not dst_node:
339
+ return False, "Node not found."
340
+ sp = src_node.properties.get(src_prop_id)
341
+ dp = dst_node.properties.get(dst_prop_id)
342
+ if not sp or not dp:
343
+ return False, "Property not found."
344
+ if sp.allowed_outputs == 0:
345
+ return False, "Source has no outputs."
346
+ if dp.allowed_inputs == 0:
347
+ return False, "Destination has no inputs."
348
+ if sp.type != dp.type:
349
+ return False, f"Type mismatch: {sp.type} -> {dp.type}"
350
+ if dp.allowed_inputs > 0:
351
+ count = sum(1 for c in self.connections.values()
352
+ if c.dst_node == dst_node_uuid and c.dst_prop == dst_prop_id)
353
+ if count >= dp.allowed_inputs:
354
+ return False, "Destination input limit reached."
355
+ if sp.allowed_outputs > 0:
356
+ count = sum(1 for c in self.connections.values()
357
+ if c.src_node == src_node_uuid and c.src_prop == src_prop_id)
358
+ if count >= sp.allowed_outputs:
359
+ return False, "Source output limit reached."
360
+ return True, ""
361
+
362
+ def add_connection(self, conn: ConnectionModel) -> Tuple[bool, str]:
363
+ ok, reason = self.can_connect((conn.src_node, conn.src_prop), (conn.dst_node, conn.dst_prop))
364
+ if not ok:
365
+ return False, reason
366
+ for c in self.connections.values():
367
+ if c.src_node == conn.src_node and c.src_prop == conn.src_prop and \
368
+ c.dst_node == conn.dst_node and c.dst_prop == conn.dst_prop:
369
+ return False, "Connection already exists."
370
+ self.connections[conn.uuid] = conn
371
+ self.connectionAdded.emit(conn)
372
+ return True, ""
373
+
374
+ def connect(self, src: Tuple[str, str], dst: Tuple[str, str]) -> Tuple[bool, str, Optional[ConnectionModel]]:
375
+ conn = ConnectionModel(uuid=gen_uuid(), src_node=src[0], src_prop=src[1], dst_node=dst[0], dst_prop=dst[1])
376
+ ok, reason = self.add_connection(conn)
377
+ return ok, reason, conn if ok else None
378
+
379
+ def remove_connection(self, conn_uuid: str):
380
+ if conn_uuid in self.connections:
381
+ del self.connections[conn_uuid]
382
+ self.connectionRemoved.emit(conn_uuid)
383
+
384
+ def set_property_value(self, node_uuid: str, prop_id: str, value: Any):
385
+ node = self.nodes.get(node_uuid)
386
+ if not node:
387
+ return
388
+ prop = node.properties.get(prop_id)
389
+ if not prop or not prop.editable:
390
+ return
391
+ prop.value = value
392
+ self.propertyValueChanged.emit(node_uuid, prop_id, value)
393
+
394
+ def to_dict(self) -> dict:
395
+ return {
396
+ "nodes": {nuuid: n.to_dict() for nuuid, n in self.nodes.items()},
397
+ "connections": {c.uuid: c.to_dict() for c in self.connections.values()},
398
+ "_node_counter": self._node_counter,
399
+ "_id_counters": dict(self._id_counters),
400
+ }
401
+
402
+ def to_schema(self) -> dict:
403
+ nodes_out: Dict[str, dict] = {}
404
+ for nuuid, n in self.nodes.items():
405
+ nodes_out[nuuid] = {
406
+ "type": n.type,
407
+ "id": n.id,
408
+ "name": n.name,
409
+ "values": {pid: p.value for pid, p in n.properties.items()},
410
+ }
411
+ conns_out = [{"src": [c.src_node, c.src_prop], "dst": [c.dst_node, c.dst_prop]}
412
+ for c in self.connections.values()]
413
+ return {"nodes": nodes_out, "connections": conns_out}
414
+
415
+ # --- Export to requested agent schema (list of nodes with slots/in-out) ---
416
+ def to_agent_schema(self) -> List[dict]:
417
+ # Build helper maps
418
+ uuid_to_node: Dict[str, NodeModel] = dict(self.nodes)
419
+ uuid_to_id: Dict[str, str] = {u: n.id for u, n in uuid_to_node.items()}
420
+ # Pre-index connections by (node_uuid, prop_id)
421
+ incoming: Dict[Tuple[str, str], List[str]] = {}
422
+ outgoing: Dict[Tuple[str, str], List[str]] = {}
423
+ for c in self.connections.values():
424
+ outgoing.setdefault((c.src_node, c.src_prop), []).append(uuid_to_id.get(c.dst_node, c.dst_node))
425
+ incoming.setdefault((c.dst_node, c.dst_prop), []).append(uuid_to_id.get(c.src_node, c.src_node))
426
+
427
+ result: List[dict] = []
428
+ for n in uuid_to_node.values():
429
+ spec = self.registry.get(n.type)
430
+ kind = spec.export_kind if spec and spec.export_kind else n.type.split("/")[-1].lower()
431
+ slots: Dict[str, Any] = {}
432
+ for pid, prop in n.properties.items():
433
+ is_port = (prop.allowed_inputs != 0) or (prop.allowed_outputs != 0)
434
+ if is_port:
435
+ slots[pid] = {
436
+ "in": list(incoming.get((n.uuid, pid), [])),
437
+ "out": list(outgoing.get((n.uuid, pid), [])),
438
+ }
439
+ else:
440
+ # Skip internal helper fields if needed
441
+ if pid == "base_id":
442
+ continue
443
+ slots[pid] = prop.value
444
+ result.append({
445
+ "type": kind,
446
+ "id": n.id,
447
+ "slots": slots,
448
+ })
449
+
450
+ # Stable order by id
451
+ result.sort(key=lambda d: d["id"])
452
+ return result
453
+
454
+ def from_dict(self, d: dict):
455
+ self.clear(silent=True)
456
+ nodes_d = d.get("nodes", {})
457
+ for nuuid, nd in nodes_d.items():
458
+ node = NodeModel.from_dict(nd)
459
+ self.nodes[node.uuid] = node
460
+ conns_d = d.get("connections", {})
461
+ for cid, cd in conns_d.items():
462
+ conn = ConnectionModel.from_dict(cd)
463
+ self.connections[conn.uuid] = conn
464
+ self._node_counter = d.get("_node_counter", len(self.nodes) + 1)
465
+ self._id_counters = dict(d.get("_id_counters", {}))
466
+ # Seed counters from existing node ids if counters were not present
467
+ if not self._id_counters:
468
+ self._seed_counters_from_existing()
469
+ for n in self.nodes.values():
470
+ self.nodeAdded.emit(n)
471
+ for c in self.connections.values():
472
+ self.connectionAdded.emit(c)
473
+
474
+ def clear(self, silent: bool = False):
475
+ self.nodes.clear()
476
+ self.connections.clear()
477
+ if not silent:
478
+ self.cleared.emit()
File without changes
pygpt_net/core/ctx/ctx.py CHANGED
File without changes
File without changes
File without changes
@@ -0,0 +1,29 @@
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
+ class AgentBuilderDebug:
13
+ def __init__(self, window=None):
14
+ """
15
+ Agent debug
16
+
17
+ :param window: Window instance
18
+ """
19
+ self.window = window
20
+ self.id = 'agent_builder'
21
+
22
+ def update(self):
23
+ """Update debug window"""
24
+ debug = self.window.core.debug
25
+ editor = self.window.ui.editor["agent.builder"]
26
+
27
+ debug.begin(self.id)
28
+ debug.add(self.id, 'nodes', str(editor.debug_state()))
29
+ debug.end(self.id)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -475,7 +475,8 @@ class Models:
475
475
 
476
476
  if cfg.has('api_proxy'):
477
477
  proxy = cfg.get('api_proxy')
478
- if proxy:
478
+ if proxy and cfg.get('api_proxy.enabled', False):
479
+ args["api_proxy"] = proxy
479
480
  transport = SyncProxyTransport.from_url(proxy)
480
481
  args["http_client"] = DefaultHttpxClient(transport=transport)
481
482
 
@@ -357,6 +357,66 @@ class Plugins:
357
357
  """Load presets"""
358
358
  self.presets = self.provider.load()
359
359
 
360
+ def remove_plugin_param_from_presets(self, plugin_id: str, param: str = None) -> bool:
361
+ """
362
+ Remove plugin param from all presets
363
+
364
+ :param plugin_id: plugin id
365
+ :param param: param key
366
+ :return: True if updated
367
+ """
368
+ updated = False
369
+ if self.presets is None:
370
+ self.load_presets()
371
+
372
+ if self.presets is None:
373
+ return False
374
+
375
+ if param is None:
376
+ # remove all params for plugin
377
+ for _preset_id, preset in self.presets.items():
378
+ preset_config = preset["config"]
379
+ if plugin_id in preset_config:
380
+ preset_config.pop(plugin_id)
381
+ updated = True
382
+ if updated:
383
+ self.save_presets()
384
+ return updated
385
+
386
+ for _preset_id, preset in self.presets.items():
387
+ preset_config = preset["config"]
388
+ if plugin_id in preset_config and param in preset_config[plugin_id]:
389
+ preset_config[plugin_id].pop(param)
390
+ updated = True
391
+ if updated:
392
+ self.save_presets()
393
+ return updated
394
+
395
+ def update_param_in_presets(self, plugin_id: str, param: str, value: Any) -> bool:
396
+ """
397
+ Update plugin param in all presets
398
+
399
+ :param plugin_id: plugin id
400
+ :param param: param key
401
+ :param value: param value
402
+ :return: True if updated
403
+ """
404
+ updated = False
405
+ if self.presets is None:
406
+ self.load_presets()
407
+
408
+ if self.presets is None:
409
+ return False
410
+
411
+ for _preset_id, preset in self.presets.items():
412
+ preset_config = preset["config"]
413
+ if plugin_id in preset_config and param in preset_config[plugin_id]:
414
+ preset_config[plugin_id][param] = value
415
+ updated = True
416
+ if updated:
417
+ self.save_presets()
418
+ return updated
419
+
360
420
  def get_presets(self) -> Dict[str, Any]:
361
421
  """
362
422
  Return all presets
File without changes