pygpt-net 2.4.37__py3-none-any.whl → 2.4.42__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 (340) hide show
  1. CHANGELOG.md +37 -0
  2. README.md +176 -182
  3. pygpt_net/CHANGELOG.txt +37 -0
  4. pygpt_net/__init__.py +3 -3
  5. pygpt_net/app.py +3 -1
  6. pygpt_net/controller/__init__.py +7 -3
  7. pygpt_net/controller/access/control.py +1 -1
  8. pygpt_net/controller/access/voice.py +11 -5
  9. pygpt_net/controller/agent/experts.py +11 -6
  10. pygpt_net/controller/agent/legacy.py +8 -6
  11. pygpt_net/controller/agent/llama.py +4 -2
  12. pygpt_net/controller/assistant/__init__.py +9 -4
  13. pygpt_net/controller/assistant/batch.py +38 -21
  14. pygpt_net/controller/assistant/editor.py +7 -6
  15. pygpt_net/controller/assistant/files.py +23 -7
  16. pygpt_net/controller/assistant/store.py +20 -7
  17. pygpt_net/controller/assistant/threads.py +34 -8
  18. pygpt_net/controller/attachment.py +29 -10
  19. pygpt_net/controller/audio/__init__.py +26 -5
  20. pygpt_net/controller/calendar/__init__.py +23 -4
  21. pygpt_net/controller/calendar/note.py +57 -11
  22. pygpt_net/controller/camera.py +4 -12
  23. pygpt_net/controller/chat/__init__.py +5 -3
  24. pygpt_net/controller/chat/attachment.py +34 -7
  25. pygpt_net/controller/chat/audio.py +2 -2
  26. pygpt_net/controller/chat/command.py +4 -2
  27. pygpt_net/controller/chat/common.py +11 -4
  28. pygpt_net/controller/chat/files.py +10 -3
  29. pygpt_net/controller/chat/image.py +17 -5
  30. pygpt_net/controller/chat/input.py +10 -7
  31. pygpt_net/controller/chat/output.py +21 -6
  32. pygpt_net/controller/chat/render.py +100 -21
  33. pygpt_net/controller/chat/response.py +34 -7
  34. pygpt_net/controller/chat/stream.py +4 -2
  35. pygpt_net/controller/chat/text.py +6 -4
  36. pygpt_net/controller/command.py +11 -3
  37. pygpt_net/controller/config/__init__.py +34 -6
  38. pygpt_net/controller/config/field/checkbox.py +7 -4
  39. pygpt_net/controller/config/field/cmd.py +7 -5
  40. pygpt_net/controller/config/field/combo.py +14 -6
  41. pygpt_net/controller/config/field/dictionary.py +14 -11
  42. pygpt_net/controller/config/field/input.py +9 -6
  43. pygpt_net/controller/config/field/slider.py +11 -8
  44. pygpt_net/controller/config/field/textarea.py +8 -5
  45. pygpt_net/controller/config/placeholder.py +66 -21
  46. pygpt_net/controller/ctx/__init__.py +138 -49
  47. pygpt_net/controller/ctx/common.py +15 -4
  48. pygpt_net/controller/ctx/extra.py +11 -3
  49. pygpt_net/controller/ctx/summarizer.py +24 -5
  50. pygpt_net/controller/debug/__init__.py +27 -6
  51. pygpt_net/controller/dialogs/confirm.py +34 -7
  52. pygpt_net/controller/dialogs/debug.py +4 -2
  53. pygpt_net/controller/dialogs/info.py +7 -2
  54. pygpt_net/controller/files.py +48 -10
  55. pygpt_net/controller/finder.py +11 -5
  56. pygpt_net/controller/idx/__init__.py +10 -3
  57. pygpt_net/controller/idx/common.py +4 -2
  58. pygpt_net/controller/idx/indexer.py +25 -17
  59. pygpt_net/controller/idx/settings.py +9 -3
  60. pygpt_net/controller/kernel/__init__.py +34 -8
  61. pygpt_net/controller/kernel/reply.py +12 -3
  62. pygpt_net/controller/kernel/stack.py +5 -3
  63. pygpt_net/controller/lang/custom.py +2 -7
  64. pygpt_net/controller/lang/mapping.py +5 -3
  65. pygpt_net/controller/layout.py +2 -2
  66. pygpt_net/controller/mode.py +16 -4
  67. pygpt_net/controller/model/__init__.py +14 -3
  68. pygpt_net/controller/model/editor.py +8 -3
  69. pygpt_net/controller/notepad.py +26 -12
  70. pygpt_net/controller/painter/capture.py +23 -4
  71. pygpt_net/controller/painter/common.py +9 -7
  72. pygpt_net/controller/plugins/__init__.py +19 -5
  73. pygpt_net/controller/plugins/presets.py +15 -6
  74. pygpt_net/controller/plugins/settings.py +9 -3
  75. pygpt_net/controller/presets/__init__.py +55 -16
  76. pygpt_net/controller/presets/editor.py +26 -10
  77. pygpt_net/controller/settings/__init__.py +3 -2
  78. pygpt_net/controller/settings/editor.py +29 -7
  79. pygpt_net/controller/settings/profile.py +22 -5
  80. pygpt_net/controller/theme/__init__.py +54 -12
  81. pygpt_net/controller/theme/common.py +24 -2
  82. pygpt_net/controller/theme/markdown.py +32 -16
  83. pygpt_net/controller/theme/menu.py +26 -5
  84. pygpt_net/controller/theme/nodes.py +2 -5
  85. pygpt_net/controller/tools/__init__.py +40 -2
  86. pygpt_net/controller/ui/__init__.py +4 -6
  87. pygpt_net/controller/ui/mode.py +16 -21
  88. pygpt_net/controller/ui/tabs.py +363 -65
  89. pygpt_net/core/access/actions.py +6 -4
  90. pygpt_net/core/access/shortcuts.py +4 -3
  91. pygpt_net/core/access/voice.py +6 -5
  92. pygpt_net/core/agents/legacy.py +4 -2
  93. pygpt_net/core/agents/memory.py +7 -2
  94. pygpt_net/core/agents/observer/evaluation.py +15 -7
  95. pygpt_net/core/agents/provider.py +9 -4
  96. pygpt_net/core/agents/runner.py +61 -15
  97. pygpt_net/core/agents/tools.py +23 -5
  98. pygpt_net/core/assistants/__init__.py +6 -4
  99. pygpt_net/core/assistants/files.py +35 -12
  100. pygpt_net/core/assistants/store.py +20 -10
  101. pygpt_net/core/attachments/__init__.py +54 -15
  102. pygpt_net/core/attachments/context.py +92 -29
  103. pygpt_net/core/audio/__init__.py +74 -3
  104. pygpt_net/core/audio/context.py +7 -2
  105. pygpt_net/core/audio/whisper.py +37 -0
  106. pygpt_net/core/bridge/__init__.py +22 -6
  107. pygpt_net/core/bridge/context.py +5 -3
  108. pygpt_net/core/bridge/worker.py +2 -2
  109. pygpt_net/core/calendar/__init__.py +57 -11
  110. pygpt_net/core/chain/__init__.py +8 -2
  111. pygpt_net/core/chain/chat.py +10 -8
  112. pygpt_net/core/chain/completion.py +10 -7
  113. pygpt_net/core/command.py +62 -17
  114. pygpt_net/core/ctx/__init__.py +260 -58
  115. pygpt_net/core/ctx/bag.py +25 -4
  116. pygpt_net/core/ctx/container.py +28 -17
  117. pygpt_net/core/ctx/idx.py +45 -8
  118. pygpt_net/core/ctx/output.py +95 -74
  119. pygpt_net/core/ctx/reply.py +5 -2
  120. pygpt_net/core/db/__init__.py +8 -7
  121. pygpt_net/core/db/viewer.py +17 -11
  122. pygpt_net/core/debug/__init__.py +10 -9
  123. pygpt_net/core/debug/events.py +22 -10
  124. pygpt_net/core/debug/tabs.py +9 -3
  125. pygpt_net/core/docker/__init__.py +11 -5
  126. pygpt_net/core/docker/builder.py +11 -3
  127. pygpt_net/core/events/app.py +5 -3
  128. pygpt_net/core/events/base.py +11 -5
  129. pygpt_net/core/events/control.py +5 -3
  130. pygpt_net/core/events/event.py +17 -7
  131. pygpt_net/core/events/kernel.py +5 -3
  132. pygpt_net/core/events/render.py +5 -3
  133. pygpt_net/core/experts/__init__.py +5 -4
  134. pygpt_net/core/filesystem/__init__.py +52 -34
  135. pygpt_net/core/filesystem/actions.py +8 -5
  136. pygpt_net/core/filesystem/editor.py +13 -3
  137. pygpt_net/core/filesystem/types.py +12 -7
  138. pygpt_net/core/filesystem/url.py +7 -3
  139. pygpt_net/core/history.py +3 -2
  140. pygpt_net/core/idx/__init__.py +48 -27
  141. pygpt_net/core/idx/chat.py +51 -17
  142. pygpt_net/core/idx/context.py +6 -2
  143. pygpt_net/core/idx/indexing.py +107 -42
  144. pygpt_net/core/idx/llm.py +11 -3
  145. pygpt_net/core/idx/metadata.py +13 -3
  146. pygpt_net/core/idx/types/ctx.py +32 -6
  147. pygpt_net/core/idx/types/external.py +41 -7
  148. pygpt_net/core/idx/types/files.py +31 -6
  149. pygpt_net/core/image.py +15 -4
  150. pygpt_net/core/installer.py +2 -4
  151. pygpt_net/core/llm/__init__.py +13 -3
  152. pygpt_net/core/locale.py +34 -8
  153. pygpt_net/core/models.py +63 -17
  154. pygpt_net/core/modes.py +11 -13
  155. pygpt_net/core/notepad.py +10 -5
  156. pygpt_net/core/plugins.py +31 -19
  157. pygpt_net/core/presets.py +37 -17
  158. pygpt_net/core/profile.py +21 -7
  159. pygpt_net/core/prompt/__init__.py +10 -3
  160. pygpt_net/core/prompt/custom.py +7 -6
  161. pygpt_net/core/prompt/template.py +9 -3
  162. pygpt_net/core/render/base.py +117 -22
  163. pygpt_net/core/render/markdown/body.py +27 -7
  164. pygpt_net/core/render/markdown/renderer.py +119 -22
  165. pygpt_net/core/render/plain/body.py +22 -5
  166. pygpt_net/core/render/plain/renderer.py +97 -21
  167. pygpt_net/core/render/web/body.py +75 -25
  168. pygpt_net/core/render/web/parser.py +3 -1
  169. pygpt_net/core/render/web/renderer.py +313 -63
  170. pygpt_net/core/settings.py +10 -5
  171. pygpt_net/core/tabs/__init__.py +290 -103
  172. pygpt_net/core/tabs/tab.py +26 -5
  173. pygpt_net/core/tokens.py +47 -12
  174. pygpt_net/core/updater/__init__.py +20 -7
  175. pygpt_net/core/vision/analyzer.py +29 -6
  176. pygpt_net/core/{web.py → web/__init__.py} +29 -7
  177. pygpt_net/core/web/helpers.py +237 -0
  178. pygpt_net/data/config/config.json +15 -4
  179. pygpt_net/data/config/models.json +3 -3
  180. pygpt_net/data/config/modes.json +3 -3
  181. pygpt_net/data/config/settings.json +55 -10
  182. pygpt_net/data/config/settings_section.json +3 -0
  183. pygpt_net/data/css/style.light.css +1 -0
  184. pygpt_net/data/css/{web.css → web-blocks.css} +162 -133
  185. pygpt_net/data/css/{web.light.css → web-blocks.light.css} +7 -0
  186. pygpt_net/data/css/web-chatgpt.css +350 -0
  187. pygpt_net/data/css/web-chatgpt.dark.css +64 -0
  188. pygpt_net/data/css/web-chatgpt.light.css +75 -0
  189. pygpt_net/data/css/web-chatgpt_wide.css +350 -0
  190. pygpt_net/data/css/web-chatgpt_wide.dark.css +64 -0
  191. pygpt_net/data/css/web-chatgpt_wide.light.css +75 -0
  192. pygpt_net/data/icons/split_screen.svg +1 -0
  193. pygpt_net/data/locale/locale.de.ini +12 -0
  194. pygpt_net/data/locale/locale.en.ini +18 -2
  195. pygpt_net/data/locale/locale.es.ini +12 -0
  196. pygpt_net/data/locale/locale.fr.ini +12 -0
  197. pygpt_net/data/locale/locale.it.ini +12 -0
  198. pygpt_net/data/locale/locale.pl.ini +12 -0
  199. pygpt_net/data/locale/locale.uk.ini +12 -0
  200. pygpt_net/data/locale/locale.zh.ini +12 -0
  201. pygpt_net/data/locale/plugin.cmd_web.de.ini +2 -0
  202. pygpt_net/data/locale/plugin.cmd_web.en.ini +22 -10
  203. pygpt_net/data/locale/plugin.cmd_web.es.ini +2 -0
  204. pygpt_net/data/locale/plugin.cmd_web.fr.ini +2 -0
  205. pygpt_net/data/locale/plugin.cmd_web.it.ini +2 -0
  206. pygpt_net/data/locale/plugin.cmd_web.pl.ini +2 -0
  207. pygpt_net/data/locale/plugin.cmd_web.uk.ini +2 -0
  208. pygpt_net/data/locale/plugin.cmd_web.zh.ini +2 -0
  209. pygpt_net/data/locale/plugin.mailer.en.ini +21 -0
  210. pygpt_net/icons.qrc +1 -0
  211. pygpt_net/icons_rc.py +165 -136
  212. pygpt_net/item/ctx.py +58 -25
  213. pygpt_net/plugin/agent/__init__.py +7 -2
  214. pygpt_net/plugin/audio_input/simple.py +21 -5
  215. pygpt_net/plugin/audio_output/__init__.py +9 -1
  216. pygpt_net/plugin/base/config.py +4 -2
  217. pygpt_net/plugin/base/plugin.py +75 -23
  218. pygpt_net/plugin/base/worker.py +42 -11
  219. pygpt_net/plugin/cmd_code_interpreter/__init__.py +39 -37
  220. pygpt_net/plugin/cmd_code_interpreter/runner.py +25 -12
  221. pygpt_net/plugin/cmd_history/config.py +2 -2
  222. pygpt_net/plugin/cmd_web/__init__.py +48 -9
  223. pygpt_net/plugin/cmd_web/config.py +135 -41
  224. pygpt_net/plugin/cmd_web/websearch.py +74 -33
  225. pygpt_net/plugin/cmd_web/worker.py +142 -13
  226. pygpt_net/plugin/idx_llama_index/config.py +3 -3
  227. pygpt_net/plugin/mailer/__init__.py +123 -0
  228. pygpt_net/plugin/mailer/config.py +149 -0
  229. pygpt_net/plugin/mailer/runner.py +285 -0
  230. pygpt_net/plugin/mailer/worker.py +123 -0
  231. pygpt_net/provider/agents/base.py +5 -2
  232. pygpt_net/provider/agents/openai.py +4 -2
  233. pygpt_net/provider/agents/openai_assistant.py +4 -2
  234. pygpt_net/provider/agents/planner.py +4 -2
  235. pygpt_net/provider/agents/react.py +4 -2
  236. pygpt_net/provider/audio_output/openai_tts.py +5 -11
  237. pygpt_net/provider/core/assistant/base.py +5 -3
  238. pygpt_net/provider/core/assistant/json_file.py +8 -5
  239. pygpt_net/provider/core/assistant_file/base.py +4 -3
  240. pygpt_net/provider/core/assistant_file/db_sqlite/__init__.py +4 -3
  241. pygpt_net/provider/core/assistant_file/db_sqlite/storage.py +3 -2
  242. pygpt_net/provider/core/assistant_store/base.py +6 -4
  243. pygpt_net/provider/core/assistant_store/db_sqlite/__init__.py +5 -4
  244. pygpt_net/provider/core/assistant_store/db_sqlite/storage.py +5 -3
  245. pygpt_net/provider/core/attachment/base.py +5 -3
  246. pygpt_net/provider/core/attachment/json_file.py +4 -3
  247. pygpt_net/provider/core/calendar/base.py +5 -3
  248. pygpt_net/provider/core/calendar/db_sqlite/__init__.py +6 -5
  249. pygpt_net/provider/core/calendar/db_sqlite/storage.py +5 -4
  250. pygpt_net/provider/core/config/base.py +8 -6
  251. pygpt_net/provider/core/config/json_file.py +9 -7
  252. pygpt_net/provider/core/config/patch.py +43 -1
  253. pygpt_net/provider/core/ctx/base.py +30 -25
  254. pygpt_net/provider/core/ctx/db_sqlite/__init__.py +59 -34
  255. pygpt_net/provider/core/ctx/db_sqlite/storage.py +62 -30
  256. pygpt_net/provider/core/ctx/db_sqlite/utils.py +11 -9
  257. pygpt_net/provider/core/index/base.py +129 -23
  258. pygpt_net/provider/core/index/db_sqlite/__init__.py +130 -23
  259. pygpt_net/provider/core/index/db_sqlite/storage.py +130 -23
  260. pygpt_net/provider/core/index/db_sqlite/utils.py +4 -2
  261. pygpt_net/provider/core/mode/base.py +5 -3
  262. pygpt_net/provider/core/mode/json_file.py +7 -6
  263. pygpt_net/provider/core/model/base.py +6 -4
  264. pygpt_net/provider/core/model/json_file.py +9 -7
  265. pygpt_net/provider/core/notepad/base.py +5 -3
  266. pygpt_net/provider/core/notepad/db_sqlite/__init__.py +5 -4
  267. pygpt_net/provider/core/notepad/db_sqlite/storage.py +4 -3
  268. pygpt_net/provider/core/plugin_preset/base.py +4 -2
  269. pygpt_net/provider/core/plugin_preset/json_file.py +5 -3
  270. pygpt_net/provider/core/preset/base.py +6 -4
  271. pygpt_net/provider/core/preset/json_file.py +9 -9
  272. pygpt_net/provider/core/prompt/base.py +6 -3
  273. pygpt_net/provider/core/prompt/json_file.py +11 -6
  274. pygpt_net/provider/gpt/assistants.py +21 -11
  275. pygpt_net/provider/gpt/audio.py +6 -5
  276. pygpt_net/provider/gpt/chat.py +10 -7
  277. pygpt_net/provider/gpt/completion.py +11 -5
  278. pygpt_net/provider/gpt/image.py +9 -2
  279. pygpt_net/provider/gpt/store.py +53 -18
  280. pygpt_net/provider/gpt/vision.py +17 -11
  281. pygpt_net/provider/llms/anthropic.py +7 -2
  282. pygpt_net/provider/llms/azure_openai.py +26 -5
  283. pygpt_net/provider/llms/base.py +47 -9
  284. pygpt_net/provider/llms/google.py +7 -2
  285. pygpt_net/provider/llms/hugging_face.py +13 -3
  286. pygpt_net/provider/llms/hugging_face_api.py +18 -4
  287. pygpt_net/provider/llms/local.py +7 -2
  288. pygpt_net/provider/llms/ollama.py +30 -6
  289. pygpt_net/provider/llms/openai.py +32 -6
  290. pygpt_net/provider/vector_stores/__init__.py +45 -14
  291. pygpt_net/provider/vector_stores/base.py +35 -8
  292. pygpt_net/provider/vector_stores/chroma.py +13 -3
  293. pygpt_net/provider/vector_stores/ctx_attachment.py +31 -12
  294. pygpt_net/provider/vector_stores/elasticsearch.py +12 -3
  295. pygpt_net/provider/vector_stores/pinecode.py +12 -3
  296. pygpt_net/provider/vector_stores/redis.py +12 -3
  297. pygpt_net/provider/vector_stores/simple.py +12 -3
  298. pygpt_net/provider/vector_stores/temp.py +16 -4
  299. pygpt_net/provider/web/base.py +10 -3
  300. pygpt_net/provider/web/google_custom_search.py +9 -3
  301. pygpt_net/provider/web/microsoft_bing.py +9 -3
  302. pygpt_net/tools/__init__.py +20 -4
  303. pygpt_net/tools/audio_transcriber/__init__.py +4 -3
  304. pygpt_net/tools/base.py +28 -7
  305. pygpt_net/tools/code_interpreter/__init__.py +177 -77
  306. pygpt_net/tools/code_interpreter/ui/dialogs.py +21 -103
  307. pygpt_net/tools/code_interpreter/ui/widgets.py +284 -9
  308. pygpt_net/tools/html_canvas/__init__.py +81 -25
  309. pygpt_net/tools/html_canvas/ui/dialogs.py +46 -62
  310. pygpt_net/tools/html_canvas/ui/widgets.py +96 -3
  311. pygpt_net/tools/image_viewer/__init__.py +10 -4
  312. pygpt_net/tools/indexer/__init__.py +8 -7
  313. pygpt_net/tools/media_player/__init__.py +4 -3
  314. pygpt_net/tools/text_editor/__init__.py +36 -10
  315. pygpt_net/ui/base/context_menu.py +2 -2
  316. pygpt_net/ui/layout/chat/input.py +10 -18
  317. pygpt_net/ui/layout/chat/output.py +27 -45
  318. pygpt_net/ui/layout/ctx/ctx_list.py +13 -4
  319. pygpt_net/ui/layout/toolbox/footer.py +18 -2
  320. pygpt_net/ui/main.py +2 -2
  321. pygpt_net/ui/menu/audio.py +12 -1
  322. pygpt_net/ui/menu/config.py +7 -11
  323. pygpt_net/ui/menu/debug.py +11 -1
  324. pygpt_net/ui/menu/theme.py +9 -2
  325. pygpt_net/ui/widget/filesystem/explorer.py +2 -2
  326. pygpt_net/ui/widget/lists/context.py +27 -5
  327. pygpt_net/ui/widget/tabs/Input.py +2 -2
  328. pygpt_net/ui/widget/tabs/body.py +2 -1
  329. pygpt_net/ui/widget/tabs/layout.py +195 -0
  330. pygpt_net/ui/widget/tabs/output.py +218 -55
  331. pygpt_net/ui/widget/textarea/html.py +11 -1
  332. pygpt_net/ui/widget/textarea/output.py +10 -1
  333. pygpt_net/ui/widget/textarea/search_input.py +4 -1
  334. pygpt_net/ui/widget/textarea/web.py +49 -9
  335. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/METADATA +177 -183
  336. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/RECORD +340 -325
  337. /pygpt_net/data/css/{web.dark.css → web-blocks.dark.css} +0 -0
  338. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/LICENSE +0 -0
  339. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/WHEEL +0 -0
  340. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.42.dist-info}/entry_points.txt +0 -0
