pygpt-net 2.6.59__py3-none-any.whl → 2.6.61__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 (91) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +9 -5
  4. pygpt_net/controller/__init__.py +1 -0
  5. pygpt_net/controller/chat/common.py +115 -6
  6. pygpt_net/controller/chat/input.py +4 -1
  7. pygpt_net/controller/presets/editor.py +442 -39
  8. pygpt_net/controller/presets/presets.py +121 -6
  9. pygpt_net/controller/settings/editor.py +0 -15
  10. pygpt_net/controller/theme/markdown.py +2 -5
  11. pygpt_net/controller/ui/ui.py +4 -7
  12. pygpt_net/core/agents/custom/__init__.py +281 -0
  13. pygpt_net/core/agents/custom/debug.py +64 -0
  14. pygpt_net/core/agents/custom/factory.py +109 -0
  15. pygpt_net/core/agents/custom/graph.py +71 -0
  16. pygpt_net/core/agents/custom/llama_index/__init__.py +10 -0
  17. pygpt_net/core/agents/custom/llama_index/factory.py +100 -0
  18. pygpt_net/core/agents/custom/llama_index/router_streamer.py +106 -0
  19. pygpt_net/core/agents/custom/llama_index/runner.py +562 -0
  20. pygpt_net/core/agents/custom/llama_index/stream.py +56 -0
  21. pygpt_net/core/agents/custom/llama_index/utils.py +253 -0
  22. pygpt_net/core/agents/custom/logging.py +50 -0
  23. pygpt_net/core/agents/custom/memory.py +51 -0
  24. pygpt_net/core/agents/custom/router.py +155 -0
  25. pygpt_net/core/agents/custom/router_streamer.py +187 -0
  26. pygpt_net/core/agents/custom/runner.py +455 -0
  27. pygpt_net/core/agents/custom/schema.py +127 -0
  28. pygpt_net/core/agents/custom/utils.py +193 -0
  29. pygpt_net/core/agents/provider.py +72 -7
  30. pygpt_net/core/agents/runner.py +7 -4
  31. pygpt_net/core/agents/runners/helpers.py +1 -1
  32. pygpt_net/core/agents/runners/llama_workflow.py +3 -0
  33. pygpt_net/core/agents/runners/openai_workflow.py +8 -1
  34. pygpt_net/core/db/viewer.py +11 -5
  35. pygpt_net/{ui/widget/builder → core/node_editor}/__init__.py +2 -2
  36. pygpt_net/core/{builder → node_editor}/graph.py +28 -226
  37. pygpt_net/core/node_editor/models.py +118 -0
  38. pygpt_net/core/node_editor/types.py +78 -0
  39. pygpt_net/core/node_editor/utils.py +17 -0
  40. pygpt_net/core/presets/presets.py +216 -29
  41. pygpt_net/core/render/markdown/parser.py +0 -2
  42. pygpt_net/core/render/web/renderer.py +10 -8
  43. pygpt_net/data/config/config.json +5 -6
  44. pygpt_net/data/config/models.json +3 -3
  45. pygpt_net/data/config/settings.json +2 -38
  46. pygpt_net/data/locale/locale.de.ini +64 -1
  47. pygpt_net/data/locale/locale.en.ini +63 -4
  48. pygpt_net/data/locale/locale.es.ini +64 -1
  49. pygpt_net/data/locale/locale.fr.ini +64 -1
  50. pygpt_net/data/locale/locale.it.ini +64 -1
  51. pygpt_net/data/locale/locale.pl.ini +65 -2
  52. pygpt_net/data/locale/locale.uk.ini +64 -1
  53. pygpt_net/data/locale/locale.zh.ini +64 -1
  54. pygpt_net/data/locale/plugin.cmd_system.en.ini +62 -66
  55. pygpt_net/item/agent.py +5 -1
  56. pygpt_net/item/preset.py +19 -1
  57. pygpt_net/provider/agents/base.py +33 -2
  58. pygpt_net/provider/agents/llama_index/flow_from_schema.py +92 -0
  59. pygpt_net/provider/agents/openai/flow_from_schema.py +96 -0
  60. pygpt_net/provider/core/agent/json_file.py +11 -5
  61. pygpt_net/provider/core/config/patch.py +10 -1
  62. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -6
  63. pygpt_net/tools/agent_builder/tool.py +233 -52
  64. pygpt_net/tools/agent_builder/ui/dialogs.py +172 -28
  65. pygpt_net/tools/agent_builder/ui/list.py +37 -10
  66. pygpt_net/ui/__init__.py +2 -4
  67. pygpt_net/ui/dialog/about.py +58 -38
  68. pygpt_net/ui/dialog/db.py +142 -3
  69. pygpt_net/ui/dialog/preset.py +62 -8
  70. pygpt_net/ui/layout/toolbox/presets.py +52 -16
  71. pygpt_net/ui/main.py +1 -1
  72. pygpt_net/ui/widget/dialog/db.py +0 -0
  73. pygpt_net/ui/widget/lists/preset.py +644 -60
  74. pygpt_net/{core/builder → ui/widget/node_editor}/__init__.py +2 -2
  75. pygpt_net/ui/widget/node_editor/command.py +373 -0
  76. pygpt_net/ui/widget/node_editor/config.py +157 -0
  77. pygpt_net/ui/widget/node_editor/editor.py +2070 -0
  78. pygpt_net/ui/widget/node_editor/item.py +493 -0
  79. pygpt_net/ui/widget/node_editor/node.py +1460 -0
  80. pygpt_net/ui/widget/node_editor/utils.py +17 -0
  81. pygpt_net/ui/widget/node_editor/view.py +364 -0
  82. pygpt_net/ui/widget/tabs/output.py +1 -1
  83. pygpt_net/ui/widget/textarea/input.py +2 -2
  84. pygpt_net/utils.py +114 -2
  85. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/METADATA +80 -93
  86. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/RECORD +88 -61
  87. pygpt_net/core/agents/custom.py +0 -150
  88. pygpt_net/ui/widget/builder/editor.py +0 -2001
  89. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/LICENSE +0 -0
  90. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/WHEEL +0 -0
  91. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/entry_points.txt +0 -0
