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.
- pygpt_net/CHANGELOG.txt +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +26 -22
- pygpt_net/config.py +44 -0
- pygpt_net/controller/audio/audio.py +0 -0
- pygpt_net/controller/calendar/calendar.py +0 -0
- pygpt_net/controller/calendar/note.py +0 -0
- pygpt_net/controller/chat/chat.py +0 -0
- pygpt_net/controller/chat/handler/openai_stream.py +2 -1
- pygpt_net/controller/chat/handler/worker.py +0 -0
- pygpt_net/controller/chat/remote_tools.py +5 -3
- pygpt_net/controller/chat/render.py +0 -0
- pygpt_net/controller/chat/text.py +0 -0
- pygpt_net/controller/ctx/common.py +0 -0
- pygpt_net/controller/debug/debug.py +26 -2
- pygpt_net/controller/debug/fixtures.py +1 -1
- pygpt_net/controller/dialogs/confirm.py +15 -1
- pygpt_net/controller/dialogs/debug.py +2 -0
- pygpt_net/controller/lang/mapping.py +0 -0
- pygpt_net/controller/launcher/launcher.py +0 -0
- pygpt_net/controller/mode/mode.py +0 -0
- pygpt_net/controller/presets/presets.py +0 -0
- pygpt_net/controller/realtime/realtime.py +0 -0
- pygpt_net/controller/theme/theme.py +0 -0
- pygpt_net/controller/ui/mode.py +5 -3
- pygpt_net/controller/ui/tabs.py +0 -0
- pygpt_net/core/agents/agents.py +3 -1
- pygpt_net/core/agents/custom.py +150 -0
- pygpt_net/core/agents/provider.py +0 -0
- pygpt_net/core/builder/__init__.py +12 -0
- pygpt_net/core/builder/graph.py +478 -0
- pygpt_net/core/calendar/calendar.py +0 -0
- pygpt_net/core/ctx/ctx.py +0 -0
- pygpt_net/core/ctx/output.py +0 -0
- pygpt_net/core/debug/agent.py +0 -0
- pygpt_net/core/debug/agent_builder.py +29 -0
- pygpt_net/core/debug/console/console.py +0 -0
- pygpt_net/core/debug/db.py +0 -0
- pygpt_net/core/debug/debug.py +0 -0
- pygpt_net/core/debug/events.py +0 -0
- pygpt_net/core/debug/indexes.py +0 -0
- pygpt_net/core/debug/kernel.py +0 -0
- pygpt_net/core/debug/tabs.py +0 -0
- pygpt_net/core/filesystem/filesystem.py +0 -0
- pygpt_net/core/fixtures/__init__ +0 -0
- pygpt_net/core/fixtures/stream/__init__.py +0 -0
- pygpt_net/core/fixtures/stream/generator.py +0 -0
- pygpt_net/core/models/models.py +2 -1
- pygpt_net/core/plugins/plugins.py +60 -0
- pygpt_net/core/render/plain/pid.py +0 -0
- pygpt_net/core/render/plain/renderer.py +26 -4
- pygpt_net/core/render/web/body.py +46 -4
- pygpt_net/core/render/web/debug.py +0 -0
- pygpt_net/core/render/web/helpers.py +0 -0
- pygpt_net/core/render/web/pid.py +0 -0
- pygpt_net/core/render/web/renderer.py +15 -20
- pygpt_net/core/tabs/tab.py +0 -0
- pygpt_net/core/tabs/tabs.py +0 -0
- pygpt_net/core/text/utils.py +0 -0
- pygpt_net/css.qrc +0 -0
- pygpt_net/css_rc.py +0 -0
- pygpt_net/data/config/config.json +8 -7
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +14 -0
- pygpt_net/data/css/web-blocks.css +9 -0
- pygpt_net/data/css/web-blocks.dark.css +6 -0
- pygpt_net/data/css/web-blocks.darkest.css +6 -0
- pygpt_net/data/css/web-chatgpt.css +14 -6
- pygpt_net/data/css/web-chatgpt.dark.css +6 -0
- pygpt_net/data/css/web-chatgpt.darkest.css +6 -0
- pygpt_net/data/css/web-chatgpt.light.css +6 -0
- pygpt_net/data/css/web-chatgpt_wide.css +14 -6
- pygpt_net/data/css/web-chatgpt_wide.dark.css +6 -0
- pygpt_net/data/css/web-chatgpt_wide.darkest.css +6 -0
- pygpt_net/data/css/web-chatgpt_wide.light.css +6 -0
- pygpt_net/data/fixtures/fake_stream.txt +14 -1
- pygpt_net/data/icons/case.svg +0 -0
- pygpt_net/data/icons/chat1.svg +0 -0
- pygpt_net/data/icons/chat2.svg +0 -0
- pygpt_net/data/icons/chat3.svg +0 -0
- pygpt_net/data/icons/chat4.svg +0 -0
- pygpt_net/data/icons/fit.svg +0 -0
- pygpt_net/data/icons/note1.svg +0 -0
- pygpt_net/data/icons/note2.svg +0 -0
- pygpt_net/data/icons/note3.svg +0 -0
- pygpt_net/data/icons/stt.svg +0 -0
- pygpt_net/data/icons/translate.svg +0 -0
- pygpt_net/data/icons/tts.svg +0 -0
- pygpt_net/data/icons/url.svg +0 -0
- pygpt_net/data/icons/vision.svg +0 -0
- pygpt_net/data/icons/web_off.svg +0 -0
- pygpt_net/data/icons/web_on.svg +0 -0
- pygpt_net/data/js/app/async.js +166 -0
- pygpt_net/data/js/app/bridge.js +88 -0
- pygpt_net/data/js/app/common.js +212 -0
- pygpt_net/data/js/app/config.js +223 -0
- pygpt_net/data/js/app/custom.js +961 -0
- pygpt_net/data/js/app/data.js +84 -0
- pygpt_net/data/js/app/dom.js +322 -0
- pygpt_net/data/js/app/events.js +400 -0
- pygpt_net/data/js/app/highlight.js +542 -0
- pygpt_net/data/js/app/logger.js +305 -0
- pygpt_net/data/js/app/markdown.js +1137 -0
- pygpt_net/data/js/app/math.js +167 -0
- pygpt_net/data/js/app/nodes.js +395 -0
- pygpt_net/data/js/app/queue.js +260 -0
- pygpt_net/data/js/app/raf.js +250 -0
- pygpt_net/data/js/app/runtime.js +582 -0
- pygpt_net/data/js/app/scroll.js +433 -0
- pygpt_net/data/js/app/stream.js +2708 -0
- pygpt_net/data/js/app/template.js +287 -0
- pygpt_net/data/js/app/tool.js +87 -0
- pygpt_net/data/js/app/ui.js +86 -0
- pygpt_net/data/js/app/user.js +380 -0
- pygpt_net/data/js/app/utils.js +64 -0
- pygpt_net/data/js/app.min.js +880 -0
- pygpt_net/data/js/markdown-it/markdown-it-katex.min.js +1 -1
- pygpt_net/data/js/markdown-it/markdown-it.min.js +0 -0
- pygpt_net/data/locale/locale.de.ini +3 -1
- pygpt_net/data/locale/locale.en.ini +8 -0
- pygpt_net/data/locale/locale.es.ini +2 -0
- pygpt_net/data/locale/locale.fr.ini +2 -0
- pygpt_net/data/locale/locale.it.ini +2 -0
- pygpt_net/data/locale/locale.pl.ini +3 -1
- pygpt_net/data/locale/locale.uk.ini +3 -1
- pygpt_net/data/locale/locale.zh.ini +2 -0
- pygpt_net/data/locale/plugin.osm.en.ini +24 -24
- pygpt_net/data/locale/plugin.wolfram.en.ini +9 -9
- pygpt_net/fonts.qrc +0 -0
- pygpt_net/fonts_rc.py +0 -0
- pygpt_net/icons.qrc +0 -0
- pygpt_net/icons_rc.py +0 -0
- pygpt_net/item/agent.py +62 -0
- pygpt_net/item/builder_layout.py +62 -0
- pygpt_net/js.qrc +24 -1
- pygpt_net/js_rc.py +51394 -33687
- pygpt_net/plugin/base/worker.py +0 -0
- pygpt_net/plugin/cmd_web/config.py +17 -17
- pygpt_net/plugin/cmd_web/worker.py +325 -171
- pygpt_net/plugin/mcp/__init__.py +0 -0
- pygpt_net/plugin/mcp/config.py +0 -0
- pygpt_net/plugin/mcp/plugin.py +0 -0
- pygpt_net/plugin/mcp/worker.py +0 -0
- pygpt_net/plugin/osm/__init__.py +0 -0
- pygpt_net/plugin/osm/config.py +0 -0
- pygpt_net/plugin/osm/plugin.py +0 -0
- pygpt_net/plugin/osm/worker.py +0 -0
- pygpt_net/plugin/wolfram/__init__.py +0 -0
- pygpt_net/plugin/wolfram/config.py +0 -0
- pygpt_net/plugin/wolfram/plugin.py +0 -0
- pygpt_net/plugin/wolfram/worker.py +0 -0
- pygpt_net/provider/api/anthropic/tools.py +0 -0
- pygpt_net/provider/api/google/__init__.py +0 -0
- pygpt_net/provider/api/google/video.py +0 -0
- pygpt_net/provider/api/openai/agents/experts.py +0 -0
- pygpt_net/provider/api/openai/agents/remote_tools.py +0 -0
- pygpt_net/provider/api/openai/remote_tools.py +0 -0
- pygpt_net/provider/api/openai/responses.py +0 -0
- pygpt_net/provider/api/x_ai/__init__.py +2 -0
- pygpt_net/provider/api/x_ai/remote.py +0 -0
- pygpt_net/provider/core/agent/__init__.py +10 -0
- pygpt_net/provider/core/agent/base.py +51 -0
- pygpt_net/provider/core/agent/json_file.py +200 -0
- pygpt_net/provider/core/config/patch.py +33 -0
- pygpt_net/provider/core/config/patches/__init__.py +0 -0
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +1 -0
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +0 -0
- pygpt_net/provider/core/model/patches/__init__.py +0 -0
- pygpt_net/provider/core/model/patches/patch_before_2_6_42.py +0 -0
- pygpt_net/provider/core/preset/patch.py +0 -0
- pygpt_net/provider/core/preset/patches/__init__.py +0 -0
- pygpt_net/provider/core/preset/patches/patch_before_2_6_42.py +0 -0
- pygpt_net/provider/llms/anthropic.py +4 -0
- pygpt_net/provider/llms/base.py +2 -0
- pygpt_net/provider/llms/deepseek_api.py +2 -0
- pygpt_net/provider/llms/google.py +2 -0
- pygpt_net/provider/llms/hugging_face_api.py +4 -0
- pygpt_net/provider/llms/hugging_face_embedding.py +0 -0
- pygpt_net/provider/llms/hugging_face_router.py +2 -0
- pygpt_net/provider/llms/local.py +0 -0
- pygpt_net/provider/llms/mistral.py +4 -0
- pygpt_net/provider/llms/open_router.py +0 -0
- pygpt_net/provider/llms/perplexity.py +2 -0
- pygpt_net/provider/llms/utils.py +0 -0
- pygpt_net/provider/llms/voyage.py +0 -0
- pygpt_net/provider/llms/x_ai.py +2 -0
- pygpt_net/tools/agent_builder/__init__.py +12 -0
- pygpt_net/tools/agent_builder/tool.py +292 -0
- pygpt_net/tools/agent_builder/ui/__init__.py +0 -0
- pygpt_net/tools/agent_builder/ui/dialogs.py +152 -0
- pygpt_net/tools/agent_builder/ui/list.py +228 -0
- pygpt_net/tools/code_interpreter/ui/html.py +0 -0
- pygpt_net/tools/code_interpreter/ui/widgets.py +0 -0
- pygpt_net/tools/html_canvas/tool.py +23 -6
- pygpt_net/tools/html_canvas/ui/widgets.py +224 -2
- pygpt_net/ui/layout/chat/chat.py +0 -0
- pygpt_net/ui/layout/chat/output.py +5 -5
- pygpt_net/ui/main.py +10 -9
- pygpt_net/ui/menu/debug.py +39 -1
- pygpt_net/ui/widget/builder/__init__.py +12 -0
- pygpt_net/ui/widget/builder/editor.py +2001 -0
- pygpt_net/ui/widget/dialog/base.py +4 -1
- pygpt_net/ui/widget/draw/painter.py +0 -0
- pygpt_net/ui/widget/element/labels.py +9 -4
- pygpt_net/ui/widget/lists/db.py +0 -0
- pygpt_net/ui/widget/lists/debug.py +0 -0
- pygpt_net/ui/widget/tabs/body.py +0 -0
- pygpt_net/ui/widget/textarea/html.py +1 -0
- pygpt_net/ui/widget/textarea/input.py +28 -10
- pygpt_net/ui/widget/textarea/output.py +21 -1
- pygpt_net/ui/widget/textarea/web.py +31 -3
- pygpt_net/utils.py +40 -0
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/METADATA +16 -2
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/RECORD +116 -77
- pygpt_net/data/js/app.js +0 -5869
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/WHEEL +0 -0
- {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
|
pygpt_net/core/ctx/output.py
CHANGED
|
File without changes
|
pygpt_net/core/debug/agent.py
CHANGED
|
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
|
pygpt_net/core/debug/db.py
CHANGED
|
File without changes
|
pygpt_net/core/debug/debug.py
CHANGED
|
File without changes
|
pygpt_net/core/debug/events.py
CHANGED
|
File without changes
|
pygpt_net/core/debug/indexes.py
CHANGED
|
File without changes
|
pygpt_net/core/debug/kernel.py
CHANGED
|
File without changes
|
pygpt_net/core/debug/tabs.py
CHANGED
|
File without changes
|
|
File without changes
|
pygpt_net/core/fixtures/__init__
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
pygpt_net/core/models/models.py
CHANGED
|
@@ -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
|