@@ -6,13 +6,16 @@
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.14 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ from typing import Any, Optional
13
+
12
14
  from PySide6.QtCore import QTimer
13
15
 
14
16
  from pygpt_net.core.events import AppEvent, RenderEvent
15
17
  from pygpt_net.core.tabs.tab import Tab
18
+ from pygpt_net.item.ctx import CtxMeta
16
19
  from pygpt_net.utils import trans
17
20
 
18
21
 
@@ -29,40 +32,91 @@ class Tabs:
29
32
  self.initialized = False
30
33
  self.appended = False
31
34
  self.current = 0
35
+ self.column_idx = 0
36
+ self.tmp_column_idx = 0
37
+ self.locked = False
38
+ self.col = {}
32
39
 
33
40
  def setup(self):
34
41
  """Setup tabs"""
35
42
  self.window.core.tabs.load()
36
43
  self.window.controller.notepad.load()
44
+ self.setup_options()
37
45
  self.initialized = True
38
46
 
39
- def add(self, type: int, title: str, icon=None, reference=None, data_id=None):
40
- """
41
- Add tab
47
+ def setup_options(self):
48
+ """Setup options"""
49
+ state = self.window.core.config.get("layout.split", False)
50
+ self.window.ui.nodes['layout.split'].setChecked(state)
51
+ if not state:
52
+ self.window.ui.splitters['columns'].setSizes([1, 0])
53
+
54
+ def add(
55
+ self,
56
+ type: int,
57
+ title: str,
58
+ icon: Optional[str] = None,
59
+ child: Any = None,
60
+ data_id: Optional[int] = None,
61
+ tool_id: Optional[str] = None,
62
+ ):
63
+ """
64
+ Add a new tab
42
65
 
