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.
- pygpt_net/CHANGELOG.txt +12 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +26 -22
- pygpt_net/config.py +44 -0
- pygpt_net/controller/audio/audio.py +0 -0
- pygpt_net/controller/calendar/calendar.py +0 -0
- pygpt_net/controller/calendar/note.py +0 -0
- pygpt_net/controller/chat/chat.py +0 -0
- pygpt_net/controller/chat/handler/openai_stream.py +2 -1
- pygpt_net/controller/chat/handler/worker.py +0 -0
- pygpt_net/controller/chat/remote_tools.py +5 -3
- pygpt_net/controller/chat/render.py +0 -0
- pygpt_net/controller/chat/text.py +0 -0
- pygpt_net/controller/ctx/common.py +0 -0
- pygpt_net/controller/debug/debug.py +26 -2
- pygpt_net/controller/debug/fixtures.py +1 -1
- pygpt_net/controller/dialogs/confirm.py +15 -1
- pygpt_net/controller/dialogs/debug.py +2 -0
- pygpt_net/controller/lang/mapping.py +0 -0
- pygpt_net/controller/launcher/launcher.py +0 -0
- pygpt_net/controller/mode/mode.py +0 -0
- pygpt_net/controller/presets/presets.py +0 -0
- pygpt_net/controller/realtime/realtime.py +0 -0
- pygpt_net/controller/theme/theme.py +0 -0
- pygpt_net/controller/ui/mode.py +5 -3
- pygpt_net/controller/ui/tabs.py +0 -0
- pygpt_net/core/agents/agents.py +3 -1
- pygpt_net/core/agents/custom.py +150 -0
- pygpt_net/core/agents/provider.py +0 -0
- pygpt_net/core/builder/__init__.py +12 -0
- pygpt_net/core/builder/graph.py +478 -0
- pygpt_net/core/calendar/calendar.py +0 -0
- pygpt_net/core/ctx/ctx.py +0 -0
- pygpt_net/core/ctx/output.py +0 -0
- pygpt_net/core/debug/agent.py +0 -0
- pygpt_net/core/debug/agent_builder.py +29 -0
- pygpt_net/core/debug/console/console.py +0 -0
- pygpt_net/core/debug/db.py +0 -0
- pygpt_net/core/debug/debug.py +0 -0
- pygpt_net/core/debug/events.py +0 -0
- pygpt_net/core/debug/indexes.py +0 -0
- pygpt_net/core/debug/kernel.py +0 -0
- pygpt_net/core/debug/tabs.py +0 -0
- pygpt_net/core/filesystem/filesystem.py +0 -0
- pygpt_net/core/fixtures/__init__ +0 -0
- pygpt_net/core/fixtures/stream/__init__.py +0 -0
- pygpt_net/core/fixtures/stream/generator.py +0 -0
- pygpt_net/core/models/models.py +2 -1
- pygpt_net/core/plugins/plugins.py +60 -0
- pygpt_net/core/render/plain/pid.py +0 -0
- pygpt_net/core/render/plain/renderer.py +26 -4
- pygpt_net/core/render/web/body.py +46 -4
- pygpt_net/core/render/web/debug.py +0 -0
- pygpt_net/core/render/web/helpers.py +0 -0
- pygpt_net/core/render/web/pid.py +0 -0
- pygpt_net/core/render/web/renderer.py +15 -20
- pygpt_net/core/tabs/tab.py +0 -0
- pygpt_net/core/tabs/tabs.py +0 -0
- pygpt_net/core/text/utils.py +0 -0
- pygpt_net/css.qrc +0 -0
- pygpt_net/css_rc.py +0 -0
- pygpt_net/data/config/config.json +8 -7
- pygpt_net/data/config/models.json +3 -3
- pygpt_net/data/config/settings.json +14 -0
- pygpt_net/data/css/web-blocks.css +9 -0
- pygpt_net/data/css/web-blocks.dark.css +6 -0
- pygpt_net/data/css/web-blocks.darkest.css +6 -0
- pygpt_net/data/css/web-chatgpt.css +14 -6
- pygpt_net/data/css/web-chatgpt.dark.css +6 -0
- pygpt_net/data/css/web-chatgpt.darkest.css +6 -0
- pygpt_net/data/css/web-chatgpt.light.css +6 -0
- pygpt_net/data/css/web-chatgpt_wide.css +14 -6
- pygpt_net/data/css/web-chatgpt_wide.dark.css +6 -0
- pygpt_net/data/css/web-chatgpt_wide.darkest.css +6 -0
- pygpt_net/data/css/web-chatgpt_wide.light.css +6 -0
- pygpt_net/data/fixtures/fake_stream.txt +14 -1
- pygpt_net/data/icons/case.svg +0 -0
- pygpt_net/data/icons/chat1.svg +0 -0
- pygpt_net/data/icons/chat2.svg +0 -0
- pygpt_net/data/icons/chat3.svg +0 -0
- pygpt_net/data/icons/chat4.svg +0 -0
- pygpt_net/data/icons/fit.svg +0 -0
- pygpt_net/data/icons/note1.svg +0 -0
- pygpt_net/data/icons/note2.svg +0 -0
- pygpt_net/data/icons/note3.svg +0 -0
- pygpt_net/data/icons/stt.svg +0 -0
- pygpt_net/data/icons/translate.svg +0 -0
- pygpt_net/data/icons/tts.svg +0 -0
- pygpt_net/data/icons/url.svg +0 -0
- pygpt_net/data/icons/vision.svg +0 -0
- pygpt_net/data/icons/web_off.svg +0 -0
- pygpt_net/data/icons/web_on.svg +0 -0
- pygpt_net/data/js/app/async.js +166 -0
- pygpt_net/data/js/app/bridge.js +88 -0
- pygpt_net/data/js/app/common.js +212 -0
- pygpt_net/data/js/app/config.js +223 -0
- pygpt_net/data/js/app/custom.js +961 -0
- pygpt_net/data/js/app/data.js +84 -0
- pygpt_net/data/js/app/dom.js +322 -0
- pygpt_net/data/js/app/events.js +400 -0
- pygpt_net/data/js/app/highlight.js +542 -0
- pygpt_net/data/js/app/logger.js +305 -0
- pygpt_net/data/js/app/markdown.js +1137 -0
- pygpt_net/data/js/app/math.js +167 -0
- pygpt_net/data/js/app/nodes.js +395 -0
- pygpt_net/data/js/app/queue.js +260 -0
- pygpt_net/data/js/app/raf.js +250 -0
- pygpt_net/data/js/app/runtime.js +582 -0
- pygpt_net/data/js/app/scroll.js +433 -0
- pygpt_net/data/js/app/stream.js +2708 -0
- pygpt_net/data/js/app/template.js +287 -0
- pygpt_net/data/js/app/tool.js +87 -0
- pygpt_net/data/js/app/ui.js +86 -0
- pygpt_net/data/js/app/user.js +380 -0
- pygpt_net/data/js/app/utils.js +64 -0
- pygpt_net/data/js/app.min.js +880 -0
- pygpt_net/data/js/markdown-it/markdown-it-katex.min.js +1 -1
- pygpt_net/data/js/markdown-it/markdown-it.min.js +0 -0
- pygpt_net/data/locale/locale.de.ini +3 -1
- pygpt_net/data/locale/locale.en.ini +8 -0
- pygpt_net/data/locale/locale.es.ini +2 -0
- pygpt_net/data/locale/locale.fr.ini +2 -0
- pygpt_net/data/locale/locale.it.ini +2 -0
- pygpt_net/data/locale/locale.pl.ini +3 -1
- pygpt_net/data/locale/locale.uk.ini +3 -1
- pygpt_net/data/locale/locale.zh.ini +2 -0
- pygpt_net/data/locale/plugin.osm.en.ini +24 -24
- pygpt_net/data/locale/plugin.wolfram.en.ini +9 -9
- pygpt_net/fonts.qrc +0 -0
- pygpt_net/fonts_rc.py +0 -0
- pygpt_net/icons.qrc +0 -0
- pygpt_net/icons_rc.py +0 -0
- pygpt_net/item/agent.py +62 -0
- pygpt_net/item/builder_layout.py +62 -0
- pygpt_net/js.qrc +24 -1
- pygpt_net/js_rc.py +51394 -33687
- pygpt_net/plugin/base/worker.py +0 -0
- pygpt_net/plugin/cmd_web/config.py +17 -17
- pygpt_net/plugin/cmd_web/worker.py +325 -171
- pygpt_net/plugin/mcp/__init__.py +0 -0
- pygpt_net/plugin/mcp/config.py +0 -0
- pygpt_net/plugin/mcp/plugin.py +0 -0
- pygpt_net/plugin/mcp/worker.py +0 -0
- pygpt_net/plugin/osm/__init__.py +0 -0
- pygpt_net/plugin/osm/config.py +0 -0
- pygpt_net/plugin/osm/plugin.py +0 -0
- pygpt_net/plugin/osm/worker.py +0 -0
- pygpt_net/plugin/wolfram/__init__.py +0 -0
- pygpt_net/plugin/wolfram/config.py +0 -0
- pygpt_net/plugin/wolfram/plugin.py +0 -0
- pygpt_net/plugin/wolfram/worker.py +0 -0
- pygpt_net/provider/api/anthropic/tools.py +0 -0
- pygpt_net/provider/api/google/__init__.py +0 -0
- pygpt_net/provider/api/google/video.py +0 -0
- pygpt_net/provider/api/openai/agents/experts.py +0 -0
- pygpt_net/provider/api/openai/agents/remote_tools.py +0 -0
- pygpt_net/provider/api/openai/remote_tools.py +0 -0
- pygpt_net/provider/api/openai/responses.py +0 -0
- pygpt_net/provider/api/x_ai/__init__.py +2 -0
- pygpt_net/provider/api/x_ai/remote.py +0 -0
- pygpt_net/provider/core/agent/__init__.py +10 -0
- pygpt_net/provider/core/agent/base.py +51 -0
- pygpt_net/provider/core/agent/json_file.py +200 -0
- pygpt_net/provider/core/config/patch.py +33 -0
- pygpt_net/provider/core/config/patches/__init__.py +0 -0
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +1 -0
- pygpt_net/provider/core/ctx/db_sqlite/storage.py +0 -0
- pygpt_net/provider/core/model/patches/__init__.py +0 -0
- pygpt_net/provider/core/model/patches/patch_before_2_6_42.py +0 -0
- pygpt_net/provider/core/preset/patch.py +0 -0
- pygpt_net/provider/core/preset/patches/__init__.py +0 -0
- pygpt_net/provider/core/preset/patches/patch_before_2_6_42.py +0 -0
- pygpt_net/provider/llms/anthropic.py +4 -0
- pygpt_net/provider/llms/base.py +2 -0
- pygpt_net/provider/llms/deepseek_api.py +2 -0
- pygpt_net/provider/llms/google.py +2 -0
- pygpt_net/provider/llms/hugging_face_api.py +4 -0
- pygpt_net/provider/llms/hugging_face_embedding.py +0 -0
- pygpt_net/provider/llms/hugging_face_router.py +2 -0
- pygpt_net/provider/llms/local.py +0 -0
- pygpt_net/provider/llms/mistral.py +4 -0
- pygpt_net/provider/llms/open_router.py +0 -0
- pygpt_net/provider/llms/perplexity.py +2 -0
- pygpt_net/provider/llms/utils.py +0 -0
- pygpt_net/provider/llms/voyage.py +0 -0
- pygpt_net/provider/llms/x_ai.py +2 -0
- pygpt_net/tools/agent_builder/__init__.py +12 -0
- pygpt_net/tools/agent_builder/tool.py +292 -0
- pygpt_net/tools/agent_builder/ui/__init__.py +0 -0
- pygpt_net/tools/agent_builder/ui/dialogs.py +152 -0
- pygpt_net/tools/agent_builder/ui/list.py +228 -0
- pygpt_net/tools/code_interpreter/ui/html.py +0 -0
- pygpt_net/tools/code_interpreter/ui/widgets.py +0 -0
- pygpt_net/tools/html_canvas/tool.py +23 -6
- pygpt_net/tools/html_canvas/ui/widgets.py +224 -2
- pygpt_net/ui/layout/chat/chat.py +0 -0
- pygpt_net/ui/layout/chat/output.py +5 -5
- pygpt_net/ui/main.py +10 -9
- pygpt_net/ui/menu/debug.py +39 -1
- pygpt_net/ui/widget/builder/__init__.py +12 -0
- pygpt_net/ui/widget/builder/editor.py +2001 -0
- pygpt_net/ui/widget/dialog/base.py +4 -1
- pygpt_net/ui/widget/draw/painter.py +0 -0
- pygpt_net/ui/widget/element/labels.py +9 -4
- pygpt_net/ui/widget/lists/db.py +0 -0
- pygpt_net/ui/widget/lists/debug.py +0 -0
- pygpt_net/ui/widget/tabs/body.py +0 -0
- pygpt_net/ui/widget/textarea/html.py +1 -0
- pygpt_net/ui/widget/textarea/input.py +28 -10
- pygpt_net/ui/widget/textarea/output.py +21 -1
- pygpt_net/ui/widget/textarea/web.py +31 -3
- pygpt_net/utils.py +40 -0
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/METADATA +16 -2
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/RECORD +116 -77
- pygpt_net/data/js/app.js +0 -5869
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/LICENSE +0 -0
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/WHEEL +0 -0
- {pygpt_net-2.6.55.dist-info → pygpt_net-2.6.57.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// Stream queue
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
// StreamQueue batches and drains incoming chunks to the engine efficiently.
|
|
6
|
+
class StreamQueue {
|
|
7
|
+
|
|
8
|
+
// Constructor: set up queue, pacing parameters, and raf handle.
|
|
9
|
+
constructor(cfg, engine, scrollMgr, raf) {
|
|
10
|
+
this.cfg = cfg;
|
|
11
|
+
this.engine = engine;
|
|
12
|
+
this.scrollMgr = scrollMgr;
|
|
13
|
+
this.raf = raf;
|
|
14
|
+
|
|
15
|
+
// Internal queue and read pointer.
|
|
16
|
+
this.q = [];
|
|
17
|
+
this.rd = 0;
|
|
18
|
+
|
|
19
|
+
// State flags for draining and UI scrolling.
|
|
20
|
+
this.drainScheduled = false;
|
|
21
|
+
this.batching = false;
|
|
22
|
+
this.needScroll = false;
|
|
23
|
+
|
|
24
|
+
// Stable key for scheduling drain tasks.
|
|
25
|
+
this.DRAIN_KEY = Symbol('SQ:drain');
|
|
26
|
+
|
|
27
|
+
// Timing and coalescing knobs.
|
|
28
|
+
const R = (this.cfg && this.cfg.RAF) || {};
|
|
29
|
+
this.DRAIN_BUDGET_MS = (R.STREAM_DRAIN_BUDGET_MS != null) ? R.STREAM_DRAIN_BUDGET_MS : 4;
|
|
30
|
+
this.COMPACT_SLICE_THRESHOLD = 1024;
|
|
31
|
+
|
|
32
|
+
this._lastCompactRd = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Return number of pending entries not yet processed.
|
|
36
|
+
_qCount() {
|
|
37
|
+
return Math.max(0, this.q.length - this.rd);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Compact adjacent queue entries with the same name by concatenating parts.
|
|
41
|
+
_compactContiguousSameName() {
|
|
42
|
+
const n = this._qCount();
|
|
43
|
+
if (n < 2) return;
|
|
44
|
+
|
|
45
|
+
const out = [];
|
|
46
|
+
let prev = null;
|
|
47
|
+
|
|
48
|
+
for (let i = this.rd; i < this.q.length; i++) {
|
|
49
|
+
const cur = this.q[i];
|
|
50
|
+
if (!cur) continue;
|
|
51
|
+
|
|
52
|
+
if (prev && prev.name === cur.name) {
|
|
53
|
+
// Merge current parts into previous bucket.
|
|
54
|
+
if (cur.parts && cur.parts.length) {
|
|
55
|
+
for (let k = 0; k < cur.parts.length; k++) prev.parts.push(cur.parts[k]);
|
|
56
|
+
} else if (cur.chunk) {
|
|
57
|
+
prev.parts.push(cur.chunk);
|
|
58
|
+
}
|
|
59
|
+
prev.len += (cur.len | 0);
|
|
60
|
+
|
|
61
|
+
// Clear merged node.
|
|
62
|
+
if (cur.parts) cur.parts.length = 0;
|
|
63
|
+
cur.chunk = '';
|
|
64
|
+
cur.len = 0;
|
|
65
|
+
cur.name = '';
|
|
66
|
+
|
|
67
|
+
} else {
|
|
68
|
+
// Start a new bucket from this entry.
|
|
69
|
+
// NOTE: move parts array instead of copying it to avoid duplicating references.
|
|
70
|
+
const parts = cur.parts ? cur.parts : (cur.chunk ? [cur.chunk] : []);
|
|
71
|
+
prev = {
|
|
72
|
+
name: cur.name,
|
|
73
|
+
parts: parts, // moved, not copied
|
|
74
|
+
len: cur.len != null ? cur.len : (cur.chunk ? cur.chunk.length : 0)
|
|
75
|
+
};
|
|
76
|
+
out.push(prev);
|
|
77
|
+
|
|
78
|
+
// Detach original node from its parts to help GC.
|
|
79
|
+
if (cur.parts) cur.parts = [];
|
|
80
|
+
cur.chunk = '';
|
|
81
|
+
cur.len = 0;
|
|
82
|
+
cur.name = '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Replace queue with the compacted version and reset read pointer.
|
|
87
|
+
this.q = out;
|
|
88
|
+
this.rd = 0;
|
|
89
|
+
this._lastCompactRd = 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Maybe shrink the queue array by slicing off processed prefix.
|
|
93
|
+
_maybeCompact() {
|
|
94
|
+
if (this.rd === 0) return;
|
|
95
|
+
const n = this._qCount();
|
|
96
|
+
if (n === 0) {
|
|
97
|
+
this.q = [];
|
|
98
|
+
this.rd = 0;
|
|
99
|
+
this._lastCompactRd = 0;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// If we advanced far, slice off processed items to keep arrays small.
|
|
103
|
+
if (this.rd >= this.COMPACT_SLICE_THRESHOLD) {
|
|
104
|
+
this.q = this.q.slice(this.rd);
|
|
105
|
+
this.rd = 0;
|
|
106
|
+
this._lastCompactRd = 0;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Periodically compact when read pointer grows or passes half the queue.
|
|
110
|
+
if (this.rd - this._lastCompactRd >= 128 || (this.rd > 64 && this.rd >= (this.q.length >> 1))) {
|
|
111
|
+
this.q = this.q.slice(this.rd);
|
|
112
|
+
this.rd = 0;
|
|
113
|
+
this._lastCompactRd = 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Schedule a drain on RAF if not already scheduled.
|
|
118
|
+
_scheduleDrain() {
|
|
119
|
+
if (this.drainScheduled) return;
|
|
120
|
+
this.drainScheduled = true;
|
|
121
|
+
this.raf.schedule(this.DRAIN_KEY, () => this.drain(), 'StreamQueue', -5);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Add a new chunk for a given stream "name" (header) to the queue.
|
|
125
|
+
enqueue(name_header, chunk) {
|
|
126
|
+
if (!chunk || chunk.length === 0) return;
|
|
127
|
+
const name = name_header;
|
|
128
|
+
|
|
129
|
+
// Coalesce with the last entry if it has the same name.
|
|
130
|
+
const hasPending = this._qCount() > 0;
|
|
131
|
+
const tail = hasPending ? this.q[this.q.length - 1] : null;
|
|
132
|
+
|
|
133
|
+
if (tail && tail.name === name) {
|
|
134
|
+
tail.parts.push(chunk);
|
|
135
|
+
tail.len += chunk.length;
|
|
136
|
+
} else {
|
|
137
|
+
this.q.push({
|
|
138
|
+
name,
|
|
139
|
+
parts: [chunk],
|
|
140
|
+
len: chunk.length
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Apply emergency coalescing when queue grows too large.
|
|
145
|
+
const cnt = this._qCount();
|
|
146
|
+
if (cnt > (this.cfg.STREAM.EMERGENCY_COALESCE_LEN | 0)) this._compactContiguousSameName();
|
|
147
|
+
else if (cnt > (this.cfg.STREAM.QUEUE_MAX_ITEMS | 0)) this._compactContiguousSameName();
|
|
148
|
+
|
|
149
|
+
this._scheduleDrain();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Process queued chunks within a time/amount budget and feed them to the engine.
|
|
153
|
+
drain() {
|
|
154
|
+
this.drainScheduled = false;
|
|
155
|
+
|
|
156
|
+
// Adaptive mode increases per-frame quota when backlog grows.
|
|
157
|
+
const adaptive = (this.cfg.STREAM.COALESCE_MODE === 'adaptive');
|
|
158
|
+
const coalesceAggressive = adaptive && (this._qCount() >= (this.cfg.STREAM.EMERGENCY_COALESCE_LEN | 0));
|
|
159
|
+
const basePerFrame = this.cfg.STREAM.MAX_PER_FRAME | 0;
|
|
160
|
+
const perFrame = adaptive ?
|
|
161
|
+
Math.min(basePerFrame + Math.floor(this._qCount() / 20), basePerFrame * 4) :
|
|
162
|
+
basePerFrame;
|
|
163
|
+
|
|
164
|
+
const start = Utils.now();
|
|
165
|
+
// If available, use isInputPending to yield when user input is pending.
|
|
166
|
+
const sched = (navigator && navigator.scheduling && navigator.scheduling.isInputPending) ? navigator.scheduling : null;
|
|
167
|
+
|
|
168
|
+
this.batching = true;
|
|
169
|
+
|
|
170
|
+
let processed = 0;
|
|
171
|
+
while (this.rd < this.q.length && processed < perFrame) {
|
|
172
|
+
const idx = this.rd++;
|
|
173
|
+
const e = this.q[idx];
|
|
174
|
+
if (!e) continue;
|
|
175
|
+
|
|
176
|
+
// Aggressive in-while coalescing for same-name entries to cut overhead.
|
|
177
|
+
if (coalesceAggressive) {
|
|
178
|
+
while (this.rd < this.q.length && this.q[this.rd] && this.q[this.rd].name === e.name) {
|
|
179
|
+
const n = this.q[this.rd++];
|
|
180
|
+
if (n.parts && n.parts.length) {
|
|
181
|
+
for (let k = 0; k < n.parts.length; k++) e.parts.push(n.parts[k]);
|
|
182
|
+
} else if (n.chunk) {
|
|
183
|
+
e.parts.push(n.chunk);
|
|
184
|
+
}
|
|
185
|
+
e.len += (n.len | 0);
|
|
186
|
+
|
|
187
|
+
// Clear merged node to help GC.
|
|
188
|
+
if (n.parts) n.parts.length = 0;
|
|
189
|
+
n.chunk = '';
|
|
190
|
+
n.len = 0;
|
|
191
|
+
n.name = '';
|
|
192
|
+
this.q[this.rd - 1] = null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Join parts into a single payload string for the engine.
|
|
197
|
+
let payload = '';
|
|
198
|
+
if (!e.parts || e.parts.length === 0) payload = e.chunk || '';
|
|
199
|
+
else if (e.parts.length === 1) payload = e.parts[0] || '';
|
|
200
|
+
else payload = e.parts.join('');
|
|
201
|
+
|
|
202
|
+
// Feed the engine (this may schedule snapshots).
|
|
203
|
+
this.engine.applyStream(e.name, payload);
|
|
204
|
+
processed++;
|
|
205
|
+
|
|
206
|
+
// Clear consumed entry.
|
|
207
|
+
if (e.parts) e.parts.length = 0;
|
|
208
|
+
e.chunk = '';
|
|
209
|
+
e.len = 0;
|
|
210
|
+
e.name = '';
|
|
211
|
+
this.q[idx] = null;
|
|
212
|
+
payload = '';
|
|
213
|
+
|
|
214
|
+
// Yield if user input is pending or if we exceeded the time budget.
|
|
215
|
+
if (sched && sched.isInputPending({
|
|
216
|
+
includeContinuous: true
|
|
217
|
+
})) break;
|
|
218
|
+
if ((Utils.now() - start) >= this.DRAIN_BUDGET_MS) break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.batching = false;
|
|
222
|
+
|
|
223
|
+
// If a scroll was requested during batching, schedule it now.
|
|
224
|
+
if (this.needScroll) {
|
|
225
|
+
this.scrollMgr.scheduleScroll(true);
|
|
226
|
+
this.needScroll = false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Keep the queue array compact.
|
|
230
|
+
this._maybeCompact();
|
|
231
|
+
|
|
232
|
+
// If there is more work, schedule another drain.
|
|
233
|
+
if (this._qCount() > 0) this._scheduleDrain();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Nudge the queue to keep draining (useful if external events pause RAF).
|
|
237
|
+
kick() {
|
|
238
|
+
if (this._qCount() || this.drainScheduled) this._scheduleDrain();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Drop all queued entries and cancel scheduled tasks.
|
|
242
|
+
clear() {
|
|
243
|
+
for (let i = this.rd; i < this.q.length; i++) {
|
|
244
|
+
const e = this.q[i];
|
|
245
|
+
if (!e) continue;
|
|
246
|
+
if (e.parts) e.parts.length = 0;
|
|
247
|
+
e.chunk = '';
|
|
248
|
+
e.len = 0;
|
|
249
|
+
e.name = '';
|
|
250
|
+
this.q[i] = null;
|
|
251
|
+
}
|
|
252
|
+
this.q = [];
|
|
253
|
+
this.rd = 0;
|
|
254
|
+
this._lastCompactRd = 0;
|
|
255
|
+
try {
|
|
256
|
+
this.raf.cancelGroup('StreamQueue');
|
|
257
|
+
} catch (_) {}
|
|
258
|
+
this.drainScheduled = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// RafManager (rAF-only)
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
class RafManager {
|
|
6
|
+
|
|
7
|
+
// rAF-only task pump with soft budget per flush to prevent long frames.
|
|
8
|
+
constructor(cfg) {
|
|
9
|
+
this.cfg = cfg || {
|
|
10
|
+
RAF: {},
|
|
11
|
+
ASYNC: {}
|
|
12
|
+
};
|
|
13
|
+
this.tasks = new Map();
|
|
14
|
+
this.groups = new Map();
|
|
15
|
+
// rAF pump state
|
|
16
|
+
this.tickId = 0;
|
|
17
|
+
this._mode = 'raf';
|
|
18
|
+
this.scheduled = false;
|
|
19
|
+
this._flushInProgress = false;
|
|
20
|
+
this._watchdogId = 0; // <-- init watchdog id
|
|
21
|
+
this._weakKeyTokens = new WeakMap();
|
|
22
|
+
|
|
23
|
+
const R = (this.cfg && this.cfg.RAF) || {};
|
|
24
|
+
this.FLUSH_BUDGET_MS = Utils.g('RAF_FLUSH_BUDGET_MS', R.FLUSH_BUDGET_MS ?? 7);
|
|
25
|
+
this.MAX_TASKS_PER_FLUSH = Utils.g('RAF_MAX_TASKS_PER_FLUSH', R.MAX_TASKS_PER_FLUSH ?? 120);
|
|
26
|
+
this.SORT_THRESHOLD = Utils.g('RAF_SORT_THRESHOLD', R.SORT_THRESHOLD ?? 32);
|
|
27
|
+
this.VISIBILITY_FALLBACK_MS = Utils.g('RAF_VISIBILITY_FALLBACK_MS', R.VISIBILITY_FALLBACK_MS ?? 300);
|
|
28
|
+
this.USE_VISIBILITY_FALLBACK = (R.USE_VISIBILITY_FALLBACK ?? true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Normalize a key into a string/symbol/number/DOM token.
|
|
32
|
+
_normalizeKey(key) {
|
|
33
|
+
if (!key) return Symbol('raf:anon');
|
|
34
|
+
const typ = typeof key;
|
|
35
|
+
if (typ === 'string' || typ === 'symbol' || typ === 'number') return key;
|
|
36
|
+
try {
|
|
37
|
+
if (typeof Node !== 'undefined' && key instanceof Node) {
|
|
38
|
+
let tok = this._weakKeyTokens.get(key);
|
|
39
|
+
if (!tok) {
|
|
40
|
+
tok = Symbol('raf:k');
|
|
41
|
+
this._weakKeyTokens.set(key, tok);
|
|
42
|
+
}
|
|
43
|
+
return tok;
|
|
44
|
+
}
|
|
45
|
+
} catch (_) {}
|
|
46
|
+
return key; // plain object key is ok
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Start the pumping loop if needed.
|
|
50
|
+
_armPump() {
|
|
51
|
+
if (this.scheduled) return;
|
|
52
|
+
this.scheduled = true;
|
|
53
|
+
|
|
54
|
+
const canRAF = typeof requestAnimationFrame === 'function';
|
|
55
|
+
if (canRAF) {
|
|
56
|
+
this._mode = 'raf';
|
|
57
|
+
try {
|
|
58
|
+
this.tickId = requestAnimationFrame(() => this.flush());
|
|
59
|
+
if (this.USE_VISIBILITY_FALLBACK && !this._watchdogId) {
|
|
60
|
+
this._watchdogId = setTimeout(() => {
|
|
61
|
+
if (this.scheduled || this.tickId) {
|
|
62
|
+
try {
|
|
63
|
+
this.flush();
|
|
64
|
+
} catch (_) {}
|
|
65
|
+
}
|
|
66
|
+
}, this.VISIBILITY_FALLBACK_MS);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
}
|
|
71
|
+
// Fallback without timers: schedule a microtask flush.
|
|
72
|
+
this._mode = 'raf';
|
|
73
|
+
Promise.resolve().then(() => this.flush());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Schedule a function with an optional group and priority.
|
|
77
|
+
schedule(key, fn, group = 'default', priority = 0) {
|
|
78
|
+
if (!key) key = {
|
|
79
|
+
k: 'anon'
|
|
80
|
+
};
|
|
81
|
+
key = this._normalizeKey(key);
|
|
82
|
+
|
|
83
|
+
const prev = this.tasks.get(key);
|
|
84
|
+
if (prev && prev.group && prev.group !== group) {
|
|
85
|
+
const oldSet = this.groups.get(prev.group);
|
|
86
|
+
if (oldSet) {
|
|
87
|
+
oldSet.delete(key);
|
|
88
|
+
if (oldSet.size === 0) this.groups.delete(prev.group);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.tasks.set(key, {
|
|
93
|
+
fn,
|
|
94
|
+
group,
|
|
95
|
+
priority
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (group) {
|
|
99
|
+
let set = this.groups.get(group);
|
|
100
|
+
if (!set) {
|
|
101
|
+
set = new Set();
|
|
102
|
+
this.groups.set(group, set);
|
|
103
|
+
}
|
|
104
|
+
set.add(key);
|
|
105
|
+
}
|
|
106
|
+
this._armPump();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Run pending tasks within a time and count budget.
|
|
110
|
+
flush() {
|
|
111
|
+
try {
|
|
112
|
+
if (this.tickId) cancelAnimationFrame(this.tickId);
|
|
113
|
+
} catch (_) {}
|
|
114
|
+
this.tickId = 0;
|
|
115
|
+
this.scheduled = false;
|
|
116
|
+
if (this._watchdogId) {
|
|
117
|
+
clearTimeout(this._watchdogId);
|
|
118
|
+
this._watchdogId = 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Snapshot + clear
|
|
122
|
+
const list = [];
|
|
123
|
+
this.tasks.forEach((v, key) => list.push({
|
|
124
|
+
key,
|
|
125
|
+
...v
|
|
126
|
+
}));
|
|
127
|
+
this.tasks.clear();
|
|
128
|
+
|
|
129
|
+
if (list.length > 1 && list.length > this.SORT_THRESHOLD) {
|
|
130
|
+
list.sort((a, b) => a.priority - b.priority);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const start = Utils.now();
|
|
134
|
+
let processed = 0;
|
|
135
|
+
|
|
136
|
+
for (let idx = 0; idx < list.length; idx++) {
|
|
137
|
+
const t = list[idx];
|
|
138
|
+
try {
|
|
139
|
+
t.fn();
|
|
140
|
+
} catch (_) {}
|
|
141
|
+
processed++;
|
|
142
|
+
|
|
143
|
+
if (t.group) {
|
|
144
|
+
const set = this.groups.get(t.group);
|
|
145
|
+
if (set) {
|
|
146
|
+
set.delete(t.key);
|
|
147
|
+
if (set.size === 0) this.groups.delete(t.group);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const elapsed = Utils.now() - start;
|
|
152
|
+
if (processed >= this.MAX_TASKS_PER_FLUSH || elapsed >= this.FLUSH_BUDGET_MS) {
|
|
153
|
+
// requeue the rest
|
|
154
|
+
for (let j = idx + 1; j < list.length; j++) {
|
|
155
|
+
const r = list[j];
|
|
156
|
+
this.tasks.set(r.key, {
|
|
157
|
+
fn: r.fn,
|
|
158
|
+
group: r.group,
|
|
159
|
+
priority: r.priority
|
|
160
|
+
});
|
|
161
|
+
if (r.group) {
|
|
162
|
+
let set = this.groups.get(r.group);
|
|
163
|
+
if (!set) {
|
|
164
|
+
set = new Set();
|
|
165
|
+
this.groups.set(r.group, set);
|
|
166
|
+
}
|
|
167
|
+
set.add(r.key);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
this._armPump();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (this.tasks.size) this._armPump();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Force immediate flush or schedule next frame.
|
|
179
|
+
kick(forceImmediate = true) {
|
|
180
|
+
if (forceImmediate && this.tasks.size) {
|
|
181
|
+
if (this._flushInProgress) return;
|
|
182
|
+
this._flushInProgress = true;
|
|
183
|
+
try {
|
|
184
|
+
this.scheduled = true;
|
|
185
|
+
this.flush();
|
|
186
|
+
} catch (_) {} finally {
|
|
187
|
+
this._flushInProgress = false;
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
this._armPump();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Cancel a specific scheduled task by key.
|
|
195
|
+
cancel(key) {
|
|
196
|
+
key = this._normalizeKey(key);
|
|
197
|
+
const t = this.tasks.get(key);
|
|
198
|
+
if (!t) return;
|
|
199
|
+
this.tasks.delete(key);
|
|
200
|
+
if (t.group) {
|
|
201
|
+
const set = this.groups.get(t.group);
|
|
202
|
+
if (set) {
|
|
203
|
+
set.delete(key);
|
|
204
|
+
if (set.size === 0) this.groups.delete(t.group);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Cancel all tasks in a group.
|
|
210
|
+
cancelGroup(group) {
|
|
211
|
+
const set = this.groups.get(group);
|
|
212
|
+
if (!set) return;
|
|
213
|
+
for (const key of set) this.tasks.delete(key);
|
|
214
|
+
this.groups.delete(group);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Cancel everything and reset the pump.
|
|
218
|
+
cancelAll() {
|
|
219
|
+
this.tasks.clear();
|
|
220
|
+
this.groups.clear();
|
|
221
|
+
try {
|
|
222
|
+
if (this.tickId) cancelAnimationFrame(this.tickId);
|
|
223
|
+
} catch (_) {}
|
|
224
|
+
this.tickId = 0;
|
|
225
|
+
this.scheduled = false;
|
|
226
|
+
if (this._watchdogId) {
|
|
227
|
+
clearTimeout(this._watchdogId);
|
|
228
|
+
this._watchdogId = 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Checks if a task is scheduled.
|
|
233
|
+
isScheduled(key) {
|
|
234
|
+
return this.tasks.has(this._normalizeKey(key));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Awaitable "next frame" helper – resolves on next flush.
|
|
238
|
+
nextFrame() {
|
|
239
|
+
return new Promise((resolve) => {
|
|
240
|
+
const key = Symbol('raf:nextFrame');
|
|
241
|
+
this.schedule(key, () => resolve(), 'RafNext', 0);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Return math rendering policy from window.MATH_STREAM_MODE.
|
|
247
|
+
function getMathMode() {
|
|
248
|
+
const v = String(window.MATH_STREAM_MODE || 'finalize-only').toLowerCase();
|
|
249
|
+
return (v === 'idle' || v === 'always' || v === 'finalize-only') ? v : 'finalize-only';
|
|
250
|
+
}
|