pygpt-net 2.4.39__py3-none-any.whl → 2.4.40__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 (85) hide show
  1. CHANGELOG.md +13 -0
  2. README.md +19 -2
  3. pygpt_net/CHANGELOG.txt +13 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/controller/__init__.py +5 -3
  6. pygpt_net/controller/audio/__init__.py +9 -1
  7. pygpt_net/controller/chat/input.py +2 -1
  8. pygpt_net/controller/chat/render.py +2 -2
  9. pygpt_net/controller/ctx/__init__.py +2 -2
  10. pygpt_net/controller/debug/__init__.py +13 -2
  11. pygpt_net/controller/kernel/__init__.py +2 -1
  12. pygpt_net/controller/notepad.py +7 -6
  13. pygpt_net/controller/theme/nodes.py +2 -5
  14. pygpt_net/controller/tools/__init__.py +37 -1
  15. pygpt_net/controller/ui/__init__.py +1 -5
  16. pygpt_net/controller/ui/tabs.py +104 -12
  17. pygpt_net/core/command.py +3 -1
  18. pygpt_net/core/ctx/__init__.py +6 -2
  19. pygpt_net/core/ctx/container.py +5 -5
  20. pygpt_net/core/debug/tabs.py +3 -1
  21. pygpt_net/core/render/base.py +2 -2
  22. pygpt_net/core/render/web/body.py +1 -1
  23. pygpt_net/core/render/web/renderer.py +208 -38
  24. pygpt_net/core/tabs/__init__.py +104 -43
  25. pygpt_net/core/tabs/tab.py +4 -1
  26. pygpt_net/core/web.py +127 -1
  27. pygpt_net/data/config/config.json +4 -3
  28. pygpt_net/data/config/models.json +3 -3
  29. pygpt_net/data/config/modes.json +3 -3
  30. pygpt_net/data/css/web-blocks.css +18 -0
  31. pygpt_net/data/css/web-blocks.light.css +7 -0
  32. pygpt_net/data/css/web-chatgpt.css +8 -0
  33. pygpt_net/data/css/web-chatgpt_wide.css +8 -0
  34. pygpt_net/data/icons/split_screen.svg +1 -0
  35. pygpt_net/data/locale/locale.de.ini +1 -1
  36. pygpt_net/data/locale/locale.en.ini +4 -2
  37. pygpt_net/data/locale/locale.es.ini +1 -1
  38. pygpt_net/data/locale/locale.fr.ini +1 -1
  39. pygpt_net/data/locale/locale.it.ini +1 -1
  40. pygpt_net/data/locale/locale.pl.ini +2 -2
  41. pygpt_net/data/locale/locale.uk.ini +1 -1
  42. pygpt_net/data/locale/locale.zh.ini +1 -1
  43. pygpt_net/data/locale/plugin.cmd_web.de.ini +2 -0
  44. pygpt_net/data/locale/plugin.cmd_web.en.ini +20 -10
  45. pygpt_net/data/locale/plugin.cmd_web.es.ini +2 -0
  46. pygpt_net/data/locale/plugin.cmd_web.fr.ini +2 -0
  47. pygpt_net/data/locale/plugin.cmd_web.it.ini +2 -0
  48. pygpt_net/data/locale/plugin.cmd_web.pl.ini +2 -0
  49. pygpt_net/data/locale/plugin.cmd_web.uk.ini +2 -0
  50. pygpt_net/data/locale/plugin.cmd_web.zh.ini +2 -0
  51. pygpt_net/icons.qrc +1 -0
  52. pygpt_net/icons_rc.py +165 -136
  53. pygpt_net/item/ctx.py +46 -24
  54. pygpt_net/plugin/audio_output/__init__.py +4 -1
  55. pygpt_net/plugin/base/plugin.py +18 -4
  56. pygpt_net/plugin/cmd_code_interpreter/__init__.py +39 -37
  57. pygpt_net/plugin/cmd_code_interpreter/runner.py +25 -12
  58. pygpt_net/plugin/cmd_web/__init__.py +46 -6
  59. pygpt_net/plugin/cmd_web/config.py +74 -48
  60. pygpt_net/plugin/cmd_web/websearch.py +61 -28
  61. pygpt_net/plugin/cmd_web/worker.py +79 -13
  62. pygpt_net/provider/core/config/patch.py +22 -1
  63. pygpt_net/tools/__init__.py +9 -1
  64. pygpt_net/tools/base.py +15 -1
  65. pygpt_net/tools/code_interpreter/__init__.py +174 -75
  66. pygpt_net/tools/code_interpreter/ui/dialogs.py +21 -103
  67. pygpt_net/tools/code_interpreter/ui/widgets.py +284 -9
  68. pygpt_net/tools/html_canvas/__init__.py +78 -23
  69. pygpt_net/tools/html_canvas/ui/dialogs.py +46 -62
  70. pygpt_net/tools/html_canvas/ui/widgets.py +96 -3
  71. pygpt_net/ui/base/context_menu.py +2 -2
  72. pygpt_net/ui/layout/ctx/ctx_list.py +13 -4
  73. pygpt_net/ui/layout/toolbox/footer.py +1 -1
  74. pygpt_net/ui/main.py +2 -2
  75. pygpt_net/ui/menu/debug.py +11 -1
  76. pygpt_net/ui/widget/filesystem/explorer.py +2 -2
  77. pygpt_net/ui/widget/lists/context.py +26 -5
  78. pygpt_net/ui/widget/tabs/Input.py +2 -2
  79. pygpt_net/ui/widget/tabs/body.py +2 -1
  80. pygpt_net/ui/widget/tabs/output.py +126 -61
  81. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/METADATA +20 -3
  82. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/RECORD +85 -84
  83. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/LICENSE +0 -0
  84. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/WHEEL +0 -0
  85. {pygpt_net-2.4.39.dist-info → pygpt_net-2.4.40.dist-info}/entry_points.txt +0 -0
CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.4.40 (2024-12-13)
4
+
5
+ - Enhanced Split Screen mode, now promoted from beta to stable.
6
+ - Python Code Interpreter tool added to the Tabs.
7
+ - HTML/JS Canvas tool added to the Tabs.
8
+ - Added attachment icon to the context list if context has attachments.
9
+ - Improved audio playback.
10
+ - Improved web search.
11
+ - Added a thumbnail image to web search results.
12
+ - Added a new commands to web search: "extract_images" and "extract_links".
13
+ - Added the option "Use raw content (without summarization)" to the web search plugin, which provides a more detailed result to the main model.
14
+ - Extended the default maximum result characters to 50,000 in the web search plugin.
15
+
3
16
  ## 2.4.39 (2024-12-09)
4
17
 
5
18
  - Added "Split Screen" mode (accessible via the switch in the bottom-right corner of the screen), which allows you to work in two windows simultaneously. It is currently experimental (beta). Future updates will include Code Interpreter and Canvas running in tabs.
README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![pygpt](https://snapcraft.io/pygpt/badge.svg)](https://snapcraft.io/pygpt)
4
4
 
5
- Release: **2.4.39** | build: **2024.12.09** | Python: **>=3.10, <3.12**
5
+ Release: **2.4.40** | build: **2024.12.13** | Python: **>=3.10, <3.12**
6
6
 
7
7
  > Official website: https://pygpt.net | Documentation: https://pygpt.readthedocs.io
8
8
  >
@@ -1984,6 +1984,10 @@ Per-page content chunk size (max characters per chunk). *Default:* `20000`
1984
1984
 
1985
1985
  Disables SSL verification when crawling web pages. *Default:* `False`
1986
1986
 
1987
+ - `Use raw content (without summarization)` *raw*
1988
+
1989
+ Return raw content from web search instead of summarized content. Provides more data but consumes more tokens. *Default:* `True`
1990
+
1987
1991
  - `Timeout` *timeout*
1988
1992
 
1989
1993
  Connection timeout (seconds). *Default:* `5`
@@ -1994,7 +1998,7 @@ User agent to use when making requests. *Default:* `Mozilla/5.0`.
1994
1998
 
1995
1999
  - `Max result length` *max_result_length*
1996
2000
 
1997
- Max length of summarized result (characters). *Default:* `1500`
2001
+ Max length of the summarized or raw result (characters). *Default:* `50000`
1998
2002
 
1999
2003
  - `Max summary tokens` *summary_max_tokens*
2000
2004
 
@@ -3873,6 +3877,19 @@ may consume additional tokens that are not displayed in the main window.
3873
3877
 
3874
3878
  ## Recent changes:
3875
3879
 
3880
+ **2.4.40 (2024-12-13)**
3881
+
3882
+ - Enhanced Split Screen mode, now promoted from beta to stable.
3883
+ - Python Code Interpreter tool added to the Tabs.
3884
+ - HTML/JS Canvas tool added to the Tabs.
3885
+ - Added attachment icon to the context list if context has attachments.
3886
+ - Improved audio playback.
3887
+ - Improved web search.
3888
+ - Added a thumbnail image to web search results.
3889
+ - Added a new commands to web search: "extract_images" and "extract_links".
3890
+ - Added the option "Use raw content (without summarization)" to the web search plugin, which provides a more detailed result to the main model.
3891
+ - Extended the default maximum result characters to 50,000 in the web search plugin.
3892
+
3876
3893
  **2.4.39 (2024-12-09)**
3877
3894
 
3878
3895
  - Added "Split Screen" mode (accessible via the switch in the bottom-right corner of the screen), which allows you to work in two windows simultaneously. It is currently experimental (beta). Future updates will include Code Interpreter and Canvas running in tabs.
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,16 @@
1
+ 2.4.40 (2024-12-13)
2
+
3
+ - Enhanced Split Screen mode, now promoted from beta to stable.
4
+ - Python Code Interpreter tool added to the Tabs.
5
+ - HTML/JS Canvas tool added to the Tabs.
6
+ - Added attachment icon to the context list if context has attachments.
7
+ - Improved audio playback.
8
+ - Improved web search.
9
+ - Added a thumbnail image to web search results.
10
+ - Added a new commands to web search: "extract_images" and "extract_links".
11
+ - Added the option "Use raw content (without summarization)" to the web search plugin, which provides a more detailed result to the main model.
12
+ - Extended the default maximum result characters to 50,000 in the web search plugin.
13
+
1
14
  2.4.39 (2024-12-09)
2
15
 
3
16
  - Added "Split Screen" mode (accessible via the switch in the bottom-right corner of the screen), which allows you to work in two windows simultaneously. It is currently experimental (beta). Future updates will include Code Interpreter and Canvas running in tabs.
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.12.08 01:00:00 #
9
+ # Updated Date: 2024.12.13 21: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.39"
17
- __build__ = "2024.12.09"
16
+ __version__ = "2.4.40"
17
+ __build__ = "2024.12.13"
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.12.09 00:00:00 #
9
+ # Updated Date: 2024.12.09 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.controller.access import Access
@@ -87,15 +87,16 @@ class Controller:
87
87
  self.chat.init()
88
88
 
89
89
  # setup layout
90
- self.ui.pre_setup()
91
90
  self.layout.setup()
92
91
  self.ui.setup()
92
+ self.ui.tabs.setup()
93
93
 
94
94
  # setup controllers
95
95
  self.lang.setup()
96
96
  self.assistant.setup()
97
97
  self.chat.setup()
98
98
  self.agent.setup()
99
+ self.tools.setup()
99
100
  self.ctx.setup()
100
101
  self.presets.setup()
101
102
  self.idx.setup()
@@ -105,7 +106,8 @@ class Controller:
105
106
  self.attachment.setup()
106
107
  self.camera.setup_ui()
107
108
  self.access.setup()
108
- self.tools.setup()
109
+
110
+
109
111
 
110
112
  def post_setup(self):
111
113
  """Post-setup, after plugins are loaded"""
@@ -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.26 19:00:00 #
9
+ # Updated Date: 2024.12.13 08:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -200,6 +200,14 @@ class Audio:
200
200
  }
