pygpt-net 2.6.1__py3-none-any.whl → 2.6.6__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 (131) hide show
  1. pygpt_net/CHANGELOG.txt +23 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +20 -1
  4. pygpt_net/config.py +55 -65
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/calendar/note.py +101 -126
  7. pygpt_net/controller/chat/chat.py +38 -35
  8. pygpt_net/controller/chat/render.py +154 -214
  9. pygpt_net/controller/chat/response.py +5 -3
  10. pygpt_net/controller/chat/stream.py +92 -27
  11. pygpt_net/controller/config/config.py +39 -42
  12. pygpt_net/controller/config/field/checkbox.py +16 -12
  13. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  14. pygpt_net/controller/config/field/cmd.py +51 -57
  15. pygpt_net/controller/config/field/combo.py +33 -16
  16. pygpt_net/controller/config/field/dictionary.py +48 -55
  17. pygpt_net/controller/config/field/input.py +50 -32
  18. pygpt_net/controller/config/field/slider.py +40 -45
  19. pygpt_net/controller/config/field/textarea.py +20 -6
  20. pygpt_net/controller/config/placeholder.py +110 -231
  21. pygpt_net/controller/ctx/common.py +48 -48
  22. pygpt_net/controller/ctx/ctx.py +91 -132
  23. pygpt_net/controller/lang/mapping.py +57 -95
  24. pygpt_net/controller/lang/plugins.py +64 -55
  25. pygpt_net/controller/lang/settings.py +39 -38
  26. pygpt_net/controller/layout/layout.py +176 -109
  27. pygpt_net/controller/mode/mode.py +88 -85
  28. pygpt_net/controller/model/model.py +73 -73
  29. pygpt_net/controller/plugins/plugins.py +209 -223
  30. pygpt_net/controller/plugins/presets.py +54 -55
  31. pygpt_net/controller/plugins/settings.py +54 -69
  32. pygpt_net/controller/presets/editor.py +33 -88
  33. pygpt_net/controller/presets/experts.py +20 -1
  34. pygpt_net/controller/presets/presets.py +293 -298
  35. pygpt_net/controller/settings/profile.py +16 -4
  36. pygpt_net/controller/theme/theme.py +72 -81
  37. pygpt_net/controller/ui/mode.py +118 -186
  38. pygpt_net/controller/ui/tabs.py +69 -90
  39. pygpt_net/controller/ui/ui.py +47 -56
  40. pygpt_net/controller/ui/vision.py +24 -23
  41. pygpt_net/core/agents/runner.py +15 -7
  42. pygpt_net/core/bridge/bridge.py +5 -5
  43. pygpt_net/core/command/command.py +149 -219
  44. pygpt_net/core/ctx/ctx.py +94 -146
  45. pygpt_net/core/debug/debug.py +48 -58
  46. pygpt_net/core/experts/experts.py +3 -3
  47. pygpt_net/core/models/models.py +74 -112
  48. pygpt_net/core/modes/modes.py +13 -21
  49. pygpt_net/core/plugins/plugins.py +154 -177
  50. pygpt_net/core/presets/presets.py +103 -176
  51. pygpt_net/core/render/web/body.py +217 -215
  52. pygpt_net/core/render/web/renderer.py +330 -474
  53. pygpt_net/core/text/utils.py +28 -44
  54. pygpt_net/core/tokens/tokens.py +104 -203
  55. pygpt_net/data/config/config.json +3 -3
  56. pygpt_net/data/config/models.json +3 -3
  57. pygpt_net/data/locale/locale.de.ini +2 -0
  58. pygpt_net/data/locale/locale.en.ini +2 -0
  59. pygpt_net/data/locale/locale.es.ini +2 -0
  60. pygpt_net/data/locale/locale.fr.ini +2 -0
  61. pygpt_net/data/locale/locale.it.ini +2 -0
  62. pygpt_net/data/locale/locale.pl.ini +3 -1
  63. pygpt_net/data/locale/locale.uk.ini +2 -0
  64. pygpt_net/data/locale/locale.zh.ini +2 -0
  65. pygpt_net/item/ctx.py +141 -139
  66. pygpt_net/plugin/agent/plugin.py +2 -1
  67. pygpt_net/plugin/audio_output/plugin.py +5 -2
  68. pygpt_net/plugin/base/plugin.py +101 -85
  69. pygpt_net/plugin/bitbucket/__init__.py +12 -0
  70. pygpt_net/plugin/bitbucket/config.py +267 -0
  71. pygpt_net/plugin/bitbucket/plugin.py +126 -0
  72. pygpt_net/plugin/bitbucket/worker.py +569 -0
  73. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  74. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  75. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  76. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  77. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  78. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  79. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  80. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  81. pygpt_net/plugin/experts/plugin.py +2 -2
  82. pygpt_net/plugin/facebook/__init__.py +12 -0
  83. pygpt_net/plugin/facebook/config.py +359 -0
  84. pygpt_net/plugin/facebook/plugin.py +113 -0
  85. pygpt_net/plugin/facebook/worker.py +698 -0
  86. pygpt_net/plugin/github/__init__.py +12 -0
  87. pygpt_net/plugin/github/config.py +441 -0
  88. pygpt_net/plugin/github/plugin.py +126 -0
  89. pygpt_net/plugin/github/worker.py +674 -0
  90. pygpt_net/plugin/google/__init__.py +12 -0
  91. pygpt_net/plugin/google/config.py +367 -0
  92. pygpt_net/plugin/google/plugin.py +126 -0
  93. pygpt_net/plugin/google/worker.py +826 -0
  94. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  95. pygpt_net/plugin/mailer/plugin.py +3 -5
  96. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  97. pygpt_net/plugin/real_time/plugin.py +52 -60
  98. pygpt_net/plugin/slack/__init__.py +12 -0
  99. pygpt_net/plugin/slack/config.py +349 -0
  100. pygpt_net/plugin/slack/plugin.py +115 -0
  101. pygpt_net/plugin/slack/worker.py +639 -0
  102. pygpt_net/plugin/telegram/__init__.py +12 -0
  103. pygpt_net/plugin/telegram/config.py +308 -0
  104. pygpt_net/plugin/telegram/plugin.py +117 -0
  105. pygpt_net/plugin/telegram/worker.py +563 -0
  106. pygpt_net/plugin/twitter/__init__.py +12 -0
  107. pygpt_net/plugin/twitter/config.py +491 -0
  108. pygpt_net/plugin/twitter/plugin.py +125 -0
  109. pygpt_net/plugin/twitter/worker.py +837 -0
  110. pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
  111. pygpt_net/tools/code_interpreter/tool.py +0 -1
  112. pygpt_net/tools/translator/tool.py +1 -1
  113. pygpt_net/ui/base/config_dialog.py +86 -100
  114. pygpt_net/ui/base/context_menu.py +48 -46
  115. pygpt_net/ui/dialog/preset.py +34 -77
  116. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  117. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  118. pygpt_net/ui/main.py +49 -31
  119. pygpt_net/ui/tray.py +61 -60
  120. pygpt_net/ui/widget/calendar/select.py +86 -70
  121. pygpt_net/ui/widget/lists/attachment.py +86 -44
  122. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  123. pygpt_net/ui/widget/lists/context.py +135 -188
  124. pygpt_net/ui/widget/lists/preset.py +59 -61
  125. pygpt_net/ui/widget/textarea/web.py +161 -48
  126. pygpt_net/utils.py +8 -1
  127. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/METADATA +164 -2
  128. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/RECORD +131 -103
  129. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/LICENSE +0 -0
  130. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/WHEEL +0 -0
  131. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/entry_points.txt +0 -0
