pygpt-net 2.6.1__py3-none-any.whl → 2.6.6__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 (131) hide show
  1. pygpt_net/CHANGELOG.txt +23 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +20 -1
  4. pygpt_net/config.py +55 -65
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/calendar/note.py +101 -126
  7. pygpt_net/controller/chat/chat.py +38 -35
  8. pygpt_net/controller/chat/render.py +154 -214
  9. pygpt_net/controller/chat/response.py +5 -3
  10. pygpt_net/controller/chat/stream.py +92 -27
  11. pygpt_net/controller/config/config.py +39 -42
  12. pygpt_net/controller/config/field/checkbox.py +16 -12
  13. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  14. pygpt_net/controller/config/field/cmd.py +51 -57
  15. pygpt_net/controller/config/field/combo.py +33 -16
  16. pygpt_net/controller/config/field/dictionary.py +48 -55
  17. pygpt_net/controller/config/field/input.py +50 -32
  18. pygpt_net/controller/config/field/slider.py +40 -45
  19. pygpt_net/controller/config/field/textarea.py +20 -6
  20. pygpt_net/controller/config/placeholder.py +110 -231
  21. pygpt_net/controller/ctx/common.py +48 -48
  22. pygpt_net/controller/ctx/ctx.py +91 -132
  23. pygpt_net/controller/lang/mapping.py +57 -95
  24. pygpt_net/controller/lang/plugins.py +64 -55
  25. pygpt_net/controller/lang/settings.py +39 -38
  26. pygpt_net/controller/layout/layout.py +176 -109
  27. pygpt_net/controller/mode/mode.py +88 -85
  28. pygpt_net/controller/model/model.py +73 -73
  29. pygpt_net/controller/plugins/plugins.py +209 -223
  30. pygpt_net/controller/plugins/presets.py +54 -55
  31. pygpt_net/controller/plugins/settings.py +54 -69
  32. pygpt_net/controller/presets/editor.py +33 -88
  33. pygpt_net/controller/presets/experts.py +20 -1
  34. pygpt_net/controller/presets/presets.py +293 -298
  35. pygpt_net/controller/settings/profile.py +16 -4
  36. pygpt_net/controller/theme/theme.py +72 -81
  37. pygpt_net/controller/ui/mode.py +118 -186
  38. pygpt_net/controller/ui/tabs.py +69 -90
  39. pygpt_net/controller/ui/ui.py +47 -56
  40. pygpt_net/controller/ui/vision.py +24 -23
  41. pygpt_net/core/agents/runner.py +15 -7
  42. pygpt_net/core/bridge/bridge.py +5 -5
  43. pygpt_net/core/command/command.py +149 -219
  44. pygpt_net/core/ctx/ctx.py +94 -146
  45. pygpt_net/core/debug/debug.py +48 -58
  46. pygpt_net/core/experts/experts.py +3 -3
  47. pygpt_net/core/models/models.py +74 -112
  48. pygpt_net/core/modes/modes.py +13 -21
  49. pygpt_net/core/plugins/plugins.py +154 -177
  50. pygpt_net/core/presets/presets.py +103 -176
  51. pygpt_net/core/render/web/body.py +217 -215
  52. pygpt_net/core/render/web/renderer.py +330 -474
  53. pygpt_net/core/text/utils.py +28 -44
  54. pygpt_net/core/tokens/tokens.py +104 -203
  55. pygpt_net/data/config/config.json +3 -3
  56. pygpt_net/data/config/models.json +3 -3
  57. pygpt_net/data/locale/locale.de.ini +2 -0
  58. pygpt_net/data/locale/locale.en.ini +2 -0
  59. pygpt_net/data/locale/locale.es.ini +2 -0
  60. pygpt_net/data/locale/locale.fr.ini +2 -0
  61. pygpt_net/data/locale/locale.it.ini +2 -0
  62. pygpt_net/data/locale/locale.pl.ini +3 -1
  63. pygpt_net/data/locale/locale.uk.ini +2 -0
  64. pygpt_net/data/locale/locale.zh.ini +2 -0
  65. pygpt_net/item/ctx.py +141 -139
  66. pygpt_net/plugin/agent/plugin.py +2 -1
  67. pygpt_net/plugin/audio_output/plugin.py +5 -2
  68. pygpt_net/plugin/base/plugin.py +101 -85
  69. pygpt_net/plugin/bitbucket/__init__.py +12 -0
  70. pygpt_net/plugin/bitbucket/config.py +267 -0
  71. pygpt_net/plugin/bitbucket/plugin.py +126 -0
  72. pygpt_net/plugin/bitbucket/worker.py +569 -0
  73. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  74. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  75. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  76. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  77. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  78. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  79. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  80. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  81. pygpt_net/plugin/experts/plugin.py +2 -2
  82. pygpt_net/plugin/facebook/__init__.py +12 -0
  83. pygpt_net/plugin/facebook/config.py +359 -0
  84. pygpt_net/plugin/facebook/plugin.py +113 -0
  85. pygpt_net/plugin/facebook/worker.py +698 -0
  86. pygpt_net/plugin/github/__init__.py +12 -0
  87. pygpt_net/plugin/github/config.py +441 -0
  88. pygpt_net/plugin/github/plugin.py +126 -0
  89. pygpt_net/plugin/github/worker.py +674 -0
  90. pygpt_net/plugin/google/__init__.py +12 -0
  91. pygpt_net/plugin/google/config.py +367 -0
  92. pygpt_net/plugin/google/plugin.py +126 -0
  93. pygpt_net/plugin/google/worker.py +826 -0
  94. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  95. pygpt_net/plugin/mailer/plugin.py +3 -5
  96. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  97. pygpt_net/plugin/real_time/plugin.py +52 -60
  98. pygpt_net/plugin/slack/__init__.py +12 -0
  99. pygpt_net/plugin/slack/config.py +349 -0
  100. pygpt_net/plugin/slack/plugin.py +115 -0
  101. pygpt_net/plugin/slack/worker.py +639 -0
  102. pygpt_net/plugin/telegram/__init__.py +12 -0
  103. pygpt_net/plugin/telegram/config.py +308 -0
  104. pygpt_net/plugin/telegram/plugin.py +117 -0
  105. pygpt_net/plugin/telegram/worker.py +563 -0
  106. pygpt_net/plugin/twitter/__init__.py +12 -0
  107. pygpt_net/plugin/twitter/config.py +491 -0
  108. pygpt_net/plugin/twitter/plugin.py +125 -0
  109. pygpt_net/plugin/twitter/worker.py +837 -0
  110. pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
  111. pygpt_net/tools/code_interpreter/tool.py +0 -1
  112. pygpt_net/tools/translator/tool.py +1 -1
  113. pygpt_net/ui/base/config_dialog.py +86 -100
  114. pygpt_net/ui/base/context_menu.py +48 -46
  115. pygpt_net/ui/dialog/preset.py +34 -77
  116. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  117. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  118. pygpt_net/ui/main.py +49 -31
  119. pygpt_net/ui/tray.py +61 -60
  120. pygpt_net/ui/widget/calendar/select.py +86 -70
  121. pygpt_net/ui/widget/lists/attachment.py +86 -44
  122. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  123. pygpt_net/ui/widget/lists/context.py +135 -188
  124. pygpt_net/ui/widget/lists/preset.py +59 -61
  125. pygpt_net/ui/widget/textarea/web.py +161 -48
  126. pygpt_net/utils.py +8 -1
  127. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/METADATA +164 -2
  128. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/RECORD +131 -103
  129. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/LICENSE +0 -0
  130. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/WHEEL +0 -0
  131. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/entry_points.txt +0 -0