201
201
  self.window.dispatch(event, all=True)
202
202
 
203
+ def stop_audio(self):
204
+ """Stop audio playback"""
205
+ ctx = CtxItem()
206
+ event = Event(Event.AUDIO_OUTPUT_STOP)
207
+ event.ctx = ctx
208
+ event.data = {}
209
+ self.window.dispatch(event, all=True)
210
+
203
211
  def play_sound(self, filename: str):
204
212
  """
205
213
  Play sound
@@ -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.26 19:00:00 #
9
+ # Updated Date: 2024.12.12 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.bridge import BridgeContext
@@ -52,6 +52,7 @@ class Input:
52
52
  """
53
53
  self.window.controller.agent.experts.unlock() # unlock experts
54
54
  self.window.controller.agent.llama.reset_eval_step() # reset evaluation steps
55
+ self.window.controller.ui.tabs.switch_to_first_chat() # switch to first active chat tab
55
56
 
56
57
  # get text from input
57
58
  text = self.window.ui.nodes['input'].toPlainText().strip()
@@ -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.12.09 03:00:00 #
9
+ # Updated Date: 2024.12.12 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Slot, QTimer
@@ -16,7 +16,7 @@ from pygpt_net.core.render.base import BaseRenderer
16
16
  from pygpt_net.core.render.markdown.renderer import Renderer as MarkdownRenderer
