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,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de Interfaz de Usuario para Telemetría de Contexto y Tokens (ChatUITelemetry).
|
|
3
|
+
* ZeroChat - js/ui-telemetry.js
|
|
4
|
+
*
|
|
5
|
+
* Responsabilidades:
|
|
6
|
+
* - Formateo de números de tokens (compacto y legible).
|
|
7
|
+
* - Clasificación semafórica de la capacidad de contexto (óptimo, aviso, crítico).
|
|
8
|
+
* - Generación de ViewModel desacoplado para vista de badge y popover.
|
|
9
|
+
* - Renderizado eficiente del badge y renderizado perezoso (lazy) del popover.
|
|
10
|
+
* - Limpieza y reseteo integral de telemetría entre sesiones.
|
|
11
|
+
* - Enlace de eventos accesibles para apertura y cierre del popover.
|
|
12
|
+
*/
|
|
13
|
+
(function (root, factory) {
|
|
14
|
+
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
15
|
+
module.exports = factory();
|
|
16
|
+
} else {
|
|
17
|
+
root.ChatUITelemetry = factory();
|
|
18
|
+
}
|
|
19
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
function resolveDep(globalName, relPath) {
|
|
23
|
+
if (typeof window !== 'undefined' && window[globalName]) return window[globalName];
|
|
24
|
+
if (typeof require !== 'undefined') {
|
|
25
|
+
try { return require(relPath); } catch (e) { return null; }
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getI18n = () => resolveDep('ChatI18n', './i18n.js');
|
|
31
|
+
const getContextManager = () => resolveDep('ChatContextManager', './context-manager.js');
|
|
32
|
+
|
|
33
|
+
function t(key, params) {
|
|
34
|
+
const I18n = getI18n();
|
|
35
|
+
if (I18n && typeof I18n.t === 'function') return I18n.t(key, params);
|
|
36
|
+
return key;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Formatea un conteo numérico de tokens de manera compacta (ej: 0, 850, 12.5k, 128k, 1.2M).
|
|
41
|
+
* @param {number|string} num
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function formatTokenCount(num) {
|
|
45
|
+
const val = Number(num);
|
|
46
|
+
if (!val || isNaN(val) || val <= 0) return '0';
|
|
47
|
+
if (val >= 1000000) {
|
|
48
|
+
return (val / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
|
|
49
|
+
}
|
|
50
|
+
if (val >= 1000) {
|
|
51
|
+
return (val / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
|
52
|
+
}
|
|
53
|
+
return String(Math.round(val));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatContextCapacity(num) {
|
|
57
|
+
const value = Number(num);
|
|
58
|
+
if (!Number.isFinite(value) || value <= 0) return '0';
|
|
59
|
+
if (value < 1000) return String(Math.round(value));
|
|
60
|
+
return `${(value / 1000).toFixed(3).replace(/\.?0+$/, '')}K`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseContextCapacity(value) {
|
|
64
|
+
const match = String(value || '').trim().match(/^(\d+(?:[.,]\d+)?)\s*([km]?)$/i);
|
|
65
|
+
if (!match) return null;
|
|
66
|
+
const amount = Number(match[1].replace(',', '.'));
|
|
67
|
+
const multiplier = match[2].toLowerCase() === 'k' ? 1000 : (match[2].toLowerCase() === 'm' ? 1000000 : 1);
|
|
68
|
+
const limit = Math.floor(amount * multiplier);
|
|
69
|
+
return Number.isFinite(limit) && limit >= 1024 ? limit : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clasifica la salud del contexto según el porcentaje de ventana consumido.
|
|
74
|
+
* @param {number} percentUsed
|
|
75
|
+
* @returns {'ok'|'warning'|'critical'}
|
|
76
|
+
*/
|
|
77
|
+
function getContextHealthStatus(percentUsed) {
|
|
78
|
+
const pct = Number(percentUsed) || 0;
|
|
79
|
+
if (pct >= 85) return 'critical';
|
|
80
|
+
if (pct >= 60) return 'warning';
|
|
81
|
+
return 'ok';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Genera un ViewModel desacoplado de telemetría con todas las métricas procesadas.
|
|
86
|
+
* @param {Object} params
|
|
87
|
+
* @param {Object} [params.stats] - Estadísticas del último turno o streaming en curso.
|
|
88
|
+
* @param {Object} [params.diagnostics] - Diagnósticos previos de ContextManager.
|
|
89
|
+
* @param {Object} [params.config] - Configuración de modelo y proveedor.
|
|
90
|
+
* @param {Array} [params.chatHistory] - Mensajes de la conversación activa.
|
|
91
|
+
* @param {Object} [params.contextManager] - Instancia de ChatContextManager inyectable.
|
|
92
|
+
* @returns {Object} ViewModel completo para consumo de vistas y tests.
|
|
93
|
+
*/
|
|
94
|
+
function computeTelemetryViewModel({ stats, diagnostics, config, chatHistory, contextManager } = {}) {
|
|
95
|
+
const CM = contextManager || getContextManager();
|
|
96
|
+
const cfg = config || {};
|
|
97
|
+
const model = cfg.model || '';
|
|
98
|
+
const apiType = cfg.apiType || 'openai';
|
|
99
|
+
|
|
100
|
+
const sPrompt = (stats && typeof stats === 'object') ? (stats.promptTokens || 0) : 0;
|
|
101
|
+
const sCached = (stats && typeof stats === 'object') ? (stats.cachedTokens || 0) : 0;
|
|
102
|
+
const sCacheCreate = (stats && typeof stats === 'object') ? (stats.cacheCreationTokens || 0) : 0;
|
|
103
|
+
const sCompletion = (stats && typeof stats === 'object') ? (stats.completionTokens || stats.tokens || 0) : 0;
|
|
104
|
+
const sSpeed = (stats && stats.tokensPerSec) ? stats.tokensPerSec : null;
|
|
105
|
+
const sLatency = (stats && stats.ttftSec) ? stats.ttftSec : null;
|
|
106
|
+
|
|
107
|
+
let diag = diagnostics;
|
|
108
|
+
if (!diag && CM && typeof CM.getContextDiagnostics === 'function') {
|
|
109
|
+
diag = CM.getContextDiagnostics(chatHistory || [], {
|
|
110
|
+
model,
|
|
111
|
+
providerType: apiType,
|
|
112
|
+
totalContextLimit: cfg.modelContextLimit || cfg.contextLimitOverride,
|
|
113
|
+
usedTokens: sPrompt > 0 ? sPrompt : null
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const configuredLimit = cfg.modelContextLimit || cfg.contextLimitOverride;
|
|
118
|
+
const totalLimit = diag?.totalLimit || (CM && typeof CM.getModelContextLimit === 'function'
|
|
119
|
+
? CM.getModelContextLimit(model, apiType, configuredLimit)
|
|
120
|
+
: (CM?.DEFAULT_CONTEXT_LIMIT || 0));
|
|
121
|
+
const usedTokens = diag?.usedTokens ?? sPrompt;
|
|
122
|
+
const percentUsed = diag?.percentUsed ?? (totalLimit > 0 ? Number(((usedTokens / totalLimit) * 100).toFixed(1)) : 0);
|
|
123
|
+
const remainingTokens = Math.max(0, totalLimit - usedTokens);
|
|
124
|
+
const isEstimated = diag?.isEstimated ?? (sPrompt <= 0);
|
|
125
|
+
|
|
126
|
+
const prefix = (isEstimated && usedTokens > 0) ? '~' : '';
|
|
127
|
+
const usedFormatted = prefix + formatTokenCount(usedTokens);
|
|
128
|
+
const limitFormatted = formatTokenCount(totalLimit);
|
|
129
|
+
const contextLimitSource = cfg.modelContextLimit ? 'server' : (cfg.contextLimitOverride ? 'manual' : 'assumed');
|
|
130
|
+
const badgeText = `${usedFormatted} / ${limitFormatted}${contextLimitSource === 'assumed' ? '*' : ''}`;
|
|
131
|
+
const healthStatus = getContextHealthStatus(percentUsed);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
model,
|
|
135
|
+
apiType,
|
|
136
|
+
usedTokens,
|
|
137
|
+
totalLimit,
|
|
138
|
+
remainingTokens,
|
|
139
|
+
percentUsed,
|
|
140
|
+
isEstimated,
|
|
141
|
+
prefix,
|
|
142
|
+
badgeText,
|
|
143
|
+
healthStatus,
|
|
144
|
+
contextLimitSource,
|
|
145
|
+
cachedTokens: sCached,
|
|
146
|
+
cachedFormatted: formatTokenCount(sCached),
|
|
147
|
+
cacheCreationTokens: sCacheCreate,
|
|
148
|
+
cacheCreationFormatted: formatTokenCount(sCacheCreate),
|
|
149
|
+
turnPrompt: sPrompt,
|
|
150
|
+
turnCompletion: sCompletion,
|
|
151
|
+
turnSpeed: sSpeed,
|
|
152
|
+
turnLatency: sLatency
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Actualiza el indicador visual del badge en el composer.
|
|
158
|
+
* @param {Object} elements - Referencias a elementos DOM de telemetría.
|
|
159
|
+
* @param {Object} vm - ViewModel generado por computeTelemetryViewModel.
|
|
160
|
+
* @param {Function} [customT] - Función traductora opcional.
|
|
161
|
+
*/
|
|
162
|
+
function updateBadge(elements, vm, customT) {
|
|
163
|
+
if (!elements || !elements.connectionTokensBadge) return;
|
|
164
|
+
const translate = customT || t;
|
|
165
|
+
|
|
166
|
+
// 1. Texto de tokens
|
|
167
|
+
if (elements.connectionTokensText) {
|
|
168
|
+
elements.connectionTokensText.textContent = vm.badgeText;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 2. Semáforo visual en el botón
|
|
172
|
+
elements.connectionTokensBadge.classList.remove('status-warning', 'status-critical');
|
|
173
|
+
if (vm.healthStatus === 'critical') {
|
|
174
|
+
elements.connectionTokensBadge.classList.add('status-critical');
|
|
175
|
+
} else if (vm.healthStatus === 'warning') {
|
|
176
|
+
elements.connectionTokensBadge.classList.add('status-warning');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 3. Píldora de tokens en caché
|
|
180
|
+
if (elements.contextHubCachePill) {
|
|
181
|
+
if (vm.cachedTokens > 0) {
|
|
182
|
+
let cacheVal = elements.contextHubCacheVal;
|
|
183
|
+
if (!cacheVal && elements.contextHubCachePill.querySelector) {
|
|
184
|
+
cacheVal = elements.contextHubCachePill.querySelector('#context-hub-cache-val');
|
|
185
|
+
}
|
|
186
|
+
if (cacheVal) {
|
|
187
|
+
cacheVal.textContent = vm.cachedFormatted;
|
|
188
|
+
}
|
|
189
|
+
elements.contextHubCachePill.style.display = 'inline-flex';
|
|
190
|
+
} else {
|
|
191
|
+
elements.contextHubCachePill.style.display = 'none';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 4. Tooltip accesible
|
|
196
|
+
const titleText = translate('context_hub_btn_title') ||
|
|
197
|
+
`Ventana de contexto: ${vm.usedTokens.toLocaleString()} de ${vm.totalLimit.toLocaleString()} tokens (${vm.percentUsed}%)`;
|
|
198
|
+
elements.connectionTokensBadge.setAttribute('title', titleText);
|
|
199
|
+
elements.connectionTokensBadge.style.display = 'inline-flex';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Actualiza los valores detallados en el Popover flotante.
|
|
204
|
+
* @param {Object} elements - Referencias a elementos DOM del popover.
|
|
205
|
+
* @param {Object} vm - ViewModel generado por computeTelemetryViewModel.
|
|
206
|
+
* @param {Function} [customT] - Función traductora opcional.
|
|
207
|
+
*/
|
|
208
|
+
function updatePopover(elements, vm, customT) {
|
|
209
|
+
if (!elements || !elements.contextHubPopover) return;
|
|
210
|
+
const translate = customT || t;
|
|
211
|
+
|
|
212
|
+
// 1. Barra de progreso
|
|
213
|
+
if (elements.contextProgressBar) {
|
|
214
|
+
elements.contextProgressBar.classList.remove('warning', 'critical');
|
|
215
|
+
if (vm.healthStatus === 'critical') {
|
|
216
|
+
elements.contextProgressBar.classList.add('critical');
|
|
217
|
+
} else if (vm.healthStatus === 'warning') {
|
|
218
|
+
elements.contextProgressBar.classList.add('warning');
|
|
219
|
+
}
|
|
220
|
+
elements.contextProgressBar.style.width = `${Math.min(100, Math.max(0, vm.percentUsed))}%`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 2. Cuadrícula de contexto global
|
|
224
|
+
if (elements.contextMetricUsedVal) {
|
|
225
|
+
elements.contextMetricUsedVal.textContent = `${vm.usedTokens.toLocaleString()} tok` + (vm.isEstimated && vm.usedTokens > 0 ? ' (est.)' : '');
|
|
226
|
+
}
|
|
227
|
+
if (elements.contextMetricLimitVal) {
|
|
228
|
+
elements.contextMetricLimitVal.textContent = `${formatContextCapacity(vm.totalLimit)} tok`;
|
|
229
|
+
}
|
|
230
|
+
if (elements.contextLimitSource) {
|
|
231
|
+
const sourceKey = vm.contextLimitSource === 'server'
|
|
232
|
+
? 'context_limit_source_server'
|
|
233
|
+
: (vm.contextLimitSource === 'manual' ? 'context_limit_source_manual' : 'context_limit_source_assumed');
|
|
234
|
+
elements.contextLimitSource.textContent = translate(sourceKey);
|
|
235
|
+
}
|
|
236
|
+
if (elements.contextLimitOverrideInput) {
|
|
237
|
+
const input = elements.contextLimitOverrideInput;
|
|
238
|
+
const isFocused = typeof document !== 'undefined' && document.activeElement === input;
|
|
239
|
+
if (!isFocused) input.value = vm.contextLimitSource === 'server' ? '' : formatContextCapacity(vm.totalLimit);
|
|
240
|
+
input.disabled = vm.contextLimitSource === 'server';
|
|
241
|
+
}
|
|
242
|
+
if (elements.btnSaveContextLimitOverride) {
|
|
243
|
+
elements.btnSaveContextLimitOverride.disabled = vm.contextLimitSource === 'server';
|
|
244
|
+
}
|
|
245
|
+
if (elements.contextMetricFreeVal) {
|
|
246
|
+
elements.contextMetricFreeVal.textContent = `${formatContextCapacity(vm.remainingTokens)} tok`;
|
|
247
|
+
}
|
|
248
|
+
if (elements.contextMetricStatusVal) {
|
|
249
|
+
elements.contextMetricStatusVal.className = 'context-metric-val';
|
|
250
|
+
if (vm.healthStatus === 'critical') {
|
|
251
|
+
elements.contextMetricStatusVal.classList.add('status-critical');
|
|
252
|
+
elements.contextMetricStatusVal.textContent = translate('context_status_critical') || 'Crítico';
|
|
253
|
+
} else if (vm.healthStatus === 'warning') {
|
|
254
|
+
elements.contextMetricStatusVal.classList.add('status-warning');
|
|
255
|
+
elements.contextMetricStatusVal.textContent = translate('context_status_warning') || 'Elevado';
|
|
256
|
+
} else {
|
|
257
|
+
elements.contextMetricStatusVal.classList.add('status-ok');
|
|
258
|
+
elements.contextMetricStatusVal.textContent = translate('context_status_ok') || 'Óptimo';
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 3. Métricas de caché (Prompt / KV)
|
|
263
|
+
if (elements.contextMetricCachedReadVal) {
|
|
264
|
+
elements.contextMetricCachedReadVal.textContent = `${vm.cachedTokens.toLocaleString()} tok`;
|
|
265
|
+
}
|
|
266
|
+
if (elements.contextMetricCachedWriteVal) {
|
|
267
|
+
elements.contextMetricCachedWriteVal.textContent = `${vm.cacheCreationTokens.toLocaleString()} tok`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 4. Métricas del turno generado
|
|
271
|
+
if (elements.contextMetricTurnPromptVal) {
|
|
272
|
+
elements.contextMetricTurnPromptVal.textContent = vm.turnPrompt > 0 ? `${vm.turnPrompt.toLocaleString()} tok` : '-';
|
|
273
|
+
}
|
|
274
|
+
if (elements.contextMetricTurnCompletionVal) {
|
|
275
|
+
elements.contextMetricTurnCompletionVal.textContent = vm.turnCompletion > 0 ? `${vm.turnCompletion.toLocaleString()} tok` : '-';
|
|
276
|
+
}
|
|
277
|
+
if (elements.contextMetricTurnSpeedVal) {
|
|
278
|
+
elements.contextMetricTurnSpeedVal.textContent = vm.turnSpeed ? `${vm.turnSpeed} t/s` : '-';
|
|
279
|
+
}
|
|
280
|
+
if (elements.contextMetricTurnLatencyVal) {
|
|
281
|
+
elements.contextMetricTurnLatencyVal.textContent = vm.turnLatency ? `${vm.turnLatency}s` : '-';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Renderiza la telemetría completa aplicando renderizado perezoso (Lazy Rendering).
|
|
287
|
+
* El badge siempre se actualiza; el popover solo se actualiza si está abierto o si se solicita forzarlo.
|
|
288
|
+
* @param {Object} elements - Referencias a elementos DOM.
|
|
289
|
+
* @param {Object} vm - ViewModel generado.
|
|
290
|
+
* @param {Object} [options={}] - Opciones de renderizado ({ forcePopover: boolean }).
|
|
291
|
+
* @param {Function} [customT] - Función traductora opcional.
|
|
292
|
+
*/
|
|
293
|
+
function renderTelemetry(elements, vm, options = {}, customT) {
|
|
294
|
+
if (!elements) return;
|
|
295
|
+
updateBadge(elements, vm, customT);
|
|
296
|
+
|
|
297
|
+
const isPopoverOpen = elements.contextHubPopover &&
|
|
298
|
+
elements.contextHubPopover.style.display !== 'none' &&
|
|
299
|
+
elements.contextHubPopover.style.display !== '';
|
|
300
|
+
|
|
301
|
+
if (options.forcePopover || isPopoverOpen) {
|
|
302
|
+
updatePopover(elements, vm, customT);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Restaura la telemetría a estado neutro limpio para una nueva conversación o al alternar sesiones.
|
|
308
|
+
* @param {Object} elements - Referencias a elementos DOM.
|
|
309
|
+
* @param {Object} config - Configuración actual de la app.
|
|
310
|
+
* @param {Array} [chatHistory=[]] - Mensajes de la nueva sesión cargada (vacío por defecto).
|
|
311
|
+
* @param {Object} [contextManager] - Instancia de ChatContextManager opcional.
|
|
312
|
+
* @param {Function} [customT] - Función traductora opcional.
|
|
313
|
+
*/
|
|
314
|
+
function resetTelemetry(elements, config, chatHistory = [], contextManager, customT) {
|
|
315
|
+
const vm = computeTelemetryViewModel({
|
|
316
|
+
stats: null,
|
|
317
|
+
diagnostics: null,
|
|
318
|
+
config,
|
|
319
|
+
chatHistory,
|
|
320
|
+
contextManager
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
renderTelemetry(elements, vm, { forcePopover: true }, customT);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Vincula los eventos de apertura, cierre y accesibilidad ARIA para el Popover.
|
|
328
|
+
* @param {Object} elements - Contenedor con connectionTokensBadge, contextHubPopover y btnCloseContextPopover.
|
|
329
|
+
* @param {Function} [onOpenCallback] - Callback invocado cuando el usuario abre el popover (útil para lazy update).
|
|
330
|
+
*/
|
|
331
|
+
function bindPopoverEvents(elements, onOpenCallback) {
|
|
332
|
+
if (!elements || !elements.connectionTokensBadge) return;
|
|
333
|
+
|
|
334
|
+
const badge = elements.connectionTokensBadge;
|
|
335
|
+
const popover = elements.contextHubPopover;
|
|
336
|
+
const btnClose = elements.btnCloseContextPopover;
|
|
337
|
+
|
|
338
|
+
function closePopover() {
|
|
339
|
+
if (popover) popover.style.display = 'none';
|
|
340
|
+
badge.setAttribute('aria-expanded', 'false');
|
|
341
|
+
badge.classList.remove('active');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function togglePopover(e) {
|
|
345
|
+
if (e) {
|
|
346
|
+
e.preventDefault();
|
|
347
|
+
e.stopPropagation();
|
|
348
|
+
}
|
|
349
|
+
if (!popover) return;
|
|
350
|
+
const isHidden = popover.style.display === 'none' || !popover.style.display;
|
|
351
|
+
popover.style.display = isHidden ? 'flex' : 'none';
|
|
352
|
+
badge.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
|
353
|
+
badge.classList.toggle('active', isHidden);
|
|
354
|
+
|
|
355
|
+
if (isHidden && typeof onOpenCallback === 'function') {
|
|
356
|
+
onOpenCallback();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
badge.addEventListener('click', togglePopover);
|
|
361
|
+
|
|
362
|
+
if (btnClose) {
|
|
363
|
+
btnClose.addEventListener('click', function (e) {
|
|
364
|
+
e.preventDefault();
|
|
365
|
+
e.stopPropagation();
|
|
366
|
+
closePopover();
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (typeof document !== 'undefined') {
|
|
371
|
+
document.addEventListener('click', function (e) {
|
|
372
|
+
if (popover && popover.style.display !== 'none') {
|
|
373
|
+
if (!popover.contains(e.target) && !badge.contains(e.target)) {
|
|
374
|
+
closePopover();
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
document.addEventListener('keydown', function (e) {
|
|
380
|
+
if (e.key === 'Escape' && popover && popover.style.display !== 'none') {
|
|
381
|
+
closePopover();
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
closePopover,
|
|
388
|
+
togglePopover
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
formatTokenCount,
|
|
394
|
+
formatContextCapacity,
|
|
395
|
+
parseContextCapacity,
|
|
396
|
+
getContextHealthStatus,
|
|
397
|
+
computeTelemetryViewModel,
|
|
398
|
+
updateBadge,
|
|
399
|
+
updatePopover,
|
|
400
|
+
renderTelemetry,
|
|
401
|
+
resetTelemetry,
|
|
402
|
+
bindPopoverEvents
|
|
403
|
+
};
|
|
404
|
+
});
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de Transferencia para ZeroChat:
|
|
3
|
+
* Gestión del modal de exportación (Markdown, JSON, Print) e importación de conversaciones desde archivo JSON.
|
|
4
|
+
* Reutiliza ChatExport (js/export.js) para los formatos de salida y parseo de datos.
|
|
5
|
+
*/
|
|
6
|
+
(function (root, factory) {
|
|
7
|
+
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
8
|
+
module.exports = factory();
|
|
9
|
+
} else {
|
|
10
|
+
root.ChatUITransfer = factory();
|
|
11
|
+
}
|
|
12
|
+
}(typeof self !== 'undefined' ? self : this, function () {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
function resolveDep(globalName, relPath) {
|
|
16
|
+
if (typeof window !== 'undefined' && window[globalName]) return window[globalName];
|
|
17
|
+
if (typeof globalThis !== 'undefined' && globalThis[globalName]) return globalThis[globalName];
|
|
18
|
+
if (typeof require !== 'undefined') {
|
|
19
|
+
try { return require(relPath); } catch (_) {}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getI18n() { return resolveDep('ChatI18n', './i18n.js'); }
|
|
25
|
+
function getExport() { return resolveDep('ChatExport', './export.js'); }
|
|
26
|
+
function getDialogs() { return resolveDep('ChatDialogs', './ui-dialogs.js'); }
|
|
27
|
+
|
|
28
|
+
function t(key, params) {
|
|
29
|
+
const I18n = getI18n();
|
|
30
|
+
return I18n?.t?.(key, params) || key;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let activeCleanupFns = [];
|
|
34
|
+
let cachedElements = null;
|
|
35
|
+
let cachedOptions = {};
|
|
36
|
+
|
|
37
|
+
function getExportTargetSessionId(elements, fallbackId = '') {
|
|
38
|
+
return elements?.exportModal?.dataset?.sessionId || fallbackId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function openExportModal(elements, targetSessionId = null, fallbackId = '') {
|
|
42
|
+
const modal = elements?.exportModal;
|
|
43
|
+
if (modal) {
|
|
44
|
+
if (!modal.dataset) modal.dataset = {};
|
|
45
|
+
modal.dataset.sessionId = targetSessionId || fallbackId;
|
|
46
|
+
if (typeof modal.showModal === 'function') {
|
|
47
|
+
modal.showModal();
|
|
48
|
+
} else {
|
|
49
|
+
modal.style.display = 'block';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function closeExportModal(elements) {
|
|
55
|
+
const modal = elements?.exportModal;
|
|
56
|
+
if (modal) {
|
|
57
|
+
if (modal.dataset) delete modal.dataset.sessionId;
|
|
58
|
+
if (typeof modal.close === 'function') {
|
|
59
|
+
modal.close();
|
|
60
|
+
} else {
|
|
61
|
+
modal.style.display = 'none';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function resolveSessionForExport(elementsOrTargetId, options = {}) {
|
|
67
|
+
const activeId = options.getActiveSessionId ? options.getActiveSessionId() : '';
|
|
68
|
+
const targetId = typeof elementsOrTargetId === 'string' ? elementsOrTargetId : null;
|
|
69
|
+
const els = typeof elementsOrTargetId === 'object' ? elementsOrTargetId : null;
|
|
70
|
+
const id = targetId || getExportTargetSessionId(els, activeId);
|
|
71
|
+
if (!id) return null;
|
|
72
|
+
|
|
73
|
+
if (id === activeId) {
|
|
74
|
+
const savedList = options.getSavedSessions ? options.getSavedSessions() : [];
|
|
75
|
+
const sess = savedList.find?.(s => s.id === id) || { id, title: 'ZeroChat_Conversation' };
|
|
76
|
+
const history = options.getHistory ? options.getHistory() : [];
|
|
77
|
+
return { id, sess, history, messages: history };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (typeof options.getSession === 'function') {
|
|
81
|
+
const conv = await options.getSession(id);
|
|
82
|
+
const messages = conv?.history || conv?.messages || [];
|
|
83
|
+
return { id, sess: conv, history: messages, messages };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function exportConversationAsMarkdown(elements, options = {}) {
|
|
90
|
+
const session = await resolveSessionForExport(elements, options);
|
|
91
|
+
const { sess, history } = session || {};
|
|
92
|
+
const title = (sess && sess.title) || 'ZeroChat_Conversation';
|
|
93
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
94
|
+
const Export = getExport();
|
|
95
|
+
const model = options.getModel ? options.getModel() : '';
|
|
96
|
+
const md = Export?.buildMarkdownExport ? Export.buildMarkdownExport(history, { title, model }) : '';
|
|
97
|
+
if (Export?.downloadFile) {
|
|
98
|
+
Export.downloadFile(md, `${title.replace(/[^a-zA-Z0-9_-]/g, '_')}_${dateStr}.md`, 'text/markdown');
|
|
99
|
+
}
|
|
100
|
+
closeExportModal(elements);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function exportConversationAsJson(elements, options = {}) {
|
|
104
|
+
const session = await resolveSessionForExport(elements, options);
|
|
105
|
+
const { sess, history } = session || {};
|
|
106
|
+
const title = (sess && sess.title) || 'ZeroChat_Conversation';
|
|
107
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
108
|
+
const Export = getExport();
|
|
109
|
+
const config = options.getConfig ? options.getConfig() : {};
|
|
110
|
+
const jsonStr = Export?.buildJsonExport ? Export.buildJsonExport(sess, history, config) : '{}';
|
|
111
|
+
if (Export?.downloadFile) {
|
|
112
|
+
Export.downloadFile(jsonStr, `${title.replace(/[^a-zA-Z0-9_-]/g, '_')}_${dateStr}.json`, 'application/json');
|
|
113
|
+
}
|
|
114
|
+
closeExportModal(elements);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function exportConversationAsPrint(elements, options = {}) {
|
|
118
|
+
const activeId = options.getActiveSessionId ? options.getActiveSessionId() : '';
|
|
119
|
+
const targetId = getExportTargetSessionId(elements, activeId);
|
|
120
|
+
closeExportModal(elements);
|
|
121
|
+
|
|
122
|
+
if (targetId && targetId !== activeId && typeof options.onSwitchSession === 'function') {
|
|
123
|
+
await options.onSwitchSession(targetId);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
if (typeof window !== 'undefined' && typeof window.print === 'function') {
|
|
128
|
+
window.print();
|
|
129
|
+
}
|
|
130
|
+
}, 200);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseConversationJson(rawText) {
|
|
134
|
+
const data = JSON.parse(rawText);
|
|
135
|
+
if (!data || (!data.messages && !Array.isArray(data))) {
|
|
136
|
+
throw new Error('Estructura de conversación no válida.');
|
|
137
|
+
}
|
|
138
|
+
const messages = Array.isArray(data) ? data : (data.messages || []);
|
|
139
|
+
return { messages, raw: data };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function handleImportFileSelected(e, elements, options = {}) {
|
|
143
|
+
const file = e.target?.files && e.target.files[0];
|
|
144
|
+
if (!file) return;
|
|
145
|
+
|
|
146
|
+
if (typeof options.isBusy === 'function' && options.isBusy()) {
|
|
147
|
+
if (elements?.importJsonInput) elements.importJsonInput.value = '';
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const maxBytes = options.maxBytes || (50 * 1024 * 1024);
|
|
152
|
+
const Dialogs = getDialogs();
|
|
153
|
+
if (file.size > maxBytes) {
|
|
154
|
+
if (Dialogs?.alert) {
|
|
155
|
+
await Dialogs.alert(t('err_file_too_large', { name: file.name, max: '50 MB' }), { type: 'error' });
|
|
156
|
+
}
|
|
157
|
+
if (elements?.importJsonInput) elements.importJsonInput.value = '';
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const text = await new Promise((resolve, reject) => {
|
|
163
|
+
const reader = new FileReader();
|
|
164
|
+
reader.onload = (evt) => resolve(evt.target.result);
|
|
165
|
+
reader.onerror = (evt) => reject(new Error('Error al leer el archivo'));
|
|
166
|
+
reader.readAsText(file);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const Export = getExport();
|
|
170
|
+
const baseName = file.name.replace(/\.json$/i, '');
|
|
171
|
+
const newSession = Export?.parseImportedJson ? Export.parseImportedJson(text, baseName) : null;
|
|
172
|
+
if (!newSession) throw new Error('Error al procesar el archivo');
|
|
173
|
+
|
|
174
|
+
if (typeof options.onImportSuccess === 'function') {
|
|
175
|
+
await options.onImportSuccess(newSession);
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.error('Error al importar archivo:', err);
|
|
179
|
+
if (Dialogs?.alert) {
|
|
180
|
+
Dialogs.alert(t('chat_import_json_err', { err: err?.message || err }), { type: 'error' });
|
|
181
|
+
}
|
|
182
|
+
} finally {
|
|
183
|
+
if (elements?.importJsonInput) elements.importJsonInput.value = '';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function mount({ elements, getActiveSessionId, getSavedSessions, getHistory, getSession, getConfig, getModel, onSwitchSession, onImportSuccess, isBusy } = {}) {
|
|
188
|
+
dispose();
|
|
189
|
+
cachedElements = elements || {};
|
|
190
|
+
cachedOptions = { getActiveSessionId, getSavedSessions, getHistory, getSession, getConfig, getModel, onSwitchSession, onImportSuccess, isBusy };
|
|
191
|
+
|
|
192
|
+
const els = cachedElements;
|
|
193
|
+
|
|
194
|
+
if (els.btnCloseExport) {
|
|
195
|
+
const h = () => closeExportModal(els);
|
|
196
|
+
els.btnCloseExport.addEventListener('click', h);
|
|
197
|
+
activeCleanupFns.push(() => els.btnCloseExport.removeEventListener('click', h));
|
|
198
|
+
}
|
|
199
|
+
if (els.btnCancelExport) {
|
|
200
|
+
const h = () => closeExportModal(els);
|
|
201
|
+
els.btnCancelExport.addEventListener('click', h);
|
|
202
|
+
activeCleanupFns.push(() => els.btnCancelExport.removeEventListener('click', h));
|
|
203
|
+
}
|
|
204
|
+
if (els.btnExportMarkdown) {
|
|
205
|
+
const h = () => exportConversationAsMarkdown(els, cachedOptions);
|
|
206
|
+
els.btnExportMarkdown.addEventListener('click', h);
|
|
207
|
+
activeCleanupFns.push(() => els.btnExportMarkdown.removeEventListener('click', h));
|
|
208
|
+
}
|
|
209
|
+
if (els.btnExportJson) {
|
|
210
|
+
const h = () => exportConversationAsJson(els, cachedOptions);
|
|
211
|
+
els.btnExportJson.addEventListener('click', h);
|
|
212
|
+
activeCleanupFns.push(() => els.btnExportJson.removeEventListener('click', h));
|
|
213
|
+
}
|
|
214
|
+
if (els.btnExportPrint) {
|
|
215
|
+
const h = () => exportConversationAsPrint(els, cachedOptions);
|
|
216
|
+
els.btnExportPrint.addEventListener('click', h);
|
|
217
|
+
activeCleanupFns.push(() => els.btnExportPrint.removeEventListener('click', h));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (els.btnImportChatFile) {
|
|
221
|
+
const h = () => { if (els.importJsonInput) els.importJsonInput.click(); };
|
|
222
|
+
els.btnImportChatFile.addEventListener('click', h);
|
|
223
|
+
activeCleanupFns.push(() => els.btnImportChatFile.removeEventListener('click', h));
|
|
224
|
+
}
|
|
225
|
+
if (els.importJsonInput) {
|
|
226
|
+
const h = (e) => handleImportFileSelected(e, els, cachedOptions);
|
|
227
|
+
els.importJsonInput.addEventListener('change', h);
|
|
228
|
+
activeCleanupFns.push(() => els.importJsonInput.removeEventListener('change', h));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
openExportModal: (targetSessionId) => openExportModal(els, targetSessionId, cachedOptions.getActiveSessionId?.()),
|
|
233
|
+
closeExportModal: () => closeExportModal(els),
|
|
234
|
+
exportConversationAsMarkdown: () => exportConversationAsMarkdown(els, cachedOptions),
|
|
235
|
+
exportConversationAsJson: () => exportConversationAsJson(els, cachedOptions),
|
|
236
|
+
exportConversationAsPrint: () => exportConversationAsPrint(els, cachedOptions),
|
|
237
|
+
handleImportFileSelected: (e) => handleImportFileSelected(e, els, cachedOptions),
|
|
238
|
+
parseConversationJson
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function dispose() {
|
|
243
|
+
activeCleanupFns.forEach(fn => { try { fn(); } catch (_) {} });
|
|
244
|
+
activeCleanupFns = [];
|
|
245
|
+
cachedElements = null;
|
|
246
|
+
cachedOptions = {};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
openExportModal,
|
|
251
|
+
closeExportModal,
|
|
252
|
+
getExportTargetSessionId,
|
|
253
|
+
resolveSessionForExport,
|
|
254
|
+
exportConversationAsMarkdown,
|
|
255
|
+
exportConversationAsJson,
|
|
256
|
+
exportConversationAsPrint,
|
|
257
|
+
parseConversationJson,
|
|
258
|
+
handleImportFileSelected,
|
|
259
|
+
mount,
|
|
260
|
+
dispose
|
|
261
|
+
};
|
|
262
|
+
}));
|