43
66
  :param type: Tab type
44
67
  :param title: Tab title
45
68
  :param icon: Tab icon
46
- :param reference: Tab reference
47
- :param data_id: Tab data ID
48
- """
49
- self.window.core.tabs.add(type, title, icon, reference, data_id)
50
-
51
- def append(self, type: int, idx: int):
52
- """
53
- Append tab in place
69
+ :param child: Tab child (child widget)
70
+ :param data_id: Tab data ID (child data ID)
71
+ :param tool_id: Tool ID
72
+ """
73
+ self.window.core.tabs.add(
74
+ type=type,
75
+ title=title,
76
+ icon=icon,
77
+ child=child,
78
+ data_id=data_id,
79
+ tool_id=tool_id
80
+ )
81
+
82
+ def append(
83
+ self,
84
+ type: int,
85
+ tool_id: Optional[str] = None,
86
+ idx: int = 0,
87
+ column_idx: int = 0
88
+ ):
89
+ """
90
+ Append tab at tab index
54
91
 
55
92
  :param type: Tab type
93
+ :param tool_id: Tool ID
56
94
  :param idx: Tab index
95
+ :param column_idx: Column index
57
96
  """
58
97
  self.appended = True # lock reload in previous tab
59
- self.window.core.tabs.append(type, idx)
60
- self.switch_tab_by_idx(idx + 1) # switch to new tab
98
+ self.column_idx = column_idx # switch to column
99
+ tab = self.window.core.tabs.append(
100
+ type=type,
101
+ idx=idx,
102
+ column_idx=column_idx,
103
+ tool_id=tool_id
104
+ )
105
+ self.switch_tab_by_idx(tab.idx, column_idx) # switch to new tab
61
106
 
62
107
  def reload_titles(self):
63
108
  """Reload tab titles"""
64
109
  self.window.core.tabs.reload_titles()
65
110
 
111
+ def update_current(self):
112
+ """Update current tab"""
113
+ curr_tab = self.get_current_tab()
114
+ curr_column = self.get_current_column_idx()
115
+ if curr_column not in self.col:
116
+ self.col[curr_column] = -1
117
+ if curr_tab is not None:
118
+ self.col[curr_column] = curr_tab.pid
119
+
66
120
  def reload(self):
67
121
  """Reload tabs"""
68
122
  self.window.core.tabs.reload()
@@ -78,15 +132,20 @@ class Tabs:
78
132
  else:
79
133
  self.window.ui.nodes['output_plain'][pid].setVisible(False)
80
134
  self.window.ui.nodes['output'][pid].setVisible(True)
81
- self.switch_tab(Tab.TAB_CHAT)
135
+ #self.switch_tab(Tab.TAB_CHAT)
82
136
 
83
- def on_tab_changed(self, idx: int):
137
+ def on_tab_changed(
138
+ self,
139
+ idx: int,
140
+ column_idx: int = 0
141
+ ):
84
142
  """