@@ -6,18 +6,20 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.19 00:00:00 #
9
+ # Updated Date: 2025.09.24 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from PySide6.QtCore import Qt
12
+ from PySide6.QtCore import Qt, QEvent
13
13
  from PySide6.QtGui import QAction, QIcon
14
- from PySide6.QtWidgets import QVBoxLayout, QMenuBar, QSplitter
14
+ from PySide6.QtWidgets import QVBoxLayout, QMenuBar, QSplitter, QSizePolicy, QWidget
15
15
 
16
- from pygpt_net.tools.agent_builder.ui.list import AgentsWidget
17
- from pygpt_net.ui.widget.builder.editor import NodeEditor
16
+ from pygpt_net.ui.widget.element.labels import HelpLabel
17
+ from pygpt_net.ui.widget.node_editor.editor import NodeEditor
18
18
  from pygpt_net.ui.widget.dialog.base import BaseDialog
19
19
  from pygpt_net.utils import trans
20
20
 
21
+ from .list import AgentsWidget
22
+
21
23
 
22
24
  class Builder:
23
25
 
@@ -34,18 +36,18 @@ class Builder:
34
36
  self.actions = {}
35
37
 
36
38
  def setup_menu(self, parent=None) -> QMenuBar:
37
- """Setup audio agent_builder dialog menu"""
39
+ """Setup agent_builder dialog menu"""
38
40
  self.menu_bar = QMenuBar(parent)
39
41
  self.file_menu = self.menu_bar.addMenu(trans("menu.file"))
40
42
  t = self.tool
41
43
 
42
- self.actions["open"] = QAction(QIcon(":/icons/folder.svg"), trans("action.open"), self.menu_bar)
44
+ self.actions["open"] = QAction(QIcon(":/icons/reload.svg"), trans("action.reload"), self.menu_bar)
43
45
  self.actions["open"].triggered.connect(lambda checked=False, t=t: t.load())
