pygpt-net 2.6.0.post2__py3-none-any.whl → 2.6.2__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 (98) hide show
  1. pygpt_net/CHANGELOG.txt +8 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +27 -9
  4. pygpt_net/controller/chat/response.py +10 -4
  5. pygpt_net/controller/chat/stream.py +40 -2
  6. pygpt_net/controller/model/editor.py +45 -4
  7. pygpt_net/controller/plugins/plugins.py +25 -0
  8. pygpt_net/controller/presets/editor.py +100 -100
  9. pygpt_net/controller/presets/experts.py +20 -1
  10. pygpt_net/controller/presets/presets.py +5 -4
  11. pygpt_net/controller/ui/mode.py +17 -66
  12. pygpt_net/core/agents/provider.py +2 -1
  13. pygpt_net/core/agents/runner.py +123 -9
  14. pygpt_net/core/agents/runners/helpers.py +3 -2
  15. pygpt_net/core/agents/runners/llama_workflow.py +176 -22
  16. pygpt_net/core/agents/runners/loop.py +22 -13
  17. pygpt_net/core/experts/experts.py +19 -25
  18. pygpt_net/core/idx/chat.py +24 -34
  19. pygpt_net/core/idx/response.py +5 -2
  20. pygpt_net/core/locale/locale.py +73 -45
  21. pygpt_net/core/render/web/body.py +152 -207
  22. pygpt_net/core/render/web/renderer.py +4 -2
  23. pygpt_net/data/config/config.json +3 -3
  24. pygpt_net/data/config/models.json +3 -3
  25. pygpt_net/data/locale/locale.de.ini +12 -8
  26. pygpt_net/data/locale/locale.en.ini +12 -8
  27. pygpt_net/data/locale/locale.es.ini +12 -8
  28. pygpt_net/data/locale/locale.fr.ini +12 -8
  29. pygpt_net/data/locale/locale.it.ini +12 -8
  30. pygpt_net/data/locale/locale.pl.ini +12 -8
  31. pygpt_net/data/locale/locale.uk.ini +12 -8
  32. pygpt_net/data/locale/locale.zh.ini +12 -8
  33. pygpt_net/item/ctx.py +2 -1
  34. pygpt_net/plugin/base/plugin.py +35 -3
  35. pygpt_net/plugin/bitbucket/__init__.py +12 -0
  36. pygpt_net/plugin/bitbucket/config.py +267 -0
  37. pygpt_net/plugin/bitbucket/plugin.py +125 -0
  38. pygpt_net/plugin/bitbucket/worker.py +569 -0
  39. pygpt_net/plugin/cmd_files/worker.py +19 -16
  40. pygpt_net/plugin/facebook/__init__.py +12 -0
  41. pygpt_net/plugin/facebook/config.py +359 -0
  42. pygpt_net/plugin/facebook/plugin.py +114 -0
  43. pygpt_net/plugin/facebook/worker.py +698 -0
  44. pygpt_net/plugin/github/__init__.py +12 -0
  45. pygpt_net/plugin/github/config.py +441 -0
  46. pygpt_net/plugin/github/plugin.py +124 -0
  47. pygpt_net/plugin/github/worker.py +674 -0
  48. pygpt_net/plugin/google/__init__.py +12 -0
  49. pygpt_net/plugin/google/config.py +367 -0
  50. pygpt_net/plugin/google/plugin.py +126 -0
  51. pygpt_net/plugin/google/worker.py +826 -0
  52. pygpt_net/plugin/slack/__init__.py +12 -0
  53. pygpt_net/plugin/slack/config.py +349 -0
  54. pygpt_net/plugin/slack/plugin.py +116 -0
  55. pygpt_net/plugin/slack/worker.py +639 -0
  56. pygpt_net/plugin/telegram/__init__.py +12 -0
  57. pygpt_net/plugin/telegram/config.py +308 -0
  58. pygpt_net/plugin/telegram/plugin.py +118 -0
  59. pygpt_net/plugin/telegram/worker.py +563 -0
  60. pygpt_net/plugin/twitter/__init__.py +12 -0
  61. pygpt_net/plugin/twitter/config.py +491 -0
  62. pygpt_net/plugin/twitter/plugin.py +126 -0
  63. pygpt_net/plugin/twitter/worker.py +837 -0
  64. pygpt_net/provider/agents/base.py +4 -1
  65. pygpt_net/provider/agents/llama_index/codeact_workflow.py +95 -0
  66. pygpt_net/provider/agents/llama_index/legacy/__init__.py +0 -0
  67. pygpt_net/provider/agents/llama_index/{openai.py → legacy/openai.py} +2 -2
  68. pygpt_net/provider/agents/llama_index/{openai_assistant.py → legacy/openai_assistant.py} +37 -5
  69. pygpt_net/provider/agents/llama_index/{planner.py → legacy/planner.py} +3 -3
  70. pygpt_net/provider/agents/llama_index/{react.py → legacy/react.py} +3 -3
  71. pygpt_net/provider/agents/llama_index/openai_workflow.py +52 -0
  72. pygpt_net/provider/agents/llama_index/planner_workflow.py +115 -0
  73. pygpt_net/provider/agents/llama_index/react_workflow.py +6 -4
  74. pygpt_net/provider/agents/llama_index/workflow/__init__.py +0 -0
  75. pygpt_net/provider/agents/llama_index/{codeact_agent_custom.py → workflow/codeact.py} +124 -8
  76. pygpt_net/provider/agents/llama_index/workflow/events.py +24 -0
  77. pygpt_net/provider/agents/llama_index/workflow/openai.py +634 -0
  78. pygpt_net/provider/agents/llama_index/workflow/planner.py +601 -0
  79. pygpt_net/provider/agents/openai/agent.py +1 -0
  80. pygpt_net/provider/agents/openai/agent_b2b.py +2 -0
  81. pygpt_net/provider/agents/openai/agent_planner.py +1 -0
  82. pygpt_net/provider/agents/openai/agent_with_experts.py +1 -0
  83. pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +1 -0
  84. pygpt_net/provider/agents/openai/agent_with_feedback.py +1 -0
  85. pygpt_net/provider/agents/openai/evolve.py +1 -0
  86. pygpt_net/provider/core/preset/patch.py +11 -17
  87. pygpt_net/ui/base/config_dialog.py +4 -0
  88. pygpt_net/ui/dialog/preset.py +34 -77
  89. pygpt_net/ui/layout/toolbox/presets.py +2 -2
  90. pygpt_net/ui/main.py +3 -1
  91. pygpt_net/ui/widget/lists/experts.py +3 -2
  92. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.2.dist-info}/METADATA +155 -4
  93. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.2.dist-info}/RECORD +96 -62
  94. pygpt_net/data/config/presets/agent_react_workflow.json +0 -34
  95. pygpt_net/provider/agents/llama_index/code_act.py +0 -58
  96. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.2.dist-info}/LICENSE +0 -0
  97. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.2.dist-info}/WHEEL +0 -0
  98. {pygpt_net-2.6.0.post2.dist-info → pygpt_net-2.6.2.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.11 00:00:00 #
9
+ # Updated Date: 2025.08.13 16:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -880,56 +880,7 @@ class Body:
880
880
  </html>
881
881
  """
882
882
 
883
- def __init__(self, window=None):
884
- """
885
- HTML Body
886
-
887
- :param window: Window instance
888
- """
889
- self.window = window
890
- self.highlight = SyntaxHighlight(window)
891
- self._tip_keys = tuple(f"output.tips.{i}" for i in range(1, self.NUM_TIPS + 1))
892
-
893
-
894
- def is_timestamp_enabled(self) -> bool:
895
- """
896
- Check if timestamp is enabled
897
-
898
- :return: True if timestamp is enabled
899
- """
900
- return self.window.core.config.get('output_timestamp')
901
-
902
- def prepare_styles(self) -> str:
903
- """
904
- Prepare CSS styles
905
-
906
- :return: CSS styles
907
- """
908
- syntax_dark = [
909
- "dracula",
910
- "fruity",
911
- "github-dark",
912
- "gruvbox-dark",
913
- "inkpot",
914
- "material",
915
- "monokai",
916
- "native",
917
- "nord",
918
- "nord-darker",
919
- "one-dark",
920
- "paraiso-dark",
921
- "rrt",
922
- "solarized-dark",
923
- "stata-dark",
924
- "vim",
925
- "zenburn",
926
- ]
927
- syntax_style = self.window.core.config.get("render.code_syntax")
928
- if syntax_style is None or syntax_style == "":
929
- syntax_style = "default"
930
- fonts_path = os.path.join(self.window.core.config.get_app_path(), "data", "fonts").replace("\\", "/")
931
- # loader
932
- stylesheet = """
883
+ _SPINNER = """
933
884
  .lds-ring {
934
885
  /* change color here */
935
886
  color: #1c4c5b
@@ -974,38 +925,74 @@ class Body:
974
925
  }
975
926
  }
976
927
  """
977
- stylesheet += self.window.controller.theme.markdown.get_web_css().replace('%fonts%', fonts_path)
978
- if syntax_style in syntax_dark:
979
- stylesheet += "pre { color: #fff; }"
980
- else:
981
- stylesheet += "pre { color: #000; }"
982
928
 
983
- return stylesheet + " " + self.highlight.get_style_defs()
929
+ def __init__(self, window=None):
930
+ """
931
+ HTML Body
984
932
 
985
- def prepare_action_icons(
986
- self,
987
- ctx: CtxItem
988
- ) -> str:
933
+ :param window: Window instance
934
+ """
935
+ self.window = window
936
+ self.highlight = SyntaxHighlight(window)
937
+ self._tip_keys = tuple(f"output.tips.{i}" for i in range(1, self.NUM_TIPS + 1))
938
+ self._syntax_dark = (
939
+ "dracula",
940
+ "fruity",
941
+ "github-dark",
942
+ "gruvbox-dark",
943
+ "inkpot",
944
+ "material",
945
+ "monokai",
946
+ "native",
947
+ "nord",
948
+ "nord-darker",
949
+ "one-dark",
950
+ "paraiso-dark",
951
+ "rrt",
952
+ "solarized-dark",
953
+ "stata-dark",
954
+ "vim",
955
+ "zenburn",
956
+ )
957
+
958
+
959
+ def is_timestamp_enabled(self) -> bool:
960
+ """
961
+ Check if timestamp is enabled
962
+
963
+ :return: True if timestamp is enabled
964
+ """
965
+ return self.window.core.config.get('output_timestamp')
966
+
967
+ def prepare_styles(self) -> str:
968
+ """
969
+ Prepare CSS styles
970
+
971
+ :return: CSS styles
972
+ """
973
+ cfg = self.window.core.config
974
+ fonts_path = os.path.join(cfg.get_app_path(), "data", "fonts").replace("\\", "/")
975
+ syntax_style = self.window.core.config.get("render.code_syntax") or "default"
976
+
977
+ theme_css = self.window.controller.theme.markdown.get_web_css().replace('%fonts%', fonts_path)
978
+ parts = [self._SPINNER, theme_css]
979
+ parts.append("pre { color: #fff; }" if syntax_style in self._syntax_dark else "pre { color: #000; }")
980
+ parts.append(self.highlight.get_style_defs()) # highlight style
981
+ return "\n".join(parts)
982
+
983
+ def prepare_action_icons(self, ctx: CtxItem) -> str:
989
984
  """
990
985
  Append action icons
991
986
 
992
987
  :param ctx: context item
993
988
  :return: HTML code
994
989
  """
995
- html = ""
996
- show_edit = True
997
- # show_edit = self.window.core.config.get('ctx.edit_icons')
998
- icons_html = "".join(self.get_action_icons(ctx, all=show_edit))
999
- if icons_html != "":
1000
- extra = "<div class=\"action-icons\" data-id=\"{}\">{}</div>".format(ctx.id, icons_html)
1001
- html += extra
1002
- return html
990
+ icons_html = "".join(self.get_action_icons(ctx, all=True))
991
+ if icons_html:
992
+ return f'<div class="action-icons" data-id="{ctx.id}">{icons_html}</div>'
993
+ return ""
1003
994
 
1004
- def get_action_icons(
1005
- self,
1006
- ctx: CtxItem,
1007
- all: bool = False
1008
- ) -> List[str]:
995
+ def get_action_icons(self, ctx: CtxItem, all: bool = False) -> List[str]:
1009
996
  """
1010
997
  Get action icons for context item
1011
998
 
@@ -1013,58 +1000,41 @@ class Body:
1013
1000
  :param all: True to show all icons
1014
1001
  :return: list of icons
1015
1002
  """
1016
- icons = []
1003
+ icons: List[str] = []
1004
+ if ctx.output:
1005
+ cid = ctx.id
1006
+ t = trans
1017
1007
 
1018
- # audio read
1019
- if ctx.output is not None and ctx.output != "":
1008
+ # audio read
1020
1009
  icons.append(
1021
- '<a href="extra-audio-read:{}" class="action-icon" data-id="{}" role="button"><span class="cmd">{}</span></a>'.format(
1022
- ctx.id,
1023
- ctx.id,
1024
- self.get_icon("volume", trans("ctx.extra.audio"), ctx)
1025
- )
1010
+ f'<a href="extra-audio-read:{cid}" class="action-icon" data-id="{cid}" role="button">'
1011
+ f'<span class="cmd">{self.get_icon("volume", t("ctx.extra.audio"), ctx)}</span></a>'
1026
1012
  )
1027
1013
  # copy ctx
1028
1014
  icons.append(
1029
- '<a href="extra-copy:{}" class="action-icon" data-id="{}" role="button"><span class="cmd">{}</span></a>'.format(
1030
- ctx.id,
1031
- ctx.id,
1032
- self.get_icon("copy", trans("ctx.extra.copy"), ctx)
1033
- )
1015
+ f'<a href="extra-copy:{cid}" class="action-icon" data-id="{cid}" role="button">'
1016
+ f'<span class="cmd">{self.get_icon("copy", t("ctx.extra.copy"), ctx)}</span></a>'
1034
1017
  )
1035
1018
  # regen link
1036
1019
  icons.append(
1037
- '<a href="extra-replay:{}" class="action-icon" data-id="{}" role="button"><span class="cmd">{}</span></a>'.format(
1038
- ctx.id,
1039
- ctx.id,
1040
- self.get_icon("reload", trans("ctx.extra.reply"), ctx)
1041
- )
1020
+ f'<a href="extra-replay:{cid}" class="action-icon" data-id="{cid}" role="button">'
1021
+ f'<span class="cmd">{self.get_icon("reload", t("ctx.extra.reply"), ctx)}</span></a>'
1042
1022
  )
1043
1023
  # edit link
1044
1024
  icons.append(
1045
- '<a href="extra-edit:{}" class="action-icon edit-icon" data-id="{}" role="button"><span class="cmd">{}</span></a>'.format(
1046
- ctx.id,
1047
- ctx.id,
1048
- self.get_icon("edit", trans("ctx.extra.edit"), ctx)
1049
- )
1025
+ f'<a href="extra-edit:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button">'
1026
+ f'<span class="cmd">{self.get_icon("edit", t("ctx.extra.edit"), ctx)}</span></a>'
1050
1027
  )
1051
1028
  # delete link
1052
1029
  icons.append(
1053
- '<a href="extra-delete:{}" class="action-icon edit-icon" data-id="{}" role="button"><span class="cmd">{}</span></a>'.format(
1054
- ctx.id,
1055
- ctx.id,
1056
- self.get_icon("delete", trans("ctx.extra.delete"), ctx)
1057
- )
1030
+ f'<a href="extra-delete:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button">'
1031
+ f'<span class="cmd">{self.get_icon("delete", t("ctx.extra.delete"), ctx)}</span></a>'
1058
1032
  )
1059
-
1060
1033
  # join link
1061
- if not self.window.core.ctx.is_first_item(ctx.id):
1034
+ if not self.window.core.ctx.is_first_item(cid):
1062
1035
  icons.append(
1063
- '<a href="extra-join:{}" class="action-icon edit-icon" data-id="{}" role="button"><span class="cmd">{}</span></a>'.format(
1064
- ctx.id,
1065
- ctx.id,
1066
- self.get_icon("playlist_add", trans("ctx.extra.join"), ctx)
1067
- )
1036
+ f'<a href="extra-join:{cid}" class="action-icon edit-icon" data-id="{cid}" role="button">'
1037
+ f'<span class="cmd">{self.get_icon("playlist_add", t("ctx.extra.join"), ctx)}</span></a>'
1068
1038
  )
1069
1039
  return icons
1070
1040
 
@@ -1082,9 +1052,11 @@ class Body:
1082
1052
  :param item: context item
1083
1053
  :return: icon HTML
1084
1054
  """
1085
- icon = os.path.join(self.window.core.config.get_app_path(), "data", "icons", icon + ".svg")
1086
- return '<img src="file://{}" class="action-img" title="{}" alt="{}" data-id="{}">'.format(
1087
- icon, title, title, item.id)
1055
+ app_path = self.window.core.config.get_app_path()
1056
+ icon_path = os.path.join(app_path, "data", "icons", f"{icon}.svg")
1057
+ return (
1058
+ f'<img src="file://{icon_path}" class="action-img" title="{title}" alt="{title}" data-id="{item.id}">'
1059
+ )
1088
1060
 
1089
1061
  def get_image_html(
1090
1062
  self,
@@ -1100,18 +1072,16 @@ class Body:
1100
1072
  :param num_all: number of all images
1101
1073
  :return: HTML code
1102
1074
  """
1103
- num_str = ""
1104
- if num is not None and num_all is not None and num_all > 1:
1105
- num_str = " [{}]".format(num)
1106
1075
  url, path = self.window.core.filesystem.extract_local_url(url)
1107
1076
  basename = os.path.basename(path)
1108
- return """<div class="extra-src-img-box" title="{url}"><div class="img-outer"><div class="img-wrapper"><a href="{url}"><img src="{path}" class="image"></a></div>
1109
- <a href="{url}" class="title">{title}</a></div></div>""". \
1110
- format(prefix=trans('chat.prefix.img'),
1111
- url=url,
1112
- title=basename,
1113
- path=path,
1114
- num=num_str)
1077
+ return (
1078
+ f'<div class="extra-src-img-box" title="{url}">'
1079
+ f'<div class="img-outer"><div class="img-wrapper">'
1080
+ f'<a href="{url}"><img src="{path}" class="image"></a>'
1081
+ f'</div>'
1082
+ f'<a href="{url}" class="title">{basename}</a>'
1083
+ f'</div></div>'
1084
+ )
1115
1085
 
1116
1086
  def get_url_html(
1117
1087
  self,
@@ -1127,19 +1097,11 @@ class Body:
1127
1097
  :param num_all: number of all URLs
1128
1098
  :return: HTML code
1129
1099
  """
1130
- icon_path = os.path.join(
1131
- self.window.core.config.get_app_path(),
1132
- "data", "icons", "language.svg"
1133
- )
1134
- icon = '<img src="file://{}" class="extra-src-icon">'.format(icon_path)
1135
- num_str = ""
1136
- if num is not None and num_all is not None and num_all > 1:
1137
- num_str = " [{}]".format(num)
1138
- return """{icon}<a href="{url}" title="{url}">{url}</a> <small>{num}</small>""". \
1139
- format(url=url,
1140
- num=num_str,
1141
- icon=icon,
1142
- )
1100
+ app_path = self.window.core.config.get_app_path()
1101
+ icon_path = os.path.join(app_path, "data", "icons", "language.svg").replace("\\", "/")
1102
+ icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
1103
+ num_str = f" [{num}]" if (num is not None and num_all is not None and num_all > 1) else ""
1104
+ return f'{icon}<a href="{url}" title="{url}">{url}</a> <small>{num_str}</small>'
1143
1105
 
1144
1106
  def get_docs_html(self, docs: List[Dict]) -> str:
1145
1107
  """
@@ -1148,45 +1110,34 @@ class Body:
1148
1110
  :param docs: list of document metadata
1149
1111
  :return: HTML code
1150
1112
  """
1151
- html = ""
1152
- html_sources = ""
1113
+ html_parts: List[str] = []
1114
+ src_parts: List[str] = []
1153
1115
  num = 1
1154
- max = 3
1116
+ limit = 3
1117
+
1155
1118
  try:
1156
1119
  for item in docs:
1157
1120
  for uuid, doc_json in item.items():
1158
- """
1159
- Example doc (file metadata):
1160
- {'a2c7af6d-3c34-4c28-bf2d-6161e7fb525e': {
1161
- 'file_path': '/home/user/.config/pygpt-net/data/my_cars.txt',
1162
- 'file_name': '/home/user/.config/pygpt-net/data/my_cars.txt', 'file_type': 'text/plain',
1163
- 'file_size': 28, 'creation_date': '2024-03-03', 'last_modified_date': '2024-03-03',
1164
- 'last_accessed_date': '2024-03-03'}}
1165
- """
1166
- doc_parts = []
1167
- for key in doc_json:
1168
- doc_parts.append("<b>{}:</b> {}".format(key, doc_json[key]))
1169
- html_sources += "<p><small>[{}] {}: {}</small></p>".format(num, uuid, ", ".join(doc_parts))
1121
+ entries = ", ".join(f"<b>{k}:</b> {doc_json[k]}" for k in doc_json)
1122
+ src_parts.append(f"<p><small>[{num}] {uuid}: {entries}</small></p>")
1170
1123
  num += 1
1171
- if num >= max:
1124
+ if num >= limit:
1125
+ break
1126
+ if num >= limit:
1172
1127
  break
1173
- except Exception as e:
1128
+ except Exception:
1174
1129
  pass
1175
1130
 
1176
- icon_path = os.path.join(
1177
- self.window.core.config.get_app_path(),
1178
- "data", "icons", "db.svg"
1179
- )
1180
- icon = '<img src="file://{}" class="extra-src-icon">'.format(icon_path)
1181
- if html_sources != "":
1182
- html += "<p>{icon}<small><b>{prefix}:</b></small></p>".format(
1183
- prefix=trans('chat.prefix.doc'),
1184
- icon=icon,
1185
- )
1186
- html += "<div class=\"cmd\">"
1187
- html += "<p>" + html_sources + "</p>"
1188
- html += "</div> "
1189
- return html
1131
+ if src_parts:
1132
+ app_path = self.window.core.config.get_app_path()
1133
+ icon_path = os.path.join(app_path, "data", "icons", "db.svg").replace("\\", "/")
1134
+ icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
1135
+ html_parts.append(f'<p>{icon}<small><b>{trans("chat.prefix.doc")}:</b></small></p>')
1136
+ html_parts.append('<div class="cmd">')
1137
+ html_parts.append(f"<p>{''.join(src_parts)}</p>")
1138
+ html_parts.append("</div> ")
1139
+
1140
+ return "".join(html_parts)
1190
1141
 
1191
1142
  def get_file_html(
1192
1143
  self,
@@ -1202,21 +1153,12 @@ class Body:
1202
1153
  :param num_all: number of all files
1203
1154
  :return: HTML code
1204
1155
  """
1205
- icon_path = os.path.join(
1206
- self.window.core.config.get_app_path(),
1207
- "data", "icons", "attachments.svg"
1208
- )
1209
- icon = '<img src="file://{}" class="extra-src-icon">'.format(icon_path)
1210
- num_str = ""
1211
- if num is not None and num_all is not None and num_all > 1:
1212
- num_str = " [{}]".format(num)
1156
+ app_path = self.window.core.config.get_app_path()
1157
+ icon_path = os.path.join(app_path, "data", "icons", "attachments.svg").replace("\\", "/")
1158
+ icon = f'<img src="file://{icon_path}" class="extra-src-icon">'
1159
+ num_str = f" [{num}]" if (num is not None and num_all is not None and num_all > 1) else ""
1213
1160
  url, path = self.window.core.filesystem.extract_local_url(url)
1214
- return """{icon} <b>{num}</b> <a href="{url}">{path}</a>""". \
1215
- format(url=url,
1216
- path=path,
1217
- num=num_str,
1218
- icon=icon,
1219
- )
1161
+ return f'{icon} <b>{num_str}</b> <a href="{url}">{path}</a>'
1220
1162
 
1221
1163
  def prepare_tool_extra(self, ctx: CtxItem) -> str:
1222
1164
  """
@@ -1225,40 +1167,43 @@ class Body:
1225
1167
  :param ctx: context item
1226
1168
  :return: HTML code
1227
1169
  """
1228
- html = ""
1229
- if ctx.extra is not None and ctx.extra != "":
1230
- html += "<div class=\"msg-extra\">"
1170
+ extra = ctx.extra
1171
+ if not extra:
1172
+ return ""
1173
+
1174
+ parts: List[str] = ['<div class="msg-extra">']
1175
+
1176
+ # single tool
1177
+ if "plugin" in extra:
1178
+ event = Event(Event.TOOL_OUTPUT_RENDER, {
1179
+ 'tool': extra["plugin"],
1180
+ 'html': '',
1181
+ 'multiple': False,
1182
+ 'content': extra, # tool output
1183
+ })
1184
+ event.ctx = ctx
1185
+ self.window.dispatch(event, all=True) # handle by plugins
1186
+ if event.data['html']:
1187
+ parts.append(f'<div class="tool-output-block">{event.data["html"]}</div>')
1231
1188
 
1232
- # single tool
1233
- if "plugin" in ctx.extra:
1189
+ # multiple tools, list
1190
+ elif "tool_output" in extra and isinstance(extra["tool_output"], list):
1191
+ for tool in extra["tool_output"]:
1192
+ if "plugin" not in tool:
1193
+ continue
1234
1194
  event = Event(Event.TOOL_OUTPUT_RENDER, {
1235
- 'tool': ctx.extra["plugin"],
1195
+ 'tool': tool["plugin"],
1236
1196
  'html': '',
1237
- 'multiple': False,
1238
- 'content': ctx.extra, # tool output
1197
+ 'multiple': True,
1198
+ 'content': tool, # tool output[]
1239
1199
  })
1240
1200
  event.ctx = ctx
1241
- self.window.dispatch(event, all=True) # handle by plugins
1201
+ self.window.dispatch(event, all=True)
1242
1202
  if event.data['html']:
1243
- html += "<div class=\"tool-output-block\">" + event.data['html'] + "</div>"
1203
+ parts.append(f'<div class="tool-output-block">{event.data["html"]}</div>')
1244
1204
 
1245
- # multiple tools, list
1246
- elif "tool_output" in ctx.extra and isinstance(ctx.extra["tool_output"], list):
1247
- for key, tool in enumerate(ctx.extra["tool_output"]):
1248
- if "plugin" not in tool:
1249
- continue
1250
- event = Event(Event.TOOL_OUTPUT_RENDER, {
1251
- 'tool': tool["plugin"],
1252
- 'html': '',
1253
- 'multiple': True,
1254
- 'content': tool, # tool output[]
1255
- })
1256
- event.ctx = ctx
1257
- self.window.dispatch(event, all=True)
1258
- if event.data['html']:
1259
- html += "<div class=\"tool-output-block\">" + event.data['html'] + "</div>"
1260
- html += "</div>"
1261
- return html
1205
+ parts.append("</div>")
1206
+ return "".join(parts)
1262
1207
 
1263
1208
  def get_all_tips(self) -> str:
1264
1209
  """
@@ -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.12 19:00:00 #
9
+ # Updated Date: 2025.08.14 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import html
@@ -523,10 +523,12 @@ class Renderer(BaseRenderer):
523
523
  html = self.parser.parse(parse_buffer)
524
524
 
525
525
  code_endings = ("</code></pre></div>", "</code></pre></div><br/>", "</code></pre></div><br>")
526
+ list_endings = ("</ul>", "</ol>", "</li>")
526
527
  is_code_block = html.endswith(code_endings)
528
+ is_list_block = html.endswith(list_endings)
527
529
 
528
530
  newline_in_chunk = "\n" in raw_chunk
529
- is_newline = newline_in_chunk or buffer.endswith("\n") or is_code_block
531
+ is_newline = newline_in_chunk or buffer.endswith("\n") or is_code_block or is_list_block
530
532
 
531
533
  force_replace = self.prev_chunk_newline
532
534
  self.prev_chunk_newline = newline_in_chunk
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.0",
4
- "app.version": "2.6.0",
5
- "updated_at": "2025-08-13T00:00:00"
3
+ "version": "2.6.2",
4
+ "app.version": "2.6.2",
5
+ "updated_at": "2025-08-15T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.0",
4
- "app.version": "2.6.0",
5
- "updated_at": "2025-08-13T23:07:35"
3
+ "version": "2.6.2",
4
+ "app.version": "2.6.2",
5
+ "updated_at": "2025-08-15T23:07:35"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -472,7 +472,7 @@ dictionary.plugin.cmd_api.cmds.get_params = GET-Params: Liste, durch Komma getre
472
472
  dictionary.plugin.cmd_api.cmds.headers = Headers: stellen Sie das JSON-Objekt mit einem Wörterbuch zusätzlicher Anforderungsheader bereit, wie Autorisierung, API-Schlüssel, etc.
473
473
  dictionary.plugin.cmd_api.cmds.instruction = Instruktion: Beschreibung für das Modell, wann und wie dieser API-Aufruf zu verwenden ist
474
474
  dictionary.plugin.cmd_api.cmds.name = Name: eindeutiger API-Aufrufname (ID)
475
- dictionary.plugin.cmd_api.cmds.post_json = POST JSON: geben Sie das JSON-Objekt, Vorlage zum Senden in POST JSON-Anforderung an, verwenden Sie %%param%% als POST-Param-Platzhalter
475
+ dictionary.plugin.cmd_api.cmds.post_json = POST JSON: geben Sie das JSON-Objekt, Vorlage zum Senden in POST JSON-Anforderung an, verwenden Sie %param% als POST-Param-Platzhalter
476
476
  dictionary.plugin.cmd_api.cmds.post_params = POST-Params: Liste, durch Komma getrennt, POST-Params, die in der POST-Anforderung gesendet werden sollen
477
477
  dictionary.plugin.cmd_api.cmds.type = Anfrage-Typ: verwenden Sie GET für einfache GET-Anforderung, POST zum Senden codierter POST-Parameter oder POST_JSON zum Senden eines JSON-codierten Objekts als Body
478
478
  dictionary.plugin.cmd_custom.cmds.cmd = Befehl ausführen
@@ -814,6 +814,8 @@ mode.langchain.tooltip = Chat mit Modellen von Langchain
814
814
  model.ctx = Kontext-Token
815
815
  model.ctx.desc = Max. Modell-Eingabe-Token
816
816
  model.default = Standard im Modus
817
+ model.extra = Zusätzliche Parameter (JSON)
818
+ model.extra.desc = Ein JSON-Objekt, das zusätzliche Parameter für das Modell enthält (wie z.B. den intellektuellen Aufwand usw.).
817
819
  model.id = Modell-ID
818
820
  model.input = Eingabe
819
821
  mode.llama_index = Chat mit Dateien
@@ -964,8 +966,10 @@ preset.prompt.rename = Umbenennen
964
966
  preset.prompt.save_custom = Als Meine Vorlage speichern
965
967
  preset.prompt.use = Einfügen...
966
968
  preset.research = Forschung
969
+ preset.tab.experts = Experten
967
970
  preset.tab.general = Allgemein
968
971
  preset.tab.personalize = Personalisieren
972
+ preset.tab.remote_tools = Remote-Tools
969
973
  preset.temperature = Temperatur
970
974
  preset.tool.function = Funktionen
971
975
  preset.tool.function.tip.agent_llama = Tipp: Funktionen aus Plugins werden automatisch aktiviert.
@@ -1225,8 +1229,8 @@ settings.prompt.agent.goal = Agent: Zielaktualisierung
1225
1229
  settings.prompt.agent.goal.desc = Anweisung zur Aktualisierung des aktuellen Zielstatus
1226
1230
  settings.prompt.agent.instruction = Agent: Systeminstruktion
1227
1231
  settings.prompt.agent.instruction.desc = Aufforderung zur Anleitung, wie man den autonomen Modus handhabtn, wenn die Option Schleife / bewerten aktiviert ist
1228
- settings.prompt.agent.llama.eval = Agent: Bewertungseingabe in der Schleife [LlamaIndex] - %% bewertung
1229
- settings.prompt.agent.llama.eval.complete = Agent: Bewertungseingabe in der Schleife [LlamaIndex] - %% abschluss
1232
+ settings.prompt.agent.llama.eval = Agent: Bewertungseingabe in der Schleife [LlamaIndex] - % bewertung
1233
+ settings.prompt.agent.llama.eval.complete = Agent: Bewertungseingabe in der Schleife [LlamaIndex] - % abschluss
1230
1234
  settings.prompt.agent.llama.eval.complete.desc = Eingabeaufforderung zur Bewertung der Antwort, wenn die Schleifen-/Bewertungsoption aktiviert ist (prozent)
1231
1235
  settings.prompt.agent.llama.eval.desc = Eingabeaufforderung zur Bewertung der Antwort, wenn die Schleifen-/Bewertungsoption aktiviert ist (bewertung)
1232
1236
  settings.prompt.cmd = Befehlsausführung: Anweisung
@@ -1317,7 +1321,7 @@ settings.vision.capture.enabled = Kamera
1317
1321
  settings.vision.capture.height = Kamerabildhöhe (px)
1318
1322
  settings.vision.capture.idx = Kameraindex (Nummer)
1319
1323
  settings.vision.capture.idx.desc = Kamera-Index zur Videoaufzeichnung (Index der Kamera, Standard: 0)
1320
- settings.vision.capture.quality = Bildqualität beim Erfassen (%%)
1324
+ settings.vision.capture.quality = Bildqualität beim Erfassen (%)
1321
1325
  settings.vision.capture.width = Kamerabildbreite (px)
1322
1326
  settings.zoom = Zoomen des Chat-Ausgabefensters
1323
1327
  speech.enable = Sprachausgabe aktivieren
@@ -1380,10 +1384,10 @@ toolbox.agent.auto_stop.label = Automatischer Stopp
1380
1384
  toolbox.agent.continue.label = Immer weiter...
1381
1385
  toolbox.agent.iterations.label = Max. Anzahl der Schritte (Iterationen, 0 = Unendlichkeit)
1382
1386
  toolbox.agent.llama.loop.enabled.label = Schleife
1383
- toolbox.agent.llama.loop.label = Schleife / bewerten (bis Punktzahl, 0%% = unendlich)
1384
- toolbox.agent.llama.loop.mode.complete = Min. %% abschluss
1385
- toolbox.agent.llama.loop.mode.score = Min. %% bewertung
1386
- toolbox.agent.llama.loop.score.tooltip = Erforderliche Punktzahl zum Beenden, 0%% = unendliche Schleife
1387
+ toolbox.agent.llama.loop.label = Schleife / bewerten (bis Punktzahl, 0% = unendlich)
1388
+ toolbox.agent.llama.loop.mode.complete = Min. % abschluss
1389
+ toolbox.agent.llama.loop.mode.score = Min. % bewertung
1390
+ toolbox.agent.llama.loop.score.tooltip = Erforderliche Punktzahl zum Beenden, 0% = unendliche Schleife
1387
1391
  toolbox.agents.label = Agenten
1388
1392
  toolbox.assistants.label = Assistenten
1389
1393
  toolbox.env.label = Env
@@ -472,7 +472,7 @@ dictionary.plugin.cmd_api.cmds.get_params = GET params: list, separated by comma
472
472
  dictionary.plugin.cmd_api.cmds.headers = Headers: provide the JSON object with a dictionary of extra request headers, like Authorization, API keys, etc.
473
473
  dictionary.plugin.cmd_api.cmds.instruction = Instruction: description for the model on when and how to use this API call
474
474
  dictionary.plugin.cmd_api.cmds.name = Name: unique API call name (ID)
475
- dictionary.plugin.cmd_api.cmds.post_json = POST JSON: provide the JSON object, template to send in POST JSON request, use %%param%% as POST param placeholders
475
+ dictionary.plugin.cmd_api.cmds.post_json = POST JSON: provide the JSON object, template to send in POST JSON request, use %param% as POST param placeholders
476
476
  dictionary.plugin.cmd_api.cmds.post_params = POST params: list, separated by comma, POST params to send in POST request
477
477
  dictionary.plugin.cmd_api.cmds.type = Request type: use GET for basic GET request, POST to send encoded POST params or POST_JSON to send JSON-encoded object as body
478
478
  dictionary.plugin.cmd_custom.cmds.cmd = Command to execute
@@ -814,6 +814,8 @@ mode.langchain.tooltip = Chat with models provided by Langchain
814
814
  model.ctx = Context tokens
815
815
  model.ctx.desc = Max model input tokens
816
816
  model.default = Default in mode
817
+ model.extra = Extra parameters (JSON)
818
+ model.extra.desc = A JSON object containing additional parameters for the model (such as reasoning effort, etc.).
817
819
  model.id = Model ID
818
820
  model.input = Input
819
821
  mode.llama_index = Chat with Files
@@ -965,8 +967,10 @@ preset.prompt.rename = Rename
965
967
  preset.prompt.save_custom = Save as My template
966
968
  preset.prompt.use = Paste...
967
969
  preset.research = Research
970
+ preset.tab.experts = Experts
968
971
  preset.tab.general = General
969
972
  preset.tab.personalize = Personalize
973
+ preset.tab.remote_tools = Remote tools
970
974
  preset.temperature = Temperature
971
975
  preset.tool.function = Functions
972
976
  preset.tool.function.tip.agent_llama = Tip: Functions from plugins are enabled automatically.
@@ -1237,8 +1241,8 @@ settings.prompt.agent.goal = Agent: goal update [Legacy]
1237
1241
  settings.prompt.agent.goal.desc = Prompt to instruct how to update the current goal status
1238
1242
  settings.prompt.agent.instruction = Agent: system instruction [Legacy]
1239
1243
  settings.prompt.agent.instruction.desc = Prompt to instruct how to handle autonomous mode
1240
- settings.prompt.agent.llama.eval = Agent: evaluation prompt in loop [LlamaIndex] - %% score
1241
- settings.prompt.agent.llama.eval.complete = Agent: evaluation prompt in loop [LlamaIndex] - %% complete
1244
+ settings.prompt.agent.llama.eval = Agent: evaluation prompt in loop [LlamaIndex] - % score
1245
+ settings.prompt.agent.llama.eval.complete = Agent: evaluation prompt in loop [LlamaIndex] - % complete
1242
1246
  settings.prompt.agent.llama.eval.complete.desc = Prompt used to response evaluation when Loop / evaluate option is enabled (percent)
1243
1247
  settings.prompt.agent.llama.eval.desc = Prompt used to response evaluation when Loop / evaluate option is enabled (score)
1244
1248
  settings.prompt.cmd = Command execute: instruction
@@ -1329,7 +1333,7 @@ settings.vision.capture.enabled = Camera
1329
1333
  settings.vision.capture.height = Camera height (px)
1330
1334
  settings.vision.capture.idx = Camera Input Device
1331
1335
  settings.vision.capture.idx.desc = Video capture camera index (index of the camera, default: 0)
1332
- settings.vision.capture.quality = Capture quality (%%)
1336
+ settings.vision.capture.quality = Capture quality (%)
1333
1337
  settings.vision.capture.width = Camera width (px)
1334
1338
  settings.zoom = Chat output window zoom
1335
1339
  speech.enable = Speak
@@ -1392,11 +1396,11 @@ toolbox.agent.auto_stop.label = Auto-stop
1392
1396
  toolbox.agent.continue.label = Always continue...
1393
1397
  toolbox.agent.iterations.label = Max run steps (iterations, 0 = infinity)
1394
1398
  toolbox.agent.llama.loop.enabled.label = Loop
1395
- toolbox.agent.llama.loop.label = Loop / evaluate (until score, 0%% = infinity)
1396
- toolbox.agent.llama.loop.mode.complete = Min. %% complete
1397
- toolbox.agent.llama.loop.mode.score = Min. %% score
1399
+ toolbox.agent.llama.loop.label = Loop / evaluate (until score, 0% = infinity)
1400
+ toolbox.agent.llama.loop.mode.complete = Min. % complete
1401
+ toolbox.agent.llama.loop.mode.score = Min. % score
1398
1402
  toolbox.agent.llama.loop.mode.tooltip = Evaluate task completion / or task result accuracy
1399
- toolbox.agent.llama.loop.score.tooltip = Required score to finish, 0%% = infinity loop
1403
+ toolbox.agent.llama.loop.score.tooltip = Required score to finish, 0% = infinity loop
1400
1404
  toolbox.agents.label = Agents
1401
1405
  toolbox.assistants.label = Assistants
1402
1406
  toolbox.env.label = Env