@@ -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: 2025.08.06 19:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from functools import partial
13
13
 
14
14
  from PySide6.QtCore import Qt
15
15
  from PySide6.QtGui import QAction, QIcon, QResizeEvent, QImage
16
- from PySide6.QtWidgets import QMenu, QApplication, QHeaderView
16
+ from PySide6.QtWidgets import QMenu, QApplication
17
17
 
18
18
  from pygpt_net.item.attachment import AttachmentItem
19
19
  from pygpt_net.ui.widget.lists.base import BaseList
@@ -22,6 +22,11 @@ import pygpt_net.icons_rc
22
22
 
23
23
 
24
24
  class AttachmentList(BaseList):
25
+ _ICON_VIEW = QIcon(":/icons/view.svg")
26
+ _ICON_FOLDER = QIcon(":/icons/folder_filled.svg")
27
+ _ICON_EDIT = QIcon(":/icons/edit.svg")
28
+ _ICON_DELETE = QIcon(":/icons/delete.svg")
29
+
25
30
  def __init__(self, window=None, id=None):
26
31
  """
27
32
  Attachments menu
@@ -36,22 +41,35 @@ class AttachmentList(BaseList):
36
41
  self.setHeaderHidden(False)
37
42
  self.clicked.disconnect(self.click)
38
43
 
39
- self.header = self.header()
40
- self.header.setStretchLastSection(False)
44
+ hdr = self.header()
45
+ hdr.setStretchLastSection(False)
41
46
 
42
47
  self.column_proportion = 0.3
48
+ self._last_width = -1
43
49
  self.adjustColumnWidths()
44
50
 
45
51
  def adjustColumnWidths(self):
46
- total_width = self.width()
52
+ hdr = self.header()
53
+ cols = hdr.count()
54
+ if cols <= 0:
55
+ return
56
+ total_width = self.viewport().width() or self.width()
47
57
  first_column_width = int(total_width * self.column_proportion)
48
- self.setColumnWidth(0, first_column_width)
49
- for column in range(1, 4):
50
- self.setColumnWidth(column, (total_width - first_column_width) // (4 - 1))
58
+ remaining = max(total_width - first_column_width, 0)
59
+ if self.columnWidth(0) != first_column_width:
60
+ self.setColumnWidth(0, first_column_width)
61
+ if cols > 1:
62
+ other = remaining // (cols - 1)
63
+ for column in range(1, cols):
64
+ if self.columnWidth(column) != other:
65
+ self.setColumnWidth(column, other)
51
66
 
52
67
  def resizeEvent(self, event: QResizeEvent):
53
68
  super().resizeEvent(event)
54
- self.adjustColumnWidths()
69
+ w = event.size().width()
70
+ if w != self._last_width:
71
+ self._last_width = w
72
+ self.adjustColumnWidths()
55
73
 
56
74
  def mousePressEvent(self, event):
57
75
  """
