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.
- zerochat-7.3.0.dist-info/METADATA +70 -0
- zerochat-7.3.0.dist-info/RECORD +130 -0
- zerochat-7.3.0.dist-info/WHEEL +5 -0
- zerochat-7.3.0.dist-info/entry_points.txt +2 -0
- zerochat-7.3.0.dist-info/licenses/LICENSE +34 -0
- zerochat-7.3.0.dist-info/top_level.txt +2 -0
- zerochat.py +2004 -0
- zerochat_runtime/__init__.py +1 -0
- zerochat_runtime/assets/css/base.css +199 -0
- zerochat_runtime/assets/css/components/composer.css +1258 -0
- zerochat_runtime/assets/css/components/debug.css +777 -0
- zerochat_runtime/assets/css/components/header.css +97 -0
- zerochat_runtime/assets/css/components/icons.css +39 -0
- zerochat_runtime/assets/css/components/markdown.css +685 -0
- zerochat_runtime/assets/css/components/messages.css +473 -0
- zerochat_runtime/assets/css/components/modals.css +1319 -0
- zerochat_runtime/assets/css/components/sidebar.css +622 -0
- zerochat_runtime/assets/css/components/tools.css +1223 -0
- zerochat_runtime/assets/css/layout.css +171 -0
- zerochat_runtime/assets/css/print.css +120 -0
- zerochat_runtime/assets/css/styles.css +18 -0
- zerochat_runtime/assets/css/theme-overrides.css +555 -0
- zerochat_runtime/assets/css/tokens.css +165 -0
- zerochat_runtime/assets/help/architecture.html +247 -0
- zerochat_runtime/assets/help/composer.html +134 -0
- zerochat_runtime/assets/help/conversations.html +105 -0
- zerochat_runtime/assets/help/debug.html +108 -0
- zerochat_runtime/assets/help/en/architecture.html +247 -0
- zerochat_runtime/assets/help/en/composer.html +134 -0
- zerochat_runtime/assets/help/en/conversations.html +104 -0
- zerochat_runtime/assets/help/en/debug.html +105 -0
- zerochat_runtime/assets/help/en/gemini-free.html +39 -0
- zerochat_runtime/assets/help/en/index.html +191 -0
- zerochat_runtime/assets/help/en/learning.html +71 -0
- zerochat_runtime/assets/help/en/mcp.html +143 -0
- zerochat_runtime/assets/help/en/openrouter-free.html +39 -0
- zerochat_runtime/assets/help/en/profiles.html +173 -0
- zerochat_runtime/assets/help/en/rag.html +130 -0
- zerochat_runtime/assets/help/en/reasoning-telemetry.html +108 -0
- zerochat_runtime/assets/help/en/tools-agent.html +133 -0
- zerochat_runtime/assets/help/en/webllm.html +355 -0
- zerochat_runtime/assets/help/gemini-free.html +39 -0
- zerochat_runtime/assets/help/help.css +604 -0
- zerochat_runtime/assets/help/help.js +51 -0
- zerochat_runtime/assets/help/index.html +194 -0
- zerochat_runtime/assets/help/learning.html +71 -0
- zerochat_runtime/assets/help/mcp.html +143 -0
- zerochat_runtime/assets/help/openrouter-free.html +39 -0
- zerochat_runtime/assets/help/profiles.html +174 -0
- zerochat_runtime/assets/help/rag.html +130 -0
- zerochat_runtime/assets/help/reasoning-telemetry.html +111 -0
- zerochat_runtime/assets/help/tools-agent.html +133 -0
- zerochat_runtime/assets/help/webllm.html +359 -0
- zerochat_runtime/assets/js/agent-core.js +1553 -0
- zerochat_runtime/assets/js/api.js +844 -0
- zerochat_runtime/assets/js/app.js +2558 -0
- zerochat_runtime/assets/js/attachments.js +220 -0
- zerochat_runtime/assets/js/charts.js +338 -0
- zerochat_runtime/assets/js/chat-engine.js +566 -0
- zerochat_runtime/assets/js/config-store.js +207 -0
- zerochat_runtime/assets/js/context-manager.js +584 -0
- zerochat_runtime/assets/js/conversation-service.js +484 -0
- zerochat_runtime/assets/js/cookies.js +688 -0
- zerochat_runtime/assets/js/data-reset-service.js +136 -0
- zerochat_runtime/assets/js/debug.js +508 -0
- zerochat_runtime/assets/js/defaults.js +16 -0
- zerochat_runtime/assets/js/export.js +179 -0
- zerochat_runtime/assets/js/file-parser.js +2088 -0
- zerochat_runtime/assets/js/generation-controller.js +417 -0
- zerochat_runtime/assets/js/i18n.js +1483 -0
- zerochat_runtime/assets/js/icons.js +156 -0
- zerochat_runtime/assets/js/ingestionEngine.js +436 -0
- zerochat_runtime/assets/js/markdown.js +588 -0
- zerochat_runtime/assets/js/mcp.js +1271 -0
- zerochat_runtime/assets/js/message-turns.js +62 -0
- zerochat_runtime/assets/js/profile-backup.js +118 -0
- zerochat_runtime/assets/js/profile-export-bundle.js +481 -0
- zerochat_runtime/assets/js/profile-repository.js +213 -0
- zerochat_runtime/assets/js/providers-webllm.js +486 -0
- zerochat_runtime/assets/js/providers.js +1560 -0
- zerochat_runtime/assets/js/rag-index.js +295 -0
- zerochat_runtime/assets/js/rag-service.js +530 -0
- zerochat_runtime/assets/js/rag-ui.js +997 -0
- zerochat_runtime/assets/js/ragStorage.js +676 -0
- zerochat_runtime/assets/js/sandbox.js +449 -0
- zerochat_runtime/assets/js/state.js +704 -0
- zerochat_runtime/assets/js/storage-db.js +144 -0
- zerochat_runtime/assets/js/tool-cards.js +330 -0
- zerochat_runtime/assets/js/tool-security.js +653 -0
- zerochat_runtime/assets/js/tools/README.md +50 -0
- zerochat_runtime/assets/js/tools/builtin/agent-checkpoint.tool.js +212 -0
- zerochat_runtime/assets/js/tools/builtin/download-pdf.tool.js +111 -0
- zerochat_runtime/assets/js/tools/builtin/execute-javascript.tool.js +219 -0
- zerochat_runtime/assets/js/tools/builtin/fetch-web-page.tool.js +112 -0
- zerochat_runtime/assets/js/tools/builtin/list-documents.tool.js +117 -0
- zerochat_runtime/assets/js/tools/builtin/read-knowledge-chunk.tool.js +129 -0
- zerochat_runtime/assets/js/tools/builtin/read-knowledge-image.tool.js +95 -0
- zerochat_runtime/assets/js/tools/builtin/render-chart.tool.js +90 -0
- zerochat_runtime/assets/js/tools/builtin/search-knowledge-base.tool.js +124 -0
- zerochat_runtime/assets/js/tools/builtin/search-web.tool.js +125 -0
- zerochat_runtime/assets/js/tools/tool-manifest.js +53 -0
- zerochat_runtime/assets/js/tools/tool-runtime.js +77 -0
- zerochat_runtime/assets/js/ui-composer.js +319 -0
- zerochat_runtime/assets/js/ui-conversation.js +655 -0
- zerochat_runtime/assets/js/ui-dialogs.js +132 -0
- zerochat_runtime/assets/js/ui-generation-status.js +119 -0
- zerochat_runtime/assets/js/ui-inspector.js +804 -0
- zerochat_runtime/assets/js/ui-mcp.js +604 -0
- zerochat_runtime/assets/js/ui-profiles.js +824 -0
- zerochat_runtime/assets/js/ui-reasoning.js +146 -0
- zerochat_runtime/assets/js/ui-settings.js +825 -0
- zerochat_runtime/assets/js/ui-shell.js +215 -0
- zerochat_runtime/assets/js/ui-sidebar.js +429 -0
- zerochat_runtime/assets/js/ui-telemetry.js +404 -0
- zerochat_runtime/assets/js/ui-transfer.js +262 -0
- zerochat_runtime/assets/js/utils.js +216 -0
- zerochat_runtime/assets/js/vendor/orama.browser.js +11 -0
- zerochat_runtime/assets/js/web-browser.js +569 -0
- zerochat_runtime/assets/js/web-search.js +519 -0
- zerochat_runtime/assets/manifest.webmanifest +20 -0
- zerochat_runtime/assets/services/dummy_mcp/dummy_mcp_server.py +47 -0
- zerochat_runtime/assets/services/dummy_mcp/service.json +22 -0
- zerochat_runtime/assets/services/lsp/installer.json +9 -0
- zerochat_runtime/assets/services/lsp/service.json +22 -0
- zerochat_runtime/assets/services/memory/installer.json +9 -0
- zerochat_runtime/assets/services/memory/service.json +24 -0
- zerochat_runtime/assets/services/playwright/installer.json +10 -0
- zerochat_runtime/assets/services/playwright/service.json +39 -0
- zerochat_runtime/assets/sw.js +164 -0
- zerochat_runtime/assets/zerochat.html +671 -0
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de parseo y renderizado de Markdown (ChatMarkdown) con soporte i18n.
|
|
3
|
+
* Parser directo por bloques (Block Tokenizer).
|
|
4
|
+
* - Soporta bloques de código con cabecera, botón de copia y botón de ejecución JS local.
|
|
5
|
+
* - Soporta bloques de razonamiento <think>...</think> (DeepSeek-R1, QwQ, Ollama).
|
|
6
|
+
* - Soporta listas, código inline, negrita, cursiva, encabezados, citas y enlaces.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
(function (root, factory) {
|
|
10
|
+
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
11
|
+
module.exports = factory();
|
|
12
|
+
} else {
|
|
13
|
+
root.ChatMarkdown = factory();
|
|
14
|
+
}
|
|
15
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const Sandbox = typeof window !== 'undefined' ? (window.ChatSandbox || {}) : {};
|
|
19
|
+
function getI18n() {
|
|
20
|
+
if (typeof window !== 'undefined' && window.ChatI18n) return window.ChatI18n;
|
|
21
|
+
if (typeof require !== 'undefined') {
|
|
22
|
+
try { return require('./i18n.js'); } catch (e) { return {}; }
|
|
23
|
+
}
|
|
24
|
+
return { uiText: (_key, fallback) => fallback };
|
|
25
|
+
}
|
|
26
|
+
const resolvedRagImagesCache = new Map();
|
|
27
|
+
const RAG_IMG_PLACEHOLDER = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1"%3E%3C/svg%3E';
|
|
28
|
+
|
|
29
|
+
function getUtils() {
|
|
30
|
+
if (typeof window !== 'undefined' && window.ChatUtils) return window.ChatUtils;
|
|
31
|
+
if (typeof require !== 'undefined') {
|
|
32
|
+
try { return require('./utils.js'); } catch (e) { return null; }
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function escapeHtml(str) {
|
|
38
|
+
const Utils = getUtils();
|
|
39
|
+
if (Utils && typeof Utils.escapeHtml === 'function') return Utils.escapeHtml(str);
|
|
40
|
+
return str == null ? '' : String(str);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sanitiza URLs para prevenir ataques XSS vía javascript:, data:, vbscript: o URLs maliciosas.
|
|
45
|
+
* @param {string} rawUrl - URL sin procesar
|
|
46
|
+
* @returns {string} - URL segura o '#' si es inválida
|
|
47
|
+
*/
|
|
48
|
+
function sanitizeUrl(rawUrl) {
|
|
49
|
+
const Utils = getUtils();
|
|
50
|
+
return Utils?.sanitizeUrl ? Utils.sanitizeUrl(rawUrl) : '#';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sanitiza URLs de imágenes (https, http, data:image, blob) para evitar inyecciones XSS.
|
|
55
|
+
* @param {string} rawUrl - URL de imagen
|
|
56
|
+
* @returns {string} - URL segura o '' si es inválida
|
|
57
|
+
*/
|
|
58
|
+
function sanitizeImageUrl(rawUrl) {
|
|
59
|
+
const Utils = getUtils();
|
|
60
|
+
return Utils?.sanitizeImageUrl ? Utils.sanitizeImageUrl(rawUrl) : '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseInlineMarkdown(text) {
|
|
64
|
+
if (!text) return '';
|
|
65
|
+
let p = text;
|
|
66
|
+
p = p.replace(/`([^`\n]+)`/g, '<code>$1</code>');
|
|
67
|
+
p = p.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
68
|
+
p = p.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
69
|
+
p = p.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
|
70
|
+
p = p.replace(/\*([^\*\n]+)\*/g, '<em>$1</em>');
|
|
71
|
+
p = p.replace(/_([^_\n]+)_/g, '<em>$1</em>');
|
|
72
|
+
// Imágenes: 
|
|
73
|
+
p = p.replace(/!\[([^\]]*)\]\(([^)]+)\)/gi, function (match, altText, url) {
|
|
74
|
+
const safeSrc = sanitizeImageUrl(url);
|
|
75
|
+
if (!safeSrc) return altText || '';
|
|
76
|
+
const cleanAlt = escapeHtml(altText ? altText.trim() : 'Imagen');
|
|
77
|
+
if (safeSrc.startsWith('rag-image://')) {
|
|
78
|
+
const cachedUrl = resolvedRagImagesCache.get(safeSrc);
|
|
79
|
+
const displaySrc = cachedUrl || RAG_IMG_PLACEHOLDER;
|
|
80
|
+
const resolvedClass = cachedUrl ? ' rag-resolved-image' : '';
|
|
81
|
+
return `<img class="chat-embedded-image inline-image${resolvedClass}" data-rag-src="${safeSrc}" src="${displaySrc}" alt="${cleanAlt}" loading="lazy" />`;
|
|
82
|
+
}
|
|
83
|
+
return `<img class="chat-embedded-image inline-image" src="${safeSrc}" alt="${cleanAlt}" loading="lazy" />`;
|
|
84
|
+
});
|
|
85
|
+
// Enlaces: [texto](url)
|
|
86
|
+
p = p.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, function (match, linkText, url) {
|
|
87
|
+
const safeHref = sanitizeUrl(url);
|
|
88
|
+
if (safeHref === '#') return linkText;
|
|
89
|
+
return `<a href="${safeHref}" target="_blank" rel="noopener noreferrer">${linkText}</a>`;
|
|
90
|
+
});
|
|
91
|
+
return p;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function splitTableRow(rowStr) {
|
|
95
|
+
let raw = rowStr.trim();
|
|
96
|
+
if (raw.startsWith('|')) raw = raw.substring(1);
|
|
97
|
+
if (raw.endsWith('|')) raw = raw.substring(0, raw.length - 1);
|
|
98
|
+
|
|
99
|
+
const cells = [];
|
|
100
|
+
let current = '';
|
|
101
|
+
let inBacktick = false;
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < raw.length; i++) {
|
|
104
|
+
const ch = raw[i];
|
|
105
|
+
if (ch === '`') {
|
|
106
|
+
inBacktick = !inBacktick;
|
|
107
|
+
current += ch;
|
|
108
|
+
} else if (ch === '\\' && i + 1 < raw.length && raw[i + 1] === '|') {
|
|
109
|
+
current += '|';
|
|
110
|
+
i++;
|
|
111
|
+
} else if (ch === '|' && !inBacktick) {
|
|
112
|
+
cells.push(current.trim());
|
|
113
|
+
current = '';
|
|
114
|
+
} else {
|
|
115
|
+
current += ch;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
cells.push(current.trim());
|
|
119
|
+
return cells;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isTableDelimiterRow(rowStr) {
|
|
123
|
+
const trimmed = rowStr.trim();
|
|
124
|
+
if (!trimmed.includes('-')) return false;
|
|
125
|
+
const cells = splitTableRow(trimmed);
|
|
126
|
+
if (cells.length === 0) return false;
|
|
127
|
+
return cells.every(c => /^:?-+:?$/.test(c.trim()));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseAlignments(delimiterRowStr) {
|
|
131
|
+
const cells = splitTableRow(delimiterRowStr);
|
|
132
|
+
return cells.map(c => {
|
|
133
|
+
const trimmed = c.trim();
|
|
134
|
+
const left = trimmed.startsWith(':');
|
|
135
|
+
const right = trimmed.endsWith(':');
|
|
136
|
+
if (left && right) return 'center';
|
|
137
|
+
if (right) return 'right';
|
|
138
|
+
if (left) return 'left';
|
|
139
|
+
return '';
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function renderTableHtml(headerRow, delimiterRow, bodyRows) {
|
|
144
|
+
const headerCells = splitTableRow(headerRow);
|
|
145
|
+
const aligns = parseAlignments(delimiterRow);
|
|
146
|
+
const numCols = Math.max(headerCells.length, aligns.length);
|
|
147
|
+
|
|
148
|
+
let html = '<div class="table-container"><table class="markdown-table"><thead><tr>';
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < numCols; i++) {
|
|
151
|
+
const cellText = headerCells[i] !== undefined ? headerCells[i] : '';
|
|
152
|
+
const align = aligns[i] ? ` style="text-align: ${aligns[i]};"` : '';
|
|
153
|
+
html += `<th${align}>${parseInlineMarkdown(cellText)}</th>`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
html += '</tr></thead>';
|
|
157
|
+
|
|
158
|
+
if (bodyRows && bodyRows.length > 0) {
|
|
159
|
+
html += '<tbody>';
|
|
160
|
+
for (const row of bodyRows) {
|
|
161
|
+
const cells = splitTableRow(row);
|
|
162
|
+
html += '<tr>';
|
|
163
|
+
for (let i = 0; i < numCols; i++) {
|
|
164
|
+
const cellText = cells[i] !== undefined ? cells[i] : '';
|
|
165
|
+
const align = aligns[i] ? ` style="text-align: ${aligns[i]};"` : '';
|
|
166
|
+
html += `<td${align}>${parseInlineMarkdown(cellText)}</td>`;
|
|
167
|
+
}
|
|
168
|
+
html += '</tr>';
|
|
169
|
+
}
|
|
170
|
+
html += '</tbody>';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
html += '</table></div>';
|
|
174
|
+
return html;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseMarkdownTables(text) {
|
|
178
|
+
const lines = text.split('\n');
|
|
179
|
+
const result = [];
|
|
180
|
+
let i = 0;
|
|
181
|
+
|
|
182
|
+
while (i < lines.length) {
|
|
183
|
+
const line = lines[i];
|
|
184
|
+
const nextLine = (i + 1 < lines.length) ? lines[i + 1] : null;
|
|
185
|
+
|
|
186
|
+
if (line.includes('|') && nextLine && isTableDelimiterRow(nextLine)) {
|
|
187
|
+
const headerRow = line;
|
|
188
|
+
const delimiterRow = nextLine;
|
|
189
|
+
const bodyRows = [];
|
|
190
|
+
i += 2;
|
|
191
|
+
|
|
192
|
+
while (i < lines.length && lines[i].trim() !== '' && lines[i].includes('|')) {
|
|
193
|
+
bodyRows.push(lines[i]);
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
result.push('\n' + renderTableHtml(headerRow, delimiterRow, bodyRows) + '\n');
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
result.push(line);
|
|
202
|
+
i++;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Parsea segmentos de texto normales (fuera de bloques de código).
|
|
210
|
+
*/
|
|
211
|
+
function parseTextMarkdown(text) {
|
|
212
|
+
if (!text) return '';
|
|
213
|
+
|
|
214
|
+
let p = escapeHtml(text);
|
|
215
|
+
|
|
216
|
+
// Reglas horizontales (---, ***, ___)
|
|
217
|
+
p = p.replace(/^[ \t]*(?:---|\*\*\*|___)[ \t]*$/gm, '<hr>');
|
|
218
|
+
|
|
219
|
+
const thoughtTitle = getI18n().uiText('md_thought_title', '💭 Proceso de razonamiento');
|
|
220
|
+
const thoughtReasoning = getI18n().uiText('md_thought_reasoning', '💭 Razonando...');
|
|
221
|
+
|
|
222
|
+
// 1. Bloques de pensamiento <think>...</think>, <thought>...</thought>, <reasoning>...</reasoning>
|
|
223
|
+
p = p.replace(/<(think|thought|reasoning)>([\s\S]*?)<\/\1>/gi, function (match, tag, thought) {
|
|
224
|
+
return `
|
|
225
|
+
<details class="thought-block" open>
|
|
226
|
+
<summary class="thought-summary">
|
|
227
|
+
<span>${thoughtTitle}</span>
|
|
228
|
+
</summary>
|
|
229
|
+
<div class="thought-content">${thought.trim().replace(/\n/g, '<br>')}</div>
|
|
230
|
+
</details>
|
|
231
|
+
`;
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
p = p.replace(/<(think|thought|reasoning)>([\s\S]*)$/gi, function (match, tag, thought) {
|
|
235
|
+
return `
|
|
236
|
+
<details class="thought-block" open>
|
|
237
|
+
<summary class="thought-summary">
|
|
238
|
+
<span>${thoughtReasoning}</span>
|
|
239
|
+
</summary>
|
|
240
|
+
<div class="thought-content">${thought.trim().replace(/\n/g, '<br>')}</div>
|
|
241
|
+
</details>
|
|
242
|
+
`;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 2. Tablas Markdown (GFM Tables)
|
|
246
|
+
p = parseMarkdownTables(p);
|
|
247
|
+
|
|
248
|
+
// 3. Código inline `código`
|
|
249
|
+
p = p.replace(/`([^`\n]+)`/g, function (match, code) {
|
|
250
|
+
return `<code>${code}</code>`;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// 4. Encabezados (# Titulo)
|
|
254
|
+
p = p.replace(/^### (.*$)/gm, '<h3>$1</h3>');
|
|
255
|
+
p = p.replace(/^## (.*$)/gm, '<h2>$1</h2>');
|
|
256
|
+
p = p.replace(/^# (.*$)/gm, '<h1>$1</h1>');
|
|
257
|
+
|
|
258
|
+
// 5. Citas / Blockquotes (> texto)
|
|
259
|
+
p = p.replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>');
|
|
260
|
+
|
|
261
|
+
// 6. Listas no ordenadas (- item o * item)
|
|
262
|
+
p = p.replace(/(?:^[ \t]*[*-][ \t]+.+(?:\n|$))+/gm, function (match) {
|
|
263
|
+
const lines = match.trim().split('\n');
|
|
264
|
+
const listItems = lines.map(line => {
|
|
265
|
+
const itemContent = line.replace(/^[ \t]*[*-][ \t]+/, '');
|
|
266
|
+
return `<li>${itemContent}</li>`;
|
|
267
|
+
}).join('');
|
|
268
|
+
return `<ul>${listItems}</ul>\n`;
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// 7. Listas ordenadas (1. item)
|
|
272
|
+
p = p.replace(/(?:^[ \t]*\d+\.[ \t]+.+(?:\n|$))+/gm, function (match) {
|
|
273
|
+
const lines = match.trim().split('\n');
|
|
274
|
+
const listItems = lines.map(line => {
|
|
275
|
+
const itemContent = line.replace(/^[ \t]*\d+\.[ \t]+/, '');
|
|
276
|
+
return `<li>${itemContent}</li>`;
|
|
277
|
+
}).join('');
|
|
278
|
+
return `<ol>${listItems}</ol>\n`;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// 8. Imágenes Markdown: 
|
|
282
|
+
p = p.replace(/!\[([^\]]*)\]\(([^)]+)\)/gi, function (match, altText, url) {
|
|
283
|
+
const safeSrc = sanitizeImageUrl(url);
|
|
284
|
+
if (!safeSrc) return altText || '';
|
|
285
|
+
const cleanAlt = altText ? escapeHtml(altText.trim()) : 'Imagen';
|
|
286
|
+
const caption = cleanAlt && !cleanAlt.startsWith('#img') ? `<figcaption class="chat-image-caption">${cleanAlt}</figcaption>` : '';
|
|
287
|
+
if (safeSrc.startsWith('rag-image://')) {
|
|
288
|
+
const cachedUrl = resolvedRagImagesCache.get(safeSrc);
|
|
289
|
+
const displaySrc = cachedUrl || RAG_IMG_PLACEHOLDER;
|
|
290
|
+
const resolvedClass = cachedUrl ? ' rag-resolved-image' : '';
|
|
291
|
+
return `\n<figure class="chat-image-figure"><img class="chat-embedded-image${resolvedClass}" data-rag-src="${safeSrc}" src="${displaySrc}" alt="${cleanAlt}" loading="lazy" />${caption}</figure>\n`;
|
|
292
|
+
}
|
|
293
|
+
return `\n<figure class="chat-image-figure"><img class="chat-embedded-image" src="${safeSrc}" alt="${cleanAlt}" loading="lazy" />${caption}</figure>\n`;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// 10. Enlaces [texto](url)
|
|
297
|
+
p = p.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
298
|
+
|
|
299
|
+
// 11. Negrita y cursiva
|
|
300
|
+
p = p.replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>');
|
|
301
|
+
p = p.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
302
|
+
p = p.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
|
303
|
+
p = p.replace(/\*([^\*\n]+)\*/g, '<em>$1</em>');
|
|
304
|
+
p = p.replace(/(?:^|\s)_([^_<>\n]+)_(?=\s|$|[.,;:!?\)])/g, ' <em>$1</em>');
|
|
305
|
+
|
|
306
|
+
// 12. Párrafos y saltos de línea
|
|
307
|
+
const chunks = p.split(/\n{2,}/);
|
|
308
|
+
return chunks.map(chunk => {
|
|
309
|
+
const trimmed = chunk.trim();
|
|
310
|
+
if (!trimmed) return '';
|
|
311
|
+
if (
|
|
312
|
+
trimmed.startsWith('<h1') ||
|
|
313
|
+
trimmed.startsWith('<h2') ||
|
|
314
|
+
trimmed.startsWith('<h3') ||
|
|
315
|
+
trimmed.startsWith('<ul') ||
|
|
316
|
+
trimmed.startsWith('<ol') ||
|
|
317
|
+
trimmed.startsWith('<blockquote') ||
|
|
318
|
+
trimmed.startsWith('<details') ||
|
|
319
|
+
trimmed.startsWith('<figure') ||
|
|
320
|
+
trimmed.startsWith('<hr') ||
|
|
321
|
+
trimmed.startsWith('<div class="table-container"')
|
|
322
|
+
) {
|
|
323
|
+
return trimmed;
|
|
324
|
+
}
|
|
325
|
+
return `<p>${trimmed.replace(/\n/g, '<br>')}</p>`;
|
|
326
|
+
}).filter(Boolean).join('');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Parser principal de Markdown estructurado por bloques.
|
|
331
|
+
*/
|
|
332
|
+
function parseMarkdown(markdown) {
|
|
333
|
+
if (!markdown) return '';
|
|
334
|
+
|
|
335
|
+
const normalized = markdown.replace(/\r\n/g, '\n');
|
|
336
|
+
const lines = normalized.split('\n');
|
|
337
|
+
|
|
338
|
+
let htmlResult = '';
|
|
339
|
+
let inCodeBlock = false;
|
|
340
|
+
let codeLang = '';
|
|
341
|
+
let codeLines = [];
|
|
342
|
+
let textBuffer = [];
|
|
343
|
+
|
|
344
|
+
const runTitle = getI18n().uiText('md_run_js_title', 'Ejecutar JavaScript (en worker local)');
|
|
345
|
+
const runBtn = getI18n().uiText('md_run_js_btn', 'Ejecutar JS');
|
|
346
|
+
const copyTitle = getI18n().uiText('md_copy_code_title', 'Copiar código');
|
|
347
|
+
const copyBtn = getI18n().uiText('md_copy_code_btn', 'Copiar');
|
|
348
|
+
|
|
349
|
+
function flushTextBuffer() {
|
|
350
|
+
if (textBuffer.length === 0) return;
|
|
351
|
+
htmlResult += parseTextMarkdown(textBuffer.join('\n'));
|
|
352
|
+
textBuffer = [];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function flushCodeBlock() {
|
|
356
|
+
const rawCode = codeLines.join('\n');
|
|
357
|
+
const safeLang = escapeHtml(codeLang || 'javascript');
|
|
358
|
+
const safeCode = escapeHtml(rawCode);
|
|
359
|
+
const rawCodeAttr = encodeURIComponent(rawCode);
|
|
360
|
+
const isJs = safeLang.toLowerCase() === 'javascript' || safeLang.toLowerCase() === 'js';
|
|
361
|
+
|
|
362
|
+
const runButtonHtml = isJs ? `
|
|
363
|
+
<button class="btn-run-code" data-code="${rawCodeAttr}" title="${runTitle}">
|
|
364
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
|
|
365
|
+
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
366
|
+
</svg>
|
|
367
|
+
<span>${runBtn}</span>
|
|
368
|
+
</button>
|
|
369
|
+
` : '';
|
|
370
|
+
|
|
371
|
+
htmlResult += `
|
|
372
|
+
<div class="code-block-container">
|
|
373
|
+
<div class="code-block-header">
|
|
374
|
+
<span class="code-lang">${safeLang}</span>
|
|
375
|
+
<div class="code-block-actions">
|
|
376
|
+
${runButtonHtml}
|
|
377
|
+
<button class="btn-copy-code" data-code="${rawCodeAttr}" title="${copyTitle}">
|
|
378
|
+
<svg class="icon-copy" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
379
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
380
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
381
|
+
</svg>
|
|
382
|
+
<span>${copyBtn}</span>
|
|
383
|
+
</button>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<pre><code class="language-${safeLang}">${safeCode}</code></pre>
|
|
387
|
+
<div class="code-output-container" style="display: none;"></div>
|
|
388
|
+
</div>
|
|
389
|
+
`;
|
|
390
|
+
codeLines = [];
|
|
391
|
+
codeLang = '';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
for (let i = 0; i < lines.length; i++) {
|
|
395
|
+
const line = lines[i];
|
|
396
|
+
const fenceMatch = line.match(/^[ \t]*```([a-zA-Z0-9_+#.-]*)\s*$/);
|
|
397
|
+
|
|
398
|
+
if (fenceMatch) {
|
|
399
|
+
if (!inCodeBlock) {
|
|
400
|
+
flushTextBuffer();
|
|
401
|
+
inCodeBlock = true;
|
|
402
|
+
codeLang = fenceMatch[1] ? fenceMatch[1].trim() : '';
|
|
403
|
+
codeLines = [];
|
|
404
|
+
} else {
|
|
405
|
+
flushCodeBlock();
|
|
406
|
+
inCodeBlock = false;
|
|
407
|
+
}
|
|
408
|
+
} else if (inCodeBlock) {
|
|
409
|
+
codeLines.push(line);
|
|
410
|
+
} else {
|
|
411
|
+
textBuffer.push(line);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (inCodeBlock) {
|
|
416
|
+
flushCodeBlock();
|
|
417
|
+
} else {
|
|
418
|
+
flushTextBuffer();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return htmlResult;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function attachCopyCodeListeners(container) {
|
|
425
|
+
if (!container) return;
|
|
426
|
+
|
|
427
|
+
// 1. Botones de copiar código
|
|
428
|
+
container.querySelectorAll('.btn-copy-code').forEach(function (button) {
|
|
429
|
+
if (button.dataset.listenerAttached) return;
|
|
430
|
+
button.dataset.listenerAttached = 'true';
|
|
431
|
+
|
|
432
|
+
button.addEventListener('click', async function () {
|
|
433
|
+
const rawCode = decodeURIComponent(button.getAttribute('data-code') || '');
|
|
434
|
+
try {
|
|
435
|
+
await navigator.clipboard.writeText(rawCode);
|
|
436
|
+
const span = button.querySelector('span');
|
|
437
|
+
const originalText = span.textContent;
|
|
438
|
+
span.textContent = getI18n().uiText('copied_text', '¡Copiado!');
|
|
439
|
+
button.classList.add('copied');
|
|
440
|
+
|
|
441
|
+
setTimeout(function () {
|
|
442
|
+
span.textContent = originalText;
|
|
443
|
+
button.classList.remove('copied');
|
|
444
|
+
}, 2000);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
console.error('Error al copiar al portapapeles:', err);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// 2. Botones de ejecución local de JavaScript
|
|
452
|
+
container.querySelectorAll('.btn-run-code').forEach(function (button) {
|
|
453
|
+
if (button.dataset.listenerAttached) return;
|
|
454
|
+
button.dataset.listenerAttached = 'true';
|
|
455
|
+
|
|
456
|
+
button.addEventListener('click', async function () {
|
|
457
|
+
const rawCode = decodeURIComponent(button.getAttribute('data-code') || '');
|
|
458
|
+
const blockContainer = button.closest('.code-block-container');
|
|
459
|
+
if (!blockContainer) return;
|
|
460
|
+
|
|
461
|
+
let outputContainer = blockContainer.querySelector('.code-output-container');
|
|
462
|
+
if (!outputContainer) {
|
|
463
|
+
outputContainer = document.createElement('div');
|
|
464
|
+
outputContainer.className = 'code-output-container';
|
|
465
|
+
blockContainer.appendChild(outputContainer);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
outputContainer.style.display = 'block';
|
|
469
|
+
outputContainer.innerHTML = '<div class="output-header"><span>' + getI18n().uiText('agent_js_title', 'Ejecutando código JavaScript...') + '</span></div>';
|
|
470
|
+
|
|
471
|
+
const sandboxRunner = window.ChatSandbox || Sandbox;
|
|
472
|
+
if (sandboxRunner && sandboxRunner.execute) {
|
|
473
|
+
const res = await sandboxRunner.execute(rawCode);
|
|
474
|
+
const statusClass = res.success ? 'success' : 'error';
|
|
475
|
+
const headerTitle = res.success ? `${getI18n().uiText('md_output_title', 'Resultado')} (${res.executionTimeMs}ms)` : `Error (${res.executionTimeMs}ms)`;
|
|
476
|
+
|
|
477
|
+
let outputContent = '';
|
|
478
|
+
if (res.logs && res.logs.length > 0) {
|
|
479
|
+
outputContent += `<div class="output-logs"><strong>Console:</strong>\n${escapeHtml(res.logs.join('\n'))}</div>`;
|
|
480
|
+
}
|
|
481
|
+
if (res.result && res.result !== 'undefined') {
|
|
482
|
+
outputContent += `<div class="output-return"><strong>Retorno:</strong> ${escapeHtml(res.result)}</div>`;
|
|
483
|
+
}
|
|
484
|
+
if (res.error) {
|
|
485
|
+
outputContent += `<div class="output-error">${escapeHtml(res.error)}</div>`;
|
|
486
|
+
}
|
|
487
|
+
if (!outputContent) {
|
|
488
|
+
outputContent = `<div class="output-empty">(${getI18n().uiText('empty_response', 'Ejecutado sin salida de consola ni retorno')})</div>`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
outputContainer.innerHTML = `
|
|
492
|
+
<div class="output-header ${statusClass}">
|
|
493
|
+
<span>${headerTitle}</span>
|
|
494
|
+
<button type="button" class="btn-close-output" title="${getI18n().uiText('md_clear_output', 'Cerrar salida')}">×</button>
|
|
495
|
+
</div>
|
|
496
|
+
<pre class="output-body">${outputContent}</pre>
|
|
497
|
+
`;
|
|
498
|
+
|
|
499
|
+
outputContainer.querySelector('.btn-close-output').addEventListener('click', () => {
|
|
500
|
+
outputContainer.style.display = 'none';
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// 3. Botones y cabeceras de colapsar / minimizar tarjetas de herramientas
|
|
507
|
+
container.querySelectorAll('.tool-card-header, .web-card-header, .search-card-header, .chat-chart-header, .btn-tool-collapse').forEach(function (el) {
|
|
508
|
+
if (el.dataset.collapseInit) return;
|
|
509
|
+
el.dataset.collapseInit = 'true';
|
|
510
|
+
el.addEventListener('click', function (e) {
|
|
511
|
+
if (e.target.closest('a')) return;
|
|
512
|
+
if (el.classList.contains('btn-tool-collapse')) {
|
|
513
|
+
e.stopPropagation();
|
|
514
|
+
}
|
|
515
|
+
const card = el.closest('.tool-execution-card, .web-request-card, .web-search-card, .chat-chart-card');
|
|
516
|
+
if (!card) return;
|
|
517
|
+
const isCollapsed = card.classList.toggle('collapsed');
|
|
518
|
+
const btn = card.querySelector('.btn-tool-collapse');
|
|
519
|
+
if (btn) {
|
|
520
|
+
btn.title = isCollapsed ? getI18n().uiText('tool_btn_expand', 'Expandir herramienta') : getI18n().uiText('tool_btn_collapse', 'Minimizar herramienta');
|
|
521
|
+
const span = btn.querySelector('span');
|
|
522
|
+
if (span) span.textContent = isCollapsed ? '▸' : '▾';
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// 4. Resolución de imágenes de la base de conocimiento (rag-image://docId:imgId)
|
|
528
|
+
container.querySelectorAll('img[data-rag-src], img[src^="rag-image://"]').forEach(async function (img) {
|
|
529
|
+
const rawRef = img.getAttribute('data-rag-src') || img.getAttribute('src') || '';
|
|
530
|
+
if (!rawRef || !rawRef.startsWith('rag-image://')) return;
|
|
531
|
+
|
|
532
|
+
if (resolvedRagImagesCache.has(rawRef)) {
|
|
533
|
+
const cached = resolvedRagImagesCache.get(rawRef);
|
|
534
|
+
if (img.getAttribute('src') !== cached) {
|
|
535
|
+
img.setAttribute('src', cached);
|
|
536
|
+
}
|
|
537
|
+
img.classList.add('rag-resolved-image');
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (img.dataset.ragResolving) return;
|
|
542
|
+
img.dataset.ragResolving = 'true';
|
|
543
|
+
|
|
544
|
+
const cleanRef = rawRef.replace(/^rag-image:\/\//i, '');
|
|
545
|
+
const colonIdx = cleanRef.indexOf(':');
|
|
546
|
+
if (colonIdx > 0) {
|
|
547
|
+
const docId = cleanRef.substring(0, colonIdx);
|
|
548
|
+
const imgId = cleanRef.substring(colonIdx + 1);
|
|
549
|
+
const ragStorage = (typeof window !== 'undefined' && window.ChatRagStorage)
|
|
550
|
+
? window.ChatRagStorage
|
|
551
|
+
: (typeof require !== 'undefined' ? (() => { try { return require('./ragStorage.js'); } catch (e) { return null; } })() : null);
|
|
552
|
+
if (ragStorage && typeof ragStorage.getDocumentImage === 'function') {
|
|
553
|
+
try {
|
|
554
|
+
const imageRecord = await ragStorage.getDocumentImage(docId, imgId);
|
|
555
|
+
if (imageRecord && imageRecord.dataUrl) {
|
|
556
|
+
let finalUrl = imageRecord.dataUrl;
|
|
557
|
+
const fileParser = (typeof window !== 'undefined' && window.ChatFileParser)
|
|
558
|
+
? window.ChatFileParser
|
|
559
|
+
: (typeof require !== 'undefined' ? (() => { try { return require('./file-parser.js'); } catch (e) { return null; } })() : null);
|
|
560
|
+
if (fileParser && typeof fileParser.convertCmykDataUrlToRgb === 'function') {
|
|
561
|
+
finalUrl = fileParser.convertCmykDataUrlToRgb(finalUrl);
|
|
562
|
+
imageRecord.dataUrl = finalUrl;
|
|
563
|
+
imageRecord.isCmyk = false;
|
|
564
|
+
}
|
|
565
|
+
resolvedRagImagesCache.set(rawRef, finalUrl);
|
|
566
|
+
img.setAttribute('src', finalUrl);
|
|
567
|
+
img.classList.add('rag-resolved-image');
|
|
568
|
+
}
|
|
569
|
+
} catch (e) {
|
|
570
|
+
console.error('Error al resolver imagen RAG:', e);
|
|
571
|
+
} finally {
|
|
572
|
+
delete img.dataset.ragResolving;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
escapeHtml,
|
|
582
|
+
sanitizeUrl,
|
|
583
|
+
sanitizeImageUrl,
|
|
584
|
+
parseMarkdown,
|
|
585
|
+
renderMarkdown: parseMarkdown,
|
|
586
|
+
attachCopyCodeListeners
|
|
587
|
+
};
|
|
588
|
+
});
|