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,84 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// Data receiver for append/replace nodes
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
class DataReceiver {
|
|
6
|
+
|
|
7
|
+
// Normalizes payload (HTML string or JSON) and delegates to NodesManager.
|
|
8
|
+
constructor(cfg, templates, nodes, scrollMgr) {
|
|
9
|
+
this.cfg = cfg || {};
|
|
10
|
+
this.templates = templates;
|
|
11
|
+
this.nodes = nodes;
|
|
12
|
+
this.scrollMgr = scrollMgr;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Tries to parse a JSON string.
|
|
16
|
+
_tryParseJSON(s) {
|
|
17
|
+
if (typeof s !== 'string') return s;
|
|
18
|
+
const t = s.trim();
|
|
19
|
+
if (!t) return null;
|
|
20
|
+
// If it's like HTML, don't parse as JSON
|
|
21
|
+
if (t[0] === '<') return null;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(t);
|
|
24
|
+
} catch (_) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Normalizes the input object to an array of block nodes.
|
|
30
|
+
_normalizeToBlocks(obj) {
|
|
31
|
+
if (!obj) return [];
|
|
32
|
+
if (Array.isArray(obj)) return obj;
|
|
33
|
+
if (obj.node) return [obj.node];
|
|
34
|
+
if (obj.nodes) return (Array.isArray(obj.nodes) ? obj.nodes : []);
|
|
35
|
+
// single node-like object
|
|
36
|
+
if (typeof obj === 'object' && (obj.input || obj.output || obj.id)) return [obj];
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Appends a new payload to the nodes.
|
|
41
|
+
append(payload) {
|
|
42
|
+
// Legacy HTML string?
|
|
43
|
+
if (typeof payload === 'string' && payload.trim().startsWith('<')) {
|
|
44
|
+
this.nodes.appendNode(payload, this.scrollMgr);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Try JSON
|
|
48
|
+
const obj = this._tryParseJSON(payload);
|
|
49
|
+
if (!obj) {
|
|
50
|
+
// Not JSON – pass through
|
|
51
|
+
this.nodes.appendNode(String(payload), this.scrollMgr);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const blocks = this._normalizeToBlocks(obj);
|
|
55
|
+
if (!blocks.length) {
|
|
56
|
+
this.nodes.appendNode('', this.scrollMgr);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const html = this.templates.renderNodes(blocks);
|
|
60
|
+
this.nodes.appendNode(html, this.scrollMgr);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Replaces the current nodes with a new payload.
|
|
64
|
+
replace(payload) {
|
|
65
|
+
// Legacy HTML string?
|
|
66
|
+
if (typeof payload === 'string' && payload.trim().startsWith('<')) {
|
|
67
|
+
this.nodes.replaceNodes(payload, this.scrollMgr);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Try JSON
|
|
71
|
+
const obj = this._tryParseJSON(payload);
|
|
72
|
+
if (!obj) {
|
|
73
|
+
this.nodes.replaceNodes(String(payload), this.scrollMgr);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const blocks = this._normalizeToBlocks(obj);
|
|
77
|
+
if (!blocks.length) {
|
|
78
|
+
this.nodes.replaceNodes('', this.scrollMgr);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const html = this.templates.renderNodes(blocks);
|
|
82
|
+
this.nodes.replaceNodes(html, this.scrollMgr);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// DOM references (memory-aware, leak-safe, with safe header HTML)
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
class DOMRefs {
|
|
6
|
+
|
|
7
|
+
// Caches DOM elements by ID and keeps ephemeral references weak.
|
|
8
|
+
constructor() {
|
|
9
|
+
// Strong cache for fixed, top-level nodes by id (safe to keep for app lifetime).
|
|
10
|
+
this.els = Object.create(null);
|
|
11
|
+
|
|
12
|
+
// Ephemeral references are stored as WeakRef to avoid retaining large subtrees.
|
|
13
|
+
this._domOutputStreamRef = null; // WeakRef<HTMLDivElement> for '_append_output_'
|
|
14
|
+
this._domStreamMsgRef = null; // WeakRef<HTMLDivElement> for current '.msg'
|
|
15
|
+
this._domStreamBoxRef = null; // WeakRef<HTMLDivElement> for current '.msg-box'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Cache frequently used elements by id (strong refs to fixed layout nodes).
|
|
19
|
+
init() {
|
|
20
|
+
const ids = [
|
|
21
|
+
'container', '_nodes_', '_append_input_', '_append_output_before_', '_append_output_',
|
|
22
|
+
'_append_live_', '_footer_', '_loader_', 'tips', 'scrollFab', 'scrollFabIcon'
|
|
23
|
+
];
|
|
24
|
+
for (let i = 0; i < ids.length; i++) {
|
|
25
|
+
const id = ids[i];
|
|
26
|
+
const el = document.getElementById(id);
|
|
27
|
+
if (el) this.els[id] = el;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Returns a strong cached element by id (and updates cache if it was not present).
|
|
32
|
+
get(id) {
|
|
33
|
+
let el = this.els[id];
|
|
34
|
+
if (el && el.isConnected) return el;
|
|
35
|
+
el = document.getElementById(id);
|
|
36
|
+
if (el) this.els[id] = el;
|
|
37
|
+
return el || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Reset ephemeral pointers (used during stream).
|
|
41
|
+
resetEphemeral() {
|
|
42
|
+
this._domStreamMsgRef = null;
|
|
43
|
+
this._domStreamBoxRef = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Release refs and restore default scroll behavior.
|
|
47
|
+
cleanup() {
|
|
48
|
+
this.resetEphemeral();
|
|
49
|
+
this._domOutputStreamRef = null;
|
|
50
|
+
this.els = Object.create(null);
|
|
51
|
+
try { history.scrollRestoration = "auto"; } catch (_) {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Dereference a WeakRef safely.
|
|
55
|
+
_deref(ref) {
|
|
56
|
+
if (!ref || typeof ref.deref !== 'function') return null;
|
|
57
|
+
try {
|
|
58
|
+
const el = ref.deref();
|
|
59
|
+
return (el && el.isConnected) ? el : null;
|
|
60
|
+
} catch (_) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Replace content of a container quickly (no innerHTML, minimal GC pressure).
|
|
66
|
+
fastClear(id) {
|
|
67
|
+
const el = this.get(id);
|
|
68
|
+
if (!el) return null;
|
|
69
|
+
if (el.firstChild) {
|
|
70
|
+
if (typeof el.replaceChildren === 'function') el.replaceChildren();
|
|
71
|
+
else el.textContent = '';
|
|
72
|
+
}
|
|
73
|
+
return el;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Clear and ensure paint on next frame (await before reading layout).
|
|
77
|
+
async fastClearAndPaint(id) {
|
|
78
|
+
const el = this.fastClear(id);
|
|
79
|
+
if (!el) return null;
|
|
80
|
+
try {
|
|
81
|
+
if (typeof runtime !== 'undefined' && runtime.raf && typeof runtime.raf.nextFrame === 'function') {
|
|
82
|
+
await runtime.raf.nextFrame();
|
|
83
|
+
} else if (typeof requestAnimationFrame === 'function') {
|
|
84
|
+
await new Promise(res => requestAnimationFrame(() => res()));
|
|
85
|
+
} else {
|
|
86
|
+
await new Promise(res => setTimeout(res, 16));
|
|
87
|
+
}
|
|
88
|
+
} catch (_) {}
|
|
89
|
+
return el;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Hard clear by temporarily hiding element to avoid intermediate paints.
|
|
93
|
+
fastClearHidden(id) {
|
|
94
|
+
const el = this.get(id);
|
|
95
|
+
if (!el) return null;
|
|
96
|
+
const prevDisplay = el.style.display;
|
|
97
|
+
el.style.display = 'none'; // pause paints
|
|
98
|
+
if (typeof el.replaceChildren === 'function') el.replaceChildren();
|
|
99
|
+
else el.textContent = '';
|
|
100
|
+
el.style.display = prevDisplay; // resume paints
|
|
101
|
+
return el;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Replace element node by a shallow clone (drops children).
|
|
105
|
+
hardReplaceByClone(id) {
|
|
106
|
+
const el = this.get(id);
|
|
107
|
+
if (!el || !el.parentNode) return null;
|
|
108
|
+
const clone = el.cloneNode(false);
|
|
109
|
+
try {
|
|
110
|
+
el.replaceWith(clone);
|
|
111
|
+
} catch (_) {
|
|
112
|
+
el.textContent = '';
|
|
113
|
+
return el;
|
|
114
|
+
}
|
|
115
|
+
this.els[id] = clone;
|
|
116
|
+
if (id === '_append_output_') {
|
|
117
|
+
this._domOutputStreamRef = (typeof WeakRef !== 'undefined') ? new WeakRef(clone) : null;
|
|
118
|
+
}
|
|
119
|
+
return clone;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Clear streaming containers and transient state.
|
|
123
|
+
hardResetStreamContainers() {
|
|
124
|
+
this.resetEphemeral();
|
|
125
|
+
this._domOutputStreamRef = null;
|
|
126
|
+
this.fastClearHidden('_append_output_before_');
|
|
127
|
+
this.fastClearHidden('_append_output_');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Return output stream container, caching a weak reference.
|
|
131
|
+
getStreamContainer() {
|
|
132
|
+
let el = this._deref(this._domOutputStreamRef);
|
|
133
|
+
if (el) return el;
|
|
134
|
+
el = this.get('_append_output_');
|
|
135
|
+
if (el) this._domOutputStreamRef = (typeof WeakRef !== 'undefined') ? new WeakRef(el) : null;
|
|
136
|
+
return el;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Allow-list sanitizer for header fragment (keeps img/span/strong/em, strips others).
|
|
140
|
+
_sanitizeHeaderFragment(root) {
|
|
141
|
+
const ALLOWED = new Set(['IMG', 'SPAN', 'STRONG', 'EM']);
|
|
142
|
+
const ALLOWED_ATTR = {
|
|
143
|
+
IMG: new Set(['src', 'alt', 'class', 'width', 'height', 'loading', 'decoding']),
|
|
144
|
+
SPAN: new Set(['class']),
|
|
145
|
+
STRONG: new Set([]),
|
|
146
|
+
EM: new Set([])
|
|
147
|
+
};
|
|
148
|
+
const isAllowedSrc = (src) => {
|
|
149
|
+
if (!src || typeof src !== 'string') return false;
|
|
150
|
+
const s = src.trim().toLowerCase();
|
|
151
|
+
// Allow typical local/embedded schemes used in this app and safe web protocols.
|
|
152
|
+
if (s.startsWith('file:')) return true;
|
|
153
|
+
if (s.startsWith('qrc:')) return true;
|
|
154
|
+
if (s.startsWith('bridge:')) return true;
|
|
155
|
+
if (s.startsWith('blob:')) return true;
|
|
156
|
+
if (s.startsWith('data:image/')) return true;
|
|
157
|
+
if (s.startsWith('http:')) return true;
|
|
158
|
+
if (s.startsWith('https:')) return true;
|
|
159
|
+
// Allow relative paths
|
|
160
|
+
if (!/^[a-z0-9.+-]+:/.test(s)) return true;
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
|
|
165
|
+
const toRemove = [];
|
|
166
|
+
while (walker.nextNode()) {
|
|
167
|
+
const el = walker.currentNode;
|
|
168
|
+
const tag = el.tagName;
|
|
169
|
+
if (!ALLOWED.has(tag)) {
|
|
170
|
+
toRemove.push(el);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
// Prune attributes
|
|
174
|
+
const allowAttrs = ALLOWED_ATTR[tag];
|
|
175
|
+
for (let i = el.attributes.length - 1; i >= 0; i--) {
|
|
176
|
+
const a = el.attributes[i];
|
|
177
|
+
if (!allowAttrs.has(a.name)) el.removeAttribute(a.name);
|
|
178
|
+
}
|
|
179
|
+
if (tag === 'IMG') {
|
|
180
|
+
const src = el.getAttribute('src') || '';
|
|
181
|
+
if (!isAllowedSrc(src)) {
|
|
182
|
+
toRemove.push(el);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// Add safe defaults
|
|
186
|
+
if (!el.hasAttribute('loading')) el.setAttribute('loading', 'lazy');
|
|
187
|
+
if (!el.hasAttribute('decoding')) el.setAttribute('decoding', 'async');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Replace stripped elements with their textContent to preserve readable header if any.
|
|
191
|
+
for (const el of toRemove) {
|
|
192
|
+
const txt = el.textContent || '';
|
|
193
|
+
const tn = document.createTextNode(txt);
|
|
194
|
+
try { el.replaceWith(tn); } catch (_) {}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Safely set header HTML (allows only minimal inline markup including <img>).
|
|
199
|
+
_setHeaderHTML(container, html) {
|
|
200
|
+
if (!container) return;
|
|
201
|
+
const s = String(html || '');
|
|
202
|
+
// Fast path: no tags => plain text
|
|
203
|
+
if (s.indexOf('<') === -1 && s.indexOf('&') === -1) {
|
|
204
|
+
container.textContent = s;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const tpl = document.createElement('template');
|
|
208
|
+
tpl.innerHTML = s;
|
|
209
|
+
const frag = tpl.content;
|
|
210
|
+
try {
|
|
211
|
+
this._sanitizeHeaderFragment(frag);
|
|
212
|
+
container.replaceChildren(frag);
|
|
213
|
+
} catch (_) {
|
|
214
|
+
// Fallback to plain text for absolute safety.
|
|
215
|
+
container.textContent = s.replace(/<[^>]*>/g, '');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Get or create current streaming message container (.msg-box > .msg > .md-snapshot-root).
|
|
220
|
+
getStreamMsg(create, name_header) {
|
|
221
|
+
const container = this.getStreamContainer();
|
|
222
|
+
if (!container) return null;
|
|
223
|
+
|
|
224
|
+
// Fast path: return cached current message if still connected.
|
|
225
|
+
let msg = this._deref(this._domStreamMsgRef);
|
|
226
|
+
if (msg) return msg;
|
|
227
|
+
|
|
228
|
+
// Try known current box first (if any).
|
|
229
|
+
let box = this._deref(this._domStreamBoxRef);
|
|
230
|
+
|
|
231
|
+
// If no current box is known, locate an existing one.
|
|
232
|
+
if (!box) {
|
|
233
|
+
try { box = container.querySelector('.msg-box'); } catch (_) { box = null; }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Create a new streaming box if none is present and requested by 'create'.
|
|
237
|
+
if (!box && create) {
|
|
238
|
+
const frag = document.createDocumentFragment();
|
|
239
|
+
|
|
240
|
+
const newBox = document.createElement('div');
|
|
241
|
+
newBox.classList.add('msg-box', 'msg-bot');
|
|
242
|
+
|
|
243
|
+
if (name_header) {
|
|
244
|
+
const name = document.createElement('div');
|
|
245
|
+
name.classList.add('name-header', 'name-bot');
|
|
246
|
+
// Safe header HTML (keeps <img> and simple inline tags).
|
|
247
|
+
this._setHeaderHTML(name, name_header);
|
|
248
|
+
newBox.appendChild(name);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const newMsg = document.createElement('div');
|
|
252
|
+
newMsg.classList.add('msg');
|
|
253
|
+
|
|
254
|
+
const snap = document.createElement('div');
|
|
255
|
+
snap.className = 'md-snapshot-root';
|
|
256
|
+
newMsg.appendChild(snap);
|
|
257
|
+
|
|
258
|
+
newBox.appendChild(newMsg);
|
|
259
|
+
frag.appendChild(newBox);
|
|
260
|
+
container.appendChild(frag);
|
|
261
|
+
|
|
262
|
+
this._domStreamBoxRef = (typeof WeakRef !== 'undefined') ? new WeakRef(newBox) : null;
|
|
263
|
+
this._domStreamMsgRef = (typeof WeakRef !== 'undefined') ? new WeakRef(newMsg) : null;
|
|
264
|
+
|
|
265
|
+
return newMsg;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// If a box exists but has no .msg or .md-snapshot-root, ensure it.
|
|
269
|
+
if (box) {
|
|
270
|
+
try { msg = box.querySelector('.msg'); } catch (_) { msg = null; }
|
|
271
|
+
if (!msg) {
|
|
272
|
+
msg = document.createElement('div');
|
|
273
|
+
msg.classList.add('msg');
|
|
274
|
+
box.appendChild(msg);
|
|
275
|
+
}
|
|
276
|
+
if (!msg.querySelector('.md-snapshot-root')) {
|
|
277
|
+
const snap = document.createElement('div');
|
|
278
|
+
snap.className = 'md-snapshot-root';
|
|
279
|
+
msg.appendChild(snap);
|
|
280
|
+
}
|
|
281
|
+
this._domStreamBoxRef = (typeof WeakRef !== 'undefined') ? new WeakRef(box) : null;
|
|
282
|
+
this._domStreamMsgRef = (typeof WeakRef !== 'undefined') ? new WeakRef(msg) : null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return msg || null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Clear the "before" area (older messages area).
|
|
289
|
+
clearStreamBefore() {
|
|
290
|
+
try {
|
|
291
|
+
if (typeof window.hideTips === 'function') window.hideTips();
|
|
292
|
+
} catch (_) {}
|
|
293
|
+
this.fastClearHidden('_append_output_before_');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Clear output stream area.
|
|
297
|
+
clearOutput() {
|
|
298
|
+
this.hardResetStreamContainers();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Clear messages list and reset state.
|
|
302
|
+
clearNodes() {
|
|
303
|
+
this.clearStreamBefore();
|
|
304
|
+
const el = this.fastClearHidden('_nodes_');
|
|
305
|
+
if (el) el.classList.add('empty_list');
|
|
306
|
+
this.resetEphemeral();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Clear input area.
|
|
310
|
+
clearInput() {
|
|
311
|
+
this.fastClearHidden('_append_input_');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Clear live area and hide it.
|
|
315
|
+
clearLive() {
|
|
316
|
+
const el = this.fastClearHidden('_append_live_');
|
|
317
|
+
if (!el) return;
|
|
318
|
+
el.classList.remove('visible');
|
|
319
|
+
el.classList.add('hidden');
|
|
320
|
+
this.resetEphemeral();
|
|
321
|
+
}
|
|
322
|
+
}
|