@@ -59,7 +77,7 @@ class AttachmentList(BaseList):
59
77
 
60
78
  :param event: mouse event
61
79
  """
62
- if event.buttons() == Qt.LeftButton:
80
+ if event.button() == Qt.LeftButton:
63
81
  index = self.indexAt(event.pos())
64
82
  if index.isValid():
65
83
  mode = self.window.core.config.get('mode')
@@ -89,58 +107,61 @@ class AttachmentList(BaseList):
89
107
 
90
108
  :param event: context menu event
91
109
  """
92
-
93
- def ignore_trigger(func, arg, *args, **kwargs):
94
- func(arg)
95
-
96
110
  mode = self.window.core.config.get('mode')
97
111
  item = self.indexAt(event.pos())
98
112
  idx = item.row()
99
- preview_actions = []
100
- path = None
101
- attachment = None
113
+ if idx < 0:
114
+ return
102
115
 
103
- if idx >= 0:
104
- attachment = self.window.controller.attachment.get_by_idx(mode, idx)
105
- if attachment:
106
- path = attachment.path
107
- if self.window.core.filesystem.actions.has_preview(path):
108
- preview_actions = self.window.core.filesystem.actions.get_preview(self, path)
116
+ attachment = self.window.controller.attachment.get_by_idx(mode, idx)
117
+ path = attachment.path if attachment else None
118
+
119
+ menu = QMenu(self)
109
120
 