85
143
  Output tab changed
86
144
 
87
145
  :param idx: tab index
146
+ :param column_idx: column index
88
147
  """
89
- tab = self.window.core.tabs.get_tab_by_index(idx)
148
+ tab = self.window.core.tabs.get_tab_by_index(idx, column_idx)
90
149
  if tab is None:
91
150
  self.appended = False
92
151
  return
@@ -99,147 +158,242 @@ class Tabs:
99
158
  if meta is not None:
100
159
  self.window.controller.ctx.load(meta.id) # reload
101
160
 
102
-
103
161
  prev_tab = self.current
162
+ prev_column = self.column_idx
104
163
  self.current = idx
164
+ self.column_idx = column_idx
105
165
  self.window.controller.ui.mode.update()
106
166
  self.window.controller.ui.vision.update()
107
167
 
108
168
  # check type
109
169
  if tab.type == Tab.TAB_NOTEPAD:
110
170
  self.window.controller.notepad.opened_once = True
111
- self.window.controller.notepad.on_open(idx)
171
+ self.window.controller.notepad.on_open(idx, column_idx)
112
172
  elif tab.type == Tab.TAB_CHAT:
113
- pid_meta = self.window.core.ctx.output.get_meta(tab.pid)
114
- meta = self.window.core.ctx.get_meta_by_id(pid_meta)
173
+ # get meta for selected tab, if not loaded yet then append meta here
174
+ meta_id = self.window.core.ctx.output.prepare_meta(tab)
175
+ meta = self.window.core.ctx.get_meta_by_id(meta_id)
115
176
  if meta is not None:
116
- self.window.controller.ctx.load(pid_meta) # reload renderer
177
+ self.window.controller.ctx.load(meta.id) # reload renderer
117
178
  elif tab.type == Tab.TAB_TOOL_PAINTER:
118
179
  if self.window.core.config.get('vision.capture.enabled'):
119
180
  self.window.controller.camera.enable_capture()
120
181
 
121
- if prev_tab != idx:
182
+ if prev_tab != idx or prev_column != column_idx:
122
183
  self.window.dispatch(AppEvent(AppEvent.TAB_SELECTED)) # app event
123
184
 
124
- def get_current_idx(self) -> int:
185
+ self.window.controller.ui.update()
186
+ self.update_current()
187
+
188
+ def get_current_idx(self, column_idx: int = 0) -> int:
125
189
  """
