pygpt-net 2.4.37__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 (81) hide show
  1. CHANGELOG.md +11 -0
  2. README.md +24 -5
  3. pygpt_net/CHANGELOG.txt +11 -0
  4. pygpt_net/__init__.py +3 -3
  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/config/placeholder.py +29 -0
  9. pygpt_net/controller/ctx/__init__.py +32 -24
  10. pygpt_net/controller/ctx/common.py +3 -2
  11. pygpt_net/controller/dialogs/confirm.py +2 -2
  12. pygpt_net/controller/lang/custom.py +2 -7
  13. pygpt_net/controller/lang/mapping.py +2 -2
  14. pygpt_net/controller/layout.py +2 -2
  15. pygpt_net/controller/notepad.py +8 -5
  16. pygpt_net/controller/settings/editor.py +6 -0
  17. pygpt_net/controller/theme/__init__.py +33 -8
  18. pygpt_net/controller/theme/common.py +22 -1
  19. pygpt_net/controller/theme/markdown.py +26 -14
  20. pygpt_net/controller/theme/menu.py +26 -5
  21. pygpt_net/controller/ui/tabs.py +201 -58
  22. pygpt_net/core/attachments/context.py +14 -12
  23. pygpt_net/core/audio/__init__.py +59 -1
  24. pygpt_net/core/ctx/__init__.py +11 -1
  25. pygpt_net/core/ctx/container.py +16 -9
  26. pygpt_net/core/ctx/output.py +86 -67
  27. pygpt_net/core/debug/tabs.py +3 -2
  28. pygpt_net/core/filesystem/__init__.py +5 -19
  29. pygpt_net/core/filesystem/url.py +7 -3
  30. pygpt_net/core/render/base.py +14 -3
  31. pygpt_net/core/render/markdown/renderer.py +3 -1
  32. pygpt_net/core/render/plain/renderer.py +3 -3
  33. pygpt_net/core/render/web/body.py +39 -17
  34. pygpt_net/core/render/web/renderer.py +7 -5
  35. pygpt_net/core/tabs/__init__.py +180 -71
  36. pygpt_net/core/tabs/tab.py +13 -4
  37. pygpt_net/data/config/config.json +14 -4
  38. pygpt_net/data/config/models.json +3 -3
  39. pygpt_net/data/config/modes.json +3 -3
  40. pygpt_net/data/config/settings.json +55 -10
  41. pygpt_net/data/config/settings_section.json +3 -0
  42. pygpt_net/data/css/style.light.css +1 -0
  43. pygpt_net/data/css/{web.css → web-blocks.css} +144 -133
  44. pygpt_net/data/css/web-chatgpt.css +342 -0
  45. pygpt_net/data/css/web-chatgpt.dark.css +64 -0
  46. pygpt_net/data/css/web-chatgpt.light.css +75 -0
  47. pygpt_net/data/css/web-chatgpt_wide.css +342 -0
  48. pygpt_net/data/css/web-chatgpt_wide.dark.css +64 -0
  49. pygpt_net/data/css/web-chatgpt_wide.light.css +75 -0
  50. pygpt_net/data/locale/locale.de.ini +12 -0
  51. pygpt_net/data/locale/locale.en.ini +14 -1
  52. pygpt_net/data/locale/locale.es.ini +12 -0
  53. pygpt_net/data/locale/locale.fr.ini +12 -0
  54. pygpt_net/data/locale/locale.it.ini +12 -0
  55. pygpt_net/data/locale/locale.pl.ini +12 -0
  56. pygpt_net/data/locale/locale.uk.ini +12 -0
  57. pygpt_net/data/locale/locale.zh.ini +12 -0
  58. pygpt_net/plugin/audio_input/simple.py +21 -5
  59. pygpt_net/provider/core/config/patch.py +22 -1
  60. pygpt_net/provider/core/ctx/base.py +4 -1
  61. pygpt_net/provider/core/ctx/db_sqlite/__init__.py +10 -1
  62. pygpt_net/provider/core/ctx/db_sqlite/storage.py +22 -1
  63. pygpt_net/ui/layout/chat/input.py +10 -18
  64. pygpt_net/ui/layout/chat/output.py +26 -44
  65. pygpt_net/ui/layout/toolbox/footer.py +18 -2
  66. pygpt_net/ui/menu/config.py +7 -11
  67. pygpt_net/ui/menu/theme.py +9 -2
  68. pygpt_net/ui/widget/lists/context.py +1 -0
  69. pygpt_net/ui/widget/tabs/layout.py +195 -0
  70. pygpt_net/ui/widget/tabs/output.py +124 -35
  71. pygpt_net/ui/widget/textarea/html.py +11 -1
  72. pygpt_net/ui/widget/textarea/output.py +10 -1
  73. pygpt_net/ui/widget/textarea/search_input.py +4 -1
  74. pygpt_net/ui/widget/textarea/web.py +49 -9
  75. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.39.dist-info}/METADATA +25 -6
  76. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.39.dist-info}/RECORD +81 -74
  77. /pygpt_net/data/css/{web.dark.css → web-blocks.dark.css} +0 -0
  78. /pygpt_net/data/css/{web.light.css → web-blocks.light.css} +0 -0
  79. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.39.dist-info}/LICENSE +0 -0
  80. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.39.dist-info}/WHEEL +0 -0
  81. {pygpt_net-2.4.37.dist-info → pygpt_net-2.4.39.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.29 23:00:00 #
9
+ # Updated Date: 2024.11.30 04:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -119,6 +119,8 @@ class Context:
119
119
  if ("type" not in file
120
120
  or file["type"] not in ["local_file", "url"]):
121
121
  continue
122
+ if not "uuid" in file:
123
+ continue
122
124
  file_id = file["uuid"]
123
125
  file_idx_path = os.path.join(meta_path, file_id)
124
126
  text_path = os.path.join(file_idx_path, file_id + ".txt")
@@ -555,17 +557,6 @@ class Context:
555
557
  if meta is not None:
556
558
  self.delete_index(meta)
557
559
 
558
- def reset_by_meta_id(self, meta_id: int, delete_files: bool = False):
559
- """
560
- Delete all attachments for meta by id
561
-
562
- :param meta_id: Meta id
563
- :param delete_files: delete files
564
- """
565
- meta = self.window.core.ctx.get_meta_by_id(meta_id)
566
- if meta is not None:
567
- self.reset_by_meta(meta, delete_files)
568
-
569
560
  def reset_by_meta(self, meta: CtxMeta, delete_files: bool = False):
570
561
  """
571
562
  Delete all attachments for meta
@@ -578,6 +569,17 @@ class Context:
578
569
  if delete_files:
579
570
  self.delete_index(meta)
580
571
 
572
+ def reset_by_meta_id(self, meta_id: int, delete_files: bool = False):
573
+ """
574
+ Delete all attachments for meta by id
575
+
576
+ :param meta_id: Meta id
577
+ :param delete_files: delete files
578
+ """
579
+ meta = self.window.core.ctx.get_meta_by_id(meta_id)
580
+ if meta is not None:
581
+ self.reset_by_meta(meta, delete_files)
582
+
581
583
  def clear(self, meta: CtxMeta, delete_files: bool = False):
582
584
  """
583
585
  Clear all attachments by ctx meta
@@ -6,10 +6,11 @@
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.08 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
13
+ from bs4 import UnicodeDammit
13
14
 
14
15
  from pygpt_net.provider.audio_input.base import BaseProvider as InputBaseProvider
15
16
  from pygpt_net.provider.audio_output.base import BaseProvider as OutputBaseProvider
@@ -27,6 +28,55 @@ class Audio:
27
28
  "input": {},
28
29
  "output": {},
29
30
  }
