pygpt-net 2.4.30__py3-none-any.whl → 2.4.35__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 (135) hide show
  1. CHANGELOG.md +32 -0
  2. README.md +2105 -1892
  3. pygpt_net/CHANGELOG.txt +32 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/controller/access/__init__.py +5 -5
  6. pygpt_net/controller/access/control.py +3 -2
  7. pygpt_net/controller/attachment.py +68 -1
  8. pygpt_net/controller/audio/__init__.py +34 -6
  9. pygpt_net/controller/chat/__init__.py +3 -1
  10. pygpt_net/controller/chat/attachment.py +263 -38
  11. pygpt_net/controller/chat/audio.py +99 -0
  12. pygpt_net/controller/chat/input.py +10 -3
  13. pygpt_net/controller/chat/output.py +4 -1
  14. pygpt_net/controller/chat/text.py +7 -3
  15. pygpt_net/controller/dialogs/confirm.py +17 -1
  16. pygpt_net/controller/lang/custom.py +3 -1
  17. pygpt_net/controller/mode.py +2 -1
  18. pygpt_net/controller/painter/capture.py +2 -2
  19. pygpt_net/controller/presets/editor.py +15 -2
  20. pygpt_net/controller/ui/__init__.py +4 -1
  21. pygpt_net/core/access/voice.py +2 -2
  22. pygpt_net/core/agents/legacy.py +3 -1
  23. pygpt_net/core/attachments/__init__.py +14 -9
  24. pygpt_net/core/attachments/context.py +226 -44
  25. pygpt_net/core/{audio.py → audio/__init__.py} +1 -1
  26. pygpt_net/core/audio/context.py +34 -0
  27. pygpt_net/core/bridge/context.py +29 -1
  28. pygpt_net/core/ctx/__init__.py +4 -1
  29. pygpt_net/core/db/__init__.py +4 -2
  30. pygpt_net/core/debug/attachments.py +3 -1
  31. pygpt_net/core/debug/context.py +5 -1
  32. pygpt_net/core/debug/presets.py +3 -1
  33. pygpt_net/core/events/event.py +2 -1
  34. pygpt_net/core/experts/__init__.py +3 -1
  35. pygpt_net/core/idx/chat.py +28 -6
  36. pygpt_net/core/idx/indexing.py +123 -15
  37. pygpt_net/core/modes.py +3 -1
  38. pygpt_net/core/presets.py +13 -2
  39. pygpt_net/core/render/markdown/pid.py +2 -1
  40. pygpt_net/core/render/plain/pid.py +2 -1
  41. pygpt_net/core/render/web/body.py +34 -12
  42. pygpt_net/core/render/web/pid.py +2 -1
  43. pygpt_net/core/render/web/renderer.py +8 -3
  44. pygpt_net/core/tokens.py +4 -2
  45. pygpt_net/core/types/mode.py +2 -1
  46. pygpt_net/data/config/config.json +7 -5
  47. pygpt_net/data/config/models.json +190 -5
  48. pygpt_net/data/config/modes.json +11 -5
  49. pygpt_net/data/config/presets/current.audio.json +34 -0
  50. pygpt_net/data/config/settings.json +15 -1
  51. pygpt_net/data/css/web.css +70 -0
  52. pygpt_net/data/css/web.dark.css +4 -1
  53. pygpt_net/data/css/web.light.css +1 -1
  54. pygpt_net/data/locale/locale.de.ini +27 -14
  55. pygpt_net/data/locale/locale.en.ini +63 -47
  56. pygpt_net/data/locale/locale.es.ini +27 -14
  57. pygpt_net/data/locale/locale.fr.ini +29 -16
  58. pygpt_net/data/locale/locale.it.ini +27 -14
  59. pygpt_net/data/locale/locale.pl.ini +31 -18
  60. pygpt_net/data/locale/locale.uk.ini +27 -14
  61. pygpt_net/data/locale/locale.zh.ini +34 -21
  62. pygpt_net/data/locale/plugin.cmd_files.de.ini +4 -4
  63. pygpt_net/data/locale/plugin.cmd_files.en.ini +4 -4
  64. pygpt_net/data/locale/plugin.cmd_files.es.ini +4 -4
  65. pygpt_net/data/locale/plugin.cmd_files.fr.ini +4 -4
  66. pygpt_net/data/locale/plugin.cmd_files.it.ini +4 -4
  67. pygpt_net/data/locale/plugin.cmd_files.pl.ini +4 -4
  68. pygpt_net/data/locale/plugin.cmd_files.uk.ini +4 -4
  69. pygpt_net/data/locale/plugin.cmd_files.zh.ini +4 -4
  70. pygpt_net/data/locale/plugin.cmd_web.de.ini +5 -5
  71. pygpt_net/data/locale/plugin.cmd_web.en.ini +5 -5
  72. pygpt_net/data/locale/plugin.cmd_web.es.ini +5 -5
  73. pygpt_net/data/locale/plugin.cmd_web.fr.ini +5 -5
  74. pygpt_net/data/locale/plugin.cmd_web.it.ini +5 -5
  75. pygpt_net/data/locale/plugin.cmd_web.pl.ini +5 -5
  76. pygpt_net/data/locale/plugin.cmd_web.uk.ini +5 -5
  77. pygpt_net/data/locale/plugin.cmd_web.zh.ini +5 -5
  78. pygpt_net/data/locale/plugin.idx_llama_index.de.ini +12 -12
  79. pygpt_net/data/locale/plugin.idx_llama_index.en.ini +12 -12
  80. pygpt_net/data/locale/plugin.idx_llama_index.es.ini +12 -12
  81. pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +12 -12
  82. pygpt_net/data/locale/plugin.idx_llama_index.it.ini +12 -12
  83. pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +12 -12
  84. pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +12 -12
  85. pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +12 -12
  86. pygpt_net/data/win32/USER-LICENSE.rtf +0 -0
  87. pygpt_net/data/win32/banner.bmp +0 -0
  88. pygpt_net/data/win32/banner_welcome.bmp +0 -0
  89. pygpt_net/item/attachment.py +9 -1
  90. pygpt_net/item/ctx.py +9 -1
  91. pygpt_net/item/preset.py +5 -1
  92. pygpt_net/launcher.py +3 -1
  93. pygpt_net/migrations/Version20241126170000.py +28 -0
  94. pygpt_net/migrations/__init__.py +3 -1
  95. pygpt_net/plugin/audio_input/__init__.py +11 -1
  96. pygpt_net/plugin/audio_input/worker.py +9 -1
  97. pygpt_net/plugin/audio_output/__init__.py +37 -7
  98. pygpt_net/plugin/audio_output/worker.py +38 -41
  99. pygpt_net/plugin/cmd_code_interpreter/runner.py +2 -2
  100. pygpt_net/plugin/cmd_mouse_control/__init__.py +4 -2
  101. pygpt_net/plugin/openai_dalle/__init__.py +3 -1
  102. pygpt_net/plugin/openai_vision/__init__.py +3 -1
  103. pygpt_net/provider/core/attachment/json_file.py +4 -1
  104. pygpt_net/provider/core/config/patch.py +22 -0
  105. pygpt_net/provider/core/ctx/db_sqlite/storage.py +14 -4
  106. pygpt_net/provider/core/ctx/db_sqlite/utils.py +19 -2
  107. pygpt_net/provider/core/model/patch.py +7 -1
  108. pygpt_net/provider/core/preset/json_file.py +5 -1
  109. pygpt_net/provider/gpt/__init__.py +14 -2
  110. pygpt_net/provider/gpt/audio.py +63 -0
  111. pygpt_net/provider/gpt/chat.py +76 -44
  112. pygpt_net/provider/gpt/utils.py +27 -0
  113. pygpt_net/provider/gpt/vision.py +37 -15
  114. pygpt_net/provider/loaders/base.py +10 -1
  115. pygpt_net/provider/loaders/web_yt.py +19 -1
  116. pygpt_net/tools/image_viewer/ui/dialogs.py +3 -1
  117. pygpt_net/ui/dialog/about.py +1 -1
  118. pygpt_net/ui/dialog/preset.py +3 -1
  119. pygpt_net/ui/dialog/url.py +29 -0
  120. pygpt_net/ui/dialogs.py +5 -1
  121. pygpt_net/ui/layout/chat/attachments.py +42 -6
  122. pygpt_net/ui/layout/chat/attachments_ctx.py +14 -4
  123. pygpt_net/ui/layout/chat/attachments_uploaded.py +8 -4
  124. pygpt_net/ui/widget/anims/toggles.py +2 -2
  125. pygpt_net/ui/widget/dialog/url.py +59 -0
  126. pygpt_net/ui/widget/lists/attachment.py +22 -17
  127. pygpt_net/ui/widget/lists/attachment_ctx.py +65 -3
  128. pygpt_net/ui/widget/option/checkbox.py +1 -3
  129. pygpt_net/ui/widget/option/toggle.py +1 -0
  130. pygpt_net/ui/widget/textarea/url.py +43 -0
  131. {pygpt_net-2.4.30.dist-info → pygpt_net-2.4.35.dist-info}/METADATA +2107 -1894
  132. {pygpt_net-2.4.30.dist-info → pygpt_net-2.4.35.dist-info}/RECORD +135 -124
  133. {pygpt_net-2.4.30.dist-info → pygpt_net-2.4.35.dist-info}/LICENSE +0 -0
  134. {pygpt_net-2.4.30.dist-info → pygpt_net-2.4.35.dist-info}/WHEEL +0 -0
  135. {pygpt_net-2.4.30.dist-info → pygpt_net-2.4.35.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,35 @@
1
+ 2.4.35 (2024-11-28)
2
+
3
+ - Docker removed from dependencies in Snap version #82
4
+ - Refactored documentation.
5
+ - Fix: toggles real-time update hook.
6
+ - Fix: missing edit icons.
7
+ - Added tokens from attachments to counters if mode == Full context.
8
+
9
+ 2.4.34 (2024-11-26)
10
+
11
+ - Added a new mode: Chat with Audio, with built-in multimodal support for audio input/output. Currently in beta, the execution of commands and tools in this mode is temporarily unavailable.
12
+ - Added new models: gpt-4o-audio-preview, gpt-4o-2024-11-20, chatgpt-4o-latest.
13
+ - Force disabled integration with the native system menu.
14
+
15
+ 2.4.33 (2024-11-26)
16
+
17
+ - Improved CSS and rendering of file and image lists.
18
+ - Added displaying of used attachments in the chat window.
19
+
20
+ 2.4.32 (2024-11-26)
21
+
22
+ - The "Add URL" option added to the "Attachments" tab allows users to include content from a given website as additional context. Currently, it only supports standard web pages and video transcription for YouTube links. More "web" options will be added in the future.
23
+ - Added UTF-8 as default in attachments content text read/write.
24
+
25
+ 2.4.31 (2024-11-25)
26
+
27
+ - Added an option checkbox `Auto-index on upload` in the `Attachments` tab:
28
+
29
+ Tip: To use the `Query only` mode, the file must be indexed in the vector database. This occurs automatically at the time of upload if the `Auto-index on upload` option in the `Attachments` tab is enabled. When uploading large files, such indexing might take a while - therefore, if you are using the `Full context` option, which does not use the index, you can disable the `Auto-index` option to speed up the upload of the attachment. In this case, it will only be indexed when the `Query only` option is called for the first time, and until then, attachment will be available in the form of `Full context` and `Summary`.
30
+
31
+ - Added context menu options in `Uploaded attachments` tab: `Open`, `Open Source directory` and `Open Storage directory`.
32
+
1
33
  2.4.30 (2024-11-25)
2
34
 
3
35
  - Added instruction to model about mapped data directory in both legacy and IPython code interepreter.
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: 2024.11.25 01:00:00 #
9
+ # Updated Date: 2024.11.28 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2024, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.4.30"
17
- __build__ = "2024.11.25"
16
+ __version__ = "2.4.35"
17
+ __build__ = "2024.11.28"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __website__ = "https://pygpt.net"
@@ -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: 2024.11.20 03:00:00 #
9
+ # Updated Date: 2024.11.26 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.events import BaseEvent, ControlEvent, AppEvent
@@ -73,9 +73,9 @@ class Access:
73
73
  self.window.core.plugins.get("audio_input").handler_simple.stop_recording(timeout=True)
74
74
 
75
75
  # stop audio output if playing
76
- if self.window.controller.audio.is_playing():
77
- self.window.controller.audio.stop_output()
76
+ #if self.window.controller.audio.is_playing():
77
+ self.window.controller.audio.stop_output()
78
78
 
79
79
  # stop generating if active
80
- if self.window.controller.chat.input.generating:
81
- self.window.controller.kernel.stop()
80
+ #if self.window.controller.chat.input.generating:
81
+ self.window.controller.kernel.stop()
@@ -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: 2024.11.21 20:00:00 #
9
+ # Updated Date: 2024.11.26 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -22,7 +22,8 @@ from pygpt_net.core.types import (
22
22
  MODE_EXPERT,
23
23
  MODE_LANGCHAIN,
24
24
  MODE_LLAMA_INDEX,
25
- MODE_VISION, MODE_IMAGE,
25
+ MODE_VISION,
26
+ MODE_IMAGE,
26
27
  )
