zerochat 7.3.0__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 (130) hide show
  1. zerochat-7.3.0.dist-info/METADATA +70 -0
  2. zerochat-7.3.0.dist-info/RECORD +130 -0
  3. zerochat-7.3.0.dist-info/WHEEL +5 -0
  4. zerochat-7.3.0.dist-info/entry_points.txt +2 -0
  5. zerochat-7.3.0.dist-info/licenses/LICENSE +34 -0
  6. zerochat-7.3.0.dist-info/top_level.txt +2 -0
  7. zerochat.py +2004 -0
  8. zerochat_runtime/__init__.py +1 -0
  9. zerochat_runtime/assets/css/base.css +199 -0
  10. zerochat_runtime/assets/css/components/composer.css +1258 -0
  11. zerochat_runtime/assets/css/components/debug.css +777 -0
  12. zerochat_runtime/assets/css/components/header.css +97 -0
  13. zerochat_runtime/assets/css/components/icons.css +39 -0
  14. zerochat_runtime/assets/css/components/markdown.css +685 -0
  15. zerochat_runtime/assets/css/components/messages.css +473 -0
  16. zerochat_runtime/assets/css/components/modals.css +1319 -0
  17. zerochat_runtime/assets/css/components/sidebar.css +622 -0
  18. zerochat_runtime/assets/css/components/tools.css +1223 -0
  19. zerochat_runtime/assets/css/layout.css +171 -0
  20. zerochat_runtime/assets/css/print.css +120 -0
  21. zerochat_runtime/assets/css/styles.css +18 -0
  22. zerochat_runtime/assets/css/theme-overrides.css +555 -0
  23. zerochat_runtime/assets/css/tokens.css +165 -0
  24. zerochat_runtime/assets/help/architecture.html +247 -0
  25. zerochat_runtime/assets/help/composer.html +134 -0
  26. zerochat_runtime/assets/help/conversations.html +105 -0
  27. zerochat_runtime/assets/help/debug.html +108 -0
  28. zerochat_runtime/assets/help/en/architecture.html +247 -0
  29. zerochat_runtime/assets/help/en/composer.html +134 -0
  30. zerochat_runtime/assets/help/en/conversations.html +104 -0
  31. zerochat_runtime/assets/help/en/debug.html +105 -0
  32. zerochat_runtime/assets/help/en/gemini-free.html +39 -0
  33. zerochat_runtime/assets/help/en/index.html +191 -0
  34. zerochat_runtime/assets/help/en/learning.html +71 -0
  35. zerochat_runtime/assets/help/en/mcp.html +143 -0
  36. zerochat_runtime/assets/help/en/openrouter-free.html +39 -0
  37. zerochat_runtime/assets/help/en/profiles.html +173 -0
  38. zerochat_runtime/assets/help/en/rag.html +130 -0
  39. zerochat_runtime/assets/help/en/reasoning-telemetry.html +108 -0
  40. zerochat_runtime/assets/help/en/tools-agent.html +133 -0
  41. zerochat_runtime/assets/help/en/webllm.html +355 -0
  42. zerochat_runtime/assets/help/gemini-free.html +39 -0
  43. zerochat_runtime/assets/help/help.css +604 -0
  44. zerochat_runtime/assets/help/help.js +51 -0
  45. zerochat_runtime/assets/help/index.html +194 -0
  46. zerochat_runtime/assets/help/learning.html +71 -0
  47. zerochat_runtime/assets/help/mcp.html +143 -0
  48. zerochat_runtime/assets/help/openrouter-free.html +39 -0
  49. zerochat_runtime/assets/help/profiles.html +174 -0
  50. zerochat_runtime/assets/help/rag.html +130 -0
  51. zerochat_runtime/assets/help/reasoning-telemetry.html +111 -0
  52. zerochat_runtime/assets/help/tools-agent.html +133 -0
  53. zerochat_runtime/assets/help/webllm.html +359 -0
  54. zerochat_runtime/assets/js/agent-core.js +1553 -0
  55. zerochat_runtime/assets/js/api.js +844 -0
  56. zerochat_runtime/assets/js/app.js +2558 -0
  57. zerochat_runtime/assets/js/attachments.js +220 -0
  58. zerochat_runtime/assets/js/charts.js +338 -0
  59. zerochat_runtime/assets/js/chat-engine.js +566 -0
  60. zerochat_runtime/assets/js/config-store.js +207 -0
  61. zerochat_runtime/assets/js/context-manager.js +584 -0
  62. zerochat_runtime/assets/js/conversation-service.js +484 -0
  63. zerochat_runtime/assets/js/cookies.js +688 -0
  64. zerochat_runtime/assets/js/data-reset-service.js +136 -0
  65. zerochat_runtime/assets/js/debug.js +508 -0
  66. zerochat_runtime/assets/js/defaults.js +16 -0
  67. zerochat_runtime/assets/js/export.js +179 -0
  68. zerochat_runtime/assets/js/file-parser.js +2088 -0
  69. zerochat_runtime/assets/js/generation-controller.js +417 -0
  70. zerochat_runtime/assets/js/i18n.js +1483 -0
  71. zerochat_runtime/assets/js/icons.js +156 -0
  72. zerochat_runtime/assets/js/ingestionEngine.js +436 -0
  73. zerochat_runtime/assets/js/markdown.js +588 -0
  74. zerochat_runtime/assets/js/mcp.js +1271 -0
  75. zerochat_runtime/assets/js/message-turns.js +62 -0
  76. zerochat_runtime/assets/js/profile-backup.js +118 -0
  77. zerochat_runtime/assets/js/profile-export-bundle.js +481 -0
  78. zerochat_runtime/assets/js/profile-repository.js +213 -0
  79. zerochat_runtime/assets/js/providers-webllm.js +486 -0
  80. zerochat_runtime/assets/js/providers.js +1560 -0
  81. zerochat_runtime/assets/js/rag-index.js +295 -0
  82. zerochat_runtime/assets/js/rag-service.js +530 -0
  83. zerochat_runtime/assets/js/rag-ui.js +997 -0
  84. zerochat_runtime/assets/js/ragStorage.js +676 -0
  85. zerochat_runtime/assets/js/sandbox.js +449 -0
  86. zerochat_runtime/assets/js/state.js +704 -0
  87. zerochat_runtime/assets/js/storage-db.js +144 -0
  88. zerochat_runtime/assets/js/tool-cards.js +330 -0
  89. zerochat_runtime/assets/js/tool-security.js +653 -0
  90. zerochat_runtime/assets/js/tools/README.md +50 -0
  91. zerochat_runtime/assets/js/tools/builtin/agent-checkpoint.tool.js +212 -0
  92. zerochat_runtime/assets/js/tools/builtin/download-pdf.tool.js +111 -0
  93. zerochat_runtime/assets/js/tools/builtin/execute-javascript.tool.js +219 -0
  94. zerochat_runtime/assets/js/tools/builtin/fetch-web-page.tool.js +112 -0
  95. zerochat_runtime/assets/js/tools/builtin/list-documents.tool.js +117 -0
  96. zerochat_runtime/assets/js/tools/builtin/read-knowledge-chunk.tool.js +129 -0
  97. zerochat_runtime/assets/js/tools/builtin/read-knowledge-image.tool.js +95 -0
  98. zerochat_runtime/assets/js/tools/builtin/render-chart.tool.js +90 -0
  99. zerochat_runtime/assets/js/tools/builtin/search-knowledge-base.tool.js +124 -0
  100. zerochat_runtime/assets/js/tools/builtin/search-web.tool.js +125 -0
  101. zerochat_runtime/assets/js/tools/tool-manifest.js +53 -0
  102. zerochat_runtime/assets/js/tools/tool-runtime.js +77 -0
  103. zerochat_runtime/assets/js/ui-composer.js +319 -0
  104. zerochat_runtime/assets/js/ui-conversation.js +655 -0
  105. zerochat_runtime/assets/js/ui-dialogs.js +132 -0
  106. zerochat_runtime/assets/js/ui-generation-status.js +119 -0
  107. zerochat_runtime/assets/js/ui-inspector.js +804 -0
  108. zerochat_runtime/assets/js/ui-mcp.js +604 -0
  109. zerochat_runtime/assets/js/ui-profiles.js +824 -0
  110. zerochat_runtime/assets/js/ui-reasoning.js +146 -0
  111. zerochat_runtime/assets/js/ui-settings.js +825 -0
  112. zerochat_runtime/assets/js/ui-shell.js +215 -0
  113. zerochat_runtime/assets/js/ui-sidebar.js +429 -0
  114. zerochat_runtime/assets/js/ui-telemetry.js +404 -0
  115. zerochat_runtime/assets/js/ui-transfer.js +262 -0
  116. zerochat_runtime/assets/js/utils.js +216 -0
  117. zerochat_runtime/assets/js/vendor/orama.browser.js +11 -0
  118. zerochat_runtime/assets/js/web-browser.js +569 -0
  119. zerochat_runtime/assets/js/web-search.js +519 -0
  120. zerochat_runtime/assets/manifest.webmanifest +20 -0
  121. zerochat_runtime/assets/services/dummy_mcp/dummy_mcp_server.py +47 -0
  122. zerochat_runtime/assets/services/dummy_mcp/service.json +22 -0
  123. zerochat_runtime/assets/services/lsp/installer.json +9 -0
  124. zerochat_runtime/assets/services/lsp/service.json +22 -0
  125. zerochat_runtime/assets/services/memory/installer.json +9 -0
  126. zerochat_runtime/assets/services/memory/service.json +24 -0
  127. zerochat_runtime/assets/services/playwright/installer.json +10 -0
  128. zerochat_runtime/assets/services/playwright/service.json +39 -0
  129. zerochat_runtime/assets/sw.js +164 -0
  130. zerochat_runtime/assets/zerochat.html +671 -0