126
190
  Get current tab index
127
191
 
192
+ :param column_idx: column index
128
193
  :return: tab index
129
194
  """
130
195
  return self.current
131
196
 
132
- def get_current_tab(self) -> Tab or None:
197
+ def get_current_column_idx(self) -> int:
198
+ """
199
+ Get current column index
200
+
201
+ :return: column index
202
+ """
203
+ return self.column_idx
204
+
205
+ def get_current_tab(self) -> Optional[Tab]:
133
206
  """
134
207
  Get current tab
135
208
 
136
209
  :return: tab
137
210
  """
138
- return self.window.core.tabs.get_tab_by_index(self.current)
211
+ return self.window.core.tabs.get_tab_by_index(self.get_current_idx(), self.column_idx)
139
212
 
140
- def get_current_type(self) -> int or None:
213
+ def get_current_type(self) -> Optional[int]:
141
214
  """
142
215
  Get current tab type
143
216
 
144
217
  :return: tab type
145
218
  """
146
- tab = self.window.core.tabs.get_tab_by_index(self.current)
219
+ tab = self.window.core.tabs.get_tab_by_index(self.get_current_idx(), self.column_idx)
220
+
147
221
  if tab is None:
148
222
  return None
149
223
  return tab.type
150
224
 
151
- def get_current_pid(self) -> int or None:
225
+ def get_current_pid(self) -> Optional[int]:
152
226
  """
