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,582 @@
1
+ // ==========================================================================
2
+ // Runtime
3
+ // ==========================================================================
4
+
5
+ class Runtime {
6
+
7
+ // Main runtime manager for the application.
8
+ constructor() {
9
+ this.cfg = new Config();
10
+ this.logger = new Logger(this.cfg);
11
+
12
+ this.dom = new DOMRefs();
13
+ this.customMarkup = new CustomMarkup(this.cfg, this.logger);
14
+ this.raf = new RafManager(this.cfg);
15
+
16
+ // Ensure logger uses central RafManager for its internal tick pump.
17
+ try {
18
+ this.logger.bindRaf(this.raf);
19
+ } catch (_) {}
20
+
21
+ this.async = new AsyncRunner(this.cfg, this.raf);
22
+ this.renderer = new MarkdownRenderer(this.cfg, this.customMarkup, this.logger, this.async, this.raf);
23
+
24
+ this.math = new MathRenderer(this.cfg, this.raf, this.async);
25
+ this.codeScroll = new CodeScrollState(this.cfg, this.raf);
26
+ this.highlighter = new Highlighter(this.cfg, this.codeScroll, this.raf);
27
+ this.scrollMgr = new ScrollManager(this.cfg, this.dom, this.raf);
28
+ this.toolOutput = new ToolOutput();
29
+ this.loading = new Loading(this.dom);
30
+ this.nodes = new NodesManager(this.dom, this.renderer, this.highlighter, this.math);
31
+ this.bridge = new BridgeManager(this.cfg, this.logger);
32
+ this.ui = new UIManager();
33
+ this.stream = new StreamEngine(this.cfg, this.dom, this.renderer, this.math, this.highlighter, this.codeScroll, this.scrollMgr, this.raf, this.async, this.logger);
34
+ this.streamQ = new StreamQueue(this.cfg, this.stream, this.scrollMgr, this.raf);
35
+ this.events = new EventManager(this.cfg, this.dom, this.scrollMgr, this.highlighter, this.codeScroll, this.toolOutput, this.bridge);
36
+
37
+ try {
38
+ this.stream.setCustomFenceSpecs(this.customMarkup.getSourceFenceSpecs());
39
+ } catch (_) {}
40
+
41
+ this.templates = new NodeTemplateEngine(this.cfg, this.logger);
42
+ this.data = new DataReceiver(this.cfg, this.templates, this.nodes, this.scrollMgr);
43
+
44
+ this.tips = null;
45
+ this._lastHeavyResetMs = 0;
46
+
47
+ this.renderer.hooks.observeNewCode = (root, opts) => this.highlighter.observeNewCode(root, opts, this.stream.activeCode);
48
+ this.renderer.hooks.observeMsgBoxes = (root) => this.highlighter.observeMsgBoxes(root, (box) => {
49
+ this.highlighter.observeNewCode(box, {
50
+ deferLastIfStreaming: true,
51
+ minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
52
+ minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
53
+ }, this.stream.activeCode);
54
+ this.codeScroll.initScrollableBlocks(box);
55
+ });
56
+ this.renderer.hooks.scheduleMathRender = (root) => {
57
+ const mm = getMathMode();
58
+ if (mm === 'idle') this.math.schedule(root);
59
+ else if (mm === 'always') this.math.schedule(root, 0, true);
60
+ };
61
+ this.renderer.hooks.codeScrollInit = (root) => this.codeScroll.initScrollableBlocks(root);
62
+ }
63
+
64
+ // Reset stream state and optionally perform a heavy reset of schedulers and observers.
65
+ resetStreamState(origin, opts) {
66
+ try {
67
+ this.streamQ.clear();
68
+ } catch (_) {}
69
+
70
+ const def = Object.assign({
71
+ finalizeActive: true,
72
+ clearBuffer: true,
73
+ clearMsg: false,
74
+ defuseOrphans: true,
75
+ forceHeavy: false,
76
+ reason: String(origin || 'external-op')
77
+ }, (opts || {}));
78
+
79
+ const now = Utils.now();
80
+ const withinDebounce = (now - (this._lastHeavyResetMs || 0)) <= (this.cfg.RESET.HEAVY_DEBOUNCE_MS || 24);
81
+ const mustHeavyByOrigin =
82
+ def.forceHeavy === true || def.clearMsg === true ||
83
+ origin === 'beginStream' || origin === 'nextStream' ||
84
+ origin === 'clearStream' || origin === 'replaceNodes' ||
85
+ origin === 'clearNodes' || origin === 'clearOutput' ||
86
+ origin === 'clearLive' || origin === 'clearInput';
87
+ const shouldHeavy = mustHeavyByOrigin || !withinDebounce;
88
+ const suppressLog = withinDebounce && origin !== 'beginStream';
89
+
90
+ try {
91
+ this.stream.abortAndReset({
92
+ ...def,
93
+ suppressLog
94
+ });
95
+ } catch (_) {}
96
+
97
+ if (shouldHeavy) {
98
+ try {
99
+ this.highlighter.cleanup();
100
+ } catch (_) {}
101
+ try {
102
+ this.math.cleanup();
103
+ } catch (_) {}
104
+ try {
105
+ this.codeScroll.cancelAllScrolls();
106
+ } catch (_) {}
107
+ try {
108
+ this.scrollMgr.cancelPendingScroll();
109
+ } catch (_) {}
110
+ try {
111
+ this.raf.cancelAll();
112
+ } catch (_) {}
113
+ this._lastHeavyResetMs = now;
114
+ } else {
115
+ try {
116
+ this.raf.cancelGroup('StreamQueue');
117
+ } catch (_) {}
118
+ }
119
+
120
+ try {
121
+ this.tips && this.tips.hide();
122
+ } catch (_) {}
123
+ }
124
+
125
+ // API: handle incoming chunk (from bridge).
126
+ api_onChunk = (name, chunk, type) => {
127
+ const t = String(type || 'text_delta');
128
+ if (t === 'text_delta') {
129
+ this.api_appendStream(name, chunk);
130
+ return;
131
+ }
132
+ // Future-proof: add other chunk types here (attachments, status, etc.)
133
+ // No-op for unknown types to keep current behavior.
134
+ this.logger.debug('STREAM', 'IGNORED_NON_TEXT_CHUNK', {
135
+ type: t,
136
+ len: (chunk ? String(chunk).length : 0)
137
+ });
138
+ };
139
+
140
+ // API: begin stream.
141
+ api_beginStream = (chunk = false) => {
142
+ this.tips && this.tips.hide();
143
+ this.resetStreamState('beginStream', {
144
+ clearMsg: true,
145
+ finalizeActive: false,
146
+ forceHeavy: true
147
+ });
148
+ this.stream.beginStream(chunk);
149
+ };
150
+
151
+ // API: end stream.
152
+ api_endStream = () => {
153
+ this.stream.endStream();
154
+ };
155
+
156
+ // API: apply chunk.
157
+ api_applyStream = (name, chunk) => {
158
+ this.stream.applyStream(name, chunk);
159
+ };
160
+
161
+ // API: enqueue chunk (drained on rAF).
162
+ api_appendStream = (name, chunk) => {
163
+ this.streamQ.enqueue(name, chunk);
164
+ };
165
+
166
+ // API: move current output to "before" area and prepare for next stream.
167
+ api_nextStream = () => {
168
+ this.tips && this.tips.hide();
169
+ const element = this.dom.get('_append_output_');
170
+ const before = this.dom.get('_append_output_before_');
171
+ if (element && before) {
172
+ const frag = document.createDocumentFragment();
173
+ while (element.firstChild) frag.appendChild(element.firstChild);
174
+ before.appendChild(frag);
175
+ }
176
+ this.resetStreamState('nextStream', {
177
+ clearMsg: true,
178
+ finalizeActive: false,
179
+ forceHeavy: true
180
+ });
181
+ this.scrollMgr.scheduleScroll();
182
+ };
183
+
184
+ // API: clear streaming output area entirely.
185
+ api_clearStream = () => {
186
+ this.tips && this.tips.hide();
187
+ this.resetStreamState('clearStream', {
188
+ clearMsg: true,
189
+ forceHeavy: true
190
+ });
191
+ const el = this.dom.getStreamContainer();
192
+ if (!el) return;
193
+ el.replaceChildren();
194
+ };
195
+
196
+ // API: append/replace messages (non-streaming).
197
+ api_appendNode = (payload) => {
198
+ this.resetStreamState('appendNode');
199
+ this.data.append(payload);
200
+ };
201
+
202
+ api_replaceNodes = (payload) => {
203
+ this.resetStreamState('replaceNodes', {
204
+ clearMsg: true,
205
+ forceHeavy: true
206
+ });
207
+ this.dom.clearNodes();
208
+ this.data.replace(payload);
209
+ };
210
+
211
+ // API: append to input area.
212
+ api_appendToInput = (payload) => {
213
+ this.nodes.appendToInput(payload);
214
+
215
+ // Ensure initial auto-follow is ON for the next stream that will start right after user input.
216
+ // Rationale: previously, if the user had scrolled up, autoFollow could remain false and the
217
+ // live stream would not follow even though we just sent a new input.
218
+ this.scrollMgr.autoFollow = true; // explicitly re-enable page auto-follow
219
+ this.scrollMgr.userInteracted = false; // Reset interaction so live scroll is allowed
220
+
221
+ // Keep lastScrollTop in sync to avoid misclassification in the next onscroll handler.
222
+ try {
223
+ this.scrollMgr.lastScrollTop = Utils.SE.scrollTop | 0;
224
+ } catch (_) {}
225
+
226
+ // Non-live scroll to bottom right away, independent of autoFollow state.
227
+ this.scrollMgr.scheduleScroll();
228
+ // NOTE: No resetStreamState() here to avoid flicker/reflow issues while previewing user input.
229
+ };
230
+
231
+ // API: clear messages list.
232
+ api_clearNodes = () => {
233
+ this.dom.clearNodes();
234
+ this.resetStreamState('clearNodes', {
235
+ clearMsg: true,
236
+ forceHeavy: true
237
+ });
238
+ };
239
+
240
+ // API: clear input area.
241
+ api_clearInput = () => {
242
+ this.resetStreamState('clearInput', {
243
+ forceHeavy: true
244
+ });
245
+ this.dom.clearInput();
246
+ };
247
+
248
+ // API: clear output area.
249
+ api_clearOutput = () => {
250
+ this.dom.clearOutput();
251
+ this.resetStreamState('clearOutput', {
252
+ clearMsg: true,
253
+ forceHeavy: true
254
+ });
255
+ };
256
+
257
+ // API: clear live area.
258
+ api_clearLive = () => {
259
+ this.dom.clearLive();
260
+ this.resetStreamState('clearLive', {
261
+ forceHeavy: true
262
+ });
263
+ };
264
+
265
+ // API: tool output helpers.
266
+ api_appendToolOutput = (c) => this.toolOutput.append(c);
267
+ api_updateToolOutput = (c) => this.toolOutput.update(c);
268
+ api_clearToolOutput = () => this.toolOutput.clear();
269
+ api_beginToolOutput = () => this.toolOutput.begin();
270
+ api_endToolOutput = () => this.toolOutput.end();
271
+ api_enableToolOutput = () => this.toolOutput.enable();
272
+ api_disableToolOutput = () => this.toolOutput.disable();
273
+ api_toggleToolOutput = (id) => this.toolOutput.toggle(id);
274
+
275
+ // API: append extra content to a bot message.
276
+ api_appendExtra = (id, c) => this.nodes.appendExtra(id, c, this.scrollMgr);
277
+
278
+ // API: remove one message by id.
279
+ api_removeNode = (id) => this.nodes.removeNode(id, this.scrollMgr);
280
+
281
+ // API: remove all messages starting from id.
282
+ api_removeNodesFromId = (id) => this.nodes.removeNodesFromId(id, this.scrollMgr);
283
+
284
+ // API: replace live area content (with local post-processing).
285
+ api_replaceLive = (content) => {
286
+ const el = this.dom.get('_append_live_');
287
+ if (!el) return;
288
+ if (el.classList.contains('hidden')) {
289
+ el.classList.remove('hidden');
290
+ el.classList.add('visible');
291
+ }
292
+ el.innerHTML = content;
293
+
294
+ try {
295
+ const maybePromise = this.renderer.renderPendingMarkdown(el);
296
+
297
+ const post = () => {
298
+ try {
299
+ this.highlighter.observeNewCode(el, {
300
+ deferLastIfStreaming: true,
301
+ minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
302
+ minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
303
+ }, this.stream.activeCode);
304
+
305
+ this.highlighter.observeMsgBoxes(el, (box) => {
306
+ this.highlighter.observeNewCode(box, {
307
+ deferLastIfStreaming: true,
308
+ minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
309
+ minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
310
+ }, this.stream.activeCode);
311
+ this.codeScroll.initScrollableBlocks(box);
312
+ });
313
+ } catch (_) {}
314
+
315
+ try {
316
+ const mm = getMathMode();
317
+ // In finalize-only we must force now; otherwise normal schedule is fine.
318
+ if (mm === 'finalize-only') this.math.schedule(el, 0, true);
319
+ else this.math.schedule(el);
320
+ } catch (_) {}
321
+
322
+ this.scrollMgr.scheduleScroll();
323
+ };
324
+
325
+ if (maybePromise && typeof maybePromise.then === 'function') {
326
+ maybePromise.then(post);
327
+ } else {
328
+ post();
329
+ }
330
+ } catch (_) {
331
+ // Worst-case: keep UX responsive even if something throws before post-processing
332
+ this.scrollMgr.scheduleScroll();
333
+ }
334
+ };
335
+
336
+ // API: update footer content.
337
+ api_updateFooter = (html) => {
338
+ const el = this.dom.get('_footer_');
339
+ if (el) el.innerHTML = html;
340
+ };
341
+
342
+ // API: toggle UI features.
343
+ api_enableEditIcons = () => this.ui.enableEditIcons();
344
+ api_disableEditIcons = () => this.ui.disableEditIcons();
345
+ api_enableTimestamp = () => this.ui.enableTimestamp();
346
+ api_disableTimestamp = () => this.ui.disableTimestamp();
347
+ api_enableBlocks = () => this.ui.enableBlocks();
348
+ api_disableBlocks = () => this.ui.disableBlocks();
349
+ api_updateCSS = (styles) => this.ui.updateCSS(styles);
350
+
351
+ // API: sync scroll position with host.
352
+ api_getScrollPosition = () => {
353
+ this.bridge.updateScrollPosition(window.scrollY);
354
+ };
355
+ api_setScrollPosition = (pos) => {
356
+ try {
357
+ window.scrollTo(0, pos);
358
+ this.scrollMgr.prevScroll = parseInt(pos);
359
+ } catch (_) {}
360
+ };
361
+
362
+ // API: show/hide loading overlay.
363
+ api_showLoading = () => this.loading.show();
364
+ api_hideLoading = () => this.loading.hide();
365
+
366
+ // API: restore collapsed state of codes in a given root.
367
+ api_restoreCollapsedCode = (root) => this.renderer.restoreCollapsedCode(root);
368
+
369
+ // API: user-triggered page scroll.
370
+ api_scrollToTopUser = () => this.scrollMgr.scrollToTopUser();
371
+ api_scrollToBottomUser = () => this.scrollMgr.scrollToBottomUser();
372
+
373
+ // API: tips visibility control.
374
+ api_showTips = () => this.tips.show();
375
+ api_hideTips = () => this.tips.hide();
376
+
377
+ // API: custom markup rules control.
378
+ api_getCustomMarkupRules = () => this.customMarkup.getRules();
379
+ api_setCustomMarkupRules = (rules) => {
380
+ this.customMarkup.setRules(rules);
381
+ // Keep StreamEngine in sync with rules producing fenced code
382
+ try {
383
+ this.stream.setCustomFenceSpecs(this.customMarkup.getSourceFenceSpecs());
384
+ } catch (_) {}
385
+ };
386
+
387
+ // Initialize runtime (called on DOMContentLoaded).
388
+ init() {
389
+ this.highlighter.initHLJS();
390
+ this.dom.init();
391
+ this.ui.ensureStickyHeaderStyle();
392
+
393
+ this.tips = new TipsManager(this.dom);
394
+ this.events.install();
395
+
396
+ this.bridge.initQWebChannel(this.cfg.PID, (bridge) => {
397
+ const onChunk = (name, chunk, type) => this.api_onChunk(name, chunk, type);
398
+ const onNode = (payload) => this.api_appendNode(payload);
399
+ const onNodeReplace = (payload) => this.api_replaceNodes(payload);
400
+ const onNodeInput = (html) => this.api_appendToInput(html);
401
+ this.bridge.connect(onChunk, onNode, onNodeReplace, onNodeInput);
402
+ try {
403
+ this.logger.bindBridge(this.bridge.bridge || this.bridge);
404
+ } catch (_) {}
405
+ });
406
+
407
+ this.renderer.init();
408
+ try {
409
+ this.renderer.renderPendingMarkdown(document);
410
+ } catch (_) {}
411
+
412
+ this.highlighter.observeMsgBoxes(document, (box) => {
413
+ this.highlighter.observeNewCode(box, {
414
+ deferLastIfStreaming: true,
415
+ minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
416
+ minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
417
+ }, this.stream.activeCode);
418
+ this.codeScroll.initScrollableBlocks(box);
419
+ });
420
+ this.highlighter.observeNewCode(document, {
421
+ deferLastIfStreaming: true,
422
+ minLinesForLast: this.cfg.PROFILE_CODE.minLinesForHL,
423
+ minCharsForLast: this.cfg.PROFILE_CODE.minCharsForHL
424
+ }, this.stream.activeCode);
425
+ this.highlighter.scheduleScanVisibleCodes(this.stream.activeCode);
426
+
427
+ this.tips.cycle();
428
+ this.scrollMgr.updateScrollFab(true);
429
+ }
430
+
431
+ // Cleanup runtime and detach from DOM/bridge.
432
+ cleanup() {
433
+ this.tips.cleanup();
434
+ try {
435
+ this.bridge.disconnect();
436
+ } catch (_) {}
437
+ this.events.cleanup();
438
+ this.highlighter.cleanup();
439
+ this.math.cleanup();
440
+ this.streamQ.clear();
441
+ this.dom.cleanup();
442
+ }
443
+ }
444
+
445
+ // Ensure RafManager.cancel uses the correct group key cleanup.
446
+ if (typeof RafManager !== 'undefined' && RafManager.prototype && typeof RafManager.prototype.cancel === 'function') {
447
+ RafManager.prototype.cancel = function(key) {
448
+ const t = this.tasks.get(key);
449
+ if (!t) return;
450
+ this.tasks.delete(key);
451
+ if (t.group) {
452
+ const set = this.groups.get(t.group);
453
+ if (set) {
454
+ set.delete(key);
455
+ if (set.size === 0) this.groups.delete(t.group);
456
+ }
457
+ }
458
+ };
459
+ }
460
+
461
+ window.__collapsed_idx = window.__collapsed_idx || [];
462
+
463
+ const runtime = new Runtime();
464
+
465
+ document.addEventListener('DOMContentLoaded', () => runtime.init());
466
+
467
+ Object.defineProperty(window, 'SE', {
468
+ get() {
469
+ return Utils.SE;
470
+ }
471
+ });
472
+
473
+ window.beginStream = (chunk) => runtime.api_beginStream(chunk);
474
+ window.endStream = () => runtime.api_endStream();
475
+ window.applyStream = (name, chunk) => runtime.api_applyStream(name, chunk);
476
+ window.appendStream = (name, chunk) => runtime.api_appendStream(name, chunk);
477
+ window.appendStreamTyped = (type, name, chunk) => runtime.api_onChunk(name, chunk, type);
478
+ window.nextStream = () => runtime.api_nextStream();
479
+ window.clearStream = () => runtime.api_clearStream();
480
+
481
+ window.appendNode = (payload) => runtime.api_appendNode(payload);
482
+ window.replaceNodes = (payload) => runtime.api_replaceNodes(payload);
483
+ window.appendToInput = (html) => runtime.api_appendToInput(html);
484
+
485
+ window.clearNodes = () => runtime.api_clearNodes();
486
+ window.clearInput = () => runtime.api_clearInput();
487
+ window.clearOutput = () => runtime.api_clearOutput();
488
+ window.clearLive = () => runtime.api_clearLive();
489
+
490
+ window.appendToolOutput = (c) => runtime.api_appendToolOutput(c);
491
+ window.updateToolOutput = (c) => runtime.api_updateToolOutput(c);
492
+ window.clearToolOutput = () => runtime.api_clearToolOutput();
493
+ window.beginToolOutput = () => runtime.api_beginToolOutput();
494
+ window.endToolOutput = () => runtime.api_endToolOutput();
495
+ window.enableToolOutput = () => runtime.api_enableToolOutput();
496
+ window.disableToolOutput = () => runtime.api_disableToolOutput();
497
+ window.toggleToolOutput = (id) => runtime.api_toggleToolOutput(id);
498
+
499
+ window.appendExtra = (id, c) => runtime.api_appendExtra(id, c);
500
+ window.removeNode = (id) => runtime.api_removeNode(id);
501
+ window.removeNodesFromId = (id) => runtime.api_removeNodesFromId(id);
502
+
503
+ window.replaceLive = (c) => runtime.api_replaceLive(c);
504
+ window.updateFooter = (c) => runtime.api_updateFooter(c);
505
+
506
+ window.enableEditIcons = () => runtime.api_enableEditIcons();
507
+ window.disableEditIcons = () => runtime.api_disableEditIcons();
508
+ window.enableTimestamp = () => runtime.api_enableTimestamp();
509
+ window.disableTimestamp = () => runtime.api_disableTimestamp();
510
+ window.enableBlocks = () => runtime.api_enableBlocks();
511
+ window.disableBlocks = () => runtime.api_disableBlocks();
512
+ window.updateCSS = (s) => runtime.api_updateCSS(s);
513
+
514
+ window.getScrollPosition = () => runtime.api_getScrollPosition();
515
+ window.setScrollPosition = (pos) => runtime.api_setScrollPosition(pos);
516
+
517
+ window.showLoading = () => runtime.api_showLoading();
518
+ window.hideLoading = () => runtime.api_hideLoading();
519
+
520
+ window.restoreCollapsedCode = (root) => runtime.api_restoreCollapsedCode(root);
521
+ window.scrollToTopUser = () => runtime.api_scrollToTopUser();
522
+ window.scrollToBottomUser = () => runtime.api_scrollToBottomUser();
523
+
524
+ window.showTips = () => runtime.api_showTips();
525
+ window.hideTips = () => runtime.api_hideTips();
526
+
527
+ window.getCustomMarkupRules = () => runtime.api_getCustomMarkupRules();
528
+ window.setCustomMarkupRules = (rules) => runtime.api_setCustomMarkupRules(rules);
529
+
530
+ window.__pygpt_cleanup = () => runtime.cleanup();
531
+
532
+
533
+ RafManager.prototype.stats = function() {
534
+ const byGroup = new Map();
535
+ for (const [key, t] of this.tasks) {
536
+ const g = t.group || 'default';
537
+ byGroup.set(g, (byGroup.get(g) || 0) + 1);
538
+ }
539
+ return {
540
+ tasks: this.tasks.size,
541
+ groups: Array.from(byGroup, ([group, count]) => ({ group, count }))
542
+ .sort((a,b) => b.count - a.count)
543
+ };
544
+ };
545
+
546
+ RafManager.prototype.dumpHotGroups = function(label='') {
547
+ const s = this.stats();
548
+ console.log('[RAF]', label, 'tasks=', s.tasks, 'byGroup=', s.groups.slice(0,8));
549
+ };
550
+ RafManager.prototype.findDomTasks = function() {
551
+ const out = [];
552
+ for (const [key, t] of this.tasks) {
553
+ let el = null;
554
+ if (key && key.nodeType === 1) el = key;
555
+ else if (key && key.el && key.el.nodeType === 1) el = key.el;
556
+ if (el) out.push({ group: t.group, tag: el.tagName, connected: el.isConnected });
557
+ }
558
+ return out;
559
+ };
560
+ // setInterval(() => runtime.raf.dumpHotGroups('tick'), 1000);
561
+
562
+ function gaugeSE(se) {
563
+ const ropeLen = (se.streamBuf.length + se._sbLen);
564
+ const ac = se.activeCode;
565
+ const domFrozen = ac?.frozenEl?.textContent?.length || 0;
566
+ const domTail = ac?.tailEl?.textContent?.length || 0;
567
+ const domLen = domFrozen + domTail;
568
+ return {
569
+ ropeLen,
570
+ domLen,
571
+ totalChars: ropeLen + domLen,
572
+ ratioRopeToDom: (domLen ? (ropeLen / domLen).toFixed(2) : 'n/a'),
573
+ fenceOpen: se.fenceOpen,
574
+ codeOpen: se.codeStream?.open
575
+ };
576
+ }
577
+
578
+ /*
579
+ setInterval(() => {
580
+ const g = gaugeSE(runtime.stream);
581
+ console.log('[SE gauge]', g);
582
+ }, 2000);*/