@@ -0,0 +1,655 @@
1
+ /**
2
+ * Módulo UI de Conversación y Canvas de Mensajes (ZeroChat).
3
+ * Encargado del renderizado de la lista de mensajes, turnos de usuario, placeholders del asistente,
4
+ * tarjetas de herramientas recuperadas, acciones de mensaje (copiar, reusar, borrar, ramificar),
5
+ * listeners de código, e indicadores visuales (typing, scroll-to-bottom).
6
+ */
7
+ (function (root, factory) {
8
+ if (typeof exports === 'object' && typeof module !== 'undefined') {
9
+ module.exports = factory(require('./message-turns.js'));
10
+ } else {
11
+ root.ChatUIConversation = factory(root.ChatMessageTurns);
12
+ }
13
+ }(typeof self !== 'undefined' ? self : this, function (MessageTurns) {
14
+ 'use strict';
15
+
16
+ function resolveDep(globalName, relPath) {
17
+ if (typeof window !== 'undefined' && window[globalName]) return window[globalName];
18
+ if (typeof globalThis !== 'undefined' && globalThis[globalName]) return globalThis[globalName];
19
+ if (typeof require !== 'undefined') {
20
+ try { return require(relPath); } catch (_) {}
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function getI18n() { return resolveDep('ChatI18n', './i18n.js'); }
26
+ function getIcons() { return resolveDep('ChatIcons', './icons.js'); }
27
+ function getState() { return resolveDep('ChatState', './state.js'); }
28
+ function getMarkdown() { return resolveDep('ChatMarkdown', './markdown.js'); }
29
+ function getSandbox() { return resolveDep('ChatSandbox', './sandbox.js'); }
30
+ function getToolCards() { return resolveDep('ChatToolCards', './tool-cards.js'); }
31
+
32
+ function t(key, params) {
33
+ const I18n = getI18n();
34
+ return I18n?.t ? I18n.t(key, params) : key;
35
+ }
36
+
37
+ function getMsgIcon(name, size = 14) {
38
+ const Icons = getIcons();
39
+ if (Icons && Icons.has && Icons.has(name)) {
40
+ return Icons.get(name, { size, className: 'ui-icon' });
41
+ }
42
+ return '';
43
+ }
44
+
45
+ const extractBaseId = MessageTurns.extractBaseId;
46
+
47
+ const isDateTimeInitialTurn = MessageTurns.isDateTimeInitialTurn;
48
+
49
+ function bindMessageCopy(button, getText, titleKey = 'btn_copy_title') {
50
+ if (!button) return () => {};
51
+ let timer = null;
52
+ let disposed = false;
53
+ const restore = () => {
54
+ button.innerHTML = getMsgIcon('copy', 14);
55
+ button.title = t(titleKey);
56
+ button.setAttribute('aria-label', t(titleKey));
57
+ button.classList.remove('copied');
58
+ };
59
+ const copy = async () => {
60
+ try {
61
+ if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) return false;
62
+ await navigator.clipboard.writeText(getText());
63
+ if (disposed) return false;
64
+ clearTimeout(timer);
65
+ button.innerHTML = getMsgIcon('check', 14);
66
+ button.title = t('copied_text');
67
+ button.setAttribute('aria-label', t('copied_text'));
68
+ button.classList.add('copied');
69
+ timer = setTimeout(restore, 2000);
70
+ return true;
71
+ } catch (error) {
72
+ console.error('Error copying message:', error);
73
+ return false;
74
+ }
75
+ };
76
+ button.onclick = copy;
77
+ return () => {
78
+ disposed = true;
79
+ clearTimeout(timer);
80
+ if (button.onclick === copy) {
81
+ button.onclick = null;
82
+ restore();
83
+ }
84
+ };
85
+ }
86
+
87
+ function renderConnectionError({ row, content, actions }, message, url) {
88
+ row?.classList?.add('message-error');
89
+ if (content) {
90
+ const escape = getMarkdown().escapeHtml;
91
+ content.innerHTML = `
92
+ <div class="network-error-card" style="display:flex; align-items:flex-start; gap:0.5rem;">
93
+ <span style="flex-shrink: 0; display: inline-flex; align-items: center; color: var(--error, #ef4444);">${getMsgIcon('alert-triangle', 18)}</span>
94
+ <div>
95
+ <strong>${t('err_server_connect_title')}</strong>
96
+ <p style="margin-top: 0.25rem;">${escape(message)}</p>
97
+ <p style="margin-top: 0.25rem; font-size: 0.75rem; color: var(--text-muted);">
98
+ ${t('err_server_connect_hint', { url: escape(url) })}
99
+ </p>
100
+ </div>
101
+ </div>`;
102
+ }
103
+ if (actions) actions.style.display = 'inline-flex';
104
+ }
105
+
106
+ function injectStreamingCursor(html) {
107
+ if (!html || html.trim() === '') return '<span class="streaming-cursor"></span>';
108
+ const trimmed = html.trimEnd();
109
+ const match = trimmed.match(/(<\/(?:p|li|h[1-6]|span|code|strong|em|td|blockquote)>)$/i);
110
+ if (match) {
111
+ const closingTag = match[1];
112
+ return trimmed.slice(0, -closingTag.length) + '<span class="streaming-cursor"></span>' + closingTag;
113
+ }
114
+ return trimmed + '<span class="streaming-cursor"></span>';
115
+ }
116
+
117
+ function createAssistantBlock(container) {
118
+ if (!container) return null;
119
+ const doc = container.ownerDocument || (typeof document !== 'undefined' ? document : null);
120
+ if (!doc) return null;
121
+ const block = doc.createElement('div');
122
+ block.className = 'agentic-turn-block';
123
+ container.appendChild(block);
124
+ return block;
125
+ }
126
+
127
+ function renderAssistantBlock(block, text, { streaming = false, attachListeners } = {}) {
128
+ if (!block) return;
129
+ const html = getMarkdown().parseMarkdown(text);
130
+ block.innerHTML = streaming ? injectStreamingCursor(html) : html;
131
+ attachListeners?.(block);
132
+ }
133
+
134
+ function scrollToBottom(container) {
135
+ if (container) {
136
+ container.scrollTop = container.scrollHeight;
137
+ }
138
+ }
139
+
140
+ let typingIndicatorEl = null;
141
+
142
+ function showTypingIndicator(container) {
143
+ if (typingIndicatorEl) return;
144
+ const doc = (container && container.ownerDocument) || (typeof document !== 'undefined' ? document : null);
145
+ if (!doc) return;
146
+
147
+ const wrapper = doc.createElement('div');
148
+ wrapper.id = 'typing-indicator-wrapper';
149
+ wrapper.className = 'message-wrapper assistant';
150
+ const row = doc.createElement('div');
151
+ row.className = 'message-row assistant';
152
+ const indicator = doc.createElement('div');
153
+ indicator.className = 'typing-indicator';
154
+ indicator.innerHTML = '<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>';
155
+ row.appendChild(indicator);
156
+ wrapper.appendChild(row);
157
+ typingIndicatorEl = wrapper;
158
+
159
+ if (container) {
160
+ container.appendChild(wrapper);
161
+ scrollToBottom(container);
162
+ }
163
+ }
164
+
165
+ function removeTypingIndicator() {
166
+ if (typingIndicatorEl && typingIndicatorEl.parentNode) {
167
+ typingIndicatorEl.parentNode.removeChild(typingIndicatorEl);
168
+ }
169
+ typingIndicatorEl = null;
170
+ }
171
+
172
+ function renderStoredToolCard(tc, toolMsg) {
173
+ const ToolCards = getToolCards();
174
+ if (ToolCards?.renderHistoricalToolCard) {
175
+ return ToolCards.renderHistoricalToolCard(tc, toolMsg);
176
+ }
177
+ return null;
178
+ }
179
+
180
+ function attachListenersToContainer(container, options = {}) {
181
+ if (!container) return;
182
+ const Markdown = getMarkdown();
183
+ const Sandbox = getSandbox();
184
+
185
+ if (Markdown?.attachCopyCodeListeners) {
186
+ Markdown.attachCopyCodeListeners(container);
187
+ }
188
+
189
+ if (Markdown?.attachRunJsListeners) {
190
+ Markdown.attachRunJsListeners(container, (code, outputEl) => {
191
+ if (typeof options.onRunJs === 'function') {
192
+ options.onRunJs(code, outputEl);
193
+ } else if (Sandbox?.execute) {
194
+ Sandbox.execute(code).then(res => {
195
+ if (outputEl) {
196
+ outputEl.textContent = res.success
197
+ ? (res.result || res.logs.join('\n') || 'undefined')
198
+ : `Error: ${res.error}`;
199
+ }
200
+ });
201
+ }
202
+ });
203
+ }
204
+ }
205
+
206
+ function removeMessage(wrapper, options = {}) {
207
+ if (!wrapper) return;
208
+ const State = getState();
209
+ const isBusy = typeof options.isBusy === 'function' ? options.isBusy() : (State?.isConversationBusy?.() === true);
210
+ if (isBusy) return;
211
+
212
+ const msgId = wrapper.getAttribute('data-msg-id') || '';
213
+ const baseId = wrapper.getAttribute('data-base-id') || extractBaseId(msgId);
214
+ const rawMsgIds = wrapper.getAttribute('data-msg-ids') || '';
215
+ const explicitIds = rawMsgIds ? rawMsgIds.split(',').filter(Boolean) : [];
216
+
217
+ let removedCount = 0;
218
+ if (explicitIds.length > 0 || baseId || msgId) {
219
+ if (typeof options.removeTurn === 'function') {
220
+ const res = options.removeTurn({ msgId, baseId, explicitIds });
221
+ if (!res || !res.ok) return;
222
+ removedCount = res.removedCount || 0;
223
+ } else if (State?.removeTurn) {
224
+ const res = State.removeTurn({ msgId, baseId, explicitIds });
225
+ if (!res || !res.ok) return;
226
+ removedCount = res.removedCount || 0;
227
+ }
228
+ }
229
+ if (removedCount === 0) return;
230
+
231
+ wrapper.remove();
232
+
233
+ if (typeof options.addDebugLog === 'function') {
234
+ options.addDebugLog('system', t('msg_deleted_log', { id: msgId || baseId, count: removedCount }));
235
+ }
236
+
237
+ const messagesList = options.messagesList || wrapper.parentNode;
238
+ const welcomeBanner = options.welcomeBanner;
239
+ if (messagesList) {
240
+ const remainingMessages = messagesList.querySelectorAll('.message-wrapper');
241
+ if (remainingMessages.length === 0 && welcomeBanner) {
242
+ messagesList.appendChild(welcomeBanner);
243
+ welcomeBanner.style.display = '';
244
+ }
245
+ }
246
+
247
+ if (typeof options.saveSession === 'function') {
248
+ options.saveSession();
249
+ }
250
+ }
251
+
252
+ function appendUserMessage(container, welcomeBanner, payload = {}, callbacks = {}) {
253
+ const text = payload.text || '';
254
+ const originalPrompt = payload.originalPrompt !== undefined ? payload.originalPrompt : text;
255
+ const attachedImages = payload.attachedImages || [];
256
+ const existingMsgId = payload.existingMsgId;
257
+
258
+ if (welcomeBanner && welcomeBanner.parentNode) {
259
+ welcomeBanner.style.display = 'none';
260
+ }
261
+
262
+ const doc = (container && container.ownerDocument) || (typeof document !== 'undefined' ? document : null);
263
+ if (!doc) return existingMsgId || '';
264
+
265
+ const msgId = existingMsgId || ((typeof crypto !== 'undefined' && crypto.randomUUID)
266
+ ? 'msg_usr_' + crypto.randomUUID()
267
+ : 'msg_usr_' + Date.now() + '_' + Math.random().toString(36).slice(2, 9));
268
+
269
+ const wrapper = doc.createElement('div');
270
+ wrapper.className = 'message-wrapper user';
271
+ wrapper.setAttribute('data-msg-id', msgId);
272
+
273
+ const row = doc.createElement('div');
274
+ row.className = 'message-row user';
275
+
276
+ const contentWrapper = doc.createElement('div');
277
+ contentWrapper.className = 'message-content-wrapper';
278
+
279
+ const content = doc.createElement('div');
280
+ content.className = 'message-content';
281
+ content.textContent = text;
282
+
283
+ const Markdown = getMarkdown();
284
+
285
+ // Miniaturas visuales de imágenes adjuntas
286
+ if (attachedImages && attachedImages.length > 0) {
287
+ const imagesGrid = doc.createElement('div');
288
+ imagesGrid.className = 'message-images-grid';
289
+ attachedImages.forEach(img => {
290
+ const itemDiv = doc.createElement('div');
291
+ itemDiv.className = 'message-image-item';
292
+ const imgName = img.name || '';
293
+ const safeUrl = Markdown?.sanitizeImageUrl ? Markdown.sanitizeImageUrl(img.dataUrl) : '';
294
+ if (safeUrl) {
295
+ const imgEl = doc.createElement('img');
296
+ imgEl.src = safeUrl;
297
+ if (typeof imgEl.setAttribute === 'function') {
298
+ imgEl.setAttribute('src', safeUrl);
299
+ imgEl.setAttribute('alt', imgName);
300
+ imgEl.setAttribute('title', imgName);
301
+ }
302
+ imgEl.alt = imgName;
303
+ imgEl.className = 'message-image-thumb';
304
+ imgEl.title = imgName;
305
+ imgEl.addEventListener('click', () => {
306
+ if (typeof window !== 'undefined' && /^https?:\/\/|^blob:|^data:image\//i.test(safeUrl)) {
307
+ window.open(safeUrl, '_blank');
308
+ }
309
+ });
310
+ itemDiv.appendChild(imgEl);
311
+ }
312
+ const caption = doc.createElement('div');
313
+ caption.className = 'message-image-caption';
314
+ caption.textContent = imgName;
315
+ itemDiv.appendChild(caption);
316
+ imagesGrid.appendChild(itemDiv);
317
+ });
318
+ content.appendChild(imagesGrid);
319
+ }
320
+
321
+ const footerRow = doc.createElement('div');
322
+ footerRow.className = 'message-footer-row';
323
+
324
+ const actions = doc.createElement('div');
325
+ actions.className = 'message-actions';
326
+
327
+ const btnCopy = doc.createElement('button');
328
+ btnCopy.type = 'button';
329
+ btnCopy.className = 'btn-msg-action btn-copy-user';
330
+ btnCopy.innerHTML = getMsgIcon('copy', 14);
331
+ btnCopy.title = t('btn_copy_user_title');
332
+ btnCopy.setAttribute('aria-label', t('btn_copy_user_title'));
333
+ bindMessageCopy(btnCopy, () => originalPrompt || text, 'btn_copy_user_title');
334
+
335
+ const btnReuse = doc.createElement('button');
336
+ btnReuse.type = 'button';
337
+ btnReuse.className = 'btn-msg-action';
338
+ btnReuse.innerHTML = getMsgIcon('edit', 14);
339
+ btnReuse.title = t('btn_reuse_title');
340
+ btnReuse.setAttribute('aria-label', t('btn_reuse_title'));
341
+ btnReuse.addEventListener('click', () => {
342
+ if (typeof callbacks.onReuse === 'function') {
343
+ callbacks.onReuse(originalPrompt || text);
344
+ }
345
+ });
346
+
347
+ const btnDelete = doc.createElement('button');
348
+ btnDelete.type = 'button';
349
+ btnDelete.className = 'btn-msg-action btn-delete';
350
+ btnDelete.innerHTML = getMsgIcon('trash', 14);
351
+ btnDelete.title = t('btn_delete_usr_title');
352
+ btnDelete.setAttribute('aria-label', t('btn_delete_usr_title'));
353
+ btnDelete.addEventListener('click', () => {
354
+ if (typeof callbacks.onDelete === 'function') {
355
+ callbacks.onDelete(wrapper);
356
+ } else {
357
+ removeMessage(wrapper, { messagesList: container, welcomeBanner, ...callbacks });
358
+ }
359
+ });
360
+
361
+ actions.appendChild(btnReuse);
362
+ actions.appendChild(btnCopy);
363
+ actions.appendChild(btnDelete);
364
+ footerRow.appendChild(actions);
365
+
366
+ contentWrapper.appendChild(content);
367
+ contentWrapper.appendChild(footerRow);
368
+
369
+ row.appendChild(contentWrapper);
370
+ wrapper.appendChild(row);
371
+
372
+ if (container) {
373
+ container.appendChild(wrapper);
374
+ if (!existingMsgId && wrapper.classList?.add) {
375
+ wrapper.classList.add('is-new-message');
376
+ wrapper.addEventListener?.('animationend', () => wrapper.classList.remove('is-new-message'), { once: true });
377
+ }
378
+ scrollToBottom(container);
379
+ }
380
+
381
+ return msgId;
382
+ }
383
+
384
+ function createAssistantMessagePlaceholder(container, existingMsgId, callbacks = {}) {
385
+ const doc = (container && container.ownerDocument) || (typeof document !== 'undefined' ? document : null);
386
+ const rawId = existingMsgId || ((typeof crypto !== 'undefined' && crypto.randomUUID)
387
+ ? 'msg_ast_' + crypto.randomUUID()
388
+ : 'msg_ast_' + Date.now() + '_' + Math.random().toString(36).slice(2, 9));
389
+ const baseId = extractBaseId(rawId) || rawId;
390
+ const msgId = existingMsgId ? rawId : baseId;
391
+
392
+ if (!doc) {
393
+ return { wrapper: null, row: null, content: null, footerRow: null, actions: null, btnCopy: null, statsContainer: null, msgId };
394
+ }
395
+
396
+ const wrapper = doc.createElement('div');
397
+ wrapper.className = 'message-wrapper assistant';
398
+ wrapper.setAttribute('data-msg-id', msgId);
399
+ wrapper.setAttribute('data-base-id', baseId);
400
+
401
+ const row = doc.createElement('div');
402
+ row.className = 'message-row assistant';
403
+
404
+ const contentWrapper = doc.createElement('div');
405
+ contentWrapper.className = 'message-content-wrapper';
406
+
407
+ const content = doc.createElement('div');
408
+ content.className = 'message-content';
409
+ content.innerHTML = '<span class="streaming-cursor initial-cursor"></span>';
410
+
411
+ const footerRow = doc.createElement('div');
412
+ footerRow.className = 'message-footer-row';
413
+
414
+ const statsContainer = doc.createElement('div');
415
+ statsContainer.className = 'message-stats';
416
+ statsContainer.style.display = 'none';
417
+
418
+ const actions = doc.createElement('div');
419
+ actions.className = 'message-actions';
420
+ actions.style.display = 'none';
421
+
422
+ const btnCopy = doc.createElement('button');
423
+ btnCopy.type = 'button';
424
+ btnCopy.className = 'btn-msg-action btn-copy-full';
425
+ btnCopy.innerHTML = getMsgIcon('copy', 14);
426
+ btnCopy.title = t('btn_copy_title');
427
+ btnCopy.setAttribute('aria-label', t('btn_copy_title'));
428
+
429
+ const btnBranch = doc.createElement('button');
430
+ btnBranch.type = 'button';
431
+ btnBranch.className = 'btn-msg-action btn-branch-conversation';
432
+ btnBranch.innerHTML = getMsgIcon('git-branch', 14);
433
+ btnBranch.title = t('btn_branch_title');
434
+ btnBranch.setAttribute('aria-label', t('btn_branch_title'));
435
+ btnBranch.addEventListener('click', () => {
436
+ if (typeof callbacks.onBranch === 'function') {
437
+ callbacks.onBranch(wrapper);
438
+ }
439
+ });
440
+
441
+ const btnDelete = doc.createElement('button');
442
+ btnDelete.type = 'button';
443
+ btnDelete.className = 'btn-msg-action btn-delete';
444
+ btnDelete.innerHTML = getMsgIcon('trash', 14);
445
+ btnDelete.title = t('btn_delete_ast_title');
446
+ btnDelete.setAttribute('aria-label', t('btn_delete_ast_title'));
447
+ btnDelete.addEventListener('click', () => {
448
+ if (typeof callbacks.onDelete === 'function') {
449
+ callbacks.onDelete(wrapper);
450
+ } else {
451
+ removeMessage(wrapper, { messagesList: container, welcomeBanner: callbacks.welcomeBanner, ...callbacks });
452
+ }
453
+ });
454
+
455
+ actions.appendChild(btnBranch);
456
+ actions.appendChild(btnCopy);
457
+ actions.appendChild(btnDelete);
458
+
459
+ footerRow.appendChild(statsContainer);
460
+ footerRow.appendChild(actions);
461
+
462
+ contentWrapper.appendChild(content);
463
+ contentWrapper.appendChild(footerRow);
464
+
465
+ row.appendChild(contentWrapper);
466
+ wrapper.appendChild(row);
467
+
468
+ if (container) {
469
+ container.appendChild(wrapper);
470
+ if (!existingMsgId && wrapper.classList?.add) {
471
+ wrapper.classList.add('is-new-message');
472
+ wrapper.addEventListener?.('animationend', () => wrapper.classList.remove('is-new-message'), { once: true });
473
+ }
474
+ scrollToBottom(container);
475
+ }
476
+
477
+ return { wrapper, row, content, footerRow, actions, btnCopy, statsContainer, msgId };
478
+ }
479
+
480
+ function renderSessionMessages(elements, history, options = {}) {
481
+ const messagesList = elements?.messagesList;
482
+ if (!messagesList) return;
483
+ messagesList.innerHTML = '';
484
+
485
+ const nonSystem = (history || []).filter(m => m && m.role !== 'system');
486
+ let validMessages = nonSystem;
487
+ if (nonSystem.length >= 2 &&
488
+ isDateTimeInitialTurn(nonSystem[0]) &&
489
+ nonSystem[1].role === 'assistant' &&
490
+ (nonSystem[1].content === 'OK' || nonSystem[1].content === 'OK.')) {
491
+ validMessages = nonSystem.slice(2);
492
+ }
493
+
494
+ if (validMessages.length === 0) {
495
+ if (elements.welcomeBanner) {
496
+ messagesList.appendChild(elements.welcomeBanner);
497
+ elements.welcomeBanner.style.display = '';
498
+ }
499
+ if (typeof options.resetTelemetry === 'function') {
500
+ options.resetTelemetry();
501
+ }
502
+ return;
503
+ }
504
+
505
+ if (elements.welcomeBanner) {
506
+ elements.welcomeBanner.style.display = 'none';
507
+ }
508
+
509
+ const Markdown = getMarkdown();
510
+ let i = 0;
511
+
512
+ while (i < validMessages.length) {
513
+ const msg = validMessages[i];
514
+
515
+ if (msg.role === 'user') {
516
+ let text = '';
517
+ const images = msg.images ? [...msg.images] : [];
518
+ if (typeof msg.content === 'string') {
519
+ text = msg.content;
520
+ } else if (Array.isArray(msg.content)) {
521
+ const textPart = msg.content.find(c => c.type === 'text');
522
+ text = textPart ? textPart.text : '';
523
+ msg.content.forEach(c => {
524
+ if (c.type === 'image_url' && c.image_url?.url) {
525
+ if (!images.some(img => img.dataUrl === c.image_url.url)) {
526
+ images.push({ name: 'Imagen adjunta', dataUrl: c.image_url.url });
527
+ }
528
+ }
529
+ });
530
+ }
531
+
532
+ appendUserMessage(messagesList, elements.welcomeBanner, {
533
+ text,
534
+ originalPrompt: text,
535
+ attachedImages: images,
536
+ existingMsgId: msg.id
537
+ }, options);
538
+
539
+ i++;
540
+ } else {
541
+ const assistantGroup = [];
542
+ const firstAssistantId = msg.id || ('msg_ast_' + Date.now());
543
+
544
+ while (i < validMessages.length && validMessages[i].role !== 'user') {
545
+ const item = validMessages[i];
546
+ if (item && !item.id) {
547
+ item.id = (typeof crypto !== 'undefined' && crypto.randomUUID)
548
+ ? 'msg_ast_' + crypto.randomUUID()
549
+ : 'msg_ast_' + Date.now() + '_' + Math.random().toString(36).slice(2, 9);
550
+ }
551
+ assistantGroup.push(item);
552
+ i++;
553
+ }
554
+
555
+ const groupMsgIds = assistantGroup.map(m => m.id).filter(Boolean);
556
+ let groupBaseId = '';
557
+ for (const item of assistantGroup) {
558
+ if (item.id) {
559
+ const base = extractBaseId(item.id);
560
+ if (base && base !== item.id) {
561
+ groupBaseId = base;
562
+ break;
563
+ }
564
+ }
565
+ }
566
+ if (!groupBaseId && assistantGroup.length > 0 && assistantGroup[0].id) {
567
+ groupBaseId = extractBaseId(assistantGroup[0].id) || assistantGroup[0].id;
568
+ }
569
+
570
+ const placeholderCallbacks = {
571
+ welcomeBanner: elements.welcomeBanner,
572
+ onBranch: options.onBranch,
573
+ onDelete: options.onDelete,
574
+ ...options
575
+ };
576
+
577
+ const { wrapper, content, actions, btnCopy } = createAssistantMessagePlaceholder(
578
+ messagesList,
579
+ groupBaseId || firstAssistantId,
580
+ placeholderCallbacks
581
+ );
582
+
583
+ if (groupBaseId && wrapper) {
584
+ wrapper.setAttribute('data-base-id', groupBaseId);
585
+ }
586
+ if (groupMsgIds.length > 0 && wrapper) {
587
+ wrapper.setAttribute('data-msg-ids', groupMsgIds.join(','));
588
+ }
589
+ if (content) content.innerHTML = '';
590
+
591
+ let fullAssistantMarkdown = '';
592
+
593
+ for (let g = 0; g < assistantGroup.length; g++) {
594
+ const item = assistantGroup[g];
595
+
596
+ if (item.role === 'assistant') {
597
+ if (item.content) {
598
+ const turnBlock = createAssistantBlock(content);
599
+ renderAssistantBlock(turnBlock, item.content);
600
+ fullAssistantMarkdown += (fullAssistantMarkdown ? '\n\n' : '') + item.content;
601
+ }
602
+
603
+ if (Array.isArray(item.tool_calls) && item.tool_calls.length > 0) {
604
+ item.tool_calls.forEach(tc => {
605
+ const toolMsg = assistantGroup.find(m => m.role === 'tool' && (m.tool_call_id === tc.id || m.name === tc.function?.name));
606
+ const cardEl = renderStoredToolCard(tc, toolMsg);
607
+ if (cardEl && content) {
608
+ content.appendChild(cardEl);
609
+ }
610
+ });
611
+ }
612
+ }
613
+ }
614
+
615
+ if (content && content.children.length === 0) {
616
+ const emptyText = t('no_text_response');
617
+ const safeEmpty = Markdown?.escapeHtml ? Markdown.escapeHtml(emptyText) : emptyText;
618
+ content.innerHTML = `<p><em>${safeEmpty}</em></p>`;
619
+ }
620
+
621
+ bindMessageCopy(btnCopy, () => fullAssistantMarkdown || (content ? content.innerText : ''));
622
+
623
+ if (actions) actions.style.display = 'inline-flex';
624
+
625
+ if (content) {
626
+ attachListenersToContainer(content, options);
627
+ }
628
+ }
629
+ }
630
+
631
+ scrollToBottom(messagesList);
632
+ if (typeof options.updateTelemetry === 'function') {
633
+ options.updateTelemetry();
634
+ }
635
+ }
636
+
637
+ return {
638
+ bindMessageCopy,
639
+ renderConnectionError,
640
+ injectStreamingCursor,
641
+ createAssistantBlock,
642
+ renderAssistantBlock,
643
+ extractBaseId,
644
+ isDateTimeInitialTurn,
645
+ scrollToBottom,
646
+ showTypingIndicator,
647
+ removeTypingIndicator,
648
+ renderStoredToolCard,
649
+ attachListenersToContainer,
650
+ removeMessage,
651
+ appendUserMessage,
652
+ createAssistantMessagePlaceholder,
653
+ renderSessionMessages
654
+ };
655
+ }));