44
46
 
45
47
  self.actions["save"] = QAction(QIcon(":/icons/save.svg"), trans("action.save"), self.menu_bar)
46
48
  self.actions["save"].triggered.connect(lambda checked=False, t=t: t.save())
47
49
 
48
- self.actions["clear"] = QAction(QIcon(":/icons/clear.svg"), trans("action.clear"), self.menu_bar)
50
+ self.actions["clear"] = QAction(QIcon(":/icons/close.svg"), trans("action.clear"), self.menu_bar)
49
51
  self.actions["clear"].triggered.connect(lambda checked=False, t=t: t.clear())
50
52
 
51
53
  self.file_menu.addAction(self.actions["open"])
@@ -55,60 +57,124 @@ class Builder:
55
57
 
56
58
  def setup(self):
57
59
  """Setup agent_builder dialog"""
60
+ # Ensure previous deferred deletions are flushed before building a new editor instance
61
+ try:
62
+ from PySide6.QtWidgets import QApplication
63
+ QApplication.sendPostedEvents(None, QEvent.DeferredDelete)
64
+ QApplication.processEvents()
65
+ except Exception:
66
+ pass
67
+
58
68
  id = 'agent.builder'
59
69
  u = self.window.ui
60
70
 
61
71
  u.dialog['agent.builder'] = BuilderDialog(self.window)
62
72
  dlg = u.dialog['agent.builder']
63
73
 