@@ -6,11 +6,9 @@
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.06 19:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
- import re
13
-
14
12
 
15
13
  def output_html2text(html: str) -> str:
16
14
  """
@@ -19,46 +17,31 @@ def output_html2text(html: str) -> str:
19
17
  :param html: HTML content
20
18
  :return: Plain text
21
19
  """
22
- from bs4 import BeautifulSoup
23
- if html == "":
20
+ if not html:
24
21
  return ""
25
22
  try:
26
- soup = BeautifulSoup(html, 'html.parser')
23
+ from bs4 import BeautifulSoup, NavigableString
24
+ try:
25
+ soup = BeautifulSoup(html, 'lxml')
26
+ except Exception:
27
+ soup = BeautifulSoup(html, 'html.parser')
27
28
  # remove headers from code blocks
28
- for tag in soup.find_all('p', class_='code-header-wrapper'):
29
- empty = soup.new_tag('p')
30
- empty.string = '\n'
31
- tag.replace_with(empty)
32
- for tag in soup.find_all('span', class_='ts'):
33
- empty = soup.new_tag('span')
34
- empty.string = ''
35
- tag.replace_with(empty)
36
- for tag in soup.find_all('div', class_='name-header'):
37
- empty = soup.new_tag('span')
38
- empty.string = ''
39
- tag.replace_with(empty)
40
- for tag in soup.find_all('span', class_='toggle-cmd-output'):
41
- empty = soup.new_tag('span')
42
- empty.string = ''
43
- tag.replace_with(empty)
29
+ for tag in soup.select('p.code-header-wrapper'):
30
+ tag.replace_with(NavigableString('\n'))
31
+ for tag in soup.select('span.ts, span.toggle-cmd-output, div.name-header'):
32
+ tag.decompose()
44
33
  # add separators
