pygpt-net 2.4.38__py3-none-any.whl → 2.4.39__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 (56) hide show
  1. CHANGELOG.md +6 -0
  2. README.md +7 -1
  3. pygpt_net/CHANGELOG.txt +6 -0
  4. pygpt_net/__init__.py +2 -2
  5. pygpt_net/controller/__init__.py +3 -1
  6. pygpt_net/controller/calendar/__init__.py +3 -1
  7. pygpt_net/controller/chat/render.py +8 -5
  8. pygpt_net/controller/ctx/__init__.py +32 -24
  9. pygpt_net/controller/ctx/common.py +3 -2
  10. pygpt_net/controller/dialogs/confirm.py +2 -2
  11. pygpt_net/controller/lang/custom.py +2 -7
  12. pygpt_net/controller/lang/mapping.py +2 -2
  13. pygpt_net/controller/layout.py +2 -2
  14. pygpt_net/controller/notepad.py +8 -5
  15. pygpt_net/controller/ui/tabs.py +201 -58
  16. pygpt_net/core/ctx/__init__.py +11 -1
  17. pygpt_net/core/ctx/container.py +16 -9
  18. pygpt_net/core/ctx/output.py +86 -67
  19. pygpt_net/core/debug/tabs.py +3 -2
  20. pygpt_net/core/filesystem/url.py +7 -3
  21. pygpt_net/core/render/base.py +14 -3
  22. pygpt_net/core/render/markdown/renderer.py +3 -1
  23. pygpt_net/core/render/plain/renderer.py +3 -3
  24. pygpt_net/core/render/web/body.py +9 -3
  25. pygpt_net/core/render/web/renderer.py +7 -5
  26. pygpt_net/core/tabs/__init__.py +180 -71
  27. pygpt_net/core/tabs/tab.py +13 -4
  28. pygpt_net/data/config/config.json +11 -5
  29. pygpt_net/data/config/models.json +3 -3
  30. pygpt_net/data/config/modes.json +3 -3
  31. pygpt_net/data/locale/locale.de.ini +3 -0
  32. pygpt_net/data/locale/locale.en.ini +3 -0
  33. pygpt_net/data/locale/locale.es.ini +3 -0
  34. pygpt_net/data/locale/locale.fr.ini +3 -0
  35. pygpt_net/data/locale/locale.it.ini +3 -0
  36. pygpt_net/data/locale/locale.pl.ini +4 -1
  37. pygpt_net/data/locale/locale.uk.ini +3 -0
  38. pygpt_net/data/locale/locale.zh.ini +3 -0
  39. pygpt_net/plugin/audio_input/simple.py +4 -2
  40. pygpt_net/provider/core/config/patch.py +8 -1
  41. pygpt_net/provider/core/ctx/base.py +4 -1
  42. pygpt_net/provider/core/ctx/db_sqlite/__init__.py +10 -1
  43. pygpt_net/provider/core/ctx/db_sqlite/storage.py +22 -1
  44. pygpt_net/ui/layout/chat/input.py +10 -18
  45. pygpt_net/ui/layout/chat/output.py +26 -44
  46. pygpt_net/ui/layout/toolbox/footer.py +18 -2
  47. pygpt_net/ui/widget/tabs/layout.py +195 -0
  48. pygpt_net/ui/widget/tabs/output.py +124 -35
  49. pygpt_net/ui/widget/textarea/html.py +11 -1
  50. pygpt_net/ui/widget/textarea/output.py +10 -1
  51. pygpt_net/ui/widget/textarea/web.py +49 -9
  52. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/METADATA +8 -2
  53. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/RECORD +56 -55
  54. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/LICENSE +0 -0
  55. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/WHEEL +0 -0
  56. {pygpt_net-2.4.38.dist-info → pygpt_net-2.4.39.dist-info}/entry_points.txt +0 -0
@@ -49,6 +49,8 @@ action.tab.add.notepad = Ajouter un nouveau bloc-notes
49
49
  action.tab.close = Fermer
50
50
  action.tab.close_all.chat = Fermer tous les chats
51
51
  action.tab.close_all.notepad = Fermer tous les blocs-notes