31
+ self.last_error = None
32
+
33
+ def get_input_devices(self) -> list:
34
+ """
35
+ Get input devices
36
+
37
+ :return devices list: [(id, name)]
38
+ """
39
+ import pyaudio
40
+ devices = []
41
+ try:
42
+ p = pyaudio.PyAudio()
43
+ num_devices = p.get_device_count()
44
+ for i in range(num_devices):
45
+ info = p.get_device_info_by_index(i)
46
+ if info["maxInputChannels"] > 0:
47
+ dammit = UnicodeDammit(info["name"])
48
+ devices.append((i, dammit.unicode_markup))
49
+ # print(f"Device ID {i}: {info['name']}")
50
+ p.terminate()
51
+ except Exception as e:
52
+ print(f"Audio input devices receive error: {e}")
53
+ return devices
54
+
55
+ def is_device_compatible(self, device_index) -> bool:
56
+ """
57
+ Check if device is compatible
58
+
59
+ :param device_index: device index
60
+ :return: True if compatible
61
+ """
62
+ import pyaudio
63
+ rate = int(self.window.core.config.get('audio.input.rate', 44100))
64
+ channels = int(self.window.core.config.get('audio.input.channels', 1))
65
+ p = pyaudio.PyAudio()
66
+ info = p.get_device_info_by_index(device_index)
67
+ supported = False
68
+ try:
69
+ p.is_format_supported(
70
+ rate=rate,
71
+ input_device=info['index'],
72
+ input_channels=channels,
73
+ input_format=pyaudio.paInt16)
74
+ supported = True
75
+ except ValueError as e:
76
+ self.last_error = str(e)
77
+ supported = False
78
+ p.terminate()
79
+ return supported
30
80
 