45
- for tag in soup.find_all('div', class_='msg-bot'):
46
- sep = soup.new_tag('p')
47
- sep.string = '\n\n'
48
- tag.insert_before(sep)
49
- for p in soup.select('.msg-user .msg p'):
50
- for br in p.find_all('br'):
51
- br.replace_with("\n")
52
- for tag in soup.find_all('div', class_='msg-user'):
53
- sep = soup.new_tag('p')
54
- sep.string = '\n\n'
55
- tag.insert_before(sep)
34
+ for tag in soup.select('div.msg-bot, div.msg-user'):
35
+ tag.insert_before(NavigableString('\n\n'))
36
+ for br in soup.select('.msg-user .msg p br'):
37
+ br.replace_with('\n')
56
38
  text = soup.get_text(separator="", strip=False)
57
39
  return text.replace('\t', ' ')
58
- except Exception as e:
40
+ except Exception:
59
41
  pass
60
42
  return ""
61
43
 
44
+
62
45
  def output_clean_html(html: str) -> str:
63
46
  """
64
47
  Clean output HTML content
@@ -66,20 +49,24 @@ def output_clean_html(html: str) -> str:
66
49
  :param html: HTML content
67
50
  :return: HTML content
68
51
  """
69
- from bs4 import BeautifulSoup
70
52
  try:
71
- soup = BeautifulSoup(html, 'html.parser')
53
+ from bs4 import BeautifulSoup
54
+ try:
55
+ soup = BeautifulSoup(html, 'lxml')
56
+ except Exception:
57
+ soup = BeautifulSoup(html, 'html.parser')
72
58
  # remove copy to clipboard from code blocks
73
- for tag in soup.find_all('a', class_='code-header-copy'):
59
+ for tag in soup.select('a.code-header-copy'):
74
60
  tag.decompose()
75
61
  # remove action icons
76
- for tag in soup.find_all('div', class_='action-icons'):
62
+ for tag in soup.select('div.action-icons'):
77
63
  tag.decompose()
78
64
  return str(soup)
79
- except Exception as e:
65
+ except Exception:
80
66
  pass
81
67
  return html
82
68
 
69
+
83
70
  def has_unclosed_code_tag(text: str) -> bool:
84
71
  """
85
72
  Check if HTML content has unclosed code block
@@ -87,9 +74,6 @@ def has_unclosed_code_tag(text: str) -> bool:
87
74
  :param text: HTML content
88
75
  :return: True if unclosed code block found
89
76
  """
90
- if text is None:
77
+ if not text:
91
78
  return False
92
- code_blocks = re.findall(r'```', text)
93
- if len(code_blocks) % 2 != 0:
94
- return True
95
- return False
79
+ return (text.count('```') % 2) != 0
@@ -6,10 +6,11 @@
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.06.28 16:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Tuple, List
13
+ from functools import lru_cache
13
14
 