17
17
  from pygpt_net.core.render.plain.renderer import Renderer as PlainTextRenderer
18
18
  from pygpt_net.core.render.web.renderer import Renderer as WebRenderer
19
- from pygpt_net.core.tabs import Tab
19
+ from pygpt_net.core.tabs.tab import Tab
20
20
  from pygpt_net.core.text.utils import output_html2text, output_clean_html
21
21
  from pygpt_net.item.ctx import CtxItem, CtxMeta
22
22
 
@@ -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.12.09 00:00:00 #
9
+ # Updated Date: 2024.12.12 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import QModelIndex
@@ -19,7 +19,7 @@ from .summarizer import Summarizer
19
19
  from .extra import Extra
20
20
 
21
21
  from pygpt_net.utils import trans
22
- from pygpt_net.core.tabs import Tab
22
+ from pygpt_net.core.tabs.tab import Tab
23
23
 
24
24
 
25
25
  class Ctx:
@@ -15,6 +15,8 @@ from logging import ERROR, WARNING, INFO, DEBUG
15
15
  from PySide6.QtCore import Slot, QObject
16
16
  from PySide6.QtGui import QTextCursor
17
17
 
18
+ from pygpt_net.core.events import RenderEvent
19
+
18
20
 
19
21
  class Debug(QObject):
20
22
  def __init__(self, window=None):
@@ -53,8 +55,17 @@ class Debug(QObject):
53
55
 
54
56
  def toggle_menu(self):
55
57
  """Toggle debug menu"""
56
- stage = self.window.core.config.get('debug')
57
- self.window.ui.menu['menu.debug'].menuAction().setVisible(stage)
58
+ state = self.window.core.config.get('debug')
59
+ self.window.ui.menu['menu.debug'].menuAction().setVisible(state)
60
+
61
+ def toggle_render(self):
62
+ """Toggle render debug"""
63
+ value = self.window.ui.menu['debug.render'].isChecked()
64
+ self.window.core.config.set('debug.render', value)
65
+ self.window.core.config.save()
66
+ event = RenderEvent(RenderEvent.ON_THEME_CHANGE)
67
+ self.window.dispatch(event)
68
+ self.window.controller.ctx.refresh()
58
69
 
59
70
  def set_log_level(self, level: str = 'error'):
60
71
  """
@@ -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.12.13 08:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import time
@@ -251,6 +251,7 @@ class Kernel(QObject):
251
251
  """
252
252
  self.halt = True
253
253
  self.window.controller.chat.common.stop(exit=exit) # it stops legacy agent also
254
+ self.window.controller.audio.stop_audio()
254
255
  if not exit:
255
256
  self.window.dispatch(KernelEvent(KernelEvent.STOP))
256
257
  self.set_state(KernelEvent(KernelEvent.STATE_IDLE, {"msg": trans("status.stopped")}))