110
121
  actions = {}
111
- actions['open'] = QAction(QIcon(":/icons/view.svg"), trans('action.open'), self)
112
- actions['open'].triggered.connect(partial(ignore_trigger, self.action_open, event))
122
+ actions['open'] = QAction(self._ICON_VIEW, trans('action.open'), menu)
123
+ actions['open'].triggered.connect(partial(self._action_open_idx, idx))
113
124
 
114
- actions['open_dir'] = QAction(QIcon(":/icons/folder_filled.svg"), trans('action.open_dir'), self)
115
- actions['open_dir'].triggered.connect(partial(ignore_trigger, self.action_open_dir, event))
125
+ actions['open_dir'] = QAction(self._ICON_FOLDER, trans('action.open_dir'), menu)
126
+ actions['open_dir'].triggered.connect(partial(self._action_open_dir_idx, idx))
116
127
 
117
- actions['rename'] = QAction(QIcon(":/icons/edit.svg"), trans('action.rename'), self)
118
- actions['rename'].triggered.connect(partial(ignore_trigger, self.action_rename, event))
128
+ actions['rename'] = QAction(self._ICON_EDIT, trans('action.rename'), menu)
129
+ actions['rename'].triggered.connect(partial(self._action_rename_idx, idx))
119
130
 
120
- actions['delete'] = QAction(QIcon(":/icons/delete.svg"), trans('action.delete'), self)
121
- actions['delete'].triggered.connect(partial(ignore_trigger, self.action_delete, event))
131
+ actions['delete'] = QAction(self._ICON_DELETE, trans('action.delete'), menu)
132
+ actions['delete'].triggered.connect(partial(self._action_delete_idx, idx))
122
133
 
123
- menu = QMenu(self)
124
134
  if attachment and attachment.type == AttachmentItem.TYPE_FILE:
125
- if idx >= 0 and preview_actions:
126
- for preview_action in preview_actions:
135
+ fs_actions = self.window.core.filesystem.actions
136
+ if fs_actions.has_preview(path):
137
+ preview_actions = fs_actions.get_preview(self, path)
138
+ for preview_action in preview_actions or []:
139
+ try:
140
+ preview_action.setParent(menu)
141
+ except Exception:
142
+ pass
127
143
  menu.addAction(preview_action)
128
144
 
129
145
  menu.addAction(actions['open'])
130
146
  menu.addAction(actions['open_dir'])
131
- if idx >= 0 and self.window.core.filesystem.actions.has_use(path):
132
- use_actions = self.window.core.filesystem.actions.get_use(self, path)
133
- use_menu = QMenu(trans('action.use'), self)
134
- for use_action in use_actions:
147
+
148
+ if fs_actions.has_use(path):
149
+ use_actions = fs_actions.get_use(self, path)
150
+ use_menu = QMenu(trans('action.use'), menu)
151
+ for use_action in use_actions or []:
152
+ try:
153
+ use_action.setParent(use_menu)
154
+ except Exception:
155
+ pass
135
156
  use_menu.addAction(use_action)
136
157
  menu.addMenu(use_menu)
137
158
 
138
159
  menu.addAction(actions['rename'])
160
+
139
161
  menu.addAction(actions['delete'])
140
162
 
141
- if idx >= 0:
142
- self.window.controller.attachment.select(mode, item.row())
143
- menu.exec_(event.globalPos())
163
+ self.window.controller.attachment.select(mode, item.row())
164
+ menu.exec_(event.globalPos())
144
165
 
145
166
  def keyPressEvent(self, event):
146
167
  """
@@ -148,7 +169,7 @@ class AttachmentList(BaseList):
148
169
 
149
170
  :param event: Event
150
171
  """
151
- if event.key() == Qt.Key_V and QApplication.keyboardModifiers() == Qt.ControlModifier:
172
+ if event.key() == Qt.Key_V and (event.modifiers() & Qt.ControlModifier):
152
173
  self.handle_paste()