14
15
  import tiktoken
15
16
 
@@ -49,41 +50,60 @@ CHAT_MODES = [
49
50
  ]
50
51
 
51
52
  class Tokens:
52
- def __init__(self, window=None):
53
- """
54
- Tokens core
53
+ _default_encoding = "cl100k_base"
54
+ _const_cache = {}
55
55
 
56
- :param window: Window instance
57
- """
56
+ def __init__(self, window=None):
58
57
  self.window = window
59
58
 
59
+ @staticmethod
60
+ @lru_cache(maxsize=128)
61
+ def _encoding_name_for_model(model: str | None) -> str:
62
+ if model:
63
+ try:
64
+ return tiktoken.encoding_for_model(model).name
65
+ except KeyError:
66
+ return Tokens._default_encoding
67
+ except ValueError:
68
+ return Tokens._default_encoding
69
+ return Tokens._default_encoding
70
+
71
+ @staticmethod
72
+ @lru_cache(maxsize=64)
73
+ def _get_encoding(encoding_name: str):
74
+ try:
75
+ return tiktoken.get_encoding(encoding_name)
76
+ except Exception:
77
+ return tiktoken.get_encoding(Tokens._default_encoding)
78
+
79
+ @classmethod
80
+ def _const_tokens(cls, text: str, model: str = "gpt-4") -> int:
81
+ enc_name = cls._encoding_name_for_model(model)
82
+ key = (enc_name, text)
83
+ cached = cls._const_cache.get(key)
84
+ if cached is not None:
85
+ return cached
86
+ try:
87
+ enc = cls._get_encoding(enc_name)
88
+ val = len(enc.encode(text))
89
+ except Exception:
90
+ val = 0
91
+ cls._const_cache[key] = val
92
+ return val
93
+
60
94
  @staticmethod
61
95
  def from_str(
62
96
  string: str,
63
97
  model: str = "gpt-4"
64
98
  ) -> int:
65
- """
66
- Return number of tokens from string
67
-
68
- :param string: string
69
- :param model: model name
70
- :return: number of tokens
71
- """
72
- if string is None or string == "":
99
+ if not string:
73
100
  return 0
74
-
75
- default = "cl100k_base"
76
101
  try:
77
102
  try:
78
- if model is not None and model != "":
79
- encoding = tiktoken.encoding_for_model(model)
80
- else:
81
- encoding = tiktoken.get_encoding(default)
82
- except KeyError:
83
- encoding = tiktoken.get_encoding(default)
103
+ enc_name = Tokens._encoding_name_for_model(model)
104
+ encoding = Tokens._get_encoding(enc_name)
84
105
  except ValueError:
85
106
  return 0
86
-
87
107
  try:
88
108
  return len(encoding.encode(str(string)))
89
109
  except Exception as e:
@@ -95,12 +115,6 @@ class Tokens:
95
115
 
96
116
  @staticmethod
97
117
  def get_extra(model: str = "gpt-4") -> int:
98
- """
99
- Return number of extra tokens
100
-
101
- :param model: model name
102
- :return: number of tokens
103
- """
104
118
  return 3
105
119
 
106
120
  @staticmethod
@@ -109,27 +123,16 @@ class Tokens:
109
123
  name: str,
110
124
  model: str = "gpt-4"
111
125
  ) -> int:
112
- """
113
- Return number of tokens from prompt
114
-
115
- :param text: prompt text
116
- :param name: input name
117
- :param model: model name
118
- :return: number of tokens
119
- """
120
126
  model, per_message, per_name = Tokens.get_config(model)
121
127
  num = 0
122
-
123
128
  try:
124
129
  num += Tokens.from_str(text, model)
125
130
  except Exception as e:
126
131
  print("Tokens calc exception", e)
127
-
128
- if name is not None and name != "":
132
+ if name:
129
133
  num += per_message + per_name
130
134
  else:
131
135
  num += per_message
132
-
133
136
  return num
134
137
 
135
138
  @staticmethod
@@ -137,55 +140,37 @@ class Tokens:
137
140
  text: str,
138
141
  model: str = "gpt-4"
