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,287 @@
1
+ // ==========================================================================
2
+ // Template engine for JSON nodes
3
+ // ==========================================================================
4
+
5
+ class NodeTemplateEngine {
6
+
7
+ // JS-side templates for nodes rendered from JSON payload (RenderBlock).
8
+ constructor(cfg, logger) {
9
+ this.cfg = cfg || {};
10
+ this.logger = logger || {
11
+ debug: () => {}
12
+ };
13
+ }
14
+
15
+ // Escapes a string for safe HTML rendering.
16
+ _esc(s) {
17
+ return (s == null) ? '' : String(s);
18
+ }
19
+
20
+ // Escapes a string for safe HTML rendering.
21
+ _escapeHtml(s) {
22
+ return (typeof Utils !== 'undefined') ? Utils.escapeHtml(s) : String(s).replace(/[&<>"']/g, m => ({
23
+ '&': '&amp;',
24
+ '<': '&lt;',
25
+ '>': '&gt;',
26
+ '"': '&quot;',
27
+ "'": '&#039;'
28
+ } [m]));
29
+ }
30
+
31
+ // Render name header given role
32
+ _nameHeader(role, name, avatarUrl) {
33
+ if (!name && !avatarUrl) return '';
34
+ const cls = (role === 'user') ? 'name-user' : 'name-bot';
35
+ const img = avatarUrl ? `<img src="${this._esc(avatarUrl)}" class="avatar"> ` : '';
36
+ return `<div class="name-header ${cls}">${img}${this._esc(name || '')}</div>`;
37
+ }
38
+
39
+ // Render user message block
40
+ _renderUser(block) {
41
+ const id = block.id;
42
+ const inp = block.input || {};
43
+ const msgId = `msg-user-${id}`;
44
+
45
+ // NOTE: timestamps intentionally disabled on frontend
46
+ // let ts = '';
47
+ // if (inp.timestamp) { ... }
48
+
49
+ const personalize = !!(block && block.extra && block.extra.personalize === true);
50
+ const nameHeader = personalize ? this._nameHeader('user', inp.name || '', inp.avatar_img || null) : '';
51
+
52
+ const content = this._escapeHtml(inp.text || '').replace(/\r?\n/g, '<br>');
53
+
54
+ // Use existing copy icon and locale strings to keep public API stable.
55
+ const I = (this.cfg && this.cfg.ICONS) || {};
56
+ const L = (this.cfg && this.cfg.LOCALE) || {};
57
+ const copyIcon = I.CODE_COPY || '';
58
+ const copyTitle = L.COPY || 'Copy';
59
+
60
+ // Single icon, no label; positioned via CSS; visible on hover.
61
+ const copyBtn = `<a href="empty:${this._esc(id)}" class="msg-copy-btn" data-id="${this._esc(id)}" data-tip="${this._escapeHtml(copyTitle)}" title="${this._escapeHtml(copyTitle)}" aria-label="${this._escapeHtml(copyTitle)}" role="button"><img src="${this._esc(copyIcon)}" class="copy-img" alt="${this._escapeHtml(copyTitle)}" data-id="${this._esc(id)}"></a>`;
62
+
63
+ return `<div class="msg-box msg-user" id="${msgId}">${nameHeader}<div class="msg">${copyBtn}<p style="margin:0">${content}</p></div></div>`;
64
+ }
65
+
66
+ // Render extra blocks (images/files/urls/docs/tool-extra)
67
+ _renderExtras(block) {
68
+ const parts = [];
69
+
70
+ // images
71
+ const images = block.images || {};
72
+ const keysI = Object.keys(images);
73
+ if (keysI.length) {
74
+ keysI.forEach((k) => {
75
+ const it = images[k];
76
+ if (!it) return;
77
+ const url = this._esc(it.url);
78
+ const path = this._esc(it.path);
79
+ const bn = this._esc(it.basename || '');
80
+ if (it.is_video) {
81
+ const src = (it.ext === '.webm' || !it.webm_path) ? path : this._esc(it.webm_path);
82
+ const ext = (src.endsWith('.webm') ? 'webm' : (path.split('.').pop() || 'mp4'));
83
+ parts.push(
84
+ `<div class="extra-src-video-box" title="${url}">` +
85
+ `<video class="video-player" controls>` +
86
+ `<source src="${src}" type="video/${ext}">` +
87
+ `</video>` +
88
+ `<p><a href="bridge://play_video/${url}" class="title">${this._escapeHtml(bn)}</a></p>` +
89
+ `</div>`
90
+ );
91
+ } else {
92
+ parts.push(
93
+ `<div class="extra-src-img-box" title="${url}">` +
94
+ `<div class="img-outer"><div class="img-wrapper"><a href="${url}"><img src="${path}" class="image"></a></div>` +
95
+ `<a href="${url}" class="title">${this._escapeHtml(bn)}</a></div>` +
96
+ `</div><br/>`
97
+ );
98
+ }
99
+ });
100
+ }
101
+
102
+ // files
103
+ const files = block.files || {};
104
+ const kF = Object.keys(files);
105
+ if (kF.length) {
106
+ const rows = [];
107
+ kF.forEach((k) => {
108
+ const it = files[k];
109
+ if (!it) return;
110
+ const url = this._esc(it.url);
111
+ const path = this._esc(it.path);
112
+ const icon = (typeof window !== 'undefined' && window.ICON_ATTACHMENTS) ? `<img src="${window.ICON_ATTACHMENTS}" class="extra-src-icon">` : '';
113
+ rows.push(`${icon} <b> [${k}] </b> <a href="${url}">${path}</a>`);
114
+ });
115
+ if (rows.length) parts.push(`<div>${rows.join("<br/><br/>")}</div>`);
116
+ }
117
+
118
+ // urls
119
+ const urls = block.urls || {};
120
+ const kU = Object.keys(urls);
121
+ if (kU.length) {
122
+ const rows = [];
123
+ kU.forEach((k) => {
124
+ const it = urls[k];
125
+ if (!it) return;
126
+ const url = this._esc(it.url);
127
+ const icon = (typeof window !== 'undefined' && window.ICON_URL) ? `<img src="${window.ICON_URL}" class="extra-src-icon">` : '';
128
+ rows.push(`${icon}<a href="${url}" title="${url}">${url}</a> <small> [${k}] </small>`);
129
+ });
130
+ if (rows.length) parts.push(`<div>${rows.join("<br/><br/>")}</div>`);
131
+ }
132
+
133
+ // docs (render on JS) or fallback to docs_html
134
+ const extra = block.extra || {};
135
+ const docsRaw = Array.isArray(extra.docs) ? extra.docs : null;
136
+
137
+ if (docsRaw && docsRaw.length) {
138
+ const icon = (typeof window !== 'undefined' && window.ICON_DB) ? `<img src="${window.ICON_DB}" class="extra-src-icon">` : '';
139
+ const prefix = (typeof window !== 'undefined' && window.LOCALE_DOC_PREFIX) ? String(window.LOCALE_DOC_PREFIX) : 'Doc:';
140
+ const limit = 3;
141
+
142
+ // normalize: [{uuid, meta}] OR [{ uuid: {...} }]
143
+ const normalized = [];
144
+ docsRaw.forEach((it) => {
145
+ if (!it || typeof it !== 'object') return;
146
+ if ('uuid' in it && 'meta' in it && typeof it.meta === 'object') {
147
+ normalized.push({
148
+ uuid: String(it.uuid),
149
+ meta: it.meta || {}
150
+ });
151
+ } else {
152
+ const keys = Object.keys(it);
153
+ if (keys.length === 1) {
154
+ const uuid = keys[0];
155
+ const meta = it[uuid];
156
+ if (meta && typeof meta === 'object') {
157
+ normalized.push({
158
+ uuid: String(uuid),
159
+ meta
160
+ });
161
+ }
162
+ }
163
+ }
164
+ });
165
+
166
+ const rows = [];
167
+ for (let i = 0; i < Math.min(limit, normalized.length); i++) {
168
+ const d = normalized[i];
169
+ const meta = d.meta || {};
170
+ const entries = Object.keys(meta).map(k => `<b>${this._escapeHtml(k)}:</b> ${this._escapeHtml(String(meta[k]))}`).join(', ');
171
+ rows.push(`<p><small>[${i + 1}] ${this._escapeHtml(d.uuid)}: ${entries}</small></p>`);
172
+ }
173
+ if (rows.length) {
174
+ parts.push(`<p>${icon}<small><b>${this._escapeHtml(prefix)}:</b></small></p>`);
175
+ parts.push(`<div class="cmd"><p>${rows.join('')}</p></div>`);
176
+ }
177
+ } else {
178
+ // backward compat
179
+ const docs_html = extra && extra.docs_html ? String(extra.docs_html) : '';
180
+ if (docs_html) parts.push(docs_html);
181
+ }
182
+
183
+ // plugin-driven tool extra HTML
184
+ const tool_extra_html = extra && extra.tool_extra_html ? String(extra.tool_extra_html) : '';
185
+ if (tool_extra_html) parts.push(`<div class="msg-extra">${tool_extra_html}</div>`);
186
+
187
+ return parts.join('');
188
+ }
189
+
190
+ // Render message-level actions
191
+ _renderActions(block) {
192
+ const extra = block.extra || {};
193
+ const actions = extra.actions || [];
194
+ if (!actions || !actions.length) return '';
195
+ const parts = actions.map((a) => {
196
+ const href = this._esc(a.href || '#');
197
+ const title = this._esc(a.title || '');
198
+ const icon = this._esc(a.icon || '');
199
+ const id = this._esc(a.id || block.id);
200
+ return `<a href="${href}" class="action-icon" data-id="${id}" role="button"><span class="cmd"><img src="${icon}" class="action-img" title="${title}" alt="${title}" data-id="${id}"></span></a>`;
201
+ });
202
+ return `<div class="action-icons" data-id="${this._esc(block.id)}">${parts.join('')}</div>`;
203
+ }
204
+
205
+ // Render tool output wrapper (always collapsed by default; wrapper visibility depends on flag)
206
+ // Inside class NodeTemplateEngine
207
+ _renderToolOutputWrapper(block) {
208
+ const extra = block.extra || {};
209
+
210
+ // IMPORTANT: keep initial tool output verbatim (HTML-ready).
211
+ // Do NOT HTML-escape here – the host already provides a safe/HTML-ready string.
212
+ // Escaping again would double-encode entities (e.g. " -> "), which
213
+ // caused visible """ in the UI instead of quotes.
214
+ const tool_output_html = (extra.tool_output != null) ? String(extra.tool_output) : '';
215
+
216
+ // Wrapper visibility: show/hide based on tool_output_visible...
217
+ const wrapperDisplay = (extra.tool_output_visible === true) ? '' : 'display:none';
218
+
219
+ const toggleTitle = (typeof trans !== 'undefined' && trans) ? trans('action.cmd.expand') : 'Expand';
220
+ const expIcon = (typeof window !== 'undefined' && window.ICON_EXPAND) ? window.ICON_EXPAND : '';
221
+
222
+ return (
223
+ `<div class='tool-output' style='${wrapperDisplay}'>` +
224
+ `<span class='toggle-cmd-output' onclick='toggleToolOutput(${this._esc(block.id)});' ` +
225
+ `title='${this._escapeHtml(toggleTitle)}' role='button'>` +
226
+ `<img src='${this._esc(expIcon)}' width='25' height='25' valign='middle'>` +
227
+ `</span>` +
228
+ // Content is initially collapsed. We intentionally do NOT escape here,
229
+ // to keep behavior consistent with ToolOutput.append/update (HTML-in).
230
+ `<div class='content' style='display:none' data-trusted='1'>${tool_output_html}</div>` +
231
+ `</div>`
232
+ );
233
+ }
234
+
235
+ // Render bot message block (md-block-markdown)
236
+ _renderBot(block) {
237
+ const id = block.id;
238
+ const out = block.output || {};
239
+ const msgId = `msg-bot-${id}`;
240
+
241
+ // timestamps intentionally disabled on frontend
242
+ // let ts = '';
243
+ // if (out.timestamp) { ... }
244
+
245
+ const personalize = !!(block && block.extra && block.extra.personalize === true);
246
+ const nameHeader = personalize ? this._nameHeader('bot', out.name || '', out.avatar_img || null) : '';
247
+
248
+ const mdText = this._escapeHtml(out.text || '');
249
+ const toolWrap = this._renderToolOutputWrapper(block);
250
+ const extras = this._renderExtras(block);
251
+ const actions = (block.extra && block.extra.footer_icons) ? this._renderActions(block) : '';
252
+ const debug = (block.extra && block.extra.debug_html) ? String(block.extra.debug_html) : '';
253
+
254
+ return (
255
+ `<div class='msg-box msg-bot' id='${msgId}'>` +
256
+ `${nameHeader}` +
257
+ `<div class='msg'>` +
258
+ `<div class='md-block' md-block-markdown='1'>${mdText}</div>` +
259
+ `<div class='msg-tool-extra'></div>` +
260
+ `${toolWrap}` +
261
+ `<div class='msg-extra'>${extras}</div>` +
262
+ `${actions}${debug}` +
263
+ `</div>` +
264
+ `</div>`
265
+ );
266
+ }
267
+
268
+ // Render one RenderBlock into HTML (may produce 1 or 2 messages – input and/or output)
269
+ renderNode(block) {
270
+ const parts = [];
271
+ if (block && block.input && block.input.text) parts.push(this._renderUser(block));
272
+ if (block && block.output && block.output.text) parts.push(this._renderBot(block));
273
+ return parts.join('');
274
+ }
275
+
276
+ // Render array of blocks
277
+ renderNodes(blocks) {
278
+ if (!Array.isArray(blocks)) return '';
279
+ const out = [];
280
+ for (let i = 0; i < blocks.length; i++) {
281
+ const b = blocks[i] || null;
282
+ if (!b) continue;
283
+ out.push(this.renderNode(b));
284
+ }
285
+ return out.join('');
286
+ }
287
+ }
@@ -0,0 +1,87 @@
1
+ // ==========================================================================
2
+ // Tool output
3
+ // ==========================================================================
4
+
5
+ class ToolOutput {
6
+
7
+ // Placeholder for loader show (can be extended by host).
8
+ showLoader() {
9
+ return;
10
+ }
11
+
12
+ // Hide spinner elements in bot messages.
13
+ hideLoader() {
14
+ const elements = document.querySelectorAll('.msg-bot');
15
+ if (elements.length > 0) elements.forEach(el => {
16
+ const s = el.querySelector('.spinner');
17
+ if (s) s.style.display = 'none';
18
+ });
19
+ }
20
+
21
+ // Begins a new tool session.
22
+ begin() {
23
+ this.showLoader();
24
+ }
25
+
26
+ // Ends the current tool session.
27
+ end() {
28
+ this.hideLoader();
29
+ }
30
+
31
+ // Enables the tool output area.
32
+ enable() {
33
+ const els = document.querySelectorAll('.tool-output');
34
+ if (els.length) els[els.length - 1].style.display = 'block';
35
+ }
36
+
37
+ // Disables the tool output area.
38
+ disable() {
39
+ const els = document.querySelectorAll('.tool-output');
40
+ if (els.length) els[els.length - 1].style.display = 'none';
41
+ }
42
+
43
+ // Append HTML into the latest tool-output content area.
44
+ append(content) {
45
+ this.hideLoader();
46
+ this.enable();
47
+ const els = document.querySelectorAll('.tool-output');
48
+ if (els.length) {
49
+ const contentEl = els[els.length - 1].querySelector('.content');
50
+ if (contentEl) contentEl.insertAdjacentHTML('beforeend', content);
51
+ }
52
+ }
53
+
54
+ // Replace inner HTML for the latest tool-output content area.
55
+ update(content) {
56
+ this.hideLoader();
57
+ this.enable();
58
+ const els = document.querySelectorAll('.tool-output');
59
+ if (els.length) {
60
+ const contentEl = els[els.length - 1].querySelector('.content');
61
+ if (contentEl) contentEl.innerHTML = content;
62
+ }
63
+ }
64
+
65
+ // Remove children from the latest tool-output content area.
66
+ clear() {
67
+ this.hideLoader();
68
+ this.enable();
69
+ const els = document.querySelectorAll('.tool-output');
70
+ if (els.length) {
71
+ const contentEl = els[els.length - 1].querySelector('.content');
72
+ if (contentEl) contentEl.replaceChildren();
73
+ }
74
+ }
75
+
76
+ // Toggle visibility of a specific tool output block by message id.
77
+ toggle(id) {
78
+ const el = document.getElementById('msg-bot-' + id);
79
+ if (!el) return;
80
+ const outputEl = el.querySelector('.tool-output');
81
+ if (!outputEl) return;
82
+ const contentEl = outputEl.querySelector('.content');
83
+ if (contentEl) contentEl.style.display = (contentEl.style.display === 'none') ? 'block' : 'none';
84
+ const toggleEl = outputEl.querySelector('.toggle-cmd-output img');
85
+ if (toggleEl) toggleEl.classList.toggle('toggle-expanded');
86
+ }
87
+ }
@@ -0,0 +1,86 @@
1
+ // ==========================================================================
2
+ // UI manager
3
+ // ==========================================================================
4
+
5
+ class UIManager {
6
+
7
+ // Replace or insert app-level CSS in a <style> tag.
8
+ updateCSS(styles) {
9
+ let style = document.getElementById('app-style');
10
+ if (!style) {
11
+ style = document.createElement('style');
12
+ style.id = 'app-style';
13
+ document.head.appendChild(style);
14
+ }
15
+ style.textContent = styles;
16
+ }
17
+
18
+ // Ensure base styles for code header sticky behavior exist.
19
+ ensureStickyHeaderStyle() {
20
+ let style = document.getElementById('code-sticky-style');
21
+ if (style) return;
22
+ style = document.createElement('style');
23
+ style.id = 'code-sticky-style';
24
+ style.textContent = [
25
+ '.code-wrapper { position: relative; }',
26
+ '.code-wrapper .code-header-wrapper { position: sticky; top: var(--code-header-sticky-top, -2px); z-index: 2; box-shadow: 0 1px 0 rgba(0,0,0,.06); }',
27
+ '.code-wrapper pre { overflow: visible; margin-top: 0; }',
28
+ '.code-wrapper pre code { display: block; white-space: pre; max-height: 100dvh; overflow: auto;',
29
+ ' overscroll-behavior: contain; -webkit-overflow-scrolling: touch; overflow-anchor: none; scrollbar-gutter: stable both-edges; scroll-behavior: auto; }',
30
+ '#_loader_.hidden { display: none !important; visibility: hidden !important; }',
31
+ '#_loader_.visible { display: block; visibility: visible; }',
32
+
33
+ /* User message collapse (uc-*) */
34
+ '.msg-box.msg-user .msg { position: relative; }',
35
+ '.msg-box.msg-user .msg > .uc-content { display: block; overflow: visible; }',
36
+ '.msg-box.msg-user .msg > .uc-content.uc-collapsed { max-height: 1000px; overflow: hidden; }',
37
+ '.msg-box.msg-user .msg > .uc-toggle { display: none; margin-top: 8px; text-align: center; cursor: pointer; user-select: none; }',
38
+ '.msg-box.msg-user .msg > .uc-toggle.visible { display: block; }',
39
+
40
+ /* Increased toggle icon size to a comfortable/default size.
41
+ Overridable via CSS var --uc-toggle-icon-size to keep host-level control. */
42
+ '.msg-box.msg-user .msg > .uc-toggle img { width: var(--uc-toggle-icon-size, 26px); height: var(--uc-toggle-icon-size, 26px); opacity: .8; }',
43
+ '.msg-box.msg-user .msg > .uc-toggle:hover img { opacity: 1; }',
44
+
45
+ /* User copy icon – single icon, top-right, visible on hover/focus */
46
+ '.msg-box.msg-user .msg .msg-copy-btn { position: absolute; top: 2px; right: 0px; z-index: 3;',
47
+ ' opacity: 0; pointer-events: none; transition: opacity .15s ease, transform .15s ease, background-color .15s ease, border-color .15s ease;',
48
+ ' border-radius: 6px; padding: 4px; line-height: 0; border: 1px solid transparent; background: transparent; }',
49
+ '.msg-box.msg-user:hover .msg .msg-copy-btn, .msg-box.msg-user .msg:focus-within .msg-copy-btn { opacity: 1; pointer-events: auto; }',
50
+ '.msg-box.msg-user .msg .msg-copy-btn:hover { transform: scale(1.06); background: var(--copy-btn-bg-hover, rgba(0,0,0,.86)); border-color: var(--copy-btn-border, rgba(0,0,0,.08)); }',
51
+ '.msg-box.msg-user .msg .msg-copy-btn.copied { background: var(--copy-btn-bg-copied, rgba(150,150,150,.12)); border-color: var(--copy-btn-border-copied, rgba(150,150,150,.35)); animation: msg-copy-pop .25s ease; }',
52
+ '.msg-box.msg-user .msg .msg-copy-btn img { display: block; width: 18px; height: 18px; }',
53
+
54
+ /* Code header action hover and click feedback (icon-only, bot messages/code blocks) */
55
+ '.code-wrapper .code-header-action.code-header-copy,',
56
+ '.code-wrapper .code-header-action.code-header-collapse { display: inline-flex; align-items: center; border-radius: 6px; padding: 2px; line-height: 0; border: 1px solid transparent; transition: transform .15s ease, background-color .15s ease, border-color .15s ease; }',
57
+ '.code-wrapper .code-header-action.code-header-copy:hover,',
58
+ '.code-wrapper .code-header-action.code-header-collapse:hover { transform: scale(1.06); background: var(--copy-btn-bg-hover, rgba(0,0,0,.76)); border-color: var(--copy-btn-border, rgba(0,0,0,.08)); }',
59
+ '.code-wrapper .code-header-action.copied { background: var(--copy-btn-bg-copied, rgba(150,150,150,.12)); border-color: var(--copy-btn-border-copied, rgba(150,150,150,.35)); animation: msg-copy-pop .25s ease; }',
60
+
61
+ /* Small scale pop when copied */
62
+ '@keyframes msg-copy-pop { 0%{ transform: scale(1); } 60%{ transform: scale(1.1); } 100%{ transform: scale(1); } }'
63
+ ].join('\n');
64
+ document.head.appendChild(style);
65
+ }
66
+
67
+ // Toggle classes controlling optional UI features.
68
+ enableEditIcons() {
69
+ document.body && document.body.classList.add('display-edit-icons');
70
+ }
71
+ disableEditIcons() {
72
+ document.body && document.body.classList.remove('display-edit-icons');
73
+ }
74
+ enableTimestamp() {
75
+ document.body && document.body.classList.add('display-timestamp');
76
+ }
77
+ disableTimestamp() {
78
+ document.body && document.body.classList.remove('display-timestamp');
79
+ }
80
+ enableBlocks() {
81
+ document.body && document.body.classList.add('display-blocks');
82
+ }
83
+ disableBlocks() {
84
+ document.body && document.body.classList.remove('display-blocks');
85
+ }
86
+ }