31
81
  def is_registered(self, id: str, type: str = "output") -> bool:
32
82
  """
@@ -92,3 +142,11 @@ class Audio:
92
142
  :return: cleaned text
93
143
  """
94
144
  return re.sub(r'~###~.*?~###~', '', str(text))
145
+
146
+ def get_last_error(self) -> str:
147
+ """
148
+ Return last error
149
+
150
+ :return: Error
151
+ """
152
+ return self.last_error
@@ -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
  import copy
@@ -490,6 +490,16 @@ class Ctx:
490
490
  for item in self.get_items():
491
491
  if item.id == id:
492
492
  return item
493
+ return self.fetch_item_by_id(id) # if no item found, try to fetch from DB
494
+
495
+ def fetch_item_by_id(self, id: int) -> CtxItem:
496
+ """
497
+ Fetch ctx item by id
498
+
499
+ :param id: item id
500
+ :return: context item
501
+ """
502
+ return self.provider.get_item_by_id(id)
493
503
 
494
504
  def get_meta(self, reload: bool = False) -> dict:
495
505
  """
@@ -6,39 +6,44 @@
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.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtWidgets import QVBoxLayout
13
13
 
14
14
  from .bag import Bag
15
15
  from pygpt_net.ui.widget.textarea.output import ChatOutput
16
+ from ..tabs import Tab
16
17
 
17
18
 
18
19
  class Container:
19
20
  def __init__(self, window=None):
20
21
  """
21
- Context container
22
+ Context output container
23
+
24
+ :param window: Window
22
25
  """
23
26
  self.window = window
24
27
  self.bags = {}
25
28
  self.bags[0] = Bag(window) # always create initial bag
26
29
 
27
- def register_output(self, id: int = 0):
30
+ def get(self, tab: Tab):
28
31
  """
29
- Register output
32
+ Register and return output
30
33
 
31
- :param id: ID
34
+ :param tab: Tab
32
35
  :return: Widget
