pygpt-net 2.4.28__py3-none-any.whl → 2.4.34__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.
- CHANGELOG.md +40 -0
- README.md +62 -5
- pygpt_net/CHANGELOG.txt +40 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/controller/access/__init__.py +5 -5
- pygpt_net/controller/access/control.py +3 -2
- pygpt_net/controller/attachment.py +67 -1
- pygpt_net/controller/audio/__init__.py +34 -6
- pygpt_net/controller/chat/__init__.py +3 -1
- pygpt_net/controller/chat/attachment.py +239 -37
- pygpt_net/controller/chat/audio.py +99 -0
- pygpt_net/controller/chat/input.py +10 -3
- pygpt_net/controller/chat/output.py +4 -1
- pygpt_net/controller/chat/text.py +10 -5
- pygpt_net/controller/dialogs/confirm.py +17 -1
- pygpt_net/controller/kernel/reply.py +5 -8
- pygpt_net/controller/lang/custom.py +3 -1
- pygpt_net/controller/mode.py +2 -1
- pygpt_net/controller/presets/editor.py +11 -2
- pygpt_net/core/access/voice.py +2 -2
- pygpt_net/core/agents/legacy.py +3 -1
- pygpt_net/core/attachments/__init__.py +11 -7
- pygpt_net/core/attachments/context.py +226 -44
- pygpt_net/core/{audio.py → audio/__init__.py} +1 -1
- pygpt_net/core/audio/context.py +34 -0
- pygpt_net/core/bridge/context.py +29 -1
- pygpt_net/core/bridge/worker.py +16 -1
- pygpt_net/core/ctx/__init__.py +4 -1
- pygpt_net/core/db/__init__.py +4 -2
- pygpt_net/core/debug/attachments.py +3 -1
- pygpt_net/core/debug/context.py +5 -1
- pygpt_net/core/debug/presets.py +3 -1
- pygpt_net/core/docker/__init__.py +170 -16
- pygpt_net/core/docker/builder.py +6 -2
- pygpt_net/core/events/event.py +3 -1
- pygpt_net/core/experts/__init__.py +24 -6
- pygpt_net/core/idx/chat.py +55 -4
- pygpt_net/core/idx/indexing.py +123 -15
- pygpt_net/core/modes.py +3 -1
- pygpt_net/core/presets.py +13 -2
- pygpt_net/core/render/markdown/pid.py +2 -1
- pygpt_net/core/render/plain/pid.py +2 -1
- pygpt_net/core/render/web/body.py +34 -12
- pygpt_net/core/render/web/pid.py +2 -1
- pygpt_net/core/render/web/renderer.py +12 -3
- pygpt_net/core/tokens.py +4 -2
- pygpt_net/core/types/mode.py +2 -1
- pygpt_net/data/config/config.json +7 -4
- pygpt_net/data/config/models.json +191 -6
- pygpt_net/data/config/modes.json +11 -5
- pygpt_net/data/config/presets/current.audio.json +34 -0
- pygpt_net/data/config/settings.json +15 -1
- pygpt_net/data/css/web.css +70 -0
- pygpt_net/data/css/web.dark.css +4 -1
- pygpt_net/data/css/web.light.css +1 -1
- pygpt_net/data/locale/locale.de.ini +33 -20
- pygpt_net/data/locale/locale.en.ini +73 -58
- pygpt_net/data/locale/locale.es.ini +33 -20
- pygpt_net/data/locale/locale.fr.ini +35 -22
- pygpt_net/data/locale/locale.it.ini +33 -20
- pygpt_net/data/locale/locale.pl.ini +36 -23
- pygpt_net/data/locale/locale.uk.ini +33 -20
- pygpt_net/data/locale/locale.zh.ini +40 -27
- pygpt_net/data/locale/plugin.cmd_code_interpreter.de.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.en.ini +15 -7
- pygpt_net/data/locale/plugin.cmd_code_interpreter.es.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.fr.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.it.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.pl.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.uk.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_code_interpreter.zh.ini +6 -0
- pygpt_net/data/locale/plugin.cmd_files.de.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.en.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.es.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.fr.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.it.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.pl.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.uk.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_files.zh.ini +4 -4
- pygpt_net/data/locale/plugin.cmd_system.de.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_system.en.ini +12 -6
- pygpt_net/data/locale/plugin.cmd_system.es.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_system.fr.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_system.it.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_system.pl.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_system.uk.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_system.zh.ini +6 -6
- pygpt_net/data/locale/plugin.cmd_web.de.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.en.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.es.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.fr.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.it.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.pl.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.uk.ini +5 -5
- pygpt_net/data/locale/plugin.cmd_web.zh.ini +5 -5
- pygpt_net/data/locale/plugin.idx_llama_index.de.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.en.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.es.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.it.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +12 -12
- pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +12 -12
- pygpt_net/item/attachment.py +9 -1
- pygpt_net/item/ctx.py +9 -1
- pygpt_net/item/preset.py +5 -1
- pygpt_net/launcher.py +3 -1
- pygpt_net/migrations/Version20241126170000.py +28 -0
- pygpt_net/migrations/__init__.py +3 -1
- pygpt_net/plugin/audio_input/__init__.py +11 -1
- pygpt_net/plugin/audio_input/worker.py +9 -1
- pygpt_net/plugin/audio_output/__init__.py +37 -7
- pygpt_net/plugin/audio_output/worker.py +38 -41
- pygpt_net/plugin/cmd_code_interpreter/__init__.py +51 -35
- pygpt_net/plugin/cmd_code_interpreter/builder.py +16 -4
- pygpt_net/plugin/cmd_code_interpreter/config.py +98 -39
- pygpt_net/plugin/cmd_code_interpreter/docker.py +4 -0
- pygpt_net/plugin/cmd_code_interpreter/ipython/__init__.py +13 -0
- pygpt_net/plugin/cmd_code_interpreter/{ipython.py → ipython/docker_kernel.py} +10 -3
- pygpt_net/plugin/cmd_code_interpreter/ipython/local_kernel.py +220 -0
- pygpt_net/plugin/cmd_code_interpreter/runner.py +5 -5
- pygpt_net/plugin/cmd_mouse_control/__init__.py +4 -2
- pygpt_net/plugin/cmd_system/config.py +50 -0
- pygpt_net/plugin/cmd_system/docker.py +4 -0
- pygpt_net/plugin/idx_llama_index/__init__.py +23 -1
- pygpt_net/plugin/idx_llama_index/worker.py +10 -0
- pygpt_net/plugin/openai_dalle/__init__.py +3 -1
- pygpt_net/plugin/openai_vision/__init__.py +3 -1
- pygpt_net/provider/core/attachment/json_file.py +4 -1
- pygpt_net/provider/core/config/patch.py +25 -0
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +14 -4
- pygpt_net/provider/core/ctx/db_sqlite/utils.py +19 -2
- pygpt_net/provider/core/model/patch.py +7 -1
- pygpt_net/provider/core/preset/json_file.py +5 -1
- pygpt_net/provider/gpt/__init__.py +14 -2
- pygpt_net/provider/gpt/audio.py +63 -0
- pygpt_net/provider/gpt/chat.py +76 -44
- pygpt_net/provider/gpt/utils.py +27 -0
- pygpt_net/provider/gpt/vision.py +37 -15
- pygpt_net/provider/loaders/base.py +10 -1
- pygpt_net/provider/loaders/web_yt.py +19 -1
- pygpt_net/tools/code_interpreter/__init__.py +1 -0
- pygpt_net/tools/image_viewer/ui/dialogs.py +3 -1
- pygpt_net/ui/dialog/preset.py +3 -1
- pygpt_net/ui/dialog/url.py +29 -0
- pygpt_net/ui/dialogs.py +5 -1
- pygpt_net/ui/layout/chat/attachments.py +42 -6
- pygpt_net/ui/layout/chat/attachments_ctx.py +14 -4
- pygpt_net/ui/layout/chat/attachments_uploaded.py +8 -4
- pygpt_net/ui/layout/toolbox/agent.py +8 -7
- pygpt_net/ui/layout/toolbox/agent_llama.py +5 -4
- pygpt_net/ui/layout/toolbox/prompt.py +8 -6
- pygpt_net/ui/menu/tools.py +17 -11
- pygpt_net/ui/widget/anims/toggles.py +167 -0
- pygpt_net/ui/widget/dialog/url.py +59 -0
- pygpt_net/ui/widget/element/group.py +2 -1
- pygpt_net/ui/widget/lists/attachment.py +22 -17
- pygpt_net/ui/widget/lists/attachment_ctx.py +65 -3
- pygpt_net/ui/widget/option/checkbox.py +69 -5
- pygpt_net/ui/widget/option/cmd.py +4 -5
- pygpt_net/ui/widget/option/toggle.py +62 -0
- pygpt_net/ui/widget/option/toggle_label.py +79 -0
- pygpt_net/ui/widget/textarea/url.py +43 -0
- {pygpt_net-2.4.28.dist-info → pygpt_net-2.4.34.dist-info}/METADATA +65 -7
- {pygpt_net-2.4.28.dist-info → pygpt_net-2.4.34.dist-info}/RECORD +168 -154
- {pygpt_net-2.4.28.dist-info → pygpt_net-2.4.34.dist-info}/LICENSE +0 -0
- {pygpt_net-2.4.28.dist-info → pygpt_net-2.4.34.dist-info}/WHEEL +0 -0
- {pygpt_net-2.4.28.dist-info → pygpt_net-2.4.34.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: 2024.11.
|
9
|
+
# Updated Date: 2024.11.26 19:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from PySide6.QtCore import Qt
|
@@ -14,6 +14,7 @@ from PySide6.QtCore import Qt
|
|
14
14
|
from pygpt_net.core.types import (
|
15
15
|
MODE_AGENT,
|
16
16
|
MODE_AGENT_LLAMA,
|
17
|
+
MODE_AUDIO,
|
17
18
|
MODE_CHAT,
|
18
19
|
MODE_COMPLETION,
|
19
20
|
MODE_EXPERT,
|
@@ -63,6 +64,7 @@ class Custom:
|
|
63
64
|
self.window.ui.config['preset'][MODE_AGENT].box.setText(trans("preset.agent"))
|
64
65
|
self.window.ui.config['preset'][MODE_AGENT_LLAMA].box.setText(trans("preset.agent_llama"))
|
65
66
|
self.window.ui.config['preset'][MODE_EXPERT].box.setText(trans("preset.expert"))
|
67
|
+
self.window.ui.config['preset'][MODE_AUDIO].box.setText(trans("preset.audio"))
|
66
68
|
|
67
69
|
self.window.ui.config['global']['img_raw'].setText(trans("img.raw"))
|
68
70
|
|
pygpt_net/controller/mode.py
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
7
|
# MIT License #
|
8
8
|
# Created By : Marcin Szczygliński #
|
9
|
-
# Updated Date: 2024.11.
|
9
|
+
# Updated Date: 2024.11.26 19:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from pygpt_net.core.events import Event, AppEvent
|
@@ -41,6 +41,7 @@ class Mode:
|
|
41
41
|
self.window.dispatch(event)
|
42
42
|
self.window.controller.attachment.update()
|
43
43
|
self.window.controller.chat.attachment.update()
|
44
|
+
self.window.controller.chat.audio.update()
|
44
45
|
self.window.dispatch(AppEvent(AppEvent.MODE_SELECTED)) # app event
|
45
46
|
|
46
47
|
def set(self, mode: str):
|
@@ -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.
|
9
|
+
# Updated Date: 2024.11.26 19:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
import datetime
|
@@ -16,12 +16,14 @@ from pygpt_net.core.types import (
|
|
16
16
|
MODE_AGENT,
|
17
17
|
MODE_AGENT_LLAMA,
|
18
18
|
MODE_ASSISTANT,
|
19
|
+
MODE_AUDIO,
|
19
20
|
MODE_CHAT,
|
20
21
|
MODE_COMPLETION,
|
21
22
|
MODE_EXPERT,
|
22
23
|
MODE_LANGCHAIN,
|
23
24
|
MODE_LLAMA_INDEX,
|
24
|
-
MODE_VISION,
|
25
|
+
MODE_VISION,
|
26
|
+
MODE_IMAGE,
|
25
27
|
)
|
26
28
|
from pygpt_net.item.preset import PresetItem
|
27
29
|
from pygpt_net.utils import trans
|
@@ -87,6 +89,10 @@ class Editor:
|
|
87
89
|
"type": "bool",
|
88
90
|
"label": "preset.agent",
|
89
91
|
},
|
92
|
+
MODE_AUDIO: {
|
93
|
+
"type": "bool",
|
94
|
+
"label": "preset.audio",
|
95
|
+
},
|
90
96
|
# "assistant": {
|
91
97
|
# "type": "bool",
|
92
98
|
# "label": "preset.assistant",
|
@@ -287,6 +293,8 @@ class Editor:
|
|
287
293
|
data.agent = True
|
288
294
|
elif mode == MODE_AGENT_LLAMA:
|
289
295
|
data.agent_llama = True
|
296
|
+
elif mode == MODE_AUDIO:
|
297
|
+
data.audio = True
|
290
298
|
|
291
299
|
options = {}
|
292
300
|
data_dict = data.to_dict()
|
@@ -347,6 +355,7 @@ class Editor:
|
|
347
355
|
MODE_LLAMA_INDEX,
|
348
356
|
MODE_EXPERT,
|
349
357
|
MODE_AGENT_LLAMA,
|
358
|
+
MODE_AUDIO,
|
350
359
|
]
|
351
360
|
|
352
361
|
# disallow editing default preset
|
pygpt_net/core/access/voice.py
CHANGED
@@ -57,8 +57,8 @@ class Voice:
|
|
57
57
|
ControlEvent.CTX_SEARCH_CLEAR: "Clear the search results",
|
58
58
|
ControlEvent.INPUT_SEND: "Send the message to input",
|
59
59
|
ControlEvent.INPUT_APPEND: "Append message to current input without sending it",
|
60
|
-
ControlEvent.MODE_CHAT: "Switch to
|
61
|
-
ControlEvent.MODE_LLAMA_INDEX: "Switch to
|
60
|
+
ControlEvent.MODE_CHAT: "Switch to Chat mode",
|
61
|
+
ControlEvent.MODE_LLAMA_INDEX: "Switch to Chat with Files (LlamaIndex) mode",
|
62
62
|
ControlEvent.MODE_NEXT: "Switch to the next mode",
|
63
63
|
ControlEvent.MODE_PREV: "Switch to the previous mode",
|
64
64
|
ControlEvent.MODEL_NEXT: "Switch to the next model",
|
pygpt_net/core/agents/legacy.py
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
7
|
# MIT License #
|
8
8
|
# Created By : Marcin Szczygliński #
|
9
|
-
# Updated Date: 2024.11.
|
9
|
+
# Updated Date: 2024.11.26 19:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from pygpt_net.core.types import (
|
@@ -15,6 +15,7 @@ from pygpt_net.core.types import (
|
|
15
15
|
MODE_LANGCHAIN,
|
16
16
|
MODE_LLAMA_INDEX,
|
17
17
|
MODE_VISION,
|
18
|
+
MODE_AUDIO,
|
18
19
|
)
|
19
20
|
|
20
21
|
class Legacy:
|
@@ -31,6 +32,7 @@ class Legacy:
|
|
31
32
|
MODE_VISION,
|
32
33
|
MODE_LANGCHAIN,
|
33
34
|
MODE_LLAMA_INDEX,
|
35
|
+
MODE_AUDIO,
|
34
36
|
]
|
35
37
|
|
36
38
|
def get_allowed_modes(self) -> list:
|
@@ -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.
|
9
|
+
# Updated Date: 2024.11.26 02:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
import copy
|
@@ -176,7 +176,8 @@ class Attachments:
|
|
176
176
|
mode: str,
|
177
177
|
name: str = None,
|
178
178
|
path: str = None,
|
179
|
-
auto_save: bool = True
|
179
|
+
auto_save: bool = True,
|
180
|
+
type: str = AttachmentItem.TYPE_FILE,
|
180
181
|
) -> AttachmentItem:
|
181
182
|
"""
|
182
183
|
Create new attachment
|
@@ -185,16 +186,19 @@ class Attachments:
|
|
185
186
|
:param name: name
|
186
187
|
:param path: path
|
187
188
|
:param auto_save: auto_save
|
189
|
+
:param type: type
|
188
190
|
:return: AttachmentItem
|
189
191
|
"""
|
190
192
|
# make local copy of external attachment if enabled
|
191
|
-
if
|
192
|
-
if
|
193
|
-
|
193
|
+
if type == AttachmentItem.TYPE_FILE and path is not None:
|
194
|
+
if self.window.core.config.get("upload.store"):
|
195
|
+
if not self.window.core.filesystem.in_work_dir(path):
|
196
|
+
path = self.window.core.filesystem.store_upload(path)
|
194
197
|
|
195
198
|
attachment = self.create()
|
196
199
|
attachment.name = name
|
197
200
|
attachment.path = path
|
201
|
+
attachment.type = type
|
198
202
|
|
199
203
|
if mode not in self.items:
|
200
204
|
self.items[mode] = {}
|
@@ -410,7 +414,7 @@ class Attachments:
|
|
410
414
|
for mode in self.items:
|
411
415
|
for id in self.items[mode]:
|
412
416
|
attachment = self.items[mode][id]
|
413
|
-
if attachment.path is not None:
|
417
|
+
if attachment.path is not None and attachment.type == AttachmentItem.TYPE_FILE:
|
414
418
|
attachment.path = self.window.core.filesystem.to_workdir(
|
415
419
|
attachment.path,
|
416
420
|
)
|
@@ -422,7 +426,7 @@ class Attachments:
|
|
422
426
|
for mode in data:
|
423
427
|
for id in data[mode]:
|
424
428
|
attachment = data[mode][id]
|
425
|
-
if attachment.path is not None:
|
429
|
+
if attachment.path is not None and attachment.type == AttachmentItem.TYPE_FILE:
|
426
430
|
attachment.path = self.window.core.filesystem.make_local(
|
427
431
|
attachment.path,
|
428
432
|
)
|
@@ -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.
|
9
|
+
# Updated Date: 2024.11.26 04:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
import copy
|
@@ -31,6 +31,11 @@ class Context:
|
|
31
31
|
"""
|
32
32
|
self.window = window
|
33
33
|
self.dir_index = "index"
|
34
|
+
self.last_used_item = None
|
35
|
+
self.last_used_content = None
|
36
|
+
self.last_used_context = None
|
37
|
+
self.last_files = []
|
38
|
+
self.last_urls = []
|
34
39
|
self.summary_prompt = """
|
35
40
|
Summarize the text below by extracting the most important information,
|
36
41
|
especially those that may help answer the question:
|
@@ -87,16 +92,38 @@ class Context:
|
|
87
92
|
context = ""
|
88
93
|
if os.path.exists(meta_path) and os.path.isdir(meta_path):
|
89
94
|
for file in meta.additional_ctx:
|
90
|
-
if "type" not in file
|
95
|
+
if ("type" not in file
|
96
|
+
or file["type"] not in ["local_file", "url"]):
|
91
97
|
continue
|
92
98
|
file_id = file["uuid"]
|
93
99
|
file_idx_path = os.path.join(meta_path, file_id)
|
94
100
|
text_path = os.path.join(file_idx_path, file_id + ".txt")
|
101
|
+
store_path = file["path"]
|
102
|
+
if "real_path" in file:
|
103
|
+
store_path = file["real_path"]
|
95
104
|
if filename:
|
96
|
-
|
105
|
+
if file["type"] == "url":
|
106
|
+
context += "URL: {}\n".format(file["path"]) + "\n"
|
107
|
+
else:
|
108
|
+
context += "Filename: {}\n".format(file["name"]) + "\n"
|
109
|
+
|
110
|
+
# store used files and URLs in ctx
|
111
|
+
if file["type"] == "url":
|
112
|
+
if store_path not in self.last_urls:
|
113
|
+
self.last_urls.append(store_path)
|
114
|
+
else:
|
115
|
+
if store_path not in self.last_files:
|
116
|
+
self.last_files.append(store_path)
|
117
|
+
|
97
118
|
if os.path.exists(text_path):
|
98
|
-
|
99
|
-
|
119
|
+
try:
|
120
|
+
with open(text_path, "r", encoding="utf-8") as f:
|
121
|
+
context += f.read() + "\n\n"
|
122
|
+
except Exception as e:
|
123
|
+
print("Attachments: read error: {}".format(e))
|
124
|
+
|
125
|
+
self.last_used_content = context
|
126
|
+
self.last_used_context = context
|
100
127
|
return context
|
101
128
|
|
102
129
|
def query_context(self, meta: CtxMeta, query: str) -> str:
|
@@ -111,14 +138,55 @@ class Context:
|
|
111
138
|
if not os.path.exists(meta_path) or not os.path.isdir(meta_path):
|
112
139
|
return ""
|
113
140
|
idx_path = os.path.join(self.get_dir(meta), self.dir_index)
|
114
|
-
|
115
|
-
|
141
|
+
|
142
|
+
indexed = False
|
143
|
+
# index files if not indexed by auto_index
|
144
|
+
for i, file in enumerate(meta.additional_ctx):
|
145
|
+
if "indexed" not in file or not file["indexed"]:
|
146
|
+
file_id = file["uuid"]
|
147
|
+
file_idx_path = os.path.join(meta_path, file_id)
|
148
|
+
file_path = os.path.join(file_idx_path, file["name"])
|
149
|
+
type = AttachmentItem.TYPE_FILE
|
150
|
+
source = file_path
|
151
|
+
if "type" in file:
|
152
|
+
if file["type"] == "url":
|
153
|
+
type = AttachmentItem.TYPE_URL
|
154
|
+
source = file["path"] # URL
|
155
|
+
doc_ids = self.index_attachment(type, source, idx_path)
|
156
|
+
file["indexed"] = True
|
157
|
+
file["doc_ids"] = doc_ids
|
158
|
+
indexed = True
|
159
|
+
|
160
|
+
if indexed:
|
161
|
+
# update ctx in DB
|
162
|
+
self.window.core.ctx.replace(meta)
|
163
|
+
self.window.core.ctx.save(meta.id)
|
164
|
+
|
165
|
+
model, model_item = self.get_selected_model("query")
|
166
|
+
result = self.window.core.idx.chat.query_attachment(query, idx_path, model_item)
|
167
|
+
self.last_used_context = result
|
116
168
|
|
117
169
|
if self.is_verbose():
|
118
170
|
print("Attachments: query result: {}".format(result))
|
119
171
|
|
120
172
|
return result
|
121
173
|
|
174
|
+
def get_selected_model(self, mode: str = "summary"):
|
175
|
+
"""
|
176
|
+
Get selected model for attachments
|
177
|
+
|
178
|
+
:return: model name, model item
|
179
|
+
"""
|
180
|
+
model_item = None
|
181
|
+
model = None
|
182
|
+
if mode == "summary":
|
183
|
+
model = self.window.core.config.get("ctx.attachment.summary.model", "gpt-4o-mini")
|
184
|
+
elif mode == "query":
|
185
|
+
model = self.window.core.config.get("ctx.attachment.query.model", "gpt-4o-mini")
|
186
|
+
if model:
|
187
|
+
model_item = self.window.core.models.get(model)
|
188
|
+
return model, model_item
|
189
|
+
|
122
190
|
def summary_context(self, ctx: CtxItem, query: str) -> str:
|
123
191
|
"""
|
124
192
|
Get summary of the context
|
@@ -127,20 +195,17 @@ class Context:
|
|
127
195
|
:param query: query string
|
128
196
|
:return: query result
|
129
197
|
"""
|
130
|
-
model_item =
|
131
|
-
model = self.window.core.config.get("ctx.attachment.summary.model", "gpt-4o-mini")
|
132
|
-
if model:
|
133
|
-
model_item = self.window.core.models.get(model)
|
134
|
-
|
198
|
+
model, model_item = self.get_selected_model("summary")
|
135
199
|
if model_item is None:
|
136
200
|
raise Exception("Attachments: summary model not found: {}".format(model))
|
137
201
|
|
138
202
|
if self.is_verbose():
|
139
203
|
print("Attachments: using summary model: {}".format(model))
|
140
204
|
|
205
|
+
content = self.get_context_text(ctx, filename=True)
|
141
206
|
prompt = self.summary_prompt.format(
|
142
207
|
query=str(query).strip(),
|
143
|
-
content=str(
|
208
|
+
content=str(content).strip(),
|
144
209
|
)
|
145
210
|
if self.is_verbose():
|
146
211
|
print("Attachments: summary prompt: {}".format(prompt))
|
@@ -158,17 +223,27 @@ class Context:
|
|
158
223
|
})
|
159
224
|
self.window.dispatch(event)
|
160
225
|
response = event.data.get("response")
|
226
|
+
self.last_used_context = response
|
161
227
|
if self.is_verbose():
|
162
228
|
print("Attachments: summary received: {}".format(response))
|
163
229
|
return response
|
164
230
|
|
165
|
-
def upload(
|
231
|
+
def upload(
|
232
|
+
self,
|
233
|
+
meta: CtxMeta,
|
234
|
+
attachment: AttachmentItem,
|
235
|
+
prompt: str,
|
236
|
+
auto_index: bool = False,
|
237
|
+
real_path: str = None
|
238
|
+
) -> dict:
|
166
239
|
"""
|
167
240
|
Upload attachment for context
|
168
241
|
|
169
242
|
:param meta: CtxMeta instance
|
170
243
|
:param attachment: AttachmentItem instance
|
171
244
|
:param prompt: user input prompt
|
245
|
+
:param auto_index: auto index
|
246
|
+
:param real_path: real path
|
172
247
|
:return: Dict with attachment data
|
173
248
|
"""
|
174
249
|
if self.is_verbose():
|
@@ -180,64 +255,147 @@ class Context:
|
|
180
255
|
meta_path = self.get_dir(meta)
|
181
256
|
file_idx_path = os.path.join(meta_path, file_id)
|
182
257
|
index_path = os.path.join(meta_path, self.dir_index)
|
258
|
+
|
183
259
|
os.makedirs(meta_path, exist_ok=True)
|
184
260
|
os.makedirs(file_idx_path, exist_ok=True)
|
185
261
|
|
186
262
|
if self.is_verbose():
|
187
263
|
print("Attachments: created path: {}".format(meta_path))
|
188
|
-
|
264
|
+
if auto_index:
|
265
|
+
print("Attachments: vector index path: {}".format(index_path))
|
189
266
|
|
190
|
-
#
|
191
|
-
|
192
|
-
if os.path.exists(raw_path):
|
193
|
-
os.remove(raw_path)
|
194
|
-
copyfile(attachment.path, raw_path)
|
267
|
+
# store content to read
|
268
|
+
src_file = self.store_content(attachment, file_idx_path)
|
195
269
|
|
196
270
|
# extract text content using data loader
|
197
|
-
|
198
|
-
|
199
|
-
} # extra loader kwargs
|
200
|
-
text = self.window.core.idx.indexing.read_text_content(
|
201
|
-
path=raw_path,
|
202
|
-
loader_kwargs=loader_kwargs,
|
203
|
-
)
|
204
|
-
if text:
|
271
|
+
content = self.read_content(attachment, src_file, prompt)
|
272
|
+
if content:
|
205
273
|
text_path = os.path.join(file_idx_path, file_id + ".txt")
|
206
|
-
with open(text_path, "w") as f:
|
207
|
-
f.write(
|
208
|
-
|
274
|
+
with open(text_path, "w", encoding="utf-8") as f:
|
275
|
+
f.write(content)
|
209
276
|
if self.is_verbose():
|
210
|
-
print("Attachments: read text content: {}".format(
|
277
|
+
print("Attachments: read text content: {}".format(content))
|
211
278
|
|
212
279
|
tokens = 0
|
213
|
-
if
|
214
|
-
tokens = self.window.core.tokens.from_str(
|
280
|
+
if content:
|
281
|
+
tokens = self.window.core.tokens.from_str(content)
|
215
282
|
|
216
|
-
|
217
|
-
|
218
|
-
|
283
|
+
type = "local_file"
|
284
|
+
size = 0
|
285
|
+
if attachment.type == AttachmentItem.TYPE_FILE:
|
286
|
+
size = os.path.getsize(attachment.path)
|
287
|
+
elif attachment.type == AttachmentItem.TYPE_URL:
|
288
|
+
size = os.path.getsize(src_file)
|
289
|
+
type = "url" # extra ctx type
|
219
290
|
|
220
|
-
|
221
|
-
|
291
|
+
# index file to ctx index
|
292
|
+
doc_ids = []
|
293
|
+
if auto_index:
|
294
|
+
source = src_file
|
295
|
+
if attachment.type == AttachmentItem.TYPE_URL:
|
296
|
+
source = attachment.path # URL
|
297
|
+
doc_ids = self.index_attachment(attachment.type, source, index_path)
|
222
298
|
|
223
299
|
result = {
|
224
300
|
"name": name,
|
225
301
|
"path": attachment.path,
|
226
|
-
"type":
|
302
|
+
"type": type,
|
227
303
|
"uuid": str(file_id),
|
228
|
-
"doc_ids": doc_ids,
|
229
|
-
"indexed": True,
|
230
304
|
"content_type": "text",
|
231
|
-
"size":
|
232
|
-
"length": len(
|
305
|
+
"size": size,
|
306
|
+
"length": len(content),
|
233
307
|
"tokens": tokens,
|
308
|
+
"indexed": False,
|
234
309
|
}
|
310
|
+
if auto_index:
|
311
|
+
result["indexed"] = True
|
312
|
+
result["doc_ids"] = doc_ids
|
313
|
+
|
314
|
+
if real_path:
|
315
|
+
result["real_path"] = real_path
|
235
316
|
|
236
317
|
if self.is_verbose():
|
237
318
|
print("Attachments: uploaded: {}".format(result))
|
238
319
|
|
239
320
|
return result
|
240
321
|
|
322
|
+
def read_content(self, attachment: AttachmentItem, path: str, prompt: str) -> str:
|
323
|
+
"""
|
324
|
+
Read content from attachment
|
325
|
+
|
326
|
+
:param attachment: AttachmentItem instance
|
327
|
+
:param path: source file path
|
328
|
+
:param prompt: user input prompt
|
329
|
+
:return: content
|
330
|
+
"""
|
331
|
+
content = ""
|
332
|
+
if attachment.type == AttachmentItem.TYPE_FILE:
|
333
|
+
loader_kwargs = {
|
334
|
+
"prompt": prompt,
|
335
|
+
} # extra loader kwargs
|
336
|
+
content = self.window.core.idx.indexing.read_text_content(
|
337
|
+
path=path,
|
338
|
+
loader_kwargs=loader_kwargs,
|
339
|
+
)
|
340
|
+
elif attachment.type == AttachmentItem.TYPE_URL:
|
341
|
+
# directly from path
|
342
|
+
with open(path, "r", encoding="utf-8") as f:
|
343
|
+
content = f.read() # already crawled
|
344
|
+
|
345
|
+
return content
|
346
|
+
|
347
|
+
def store_content(self, attachment: AttachmentItem, dir: str) -> str:
|
348
|
+
"""
|
349
|
+
Prepare content for attachment
|
350
|
+
|
351
|
+
:param attachment: AttachmentItem instance
|
352
|
+
:param dir: directory to save content
|
353
|
+
:return: content
|
354
|
+
"""
|
355
|
+
path = None
|
356
|
+
if attachment.type == AttachmentItem.TYPE_FILE:
|
357
|
+
# copy raw file
|
358
|
+
name = os.path.basename(attachment.path)
|
359
|
+
path = os.path.join(dir, name)
|
360
|
+
if os.path.exists(path):
|
361
|
+
os.remove(path)
|
362
|
+
copyfile(attachment.path, path)
|
363
|
+
elif attachment.type == AttachmentItem.TYPE_URL:
|
364
|
+
web_type = self.window.core.idx.indexing.get_webtype(attachment.path)
|
365
|
+
content = self.window.core.idx.indexing.read_web_content(
|
366
|
+
url=attachment.path,
|
367
|
+
type=web_type, # webpage, default, TODO: add more types
|
368
|
+
extra_args={},
|
369
|
+
)
|
370
|
+
# src file save
|
371
|
+
name = "url.txt"
|
372
|
+
path = os.path.join(dir, name)
|
373
|
+
if os.path.exists(path):
|
374
|
+
os.remove(path)
|
375
|
+
with open(path, "w", encoding="utf-8") as f:
|
376
|
+
f.write(content)
|
377
|
+
return path
|
378
|
+
|
379
|
+
def index_attachment(self, type: str, source: str, idx_path: str, documents: list = None) -> list:
|
380
|
+
"""
|
381
|
+
Index attachment
|
382
|
+
|
383
|
+
:param type: attachment type
|
384
|
+
:param source: source file or URL
|
385
|
+
:param idx_path: index path
|
386
|
+
:param documents: list of documents (optional)
|
387
|
+
:return: list of doc IDs
|
388
|
+
"""
|
389
|
+
model = None
|
390
|
+
doc_ids = []
|
391
|
+
if type == AttachmentItem.TYPE_FILE:
|
392
|
+
doc_ids = self.window.core.idx.indexing.index_attachment(source, idx_path, model, documents)
|
393
|
+
elif type == AttachmentItem.TYPE_URL:
|
394
|
+
doc_ids = self.window.core.idx.indexing.index_attachment_web(source, idx_path, model, documents)
|
395
|
+
if self.is_verbose():
|
396
|
+
print("Attachments: indexed. Doc IDs: {}".format(doc_ids))
|
397
|
+
return doc_ids
|
398
|
+
|
241
399
|
def duplicate(self, from_meta_id: int, to_meta_id: int) -> bool:
|
242
400
|
"""
|
243
401
|
Duplicate attachments from one meta to another
|
@@ -390,6 +548,30 @@ class Context:
|
|
390
548
|
except Exception as e:
|
391
549
|
self.window.core.debug.error("Attachment.truncate", e)
|
392
550
|
|
551
|
+
def reset(self):
|
552
|
+
"""Reset context info"""
|
553
|
+
self.last_used_item = None
|
554
|
+
self.last_used_content = None
|
555
|
+
self.last_used_context = None
|
556
|
+
self.last_files = []
|
557
|
+
self.last_urls = []
|
558
|
+
|
559
|
+
def get_used_files(self) -> list:
|
560
|
+
"""
|
561
|
+
Get last used files
|
562
|
+
|
563
|
+
:return: list of files
|
564
|
+
"""
|
565
|
+
return self.last_files
|
566
|
+
|
567
|
+
def get_used_urls(self) -> list:
|
568
|
+
"""
|
569
|
+
Get last used URLs
|
570
|
+
|
571
|
+
:return: list of URLs
|
572
|
+
"""
|
573
|
+
return self.last_urls
|
574
|
+
|
393
575
|
def is_verbose(self) -> bool:
|
394
576
|
"""
|
395
577
|
Check if verbose mode is enabled
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# ================================================== #
|
4
|
+
# This file is a part of PYGPT package #
|
5
|
+
# Website: https://pygpt.net #
|
6
|
+
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
|
+
# MIT License #
|
8
|
+
# Created By : Marcin Szczygliński #
|
9
|
+
# Updated Date: 2024.11.21 20:00:00 #
|
10
|
+
# ================================================== #
|
11
|
+
|
12
|
+
class AudioContext:
|
13
|
+
def __init__(self, **kwargs):
|
14
|
+
"""
|
15
|
+
Audio context
|
16
|
+
|
17
|
+
:param kwargs: keyword arguments
|
18
|
+
"""
|
19
|
+
self.data = kwargs.get("ctx", None)
|
20
|
+
self.prev_id = kwargs.get("prev_id", None)
|
21
|
+
|
22
|
+
def to_dict(self) -> dict:
|
23
|
+
"""
|
24
|
+
Return as dictionary
|
25
|
+
|
26
|
+
:return: dictionary
|
27
|
+
"""
|
28
|
+
data = {
|
29
|
+
"data": self.data,
|
30
|
+
"prev_id": self.prev_id
|
31
|
+
}
|
32
|
+
# sort by keys
|
33
|
+
data = dict(sorted(data.items(), key=lambda item: item[0]))
|
34
|
+
return data
|
pygpt_net/core/bridge/context.py
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
7
7
|
# MIT License #
|
8
8
|
# Created By : Marcin Szczygliński #
|
9
|
-
# Updated Date: 2024.11.
|
9
|
+
# Updated Date: 2024.11.26 19:00:00 #
|
10
10
|
# ================================================== #
|
11
11
|
|
12
12
|
from pygpt_net.item.ctx import CtxItem
|
@@ -39,6 +39,7 @@ class BridgeContext:
|
|
39
39
|
self.attachments = kwargs.get("attachments", [])
|
40
40
|
self.file_ids = kwargs.get("file_ids", [])
|
41
41
|
self.reply_context = kwargs.get("reply_ctx", None) # ReplyContext
|
42
|
+
self.multimodal_ctx = kwargs.get("multimodal_ctx", MultimodalContext()) # AudioContext
|
42
43
|
|
43
44
|
# check types
|
44
45
|
if self.ctx is not None and not isinstance(self.ctx, CtxItem):
|
@@ -78,6 +79,33 @@ class BridgeContext:
|
|
78
79
|
if self.model is not None:
|
79
80
|
data["model"] = self.model.to_dict()
|
80
81
|
|
82
|
+
# sort by keys
|
83
|
+
data = dict(sorted(data.items(), key=lambda item: item[0]))
|
84
|
+
return data
|
85
|
+
|
86
|
+
class MultimodalContext:
|
87
|
+
def __init__(self, **kwargs):
|
88
|
+
"""
|
89
|
+
Multimodal context
|
90
|
+
|
91
|
+
:param kwargs: keyword arguments
|
92
|
+
"""
|
93
|
+
self.is_audio_input = kwargs.get("is_audio_input", False)
|
94
|
+
self.is_audio_output = kwargs.get("is_audio_output", False)
|
95
|
+
self.audio_data = kwargs.get("audio_data", None)
|
96
|
+
self.audio_format = kwargs.get("audio_format", "wav")
|
97
|
+
|
98
|
+
def to_dict(self) -> dict:
|
99
|
+
"""
|
100
|
+
Return as dictionary
|
101
|
+
|
102
|
+
:return: dictionary
|
103
|
+
"""
|
104
|
+
data = {
|
105
|
+
"is_audio_input": self.is_audio_input,
|
106
|
+
"is_audio_output": self.is_audio_output,
|
107
|
+
"audio_format": self.audio_format,
|
108
|
+
}
|
81
109
|
# sort by keys
|
82
110
|
data = dict(sorted(data.items(), key=lambda item: item[0]))
|
83
111
|
return data
|