153
227
  Get current tab PID
154
228
 
155
229
  :return: tab PID
156
230
  """
157
- tab = self.window.core.tabs.get_tab_by_index(self.current)
231
+ tab = self.window.core.tabs.get_tab_by_index(self.get_current_idx(), self.column_idx)
158
232
  if tab is None:
159
233
  return None
160
234
  return tab.pid
161
235
 
162
- def get_type_by_idx(self, idx: int) -> int or None:
236
+ def get_type_by_idx(self, idx: int) -> Optional[int]:
163
237
  """
164
238
  Get tab type by index
165
239
 
166
240
  :param idx: tab index
167
241
  :return: tab type
168
242
  """
169
- tab = self.window.core.tabs.get_tab_by_index(idx)
243
+ tab = self.window.core.tabs.get_tab_by_index(idx, self.column_idx)
170
244
  if tab is None:
171
245
  return None
172
246
  return tab.type
173
247
 
174
- def get_first_idx_by_type(self, type: int) -> int or None:
248
+ def get_first_idx_by_type(self, type: int) -> Optional[int]:
175
249
  """
176
250
  Get first tab index by type
177
251
 
178
252
  :param type: tab type
179
253
  :return: tab index
180
254
  """
181
- return self.window.core.tabs.get_min_idx_by_type(type)
255
+ return self.window.core.tabs.get_min_idx_by_type(type, self.column_idx)
182
256
 
183
- def on_tab_clicked(self, idx: int):
257
+ def on_column_changed(self):
258
+ """Column changed event"""
259
+ if self.locked:
260
+ return
261
+ tabs = self.window.ui.layout.get_tabs_by_idx(self.column_idx)
262
+ tabs.set_active(True)
263
+
264
+ if self.column_idx == 0:
265
+ second_tabs = self.window.ui.layout.get_tabs_by_idx(1)
266
+ else:
267
+ second_tabs = self.window.ui.layout.get_tabs_by_idx(0)
268
+ second_tabs.set_active(False)
269
+ idx = tabs.currentIndex()
270
+ self.current = idx
271
+ tab = self.window.core.tabs.get_tab_by_index(self.current, self.column_idx)
272
+ if tab is None:
273
+ return
274
+ current_ctx = self.window.core.ctx.get_current()
275
+ if (current_ctx is not None and current_ctx != tab.data_id) or current_ctx is None:
276
+ self.window.controller.ctx.select_on_list_only(tab.data_id)
277
+ self.window.controller.ui.update()
278
+ self.update_current()
279
+
280
+ def on_tab_clicked(
281
+ self,
282
+ idx: int,
283
+ column_idx: int = 0
284
+ ):
184
285
  """
185
286
  Tab click event
186
287
 
187
288
  :param idx: tab index
289
+ :param column_idx: column index
290
+ """
291
+ self.current = idx
292
+ self.column_idx = column_idx
293
+ self.on_column_changed()
294
+ self.update_current()
295
+
296
+ def on_column_focus(self, idx: int):
297
+ """
298
+ Column focus event
299
+
300
+ :param idx: column index
188
301
  """
189
- pass
302
+ self.column_idx = idx
303
+ self.on_column_changed()
304
+ self.update_current()
190
305
 
191
- def on_tab_dbl_clicked(self, idx: int):
306
+ def on_tab_dbl_clicked(
307
+ self,
308
+ idx: int,
309
+ column_idx: int = 0
310
+ ):
192
311
  """
193
312
  Tab double click event
194
313
 
195
314
  :param idx: tab index
315
+ :param column_idx: column index
196
316
  """
197
- pass
317
+ self.column_idx = column_idx
318
+ self.on_tab_changed(idx, column_idx)
319
+ self.update_current()
198
320
 
199
- def on_tab_closed(self, idx: int):
321
+ def on_tab_closed(
322
+ self,
323
+ idx: int,
324
+ column_idx: int = 0
325
+ ):
200
326
  """