33
36
  """
34
37
  # plain output
35
38
  output_plain = ChatOutput(self.window)
39
+ output_plain.set_tab(tab)
36
40
 
37
41
  # web
38
42
  if self.window.core.config.get("render.engine") == "web":
39
43
  from pygpt_net.ui.widget.textarea.web import ChatWebOutput, CustomWebEnginePage
40
44
  # build output
41
45
  output_html = ChatWebOutput(self.window)
46
+ output_html.set_tab(tab)
42
47
  output_html.setPage(
43
48
  CustomWebEnginePage(self.window, output_html)
44
49
  )
@@ -48,13 +53,15 @@ class Container:
48
53
  else:
49
54
  # legacy
50
55
  output_html = ChatOutput(self.window)
56
+ output_html.set_tab(tab)
51
57
 
52
58
  if 'output_plain' not in self.window.ui.nodes:
53
59
  self.window.ui.nodes['output_plain'] = {}
54
60
  if 'output' not in self.window.ui.nodes:
55
61
  self.window.ui.nodes['output'] = {}
56
- self.window.ui.nodes['output_plain'][id] = output_plain
57
- self.window.ui.nodes['output'][id] = output_html
62
+
63
+ self.window.ui.nodes['output_plain'][tab.pid] = output_plain
64
+ self.window.ui.nodes['output'][tab.pid] = output_html
58
65
 
59
66
  # show/hide plain/html
60
67
  if self.window.core.config.get("render.plain") is True:
@@ -66,8 +73,8 @@ class Container:
66
73
 
67
74
  # build layout
68
75
  layout = QVBoxLayout()
69
- layout.addWidget(self.window.ui.nodes['output_plain'][id])
70
- layout.addWidget(self.window.ui.nodes['output'][id])
76
+ layout.addWidget(self.window.ui.nodes['output_plain'][tab.pid])
77
+ layout.addWidget(self.window.ui.nodes['output'][tab.pid])
71
78
  layout.setContentsMargins(0, 0, 0, 0)
72
79
  widget = self.window.core.tabs.from_layout(layout)
73
80
  return widget
@@ -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.05 23:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.item.ctx import CtxItem, CtxMeta
@@ -16,11 +16,28 @@ class Output:
16
16
  def __init__(self, window=None):
17
17
  """
18
18
  Context output mapping
19
+
20
+ :param window: Window
19
21
  """
20
22
  self.window = window
21
- self.mapping = {} # pid => meta.id
23
+ self.mapping = {} # [column_idx => [pid => meta.id]]
22
24
  self.last_pids = {} # meta.id => pid
23
25
  self.last_pid = 0 # last used PID
26
+ self.initialized = False
27
+
28
+ def init(self, force: bool = False):
29
+ """
30
+ Initialize mappings
31
+
32
+ :param force: Force reinitialize
33
+ """
34
+ if self.initialized and not force:
35
+ return
36
+ self.clear()
37
+ for n in range(0, self.window.core.tabs.NUM_COLS):
38
+ self.mapping[n] = {}
39
+ self.last_pids[n] = {}
40
+ self.initialized = True
24
41
 
25
42
  def store(self, meta: CtxMeta) -> int:
26
43
  """
@@ -29,31 +46,20 @@ class Output:
29
46
  :param meta: Meta
30
47
  :return: PID
31
48
  """
49
+ self.init()
32
50
  pid = self.window.core.tabs.get_active_pid()
33
- # check if allowed tab
34
51
  tab = self.window.core.tabs.get_tab_by_pid(pid)
35
52
  if tab is None or tab.type != Tab.TAB_CHAT:
36
53
  return 0
37
- self.mapping[pid] = meta.id
38
- self.last_pids[meta.id] = pid
54
+ col_idx = tab.column_idx
55
+ self.mapping[col_idx][pid] = meta.id
56
+ self.last_pids[col_idx][meta.id] = pid
39
57
  self.last_pid = pid
40
58
  tab = self.window.core.tabs.get_tab_by_pid(pid)
41
59
  if tab is not None:
42
60
  tab.data_id = meta.id # store meta id in tab data
43
61
  return pid
44
62
 
45
- def meta_id_in_tab(self, meta_id: int) -> bool:
46
- """
47
- Check if meta id is in tab
48
-
49
- :param meta_id: Meta ID
50
- :return: bool
51
- """
52
- for pid, mid in self.mapping.items():
53
- if mid == meta_id:
54
- return True
55
- return False
56
-
57
63
  def is_mapped(self, meta: CtxMeta) -> bool:
58
64
  """
59
65
  Check if meta is mapped
@@ -61,19 +67,46 @@ class Output:
61
67
  :param meta: Meta
62
68
  :return: bool
63
69
  """
64
- return meta.id in self.mapping.values()
70
+ self.init()
71
+ for col_idx in self.mapping:
72
+ if meta.id in self.mapping[col_idx].values():
73
+ return True
74
+ return False
65
75
 
66
- def get_meta(self, pid: int) -> CtxMeta or None:
76
+ def get_meta(self, pid: int) -> int or None:
67
77
  """