139
142
  ) -> int:
140
- """
141
- Return number of tokens from text, without any extra tokens
142
-
143
- :param text: text
144
- :param model: model name
145
- :return: number of tokens
146
- """
147
- model, per_message, per_name = Tokens.get_config(model)
148
- num = 0
149
-
150
- if text is None or text == "":
143
+ if not text:
151
144
  return 0
152
-
153
145
  try:
154
- num += Tokens.from_str(text, model)
146
+ return Tokens.from_str(text, model)
155
147
  except Exception as e:
156
148
  print("Tokens calc exception", e)
157
-
158
- return num
149
+ return 0
159
150
 
160
151
  @staticmethod
161
152
  def from_messages(
162
153
  messages: List[dict],
163
154
  model: str = "gpt-4"
164
155
  ) -> int:
165
- """
166
- Return number of tokens from messages list
167
-
168
- :param messages: messages
169
- :param model: model name
170
- :return: number of tokens
171
- """
172
156
  model, per_message, per_name = Tokens.get_config(model)
173
157
  num = 0
158
+ f = Tokens.from_str
174
159
  for message in messages:
175
160
  num += per_message
176
161
  for key, value in message.items():
177
- # text message
178
162
  if isinstance(value, str):
179
- num += Tokens.from_str(value)
163
+ num += f(value)
180
164
  if key == "name":
181
165
  num += per_name
182
- # multimodal message
183
166
  elif key == "content" and isinstance(value, list):
184
167
  for part in value:
185
- if "type" in part and "text" in part:
186
- if part["type"] == "text":
187
- num += Tokens.from_str(part["text"])
188
- num += 3 # every reply is primed with <|start|>assistant<|message|>
168
+ t = part.get("type")
169
+ if t == "text":
170
+ tv = part.get("text")
171
+ if tv:
172
+ num += f(tv)
173
+ num += 3
189
174
  return num
190
175
 
191
176
  @staticmethod
@@ -193,19 +178,13 @@ class Tokens:
193
178
  messages: List,
194
179
  model: str = "gpt-4"
195
180
  ) -> int:
196
- """
197
- Return number of tokens from prompt
198
-
199
- :param messages: messages
200
- :param model: model name
201
- :return: number of tokens
202
- """
203
181
  model, per_message, per_name = Tokens.get_config(model)
204
182
  num = 0
183
+ f = Tokens.from_str
205
184
  for message in messages:
206
185
  num += per_message
207
- num += Tokens.from_str(message.content)
208
- num += 3 # every reply is primed with <|start|>assistant<|message|>
186
+ num += f(message.content)
187
+ num += 3
209
188
  return num
210
189
 
211
190
  @staticmethod
@@ -214,21 +193,14 @@ class Tokens:
214
193
  messages: List[ChatMessageLlama],
215
194
  model: str = "gpt-4"
216
195
  ) -> int:
217
- """
218
- Return number of tokens from prompt
219
-
220
- :param query: query
221
- :param messages: messages
222
- :param model: model name
223
- :return: number of tokens
224
- """
225
196
  model, per_message, per_name = Tokens.get_config(model)
226
197
  num = 0
227
- num += Tokens.from_str(query)
198
+ f = Tokens.from_str
199
+ num += f(query)
228
200
  for message in messages:
229
201
  num += per_message
230
- num += Tokens.from_str(message.content)
231
- num += 3 # every reply is primed with <|start|>assistant<|message|>
202
+ num += f(message.content)
203
+ num += 3
232
204
  return num
233
205
 
234
206
  @staticmethod
@@ -237,94 +209,56 @@ class Tokens:
237
209
  mode: str = MODE_CHAT,
238
210
  model: str = "gpt-4"
239
211
  ) -> int:
240
- """
241
- Return number of tokens from context ctx
242
-
243
- :param ctx: CtxItem
244
- :param mode: mode
245
- :param model: model ID
246
- :return: number of tokens
247
- """
248
212
  model, per_message, per_name = Tokens.get_config(model)
249
213
  num = 0
250
-
214
+ f = Tokens.from_str
251
215
  if mode in CHAT_MODES:
252
- # input message
253
216
  try:
254
- num += Tokens.from_str(str(ctx.final_input), model)
217
+ num += f(str(ctx.final_input), model)
255
218
  except Exception as e:
256
219
  print("Tokens calc exception", e)
257
-
258
- # output message
259
220
  try:
260
- num += Tokens.from_str(str(ctx.final_output), model)
221
+ num += f(str(ctx.final_output), model)
261
222
  except Exception as e:
262
223
  print("Tokens calc exception", e)
263
-
264
- # + fixed tokens
265
- num += per_message * 2 # input + output
266
- num += per_name * 2 # input + output
224
+ num += per_message * 2
225
+ num += per_name * 2
267
226
  try:
268
- num += Tokens.from_str("system", model) * 2 # input + output
227
+ num += Tokens._const_tokens("system", model) * 2
269
228
  except Exception as e:
270
229
  print("Tokens calc exception", e)
271
-
272
- # input name
273
- if ctx.input_name is not None and ctx.input_name != "":
274
- name = ctx.input_name
275
- else:
276
- name = "user"
230
+ name = ctx.input_name if ctx.input_name else "user"
277
231
  try:
278
- num += Tokens.from_str(name, model)
232
+ num += f(name, model)
279
233
  except Exception as e:
280
234
  print("Tokens calc exception", e)
281
-
282
- # output name
283
- if ctx.output_name is not None and ctx.output_name != "":
284
- name = ctx.output_name
285
- else:
286
- name = "assistant"
235
+ name = ctx.output_name if ctx.output_name else "assistant"
287
236
  try:
288
- num += Tokens.from_str(name, model)
237
+ num += f(name, model)
289
238
  except Exception as e:
290
239
  print("Tokens calc exception", e)
291
-
292
- # build tmp message if completion mode
293
240
  elif mode == MODE_COMPLETION:
294
- message = ""
295
- # if with names
296
- if ctx.input_name is not None \
297
- and ctx.output_name is not None \
298
- and ctx.input_name != "" \
299
- and ctx.output_name != "":
300
- if ctx.final_input is not None and ctx.final_input != "":
301
- message += "\n" + ctx.input_name + ": " + ctx.final_input
302
- if ctx.final_output is not None and ctx.final_output != "":
303
- message += "\n" + ctx.output_name + ": " + ctx.final_output
304
- # if without names
241
+ parts = []
242
+ if ctx.input_name and ctx.output_name:
243
+ if ctx.final_input:
244
+ parts.append("\n" + ctx.input_name + ": " + ctx.final_input)
245
+ if ctx.final_output:
246
+ parts.append("\n" + ctx.output_name + ": " + ctx.final_output)
305
247
  else:
306
- if ctx.final_input is not None and ctx.final_input != "":
307
- message += "\n" + ctx.final_input
308
- if ctx.final_output is not None and ctx.final_output != "":
309
- message += "\n" + ctx.final_output
248
+ if ctx.final_input:
249
+ parts.append("\n" + ctx.final_input)
250
+ if ctx.final_output:
251
+ parts.append("\n" + ctx.final_output)
310
252
  try:
311
- num += Tokens.from_str(message, model)
253
+ num += f("".join(parts), model)
312
254
  except Exception as e:
313
255
  print("Tokens calc exception", e)
314
-
315
256
  return num
316
257
 
317
258
  def get_current(
318
259
  self,
319
260
  input_prompt: str
320
261
  ) -> Tuple[int, int, int, int, int, int, int, int, int]:
321
- """
322
- Return current number of used tokens
323
-
324
- :param input_prompt: input prompt
325
- :return: A tuple of (input_tokens, system_tokens, extra_tokens, ctx_tokens, ctx_len, ctx_len_all, \
326
- sum_tokens, max_current, threshold)
327
- """
328
262
  model = self.window.core.config.get('model')
329
263
  model_id = ""
330
264
  model_data = self.window.core.models.get(model)
@@ -341,64 +275,45 @@ class Tokens:
341
275
  extra_tokens = self.get_extra(model)
342
276
 
343
277
  if mode in CHAT_MODES:
344
- # system prompt (without extra tokens)
345
278
  system_prompt = str(self.window.core.config.get('prompt')).strip()
346
- system_prompt = self.window.core.prompt.build_final_system_prompt(system_prompt, mode, model_data) # add addons
347
-
348
- if system_prompt is not None and system_prompt != "":
279
+ system_prompt = self.window.core.prompt.build_final_system_prompt(system_prompt, mode, model_data)
280
+ if system_prompt:
349
281
  system_tokens = self.from_prompt(system_prompt, "", model_id)
350
- system_tokens += self.from_text("system", model_id)
351
-
352
- # input prompt
353
- if input_prompt is not None and input_prompt != "":
282
+ system_tokens += Tokens._const_tokens("system", model_id)
283
+ if input_prompt:
354
284
  input_tokens = self.from_prompt(input_prompt, "", model_id)
355
- input_tokens += self.from_text("user", model_id)
285
+ input_tokens += Tokens._const_tokens("user", model_id)
356
286
  elif mode == MODE_COMPLETION:
357
- # system prompt (without extra tokens)
358
287
  system_prompt = str(self.window.core.config.get('prompt')).strip()
359
- system_prompt = self.window.core.prompt.build_final_system_prompt(system_prompt, mode, model_data) # add addons
288
+ system_prompt = self.window.core.prompt.build_final_system_prompt(system_prompt, mode, model_data)
360
289
  system_tokens = self.from_text(system_prompt, model_id)
361
-
362
- # input prompt
363
- if input_prompt is not None and input_prompt != "":
364
- message = ""
365
- if user_name is not None \
366
- and ai_name is not None \
367
- and user_name != "" \
368
- and ai_name != "":
369
- message += "\n" + user_name + ": " + str(input_prompt)
370
- message += "\n" + ai_name + ":"
290
+ if input_prompt:
291
+ if user_name and ai_name:
292
+ message = "\n" + user_name + ": " + str(input_prompt) + "\n" + ai_name + ":"
371
293
  else:
372
- message += "\n" + str(input_prompt)
294
+ message = "\n" + str(input_prompt)
373
295
  input_tokens = self.from_text(message, model_id)
374
- extra_tokens = 0 # no extra tokens in completion mode
296
+ extra_tokens = 0
375
297
 
376
- # TMP system prompt (for debug purposes)
377
298
  self.window.core.ctx.current_sys_prompt = system_prompt
378
299
 
379
- # used tokens
380
300
  used_tokens = system_tokens + input_tokens
381
301
 
382
- # check model max allowed ctx tokens
383
302
  max_current = max_total_tokens
384
303
  model_ctx = self.window.core.models.get_num_ctx(model)
385
304
  if max_current > model_ctx:
386
305
  max_current = model_ctx
387
306
 
388
- # context threshold (reserved for output)
389
307
  threshold = self.window.core.config.get('context_threshold')
390
308
  max_to_check = max_current - threshold
391
309
 
392
- # context tokens
393
310
  ctx_len_all = self.window.core.ctx.count_items()
394
311
  ctx_len, ctx_tokens = self.window.core.ctx.count_prompt_items(model_id, mode, used_tokens, max_to_check)
395
312
 
396
- # empty ctx tokens if context is not used
397
313
  if not self.window.core.config.get('use_context'):
398
314
  ctx_tokens = 0
399
315
  ctx_len = 0
400
316
 
401
- # sum of input tokens
402
317
  sum_tokens = system_tokens + input_tokens + ctx_tokens + extra_tokens
403
318
 
404
319
  return input_tokens, system_tokens, extra_tokens, ctx_tokens, ctx_len, ctx_len_all, \
@@ -409,39 +324,25 @@ class Tokens:
409
324
  system_prompt: str,
410
325
  input_prompt: str
411
326
  ) -> int:
412
- """
413
- Count per-user used tokens
414
-
415
- :param system_prompt: system prompt
416
- :param input_prompt: input prompt
417
- :return: used tokens
418
- """
419
327
  model = self.window.core.config.get('model')
420
328
  model_id = self.window.core.models.get_id(model)
421
329
  mode = self.window.core.config.get('mode')
