pygpt-net 2.6.20__py3-none-any.whl → 2.6.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygpt_net/CHANGELOG.txt +13 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +3 -1
- pygpt_net/controller/__init__.py +4 -8
- pygpt_net/controller/access/voice.py +2 -2
- pygpt_net/controller/agent/agent.py +130 -2
- pygpt_net/controller/agent/experts.py +93 -96
- pygpt_net/controller/agent/llama.py +2 -1
- pygpt_net/controller/assistant/assistant.py +18 -1
- pygpt_net/controller/assistant/batch.py +2 -3
- pygpt_net/controller/assistant/editor.py +2 -2
- pygpt_net/controller/assistant/files.py +2 -3
- pygpt_net/controller/assistant/store.py +2 -2
- pygpt_net/controller/attachment/attachment.py +17 -1
- pygpt_net/controller/audio/audio.py +2 -2
- pygpt_net/controller/camera/camera.py +15 -7
- pygpt_net/controller/chat/chat.py +2 -2
- pygpt_net/controller/chat/common.py +50 -33
- pygpt_net/controller/chat/image.py +67 -77
- pygpt_net/controller/chat/input.py +94 -166
- pygpt_net/controller/chat/output.py +83 -140
- pygpt_net/controller/chat/response.py +83 -102
- pygpt_net/controller/chat/text.py +116 -149
- pygpt_net/controller/ctx/common.py +2 -1
- pygpt_net/controller/ctx/ctx.py +87 -6
- pygpt_net/controller/files/files.py +13 -1
- pygpt_net/controller/idx/idx.py +26 -2
- pygpt_net/controller/idx/indexer.py +85 -76
- pygpt_net/controller/kernel/reply.py +53 -66
- pygpt_net/controller/kernel/stack.py +16 -16
- pygpt_net/controller/lang/lang.py +52 -34
- pygpt_net/controller/model/importer.py +3 -2
- pygpt_net/controller/model/model.py +62 -3
- pygpt_net/controller/notepad/notepad.py +86 -84
- pygpt_net/controller/plugins/settings.py +3 -4
- pygpt_net/controller/settings/editor.py +4 -4
- pygpt_net/controller/settings/profile.py +105 -124
- pygpt_net/controller/theme/menu.py +154 -57
- pygpt_net/controller/theme/nodes.py +51 -44
- pygpt_net/controller/theme/theme.py +33 -9
- pygpt_net/controller/tools/tools.py +2 -2
- pygpt_net/controller/ui/tabs.py +2 -3
- pygpt_net/controller/ui/ui.py +16 -2
- pygpt_net/core/agents/observer/evaluation.py +3 -3
- pygpt_net/core/agents/provider.py +25 -3
- pygpt_net/core/agents/runner.py +4 -1
- pygpt_net/core/agents/runners/llama_workflow.py +19 -7
- pygpt_net/core/agents/runners/loop.py +3 -1
- pygpt_net/core/agents/runners/openai_workflow.py +17 -3
- pygpt_net/core/agents/tools.py +4 -1
- pygpt_net/core/bridge/context.py +34 -37
- pygpt_net/core/ctx/container.py +13 -12
- pygpt_net/core/ctx/ctx.py +1 -1
- pygpt_net/core/ctx/output.py +7 -4
- pygpt_net/core/db/database.py +2 -2
- pygpt_net/core/debug/console/console.py +2 -2
- pygpt_net/core/debug/debug.py +12 -1
- pygpt_net/core/dispatcher/dispatcher.py +24 -1
- pygpt_net/core/events/app.py +7 -7
- pygpt_net/core/events/control.py +26 -26
- pygpt_net/core/events/event.py +6 -3
- pygpt_net/core/events/kernel.py +2 -2
- pygpt_net/core/events/render.py +13 -13
- pygpt_net/core/experts/experts.py +76 -82
- pygpt_net/core/experts/worker.py +12 -12
- pygpt_net/core/filesystem/actions.py +1 -2
- pygpt_net/core/models/models.py +5 -1
- pygpt_net/core/models/ollama.py +14 -5
- pygpt_net/core/render/plain/helpers.py +2 -5
- pygpt_net/core/render/plain/renderer.py +26 -30
- pygpt_net/core/render/web/body.py +1 -1
- pygpt_net/core/render/web/helpers.py +2 -2
- pygpt_net/core/render/web/renderer.py +4 -4
- pygpt_net/core/settings/settings.py +43 -13
- pygpt_net/core/tabs/tabs.py +20 -13
- pygpt_net/core/types/__init__.py +2 -1
- pygpt_net/core/types/agent.py +4 -4
- pygpt_net/core/types/base.py +19 -0
- pygpt_net/core/types/console.py +6 -6
- pygpt_net/core/types/mode.py +8 -8
- pygpt_net/core/types/multimodal.py +3 -3
- pygpt_net/core/types/openai.py +2 -1
- pygpt_net/data/config/config.json +5 -5
- pygpt_net/data/config/models.json +19 -3
- pygpt_net/data/config/settings.json +14 -14
- pygpt_net/data/locale/locale.de.ini +4 -1
- pygpt_net/data/locale/locale.en.ini +6 -3
- pygpt_net/data/locale/locale.es.ini +4 -1
- pygpt_net/data/locale/locale.fr.ini +4 -1
- pygpt_net/data/locale/locale.it.ini +4 -1
- pygpt_net/data/locale/locale.pl.ini +5 -4
- pygpt_net/data/locale/locale.uk.ini +4 -1
- pygpt_net/data/locale/locale.zh.ini +4 -1
- pygpt_net/item/ctx.py +256 -240
- pygpt_net/item/model.py +59 -116
- pygpt_net/item/preset.py +122 -105
- pygpt_net/plugin/twitter/plugin.py +2 -2
- pygpt_net/provider/agents/llama_index/workflow/planner.py +3 -3
- pygpt_net/provider/agents/openai/agent.py +4 -12
- pygpt_net/provider/agents/openai/agent_b2b.py +10 -15
- pygpt_net/provider/agents/openai/agent_planner.py +4 -4
- pygpt_net/provider/agents/openai/agent_with_experts.py +3 -7
- pygpt_net/provider/agents/openai/agent_with_experts_feedback.py +4 -8
- pygpt_net/provider/agents/openai/agent_with_feedback.py +4 -8
- pygpt_net/provider/agents/openai/bot_researcher.py +2 -18
- pygpt_net/provider/agents/openai/bots/__init__.py +0 -0
- pygpt_net/provider/agents/openai/bots/research_bot/__init__.py +0 -0
- pygpt_net/provider/agents/openai/bots/research_bot/agents/__init__.py +0 -0
- pygpt_net/provider/agents/openai/bots/research_bot/agents/planner_agent.py +1 -1
- pygpt_net/provider/agents/openai/bots/research_bot/agents/search_agent.py +1 -0
- pygpt_net/provider/agents/openai/bots/research_bot/agents/writer_agent.py +1 -1
- pygpt_net/provider/agents/openai/bots/research_bot/manager.py +1 -10
- pygpt_net/provider/agents/openai/evolve.py +5 -9
- pygpt_net/provider/agents/openai/supervisor.py +4 -8
- pygpt_net/provider/core/config/patch.py +10 -3
- pygpt_net/provider/core/ctx/db_sqlite/utils.py +43 -43
- pygpt_net/provider/core/model/patch.py +11 -1
- pygpt_net/provider/core/preset/json_file.py +47 -49
- pygpt_net/provider/gpt/agents/experts.py +2 -2
- pygpt_net/tools/audio_transcriber/ui/dialogs.py +44 -54
- pygpt_net/tools/code_interpreter/body.py +1 -2
- pygpt_net/tools/code_interpreter/tool.py +7 -4
- pygpt_net/tools/code_interpreter/ui/html.py +1 -3
- pygpt_net/tools/code_interpreter/ui/widgets.py +2 -3
- pygpt_net/tools/html_canvas/ui/widgets.py +1 -3
- pygpt_net/tools/image_viewer/ui/dialogs.py +40 -37
- pygpt_net/tools/indexer/ui/widgets.py +2 -4
- pygpt_net/tools/media_player/tool.py +2 -5
- pygpt_net/tools/media_player/ui/widgets.py +60 -36
- pygpt_net/tools/text_editor/ui/widgets.py +18 -19
- pygpt_net/tools/translator/ui/widgets.py +39 -35
- pygpt_net/ui/base/context_menu.py +9 -4
- pygpt_net/ui/dialog/db.py +1 -3
- pygpt_net/ui/dialog/models.py +1 -3
- pygpt_net/ui/dialog/models_importer.py +2 -4
- pygpt_net/ui/dialogs.py +34 -30
- pygpt_net/ui/layout/chat/attachments.py +72 -84
- pygpt_net/ui/layout/chat/attachments_ctx.py +40 -44
- pygpt_net/ui/layout/chat/attachments_uploaded.py +36 -39
- pygpt_net/ui/layout/chat/calendar.py +100 -70
- pygpt_net/ui/layout/chat/chat.py +23 -17
- pygpt_net/ui/layout/chat/input.py +95 -118
- pygpt_net/ui/layout/chat/output.py +100 -162
- pygpt_net/ui/layout/chat/painter.py +89 -61
- pygpt_net/ui/layout/ctx/ctx_list.py +43 -52
- pygpt_net/ui/layout/status.py +23 -14
- pygpt_net/ui/layout/toolbox/agent.py +27 -38
- pygpt_net/ui/layout/toolbox/agent_llama.py +42 -45
- pygpt_net/ui/layout/toolbox/assistants.py +42 -38
- pygpt_net/ui/layout/toolbox/computer_env.py +32 -23
- pygpt_net/ui/layout/toolbox/footer.py +13 -16
- pygpt_net/ui/layout/toolbox/image.py +18 -21
- pygpt_net/ui/layout/toolbox/indexes.py +46 -89
- pygpt_net/ui/layout/toolbox/mode.py +20 -7
- pygpt_net/ui/layout/toolbox/model.py +12 -10
- pygpt_net/ui/layout/toolbox/presets.py +68 -52
- pygpt_net/ui/layout/toolbox/prompt.py +31 -58
- pygpt_net/ui/layout/toolbox/toolbox.py +25 -21
- pygpt_net/ui/layout/toolbox/vision.py +20 -22
- pygpt_net/ui/main.py +2 -4
- pygpt_net/ui/menu/about.py +64 -84
- pygpt_net/ui/menu/audio.py +87 -63
- pygpt_net/ui/menu/config.py +121 -127
- pygpt_net/ui/menu/debug.py +69 -76
- pygpt_net/ui/menu/file.py +32 -35
- pygpt_net/ui/menu/menu.py +2 -3
- pygpt_net/ui/menu/plugins.py +69 -33
- pygpt_net/ui/menu/theme.py +45 -46
- pygpt_net/ui/menu/tools.py +56 -60
- pygpt_net/ui/menu/video.py +20 -25
- pygpt_net/ui/tray.py +1 -2
- pygpt_net/ui/widget/audio/bar.py +1 -3
- pygpt_net/ui/widget/audio/input_button.py +3 -4
- pygpt_net/ui/widget/calendar/select.py +1 -2
- pygpt_net/ui/widget/dialog/base.py +12 -9
- pygpt_net/ui/widget/dialog/editor_file.py +20 -23
- pygpt_net/ui/widget/dialog/find.py +25 -24
- pygpt_net/ui/widget/dialog/profile.py +57 -53
- pygpt_net/ui/widget/draw/painter.py +62 -93
- pygpt_net/ui/widget/element/button.py +42 -30
- pygpt_net/ui/widget/element/checkbox.py +23 -15
- pygpt_net/ui/widget/element/group.py +6 -5
- pygpt_net/ui/widget/element/labels.py +1 -2
- pygpt_net/ui/widget/filesystem/explorer.py +93 -102
- pygpt_net/ui/widget/image/display.py +1 -2
- pygpt_net/ui/widget/lists/assistant.py +1 -2
- pygpt_net/ui/widget/lists/attachment.py +1 -2
- pygpt_net/ui/widget/lists/attachment_ctx.py +1 -2
- pygpt_net/ui/widget/lists/context.py +2 -4
- pygpt_net/ui/widget/lists/index.py +1 -2
- pygpt_net/ui/widget/lists/model.py +1 -2
- pygpt_net/ui/widget/lists/model_editor.py +1 -2
- pygpt_net/ui/widget/lists/model_importer.py +1 -2
- pygpt_net/ui/widget/lists/preset.py +1 -2
- pygpt_net/ui/widget/lists/preset_plugins.py +1 -2
- pygpt_net/ui/widget/lists/profile.py +1 -2
- pygpt_net/ui/widget/lists/uploaded.py +1 -2
- pygpt_net/ui/widget/option/checkbox.py +2 -4
- pygpt_net/ui/widget/option/checkbox_list.py +1 -4
- pygpt_net/ui/widget/option/cmd.py +1 -4
- pygpt_net/ui/widget/option/dictionary.py +25 -28
- pygpt_net/ui/widget/option/input.py +1 -3
- pygpt_net/ui/widget/tabs/Input.py +16 -12
- pygpt_net/ui/widget/tabs/body.py +5 -3
- pygpt_net/ui/widget/tabs/layout.py +36 -25
- pygpt_net/ui/widget/tabs/output.py +96 -74
- pygpt_net/ui/widget/textarea/calendar_note.py +1 -2
- pygpt_net/ui/widget/textarea/editor.py +41 -73
- pygpt_net/ui/widget/textarea/find.py +11 -10
- pygpt_net/ui/widget/textarea/html.py +3 -6
- pygpt_net/ui/widget/textarea/input.py +63 -64
- pygpt_net/ui/widget/textarea/notepad.py +54 -38
- pygpt_net/ui/widget/textarea/output.py +65 -54
- pygpt_net/ui/widget/textarea/search_input.py +5 -4
- pygpt_net/ui/widget/textarea/web.py +2 -4
- pygpt_net/ui/widget/vision/camera.py +2 -31
- {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/METADATA +25 -154
- {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/RECORD +218 -217
- {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.20.dist-info → pygpt_net-2.6.22.dist-info}/entry_points.txt +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6 import QtCore
|
|
@@ -15,17 +15,20 @@ from PySide6.QtGui import QAction, QIcon, QImage
|
|
|
15
15
|
from PySide6.QtWidgets import QTextEdit, QApplication
|
|
16
16
|
|
|
17
17
|
from pygpt_net.utils import trans
|
|
18
|
-
import pygpt_net.icons_rc
|
|
19
|
-
|
|
20
18
|
|
|
21
19
|
class ChatInput(QTextEdit):
|
|
20
|
+
|
|
21
|
+
ICON_PASTE = QIcon(":/icons/paste.svg")
|
|
22
|
+
ICON_VOLUME = QIcon(":/icons/volume.svg")
|
|
23
|
+
ICON_SAVE = QIcon(":/icons/save.svg")
|
|
24
|
+
|
|
22
25
|
def __init__(self, window=None):
|
|
23
26
|
"""
|
|
24
27
|
Chat input
|
|
25
28
|
|
|
26
29
|
:param window: main window
|
|
27
30
|
"""
|
|
28
|
-
super(
|
|
31
|
+
super().__init__(window)
|
|
29
32
|
self.window = window
|
|
30
33
|
self.setAcceptRichText(False)
|
|
31
34
|
self.setFocus()
|
|
@@ -63,7 +66,8 @@ class ChatInput(QTextEdit):
|
|
|
63
66
|
self.window.controller.attachment.from_clipboard_url(local_path)
|
|
64
67
|
elif source.hasText():
|
|
65
68
|
text = source.text()
|
|
66
|
-
|
|
69
|
+
if text:
|
|
70
|
+
self.window.controller.attachment.from_clipboard_text(text)
|
|
67
71
|
|
|
68
72
|
def contextMenuEvent(self, event):
|
|
69
73
|
"""
|
|
@@ -72,51 +76,45 @@ class ChatInput(QTextEdit):
|
|
|
72
76
|
:param event: event
|
|
73
77
|
"""
|
|
74
78
|
menu = self.createStandardContextMenu()
|
|
75
|
-
|
|
76
|
-
# paste attachment
|
|
77
|
-
if self.window.controller.attachment.clipboard_has_attachment():
|
|
78
|
-
action = QAction(QIcon(":/icons/paste.svg"), trans("action.use.attachment"), self)
|
|
79
|
-
action.triggered.connect(self.action_from_clipboard)
|
|
80
|
-
menu.addAction(action)
|
|
81
|
-
|
|
82
|
-
selected_text = self.textCursor().selectedText()
|
|
83
|
-
if selected_text:
|
|
84
|
-
# plain text
|
|
85
|
-
plain_text = self.textCursor().selection().toPlainText()
|
|
86
|
-
|
|
87
|
-
# audio read
|
|
88
|
-
action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
|
|
89
|
-
action.triggered.connect(self.audio_read_selection)
|
|
90
|
-
menu.addAction(action)
|
|
91
|
-
|
|
92
|
-
# copy to
|
|
93
|
-
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, selected_text, excluded=["input"])
|
|
94
|
-
menu.addMenu(copy_to_menu)
|
|
95
|
-
|
|
96
|
-
# save as (selected)
|
|
97
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
|
|
98
|
-
action.triggered.connect(
|
|
99
|
-
lambda: self.window.controller.chat.common.save_text(plain_text))
|
|
100
|
-
menu.addAction(action)
|
|
101
|
-
else:
|
|
102
|
-
# save as (all)
|
|
103
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_as'), self)
|
|
104
|
-
action.triggered.connect(
|
|
105
|
-
lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
106
|
-
menu.addAction(action)
|
|
107
|
-
|
|
108
79
|
try:
|
|
109
|
-
self.window.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
80
|
+
if self.window.controller.attachment.clipboard_has_attachment():
|
|
81
|
+
action = QAction(self.ICON_PASTE, trans("action.use.attachment"), menu)
|
|
82
|
+
action.triggered.connect(self.action_from_clipboard)
|
|
83
|
+
menu.addAction(action)
|
|
84
|
+
|
|
85
|
+
cursor = self.textCursor()
|
|
86
|
+
selected_text = cursor.selectedText()
|
|
87
|
+
if selected_text:
|
|
88
|
+
plain_text = cursor.selection().toPlainText()
|
|
89
|
+
|
|
90
|
+
action = QAction(self.ICON_VOLUME, trans('text.context_menu.audio.read'), menu)
|
|
91
|
+
action.triggered.connect(self.audio_read_selection)
|
|
92
|
+
menu.addAction(action)
|
|
93
|
+
|
|
94
|
+
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text, excluded=["input"])
|
|
95
|
+
menu.addMenu(copy_to_menu)
|
|
96
|
+
|
|
97
|
+
action = QAction(self.ICON_SAVE, trans('action.save_selection_as'), menu)
|
|
98
|
+
action.triggered.connect(lambda: self.window.controller.chat.common.save_text(plain_text))
|
|
99
|
+
menu.addAction(action)
|
|
100
|
+
else:
|
|
101
|
+
action = QAction(self.ICON_SAVE, trans('action.save_as'), menu)
|
|
102
|
+
action.triggered.connect(lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
103
|
+
menu.addAction(action)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
self.window.core.prompt.template.to_menu_options(menu, "input")
|
|
107
|
+
self.window.core.prompt.custom.to_menu_options(menu, "input")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
self.window.core.debug.log(e)
|
|
110
|
+
|
|
111
|
+
action = QAction(self.ICON_SAVE, trans('preset.prompt.save_custom'), menu)
|
|
112
|
+
action.triggered.connect(self.window.controller.presets.save_prompt)
|
|
113
|
+
menu.addAction(action)
|
|
118
114
|
|
|
119
|
-
|
|
115
|
+
menu.exec(event.globalPos())
|
|
116
|
+
finally:
|
|
117
|
+
menu.deleteLater()
|
|
120
118
|
|
|
121
119
|
def action_from_clipboard(self):
|
|
122
120
|
"""
|
|
@@ -137,28 +135,26 @@ class ChatInput(QTextEdit):
|
|
|
137
135
|
:param event: key event
|
|
138
136
|
"""
|
|
139
137
|
handled = False
|
|
140
|
-
|
|
138
|
+
key = event.key()
|
|
139
|
+
if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
|
141
140
|
mode = self.window.core.config.get('send_mode')
|
|
142
|
-
if mode > 0:
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
if mode > 0:
|
|
142
|
+
modifiers = event.modifiers()
|
|
143
|
+
if mode == 2:
|
|
145
144
|
if modifiers == QtCore.Qt.ShiftModifier or modifiers == QtCore.Qt.ControlModifier:
|
|
146
145
|
self.window.controller.chat.input.send_input()
|
|
147
146
|
handled = True
|
|
148
|
-
else:
|
|
149
|
-
modifiers = QApplication.keyboardModifiers()
|
|
147
|
+
else:
|
|
150
148
|
if modifiers != QtCore.Qt.ShiftModifier and modifiers != QtCore.Qt.ControlModifier:
|
|
151
149
|
self.window.controller.chat.input.send_input()
|
|
152
150
|
handled = True
|
|
153
151
|
self.setFocus()
|
|
154
|
-
|
|
155
|
-
# cancel edit
|
|
156
|
-
elif event.key() == Qt.Key_Escape and self.window.controller.ctx.extra.is_editing():
|
|
152
|
+
elif key == Qt.Key_Escape and self.window.controller.ctx.extra.is_editing():
|
|
157
153
|
self.window.controller.ctx.extra.edit_cancel()
|
|
158
154
|
handled = True
|
|
159
155
|
|
|
160
156
|
if not handled:
|
|
161
|
-
super(
|
|
157
|
+
super().keyPressEvent(event)
|
|
162
158
|
|
|
163
159
|
def wheelEvent(self, event):
|
|
164
160
|
"""
|
|
@@ -167,16 +163,19 @@ class ChatInput(QTextEdit):
|
|
|
167
163
|
:param event: Event
|
|
168
164
|
"""
|
|
169
165
|
if event.modifiers() & Qt.ControlModifier:
|
|
170
|
-
|
|
166
|
+
prev = self.value
|
|
167
|
+
dy = event.angleDelta().y()
|
|
168
|
+
if dy > 0:
|
|
171
169
|
if self.value < self.max_font_size:
|
|
172
170
|
self.value += 1
|
|
173
171
|
else:
|
|
174
172
|
if self.value > self.min_font_size:
|
|
175
173
|
self.value -= 1
|
|
176
174
|
|
|
177
|
-
self.
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
if self.value != prev:
|
|
176
|
+
self.window.core.config.data['font_size.input'] = self.value
|
|
177
|
+
self.window.core.config.save()
|
|
178
|
+
self.window.controller.ui.update_font_size()
|
|
180
179
|
event.accept()
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
return
|
|
181
|
+
super().wheelEvent(event)
|
|
@@ -6,18 +6,17 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date: 2025.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtCore import Qt, QEvent, QTimer
|
|
13
13
|
from PySide6.QtGui import QAction, QIcon, QKeySequence, QTextCursor, QFontMetrics
|
|
14
|
-
from PySide6.QtWidgets import QTextEdit, QWidget, QVBoxLayout
|
|
14
|
+
from PySide6.QtWidgets import QTextEdit, QWidget, QVBoxLayout
|
|
15
15
|
|
|
16
16
|
from pygpt_net.core.tabs.tab import Tab
|
|
17
17
|
from pygpt_net.core.text.finder import Finder
|
|
18
18
|
from pygpt_net.ui.widget.element.labels import HelpLabel
|
|
19
19
|
from pygpt_net.utils import trans
|
|
20
|
-
import pygpt_net.icons_rc
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
class NotepadWidget(QWidget):
|
|
@@ -83,6 +82,10 @@ class NotepadWidget(QWidget):
|
|
|
83
82
|
self.deleteLater()
|
|
84
83
|
|
|
85
84
|
class NotepadOutput(QTextEdit):
|
|
85
|
+
ICON_VOLUME = QIcon(":/icons/volume.svg")
|
|
86
|
+
ICON_SAVE = QIcon(":/icons/save.svg")
|
|
87
|
+
ICON_SEARCH = QIcon(":/icons/search.svg")
|
|
88
|
+
|
|
86
89
|
def __init__(self, window=None):
|
|
87
90
|
"""
|
|
88
91
|
Notepad output textarea
|
|
@@ -104,26 +107,34 @@ class NotepadOutput(QTextEdit):
|
|
|
104
107
|
self.installEventFilter(self)
|
|
105
108
|
self.setProperty('class', 'layout-notepad')
|
|
106
109
|
|
|
107
|
-
# tabulation
|
|
108
110
|
metrics = QFontMetrics(self.font())
|
|
109
111
|
space_width = metrics.horizontalAdvance(" ")
|
|
110
112
|
self.setTabStopDistance(4 * space_width)
|
|
111
113
|
|
|
114
|
+
self._vscroll = self.verticalScrollBar()
|
|
115
|
+
self._vscroll.valueChanged.connect(self._on_scrollbar_value_changed)
|
|
116
|
+
self._restore_attempts = 0
|
|
117
|
+
|
|
112
118
|
def on_delete(self):
|
|
113
119
|
"""On delete"""
|
|
114
120
|
if self.finder:
|
|
115
121
|
self.finder.disconnect() # disconnect finder
|
|
116
122
|
self.finder = None # delete finder
|
|
123
|
+
try:
|
|
124
|
+
self._vscroll.valueChanged.disconnect(self._on_scrollbar_value_changed)
|
|
125
|
+
except Exception:
|
|
126
|
+
pass
|
|
117
127
|
self.deleteLater()
|
|
118
128
|
|
|
119
129
|
def showEvent(self, event):
|
|
120
130
|
super().showEvent(event)
|
|
131
|
+
self._restore_attempts = 0
|
|
121
132
|
QTimer.singleShot(0, self.restore_scroll_pos)
|
|
122
133
|
|
|
123
134
|
def scroll_to_bottom(self):
|
|
124
135
|
self.moveCursor(QTextCursor.End)
|
|
125
136
|
self.ensureCursorVisible()
|
|
126
|
-
scroll_bar = self.
|
|
137
|
+
scroll_bar = self._vscroll
|
|
127
138
|
scroll_bar.setValue(scroll_bar.maximum())
|
|
128
139
|
|
|
129
140
|
def eventFilter(self, source, event):
|
|
@@ -133,15 +144,10 @@ class NotepadOutput(QTextEdit):
|
|
|
133
144
|
:param source: source
|
|
134
145
|
:param event: event
|
|
135
146
|
"""
|
|
136
|
-
if event.type() ==
|
|
147
|
+
if event.type() == QEvent.FocusIn:
|
|
137
148
|
if self.tab is not None:
|
|
138
149
|
col_idx = self.tab.column_idx
|
|
139
150
|
self.window.controller.ui.tabs.on_column_focus(col_idx)
|
|
140
|
-
elif source == self.verticalScrollBar():
|
|
141
|
-
if event.type() == QEvent.Wheel:
|
|
142
|
-
self.last_scroll_pos = self.verticalScrollBar().value()
|
|
143
|
-
elif event.type() in (QEvent.MouseButtonPress, QEvent.MouseButtonRelease):
|
|
144
|
-
self.last_scroll_pos = self.verticalScrollBar().value()
|
|
145
151
|
return super().eventFilter(source, event)
|
|
146
152
|
|
|
147
153
|
def set_tab(self, tab: Tab):
|
|
@@ -152,23 +158,33 @@ class NotepadOutput(QTextEdit):
|
|
|
152
158
|
"""
|
|
153
159
|
self.tab = tab
|
|
154
160
|
|
|
161
|
+
def setText(self, text: str):
|
|
162
|
+
if self.toPlainText() == text:
|
|
163
|
+
return
|
|
164
|
+
self.setPlainText(text)
|
|
165
|
+
|
|
155
166
|
def text_changed(self):
|
|
156
167
|
"""On text changed"""
|
|
157
168
|
if not self.window.core.notepad.locked:
|
|
158
169
|
self.window.controller.notepad.save(self.id) # use notepad id
|
|
159
|
-
self.finder
|
|
160
|
-
|
|
170
|
+
if self.finder is not None:
|
|
171
|
+
self.finder.text_changed()
|
|
172
|
+
self.last_scroll_pos = self._vscroll.value()
|
|
173
|
+
|
|
174
|
+
def _on_scrollbar_value_changed(self, value: int):
|
|
175
|
+
self.last_scroll_pos = value
|
|
161
176
|
|
|
162
177
|
def restore_scroll_pos(self):
|
|
163
178
|
if self.last_scroll_pos is None:
|
|
164
179
|
return
|
|
165
|
-
|
|
166
|
-
scroll_bar = self.verticalScrollBar()
|
|
180
|
+
scroll_bar = self._vscroll
|
|
167
181
|
current_max = scroll_bar.maximum()
|
|
168
182
|
if self.last_scroll_pos > current_max:
|
|
169
|
-
self.
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
if self._restore_attempts < 30:
|
|
184
|
+
self._restore_attempts += 1
|
|
185
|
+
QTimer.singleShot(16, self.restore_scroll_pos)
|
|
186
|
+
else:
|
|
187
|
+
scroll_bar.setValue(current_max)
|
|
172
188
|
else:
|
|
173
189
|
scroll_bar.setValue(self.last_scroll_pos)
|
|
174
190
|
|
|
@@ -179,34 +195,30 @@ class NotepadOutput(QTextEdit):
|
|
|
179
195
|
:param event: Event
|
|
180
196
|
"""
|
|
181
197
|
menu = self.createStandardContextMenu()
|
|
182
|
-
|
|
198
|
+
cursor = self.textCursor()
|
|
199
|
+
selected_text = cursor.selectedText()
|
|
183
200
|
if selected_text:
|
|
184
|
-
|
|
185
|
-
plain_text = self.textCursor().selection().toPlainText()
|
|
201
|
+
plain_text = cursor.selection().toPlainText()
|
|
186
202
|
|
|
187
|
-
|
|
188
|
-
action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
|
|
203
|
+
action = QAction(self.ICON_VOLUME, trans('text.context_menu.audio.read'), self)
|
|
189
204
|
action.triggered.connect(self.audio_read_selection)
|
|
190
205
|
menu.addAction(action)
|
|
191
206
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, selected_text, excluded=[excluded_id])
|
|
207
|
+
excluded_id = f"notepad_id_{self.id}"
|
|
208
|
+
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text, excluded=[excluded_id])
|
|
195
209
|
menu.addMenu(copy_to_menu)
|
|
196
210
|
|
|
197
|
-
|
|
198
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_selection_as'), self)
|
|
211
|
+
action = QAction(self.ICON_SAVE, trans('action.save_selection_as'), self)
|
|
199
212
|
action.triggered.connect(
|
|
200
213
|
lambda: self.window.controller.chat.common.save_text(plain_text))
|
|
201
214
|
menu.addAction(action)
|
|
202
215
|
else:
|
|
203
|
-
|
|
204
|
-
action = QAction(QIcon(":/icons/save.svg"), trans('action.save_as'), self)
|
|
216
|
+
action = QAction(self.ICON_SAVE, trans('action.save_as'), self)
|
|
205
217
|
action.triggered.connect(
|
|
206
218
|
lambda: self.window.controller.chat.common.save_text(self.toPlainText()))
|
|
207
219
|
menu.addAction(action)
|
|
208
220
|
|
|
209
|
-
action = QAction(
|
|
221
|
+
action = QAction(self.ICON_SEARCH, trans('text.context_menu.find'), self)
|
|
210
222
|
action.triggered.connect(self.find_open)
|
|
211
223
|
action.setShortcut(QKeySequence("Ctrl+F"))
|
|
212
224
|
menu.addAction(action)
|
|
@@ -246,28 +258,33 @@ class NotepadOutput(QTextEdit):
|
|
|
246
258
|
:param event: Event
|
|
247
259
|
"""
|
|
248
260
|
if event.modifiers() & Qt.ControlModifier:
|
|
249
|
-
|
|
261
|
+
delta = event.angleDelta().y()
|
|
262
|
+
if delta > 0:
|
|
250
263
|
if self.value < self.max_font_size:
|
|
251
264
|
self.value += 1
|
|
265
|
+
else:
|
|
266
|
+
return
|
|
252
267
|
else:
|
|
253
268
|
if self.value > self.min_font_size:
|
|
254
269
|
self.value -= 1
|
|
270
|
+
else:
|
|
271
|
+
return
|
|
255
272
|
|
|
256
273
|
self.window.core.config.data['font_size'] = self.value
|
|
257
274
|
self.window.core.config.save()
|
|
258
275
|
option = self.window.controller.settings.editor.get_option('font_size')
|
|
259
276
|
option['value'] = self.value
|
|
260
277
|
self.window.controller.config.apply(
|
|
261
|
-
parent_id='config',
|
|
262
|
-
key='font_size',
|
|
278
|
+
parent_id='config',
|
|
279
|
+
key='font_size',
|
|
263
280
|
option=option,
|
|
264
281
|
)
|
|
265
282
|
self.window.controller.ui.update_font_size()
|
|
266
283
|
event.accept()
|
|
267
|
-
self.last_scroll_pos = self.
|
|
284
|
+
self.last_scroll_pos = self._vscroll.value()
|
|
268
285
|
else:
|
|
269
286
|
super(NotepadOutput, self).wheelEvent(event)
|
|
270
|
-
self.last_scroll_pos = self.
|
|
287
|
+
self.last_scroll_pos = self._vscroll.value()
|
|
271
288
|
|
|
272
289
|
def focusInEvent(self, e):
|
|
273
290
|
"""
|
|
@@ -285,5 +302,4 @@ class NotepadOutput(QTextEdit):
|
|
|
285
302
|
:param e: focus event
|
|
286
303
|
"""
|
|
287
304
|
super(NotepadOutput, self).focusOutEvent(e)
|
|
288
|
-
self.window.controller.finder.focus_out(self.finder)
|
|
289
|
-
|
|
305
|
+
self.window.controller.finder.focus_out(self.finder)
|
|
@@ -6,30 +6,34 @@
|
|
|
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.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
|
-
from PySide6.QtCore import Qt
|
|
12
|
+
from PySide6.QtCore import Qt, QEvent
|
|
13
13
|
from PySide6.QtWidgets import QTextBrowser
|
|
14
14
|
from PySide6.QtGui import QAction, QIcon, QTextOption, QKeySequence
|
|
15
15
|
|
|
16
16
|
from pygpt_net.core.text.finder import Finder
|
|
17
17
|
from pygpt_net.utils import trans
|
|
18
|
-
import pygpt_net.icons_rc
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
class ChatOutput(QTextBrowser):
|
|
21
|
+
ICON_VOLUME = QIcon(":/icons/volume.svg")
|
|
22
|
+
ICON_SAVE = QIcon(":/icons/save.svg")
|
|
23
|
+
ICON_SEARCH = QIcon(":/icons/search.svg")
|
|
24
|
+
|
|
22
25
|
def __init__(self, window=None):
|
|
23
26
|
"""
|
|
24
27
|
Chat output
|
|
25
28
|
|
|
26
29
|
:param window: main window
|
|
27
30
|
"""
|
|
28
|
-
super(
|
|
31
|
+
super().__init__(window)
|
|
29
32
|
self.window = window
|
|
30
33
|
self.finder = Finder(window, self)
|
|
31
34
|
self.setReadOnly(True)
|
|
32
35
|
self.setAcceptRichText(False)
|
|
36
|
+
self.setUndoRedoEnabled(False)
|
|
33
37
|
self.setStyleSheet(self.window.controller.theme.style('font.chat.output'))
|
|
34
38
|
self.value = self.window.core.config.get('font_size')
|
|
35
39
|
self.max_font_size = 42
|
|
@@ -45,15 +49,14 @@ class ChatOutput(QTextBrowser):
|
|
|
45
49
|
def on_delete(self):
|
|
46
50
|
"""Clean up on delete"""
|
|
47
51
|
if self.finder:
|
|
48
|
-
self.finder.disconnect()
|
|
49
|
-
self.finder = None
|
|
52
|
+
self.finder.disconnect()
|
|
53
|
+
self.finder = None
|
|
50
54
|
|
|
51
|
-
self.tab = None
|
|
55
|
+
self.tab = None
|
|
52
56
|
self.clear()
|
|
53
|
-
|
|
54
|
-
# disconnect signals
|
|
57
|
+
self.removeEventFilter(self)
|
|
55
58
|
self.anchorClicked.disconnect(self.open_external_link)
|
|
56
|
-
self.deleteLater()
|
|
59
|
+
self.deleteLater()
|
|
57
60
|
|
|
58
61
|
def eventFilter(self, source, event):
|
|
59
62
|
"""
|
|
@@ -62,7 +65,7 @@ class ChatOutput(QTextBrowser):
|
|
|
62
65
|
:param source: source
|
|
63
66
|
:param event: event
|
|
64
67
|
"""
|
|
65
|
-
if event.type() ==
|
|
68
|
+
if event.type() == QEvent.FocusIn:
|
|
66
69
|
if self.tab is not None:
|
|
67
70
|
col_idx = self.tab.column_idx
|
|
68
71
|
self.window.controller.ui.tabs.on_column_focus(col_idx)
|
|
@@ -91,41 +94,42 @@ class ChatOutput(QTextBrowser):
|
|
|
91
94
|
:param event: Event
|
|
92
95
|
"""
|
|
93
96
|
menu = self.createStandardContextMenu()
|
|
94
|
-
|
|
97
|
+
cursor = self.textCursor()
|
|
98
|
+
has_selection = cursor.hasSelection()
|
|
95
99
|
|
|
96
|
-
if
|
|
97
|
-
|
|
98
|
-
plain_text = self.textCursor().selection().toPlainText()
|
|
100
|
+
if has_selection:
|
|
101
|
+
selected_text = cursor.selectedText()
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
action = QAction(QIcon(":/icons/volume.svg"), trans('text.context_menu.audio.read'), self)
|
|
103
|
+
action = QAction(self.ICON_VOLUME, trans('text.context_menu.audio.read'), self)
|
|
102
104
|
action.triggered.connect(self.audio_read_selection)
|
|
103
105
|
menu.addAction(action)
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(self, selected_text)
|
|
107
|
+
copy_to_menu = self.window.ui.context_menu.get_copy_to_menu(menu, selected_text)
|
|
107
108
|
menu.addMenu(copy_to_menu)
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
action
|
|
111
|
-
action.triggered.connect(
|
|
112
|
-
lambda: self.window.controller.chat.common.save_text(plain_text)
|
|
113
|
-
)
|
|
110
|
+
action = QAction(self.ICON_SAVE, trans('action.save_selection_as'), self)
|
|
111
|
+
action.triggered.connect(self._save_selected_text)
|
|
114
112
|
menu.addAction(action)
|
|
115
113
|
else:
|
|
116
|
-
|
|
117
|
-
action
|
|
118
|
-
action.triggered.connect(
|
|
119
|
-
lambda: self.window.controller.chat.common.save_text(self.toPlainText())
|
|
120
|
-
)
|
|
114
|
+
action = QAction(self.ICON_SAVE, trans('action.save_as'), self)
|
|
115
|
+
action.triggered.connect(self._save_all_text)
|
|
121
116
|
menu.addAction(action)
|
|
122
117
|
|
|
123
|
-
action = QAction(
|
|
118
|
+
action = QAction(self.ICON_SEARCH, trans('text.context_menu.find'), self)
|
|
124
119
|
action.triggered.connect(self.find_open)
|
|
125
120
|
action.setShortcut(QKeySequence("Ctrl+F"))
|
|
126
121
|
menu.addAction(action)
|
|
127
122
|
|
|
128
|
-
menu.
|
|
123
|
+
menu.exec(event.globalPos())
|
|
124
|
+
menu.deleteLater()
|
|
125
|
+
|
|
126
|
+
def _save_selected_text(self):
|
|
127
|
+
cursor = self.textCursor()
|
|
128
|
+
if cursor.hasSelection():
|
|
129
|
+
self.window.controller.chat.common.save_text(cursor.selection().toPlainText())
|
|
130
|
+
|
|
131
|
+
def _save_all_text(self):
|
|
132
|
+
self.window.controller.chat.common.save_text(self.toPlainText())
|
|
129
133
|
|
|
130
134
|
def audio_read_selection(self):
|
|
131
135
|
"""Read selected text (audio)"""
|
|
@@ -138,7 +142,7 @@ class ChatOutput(QTextBrowser):
|
|
|
138
142
|
def on_update(self):
|
|
139
143
|
"""On content update"""
|
|
140
144
|
if self.finder:
|
|
141
|
-
self.finder.clear()
|
|
145
|
+
self.finder.clear()
|
|
142
146
|
|
|
143
147
|
def keyPressEvent(self, e):
|
|
144
148
|
"""
|
|
@@ -147,9 +151,9 @@ class ChatOutput(QTextBrowser):
|
|
|
147
151
|
:param e: Event
|
|
148
152
|
"""
|
|
149
153
|
if e.key() == Qt.Key_F and e.modifiers() & Qt.ControlModifier:
|
|
150
|
-
self.find_open()
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
self.find_open()
|
|
155
|
+
return
|
|
156
|
+
super().keyPressEvent(e)
|
|
153
157
|
|
|
154
158
|
def wheelEvent(self, event):
|
|
155
159
|
"""
|
|
@@ -158,26 +162,33 @@ class ChatOutput(QTextBrowser):
|
|
|
158
162
|
:param event: Event
|
|
159
163
|
"""
|
|
160
164
|
if event.modifiers() & Qt.ControlModifier:
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
dy = event.angleDelta().y()
|
|
166
|
+
if dy > 0:
|
|
167
|
+
new_value = min(self.value + 1, self.max_font_size)
|
|
168
|
+
elif dy < 0:
|
|
169
|
+
new_value = max(self.value - 1, self.min_font_size)
|
|
164
170
|
else:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
self.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
)
|
|
177
|
-
|
|
171
|
+
event.accept()
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
if new_value == self.value:
|
|
175
|
+
event.accept()
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
self.value = new_value
|
|
179
|
+
|
|
180
|
+
cfg = self.window.core.config
|
|
181
|
+
cfg.data['font_size'] = new_value
|
|
182
|
+
cfg.save()
|
|
183
|
+
|
|
184
|
+
ctrl = self.window.controller
|
|
185
|
+
option = ctrl.settings.editor.get_option('font_size')
|
|
186
|
+
option['value'] = new_value
|
|
187
|
+
ctrl.config.apply(parent_id='config', key='font_size', option=option)
|
|
188
|
+
ctrl.ui.update_font_size()
|
|
178
189
|
event.accept()
|
|
179
190
|
else:
|
|
180
|
-
super(
|
|
191
|
+
super().wheelEvent(event)
|
|
181
192
|
|
|
182
193
|
def focusInEvent(self, e):
|
|
183
194
|
"""
|
|
@@ -185,5 +196,5 @@ class ChatOutput(QTextBrowser):
|
|
|
185
196
|
|
|
186
197
|
:param e: focus event
|
|
187
198
|
"""
|
|
188
|
-
super(
|
|
189
|
-
self.window.controller.finder.focus_in(self.finder)
|
|
199
|
+
super().focusInEvent(e)
|
|
200
|
+
self.window.controller.finder.focus_in(self.finder)
|
|
@@ -6,15 +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: 2025.08.
|
|
9
|
+
# Updated Date: 2025.08.24 23:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
from PySide6.QtGui import QAction, QIcon
|
|
13
|
-
from PySide6.QtWidgets import QLineEdit
|
|
14
|
-
from PySide6.QtCore import QTimer
|
|
13
|
+
from PySide6.QtWidgets import QLineEdit
|
|
14
|
+
from PySide6.QtCore import QTimer, Slot
|
|
15
15
|
|
|
16
16
|
from pygpt_net.utils import trans
|
|
17
|
-
import pygpt_net.icons_rc
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class CtxSearchInput(QLineEdit):
|
|
@@ -56,6 +55,7 @@ class CtxSearchInput(QLineEdit):
|
|
|
56
55
|
self._search_timer.stop() # stop the timer to prevent triggering the search action
|
|
57
56
|
self.window.controller.ctx.search_string_clear()
|
|
58
57
|
|
|
58
|
+
@Slot(str)
|
|
59
59
|
def on_text_changed(self, text):
|
|
60
60
|
"""
|
|
61
61
|
On text changed
|
|
@@ -67,6 +67,7 @@ class CtxSearchInput(QLineEdit):
|
|
|
67
67
|
# after a pause in typing
|
|
68
68
|
self._search_timer.start()
|
|
69
69
|
|
|
70
|
+
@Slot()
|
|
70
71
|
def _execute_search(self):
|
|
71
72
|
"""Invoke the search action after a specified delay."""
|
|
72
73
|
search_text = self.text()
|