68
78
  Get meta by PID
69
79
 
70
80
  :param pid: PID
71
- :return: Meta or None
81
+ :return: meta ID or None
72
82
  """
73
- if pid in self.mapping:
74
- return self.mapping[pid]
83
+ self.init()
84
+ for col_idx in self.mapping:
85
+ if pid in self.mapping[col_idx]:
86
+ return self.mapping[col_idx][pid]
75
87
  return
76
88
 
89
+ def prepare_meta(self, tab: Tab) -> int or None:
90
+ """
91
+ Get meta ID by PID
92
+
93
+ :param tab: Tab
94
+ :return: Meta ID or None
95
+ """
96
+ self.init()
97
+ pid = tab.pid
98
+ col_idx = tab.column_idx
99
+ meta_id = None
100
+ if pid in self.mapping[col_idx]:
101
+ return self.mapping[col_idx][pid]
102
+ if meta_id is None:
103
+ meta_id = tab.data_id
104
+ if meta_id is not None:
105
+ self.mapping[col_idx][pid] = meta_id
106
+ self.last_pids[col_idx][meta_id] = pid
107
+ self.last_pid = pid
108
+ return meta_id
109
+
77
110
  def get_mapped(self, meta: CtxMeta) -> int or None:
78
111
  """
79
112
  Get PID by meta
@@ -81,75 +114,59 @@ class Output:
81
114
  :param meta: Meta
82
115
  :return: PID or None
83
116
  """
117
+ self.init()
84
118
  all = []
85
119
  pid = None
86
120
  active_pid = self.window.core.tabs.get_active_pid()
87
- for pid, meta_id in self.mapping.items():
121
+ tab = self.window.core.tabs.get_tab_by_pid(active_pid)
122
+ if tab is None:
123
+ return None
124
+ col_idx = tab.column_idx
125
+ for pid, meta_id in self.mapping[col_idx].items():
88
126
  if meta_id == meta.id:
89
127
  all.append(pid)
90
128
  for pid in all:
91
- if pid == active_pid: # at first check if active
129
+ if pid == active_pid: # at first check for current tab
92
130
  return pid
93
131
  return pid
94
132
 
95
- def get_mapped_last(self, meta: CtxMeta) -> int or None:
96
- """
97
- Get last PID by meta
98
-
99
- :param meta: Meta
100
- :return: PID or None
101
- """
102
- active_pid = self.window.core.tabs.get_active_pid()
103
- if self.mapping[active_pid] == meta.id:
104
- return active_pid # return active PID at first
105
- if meta.id in self.last_pids:
106
- return self.last_pids[meta.id]
107
- return
108
-
109
- def prepare(self, meta: CtxMeta):
110
- """
111
- Clear mapping
112
-
113
- :param meta: meta to prepare
114
- """
115
- active_pid = self.window.core.tabs.get_active_pid()
116
- if active_pid in self.mapping:
117
- del self.mapping[active_pid] # remove from mapping
118
-
119
- def clear(self):
120
- """
121
- Clear mapping
122
- """
123
- self.mapping = {}
124
- self.last_pids = {}
125
- self.last_pid = 0
126
-
127
133
  def is_empty(self) -> bool:
128
134
  """
129
135
  Check if mapping is empty
130
136
 
131
137
  :return: bool
132
138
  """
139
+ self.init()
133
140
  active_pid = self.window.core.tabs.get_active_pid()
134
- if active_pid in self.mapping:
135
- return False
141
+ for col_idx in list(self.mapping.keys()):
142
+ if active_pid in self.mapping[col_idx]:
143
+ return False
136
144
  return True
137
145
 
138
- def get_pid(self, meta: CtxMeta = None) -> int:
146
+ def get_pid(self, meta: CtxMeta = None) -> int or None:
139
147
  """
140
148
  Get PID by meta
141
149
 
142
150
  :param meta: Meta
143
151
  :return: PID