27
28
  from pygpt_net.core.tabs.tab import Tab
28
29
  from pygpt_net.core.events import ControlEvent
@@ -6,11 +6,12 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.23 00:00:00 #
9
+ # Updated Date: 2024.11.26 02:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
13
13
  from datetime import datetime
14
+ from urllib.parse import urlparse
14
15
 
15
16
  from PySide6.QtGui import QImage
16
17
  from PySide6.QtWidgets import QFileDialog, QApplication
@@ -51,6 +52,13 @@ class Attachment:
51
52
  else:
52
53
  self.window.ui.nodes['attachments.capture_clear'].setChecked(False)
53
54
 
55
+ # auto-index
56
+ if self.window.core.config.has('attachments_auto_index') \
57
+ and self.window.core.config.get('attachments_auto_index'):
58
+ self.window.ui.nodes['attachments.auto_index'].setChecked(True)
59
+ else:
60
+ self.window.ui.nodes['attachments.auto_index'].setChecked(False)
61
+
54
62
  self.window.core.attachments.load()
55
63
  self.update()
56
64
 
@@ -224,6 +232,7 @@ class Attachment:
224
232
  mode=mode,
225
233
  remove_local=remove_local,
226
234
  auto=auto,
235
+ force=force,
227
236
  )
228
237
  self.window.controller.chat.vision.unavailable() # set no content to provide