201
327
  Tab close event
202
328
 
203
329
  :param idx: tab index
330
+ :param column_idx: column index
204
331
  """
205
- self.window.core.tabs.remove_tab_by_idx(idx)
332
+ if self.locked:
333
+ return
334
+ self.window.core.tabs.remove_tab_by_idx(idx, column_idx)
335
+ self.update_current()
206
336
 
207
- def on_tab_moved(self, idx: int):
337
+ def on_tab_moved(
338
+ self,
339
+ idx: int,
340
+ column_idx: int = 0
341
+ ):
208
342
  """
209
343
  Tab moved event
210
344
 
211
345
  :param idx: tab index
346
+ :param column_idx: column index
212
347
  """
348
+ if self.locked:
349
+ return
213
350
  self.window.core.tabs.update()
351
+ self.update_current()
214
352
 
215
- def close(self, idx: int):
353
+ def close(
354
+ self,
355
+ idx: int,
356
+ column_idx: int = 0
357
+ ):
216
358
  """
217
359
  Close tab
218
360
 
219
361
  :param idx: tab index
362
+ :param column_idx: column index
220
363
  """
221
- self.on_tab_closed(idx)
364
+ self.on_tab_closed(idx, column_idx)
365
+ self.update_current()
222
366
 
223
- def close_all(self, type: int, force: bool = False):
367
+ def close_all(
368
+ self,
369
+ type: int,
370
+ column_idx: int = 0,
371
+ force: bool = False
372
+ ):
224
373
  """
225
374
  Close all tabs
226
375
 
227
376
  :param type: tab type
377
+ :param column_idx: column index
228
378
  :param force: force close
229
379
  """
230
380
  if not force:
381
+ self.tmp_column_idx = column_idx
231
382
  self.window.ui.dialogs.confirm(
232
383
  type='tab.close_all',
233
384
  id=type,
234
385
  msg=trans('tab.close_all.confirm'),
235
386
  )
236
387
  return
237
- self.window.core.tabs.remove_all_by_type(type)
388
+ column_idx = self.tmp_column_idx
389
+ self.window.core.tabs.remove_all_by_type(type, column_idx)
390
+ self.update_current()
238
391
 
239
392
  def next_tab(self):
240
393
  """Switch to next tab"""
241
- current = self.window.ui.tabs['output'].currentIndex()
242
- all = len(self.window.ui.tabs['output'].children())
394
+ tabs = self.window.ui.layout.get_active_tabs()
395
+ current = tabs.currentIndex()
396
+ all = len(tabs.children())
243
397
  next = current + 1
244
398
  if next >= all:
245
399
  next = 0
@@ -247,8 +401,9 @@ class Tabs:
247
401
 
248
402
  def prev_tab(self):
249
403
  """Switch to previous tab"""
250
- current = self.window.ui.tabs['output'].currentIndex()
251
- all = len(self.window.ui.tabs['output'].children())
404
+ tabs = self.window.ui.layout.get_active_tabs()
405
+ current = tabs.currentIndex()
406
+ all = len(tabs.children())
252
407
  prev = current - 1
253
408
  if prev < 0:
254
409
  prev = all - 1
@@ -264,14 +419,20 @@ class Tabs:
264
419
  if idx is not None:
265
420
  self.switch_tab_by_idx(idx)
266
421
 
267
- def switch_tab_by_idx(self, idx: int):
422
+ def switch_tab_by_idx(
423
+ self,
424
+ idx: int,
425
+ column_idx: int = 0
426
+ ):
268
427
  """
269
428
  Switch tab by index
270
429
 
271
430
  :param idx: tab index
431
+ :param column_idx: column index
272
432
  """
273
- self.window.ui.tabs['output'].setCurrentIndex(idx)
274
- self.on_tab_changed(idx)
433
+ tabs = self.window.ui.layout.get_tabs_by_idx(column_idx)
434
+ tabs.setCurrentIndex(idx)
435
+ self.on_tab_changed(idx, column_idx)
275
436
 
276
437
  def get_current_tab_name(self) -> str:
277
438
  """
@@ -279,7 +440,8 @@ class Tabs:
279
440
 
280
441
  :return: tab name
281
442
  """
282
- return self.window.ui.tabs['output'].tabText(self.current)
443
+ tabs = self.window.ui.layout.get_active_tabs()
444
+ return tabs.tabText(self.current)
283
445
 
284
446
  def get_current_tab_name_for_audio(self) -> str:
285
447
  """
@@ -309,18 +471,24 @@ class Tabs:
309
471
  """
310
472
  Update tab tooltip
311
473
 
312
- :param tooltip: tooltip
474
+ :param tooltip: tooltip text
313
475
  """
314
- self.window.ui.tabs['output'].setTabToolTip(self.current, tooltip)
476
+ tabs = self.window.ui.layout.get_active_tabs()
477
+ tabs.setTabToolTip(self.current, tooltip)
315
478
 
316
- def rename(self, idx: int):
479
+ def rename(
480
+ self,
481
+ idx: int,
482
+ column_idx: int = 0
483
+ ):
317
484
  """
318
- Rename tab
485
+ Rename tab (show dialog)
319
486
 
320
487
  :param idx: tab idx
488
+ :param column_idx: column idx
321
489
  """
322
490
  # get tab
323
- tab = self.window.core.tabs.get_tab_by_index(idx)
491
+ tab = self.window.core.tabs.get_tab_by_index(idx, column_idx)
324
492
  if tab is None:
325
493
  return
326
494
  # set dialog and show
@@ -329,7 +497,12 @@ class Tabs:
329
497
  self.window.ui.dialog['rename'].current = idx
330
498
  self.window.ui.dialog['rename'].show()
331
499
 
332
- def update_name(self, idx: int, name: str, close: bool = True):
500
+ def update_name(
501
+ self,
502
+ idx: int,
503
+ name: str,
504
+ close: bool = True
505
+ ):
333
506
  """
334
507
  Update tab title
335
508
 
@@ -349,7 +522,11 @@ class Tabs:
349
522
  """
