pygpt-net 2.6.55__py3-none-any.whl → 2.6.57__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 (218) hide show
  1. pygpt_net/CHANGELOG.txt +12 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +26 -22
  4. pygpt_net/config.py +44 -0
  5. pygpt_net/controller/audio/audio.py +0 -0
  6. pygpt_net/controller/calendar/calendar.py +0 -0
  7. pygpt_net/controller/calendar/note.py +0 -0
  8. pygpt_net/controller/chat/chat.py +0 -0
  9. pygpt_net/controller/chat/handler/openai_stream.py +2 -1
  10. pygpt_net/controller/chat/handler/worker.py +0 -0
  11. pygpt_net/controller/chat/remote_tools.py +5 -3
  12. pygpt_net/controller/chat/render.py +0 -0
  13. pygpt_net/controller/chat/text.py +0 -0
  14. pygpt_net/controller/ctx/common.py +0 -0
  15. pygpt_net/controller/debug/debug.py +26 -2
  16. pygpt_net/controller/debug/fixtures.py +1 -1
  17. pygpt_net/controller/dialogs/confirm.py +15 -1
  18. pygpt_net/controller/dialogs/debug.py +2 -0
  19. pygpt_net/controller/lang/mapping.py +0 -0
  20. pygpt_net/controller/launcher/launcher.py +0 -0
  21. pygpt_net/controller/mode/mode.py +0 -0
  22. pygpt_net/controller/presets/presets.py +0 -0
  23. pygpt_net/controller/realtime/realtime.py +0 -0
  24. pygpt_net/controller/theme/theme.py +0 -0
  25. pygpt_net/controller/ui/mode.py +5 -3
  26. pygpt_net/controller/ui/tabs.py +0 -0
  27. pygpt_net/core/agents/agents.py +3 -1
  28. pygpt_net/core/agents/custom.py +150 -0
  29. pygpt_net/core/agents/provider.py +0 -0
  30. pygpt_net/core/builder/__init__.py +12 -0
  31. pygpt_net/core/builder/graph.py +478 -0
  32. pygpt_net/core/calendar/calendar.py +0 -0
  33. pygpt_net/core/ctx/ctx.py +0 -0
  34. pygpt_net/core/ctx/output.py +0 -0
  35. pygpt_net/core/debug/agent.py +0 -0
  36. pygpt_net/core/debug/agent_builder.py +29 -0
  37. pygpt_net/core/debug/console/console.py +0 -0
  38. pygpt_net/core/debug/db.py +0 -0
  39. pygpt_net/core/debug/debug.py +0 -0
  40. pygpt_net/core/debug/events.py +0 -0
  41. pygpt_net/core/debug/indexes.py +0 -0
  42. pygpt_net/core/debug/kernel.py +0 -0
  43. pygpt_net/core/debug/tabs.py +0 -0
  44. pygpt_net/core/filesystem/filesystem.py +0 -0
  45. pygpt_net/core/fixtures/__init__ +0 -0
  46. pygpt_net/core/fixtures/stream/__init__.py +0 -0
  47. pygpt_net/core/fixtures/stream/generator.py +0 -0
  48. pygpt_net/core/models/models.py +2 -1
  49. pygpt_net/core/plugins/plugins.py +60 -0
  50. pygpt_net/core/render/plain/pid.py +0 -0
  51. pygpt_net/core/render/plain/renderer.py +26 -4
  52. pygpt_net/core/render/web/body.py +46 -4
  53. pygpt_net/core/render/web/debug.py +0 -0
  54. pygpt_net/core/render/web/helpers.py +0 -0
  55. pygpt_net/core/render/web/pid.py +0 -0
  56. pygpt_net/core/render/web/renderer.py +15 -20
  57. pygpt_net/core/tabs/tab.py +0 -0
  58. pygpt_net/core/tabs/tabs.py +0 -0
  59. pygpt_net/core/text/utils.py +0 -0
  60. pygpt_net/css.qrc +0 -0
  61. pygpt_net/css_rc.py +0 -0
  62. pygpt_net/data/config/config.json +8 -7
  63. pygpt_net/data/config/models.json +3 -3
  64. pygpt_net/data/config/settings.json +14 -0
  65. pygpt_net/data/css/web-blocks.css +9 -0
  66. pygpt_net/data/css/web-blocks.dark.css +6 -0
  67. pygpt_net/data/css/web-blocks.darkest.css +6 -0
  68. pygpt_net/data/css/web-chatgpt.css +14 -6
  69. pygpt_net/data/css/web-chatgpt.dark.css +6 -0
  70. pygpt_net/data/css/web-chatgpt.darkest.css +6 -0
  71. pygpt_net/data/css/web-chatgpt.light.css +6 -0
  72. pygpt_net/data/css/web-chatgpt_wide.css +14 -6
  73. pygpt_net/data/css/web-chatgpt_wide.dark.css +6 -0
  74. pygpt_net/data/css/web-chatgpt_wide.darkest.css +6 -0
  75. pygpt_net/data/css/web-chatgpt_wide.light.css +6 -0
  76. pygpt_net/data/fixtures/fake_stream.txt +14 -1
  77. pygpt_net/data/icons/case.svg +0 -0
  78. pygpt_net/data/icons/chat1.svg +0 -0
  79. pygpt_net/data/icons/chat2.svg +0 -0
  80. pygpt_net/data/icons/chat3.svg +0 -0
  81. pygpt_net/data/icons/chat4.svg +0 -0
  82. pygpt_net/data/icons/fit.svg +0 -0
  83. pygpt_net/data/icons/note1.svg +0 -0
  84. pygpt_net/data/icons/note2.svg +0 -0
  85. pygpt_net/data/icons/note3.svg +0 -0
  86. pygpt_net/data/icons/stt.svg +0 -0
  87. pygpt_net/data/icons/translate.svg +0 -0
  88. pygpt_net/data/icons/tts.svg +0 -0
  89. pygpt_net/data/icons/url.svg +0 -0
  90. pygpt_net/data/icons/vision.svg +0 -0
  91. pygpt_net/data/icons/web_off.svg +0 -0
  92. pygpt_net/data/icons/web_on.svg +0 -0
  93. pygpt_net/data/js/app/async.js +166 -0
  94. pygpt_net/data/js/app/bridge.js +88 -0
  95. pygpt_net/data/js/app/common.js +212 -0
  96. pygpt_net/data/js/app/config.js +223 -0
  97. pygpt_net/data/js/app/custom.js +961 -0
  98. pygpt_net/data/js/app/data.js +84 -0
  99. pygpt_net/data/js/app/dom.js +322 -0
  100. pygpt_net/data/js/app/events.js +400 -0
  101. pygpt_net/data/js/app/highlight.js +542 -0
  102. pygpt_net/data/js/app/logger.js +305 -0
  103. pygpt_net/data/js/app/markdown.js +1137 -0
  104. pygpt_net/data/js/app/math.js +167 -0
  105. pygpt_net/data/js/app/nodes.js +395 -0
  106. pygpt_net/data/js/app/queue.js +260 -0
  107. pygpt_net/data/js/app/raf.js +250 -0
  108. pygpt_net/data/js/app/runtime.js +582 -0
  109. pygpt_net/data/js/app/scroll.js +433 -0
  110. pygpt_net/data/js/app/stream.js +2708 -0
  111. pygpt_net/data/js/app/template.js +287 -0
  112. pygpt_net/data/js/app/tool.js +87 -0
  113. pygpt_net/data/js/app/ui.js +86 -0
  114. pygpt_net/data/js/app/user.js +380 -0
  115. pygpt_net/data/js/app/utils.js +64 -0
  116. pygpt_net/data/js/app.min.js +880 -0
  117. pygpt_net/data/js/markdown-it/markdown-it-katex.min.js +1 -1
  118. pygpt_net/data/js/markdown-it/markdown-it.min.js +0 -0
  119. pygpt_net/data/locale/locale.de.ini +3 -1
  120. pygpt_net/data/locale/locale.en.ini +8 -0
  121. pygpt_net/data/locale/locale.es.ini +2 -0
  122. pygpt_net/data/locale/locale.fr.ini +2 -0
  123. pygpt_net/data/locale/locale.it.ini +2 -0
  124. pygpt_net/data/locale/locale.pl.ini +3 -1
  125. pygpt_net/data/locale/locale.uk.ini +3 -1
  126. pygpt_net/data/locale/locale.zh.ini +2 -0
  127. pygpt_net/data/locale/plugin.osm.en.ini +24 -24
  128. pygpt_net/data/locale/plugin.wolfram.en.ini +9 -9
  129. pygpt_net/fonts.qrc +0 -0
  130. pygpt_net/fonts_rc.py +0 -0
  131. pygpt_net/icons.qrc +0 -0
  132. pygpt_net/icons_rc.py +0 -0
  133. pygpt_net/item/agent.py +62 -0
  134. pygpt_net/item/builder_layout.py +62 -0
  135. pygpt_net/js.qrc +24 -1
  136. pygpt_net/js_rc.py +51394 -33687
  137. pygpt_net/plugin/base/worker.py +0 -0
  138. pygpt_net/plugin/cmd_web/config.py +17 -17
  139. pygpt_net/plugin/cmd_web/worker.py +325 -171
  140. pygpt_net/plugin/mcp/__init__.py +0 -0
  141. pygpt_net/plugin/mcp/config.py +0 -0
  142. pygpt_net/plugin/mcp/plugin.py +0 -0
  143. pygpt_net/plugin/mcp/worker.py +0 -0
  144. pygpt_net/plugin/osm/__init__.py +0 -0
  145. pygpt_net/plugin/osm/config.py +0 -0
  146. pygpt_net/plugin/osm/plugin.py +0 -0
  147. pygpt_net/plugin/osm/worker.py +0 -0
  148. pygpt_net/plugin/wolfram/__init__.py +0 -0
  149. pygpt_net/plugin/wolfram/config.py +0 -0
  150. pygpt_net/plugin/wolfram/plugin.py +0 -0
  151. pygpt_net/plugin/wolfram/worker.py +0 -0
  152. pygpt_net/provider/api/anthropic/tools.py +0 -0
  153. pygpt_net/provider/api/google/__init__.py +0 -0
  154. pygpt_net/provider/api/google/video.py +0 -0
  155. pygpt_net/provider/api/openai/agents/experts.py +0 -0
  156. pygpt_net/provider/api/openai/agents/remote_tools.py +0 -0
  157. pygpt_net/provider/api/openai/remote_tools.py +0 -0
  158. pygpt_net/provider/api/openai/responses.py +0 -0
  159. pygpt_net/provider/api/x_ai/__init__.py +2 -0
  160. pygpt_net/provider/api/x_ai/remote.py +0 -0
  161. pygpt_net/provider/core/agent/__init__.py +10 -0
  162. pygpt_net/provider/core/agent/base.py +51 -0
  163. pygpt_net/provider/core/agent/json_file.py +200 -0
  164. pygpt_net/provider/core/config/patch.py +33 -0
  165. pygpt_net/provider/core/config/patches/__init__.py +0 -0
  166. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +1 -0
  167. pygpt_net/provider/core/ctx/db_sqlite/storage.py +0 -0
  168. pygpt_net/provider/core/model/patches/__init__.py +0 -0
  169. pygpt_net/provider/core/model/patches/patch_before_2_6_42.py +0 -0
  170. pygpt_net/provider/core/preset/patch.py +0 -0
  171. pygpt_net/provider/core/preset/patches/__init__.py +0 -0
  172. pygpt_net/provider/core/preset/patches/patch_before_2_6_42.py +0 -0
  173. pygpt_net/provider/llms/anthropic.py +4 -0
  174. pygpt_net/provider/llms/base.py +2 -0
  175. pygpt_net/provider/llms/deepseek_api.py +2 -0
  176. pygpt_net/provider/llms/google.py +2 -0
  177. pygpt_net/provider/llms/hugging_face_api.py +4 -0
  178. pygpt_net/provider/llms/hugging_face_embedding.py +0 -0
  179. pygpt_net/provider/llms/hugging_face_router.py +2 -0
  180. pygpt_net/provider/llms/local.py +0 -0
  181. pygpt_net/provider/llms/mistral.py +4 -0
  182. pygpt_net/provider/llms/open_router.py +0 -0
  183. pygpt_net/provider/llms/perplexity.py +2 -0
  184. pygpt_net/provider/llms/utils.py +0 -0
  185. pygpt_net/provider/llms/voyage.py +0 -0
  186. pygpt_net/provider/llms/x_ai.py +2 -0
  187. pygpt_net/tools/agent_builder/__init__.py +12 -0
  188. pygpt_net/tools/agent_builder/tool.py +292 -0
  189. pygpt_net/tools/agent_builder/ui/__init__.py +0 -0
  190. pygpt_net/tools/agent_builder/ui/dialogs.py +152 -0
  191. pygpt_net/tools/agent_builder/ui/list.py +228 -0
  192. pygpt_net/tools/code_interpreter/ui/html.py +0 -0
  193. pygpt_net/tools/code_interpreter/ui/widgets.py +0 -0
  194. pygpt_net/tools/html_canvas/tool.py +23 -6
  195. pygpt_net/tools/html_canvas/ui/widgets.py +224 -2
  196. pygpt_net/ui/layout/chat/chat.py +0 -0
  197. pygpt_net/ui/layout/chat/output.py +5 -5
  198. pygpt_net/ui/main.py +10 -9
  199. pygpt_net/ui/menu/debug.py +39 -1
  200. pygpt_net/ui/widget/builder/__init__.py +12 -0
  201. pygpt_net/ui/widget/builder/editor.py +2001 -0
  202. pygpt_net/ui/widget/dialog/base.py +4 -1
  203. pygpt_net/ui/widget/draw/painter.py +0 -0
  204. pygpt_net/ui/widget/element/labels.py +9 -4
  205. pygpt_net/ui/widget/lists/db.py +0 -0
  206. pygpt_net/ui/widget/lists/debug.py +0 -0
  207. pygpt_net/ui/widget/tabs/body.py +0 -0
  208. pygpt_net/ui/widget/textarea/html.py +1 -0
  209. pygpt_net/ui/widget/textarea/input.py +28 -10
  210. pygpt_net/ui/widget/textarea/output.py +21 -1
  211. pygpt_net/ui/widget/textarea/web.py +31 -3
  212. pygpt_net/utils.py +40 -0
  213. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/METADATA +16 -2
  214. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/RECORD +116 -77
  215. pygpt_net/data/js/app.js +0 -5869
  216. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/LICENSE +0 -0
  217. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/WHEEL +0 -0
  218. {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,400 @@
1
+ // ==========================================================================
2
+ // Event manager
3
+ // ==========================================================================
4
+
5
+ class EventManager {
6
+
7
+ // Initializes the event manager.
8
+ constructor(cfg, dom, scrollMgr, highlighter, codeScroll, toolOutput, bridge) {
9
+ this.cfg = cfg;
10
+ this.dom = dom;
11
+ this.scrollMgr = scrollMgr;
12
+ this.highlighter = highlighter;
13
+ this.codeScroll = codeScroll;
14
+ this.toolOutput = toolOutput;
15
+ this.bridge = bridge;
16
+ this.handlers = {
17
+ wheel: null,
18
+ scroll: null,
19
+ resize: null,
20
+ fabClick: null,
21
+ mouseover: null,
22
+ mouseout: null,
23
+ click: null,
24
+ keydown: null,
25
+ docClickFocus: null,
26
+ visibility: null,
27
+ focus: null,
28
+ pageshow: null
29
+ };
30
+ }
31
+
32
+ // Finds the closest code wrapper element.
33
+ _findWrapper(target) {
34
+ if (!target || typeof target.closest !== 'function') return null;
35
+ return target.closest('.code-wrapper');
36
+ }
37
+
38
+ // Gets the code element inside a wrapper.
39
+ _getCodeEl(wrapper) {
40
+ if (!wrapper) return null;
41
+ return wrapper.querySelector('pre > code');
42
+ }
43
+
44
+ // Collects the text content from a code element.
45
+ _collectCodeText(codeEl) {
46
+ if (!codeEl) return '';
47
+ const frozen = codeEl.querySelector('.hl-frozen');
48
+ const tail = codeEl.querySelector('.hl-tail');
49
+ if (frozen || tail) return (frozen?.textContent || '') + (tail?.textContent || '');
50
+ return codeEl.textContent || '';
51
+ }
52
+
53
+ // Collect plain text from a user message, ignoring helper UI (ellipsis/toggle/icon).
54
+ _collectUserText(msgBox) {
55
+ if (!msgBox) return '';
56
+ const msg = msgBox.querySelector('.msg');
57
+ if (!msg) return '';
58
+ // Prefer the content container if present.
59
+ const root = msg.querySelector('.uc-content') || msg;
60
+
61
+ let out = '';
62
+ const walker = document.createTreeWalker(
63
+ root,
64
+ NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
65
+ {
66
+ acceptNode: (node) => {
67
+ if (node.nodeType === Node.ELEMENT_NODE) {
68
+ const el = node;
69
+ // Skip helper UI and prune subtree.
70
+ if (el.matches('.uc-ellipsis,[data-copy-ignore="1"],.msg-copy-btn,.uc-toggle')) {
71
+ return NodeFilter.FILTER_REJECT;
72
+ }
73
+ // Convert <br> into newlines.
74
+ if (el.tagName === 'BR') {
75
+ out += '\n';
76
+ return NodeFilter.FILTER_SKIP;
77
+ }
78
+ }
79
+ if (node.nodeType === Node.TEXT_NODE) {
80
+ return NodeFilter.FILTER_ACCEPT;
81
+ }
82
+ return NodeFilter.FILTER_SKIP;
83
+ }
84
+ },
85
+ false
86
+ );
87
+
88
+ let n;
89
+ while ((n = walker.nextNode())) {
90
+ out += n.nodeValue;
91
+ }
92
+ return String(out || '').replace(/\r\n?/g, '\n');
93
+ }
94
+
95
+ // Copy to clipboard via bridge if available, otherwise use browser APIs.
96
+ async _copyTextRobust(text) {
97
+ try {
98
+ if (this.bridge && typeof this.bridge.copyCode === 'function') {
99
+ this.bridge.copyCode(text);
100
+ return true;
101
+ }
102
+ } catch (_) {}
103
+ try {
104
+ if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
105
+ await navigator.clipboard.writeText(text);
106
+ return true;
107
+ }
108
+ } catch (_) {}
109
+ try {
110
+ const ta = document.createElement('textarea');
111
+ ta.value = text;
112
+ ta.setAttribute('readonly', '');
113
+ ta.style.position = 'fixed';
114
+ ta.style.top = '-9999px';
115
+ ta.style.opacity = '0';
116
+ document.body.appendChild(ta);
117
+ ta.select();
118
+ const ok = document.execCommand && document.execCommand('copy');
119
+ document.body.removeChild(ta);
120
+ return !!ok;
121
+ } catch (_) {
122
+ return false;
123
+ }
124
+ }
125
+
126
+ // Flash "Copied" feedback on the copy button.
127
+ _flashCopied(btn, wrapper) {
128
+ if (!btn) return;
129
+ const DUR = 1200;
130
+
131
+ // Try to find an icon to swap (works for both code-header and msg copy buttons)
132
+ const img = btn.querySelector('img.copy-img') || btn.querySelector('img.action-img') || btn.querySelector('img');
133
+
134
+ // Clear pending timers to avoid races on rapid clicks
135
+ try { if (btn.__copyTimer) { clearTimeout(btn.__copyTimer); btn.__copyTimer = 0; } } catch (_) {}
136
+ try { if (btn.__iconTimer) { clearTimeout(btn.__iconTimer); btn.__iconTimer = 0; } } catch (_) {}
137
+
138
+ // Icon swap to success (window.ICON_DONE), then restore
139
+ if (typeof window !== 'undefined' && window.ICON_DONE && img) {
140
+ // Cache original icon once
141
+ if (!btn.__origIconSrc) {
142
+ try { btn.__origIconSrc = img.getAttribute('src') || ''; } catch (_) { btn.__origIconSrc = ''; }
143
+ }
144
+ try { img.setAttribute('src', String(window.ICON_DONE)); } catch (_) {}
145
+ btn.__iconTimer = setTimeout(() => {
146
+ try {
147
+ const orig = btn.__origIconSrc || '';
148
+ if (orig) img.setAttribute('src', orig);
149
+ } catch (_) {}
150
+ btn.__iconTimer = 0;
151
+ }, DUR);
152
+ }
153
+
154
+ const span = btn.querySelector('span');
155
+ // Icon-only fallback (no label)
156
+ if (!span) {
157
+ btn.classList.add('copied');
158
+ btn.__copyTimer = setTimeout(() => {
159
+ try { btn.classList.remove('copied'); } catch (_) {}
160
+ btn.__copyTimer = 0;
161
+ }, DUR);
162
+ return;
163
+ }
164
+
165
+ const L_COPY = (wrapper && wrapper.getAttribute('data-locale-copy')) || 'Copy';
166
+ const L_COPIED = (wrapper && wrapper.getAttribute('data-locale-copied')) || 'Copied';
167
+ span.textContent = L_COPIED;
168
+ btn.classList.add('copied');
169
+ btn.__copyTimer = setTimeout(() => {
170
+ try {
171
+ span.textContent = L_COPY;
172
+ btn.classList.remove('copied');
173
+ } catch (_) {}
174
+ btn.__copyTimer = 0;
175
+ }, DUR);
176
+ }
177
+
178
+ // Toggle code collapse/expand and remember collapsed indices.
179
+ _toggleCollapse(wrapper) {
180
+ if (!wrapper) return;
181
+ const codeEl = this._getCodeEl(wrapper);
182
+ if (!codeEl) return;
183
+ const btn = wrapper.querySelector('.code-header-collapse');
184
+ const span = btn ? btn.querySelector('span') : null;
185
+ const L_COLLAPSE = wrapper.getAttribute('data-locale-collapse') || 'Collapse';
186
+ const L_EXPAND = wrapper.getAttribute('data-locale-expand') || 'Expand';
187
+ const idx = String(wrapper.getAttribute('data-index') || '');
188
+ const arr = window.__collapsed_idx || (window.__collapsed_idx = []);
189
+ const isHidden = (codeEl.style.display === 'none');
190
+
191
+ if (isHidden) {
192
+ codeEl.style.display = 'block';
193
+ if (span) span.textContent = L_COLLAPSE;
194
+ const p = arr.indexOf(idx);
195
+ if (p !== -1) arr.splice(p, 1);
196
+ if (btn) btn.setAttribute('title', L_COLLAPSE);
197
+ } else {
198
+ codeEl.style.display = 'none';
199
+ if (span) span.textContent = L_EXPAND;
200
+ if (!arr.includes(idx)) arr.push(idx);
201
+ if (btn) btn.setAttribute('title', L_EXPAND);
202
+ }
203
+
204
+ // Click feedback (same "pop" effect as input)
205
+ if (btn) {
206
+ try { if (btn.__popTimer) { clearTimeout(btn.__popTimer); btn.__popTimer = 0; } } catch (_) {}
207
+ btn.classList.add('copied');
208
+ btn.__popTimer = setTimeout(() => {
209
+ try { btn.classList.remove('copied'); } catch (_) {}
210
+ btn.__popTimer = 0;
211
+ }, 1200);
212
+ }
213
+ }
214
+
215
+ // Attach global UI event handlers and container-level interactions.
216
+ install() {
217
+ try {
218
+ history.scrollRestoration = "manual";
219
+ } catch (_) {}
220
+
221
+ this.handlers.keydown = (event) => {
222
+ if (event.ctrlKey && event.key === 'f') {
223
+ window.location.href = 'bridge://open_find:' + runtime.cfg.PID;
224
+ event.preventDefault();
225
+ }
226
+ if (event.key === 'Escape') {
227
+ window.location.href = 'bridge://escape';
228
+ event.preventDefault();
229
+ }
230
+ };
231
+ document.addEventListener('keydown', this.handlers.keydown, { passive: false });
232
+
233
+ // Removed global click-to-focus navigation and visibility/focus wakeups to keep the pump rAF-only and click-agnostic.
234
+
235
+ const container = this.dom.get('container');
236
+ const inputArea = this.dom.get('_append_input_');
237
+
238
+ const addClassToMsg = (id, className) => {
239
+ const el = document.getElementById('msg-bot-' + id);
240
+ if (el) el.classList.add(className);
241
+ };
242
+ const removeClassFromMsg = (id, className) => {
243
+ const el = document.getElementById('msg-bot-' + id);
244
+ if (el) el.classList.remove(className);
245
+ };
246
+
247
+ this.handlers.mouseover = (event) => {
248
+ if (event.target.classList.contains('action-img')) {
249
+ const id = event.target.getAttribute('data-id');
250
+ addClassToMsg(id, 'msg-highlight');
251
+ }
252
+ };
253
+ this.handlers.mouseout = (event) => {
254
+ if (event.target.classList.contains('action-img')) {
255
+ const id = event.target.getAttribute('data-id');
256
+ const el = document.getElementById('msg-bot-' + id);
257
+ if (el) el.classList.remove('msg-highlight');
258
+ }
259
+ };
260
+ if (container) {
261
+ container.addEventListener('mouseover', this.handlers.mouseover, { passive: true });
262
+ container.addEventListener('mouseout', this.handlers.mouseout, { passive: true });
263
+ }
264
+
265
+ this.handlers.click = async (ev) => {
266
+ // Code block header actions
267
+ const aCode = ev.target && (ev.target.closest ? ev.target.closest('a.code-header-action') : null) || null;
268
+ // User message copy action
269
+ const aUserCopy = ev.target && (ev.target.closest ? ev.target.closest('a.msg-copy-btn') : null) || null;
270
+
271
+ if (!aCode && !aUserCopy) return;
272
+
273
+ ev.preventDefault();
274
+ ev.stopPropagation();
275
+
276
+ // Handle code header actions first (unchanged behavior)
277
+ if (aCode) {
278
+ const wrapper = this._findWrapper(aCode);
279
+ if (!wrapper) return;
280
+
281
+ const isCopy = aCode.classList.contains('code-header-copy');
282
+ const isCollapse = aCode.classList.contains('code-header-collapse');
283
+ const isRun = aCode.classList.contains('code-header-run');
284
+ const isPreview = aCode.classList.contains('code-header-preview');
285
+
286
+ let codeEl = null, text = '';
287
+ if (isCopy || isRun || isPreview) {
288
+ codeEl = this._getCodeEl(wrapper);
289
+ text = this._collectCodeText(codeEl);
290
+ }
291
+
292
+ try {
293
+ if (isCopy) {
294
+ const ok = await this._copyTextRobust(text);
295
+ if (ok) this._flashCopied(aCode, wrapper);
296
+ } else if (isCollapse) {
297
+ this._toggleCollapse(wrapper);
298
+ } else if (isRun) {
299
+ if (this.bridge && typeof this.bridge.runCode === 'function') this.bridge.runCode(text);
300
+ } else if (isPreview) {
301
+ if (this.bridge && typeof this.bridge.previewCode === 'function') this.bridge.previewCode(text);
302
+ }
303
+ } catch (_) {
304
+ /* swallow */
305
+ }
306
+ return;
307
+ }
308
+
309
+ // Handle user message copy button (icon-only)
310
+ if (aUserCopy) {
311
+ try {
312
+ const msgBox = aUserCopy.closest('.msg-box.msg-user');
313
+ const text = this._collectUserText(msgBox);
314
+ const ok = await this._copyTextRobust(text);
315
+
316
+ // Localized tooltip swap and visual feedback
317
+ const L = (this.cfg && this.cfg.LOCALE) || {};
318
+ const L_COPY = L.COPY || 'Copy';
319
+ const L_COPIED = L.COPIED || 'Copied';
320
+
321
+ // Visual feedback only (no tooltip) + temporary icon swap handled centrally
322
+ if (ok) {
323
+ this._flashCopied(aUserCopy, null);
324
+ }
325
+ } catch (_) {
326
+ /* swallow */
327
+ }
328
+ }
329
+ };
330
+
331
+ // Delegate clicks from both main container and input area to support copy icons everywhere.
332
+ if (container) container.addEventListener('click', this.handlers.click, { passive: false });
333
+ if (inputArea) inputArea.addEventListener('click', this.handlers.click, { passive: false });
334
+
335
+ this.handlers.wheel = (ev) => {
336
+ runtime.scrollMgr.userInteracted = true;
337
+ if (ev.deltaY < 0) runtime.scrollMgr.autoFollow = false;
338
+ else runtime.scrollMgr.maybeEnableAutoFollowByProximity();
339
+ this.highlighter.scheduleScanVisibleCodes(runtime.stream.activeCode);
340
+ };
341
+ document.addEventListener('wheel', this.handlers.wheel, { passive: true });
342
+
343
+ this.handlers.scroll = () => {
344
+ const el = Utils.SE;
345
+ const top = el.scrollTop;
346
+ if (top + 1 < runtime.scrollMgr.lastScrollTop) runtime.scrollMgr.autoFollow = false;
347
+ runtime.scrollMgr.maybeEnableAutoFollowByProximity();
348
+ runtime.scrollMgr.lastScrollTop = top;
349
+ const action = runtime.scrollMgr.computeFabAction();
350
+ if (action !== runtime.scrollMgr.currentFabAction) runtime.scrollMgr.updateScrollFab(false, action, true);
351
+ this.highlighter.scheduleScanVisibleCodes(runtime.stream.activeCode);
352
+ };
353
+ window.addEventListener('scroll', this.handlers.scroll, { passive: true });
354
+
355
+ const fab = this.dom.get('scrollFab');
356
+ if (fab) {
357
+ this.handlers.fabClick = (ev) => {
358
+ ev.preventDefault();
359
+ ev.stopPropagation();
360
+ const action = runtime.scrollMgr.computeFabAction();
361
+ if (action === 'up') runtime.scrollMgr.scrollToTopUser();
362
+ else if (action === 'down') runtime.scrollMgr.scrollToBottomUser();
363
+ runtime.scrollMgr.fabFreezeUntil = Utils.now() + this.cfg.FAB.TOGGLE_DEBOUNCE_MS;
364
+ runtime.scrollMgr.updateScrollFab(true);
365
+ };
366
+ fab.addEventListener('click', this.handlers.fabClick, { passive: false });
367
+ }
368
+
369
+ this.handlers.resize = () => {
370
+ runtime.scrollMgr.maybeEnableAutoFollowByProximity();
371
+ runtime.scrollMgr.scheduleScrollFabUpdate();
372
+ this.highlighter.scheduleScanVisibleCodes(runtime.stream.activeCode);
373
+ };
374
+ window.addEventListener('resize', this.handlers.resize, { passive: true });
375
+
376
+ // Note: visibility/focus/pageshow kickers removed intentionally.
377
+ }
378
+
379
+ // Detach all installed handlers and reset local refs.
380
+ cleanup() {
381
+ const container = this.dom.get('container');
382
+ const inputArea = this.dom.get('_append_input_');
383
+
384
+ if (this.handlers.wheel) document.removeEventListener('wheel', this.handlers.wheel);
385
+ if (this.handlers.scroll) window.removeEventListener('scroll', this.handlers.scroll);
386
+ if (this.handlers.resize) window.removeEventListener('resize', this.handlers.resize);
387
+ const fab = this.dom.get('scrollFab');
388
+ if (fab && this.handlers.fabClick) fab.removeEventListener('click', this.handlers.fabClick);
389
+ if (container && this.handlers.mouseover) container.removeEventListener('mouseover', this.handlers.mouseover);
390
+ if (container && this.handlers.mouseout) container.removeEventListener('mouseout', this.handlers.mouseout);
391
+ if (container && this.handlers.click) container.removeEventListener('click', this.handlers.click);
392
+ if (inputArea && this.handlers.click) inputArea.removeEventListener('click', this.handlers.click);
393
+ if (this.handlers.keydown) document.removeEventListener('keydown', this.handlers.keydown);
394
+ if (this.handlers.docClickFocus) document.removeEventListener('click', this.handlers.docClickFocus);
395
+ if (this.handlers.visibility) document.removeEventListener('visibilitychange', this.handlers.visibility);
396
+ if (this.handlers.focus) window.removeEventListener('focus', this.handlers.focus);
397
+ if (this.handlers.pageshow) window.removeEventListener('pageshow', this.handlers.pageshow);
398
+ this.handlers = {};
399
+ }
400
+ }