153
174
 
154
175
  def handle_paste(self):
@@ -159,13 +180,15 @@ class AttachmentList(BaseList):
159
180
  image = source.imageData()
160
181
  if isinstance(image, QImage):
161
182
  self.window.controller.attachment.from_clipboard_image(image)
162
- elif source.hasUrls():
183
+ return
184
+ if source.hasUrls():
163
185
  urls = source.urls()
164
186
  for url in urls:
165
187
  if url.isLocalFile():
166
188
  local_path = url.toLocalFile()
167
189
  self.window.controller.attachment.from_clipboard_url(local_path, all=True)
168
- elif source.hasText():
190
+ return
191
+ if source.hasText():
169
192
  text = source.text()
170
193
  self.window.controller.attachment.from_clipboard_text(text, all=True)
171
194
 
@@ -215,3 +238,22 @@ class AttachmentList(BaseList):
215
238
  idx = item.row()
216
239
  if idx >= 0:
217
240
  self.window.controller.attachment.delete(idx)
241
+
242
+ def _action_open_idx(self, idx, checked=False):
243
+ if idx >= 0:
244
+ mode = self.window.core.config.get('mode')
245
+ self.window.controller.attachment.open(mode, idx)
246
+
247
+ def _action_open_dir_idx(self, idx, checked=False):
248
+ if idx >= 0:
249
+ mode = self.window.core.config.get('mode')
250
+ self.window.controller.attachment.open_dir(mode, idx)
251
+
252
+ def _action_rename_idx(self, idx, checked=False):
253
+ if idx >= 0:
254
+ mode = self.window.core.config.get('mode')
255
+ self.window.controller.attachment.rename(mode, idx)
256
+
257
+ def _action_delete_idx(self, idx, checked=False):
258
+ if idx >= 0:
259
+ self.window.controller.attachment.delete(idx)
@@ -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: 2025.08.02 20:00:00 #
9
+ # Updated Date: 2025.08.15 03:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtWidgets import QHBoxLayout, QWidget, QComboBox
13
+ from PySide6.QtCore import QSignalBlocker
13
14
 
14
15
  from pygpt_net.ui.widget.option.combo import SeparatorComboBox, NoScrollCombo
15
16
 
@@ -32,8 +33,10 @@ class BaseListCombo(QWidget):
32
33
  self.combo.currentIndexChanged.connect(self.on_combo_change)
33
34
  self.initialized = False
34
35
  self.locked = False
36
+ self._data_index_map = {}
37
+ self._keys_cache = None
38
+ self._keys_cache_id = 0
35
39
 
36
- # add items
37
40
  self.update()
38
41
 
39
42
  self.layout = QHBoxLayout()
@@ -45,20 +48,53 @@ class BaseListCombo(QWidget):
45
48
 
46
49
  def update(self):
47
50
  """Prepare items"""