144
152
  """
153
+ self.init()
154
+ active_pid = self.window.core.tabs.get_active_pid()
145
155
  if meta is None:
146
- return self.window.core.tabs.get_active_pid()
156
+ return None
147
157
  if self.is_mapped(meta):
148
- if self.window.core.tabs.get_active_pid() != self.get_mapped(meta): # if loaded in another tab
149
- return self.store(meta) # update to current
150
- return self.get_mapped_last(meta)
151
- else:
152
- return self.store(meta)
158
+ if active_pid == self.get_mapped(meta): # if loaded in current tab
159
+ return active_pid # return current
160
+ return self.store(meta)
161
+
162
+ def clear(self):
163
+ """
164
+ Clear mapping
165
+ """
166
+ self.mapping = {}
167
+ self.last_pids = {}
168
+ self.last_pid = 0
169
+ self.initialized = False
153
170
 
154
171
  def get_current(self, meta: CtxMeta = None):
155
172
  """
@@ -176,7 +193,8 @@ class Output:
176
193
  if pid in self.window.ui.nodes['output_plain']:
177
194
  return self.window.ui.nodes['output_plain'][pid]
178
195
  else:
179
- return self.window.ui.nodes['output_plain'][0]
196
+ for pid in self.window.ui.nodes['output_plain']:
197
+ return self.window.ui.nodes['output_plain'][pid]
180
198
 
181
199
  def get_by_pid(self, pid = None):
182
200
  """
@@ -201,7 +219,8 @@ class Output:
201
219
  if pid in self.window.ui.nodes['output_plain']:
202
220
  return self.window.ui.nodes['output_plain'][pid]
203
221
  else:
204
- return self.window.ui.nodes['output_plain'][0]
222
+ for pid in self.window.ui.nodes['output_plain']:
223
+ return self.window.ui.nodes['output_plain'][pid]
205
224
 
206
225
  def get_all(self) -> list:
207
226
  """
@@ -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.07 23:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  class TabsDebug:
@@ -25,6 +25,7 @@ class TabsDebug:
25
25
  self.window.core.debug.add(self.id, 'current PID', str(self.window.controller.ui.tabs.get_current_pid()))
26
26
  self.window.core.debug.add(self.id, 'current IDX', str(self.window.controller.ui.tabs.get_current_idx()))
27
27
  self.window.core.debug.add(self.id, 'current Type', str(self.window.controller.ui.tabs.get_current_type()))
28
+ self.window.core.debug.add(self.id, 'current Column', str(self.window.controller.ui.tabs.get_current_column_idx()))
28
29
  self.window.core.debug.add(self.id, 'last_pid', str(self.window.core.tabs.last_pid))
29
30
  self.window.core.debug.add(self.id, 'count(pids)', str(len(self.window.core.tabs.pids)))
30
31
  self.window.core.debug.add(self.id, 'count(ctx bags)', str(len(self.window.core.ctx.container.bags)))
@@ -38,6 +39,6 @@ class TabsDebug:
38
39
 
39
40
  # mapping PID => meta.id
40
41
  self.window.core.debug.add(self.id, 'PID => meta.id', str(self.window.core.ctx.output.mapping))
41
- self.window.core.debug.add(self.id, '(last) PID => meta.id', str(self.window.core.ctx.output.last_pids))
42
+ self.window.core.debug.add(self.id, '(last) meta.id => PID', str(self.window.core.ctx.output.last_pids))
42
43
  self.window.core.debug.add(self.id, '(last) PID', str(self.window.core.ctx.output.last_pid))
43
44
  self.window.core.debug.end(self.id)
@@ -6,13 +6,12 @@
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 21:00:00 #
9
+ # Updated Date: 2024.12.07 21:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
13
13
  import shutil
14
14
 
15
- from datetime import datetime
16
15
  from pathlib import PurePath
17
16
  from uuid import uuid4
18
17
 
@@ -38,18 +37,6 @@ class Filesystem:
38
37
  self.types = Types(window)
39
38
  self.url = Url(window)
40
39
  self.workdir_placeholder = "%workdir%"
41
- self.styles = [
42
- "style.css",
43
- "style.dark.css",
44
- "style.light.css",
45
- "markdown.css",
46
- "markdown.dark.css",
47
- "markdown.light.css",
48
- "web.css",
49
- "web.dark.css",
50
- "web.light.css",
51
- "fix_windows.css",
52
- ]
53
40
 