229
238
  self.update()
@@ -268,6 +277,38 @@ class Attachment:
268
277
  self.window.core.attachments.save()
269
278
  self.update()
270
279
 
280
+ def open_add_url(self):
281
+ """Open add attachment URL dialog"""
282
+ self.window.ui.dialog['url'].id = "attachment"
283
+ self.window.ui.dialog['url'].input.setText("")
284
+ self.window.ui.dialog['url'].current = ""
285
+ self.window.ui.dialog['url'].show()
286
+ self.window.ui.dialog['url'].input.setFocus()
287
+
288
+ def add_url(self, url: str):
289
+ """
290
+ Add URL
291
+
292
+ :param url: URL
293
+ """
294
+ if not url:
295
+ return
296
+ mode = self.window.core.config.get('mode')
297
+ try:
298
+ domain = urlparse(url).netloc
299
+ except Exception as e:
300
+ domain = os.path.basename(url)
301
+ attachment = self.window.core.attachments.new(
302
+ mode=mode,
303
+ name=domain,
304
+ path=url,
305
+ auto_save=False,
306
+ type=AttachmentItem.TYPE_URL,
307
+ )
308
+ self.window.core.attachments.save()
309
+ self.update()
310
+ self.window.ui.dialog['url'].close()
311
+
271
312
  def open_dir(self, mode: str, idx: int):