@@ -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.12.09 00:00:00 #
9
+ # Updated Date: 2024.12.12 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtGui import QTextCursor
@@ -38,13 +38,14 @@ class Notepad:
38
38
  """
39
39
  if idx is None:
40
40
  idx = self.window.core.tabs.count_by_type(Tab.TAB_NOTEPAD) + 1
41
- self.window.ui.notepad[idx] = NotepadWidget(self.window)
42
- self.window.ui.notepad[idx].id = idx
43
- self.window.ui.notepad[idx].textarea.id = idx
41
+ data_id = idx
42
+ self.window.ui.notepad[data_id] = NotepadWidget(self.window)
43
+ self.window.ui.notepad[data_id].id = idx
44
+ self.window.ui.notepad[data_id].textarea.id = idx
44
45
  title = trans('output.tab.notepad')
45
46
  title += " " + str(idx)
46
- children = self.window.core.tabs.from_widget(self.window.ui.notepad[idx])
47
- return children, idx
47
+ children = self.window.core.tabs.from_widget(self.window.ui.notepad[data_id])
48
+ return children, idx, data_id
48
49
 
49
50
  def load(self):
50
51
  """Load all notepads contents"""
@@ -6,9 +6,9 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.20 21:00:00 #
9
+ # Updated Date: 2024.12.12 01:00:00 #
10
10
  # ================================================== #
11
- #
11
+
12
12
  from pygpt_net.core.events import RenderEvent
13
13
 
14
14
 
@@ -93,9 +93,6 @@ class Nodes:
93
93
  continue
94
94
  self.apply(key, type)
95
95
 
96
- # self.window.interpreter.update_stylesheet(self.window.controller.theme.style('font.chat.output'))
97
- # self.window.ui.nodes['interpreter.input'].update_stylesheet(self.window.controller.theme.style('font.chat.input'))
98
-
99
96
  # apply to notepads
100
97
  num_notepads = self.window.controller.notepad.get_num_notepads()
101
98
  if num_notepads > 0:
@@ -6,10 +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.07 23:00:00 #
9
+ # Updated Date: 2024.12.12 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ from PySide6.QtGui import QAction, QIcon
13
+ from PySide6.QtWidgets import QTabWidget, QMenu
14
+
12
15
  from pygpt_net.core.tabs.tab import Tab
16
+ from pygpt_net.utils import trans
17
+
13
18
 
14
19
  class Tools:
15
20
  def __init__(self, window=None):
@@ -44,6 +49,37 @@ class Tools:
44
49
  if idx is not None:
45
50
  self.window.controller.ui.tabs.switch_tab_by_idx(idx)
46
51
 
52
+ def append_tab_menu(
53
+ self,
54
+ parent: QTabWidget,
55
+ menu: QMenu,
56
+ idx: int,
57
+ column_idx: int
58
+ ) -> QMenu:
59
+ """
60
+ Append tab menu
61
+
62
+ :param parent: parent widget
63
+ :param menu: menu
64
+ :param idx: tab index
65
+ :param column_idx: column index
66
+ :return: tab add submenu
67
+ """
68
+ submenu = menu.addMenu(QIcon(":/icons/add.svg"), trans("action.tab.add.tool"))
69
+ tools = self.window.tools.get_all()
70
+ for id in tools:
71
+ tool = tools[id]
72
+ if not tool.has_tab:
73
+ continue
74
+ icon = tool.tab_icon
75
+ title = trans(tool.tab_title)
76
+ action = QAction(QIcon(icon), title, parent)
77
+ action.triggered.connect(
78
+ lambda idx=idx, column_idx=column_idx, id=id: parent.add_tab(idx, column_idx, Tab.TAB_TOOL, id)
79
+ )
80
+ submenu.addAction(action)
81
+ return submenu
82
+
47
83
  def get_tab_tools(self) -> dict:
48
84
  """
49
85
  Get tab tools
@@ -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.12.09 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtGui import QColor
@@ -47,10 +47,6 @@ class UI:
47
47
  self.update()
48
48
  self.init_toolbox()
49
49
 
50
- def pre_setup(self):
51
- """Post setup UI"""
52
- self.tabs.setup()
53
-
54
50
  def update(self):
55
51
  """Update all elements"""
56
52
 
@@ -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.12.09 03:00:00 #
9
+ # Updated Date: 2024.12.12 01:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import QTimer
@@ -32,6 +32,7 @@ class Tabs:
32
32
  self.column_idx = 0
33
33
  self.tmp_column_idx = 0
34
34
  self.locked = False
35
+ self.col = {}
35
36
 
36
37
  def setup(self):
37
38
  """Setup tabs"""
@@ -53,7 +54,8 @@ class Tabs:
53
54
  title: str,
54
55
  icon=None,
55
56
  child=None,
56
- data_id=None
57
+ data_id=None,
58
+ tool_id=None,
57
59
  ):
58
60
  """
59
61
  Add a new tab
@@ -63,37 +65,55 @@ class Tabs:
63
65
  :param icon: Tab icon
64
66
  :param child: Tab child (child widget)
65
67
  :param data_id: Tab data ID (child data ID)
68
+ :param tool_id: Tool ID
66
69
  """
67
70
  self.window.core.tabs.add(
68
71
  type=type,
69
72
  title=title,
70
73
  icon=icon,
71
74
  child=child,
72
- data_id=data_id
75
+ data_id=data_id,
76
+ tool_id=tool_id
73
77
  )
74
78
 
75
79
  def append(
76
80
  self,
77
81
  type: int,
78
- idx: int,
82
+ tool_id: str = None,
83
+ idx: int = 0,
79
84
  column_idx: int = 0
80
85
  ):