64
- editor = NodeEditor(dlg) # parent == dialog
65
- editor.setStyleSheet("""
74
+ registry = self.tool.get_registry() # node types
75
+ editor = NodeEditor(
76
+ parent=dlg,
77
+ registry=registry
78
+ ) # parent == dialog
79
+
80
+ theme = self.window.core.config.get("theme")
81
+ if theme.startswith("light"):
82
+ style = """
83
+ NodeEditor {
84
+ qproperty-gridBackColor: #ffffff;
85
+ qproperty-gridPenColor: #eaeaea;
86
+
87
+ qproperty-nodeBackgroundColor: #2d2f34;
88
+ qproperty-nodeBorderColor: #4b4f57;
89
+ qproperty-nodeSelectionColor: #ff9900;
90
+ qproperty-nodeTitleColor: #3a3d44;
91
+
92
+ qproperty-portInputColor: #66b2ff;
93
+ qproperty-portOutputColor: #70e070;
94
+ qproperty-portConnectedColor: #ffd166;
95
+
96
+ qproperty-edgeColor: #c0c0c0;
97
+ qproperty-edgeSelectedColor: #ff8a5c;
98
+ }
99
+ """
100
+ else:
101
+ style = """
66
102
  NodeEditor {
67
103
  qproperty-gridBackColor: #242629;
68
104
  qproperty-gridPenColor: #3b3f46;
69
-
105
+
70
106
  qproperty-nodeBackgroundColor: #2d2f34;
71
107
  qproperty-nodeBorderColor: #4b4f57;
72
108
  qproperty-nodeSelectionColor: #ff9900;
73
109
  qproperty-nodeTitleColor: #3a3d44;
74
-
110
+
75
111
  qproperty-portInputColor: #66b2ff;
76
112
  qproperty-portOutputColor: #70e070;
77
113
  qproperty-portConnectedColor: #ffd166;
78
-
114
+
79
115
  qproperty-edgeColor: #c0c0c0;
80
116
  qproperty-edgeSelectedColor: #ff8a5c;
81
117
  }
82
- """)
83
-
84
- # Demo: add a couple of nodes
85
- #editor.add_node("Value/Float", QPointF(100, 120))
86
- #editor.add_node("Value/Float", QPointF(100, 240))
87
- #editor.add_node("Math/Add", QPointF(420, 180))
118
+ """
119
+ editor.setStyleSheet(style)
88
120
  editor.on_clear = self.tool.clear
121
+ editor.editing_allowed = self.tool.editing_allowed
89
122
 
90
123
  u.editor[id] = editor
91
124
 
92
125
  layout = QVBoxLayout()
93
126
  layout.setMenuBar(self.setup_menu(dlg))
94
127
 
95
- agents_list = AgentsWidget(self.window, tool=self.tool)
128
+ agents_list = AgentsWidget(self.window, tool=self.tool, parent=dlg)
96
129
  list_widget = agents_list.setup()
97
- list_widget.setFixedWidth(250)
130
+
131
+ # Left side container: list fills all space, help label stays at the bottom
132
+ left_side = QWidget(dlg)
133
+ left_layout = QVBoxLayout(left_side)
134
+ left_layout.setContentsMargins(0, 0, 0, 0)
135
+ left_layout.setSpacing(6)
136
+
137
+ left_help_label = HelpLabel(trans("node.editor.list.tip"))
138
+ left_help_label.setWordWrap(True)
139
+ left_help_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
140
+ left_help_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
141
+ left_help_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
142
+
143
+ left_layout.addWidget(list_widget)
144
+ left_layout.addWidget(left_help_label)
145
+ left_layout.setStretch(0, 1) # list -> fills all vertical space
146
+
147
+ # Fix the width of the whole left panel (not only the list)
148
+ left_side.setFixedWidth(250)
149
+
98
150
  center_splitter = QSplitter(Qt.Horizontal)
99
- center_splitter.addWidget(list_widget)
151
+ center_splitter.addWidget(left_side)
100
152
  center_splitter.addWidget(u.editor[id])
101
153
  center_splitter.setStretchFactor(0, 1)
102
154
  center_splitter.setStretchFactor(1, 8)
103
155
  layout.addWidget(center_splitter)
156
+ # Make the splitter take all extra vertical space so the legend stays compact at the bottom
157
+ layout.setStretch(0, 1)
104
158
 
159
+ # Bottom legend as a compact, centered help label
160
+ legend_label = HelpLabel(trans("node.editor.bottom.tip"))
161
+ legend_label.setAlignment(Qt.AlignCenter)
162
+ legend_label.setWordWrap(True)
163
+ legend_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
164
+ legend_label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
165
+
166
+ layout.addWidget(legend_label)
167
+
168
+ u.nodes["agent.builder.splitter"] = center_splitter
105
169
  u.nodes["agent.builder.list"] = agents_list
170
+ u.nodes["agent.builder.legend"] = legend_label
171
+ u.nodes["agent.builder.list.help"] = left_help_label
106
172
 
107
173
  dlg.setLayout(layout)
108
174
  dlg.setWindowTitle(trans("agent.builder.title"))
109
175
 
110
176
  def clear(self):
111
- """Clear transcribe dialog"""
177
+ """Clear dialog"""
112
178
  pass
113
179
 
114
180
 
@@ -123,6 +189,7 @@ class BuilderDialog(BaseDialog):
123
189
  super(BuilderDialog, self).__init__(window, id)
124
190
  self.window = window
125
191
  self.id = id # id
192
+ self._cleaned = False
126
193
 
127
194
  def closeEvent(self, event):
128
195
  """
@@ -140,13 +207,90 @@ class BuilderDialog(BaseDialog):
140
207
  :param event: key press event
141
208
  """
142
209
  if event.key() == Qt.Key_Escape:
143
- self.cleanup()
144
210
  self.close()
145
211
  else:
146
212
  super(BuilderDialog, self).keyPressEvent(event)
147
213
 
148
214
  def cleanup(self):
149
- """
150
- Cleanup on close
151
- """
152
- self.window.tools.get("agent_builder").on_close()
215
+ """Cleanup on dialog close"""
216
+ if self._cleaned:
217
+ return
218
+ self._cleaned = True
219
+
220
+ tool = self.window.tools.get("agent_builder")
221
+ if tool:
222
+ try:
223
+ tool.on_close() # store current state (layout, nodes positions, etc.)
224
+ except Exception:
225
+ pass
226
+
227
+ u = self.window.ui
228
+
229
+ try:
230
+ u.nodes["agent.builder.list"].cleanup()
231
+ except Exception as e:
232
+ print("Agents list close failed:", e)
233
+
234
+ splitter = u.nodes.get("agent.builder.splitter")
235
+ editor = u.editor.pop('agent.builder', None)
236
+
237
+ # Detach editor from splitter before closing/deleting it to avoid dangling pointers in QSplitter
238
+ try:
239
+ if splitter is not None and editor is not None:
240
+ # Ensure the editor actually belongs to this splitter
241
+ idx = splitter.indexOf(editor) if editor.parent() is splitter else -1
242
+ if idx != -1:
243
+ # Create the replacement without a parent; QSplitter will adopt it.
244
+ from PySide6.QtWidgets import QWidget as _QW
245
+ placeholder = _QW()
246
+ splitter.replaceWidget(idx, placeholder)
247
+ except Exception as e:
248
+ print("Splitter detach failed:", e)
249
+
250
+ if editor is not None:
251
+ try:
252
+ editor.setStyleSheet("")
253
+ except Exception:
254
+ pass
255
+ try:
256
+ editor.close()
257
+ except Exception as e:
258
+ print("NodeEditor close failed:", e)
259
+ try:
260
+ editor.setParent(None)
261
+ editor.deleteLater()
262
+ except Exception:
263
+ pass
264
+
265
+ # Dispose legend label safely
266
+ legend = u.nodes.pop("agent.builder.legend", None)
267
+ if legend is not None:
268
+ try:
269
+ legend.setParent(None)
270
+ legend.deleteLater()
271
+ except Exception:
272
+ pass
273
+
274
+ # Dispose left-side help label safely
275
+ try:
276
+ help_lbl = u.nodes.pop("agent.builder.list.help", None)
277
+ if help_lbl is not None:
278
+ help_lbl.setParent(None)
279
+ help_lbl.deleteLater()
280
+ except Exception:
281
+ pass
282
+
283
+ # Drop splitter reference
284
+ try:
285
+ u.nodes.pop("agent.builder.splitter", None)
286
+ except Exception:
287
+ pass
288
+
289
+ # Ensure all deferred deletions are processed (do it a few rounds to be safe)
290
+ try:
291
+ from PySide6.QtWidgets import QApplication
292
+ for _ in range(3):
293
+ QApplication.sendPostedEvents(None, QEvent.DeferredDelete)
294
+ QApplication.processEvents()
295
+ except Exception:
296
+ 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: 2025.08.24 23:00:00 #
9
+ # Updated Date: 2025.09.24 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6 import QtCore
@@ -19,7 +19,7 @@ from pygpt_net.utils import trans
19
19
 
20
20
 
21
21
  class AgentsWidget:
22
- def __init__(self, window=None, tool=None):
22
+ def __init__(self, window=None, tool=None, parent=None):
23
23
  """
24
24
  Agents select widget
25
25
 
@@ -27,6 +27,7 @@ class AgentsWidget:
27
27
  :param tool: tool instance
28
28
  """
29
29
  self.window = window
30
+ self.parent = parent
30
31
  self.tool = tool
31
32
  self.id = "agent.builder.list"
32
33
  self.list = None
@@ -37,21 +38,42 @@ class AgentsWidget:
37
38
 
38
39
  :return: QWidget
39
40
  """
40
- new_btn = QPushButton(QIcon(":/icons/add.svg"), "")
41
+ new_btn = QPushButton(QIcon(":/icons/add.svg"), "", self.parent)
41
42
  new_btn.clicked.connect(self.action_new)
42
43
 
43
- self.list = AgentsList(self.window, tool=self.tool, id=self.id)
44
+ self.list = AgentsList(self.parent, tool=self.tool, id=self.id)
44
45
  layout = QVBoxLayout()
45
46
  layout.addWidget(new_btn)
46
47
  layout.addWidget(self.list)
47
48
 
48
- self.window.ui.models[self.id] = self.create_model(self.window)
49
+ self.window.ui.models[self.id] = self.create_model(self.parent)
49
50
  self.list.setModel(self.window.ui.models[self.id])
50
51
 
51
52
  widget = QWidget()
52
53
  widget.setLayout(layout)
53
54
  return widget
54
55
 
56
+ def cleanup(self):
57
+ """Cleanup agents list widget"""
58
+ # Defensively detach the model and schedule deletion only if the view still exists
59
+ try:
60
+ if self.list is not None:
61
+ try:
62
+ self.list.setModel(None)
63
+ except Exception:
64
+ pass
65
+ try:
66
+ self.list.deleteLater()
67
+ except Exception:
68
+ pass
69
+ finally:
70
+ # Drop model reference in UI registry
71
+ try:
72
+ self.window.ui.models.pop(self.id, None)
73
+ except Exception:
74
+ pass
75
+ self.list = None
76
+
55
77
  def action_new(self):
56
78
  """
57
79
  New agent action
@@ -64,7 +86,7 @@ class AgentsWidget:
64
86
  :param parent: parent widget
65
87
  :return: QStandardItemModel
66
88
  """
67
- return QStandardItemModel(0, 1, parent)
89
+ return QStandardItemModel(0, 1, parent=parent)
68
90
 
69
91
  def update_list(self, data):
70
92
  """
@@ -75,13 +97,18 @@ class AgentsWidget:
75
97
  nodes = self.window.ui.nodes
76
98
  models = self.window.ui.models
77
99
 
78
- view = nodes[self.id]
79
- model = models.get(self.id)
100
+ # Guard: dialog may be closed; widget/list may not exist anymore
101
+ widget = nodes.get(self.id)
102
+ if widget is None or widget.list is None:
103
+ return
80
104
 
105
+ model = models.get(self.id)
81
106
  if model is None:
82
- model = self.create_model(self.window)
107
+ # When reopened after cleanup, re-create model and re-bind it to the view
108
+ model = self.create_model(self.parent)
83
109
  models[self.id] = model
84
- view.setModel(model)
110
+ widget.list.setModel(model)
111
+
85
112
  try:
86
113
  if not data:
87
114
  model.setRowCount(0)
pygpt_net/ui/__init__.py CHANGED
@@ -172,15 +172,13 @@ class UI:
172
172
 
173
173
  def show_loading(self):
174
174
  """Show loading"""
175
- if self.window.core.config.get('layout.animation.disable', False):
176
- return
175
+ return
177
176
  self.window.ui.nodes['anim.loading'].start_anim()
178
177
  self.window.ui.nodes['anim.loading'].show()
179
178
 
180
179
  def hide_loading(self):
181
180
  """Hide loading"""
182
- if self.window.core.config.get('layout.animation.disable', False):
183
- return
181
+ return
184
182
  self.window.ui.nodes['anim.loading'].stop_anim()
185
183
  self.window.ui.nodes['anim.loading'].hide()
186
184
 
@@ -19,6 +19,9 @@ from pygpt_net.utils import trans
19
19
 
20
20
 
21
21
  class About:
22
+
23
+ RELEASE_YEAR = 2025
24
+
22
25
  def __init__(self, window=None):
23
26
  """
24
27
  About dialog
@@ -36,34 +39,65 @@ class About:
36
39
  """
37
40
  return self.window.core.updater.get_fetch_thanks()
38
41
 
42
+ def build_versions_str(self, lib_versions: dict, break_after: str = "LlamaIndex") -> str:
43
+ parts = []
44
+ line = []
45
+ for k, v in lib_versions.items():
46
+ line.append(f"{k}: {v}")
47
+ if k == break_after:
48
+ parts.append(", ".join(line))
49
+ line = []
50
+ if line:
51
+ parts.append(", ".join(line))
52
+ return "\n".join(parts)
53
+
39
54
  def prepare_content(self) -> str:
40
55
  """
41
56
  Get info text
42
57
 
43
58
  :return: info text
44
59
  """
45
- llama_index_version = None
46
- langchain_version = None
47
- openai_version = None
48
- versions = True
60
+ lib_versions = {}
61
+
62
+ try:
63
+ import platform
64
+ lib_versions['Python'] = platform.python_version()
65
+ except ImportError:
66
+ pass
49
67
 
50
68
  try:
51
- from llama_index.core import __version__ as llama_index_version
52
- # from langchain import __version__ as langchain_version
53
69
  from openai.version import VERSION as openai_version
70
+ lib_versions['OpenAI API'] = openai_version
71
+ except ImportError:
72
+ pass
73
+
74
+ try:
75
+ from llama_index.core import __version__ as llama_index_version
76
+ lib_versions['LlamaIndex'] = llama_index_version
77
+ except ImportError:
78
+ pass
79
+
80
+ try:
81
+ from anthropic import __version__ as anthropic_version
82
+ lib_versions['Anthropic API'] = anthropic_version
83
+ except ImportError:
84
+ pass
85
+
86
+ try:
87
+ from google.genai import __version__ as google_genai_version
88
+ lib_versions['Google API'] = google_genai_version
54
89
  except ImportError:
55
90
  pass
56
91
 
57
- lib_versions = ""
58
- if llama_index_version is None or openai_version is None:
59
- versions = False
92
+ try:
93
+ from xai_sdk import __version__ as xai_sdk_version
94
+ lib_versions['xAI API'] = xai_sdk_version
95
+ except ImportError:
96
+ pass
60
97
 
61
- if versions:
62
- lib_versions = "OpenAI API: {}, LlamaIndex: {}\n\n".format(
63
- openai_version,
64
- # langchain_version,
65
- llama_index_version,
66
- )
98
+ versions_str = ""
99
+ if lib_versions:
100
+ versions_str = self.build_versions_str(lib_versions, break_after="LlamaIndex")
67
101
 
68
102
  platform = self.window.core.platforms.get_as_string()
69
103
  version = self.window.meta['version']
@@ -80,29 +114,14 @@ class About:
80
114
  label_github = trans("dialog.about.github")
81
115
  label_docs = trans("dialog.about.docs")
82
116
 
83
- data = "{label_version}: {version}, {platform}\n" \
84
- "{label_build}: {build}\n" \
85
- "{lib_versions}" \
86
- "{label_website}: {website}\n" \
87
- "{label_github}: {github}\n" \
88
- "{label_docs}: {docs}\n\n" \
89
- "(c) 2025 {author}\n" \
90
- "{email}\n".format(
91
- label_version=label_version,
92
- version=version,
93
- platform=platform,
94
- label_build=label_build,
95
- build=build.replace('.', '-'),
96
- label_website=label_website,
97
- website=website,
98
- label_github=label_github,
99
- github=github,
100
- label_docs=label_docs,
101
- docs=docs,
102
- author=author,
103
- email=email,
104
- lib_versions=lib_versions,
105
- )
117
+ data = f"{label_version}: {version}, {platform}\n" \
118
+ f"{label_build}: {build.replace('.', '-')}\n\n" \
119
+ f"{versions_str}\n\n" \
120
+ f"{label_website}: {website}\n" \
121
+ f"{label_github}: {github}\n" \
122
+ f"{label_docs}: {docs}\n\n" \
123
+ f"(c) {self.RELEASE_YEAR} {author}\n" \
124
+ f"{email}\n"
106
125
  return data
107
126
 
108
127
  def setup(self):
@@ -139,6 +158,7 @@ class About:
139
158
  string = self.prepare_content()
140
159
  content = QLabel(string)
141
160
  content.setTextInteractionFlags(Qt.TextSelectableByMouse)
161
+ content.setWordWrap(True)
142
162
  self.window.ui.nodes['dialog.about.content'] = content
143
163
 
144
164
  thanks = QLabel(trans('about.thanks'))