272
313
  """
273
314
  Open in directory
@@ -321,6 +362,24 @@ class Attachment:
321
362
  return ''
322
363
  return data.path
323
364
 
365
+ def get_by_idx(self, mode: str, idx: int) -> str:
366
+ """
367
+ Get attachment by index
368
+
369
+ :param mode: mode
370
+ :param idx: index
371
+ :return: path
372
+ """
373
+ file_id = self.window.core.attachments.get_id_by_idx(
374
+ mode=mode,
375
+ idx=idx,
376
+ )
377
+ data = self.window.core.attachments.get_by_id(
378
+ mode=mode,
379
+ id=file_id,
380
+ )
381
+ return data
382
+
324
383
  def has(self, mode: str) -> bool:
325
384
  """
326
385
  Return True if current mode has attachments
@@ -411,6 +470,14 @@ class Attachment:
411
470
  """
412
471
  self.window.core.config.set('attachments_capture_clear', value)
413
472
 
473
+ def toggle_auto_index(self, value: bool):
474
+ """
475
+ Toggle auto index
476
+
477
+ :param value: value of the checkbox
478
+ """
479
+ self.window.core.config.set('attachments_auto_index', value)
480
+
414
481
  def is_capture_clear(self) -> bool:
415
482
  """
416
483
  Return True if capture clear is enabled