81
86
  """
82
87
  Append tab at tab index
83
88
 
84
89
  :param type: Tab type
90
+ :param tool_id: Tool ID
85
91
  :param idx: Tab index
86
92
  :param column_idx: Column index
87
93
  """
88
94
  self.appended = True # lock reload in previous tab
89
95
  self.column_idx = column_idx # switch to column
90
- self.window.core.tabs.append(type, idx, column_idx)
91
- self.switch_tab_by_idx(idx + 1, column_idx) # switch to new tab
96
+ tab = self.window.core.tabs.append(
97
+ type=type,
98
+ idx=idx,
99
+ column_idx=column_idx,
100
+ tool_id=tool_id
101
+ )
102
+ self.switch_tab_by_idx(tab.idx, column_idx) # switch to new tab
92
103
 
93
104
  def reload_titles(self):
94
105
  """Reload tab titles"""
95
106
  self.window.core.tabs.reload_titles()
96
107
 
108
+ def update_current(self):
109
+ """Update current tab"""
110
+ curr_tab = self.get_current_tab()
111
+ curr_column = self.get_current_column_idx()
112
+ if curr_column not in self.col:
113
+ self.col[curr_column] = -1
114
+ if curr_tab is not None:
115
+ self.col[curr_column] = curr_tab.pid
116
+
97
117
  def reload(self):
98
118
  """Reload tabs"""
99
119
  self.window.core.tabs.reload()
@@ -131,7 +151,6 @@ class Tabs:
131
151
  if meta is not None:
132
152
  self.window.controller.ctx.load(meta.id) # reload
133
153
 
134
-
135
154
  prev_tab = self.current
136
155
  prev_column = self.column_idx
137
156
  self.current = idx
@@ -156,6 +175,9 @@ class Tabs:
156
175
  if prev_tab != idx or prev_column != column_idx:
157
176
  self.window.dispatch(AppEvent(AppEvent.TAB_SELECTED)) # app event
158
177
 
178
+ self.window.controller.ui.update()
179
+ self.update_current()
180
+
159
181
  def get_current_idx(self, column_idx: int = 0) -> int:
160
182
  """
161
183
  Get current tab index
@@ -234,6 +256,8 @@ class Tabs:
234
256
  current_ctx = self.window.core.ctx.get_current()
235
257
  if current_ctx is not None and current_ctx != tab.data_id:
236
258
  self.window.controller.ctx.select_on_list_only(tab.data_id)
259
+ self.window.controller.ui.update()
260
+ self.update_current()
237
261
 
238
262
  def on_tab_clicked(self, idx: int, column_idx: int = 0):
239
263
  """
@@ -245,6 +269,7 @@ class Tabs:
245
269
  self.current = idx
246
270
  self.column_idx = column_idx
247
271
  self.on_column_changed()
272
+ self.update_current()
248
273
 
249
274
  def on_column_focus(self, idx: int):
250
275
  """
@@ -254,6 +279,7 @@ class Tabs:
254
279
  """
255
280
  self.column_idx = idx
256
281
  self.on_column_changed()
282
+ self.update_current()
257
283
 
258
284
  def on_tab_dbl_clicked(self, idx: int, column_idx: int = 0):
259
285
  """
@@ -264,6 +290,7 @@ class Tabs:
264
290
  """
265
291
  self.column_idx = column_idx
266
292
  self.on_tab_changed(idx, column_idx)
293
+ self.update_current()
267
294
 
268
295
  def on_tab_closed(self, idx: int, column_idx: int = 0):
269
296
  """
@@ -272,7 +299,10 @@ class Tabs:
272
299
  :param idx: tab index
273
300
  :param column_idx: column index
274
301
  """
302
+ if self.locked:
303
+ return
275
304
  self.window.core.tabs.remove_tab_by_idx(idx, column_idx)
305
+ self.update_current()
276
306
 
277
307
  def on_tab_moved(self, idx: int, column_idx: int = 0):
278
308
  """
@@ -281,7 +311,10 @@ class Tabs:
281
311
  :param idx: tab index
282
312
  :param column_idx: column index
283
313
  """
314
+ if self.locked:
315
+ return
284
316
  self.window.core.tabs.update()
317
+ self.update_current()
285
318
 
286
319
  def close(self, idx: int, column_idx: int = 0):