54
41
  def install(self):
55
42
  """Install provider data"""
@@ -77,9 +64,9 @@ class Filesystem:
77
64
 
78
65
  src_dir = os.path.join(self.window.core.config.get_app_path(), 'data', 'css')
79
66
  dst_dir = os.path.join(self.window.core.config.path, 'css')
80
-
67
+ app_styles = os.listdir(src_dir)
81
68
  try:
82
- for style in self.styles:
69
+ for style in app_styles:
83
70
  src = os.path.join(src_dir, style)
84
71
  dst = os.path.join(dst_dir, style)
85
72
  if (not os.path.exists(dst) or force) and os.path.exists(src):
@@ -91,7 +78,8 @@ class Filesystem:
91
78
  """Backup user custom css styles"""
92
79
  css_dir = os.path.join(self.window.core.config.path, 'css')
93
80
  backup_file_extension = '.backup'
94
- for style in self.styles:
81
+ user_styles = os.listdir(css_dir)
82
+ for style in user_styles:
95
83
  src = os.path.join(css_dir, style)
96
84
  dst = os.path.join(css_dir, style + backup_file_extension)
97
85
  if os.path.exists(src):
@@ -457,5 +445,3 @@ class Filesystem:
457
445
  else:
458
446
  files = [os.path.join(path, f) for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
459
447
  return files
460
-
461
-
@@ -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.08 06:00:00 #
9
+ # Updated Date: 2024.12.09 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import QUrl
@@ -38,14 +38,18 @@ class Url:
38
38
  ]
39
39
 
40
40
  # JS bridge
41
- if url.toString() == 'bridge://open_find':
42
- pid = self.window.controller.ui.tabs.get_current_pid()
41
+ if url.toString().startswith('bridge://open_find'):
42
+ pid = int(url.toString().split(':')[2])
43
43
  if pid in self.window.ui.nodes['output']:
44
44
  self.window.ui.nodes['output'][pid].find_open()
45
45
  return
46
46
  elif url.toString() == 'bridge://escape':
47
47
  self.window.controller.access.on_escape()
48
48
  return
49
+ elif url.toString() == 'bridge://focus':
50
+ pid = self.window.controller.ui.tabs.get_current_pid()
51
+ if pid in self.window.ui.nodes['output']:
52
+ self.window.ui.nodes['output'][pid].on_focus_js()
49
53
 
50
54
  # -------------
51
55
 
@@ -6,9 +6,10 @@
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.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
+ from pygpt_net.core.tabs import Tab
12
13
  from pygpt_net.item.ctx import CtxItem, CtxMeta
13
14
 
14
15
 
@@ -20,6 +21,15 @@ class BaseRenderer:
20
21
  :param window: Window instance
21
22
  """
22
23
  self.window = window
24
+ self.tab = None
25
+
26
+ def set_tab(self, tab: Tab):
27
+ """
28
+ Append tab
29
+
30
+ :param tab: Tab
31
+ """
32
+ self.tab = tab
23
33
 
24
34
  def prepare(self):
25
35
  """
@@ -218,13 +228,14 @@ class BaseRenderer:
218
228
  """
219
229
  pass
220
230
 
221
- def on_page_loaded(self, meta: CtxMeta = None):
231
+ def on_page_loaded(self, meta: CtxMeta = None, tab: Tab = None):
222
232
  """
223
233
  On page loaded callback
224
234
 
225
235
  :param meta: context meta
236
+ :param tab: Tab
226
237
  """
227
- pass
238
+ self.tab = tab
228
239
 
229
240
  def on_enable_edit(self, live: bool = True):
230
241
  """
@@ -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.05 23:00:00 #
9
+ # Updated Date: 2024.12.09 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import re
@@ -50,6 +50,8 @@ class Renderer(BaseRenderer):
50
50
 
51
51
  :param meta: context PID
52
52
  """
53
+ if self.tab is not None:
54
+ return self.tab.pid # get PID from tab if exists
53
55
  return self.window.core.ctx.output.get_pid(meta)
54
56
 
55
57
  def get_or_create_pid(self, meta: CtxMeta):