@@ -6,13 +6,12 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.20 21:00:00 #
9
+ # Updated Date: 2024.11.26 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
13
13
 
14
14
  from pygpt_net.core.events import Event, BaseEvent
15
- from pygpt_net.plugin.audio_output.worker import PlayWorker
16
15
  from pygpt_net.item.ctx import CtxItem
17
16
  from pygpt_net.utils import trans
18
17
 
@@ -64,6 +63,12 @@ class Audio:
64
63
  self.window.core.config.save()
65
64
  self.update()
66
65
 
66
+ def enable_input(self):
67
+ """Enable audio input"""
68
+ self.window.controller.plugins.enable('audio_input')
69
+ self.window.core.config.save()
70
+ self.update()
71
+
67
72
  def disable_input(self, update: bool = True):
68
73
  """
69
74
  Disable audio input
@@ -104,6 +109,16 @@ class Audio:
104
109
  return True
105
110
  return False
106
111
 
112
+ def is_input_enabled(self) -> bool:
113
+ """
114
+ Check if any audio input is enabled
115
+
116
+ :return: True if enabled
117
+ """
118
+ if self.window.controller.plugins.is_enabled('audio_input'):
119
+ return True
120
+ return False
121
+
107
122
  def update_listeners(self):
108
123
  """Update audio listeners"""
109
124
  is_output = False
@@ -161,16 +176,29 @@ class Audio:
161
176
  }
162
177
  self.window.dispatch(event, all=all)
163
178
 
179
+ def play_chat_audio(self, path: str):
180
+ """
181
+ Play audio file (chat multimodal response)
182
+
183
+ :param path: audio file path
184
+ """
185
+ if not self.is_output_enabled():
186
+ return
187
+ self.play_audio(path)
188
+
164
189
  def play_audio(self, path: str):
165
190
  """
166
191
  Play audio file
167
192
 
168
193
  :param path: audio file path
169
194
  """
170
- worker = PlayWorker()
171
- worker.window = self.window
172
- worker.path = path
173
- self.window.threadpool.start(worker)
195
+ ctx = CtxItem()
196
+ event = Event(Event.AUDIO_PLAYBACK)
197
+ event.ctx = ctx
198
+ event.data = {
199
+ 'audio_file': path,
200
+ }
201
+ self.window.dispatch(event, all=True)
174
202
 
175
203
  def play_sound(self, filename: str):
176
204
  """
@@ -6,13 +6,14 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.23 00:00:00 #
9
+ # Updated Date: 2024.11.26 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.events import AppEvent
13
13
  from pygpt_net.item.ctx import CtxItem
14
14
 
15
15
  from .attachment import Attachment
16
+ from .audio import Audio
16
17
  from .command import Command
17
18
  from .common import Common
18
19
  from .files import Files
@@ -35,6 +36,7 @@ class Chat:
35
36
  """
36
37
  self.window = window
37
38
  self.attachment = Attachment(window)
39
+ self.audio = Audio(window)
38
40
  self.command = Command(window)
39
41
  self.common = Common(window)
40
42
  self.files = Files(window)
@@ -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: 2024.11.23 21:00:00 #
9
+ # Updated Date: 2024.11.26 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -114,52 +114,123 @@ class Attachment(QObject):
114
114
  :return: True if uploaded
115
115
  """
116
116
  self.uploaded = False
117
+ auto_index = self.window.core.config.get("attachments_auto_index", False)
117
118
  attachments = self.window.core.attachments.get_all(mode, only_files=True)
119
+
118
120
  if self.is_verbose() and len(attachments) > 0:
119
121
  print("\nUploading attachments...\nWork Mode: {}".format(mode))
122
+
120
123
  for uuid in attachments:
121
124
  attachment = attachments[uuid]