287
320
  """
@@ -291,6 +324,7 @@ class Tabs:
291
324
  :param column_idx: column index
292
325
  """
293
326
  self.on_tab_closed(idx, column_idx)
327
+ self.update_current()
294
328
 
295
329
  def close_all(
296
330
  self,
@@ -315,6 +349,7 @@ class Tabs:
315
349
  return
316
350
  column_idx = self.tmp_column_idx
317
351
  self.window.core.tabs.remove_all_by_type(type, column_idx)
352
+ self.update_current()
318
353
 
319
354
  def next_tab(self):
320
355
  """Switch to next tab"""
@@ -487,7 +522,12 @@ class Tabs:
487
522
  :param column_idx: column index
488
523
  """
489
524
  idx = self.get_current_idx(column_idx)
490
- self.append(Tab.TAB_CHAT, idx, column_idx)
525
+ self.append(
526
+ type=Tab.TAB_CHAT,
527
+ tool_id=None,
528
+ idx=idx,
529
+ column_idx=column_idx
530
+ )
491
531
 
492
532
  def restore_data(self):
493
533
  """Restore tab data"""
@@ -495,6 +535,9 @@ class Tabs:
495
535
  if not data:
496
536
  self.switch_tab_by_idx(0, 0)
497
537
  return
538
+
539
+ # reverse order, second column is first
540
+ data = dict(reversed(list(data.items())))
498
541
  for col_idx in data:
499
542
  tab_idx = data[col_idx]
500
543
  self.switch_tab_by_idx(int(tab_idx), int(col_idx))
@@ -515,18 +558,67 @@ class Tabs:
515
558
  tab = self.window.core.tabs.get_tab_by_index(idx, column_idx)
516
559
  self.window.core.tabs.move_tab(tab, new_column_idx)
517
560
  self.locked = False
561
+ # switch to new column
562
+ self.column_idx = new_column_idx
563
+ self.on_column_changed()
564
+ # switch to new tab
565
+ self.switch_tab_by_idx(tab.idx, new_column_idx)
518
566
 
519
567
  def toggle_split_screen(self, state):
520
568
  """
521
- Toggle split screen
569
+ Toggle split screen mode
522
570
 
523
- :param state: state
571
+ :param state: True if split screen is enabled
524
572
  """
525
573
  if state:
526
- #self.rightWidget.show()
574
+ # self.rightWidget.show()
527
575
  self.window.ui.splitters['columns'].setSizes([1, 1])
528
576
  else:
529
- #self.rightWidget.hide()
577
+ # self.rightWidget.hide()
530
578
  self.window.ui.splitters['columns'].setSizes([1, 0])
579
+ # set to first column
580
+ self.column_idx = 0
581
+ self.on_column_changed()
531
582
  self.window.core.config.set("layout.split", state)
532
583
  self.window.core.config.save()
584
+
585
+ def is_current_by_type(self, type: int) -> bool:
586
+ """
587
+ Check if one of current tabs is of given type
588
+
589
+ :param type: tab type
590
+ :return: True if one of tab is of given type
591
+ """
592
+ for col in self.col:
593
+ pid = self.col[col]
594
+ tab = self.window.core.tabs.get_tab_by_pid(pid)
595
+ if tab is not None and tab.type == type:
596
+ return True
597
+
598
+ def is_current_tool(self, tool_id: str) -> bool:
599
+ """
600
+ Check if one of current tabs is of given tool ID
601
+
602
+ :param tool_id: tool ID
603
+ :return: True if one of tab is of given tool ID
604
+ """
605
+ for col in self.col:
606
+ pid = self.col[col]
607
+ tab = self.window.core.tabs.get_tab_by_pid(pid)
608
+ if tab is not None and tab.tool_id == tool_id:
609
+ return True
610
+
611
+ def switch_to_first_chat(self):
612
+ """Switch to first chat tab"""
613
+ if self.is_current_by_type(Tab.TAB_CHAT):
614
+ return
615
+ # abort if active tab is chat
616
+ if self.get_current_type() == Tab.TAB_CHAT:
617
+ return
618
+ # find first chat tab
619
+ for col in self.col:
620
+ pid = self.col[col]
621
+ tab = self.window.core.tabs.get_tab_by_pid(pid)
622
+ if tab is not None and tab.type == Tab.TAB_CHAT:
623
+ self.switch_tab_by_idx(tab.idx, col)
624
+ return