48
- self.combo.clear() # Clear combo before updating with new items
49
- if isinstance(self.keys, list):
50
- for item in self.keys:
51
- if isinstance(item, dict):
52
- for key, value in item.items():
53
- self.combo.addItem(value, key)
54
- else:
55
- self.combo.addItem(item, item)
56
- elif isinstance(self.keys, dict):
57
- for key, value in self.keys.items():
58
- if key.startswith("separator::"):
59
- self.combo.addSeparator(value)
60
- else:
61
- self.combo.addItem(value, key)
51
+ combo = self.combo
52
+ blocker = QSignalBlocker(combo)
53
+ combo.setUpdatesEnabled(False)
54
+ try:
55
+ combo.clear()
56
+ self._data_index_map = {}
57
+ idx = 0
58
+ keys = self.keys
59
+ add_item = combo.addItem
60
+ if isinstance(keys, list):
61
+ for item in keys:
62
+ if isinstance(item, dict):
63
+ for key, value in item.items():
64
+ add_item(value, key)
65
+ if key not in self._data_index_map:
66
+ self._data_index_map[key] = idx
67
+ idx += 1
68
+ else:
69
+ add_item(item, item)
70
+ if item not in self._data_index_map:
71
+ self._data_index_map[item] = idx
72
+ idx += 1
73
+ elif isinstance(keys, dict):
74
+ add_sep = combo.addSeparator
75
+ for key, value in keys.items():
76
+ if isinstance(key, str) and key.startswith("separator::"):
77
+ add_sep(value)
78
+ idx += 1
79
+ else:
80
+ add_item(value, key)
81
+ if key not in self._data_index_map:
82
+ self._data_index_map[key] = idx
83
+ idx += 1
84
+ cache = set()
85
+ if isinstance(keys, list):
86
+ for item in keys:
87
+ if isinstance(item, dict):
88
+ cache.update(item.keys())
89
+ else:
90
+ cache.add(item)
91
+ elif isinstance(keys, dict):
92
+ cache.update(keys.keys())
93
+ self._keys_cache = cache
94
+ self._keys_cache_id = id(keys)
95
+ finally:
96
+ combo.setUpdatesEnabled(True)
97
+ del blocker
62
98
 
63
99
  def set_value(self, value):
64
100
  """
@@ -67,10 +103,21 @@ class BaseListCombo(QWidget):
67
103
  :param value: value
68
104
  """
69
105
  self.locked = True
70
- index = self.combo.findData(value)
71
- if index != -1:
72
- self.combo.setCurrentIndex(index)
73
- self.locked = False
106
+ combo = self.combo
107
+ blocker = QSignalBlocker(combo)
108
+ try:
109
+ index = self._data_index_map.get(value, -1)
110
+ if index == -1:
111
+ index = combo.findData(value)
112
+ if index != -1 and value not in self._data_index_map:
113
+ self._data_index_map[value] = index
114
+ if index != -1:
115
+ if index != combo.currentIndex():
116
+ combo.setCurrentIndex(index)
117
+ self.current_id = value
118
+ finally:
119
+ del blocker
120
+ self.locked = False
74
121
 
75
122
  def get_value(self):
76
123
  """
@@ -87,17 +134,20 @@ class BaseListCombo(QWidget):
87
134
  :param name: key name
88
135
  :return:
89
136
  """
90
- if isinstance(self.keys, list):
91
- for key in self.keys:
92
- if isinstance(key, dict):
93
- if name in key:
94
- return True
95
- elif name == key:
96
- return True
97
- elif isinstance(self.keys, dict):
98
- if name in self.keys:
99
- return True
100
- return False
137
+ keys = self.keys
138
+ if self._keys_cache is None or self._keys_cache_id != id(keys):
139
+ cache = set()
140
+ if isinstance(keys, list):
141
+ for item in keys:
142
+ if isinstance(item, dict):
143
+ cache.update(item.keys())
144
+ else:
145
+ cache.add(item)
146
+ elif isinstance(keys, dict):
147
+ cache.update(keys.keys())
148
+ self._keys_cache = cache
149
+ self._keys_cache_id = id(keys)
150
+ return name in self._keys_cache
101
151
 
102
152
  def set_keys(self, keys):
103
153
  """
@@ -107,7 +157,9 @@ class BaseListCombo(QWidget):
107
157
  """
108
158
  self.locked = True
109
159
  self.keys = keys
110
- self.update() # Auto-clear handled in update
160
+ self._keys_cache = None
161
+ self._keys_cache_id = 0
162
+ self.update()
111
163
  self.locked = False
112
164
 
113
165
  def on_combo_change(self, index):
@@ -118,7 +170,7 @@ class BaseListCombo(QWidget):
118
170
  """
119
171
  if not self.initialized or self.locked:
120
172
  return
121
- self.current_id = self.combo.itemData(index)
173
+ self.current_id = self.combo.itemData(index) if index >= 0 else None
122
174
 
123
175
  def fit_to_content(self):
124
176
  """Fit to content"""