122
- if not self.is_allowed(attachment.path):
123
- continue # skip not allowed files
124
- if self.window.core.filesystem.packer.is_archive(attachment.path):
125
- if self.is_verbose():
126
- print("Unpacking archive: {}".format(attachment.path))
127
- tmp_path = self.window.core.filesystem.packer.unpack(attachment.path)
128
- if tmp_path:
129
- for root, dirs, files in os.walk(tmp_path):
130
- for file in files:
131
- path = os.path.join(root, file)
132
- sub_attachment = AttachmentItem()
133
- sub_attachment.path = path
134
- sub_attachment.name = os.path.basename(path)
135
- sub_attachment.consumed = False
136
- path_relative = os.path.relpath(path, tmp_path)
137
- if self.is_allowed(str(path)):
138
- if self.is_verbose():
139
- print("Uploading unpacked from archive: {}".format(path_relative))
140
- item = self.window.core.attachments.context.upload(meta, sub_attachment, prompt)
141
- if item:
142
- item["path"] = os.path.basename(attachment.path) + "/" + path_relative
143
- item["size"] = os.path.getsize(path)
144
- if meta.additional_ctx is None:
145
- meta.additional_ctx = []
146
- meta.additional_ctx.append(item)
147
- self.uploaded = True
148
- sub_attachment.consumed = True
149
- attachment.consumed = True
150
- self.window.core.filesystem.packer.remove_tmp(tmp_path) # clean
151
- else:
152
- item = self.window.core.attachments.context.upload(meta, attachment, prompt)
153
- if item:
154
- if meta.additional_ctx is None:
155
- meta.additional_ctx = []
156
- meta.additional_ctx.append(item)
157
- attachment.consumed = True # allow for deletion
125
+ if attachment.type == AttachmentItem.TYPE_FILE:
126
+ result = self.upload_file(
127
+ attachment=attachment,
128
+ meta=meta,
129
+ prompt=prompt,
130
+ auto_index=auto_index,
131
+ )
132
+ if result:
133
+ self.uploaded = True
134
+ elif attachment.type == AttachmentItem.TYPE_URL:
135
+ result = self.upload_web(
136
+ attachment=attachment,
137
+ meta=meta,
138
+ prompt=prompt,
139
+ auto_index=auto_index,
140
+ )
141
+ if result:
158
142
  self.uploaded = True
159
143
  if self.uploaded:
160
144
  self.window.core.ctx.save(meta.id) # save meta
161
145
  return self.uploaded
162
146
 
147
+ def upload_file(
148
+ self,
149
+ attachment: AttachmentItem,
150
+ meta: CtxMeta,
151
+ prompt: str,
152
+ auto_index: bool
153
+ ) -> bool:
154
+ """
155
+ Upload file attachment
156
+
157
+ :param attachment: AttachmentItem
158
+ :param meta: CtxMeta
159
+ :param prompt: User input prompt
160
+ :param auto_index: Auto index
161
+ :return: True if uploaded
162
+ """
163
+ uploaded = False
164
+ if not self.is_allowed(attachment.path):
165
+ return False
166
+ if self.window.core.filesystem.packer.is_archive(attachment.path):
167
+ if self.is_verbose():
168
+ print("Unpacking archive: {}".format(attachment.path))
169
+ tmp_path = self.window.core.filesystem.packer.unpack(attachment.path)
170
+ if tmp_path:
171
+ for root, dirs, files in os.walk(tmp_path):
172
+ for file in files:
173
+ path = os.path.join(root, file)
174
+ sub_attachment = AttachmentItem()
175
+ sub_attachment.path = path
176
+ sub_attachment.name = os.path.basename(path)
177
+ sub_attachment.consumed = False
178
+ path_relative = os.path.relpath(path, tmp_path)
179
+ if self.is_allowed(str(path)):
180
+ if self.is_verbose():
181
+ print("Uploading unpacked from archive: {}".format(path_relative))
182
+ item = self.window.core.attachments.context.upload(
183
+ meta=meta,
184
+ attachment=sub_attachment,
185
+ prompt=prompt,
186
+ real_path=attachment.path,
187
+ auto_index=auto_index,
188
+ )
189
+ if item:
190
+ item["path"] = os.path.basename(attachment.path) + "/" + path_relative
191
+ item["size"] = os.path.getsize(path)
192
+ if meta.additional_ctx is None:
193
+ meta.additional_ctx = []
194
+ meta.additional_ctx.append(item)
195
+ uploaded = True
196
+ sub_attachment.consumed = True
197
+ attachment.consumed = True
198
+ self.window.core.filesystem.packer.remove_tmp(tmp_path) # clean
199
+ else:
200
+ item = self.window.core.attachments.context.upload(
201
+ meta=meta,
202
+ attachment=attachment,
203
+ prompt=prompt,
204
+ real_path=attachment.path,
205
+ auto_index=auto_index,
206
+ )
207
+ if item:
208
+ if meta.additional_ctx is None:
209
+ meta.additional_ctx = []
210
+ meta.additional_ctx.append(item)
211
+ attachment.consumed = True # allow for deletion
212
+ uploaded = True
213
+
214
+ return uploaded
215
+
216
+ def upload_web(
217
+ self,
218
+ attachment: AttachmentItem,
219
+ meta: CtxMeta,
220
+ prompt: str,
221
+ auto_index: bool
222
+ ) -> bool:
223
+ """
224
+ Upload web attachment
225
+
226
+ :param attachment: AttachmentItem
227
+ :param meta: CtxMeta
228
+ :param prompt: User input prompt
229
+ :param auto_index: Auto index
230
+ :return: True if uploaded
231
+ """
232
+ return self.upload_file(attachment, meta, prompt, auto_index)
233
+
163
234
  def has_context(self, meta: CtxMeta) -> bool:
164
235
  """
165
236
  Check if has additional context for attachment
@@ -203,6 +274,7 @@ class Attachment(QObject):
203
274
  if self.is_verbose():
204
275
  print("\nPreparing additional context...\nContext Mode: {}".format(self.mode))
205
276
 
277
+ self.window.core.attachments.context.reset()
206
278
  if self.mode == self.MODE_FULL_CONTEXT:
207
279
  content = self.get_full_context(ctx)
208
280
  elif self.mode == self.MODE_QUERY_CONTEXT:
@@ -210,6 +282,14 @@ class Attachment(QObject):
210
282
  elif self.mode == self.MODE_QUERY_CONTEXT_SUMMARY:
211
283
  content = self.get_context_summary(ctx)
212
284
 
285
+ # append used files and urls to context
286
+ files = self.window.core.attachments.context.get_used_files()
287
+ urls = self.window.core.attachments.context.get_used_urls()
288
+ if files:
289
+ ctx.files = files
290
+ if urls:
291
+ ctx.urls = urls
292
+
213
293
  if content:
214
294
  if self.is_verbose():
215
295
  print("\n--- Using additional context ---\n\n{}".format(content))
@@ -362,6 +442,150 @@ class Attachment(QObject):
362
442
  self.window.core.attachments.context.clear(meta, delete_files=remove_local)
363
443
  self.update_list(meta)
364
444
 
445
+ def select(self, idx: int):
446
+ """
447
+ Select uploaded file
448
+
449
+ :param idx: index of file
450
+ """
451
+ pass
452
+
453
+ def open_by_idx(self, idx: int):
454
+ """
455
+ Open attachment by index
456
+
457
+ :param idx: Index on list
458
+ """
459
+ meta = self.window.core.ctx.get_current_meta()
460
+ if meta is None or meta.additional_ctx is None:
461
+ return
462
+ items = self.window.core.attachments.context.get_all(meta)
463
+ if idx < len(items):
464
+ item = items[idx]
465
+ path = item["path"]
466
+ if "real_path" in item:
467
+ path = item["real_path"]
468
+ if os.path.exists(path) and os.path.isfile(path):
469
+ print("Opening attachment: {}".format(path))
470
+ self.window.controller.files.open(path)
471
+
472
+ def open_dir_src_by_idx(self, idx: int):
473
+ """
474
+ Open source directory by index
475
+
476
+ :param idx: Index on list
477
+ """
478
+ meta = self.window.core.ctx.get_current_meta()
479
+ if meta is None or meta.additional_ctx is None:
480
+ return
481
+ items = self.window.core.attachments.context.get_all(meta)
482
+ if idx < len(items):
483
+ item = items[idx]
484
+ path = item["path"]
485
+ if "real_path" in item:
486
+ path = item["real_path"]
487
+ dir = os.path.dirname(path)
488
+ if os.path.exists(dir) and os.path.isdir(dir):
489
+ print("Opening source directory: {}".format(dir))
490
+ self.window.controller.files.open(dir)
491
+
492
+ def open_dir_dest_by_idx(self, idx: int):
493
+ """
494
+ Open destination directory by index
495
+
496
+ :param idx: Index on list
497
+ """
498
+ meta = self.window.core.ctx.get_current_meta()
499
+ if meta is None or meta.additional_ctx is None:
500
+ return
501
+ items = self.window.core.attachments.context.get_all(meta)
502
+ if idx < len(items):
503
+ item = items[idx]
504
+ root_dir = self.window.core.attachments.context.get_dir(meta)
505
+ dir = os.path.join(root_dir, item["uuid"])
506
+ if os.path.exists(dir) and os.path.isdir(dir):
507
+ self.window.controller.files.open(dir)
508
+ print("Opening destination directory: {}".format(dir))
509
+
510
+ def has_file_by_idx(self, idx: int) -> bool:
511
+ """
512
+ Check if has file by index
513
+
514
+ :param idx: Index on list
515
+ :return: True if has file
516
+ """
517
+ meta = self.window.core.ctx.get_current_meta()
518
+ if meta is None or meta.additional_ctx is None:
519
+ return False
520
+ items = self.window.core.attachments.context.get_all(meta)
521
+ if idx < len(items):
522
+ item = items[idx]
523
+ path = item["path"]
524
+ if "real_path" in item:
525
+ path = item["real_path"]
526
+ return os.path.exists(path) and os.path.isfile(path)
527
+ return False
528
+
529
+ def has_src_by_idx(self, idx: int) -> bool:
530
+ """
531
+ Check if has source directory by index
532
+
533
+ :param idx: Index on list
534
+ :return: True if has source directory
535
+ """
536
+ meta = self.window.core.ctx.get_current_meta()
537
+ if meta is None or meta.additional_ctx is None:
538
+ return False
539
+ items = self.window.core.attachments.context.get_all(meta)
540
+ if idx < len(items):
541
+ item = items[idx]
542
+ path = item["path"]
543
+ if "real_path" in item:
544
+ path = item["real_path"]
545
+ dir = os.path.dirname(path)
546
+ return os.path.exists(dir) and os.path.isdir(dir)
547
+ return False
548
+
549
+ def has_dest_by_idx(self, idx: int) -> bool:
550
+ """
551
+ Check if has destination directory by index
552
+
553
+ :param idx: Index on list
554
+ :return: True if has destination directory
555
+ """
556
+ meta = self.window.core.ctx.get_current_meta()
557
+ if meta is None or meta.additional_ctx is None:
558
+ return False
559
+ items = self.window.core.attachments.context.get_all(meta)
560
+ if idx < len(items):
561
+ item = items[idx]
562
+ root_dir = self.window.core.attachments.context.get_dir(meta)
563
+ dir = os.path.join(root_dir, item["uuid"])
564
+ return os.path.exists(dir) and os.path.isdir(dir)
565
+ return False
566
+
567
+ def get_current_tokens(self) -> int:
568
+ """
569
+ Get current tokens
570
+
571
+ :return: Current attachments tokens
572
+ """
573
+ if self.mode != self.MODE_FULL_CONTEXT:
574
+ return 0
575
+ meta = self.window.core.ctx.get_current_meta()
576
+ if meta is None:
577
+ return 0
578
+ if meta.additional_ctx is None:
579
+ return 0
580
+ tokens = 0
581
+ for item in meta.additional_ctx:
582
+ if "tokens" in item:
583
+ try:
584
+ tokens += int(item["tokens"])
585
+ except Exception as e:
586
+ pass
587
+ return tokens
588
+
365
589
  @Slot(object)
366
590
  def handle_upload_error(self, error: Exception):
367
591
  """
@@ -397,4 +621,5 @@ class Attachment(QObject):
397
621
  """
398
622
  self.mode = mode
399
623
  self.window.core.config.set("ctx.attachment.mode", mode)
400
- self.window.core.config.save()
624
+ self.window.core.config.save()
625
+ self.window.controller.ui.update_tokens()