pygpt-net 2.6.61__py3-none-any.whl → 2.6.63__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/controller/chat/response.py +8 -2
- pygpt_net/controller/presets/editor.py +65 -1
- pygpt_net/controller/settings/profile.py +16 -4
- pygpt_net/controller/settings/workdir.py +30 -5
- pygpt_net/controller/theme/common.py +4 -2
- pygpt_net/controller/theme/markdown.py +2 -2
- pygpt_net/controller/theme/theme.py +2 -1
- pygpt_net/controller/ui/ui.py +31 -3
- pygpt_net/core/agents/custom/llama_index/runner.py +30 -52
- pygpt_net/core/agents/custom/runner.py +199 -76
- pygpt_net/core/agents/runners/llama_workflow.py +122 -12
- pygpt_net/core/agents/runners/openai_workflow.py +2 -1
- pygpt_net/core/node_editor/types.py +13 -1
- pygpt_net/core/render/web/renderer.py +76 -11
- pygpt_net/data/config/config.json +3 -3
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/presets/agent_openai_b2b.json +1 -15
- pygpt_net/data/config/presets/agent_openai_coder.json +1 -15
- pygpt_net/data/config/presets/agent_openai_evolve.json +1 -23
- pygpt_net/data/config/presets/agent_openai_planner.json +1 -21
- pygpt_net/data/config/presets/agent_openai_researcher.json +1 -21
- pygpt_net/data/config/presets/agent_openai_supervisor.json +1 -13
- pygpt_net/data/config/presets/agent_openai_writer.json +1 -15
- pygpt_net/data/config/presets/agent_supervisor.json +1 -11
- pygpt_net/data/css/style.dark.css +18 -0
- pygpt_net/data/css/style.light.css +20 -1
- pygpt_net/data/js/app/runtime.js +4 -1
- pygpt_net/data/js/app.min.js +3 -2
- pygpt_net/data/locale/locale.de.ini +2 -0
- pygpt_net/data/locale/locale.en.ini +7 -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 +2 -0
- pygpt_net/data/locale/locale.zh.ini +2 -0
- pygpt_net/item/ctx.py +23 -1
- pygpt_net/js_rc.py +13 -10
- pygpt_net/provider/agents/base.py +0 -0
- pygpt_net/provider/agents/llama_index/flow_from_schema.py +0 -0
- pygpt_net/provider/agents/llama_index/workflow/codeact.py +9 -6
- pygpt_net/provider/agents/llama_index/workflow/openai.py +38 -11
- pygpt_net/provider/agents/llama_index/workflow/planner.py +248 -28
- pygpt_net/provider/agents/llama_index/workflow/supervisor.py +60 -10
- pygpt_net/provider/agents/openai/agent.py +3 -1
- pygpt_net/provider/agents/openai/agent_b2b.py +17 -13
- pygpt_net/provider/agents/openai/agent_planner.py +617 -258
- pygpt_net/provider/agents/openai/agent_with_experts.py +4 -1
- pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +8 -6
- pygpt_net/provider/agents/openai/agent_with_feedback.py +8 -6
- pygpt_net/provider/agents/openai/evolve.py +12 -8
- pygpt_net/provider/agents/openai/flow_from_schema.py +0 -0
- pygpt_net/provider/agents/openai/supervisor.py +292 -37
- pygpt_net/provider/api/openai/agents/response.py +1 -0
- pygpt_net/provider/api/x_ai/__init__.py +0 -0
- pygpt_net/provider/core/agent/__init__.py +0 -0
- pygpt_net/provider/core/agent/base.py +0 -0
- pygpt_net/provider/core/agent/json_file.py +0 -0
- pygpt_net/provider/core/config/patch.py +8 -0
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -0
- pygpt_net/provider/llms/base.py +0 -0
- pygpt_net/provider/llms/deepseek_api.py +0 -0
- pygpt_net/provider/llms/google.py +0 -0
- pygpt_net/provider/llms/hugging_face_api.py +0 -0
- pygpt_net/provider/llms/hugging_face_router.py +0 -0
- pygpt_net/provider/llms/mistral.py +0 -0
- pygpt_net/provider/llms/perplexity.py +0 -0
- pygpt_net/provider/llms/x_ai.py +0 -0
- pygpt_net/tools/agent_builder/tool.py +6 -0
- pygpt_net/tools/agent_builder/ui/dialogs.py +0 -41
- pygpt_net/ui/layout/toolbox/presets.py +14 -2
- pygpt_net/ui/main.py +2 -2
- pygpt_net/ui/widget/dialog/confirm.py +55 -5
- pygpt_net/ui/widget/draw/painter.py +90 -1
- pygpt_net/ui/widget/lists/preset.py +289 -25
- pygpt_net/ui/widget/node_editor/editor.py +53 -15
- pygpt_net/ui/widget/node_editor/node.py +82 -104
- pygpt_net/ui/widget/node_editor/view.py +4 -5
- pygpt_net/ui/widget/textarea/input.py +155 -21
- {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.63.dist-info}/METADATA +22 -8
- {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.63.dist-info}/RECORD +70 -70
- {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.63.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.63.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.61.dist-info → pygpt_net-2.6.63.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
2.6.63 (2025-09-27)
|
|
2
|
+
|
|
3
|
+
- Improved agents' workflows.
|
|
4
|
+
- Enhanced the display of agents' steps in the UI.
|
|
5
|
+
|
|
6
|
+
2.6.62 (2025-09-26)
|
|
7
|
+
|
|
8
|
+
- Enhanced agent workflow execution.
|
|
9
|
+
- Improved preset list handling by adding a drop field indicator and fixing auto-scroll.
|
|
10
|
+
- Added middle-mouse button panning to Painter.
|
|
11
|
+
- Added an input character counter.
|
|
12
|
+
|
|
1
13
|
2.6.61 (2025-09-26)
|
|
2
14
|
|
|
3
15
|
- Enhanced the agents node editor, custom agent flow, and instruction following.
|
pygpt_net/__init__.py
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.09.27 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
__author__ = "Marcin Szczygliński"
|
|
13
13
|
__copyright__ = "Copyright 2025, Marcin Szczygliński"
|
|
14
14
|
__credits__ = ["Marcin Szczygliński"]
|
|
15
15
|
__license__ = "MIT"
|
|
16
|
-
__version__ = "2.6.
|
|
17
|
-
__build__ = "2025-09-
|
|
16
|
+
__version__ = "2.6.63"
|
|
17
|
+
__build__ = "2025-09-27"
|
|
18
18
|
__maintainer__ = "Marcin Szczygliński"
|
|
19
19
|
__github__ = "https://github.com/szczyglis-dev/py-gpt"
|
|
20
20
|
__report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
|
|
@@ -36,6 +36,7 @@ class Response:
|
|
|
36
36
|
"""
|
|
37
37
|
super(Response, self).__init__()
|
|
38
38
|
self.window = window
|
|
39
|
+
self.last_response_id = None
|
|
39
40
|
|
|
40
41
|
def handle(
|
|
41
42
|
self,
|
|
@@ -273,9 +274,14 @@ class Response:
|
|
|
273
274
|
self.window.update_status(trans("status.agent.reasoning"))
|
|
274
275
|
controller.chat.common.lock_input() # lock input, re-enable stop button
|
|
275
276
|
|
|
276
|
-
# agent final response
|
|
277
|
+
# agent final response, with fix for async delayed finish (prevent multiple calls for the same response)
|
|
277
278
|
if ctx.extra is not None and (isinstance(ctx.extra, dict) and "agent_finish" in ctx.extra):
|
|
278
|
-
|
|
279
|
+
consume = False
|
|
280
|
+
if self.last_response_id is None or self.last_response_id < ctx.id:
|
|
281
|
+
consume = True
|
|
282
|
+
self.last_response_id = ctx.id
|
|
283
|
+
if consume:
|
|
284
|
+
controller.agent.llama.on_finish(ctx) # evaluate response and continue if needed
|
|
279
285
|
|
|
280
286
|
def end(
|
|
281
287
|
self,
|
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.27 00:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
@@ -393,6 +393,9 @@ class Editor:
|
|
|
393
393
|
value=extra_options[key].get('default', None),
|
|
394
394
|
)
|
|
395
395
|
|
|
396
|
+
# ensure combo defaults are effectively applied for this tab (only empty values are updated)
|
|
397
|
+
self._apply_combo_defaults_for_group(option_key, extra_options)
|
|
398
|
+
|
|
396
399
|
def load_extra_defaults(self):
|
|
397
400
|
"""Load extra options defaults for preset editor"""
|
|
398
401
|
if not self.tab_options_idx:
|
|
@@ -423,6 +426,8 @@ class Editor:
|
|
|
423
426
|
option=extra_options[key],
|
|
424
427
|
value=value,
|
|
425
428
|
)
|
|
429
|
+
# ensure combo defaults are effectively applied for this tab (only empty values are updated)
|
|
430
|
+
self._apply_combo_defaults_for_group(option_key, extra_options)
|
|
426
431
|
|
|
427
432
|
def load_extra_defaults_current(self):
|
|
428
433
|
"""Load extra options defaults on mode change"""
|
|
@@ -479,6 +484,8 @@ class Editor:
|
|
|
479
484
|
option=extra_options[key],
|
|
480
485
|
value=value,
|
|
481
486
|
)
|
|
487
|
+
# ensure combo defaults are effectively applied for this tab (only empty values are updated)
|
|
488
|
+
self._apply_combo_defaults_for_group(option_key, extra_options)
|
|
482
489
|
|
|
483
490
|
def append_extra_options(self, preset: PresetItem):
|
|
484
491
|
"""
|
|
@@ -785,6 +792,9 @@ class Editor:
|
|
|
785
792
|
value=opt_schema.get('default'),
|
|
786
793
|
)
|
|
787
794
|
|
|
795
|
+
# ensure combo defaults are effectively applied for this tab (only empty values are updated)
|
|
796
|
+
self._apply_combo_defaults_for_group(config_id, schema_options)
|
|
797
|
+
|
|
788
798
|
# 4) Recompute mapping fully based on actual tabs and their 'agent_id' properties.
|
|
789
799
|
self._rebuild_tab_index_mapping()
|
|
790
800
|
|
|
@@ -1521,6 +1531,9 @@ class Editor:
|
|
|
1521
1531
|
value=opt_schema.get('default'),
|
|
1522
1532
|
)
|
|
1523
1533
|
|
|
1534
|
+
# ensure combo defaults are effectively applied for this tab (only empty values are updated)
|
|
1535
|
+
self._apply_combo_defaults_for_group(config_id, schema_options)
|
|
1536
|
+
|
|
1524
1537
|
# 7) Recompute the index mapping strictly from the QTabWidget
|
|
1525
1538
|
self._rebuild_tab_index_mapping()
|
|
1526
1539
|
|
|
@@ -1529,3 +1542,54 @@ class Editor:
|
|
|
1529
1542
|
|
|
1530
1543
|
finally:
|
|
1531
1544
|
tabs.setUpdatesEnabled(True)
|
|
1545
|
+
|
|
1546
|
+
# ---------- Helpers for reliable combo defaults in agent extra options ----------
|
|
1547
|
+
|
|
1548
|
+
def _apply_combo_defaults_for_group(self, parent_id: str, schema_options: Dict[str, Any]) -> None:
|
|
1549
|
+
"""
|
|
1550
|
+
Ensure that combo-type inputs inside a given UI config group have their default values applied
|
|
1551
|
+
when the current value is empty ("", None or "_"). This avoids the situation where combo boxes
|
|
1552
|
+
remain uninitialized while other field types receive defaults correctly.
|
|
1553
|
+
|
|
1554
|
+
This function never overrides a non-empty value set by the user or loaded from a preset.
|
|
1555
|
+
"""
|
|
1556
|
+
if not schema_options:
|
|
1557
|
+
return
|
|
1558
|
+
|
|
1559
|
+
get_value = self.window.controller.config.get_value
|
|
1560
|
+
apply_value = self.window.controller.config.apply_value
|
|
1561
|
+
|
|
1562
|
+
for key, opt_schema in schema_options.items():
|
|
1563
|
+
if not isinstance(opt_schema, dict):
|
|
1564
|
+
continue
|
|
1565
|
+
if opt_schema.get('type') != 'combo':
|
|
1566
|
+
continue
|
|
1567
|
+
|
|
1568
|
+
default_val = opt_schema.get('default', None)
|
|
1569
|
+
if default_val is None:
|
|
1570
|
+
continue
|
|
1571
|
+
|
|
1572
|
+
current_val = get_value(
|
|
1573
|
+
parent_id=parent_id,
|
|
1574
|
+
key=key,
|
|
1575
|
+
option=opt_schema,
|
|
1576
|
+
)
|
|
1577
|
+
|
|
1578
|
+
# Treat "_", "", None as empty and safe to replace with default
|
|
1579
|
+
if current_val in (None, "", "_"):
|
|
1580
|
+
# First try apply_value (standard path)
|
|
1581
|
+
apply_value(
|
|
1582
|
+
parent_id=parent_id,
|
|
1583
|
+
key=key,
|
|
1584
|
+
option=opt_schema,
|
|
1585
|
+
value=default_val,
|
|
1586
|
+
)
|
|
1587
|
+
# Additionally set directly on widget if accessible to guard against timing of key population
|
|
1588
|
+
try:
|
|
1589
|
+
widget_group = self.window.ui.config.get(parent_id, {})
|
|
1590
|
+
widget = widget_group.get(key)
|
|
1591
|
+
if widget and hasattr(widget, "set_value"):
|
|
1592
|
+
widget.set_value(default_val)
|
|
1593
|
+
except Exception:
|
|
1594
|
+
# Silent fallback; apply_value above should already handle most cases
|
|
1595
|
+
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.
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -33,6 +33,8 @@ class Profile:
|
|
|
33
33
|
self.height = 500
|
|
34
34
|
self.initialized = False
|
|
35
35
|
self.dialog_initialized = False
|
|
36
|
+
self.before_theme = None
|
|
37
|
+
self.before_language = None
|
|
36
38
|
|
|
37
39
|
def setup(self):
|
|
38
40
|
"""Setup profile"""
|
|
@@ -54,7 +56,8 @@ class Profile:
|
|
|
54
56
|
uuid: str,
|
|
55
57
|
force: bool = False,
|
|
56
58
|
save_current: bool = True,
|
|
57
|
-
on_finish: Optional[callable] = None
|
|
59
|
+
on_finish: Optional[callable] = None,
|
|
60
|
+
is_create: bool = False,
|
|
58
61
|
):
|
|
59
62
|
"""
|
|
60
63
|
Switch profile
|
|
@@ -63,6 +66,7 @@ class Profile:
|
|
|
63
66
|
:param force: Force switch
|
|
64
67
|
:param save_current: Save current profile
|
|
65
68
|
:param on_finish: Callback function to call after switch
|
|
69
|
+
:param is_create: Is called from create profile
|
|
66
70
|
"""
|
|
67
71
|
current = self.window.core.config.profile.get_current()
|
|
68
72
|
if uuid == current and not force:
|
|
@@ -85,7 +89,8 @@ class Profile:
|
|
|
85
89
|
self.window.controller.settings.workdir.update(
|
|
86
90
|
path,
|
|
87
91
|
force=True,
|
|
88
|
-
profile_name=profile['name']
|
|
92
|
+
profile_name=profile['name'],
|
|
93
|
+
is_create=is_create,
|
|
89
94
|
)
|
|
90
95
|
else:
|
|
91
96
|
self.after_update(profile['name'])
|
|
@@ -288,7 +293,14 @@ class Profile:
|
|
|
288
293
|
|
|
289
294
|
:param uuid: profile UUID
|
|
290
295
|
"""
|
|
291
|
-
self.
|
|
296
|
+
self.before_theme = self.window.core.config.get("theme")
|
|
297
|
+
self.before_language = self.window.core.config.get("lang")
|
|
298
|
+
self.switch(
|
|
299
|
+
uuid,
|
|
300
|
+
force=True,
|
|
301
|
+
on_finish=self.after_create_finish,
|
|
302
|
+
is_create=True
|
|
303
|
+
)
|
|
292
304
|
|
|
293
305
|
def after_create_finish(self, uuid: str):
|
|
294
306
|
"""
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import copy
|
|
@@ -288,13 +288,15 @@ class Workdir:
|
|
|
288
288
|
def update_workdir(
|
|
289
289
|
self,
|
|
290
290
|
force: bool = False,
|
|
291
|
-
path: str = None
|
|
291
|
+
path: str = None,
|
|
292
|
+
is_create: bool = False,
|
|
292
293
|
):
|
|
293
294
|
"""
|
|
294
295
|
Update working directory
|
|
295
296
|
|
|
296
297
|
:param force: boolean indicating if update should be forced (confirm)
|
|
297
298
|
:param path: new working directory to set
|
|
299
|
+
:param is_create: True if called on profile creation
|
|
298
300
|
"""
|
|
299
301
|
print("\n====================")
|
|
300
302
|
print(f"Changing workdir to: {path}")
|
|
@@ -313,8 +315,25 @@ class Workdir:
|
|
|
313
315
|
# update path in current profile
|
|
314
316
|
self.window.core.config.profile.update_current_workdir(path)
|
|
315
317
|
|
|
318
|
+
# save previous theme and language to retain them after workdir change
|
|
319
|
+
prev_theme = None
|
|
320
|
+
prev_lang = None
|
|
321
|
+
if is_create:
|
|
322
|
+
prev_theme = self.window.core.config.get('theme')
|
|
323
|
+
prev_lang = self.window.core.config.get('lang')
|
|
324
|
+
|
|
316
325
|
# reload config
|
|
317
326
|
self.window.core.config.set_workdir(path, reload=True)
|
|
327
|
+
|
|
328
|
+
# if profile is just created, use current theme and language
|
|
329
|
+
if is_create:
|
|
330
|
+
print("Using current theme and language: ", prev_theme, prev_lang)
|
|
331
|
+
if prev_theme is not None:
|
|
332
|
+
self.window.core.config.set('theme', prev_theme)
|
|
333
|
+
if prev_lang is not None:
|
|
334
|
+
self.window.core.config.set('lang', prev_lang)
|
|
335
|
+
self.window.core.config.save()
|
|
336
|
+
|
|
318
337
|
self.window.core.config.set('license.accepted', True) # accept license to prevent show dialog again
|
|
319
338
|
|
|
320
339
|
@Slot(bool, str, str, str)
|
|
@@ -323,7 +342,8 @@ class Workdir:
|
|
|
323
342
|
force: bool,
|
|
324
343
|
profile_name: str,
|
|
325
344
|
current_path: str,
|
|
326
|
-
new_path: str
|
|
345
|
+
new_path: str,
|
|
346
|
+
is_create: bool = False
|
|
327
347
|
) -> bool:
|
|
328
348
|
"""
|
|
329
349
|
Update working directory
|
|
@@ -332,18 +352,20 @@ class Workdir:
|
|
|
332
352
|
:param profile_name: profile name to update after workdir change
|
|
333
353
|
:param current_path: current working directory before update
|
|
334
354
|
:param new_path: new working directory to set
|
|
355
|
+
:param is_create: if True, skip check for existing workdir in path
|
|
335
356
|
:return: boolean indicating if update was successful
|
|
336
357
|
"""
|
|
337
358
|
self.update_workdir(
|
|
338
359
|
force=force,
|
|
339
360
|
path=new_path,
|
|
361
|
+
is_create=is_create,
|
|
340
362
|
)
|
|
341
363
|
rollback = False
|
|
342
364
|
success = False
|
|
343
365
|
if force:
|
|
344
366
|
try:
|
|
345
367
|
self.window.ui.dialogs.workdir.show_status(trans("dialog.workdir.result.wait"))
|
|
346
|
-
self.window.controller.reload()
|
|
368
|
+
self.window.controller.reload() # reload all
|
|
347
369
|
self.window.ui.dialogs.workdir.show_status(trans("dialog.workdir.result.wait"))
|
|
348
370
|
msg = trans("dialog.workdir.result.success").format(path=new_path)
|
|
349
371
|
self.window.ui.dialogs.workdir.show_status(msg)
|
|
@@ -498,7 +520,8 @@ class Workdir:
|
|
|
498
520
|
self,
|
|
499
521
|
path: str,
|
|
500
522
|
force: bool = False,
|
|
501
|
-
profile_name: str = None
|
|
523
|
+
profile_name: str = None,
|
|
524
|
+
is_create: bool = False,
|
|
502
525
|
):
|
|
503
526
|
"""
|
|
504
527
|
Switch working directory to the existing one
|
|
@@ -506,12 +529,14 @@ class Workdir:
|
|
|
506
529
|
:param path: existing working directory
|
|
507
530
|
:param force: force update (confirm)
|
|
508
531
|
:param profile_name: profile name (optional, for future use)
|
|
532
|
+
:param is_create: if True, skip check for existing workdir in path
|
|
509
533
|
"""
|
|
510
534
|
self.do_update(
|
|
511
535
|
force=force,
|
|
512
536
|
profile_name=profile_name,
|
|
513
537
|
current_path=self.window.core.config.get_user_path(),
|
|
514
538
|
new_path=path,
|
|
539
|
+
is_create=is_create,
|
|
515
540
|
)
|
|
516
541
|
|
|
517
542
|
def migrate(
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -32,6 +32,8 @@ class Common:
|
|
|
32
32
|
:return: custom css filename (e.g. style.dark.css)
|
|
33
33
|
"""
|
|
34
34
|
# check per theme style css
|
|
35
|
+
if name is None:
|
|
36
|
+
name = ""
|
|
35
37
|
filename = 'style.css'
|
|
36
38
|
if filename is not None:
|
|
37
39
|
# per theme mode (light / dark)
|
|
@@ -58,7 +60,7 @@ class Common:
|
|
|
58
60
|
|
|
59
61
|
:return: True if light theme, False otherwise
|
|
60
62
|
"""
|
|
61
|
-
theme = self.window.core.config.get('theme')
|
|
63
|
+
theme = str(self.window.core.config.get('theme'))
|
|
62
64
|
return theme.startswith('light_') or theme == 'light'
|
|
63
65
|
|
|
64
66
|
def toggle_tooltips(self):
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -92,7 +92,7 @@ class Markdown:
|
|
|
92
92
|
if base_name == 'web':
|
|
93
93
|
suffix = "-" + web_style
|
|
94
94
|
self.web_style = web_style
|
|
95
|
-
theme = self.window.core.config.get('theme')
|
|
95
|
+
theme = str(self.window.core.config.get('theme'))
|
|
96
96
|
name = str(base_name)
|
|
97
97
|
if theme.startswith('light'):
|
|
98
98
|
color = '.light'
|
|
@@ -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.
|
|
9
|
+
# Updated Date: 2025.09.26 13:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -83,6 +83,7 @@ class Theme:
|
|
|
83
83
|
:param name: theme name
|
|
84
84
|
:param force: force theme change (manual trigger)
|
|
85
85
|
"""
|
|
86
|
+
self.current_theme = name
|
|
86
87
|
window = self.window
|
|
87
88
|
core = window.core
|
|
88
89
|
controller = window.controller
|
pygpt_net/controller/ui/ui.py
CHANGED
|
@@ -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.09.
|
|
9
|
+
# Updated Date: 2025.09.26 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from typing import Optional
|
|
@@ -51,6 +51,9 @@ class UI:
|
|
|
51
51
|
self._last_chat_model = None
|
|
52
52
|
self._last_chat_label = None
|
|
53
53
|
|
|
54
|
+
# Cache for Input tab tooltip to avoid redundant updates
|
|
55
|
+
self._last_input_tab_tooltip = None
|
|
56
|
+
|
|
54
57
|
def setup(self):
|
|
55
58
|
"""Setup UI"""
|
|
56
59
|
self.update_font_size()
|
|
@@ -150,7 +153,11 @@ class UI:
|
|
|
150
153
|
def update_tokens(self):
|
|
151
154
|
"""Update tokens counter in real-time"""
|
|
152
155
|
ui_nodes = self.window.ui.nodes
|
|
153
|
-
|
|
156
|
+
|
|
157
|
+
# Read raw input for accurate character count (without trimming)
|
|
158
|
+
raw_text = ui_nodes['input'].toPlainText()
|
|
159
|
+
prompt = raw_text.strip()
|
|
160
|
+
|
|
154
161
|
input_tokens, system_tokens, extra_tokens, ctx_tokens, ctx_len, ctx_len_all, \
|
|
155
162
|
sum_tokens, max_current, threshold = self.window.core.tokens.get_current(prompt)
|
|
156
163
|
attachments_tokens = self.window.controller.chat.attachment.get_current_tokens()
|
|
@@ -161,11 +168,32 @@ class UI:
|
|
|
161
168
|
ui_nodes['prompt.context'].setText(ctx_string)
|
|
162
169
|
self._last_ctx_string = ctx_string
|
|
163
170
|
|
|
164
|
-
|
|
171
|
+
if max_current > 0:
|
|
172
|
+
max_str = short_num(max_current)
|
|
173
|
+
else:
|
|
174
|
+
max_str = "∞"
|
|
175
|
+
|
|
176
|
+
input_string = f"{short_num(input_tokens)} + {short_num(system_tokens)} + {short_num(ctx_tokens)} + {short_num(extra_tokens)} + {short_num(attachments_tokens)} = {short_num(sum_tokens)} / {max_str}"
|
|
165
177
|
if input_string != self._last_input_string:
|
|
166
178
|
ui_nodes['input.counter'].setText(input_string)
|
|
167
179
|
self._last_input_string = input_string
|
|
168
180
|
|
|
181
|
+
# Update Input tab tooltip with live "<chars> chars (~<tokens> tokens)" string
|
|
182
|
+
try:
|
|
183
|
+
tabs = self.window.ui.tabs.get('input')
|
|
184
|
+
except Exception:
|
|
185
|
+
tabs = None
|
|
186
|
+
|
|
187
|
+
if tabs is not None:
|
|
188
|
+
try:
|
|
189
|
+
tooltip = trans("input.tab.tooltip").format(chars=short_num(len(raw_text)), tokens=short_num(input_tokens))
|
|
190
|
+
except Exception:
|
|
191
|
+
tooltip = ""
|
|
192
|
+
#tooltip = f"{short_num(len(raw_text))} chars (~{short_num(input_tokens)} tokens)"
|
|
193
|
+
if tooltip != self._last_input_tab_tooltip:
|
|
194
|
+
tabs.setTabToolTip(0, tooltip)
|
|
195
|
+
self._last_input_tab_tooltip = tooltip
|
|
196
|
+
|
|
169
197
|
def store_state(self):
|
|
170
198
|
"""Store UI state"""
|
|
171
199
|
self.window.controller.layout.scroll_save()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# core/agents/runners/llama_workflow.py
|
|
2
|
+
|
|
1
3
|
#!/usr/bin/env python3
|
|
2
4
|
# -*- coding: utf-8 -*-
|
|
3
5
|
# ================================================== #
|
|
@@ -6,7 +8,7 @@
|
|
|
6
8
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
9
|
# MIT License #
|
|
8
10
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.09.
|
|
11
|
+
# Updated Date: 2025.09.27 06:00:00 #
|
|
10
12
|
# ================================================== #
|
|
11
13
|
|
|
12
14
|
from __future__ import annotations
|
|
@@ -207,37 +209,6 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
207
209
|
return False
|
|
208
210
|
return False
|
|
209
211
|
|
|
210
|
-
def _friendly_map(self) -> Dict[str, str]:
|
|
211
|
-
return {aid: a.name or aid for aid, a in self.fs.agents.items()}
|
|
212
|
-
|
|
213
|
-
def _friendly_map_for_routes(self, route_ids: List[str]) -> Dict[str, Any]:
|
|
214
|
-
"""
|
|
215
|
-
Build a friendly map for the given route ids:
|
|
216
|
-
- Always include a human-friendly name.
|
|
217
|
-
- Include role only if provided in preset options or schema and non-empty.
|
|
218
|
-
"""
|
|
219
|
-
out: Dict[str, Any] = {}
|
|
220
|
-
for rid in route_ids or []:
|
|
221
|
-
a = self.fs.agents.get(rid)
|
|
222
|
-
name = (a.name if a and a.name else rid)
|
|
223
|
-
# Prefer preset option, then schema role
|
|
224
|
-
role_opt = None
|
|
225
|
-
try:
|
|
226
|
-
role_opt = self.option_get(rid, "role", None)
|
|
227
|
-
except Exception:
|
|
228
|
-
role_opt = None
|
|
229
|
-
role_schema = getattr(a, "role", None) if a is not None else None
|
|
230
|
-
role_val = None
|
|
231
|
-
if isinstance(role_opt, str) and role_opt.strip():
|
|
232
|
-
role_val = role_opt.strip()
|
|
233
|
-
elif isinstance(role_schema, str) and role_schema.strip():
|
|
234
|
-
role_val = role_schema.strip()
|
|
235
|
-
item = {"name": name}
|
|
236
|
-
if role_val:
|
|
237
|
-
item["role"] = role_val
|
|
238
|
-
out[rid] = item
|
|
239
|
-
return out
|
|
240
|
-
|
|
241
212
|
async def _emit(self, ctx: Context, ev: Any):
|
|
242
213
|
if self.dbg.event_echo:
|
|
243
214
|
self.logger.debug(f"[event] emit {ev.__class__.__name__}")
|
|
@@ -245,8 +216,8 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
245
216
|
|
|
246
217
|
async def _emit_agent_text(self, ctx: Context, text: str, agent_name: str = "Agent"):
|
|
247
218
|
"""
|
|
248
|
-
Emit AgentStream(delta=text) robustly. If
|
|
249
|
-
fall back to extended AgentStream
|
|
219
|
+
Emit AgentStream(delta=text) robustly. If env requires extra fields,
|
|
220
|
+
fall back to extended AgentStream.
|
|
250
221
|
"""
|
|
251
222
|
try:
|
|
252
223
|
if self.dbg.event_echo:
|
|
@@ -266,15 +237,25 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
266
237
|
)
|
|
267
238
|
|
|
268
239
|
async def _emit_header(self, ctx: Context, name: str):
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await self._emit_agent_text(ctx, f"\n\n**{name}**\n\n", agent_name=name)
|
|
272
|
-
if self.dbg.event_echo:
|
|
273
|
-
self.logger.debug("[event] header emit done")
|
|
240
|
+
# Lightweight header to ensure agent name is known before tokens.
|
|
241
|
+
await self._emit_agent_text(ctx, "", agent_name=name)
|
|
274
242
|
|
|
275
243
|
async def _emit_step_sep(self, ctx: Context, node_id: str):
|
|
276
244
|
try:
|
|
277
|
-
|
|
245
|
+
a = self.fs.agents.get(node_id)
|
|
246
|
+
friendly_name = (a.name if a and a.name else node_id)
|
|
247
|
+
await self._emit(
|
|
248
|
+
ctx,
|
|
249
|
+
StepEvent(
|
|
250
|
+
name="next",
|
|
251
|
+
index=self._steps,
|
|
252
|
+
total=self.max_iterations,
|
|
253
|
+
meta={
|
|
254
|
+
"node": node_id,
|
|
255
|
+
"agent_name": friendly_name, # pass current agent display name
|
|
256
|
+
},
|
|
257
|
+
),
|
|
258
|
+
)
|
|
278
259
|
except Exception as e:
|
|
279
260
|
self.logger.error(f"[event] StepEvent emit failed: {e}")
|
|
280
261
|
|
|
@@ -335,6 +316,9 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
335
316
|
return user_msg, [], "no-mem:last_output"
|
|
336
317
|
|
|
337
318
|
async def _update_memory_after_step(self, node_id: str, user_msg_text: str, display_text: str):
|
|
319
|
+
"""
|
|
320
|
+
Update per-node memory after a step, storing baton user message and assistant output.
|
|
321
|
+
"""
|
|
338
322
|
mem_id = self.g.agent_to_memory.get(node_id)
|
|
339
323
|
mem_state = self.mem.get(mem_id) if mem_id else None
|
|
340
324
|
if not mem_state:
|
|
@@ -359,7 +343,7 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
359
343
|
# ============== Workflow steps ==============
|
|
360
344
|
|
|
361
345
|
def run(self, query: str, ctx: Optional[Context] = None, memory: Any = None, verbose: bool = False, on_stop=None):
|
|
362
|
-
"""Entry point used by
|
|
346
|
+
"""Entry point used by LlamaWorkflow runner."""
|
|
363
347
|
self._on_stop = on_stop
|
|
364
348
|
|
|
365
349
|
# Build initial chat once
|
|
@@ -429,8 +413,9 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
429
413
|
return FlowTickEvent() if self._current_ids else FlowStopEvent(final_answer=self._last_plain_output or "")
|
|
430
414
|
|
|
431
415
|
node: AgentNode = self.fs.agents[current_id]
|
|
432
|
-
|
|
433
|
-
|
|
416
|
+
|
|
417
|
+
# IMPORTANT: emit StepEvent also for the very first agent step.
|
|
418
|
+
await self._emit_step_sep(ctx, current_id)
|
|
434
419
|
await self._emit_header(ctx, node.name or current_id)
|
|
435
420
|
|
|
436
421
|
# Resolve runtime + per-node LLM/tools
|
|
@@ -459,11 +444,10 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
459
444
|
f"user='{ellipsize(user_msg_text, self.dbg.preview_chars)}'"
|
|
460
445
|
)
|
|
461
446
|
|
|
462
|
-
#
|
|
447
|
+
# Build agent
|
|
463
448
|
allowed_routes_now = list(node.outputs or [])
|
|
464
|
-
friendly_map = self.
|
|
449
|
+
friendly_map = {rid: self.fs.agents.get(rid).name or rid for rid in allowed_routes_now if rid in self.fs.agents}
|
|
465
450
|
|
|
466
|
-
# Build agent (chat_history/max_iterations in ctor – best practice)
|
|
467
451
|
built = self.factory.build(
|
|
468
452
|
node=node,
|
|
469
453
|
node_runtime=node_rt,
|
|
@@ -513,9 +497,6 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
513
497
|
display_text = decision.content or ""
|
|
514
498
|
if display_text:
|
|
515
499
|
await self._emit_agent_text(ctx, display_text, agent_name=(node.name or current_id))
|
|
516
|
-
if self.dbg.log_memory_dump:
|
|
517
|
-
self.logger.debug(f"[mem.prep] node={current_id} save user='{ellipsize(user_msg_text, self.dbg.preview_chars)}' "
|
|
518
|
-
f"assist='{ellipsize(display_text, self.dbg.preview_chars)}'")
|
|
519
500
|
await self._update_memory_after_step(current_id, user_msg_text, display_text)
|
|
520
501
|
next_id = decision.route if decision.valid else (allowed_routes[0] if allowed_routes else None)
|
|
521
502
|
if self.dbg.log_routes:
|
|
@@ -526,9 +507,6 @@ class DynamicFlowWorkflowLI(Workflow):
|
|
|
526
507
|
display_text = raw_text_clean or ""
|
|
527
508
|
if display_text:
|
|
528
509
|
await self._emit_agent_text(ctx, display_text, agent_name=(node.name or current_id))
|
|
529
|
-
if self.dbg.log_memory_dump:
|
|
530
|
-
self.logger.debug(f"[mem.prep] node={current_id} save user='{ellipsize(user_msg_text, self.dbg.preview_chars)}' "
|
|
531
|
-
f"assist='{ellipsize(display_text, self.dbg.preview_chars)}'")
|
|
532
510
|
await self._update_memory_after_step(current_id, user_msg_text, display_text)
|
|
533
511
|
outs = self.g.get_next(current_id)
|
|
534
512
|
next_id = outs[0] if outs else self.g.first_connected_end(current_id)
|