422
330
  tokens = 0
423
331
  if mode in [MODE_CHAT, MODE_VISION, MODE_AUDIO, MODE_RESEARCH]:
424
- tokens += self.from_prompt(system_prompt, "", model_id) # system prompt
332
+ tokens += self.from_prompt(system_prompt, "", model_id)
425
333
  tokens += self.from_text("system", model_id)
426
- tokens += self.from_prompt(input_prompt, "", model_id) # input prompt
334
+ tokens += self.from_prompt(input_prompt, "", model_id)
427
335
  tokens += self.from_text("user", model_id)
428
336
  else:
429
- # rest of modes
430
- tokens += self.from_text(system_prompt, model_id) # system prompt
431
- tokens += self.from_text(input_prompt, model_id) # input prompt
432
- tokens += self.window.core.config.get('context_threshold') # context threshold (reserved for output)
433
- tokens += self.get_extra(model_id) # extra tokens (required for output)
337
+ tokens += self.from_text(system_prompt, model_id)
338
+ tokens += self.from_text(input_prompt, model_id)
339
+ tokens += self.window.core.config.get('context_threshold')
340
+ tokens += self.get_extra(model_id)
434
341
  return tokens
435
342
 
436
343
  @staticmethod
437
344
  def get_config(model: str) -> Tuple[str, int, int]:
438
- """
439
- Return tokens config values
440
-
441
- :param model: model ID
442
- :return: model, per_message, per_name
443
- """
444
- per_message = 4 # message follows <|start|>{role/name}\n{content}<|end|>\n
345
+ per_message = 4
445
346
  per_name = -1
446
347
 
447
348
  if model is not None:
@@ -456,8 +357,8 @@ class Tokens:
456
357
  per_message = 3
457
358
  per_name = 1
458
359
  elif model == "gpt-3.5-turbo-0301":
459
- per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
460
- per_name = -1 # if there's a name, the role is omitted
360
+ per_message = 4
361
+ per_name = -1
461
362
  elif "gpt-3.5-turbo" in model:
462
363
  return Tokens.get_config(model="gpt-3.5-turbo-0613")
463
364
  elif "gpt-4" in model:
@@ -466,4 +367,4 @@ class Tokens:
466
367
  per_message = 1
467
368
  per_name = 0
468
369
 
469
- return model, per_message, per_name
370
+ return model, per_message, per_name
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.1",
4
- "app.version": "2.6.1",
5
- "updated_at": "2025-08-13T00:00:00"
3
+ "version": "2.6.6",
4
+ "app.version": "2.6.6",
5
+ "updated_at": "2025-08-16T00: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.1",
4
- "app.version": "2.6.1",
5
- "updated_at": "2025-08-13T23:07:35"
3
+ "version": "2.6.6",
4
+ "app.version": "2.6.6",
5
+ "updated_at": "2025-08-16T23:07:35"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -966,8 +966,10 @@ preset.prompt.rename = Umbenennen
966
966
  preset.prompt.save_custom = Als Meine Vorlage speichern
967
967
  preset.prompt.use = Einfügen...
968
968
  preset.research = Forschung
969
+ preset.tab.experts = Experten
969
970
  preset.tab.general = Allgemein
970
971
  preset.tab.personalize = Personalisieren
972
+ preset.tab.remote_tools = Remote-Tools
971
973
  preset.temperature = Temperatur
972
974
  preset.tool.function = Funktionen
973
975
  preset.tool.function.tip.agent_llama = Tipp: Funktionen aus Plugins werden automatisch aktiviert.
@@ -967,8 +967,10 @@ preset.prompt.rename = Rename
967
967
  preset.prompt.save_custom = Save as My template
968
968
  preset.prompt.use = Paste...
969
969
  preset.research = Research
970
+ preset.tab.experts = Experts
970
971
  preset.tab.general = General
971
972
  preset.tab.personalize = Personalize
973
+ preset.tab.remote_tools = Remote tools
972
974
  preset.temperature = Temperature
973
975
  preset.tool.function = Functions
974
976
  preset.tool.function.tip.agent_llama = Tip: Functions from plugins are enabled automatically.