52
+ action.tab.move.left = Déplacer vers la colonne de gauche
53
+ action.tab.move.right = Déplacer vers la colonne de droite
52
54
  action.touch = Créer un fichier...
53
55
  action.truncate = Tout effacer
54
56
  action.undo = Annuler
@@ -548,6 +550,7 @@ ipython.docker.build.finish = Succès! L'image Docker pour IPython a été const
548
550
  ipython.docker.build.start = Construction de l'image Docker... veuillez patienter...
549
551
  ipython.docker.install = L'exécution d'IPython nécessite l'installation de Docker. Docker n'a pas été détecté sur ce système. Veuillez d'abord installer Docker ; vous pouvez trouver les instructions ici : https://docs.docker.com/engine/install/. L'exécution de la commande actuelle a été mise en pause.
550
552
  ipython.image.build = L'image Docker pour IPython n'a pas encore été construite. Cela va se produire maintenant, et cela peut prendre un certain temps (mais c'est une procédure unique). L'exécution de la commande a été mise en pause. Une fois l'image construite, veuillez exécuter à nouveau la commande.
553
+ layout.split = Écran scindé (bêta)
551
554
  menu.audio = Audio / Voix
552
555
  menu.audio.cache.clear = Effacer le cache audio...
553
556
  menu.audio.control.global = Contrôle vocal (global)
@@ -49,6 +49,8 @@ action.tab.add.notepad = Aggiungi un nuovo blocco note
49
49
  action.tab.close = Chiudi
50
50
  action.tab.close_all.chat = Chiudi tutte le chat
51
51
  action.tab.close_all.notepad = Chiudi tutti i blocchi note
52
+ action.tab.move.left = Sposta nella colonna di sinistra
53
+ action.tab.move.right = Sposta nella colonna di destra
52
54
  action.touch = Crea file...
53
55
  action.truncate = Cancellare tutto
54
56
  action.undo = Annulla
@@ -548,6 +550,7 @@ ipython.docker.build.finish = Successo! L'immagine Docker per IPython è stata c
548
550
  ipython.docker.build.start = Creazione dell'immagine Docker... per favore aspetta...
549
551
  ipython.docker.install = L'esecuzione di IPython richiede l'installazione di Docker. Docker non è stato rilevato su questo sistema. Si prega di installare prima Docker; puoi trovare le istruzioni qui: https://docs.docker.com/engine/install/. L'esecuzione del comando corrente è stata messa in pausa.
550
552
  ipython.image.build = L'immagine Docker per IPython non è stata ancora costruita. Questo avverrà ora e potrebbe richiedere del tempo (ma è una procedura una tantum). L'esecuzione del comando è stata messa in pausa. Una volta costruita l'immagine, esegui nuovamente il comando.
553
+ layout.split = Schermo diviso (beta)
551
554
  menu.audio = Audio / Voce
552
555
  menu.audio.cache.clear = Cancella cache audio...
553
556
  menu.audio.control.global = Controllo vocale (globale)
@@ -49,6 +49,8 @@ action.tab.add.notepad = Dodaj nowy notatnik
49
49
  action.tab.close = Zamknij
50
50
  action.tab.close_all.chat = Zamknij wszystkie czaty
51
51
  action.tab.close_all.notepad = Zamknij wszystkie notatniki
52
+ action.tab.move.left = Przenieś do lewej kolumny
53
+ action.tab.move.right = Przenieś do prawej kolumny
52
54
  action.touch = Utwórz plik...
53
55
  action.truncate = Wyczyść wszystko
54
56
  action.undo = Cofnij
@@ -548,6 +550,7 @@ ipython.docker.build.finish = Sukces! Obraz Docker dla IPython został zbudowany
548
550
  ipython.docker.build.start = Budowanie obrazu Dockera... proszę czekać...
549
551
  ipython.docker.install = Uruchomienie IPython wymaga zainstalowania Dockera. Docker nie został wykryty na tym systemie. Proszę najpierw zainstalować Dockera; instrukcje można znaleźć tutaj: https://docs.docker.com/engine/install/. Wykonywanie bieżącego polecenia zostało wstrzymane.
550
552
  ipython.image.build = Obraz Dockera dla IPython nie został jeszcze zbudowany. Nastąpi to teraz i może to potrwać chwilę (ale to jednorazowa procedura). Wykonywanie polecenia zostało wstrzymane. Po zbudowaniu obrazu możesz ponownie poprosić o wykonanie polecenia.
553
+ layout.split = Podziel ekran (beta)
551
554
  menu.audio = Audio / Mowa
552
555
  menu.audio.cache.clear = Wyczyść pamięć podręczną audio...
553
556
  menu.audio.control.global = Kontrola głosowa (globalna)
@@ -759,7 +762,7 @@ settings.audio.input.channels.desc = Kanały wejściowe, domyślnie: 1
759
762
  settings.audio.input.device = Urządzenie Wejściowe Audio
760
763
  settings.audio.input.device.desc = Wybierz urządzenie audio dla wejścia mikrofonu.
761
764
  settings.audio.input.rate = Częstotliwość próbkowania
762
- settings.audio.input.rate.desc = Częstotliwość próbkowania, domyślnie: 44100
765
+ settings.audio.input.rate.desc = Częstotliwość próbkowania, domyślnie: 44100
763
766
  settings.check_updates = Sprawdź aktualizacje przy starcie
764
767
  settings.check_updates.bg = Sprawdź aktualizacje w tle
765
768
  settings.cmd.field.desc = Włącz narzędzie `{cmd}`.
@@ -49,6 +49,8 @@ action.tab.add.notepad = Додати новий блокнот
49
49
  action.tab.close = Закрити
50
50
  action.tab.close_all.chat = Закрити всі чати
51
51
  action.tab.close_all.notepad = Закрити всі блокноти
52
+ action.tab.move.left = Перемістити в ліву колонку
53
+ action.tab.move.right = Перемістити в праву колонку
52
54
  action.touch = Створити файл...
53
55
  action.truncate = Очистити все
54
56
  action.undo = Скасувати
@@ -547,6 +549,7 @@ ipython.docker.build.finish = Успіх! Образ Docker для IPython ст
547
549
  ipython.docker.build.start = Створення образу Docker... будь ласка, зачекайте...
548
550
  ipython.docker.install = Для запуску IPython необхідно встановити Docker. Docker не було виявлено на цій системі. Будь ласка, спочатку встановіть Docker; інструкції можна знайти тут: https://docs.docker.com/engine/install/. Виконання поточної команди було призупинено.
549
551
  ipython.image.build = Docker-образ для IPython ще не був створений. Це відбудеться зараз, і це може зайняти деякий час (але це одноразова процедура). Виконання команди було призупинено. Після створення образу, будь ласка, виконайте команду знову.
552
+ layout.split = Розділити екран (бета)
550
553
  menu.audio = Аудіо / Голос
551
554
  menu.audio.cache.clear = Очистити кеш аудіо...
552
555
  menu.audio.control.global = Контроль голосу (глобальний)
@@ -50,6 +50,8 @@ action.tab.add.notepad = 添加新记事本
50
50
  action.tab.close = 关闭
51
51
  action.tab.close_all.chat = 关闭所有聊天
52
52
  action.tab.close_all.notepad = 关闭所有记事本
53
+ action.tab.move.left = 移到左列
54
+ action.tab.move.right = 移到右列
53
55
  action.touch = 創建文件...
54
56
  action.truncate = 截斷
55
57
  action.undo = 撤銷
@@ -621,6 +623,7 @@ ipython.docker.build.finish = 成功!IPython 的 Docker 镜像已构建完成
621
623
  ipython.docker.build.start = 正在构建 Docker 镜像... 请稍候...
622
624
  ipython.docker.install = 运行 IPython 需要安装 Docker。系统上未检测到 Docker。请先安装 Docker;您可以在此处找到说明:https://docs.docker.com/engine/install/。当前命令的执行已暂停。
623
625
  ipython.image.build = IPython 的 Docker 镜像尚未构建。这将立即发生,可能需要一段时间(但这是一次性过程)。命令的执行已暂停。镜像构建完成后,请再次执行命令。
626
+ layout.split = 分屏 (测试版)
624
627
  menu.audio = 音頻/語音
625
628
  menu.audio.cache.clear = 清除音频缓存...
626
629
  menu.audio.control.global = 语音控制(全局)
@@ -151,9 +151,11 @@ class Simple:
151
151
  self.plugin.window.dispatch(AppEvent(AppEvent.VOICE_CONTROL_STOPPED)) # app event
152
152
  return
153
153
  wf = wave.open(path, 'wb')
154
- wf.setnchannels(1)
154
+ channels = int(self.plugin.window.core.config.get('audio.input.channels', 1))
155
+ rate = int(self.plugin.window.core.config.get('audio.input.rate', 44100))
156
+ wf.setnchannels(channels)
155
157
  wf.setsampwidth(self.p.get_sample_size(pyaudio.paInt16))
156
- wf.setframerate(44100)
158
+ wf.setframerate(rate)
157
159
  wf.writeframes(b''.join(self.frames))
158
160
  wf.close()
159
161
  self.plugin.handle_thread(True) # handle transcription in simple mode
@@ -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.07 21:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -1748,6 +1748,13 @@ class Patch:
1748
1748
  self.window.core.updater.patch_css('style.light.css', True) # force update
1749
1749
  updated = True
1750
1750
 
1751
+ # < 2.4.39
1752
+ if old < parse_version("2.4.39"):
1753
+ print("Migrating config from < 2.4.39...")
1754
+ if 'layout.split' not in data:
1755
+ data["layout.split"] = False
1756
+ updated = True
1757
+
1751
1758
  # update file
1752
1759
  migrated = False
1753
1760
  if updated:
@@ -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.04.08 21:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from packaging.version import Version
@@ -71,6 +71,9 @@ class BaseProvider:
71
71
  def get_meta_indexed(self):
72
72
  pass
73
73
 
74
+ def get_item_by_id(self, id: int) -> CtxItem:
75
+ pass
76
+
74
77
  def dump(self, ctx: CtxItem):
75
78
  pass
76
79
 
@@ -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.08.29 04:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import time
@@ -118,6 +118,15 @@ class DbSqliteProvider(BaseProvider):
118
118
  """
119
119
  return self.storage.get_last_meta_id()
120
120
 
121
+ def get_item_by_id(self, id: int) -> CtxItem:
122
+ """
123
+ Get ctx item by ID
124
+
125
+ :param id: ctx item ID
126
+ :return: ctx item
127
+ """
128
+ return self.storage.get_item_by_id(id)
129
+
121
130
  def load(self, id: int) -> list:
122
131
  """
123
132
  Load items for ctx ID
@@ -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.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from datetime import datetime
@@ -189,6 +189,27 @@ class Storage:
189
189
  items[meta.id] = meta
190
190
  return items
191
191
 
192
+ def get_item_by_id(self, id: int) -> CtxItem:
193
+ """
194
+ Return ctx item by ID
195
+
196
+ :return: CtxItem
197
+ """
198
+ stmt = text("""
199
+ SELECT * FROM ctx_item WHERE id = :id
200
+ """).bindparams(id=id)
201
+ db = self.window.core.db.get_db()
202
+ with db.connect() as conn:
203
+ result = conn.execute(stmt)
204
+ row = result.fetchone()
205
+ if row:
206
+ item = CtxItem()
207
+ unpack_item(item, row._asdict())
208
+ meta = self.get_meta_by_id(item.meta_id) # append meta
209
+ item.meta = meta
210
+ return item
211
+ return None
212
+
192
213
  def get_meta_by_root_id_and_preset_id(self, root_id: int, preset_id: str) -> dict:
193
214
  """
194
215
  Return dict with indexed CtxMeta objects, indexed by ID
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.23 00:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
@@ -153,14 +153,6 @@ class Input:
153
153
  """
154
154
  # header (input label + input counter)
155
155
  self.window.ui.nodes['input.label'] = HelpLabel(trans("input.label"))
156
- self.window.ui.nodes['input.counter'] = QLabel("")
157
- self.window.ui.nodes['input.counter'].setToolTip(trans('tip.tokens.input'))
158
-
159
- # inline vision
160
- self.window.ui.nodes['inline.vision'] = HelpLabel(trans('inline.vision'))
161
- self.window.ui.nodes['inline.vision'].setVisible(False)
162
- self.window.ui.nodes['inline.vision'].setContentsMargins(0, 0, 0, 0)
163
- self.window.ui.nodes['inline.vision'].setToolTip(trans('vision.checkbox.tooltip'))
164
156
 
165
157
  # plugin audio input addon
166
158
  self.window.ui.plugin_addon['audio.input'] = AudioInput(self.window)
@@ -168,10 +160,10 @@ class Input:
168
160
 
169
161
  grid = QGridLayout()
170
162
 
171
- left_layout = QHBoxLayout()
172
- left_layout.addWidget(self.window.ui.nodes['input.label'])
173
- left_layout.addWidget(self.window.ui.nodes['inline.vision'])
174
- left_layout.addStretch(1)
163
+ #left_layout = QHBoxLayout()
164
+ #left_layout.addWidget(self.window.ui.nodes['input.label'])
165
+ #left_layout.addWidget(self.window.ui.nodes['inline.vision'])
166
+ #left_layout.addStretch(1)
175
167
 
176
168
  center_layout = QHBoxLayout()
177
169
  center_layout.addStretch()
@@ -179,13 +171,13 @@ class Input:
179
171
  center_layout.addWidget(self.window.ui.plugin_addon['audio.input.btn'])
180
172
  center_layout.addStretch()
181
173
 
182
- right_layout = QHBoxLayout()
183
- right_layout.addStretch(1)
184
- right_layout.addWidget(self.window.ui.nodes['input.counter'])
174
+ #right_layout = QHBoxLayout()
175
+ #right_layout.addStretch(1)
176
+ #right_layout.addWidget(self.window.ui.nodes['input.counter'])
185
177
 
186
- grid.addLayout(left_layout, 0, 0)
178
+ #grid.addLayout(left_layout, 0, 0)
187
179
  grid.addLayout(center_layout, 0, 1, alignment=Qt.AlignCenter)
188
- grid.addLayout(right_layout, 0, 2, alignment=Qt.AlignRight)
180
+ #grid.addLayout(right_layout, 0, 2, alignment=Qt.AlignRight)
189
181
 
190
182
  grid.setContentsMargins(0, 0, 0, 0)
191
183
  return grid
@@ -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: 2024.11.18 00:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Qt
13
- from PySide6.QtGui import QIcon
14
13
  from PySide6.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QCheckBox, QWidget, QSizePolicy, QPushButton, \
15
- QGridLayout, QSpacerItem, QWidgetItem, QLayout
14
+ QGridLayout, QWidgetItem, QLayout
16
15
 
17
16
  from pygpt_net.ui.widget.audio.output import AudioOutput
18
17
  from pygpt_net.ui.widget.element.labels import ChatStatusLabel, IconLabel, HelpLabel
19
- from pygpt_net.ui.widget.tabs.output import OutputTabs
20
- from pygpt_net.ui.widget.anims.loader import Loader, Loading
18
+ from pygpt_net.ui.widget.anims.loader import Loading
19
+ from pygpt_net.ui.widget.tabs.layout import OutputLayout
21
20
 
22
21
  from .explorer import Explorer
23
22
  from .input import Input
@@ -28,6 +27,7 @@ from pygpt_net.utils import trans
28
27
  import pygpt_net.icons_rc
29
28
 
30
29
 
30
+
31
31
  class Output:
32
32
  def __init__(self, window=None):
33
33
  """
@@ -47,46 +47,15 @@ class Output:
47
47
 
48
48
  :return: QWidget
49
49
  """
50
- # tabs
51
- self.window.ui.tabs['output'] = OutputTabs(self.window)
52
-
53
- # Create the [+] button
54
- plus_button = QPushButton(QIcon(":/icons/add.svg"), "")
55
- plus_button.setFixedSize(30, 25)
56
- plus_button.setFlat(True)
57
- plus_button.clicked.connect(self.window.controller.ui.tabs.new_tab)
58
- plus_button.setObjectName('tab-add')
59
- plus_button.setProperty('tabAdd', True)
60
- plus_button.setToolTip(trans('action.tab.add.chat'))
61
-
62
- # Add the button to the top right corner of the tab bar
63
- self.window.ui.tabs['output'].setCornerWidget(plus_button, corner=Qt.TopRightCorner)
64
-
65
- # create empty tabs
50
+ # prepare columns
51
+ self.window.ui.layout = OutputLayout(self.window)
52
+
53
+ # create empty containers for chat output
66
54
  self.window.ui.nodes['output'] = {}
67
55
  self.window.ui.nodes['output_plain'] = {}
68
56
 
69
- # connect signals
70
- self.window.ui.tabs['output'].currentChanged.connect(
71
- self.window.controller.ui.tabs.on_tab_changed
72
- )
73
- self.window.ui.tabs['output'].tabBarClicked.connect(
74
- self.window.controller.ui.tabs.on_tab_clicked
75
- )
76
- self.window.ui.tabs['output'].tabBarDoubleClicked.connect(
77
- self.window.controller.ui.tabs.on_tab_dbl_clicked
78
- )
79
- self.window.ui.tabs['output'].tabCloseRequested.connect(
80
- self.window.controller.ui.tabs.on_tab_closed
81
- )
82
-
83
- # tab bar signals
84
- self.window.ui.tabs['output'].tabBar().tabMoved.connect(
85
- self.window.controller.ui.tabs.on_tab_moved
86
- )
87
-
88
57
  layout = QVBoxLayout()
89
- layout.addWidget(self.window.ui.tabs['output'])
58
+ layout.addWidget(self.window.ui.layout)
90
59
  layout.addLayout(self.setup_bottom())
91
60
  layout.setContentsMargins(0, 5, 0, 0)
92
61
 
@@ -177,18 +146,29 @@ class Output:
177
146
  self.window.ui.nodes['prompt.context'].setToolTip(trans('tip.tokens.ctx'))
178
147
  self.window.ui.nodes['prompt.context'].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
179
148
 
149
+ self.window.ui.nodes['input.counter'] = ChatStatusLabel("")
150
+ self.window.ui.nodes['input.counter'].setToolTip(trans('tip.tokens.input'))
151
+ self.window.ui.nodes['input.counter'].setWordWrap(False)
152
+
180
153
  # plugin audio output addon
181
154
  self.window.ui.plugin_addon['audio.output'] = AudioOutput(self.window)
182
155
 
183
156
  # schedule
184
157
  self.window.ui.plugin_addon['schedule'] = ChatStatusLabel("")
185
158
 
159
+ # inline vision
160
+ self.window.ui.nodes['inline.vision'] = HelpLabel(trans('inline.vision'))
161
+ self.window.ui.nodes['inline.vision'].setVisible(False)
162
+ self.window.ui.nodes['inline.vision'].setContentsMargins(0, 0, 0, 0)
163
+ self.window.ui.nodes['inline.vision'].setToolTip(trans('vision.checkbox.tooltip'))
164
+
186
165
  opts_layout = QHBoxLayout()
187
166
  # opts_layout.setSpacing(2) #
188
167
  opts_layout.setContentsMargins(0, 0, 0, 0)
189
168
  opts_layout.addWidget(self.window.ui.nodes['output.timestamp'])
190
169
  # opts_layout.addWidget(self.window.ui.nodes['output.edit'])
191
170
  opts_layout.addWidget(self.window.ui.nodes['output.raw'])
171
+ opts_layout.addWidget(self.window.ui.nodes['inline.vision'])
192
172
  opts_layout.setAlignment(Qt.AlignLeft)
193
173
 
194
174
  left_layout = QHBoxLayout()
@@ -210,6 +190,8 @@ class Output:
210
190
  right_layout.addWidget(self.window.ui.nodes['chat.model'])
211
191
  right_layout.addWidget(QLabel(" "))
212
192
  right_layout.addWidget(self.window.ui.nodes['prompt.context'])
193
+ right_layout.addWidget(QLabel(" "))
194
+ right_layout.addWidget(self.window.ui.nodes['input.counter'])
213
195
  right_layout.setContentsMargins(0, 0, 0, 0)
214
196
 
215
197
  left_widget = QWidget()
@@ -224,14 +206,14 @@ class Output:
224
206
 
225
207
  left_layout = QHBoxLayout()
226
208
  left_layout.addWidget(left_widget)
227
- left_layout.addStretch(1)
209
+ #left_layout.addStretch(1)
228
210
  left_layout.setContentsMargins(0, 0, 0, 0)
229
211
 
230
212
  center_layout = QHBoxLayout()
231
- center_layout.addStretch()
213
+ #center_layout.addStretch()
232
214
  center_layout.addWidget(self.window.ui.nodes['anim.loading'])
233
215
  center_layout.addStretch()
234
- center_layout.setContentsMargins(0, 0, 0, 0)
216
+ center_layout.setContentsMargins(20, 0, 0, 0)
235
217
 
236
218
  right_layout = QHBoxLayout()
237
219
  right_layout.addStretch(1)
@@ -6,14 +6,14 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.17 03:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
13
13
 
14
14
  from PySide6.QtCore import QSize
15
15
  from PySide6.QtGui import QIcon
16
- from PySide6.QtWidgets import QVBoxLayout, QLabel, QPushButton, QWidget, QSizePolicy
16
+ from PySide6.QtWidgets import QVBoxLayout, QLabel, QPushButton, QWidget, QSizePolicy, QHBoxLayout
17
17
 
18
18
  from pygpt_net.ui.widget.textarea.name import NameInput
19
19
  from pygpt_net.ui.widget.option.slider import OptionSlider
@@ -25,6 +25,7 @@ from .agent_llama import AgentLlama
25
25
  from .image import Image
26
26
  from .indexes import Indexes
27
27
  from .vision import Vision
28
+ from ...widget.option.toggle_label import ToggleLabel
28
29
 
29
30
 
30
31
  class Footer:
@@ -75,6 +76,21 @@ class Footer:
75
76
  rows.addWidget(self.window.ui.nodes['voice.control.btn'])
76
77
  rows.setContentsMargins(2, 0, 0, 0)
77
78
 
79
+ self.window.ui.nodes['layout.split'] = ToggleLabel(trans('layout.split'), label_position="left",
80
+ icon=":/icons/window.svg")
81
+ self.window.ui.nodes['layout.split'].box.stateChanged.connect(
82
+ lambda: self.window.controller.ui.tabs.toggle_split_screen(self.window.ui.nodes['layout.split'].box.isChecked())
83
+ )
84
+ split_layout = QHBoxLayout()
85
+
86
+ split_layout.addWidget(QLabel(""))
87
+ split_layout.addStretch(1)
88
+ split_layout.addWidget(self.window.ui.nodes['layout.split'])
89
+ split_layout.setContentsMargins(5, 0, 15, 0)
90
+ split_widget = QWidget()
91
+ split_widget.setLayout(split_layout)
92
+ rows.addWidget(split_widget)
93
+
78
94
  # logo
79
95
  logo_button = self.setup_logo()
80
96
 
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
+ # ================================================== #
11
+
12
+ from PySide6.QtCore import Qt, QObject, QEvent
13
+ from PySide6.QtWidgets import QTabWidget, QWidget, QVBoxLayout, QSplitter, QSizePolicy
14
+
15
+ from pygpt_net.ui.widget.tabs.output import OutputTabs
16
+
17
+
18
+ class OutputColumn(QWidget):
19
+ def __init__(self, window=None):
20
+ """
21
+ Output column
22
+
23
+ :param window: window instance
24
+ """
25
+ super(OutputColumn, self).__init__(window)
26
+ self.window = window
27
+ self.idx = -1
28
+ self.tabs = OutputTabs(self.window, column=self)
29
+ self.layout = QVBoxLayout()
30
+ self.layout.addWidget(self.tabs)
31
+ self.layout.setContentsMargins(0, 0, 0, 0)
32
+ self.setLayout(self.layout)
33
+ self.filter = FocusEventFilter(self, self.on_focus)
34
+ self.installEventFilter(self.filter)
35
+ self.setFocusPolicy(Qt.StrongFocus)
36
+
37
+ def on_focus(self, widget):
38
+ """
39
+ On widget clicked
40
+
41
+ :param widget: widget
42
+ """
43
+ self.window.controller.ui.tabs.on_column_focus(self.idx)
44
+ widget.setFocus()
45
+
46
+ def set_idx(self, idx: int):
47
+ """
48
+ Set index
49
+
50
+ :param idx: int
51
+ """
52
+ self.idx = idx
53
+
54
+ def get_idx(self) -> int:
55
+ """
56
+ Get index
57
+
58
+ :return: int
59
+ """
60
+ return self.idx
61
+
62
+ def set_tabs(self, tabs: QTabWidget):
63
+ """
64
+ Set tabs widget
65
+
66
+ :param tabs: QTabWidget
67
+ """
68
+ self.tabs = tabs
69
+
70
+ def get_tabs(self) -> OutputTabs:
71
+ """
72
+ Get tabs
73
+
74
+ :return: OutputTabs
75
+ """
76
+ return self.tabs
77
+
78
+
79
+ class OutputLayout(QWidget):
80
+ def __init__(self, window=None):
81
+ """
82
+ Output layout
83
+
84
+ :param window: window instance
85
+ """
86
+ super(OutputLayout, self).__init__(window)
87
+ self.window = window
88
+ self.columns = []
89
+
90
+ column1 = OutputColumn(self.window)
91
+ column2 = OutputColumn(self.window)
92
+ self.add_column(column1)
93
+ self.add_column(column2)
94
+
95
+ self.splitter = QSplitter(Qt.Horizontal)
96
+ self.splitter.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
97
+ for column in self.columns:
98
+ self.splitter.addWidget(column)
99
+
100
+ self.window.ui.splitters['columns'] = self.splitter
101
+
102
+ self.layout = QVBoxLayout()
103
+ self.layout.addWidget(self.splitter, stretch=1)
104
+ self.layout.setContentsMargins(0, 0, 0, 0)
105
+ self.setLayout(self.layout)
106
+
107
+ def get_next_idx(self) -> int:
108
+ """
109
+ Get next index
110
+
111
+ :return: int
112
+ """
113
+ return len(self.columns)
114
+
115
+ def add_column(self, column: OutputColumn):
116
+ """
117
+ Add column
118
+
119
+ :param column: OutputColumn
120
+ """
121
+ idx = self.get_next_idx()
122
+ column.set_idx(idx)
123
+ self.columns.append(column)
124
+
125
+ def get_tabs_by_idx(self, idx: int) -> OutputTabs:
126
+ """
127
+ Get tabs by column index
128
+
129
+ :param idx: int
130
+ :return: OutputTabs
131
+ """
132
+ for column in self.columns:
133
+ if column.idx == idx:
134
+ return column.tabs
135
+ return None
136
+
137
+ def get_active_tabs(self) -> OutputTabs:
138
+ """
139
+ Get active tabs
140
+
141
+ :return: OutputTabs
142
+ """
143
+ current = self.window.controller.ui.tabs.get_current_column_idx()
144
+ for column in self.columns:
145
+ if column.idx == current:
146
+ return column.tabs
147
+
148
+ def get_column_by_idx(self, idx: int) -> OutputColumn:
149
+ """
150
+ Get column by index
151
+
152
+ :param idx: int
153
+ :return: OutputColumn
154
+ """
155
+ for column in self.columns:
156
+ if column.idx == idx:
157
+ return column
158
+ return None
159
+
160
+ def get_active_column(self) -> OutputColumn:
161
+ """
162
+ Get active column
163
+
164
+ :return: OutputColumn
165
+ """
166
+ current = self.window.controller.ui.tabs.get_current_column_idx()
167
+ for column in self.columns:
168
+ if column.idx == current:
169
+ return column
170
+
171
+ class FocusEventFilter(QObject):
172
+ def __init__(self, column, callback):
173
+ """
174
+ Column event filter
175
+
176
+ :param column: parent column
177
+ :param callback: callback
178
+ """
179
+ super().__init__()
180
+ self.column = column
181
+ self.callback = callback
182
+
183
+ def eventFilter(self, obj, event):
184
+ """
185
+ Click event filter
186
+
187
+ :param obj: object
188
+ :param event: event
189
+ """
190
+ if event.type() == QEvent.MouseButtonPress or event.type() == QEvent.FocusIn:
191
+ widget = obj
192
+ if widget is not None:
193
+ self.callback(widget)
194
+ return False
195
+ return False