350
523
  self.update_name(self.current, name)
351
524
 
352
- def update_title(self, idx: int, title: str):
525
+ def update_title(
526
+ self,
527
+ idx: int,
528
+ title: str
529
+ ):
353
530
  """
354
531
  Update tab title
355
532
 
@@ -359,8 +536,9 @@ class Tabs:
359
536
  # check if current tab is chat
360
537
  if self.get_current_type() != Tab.TAB_CHAT:
361
538
  return
539
+ tabs = self.window.ui.layout.get_active_tabs()
362
540
  tooltip = title
363
- self.window.ui.tabs['output'].setTabToolTip(idx, tooltip)
541
+ tabs.setTabToolTip(idx, tooltip)
364
542
  if len(title) > 8:
365
543
  title = title[:8] + '...' # truncate to max 8 chars
366
544
  self.window.core.tabs.update_title(idx, title, tooltip)
@@ -373,6 +551,18 @@ class Tabs:
373
551
  """
374
552
  self.update_title(self.current, title)
375
553
 
554
+ def on_load_ctx(self, meta: CtxMeta):
555
+ """
556
+ Load context
557
+
558
+ :param meta: context meta
559
+ """
560
+ # get current tab
561
+ tab = self.get_current_tab()
562
+ if tab is not None and tab.type == Tab.TAB_CHAT:
563
+ tab.data_id = meta.id
564
+ self.update_title_current(meta.name)
565
+
376
566
  def open_by_type(self, type: int):
377
567
  """
378
568
  Open first tab by type
@@ -383,7 +573,115 @@ class Tabs:
383
573
  if idx is not None:
384
574
  self.switch_tab_by_idx(idx)
385
575
 
386
- def new_tab(self):
387
- """Handle [+} button"""
388
- idx = self.get_current_idx()
389
- self.append(Tab.TAB_CHAT, idx)
576
+ def new_tab(self, column_idx: int = 0):
577
+ """
578
+ Handle [+] button
579
+
580
+ :param column_idx: column index
581
+ """
582
+ idx = self.get_current_idx(column_idx)
583
+ self.append(
584
+ type=Tab.TAB_CHAT,
585
+ tool_id=None,
586
+ idx=idx,
587
+ column_idx=column_idx
588
+ )
589
+
590
+ def restore_data(self):
591
+ """Restore tab data"""
592
+ data = self.window.core.config.get("tabs.opened", [])
593
+ if not data:
594
+ self.switch_tab_by_idx(0, 0)
595
+ return
596
+
597
+ # reverse order, second column is first
598
+ data = dict(reversed(list(data.items())))
599
+ for col_idx in data:
600
+ tab_idx = data[col_idx]
601
+ self.switch_tab_by_idx(int(tab_idx), int(col_idx))
602
+
603
+ # set default column to 0
604
+ self.column_idx = 0
605
+ self.on_column_changed()
606
+
607
+ def move_tab(
608
+ self,
609
+ idx: int,
610
+ column_idx: int,
611
+ new_column_idx: int
612
+ ):
613
+ """
614
+ Move tab to another column
615
+
616
+ :param idx: tab index
617
+ :param column_idx: column index
618
+ :param new_column_idx: new column index
619
+ """
620
+ self.locked = True
621
+ tab = self.window.core.tabs.get_tab_by_index(idx, column_idx)
622
+ self.window.core.tabs.move_tab(tab, new_column_idx)
623
+ self.locked = False
624
+ # switch to new column
625
+ self.column_idx = new_column_idx
626
+ self.on_column_changed()
627
+ # switch to new tab
628
+ self.switch_tab_by_idx(tab.idx, new_column_idx)
629
+
630
+ def toggle_split_screen(self, state):
631
+ """
632
+ Toggle split screen mode
633
+
634
+ :param state: True if split screen is enabled
635
+ """
636
+ if state:
637
+ # self.rightWidget.show()
638
+ self.window.ui.splitters['columns'].setSizes([1, 1])
639
+ else:
640
+ # self.rightWidget.hide()
641
+ self.window.ui.splitters['columns'].setSizes([1, 0])
642
+ # set to first column
643
+ self.column_idx = 0
644
+ self.on_column_changed()
645
+ self.window.core.config.set("layout.split", state)
646
+ self.window.core.config.save()
647
+
648
+ def is_current_by_type(self, type: int) -> bool:
649
+ """
650
+ Check if one of current tabs is of given type
651
+
652
+ :param type: tab type
653
+ :return: True if one of tab is of given type
654
+ """
655
+ for col in self.col:
656
+ pid = self.col[col]
657
+ tab = self.window.core.tabs.get_tab_by_pid(pid)
658
+ if tab is not None and tab.type == type:
659
+ return True
660
+
661
+ def is_current_tool(self, tool_id: str) -> bool:
662
+ """
663
+ Check if one of current tabs is of given tool ID
664
+
665
+ :param tool_id: tool ID
666
+ :return: True if one of tab is of given tool ID
667
+ """
668
+ for col in self.col:
669
+ pid = self.col[col]
670
+ tab = self.window.core.tabs.get_tab_by_pid(pid)
671
+ if tab is not None and tab.tool_id == tool_id:
672
+ return True
673
+
674
+ def switch_to_first_chat(self):
675
+ """Switch to first chat tab"""
676
+ if self.is_current_by_type(Tab.TAB_CHAT):
677
+ return
678
+ # abort if active tab is chat
679
+ if self.get_current_type() == Tab.TAB_CHAT:
680
+ return
681
+ # find first chat tab
682
+ for col in self.col:
683
+ pid = self.col[col]
684
+ tab = self.window.core.tabs.get_tab_by_pid(pid)
685
+ if tab is not None and tab.type == Tab.TAB_CHAT:
686
+ self.switch_tab_by_idx(tab.idx, col)
687
+ return