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,2558 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aplicación principal del cliente de chat Web (ZeroChat).
|
|
3
|
+
* Desarrollo 100% asistido por IA: colaboración entre Codex y Antigravity para implementar
|
|
4
|
+
* un motor RAG local funcional utilizando la librería externa Orama como vendor autónomo,
|
|
5
|
+
* iterando e implementando sin tocar una sola línea de código manual por parte del usuario.
|
|
6
|
+
* Incluye:
|
|
7
|
+
* - Soporte Multi-idioma (Castellano / Inglés) con autodetección por navegador y persistencia.
|
|
8
|
+
* - Selector de nivel de razonamiento (Thinking/CoT) con detección automática de capacidades del modelo.
|
|
9
|
+
* - Nuevo chat abriendo en una nueva pestaña.
|
|
10
|
+
* - Estadísticas y acciones agrupadas en la misma línea debajo de la respuesta.
|
|
11
|
+
* - Borrado de preguntas y respuestas individuales.
|
|
12
|
+
* - Copia de respuesta completa al portapapeles.
|
|
13
|
+
* - Estadísticas en tiempo real (tokens, tiempo, tokens/segundo).
|
|
14
|
+
* - Adjuntar archivos de texto y código con vista previa y arrastrar/soltar.
|
|
15
|
+
* - Compatibilidad total con file:// y http://.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
(function () {
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
// Módulos globales
|
|
22
|
+
const Utils = window.ChatUtils || {};
|
|
23
|
+
const Storage = window.ChatStorage || {};
|
|
24
|
+
const Markdown = window.ChatMarkdown || {};
|
|
25
|
+
const API = window.ChatAPI || {};
|
|
26
|
+
const FileParser = window.ChatFileParser || {};
|
|
27
|
+
const Sandbox = window.ChatSandbox || {};
|
|
28
|
+
const Charts = window.ChatCharts || {};
|
|
29
|
+
const WebBrowser = window.ChatWebBrowser || {};
|
|
30
|
+
const WebSearch = window.ChatWebSearch || {};
|
|
31
|
+
const I18n = window.ChatI18n || {};
|
|
32
|
+
const Debug = window.ChatDebug || {};
|
|
33
|
+
const ToolCards = window.ChatToolCards || {};
|
|
34
|
+
const Attachments = window.ChatAttachments || {};
|
|
35
|
+
const Export = window.ChatExport || {};
|
|
36
|
+
const ProfileBackup = window.ChatProfileBackup || {};
|
|
37
|
+
const State = window.ChatState || {};
|
|
38
|
+
const ContextManager = window.ChatContextManager || {};
|
|
39
|
+
const AgentCore = window.ChatAgentCore || {};
|
|
40
|
+
const Engine = window.ChatEngine || {};
|
|
41
|
+
const UIReasoning = window.ChatUIReasoning || {};
|
|
42
|
+
const GenerationStatus = window.ChatUIGenerationStatus || {};
|
|
43
|
+
const UIInspector = window.ChatUIInspector || {};
|
|
44
|
+
const UISidebar = window.ChatUISidebar || {};
|
|
45
|
+
const UISettings = window.ChatUISettings || {};
|
|
46
|
+
const Config = window.ChatConfig;
|
|
47
|
+
const Profiles = window.ChatProfileRepository || {};
|
|
48
|
+
const Providers = window.ChatProviders || {};
|
|
49
|
+
const UIShell = window.ChatUIShell || {};
|
|
50
|
+
const UITransfer = window.ChatUITransfer || {};
|
|
51
|
+
const UIProfiles = window.ChatUIProfiles || {};
|
|
52
|
+
const UIComposer = window.ChatUIComposer || {};
|
|
53
|
+
const ConversationService = window.ChatConversationService || {};
|
|
54
|
+
const UIConversation = window.ChatUIConversation || {};
|
|
55
|
+
const GenerationController = window.ChatGenerationController || {};
|
|
56
|
+
|
|
57
|
+
function t(key, params) {
|
|
58
|
+
if (I18n.t) return I18n.t(key, params);
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getMsgIcon(name, size = 12) {
|
|
63
|
+
if (typeof window !== 'undefined' && window.ChatIcons && window.ChatIcons.has(name)) {
|
|
64
|
+
return window.ChatIcons.get(name, { size });
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const icons = require('./icons.js');
|
|
68
|
+
if (icons && icons.has(name)) return icons.get(name, { size });
|
|
69
|
+
} catch (_) {}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof Config?.initialize !== 'function' || typeof Config?.getActive !== 'function') {
|
|
74
|
+
throw new Error('ZeroChat requires ChatConfig before application startup.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
Config.initialize();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.warn('Error durante la inicialización de configuración:', error);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getRuntimeConfig() {
|
|
84
|
+
return Config.getActive();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Transitional read-through facade for legacy helpers inside this module.
|
|
88
|
+
// It owns no data: every read is resolved from the canonical runtime store.
|
|
89
|
+
const appConfig = new Proxy({}, {
|
|
90
|
+
get: (_target, key) => getRuntimeConfig()[key],
|
|
91
|
+
ownKeys: () => Reflect.ownKeys(getRuntimeConfig()),
|
|
92
|
+
getOwnPropertyDescriptor: (_target, key) => ({ enumerable: true, configurable: true, value: getRuntimeConfig()[key] })
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
function getChatHistory() {
|
|
96
|
+
return State.get ? (State.get('messages') || []) : [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getCurrentSessionId() {
|
|
100
|
+
return State.get ? (State.get('sessions')?.activeId || '') : '';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getSavedSessions() {
|
|
104
|
+
return State.get ? (State.get('sessions')?.list || []) : [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Referencias al DOM
|
|
108
|
+
let elements = {};
|
|
109
|
+
|
|
110
|
+
function cacheDomElements() {
|
|
111
|
+
elements = {
|
|
112
|
+
// Barra lateral de chats
|
|
113
|
+
chatSidebar: document.getElementById('chat-sidebar'),
|
|
114
|
+
btnToggleSidebar: document.getElementById('btn-toggle-sidebar'),
|
|
115
|
+
btnCloseSidebar: document.getElementById('btn-close-sidebar'),
|
|
116
|
+
btnSidebarNewTab: document.getElementById('btn-sidebar-new-tab'),
|
|
117
|
+
btnSidebarNewChat: document.getElementById('btn-sidebar-new-chat'),
|
|
118
|
+
sidebarSearchInput: document.getElementById('sidebar-search-input'),
|
|
119
|
+
sidebarChatsList: document.getElementById('sidebar-chats-list'),
|
|
120
|
+
btnImportChatFile: document.getElementById('btn-import-chat-file'),
|
|
121
|
+
importJsonInput: document.getElementById('import-json-input'),
|
|
122
|
+
btnDeleteAllChats: document.getElementById('btn-delete-all-chats'),
|
|
123
|
+
sidebarViewChat: document.getElementById('sidebar-view-chat'),
|
|
124
|
+
sidebarViewSettings: document.getElementById('sidebar-view-settings'),
|
|
125
|
+
btnSidebarBackToChats: document.getElementById('btn-sidebar-back-to-chats'),
|
|
126
|
+
btnCloseSidebarSettings: document.getElementById('btn-close-sidebar-settings'),
|
|
127
|
+
sidebarSettingsNav: document.getElementById('sidebar-settings-nav'),
|
|
128
|
+
sidebarSettingsItems: document.querySelectorAll('.sidebar-settings-item'),
|
|
129
|
+
|
|
130
|
+
// Modal de exportación
|
|
131
|
+
exportModal: document.getElementById('export-modal'),
|
|
132
|
+
btnCloseExport: document.getElementById('btn-close-export'),
|
|
133
|
+
btnCancelExport: document.getElementById('btn-cancel-export'),
|
|
134
|
+
btnExportMarkdown: document.getElementById('btn-export-markdown'),
|
|
135
|
+
btnExportJson: document.getElementById('btn-export-json'),
|
|
136
|
+
btnExportPrint: document.getElementById('btn-export-print'),
|
|
137
|
+
|
|
138
|
+
activeProfileMenu: document.getElementById('active-profile-menu'),
|
|
139
|
+
activeProfileTrigger: document.getElementById('active-profile-trigger'),
|
|
140
|
+
activeProfileName: document.getElementById('active-profile-name'),
|
|
141
|
+
activeProfilePopover: document.getElementById('active-profile-popover'),
|
|
142
|
+
activeProfileList: document.getElementById('active-profile-list'),
|
|
143
|
+
btnMenuNewProfile: document.getElementById('btn-menu-new-profile'),
|
|
144
|
+
btnMenuExportProfiles: document.getElementById('btn-menu-export-profiles'),
|
|
145
|
+
btnMenuImportProfiles: document.getElementById('btn-menu-import-profiles'),
|
|
146
|
+
btnMenuCloseProfiles: document.getElementById('btn-menu-close-profiles'),
|
|
147
|
+
btnEditProfiles: document.getElementById('btn-edit-profiles'),
|
|
148
|
+
connectionTokensBadge: document.getElementById('connection-tokens-badge'),
|
|
149
|
+
connectionTokensText: document.getElementById('connection-tokens-text'),
|
|
150
|
+
contextHubCachePill: document.getElementById('context-hub-cache-pill'),
|
|
151
|
+
contextHubCacheVal: document.getElementById('context-hub-cache-val'),
|
|
152
|
+
contextHubPopover: document.getElementById('context-hub-popover'),
|
|
153
|
+
btnCloseContextPopover: document.getElementById('btn-close-context-popover'),
|
|
154
|
+
contextProgressBar: document.getElementById('context-progress-bar'),
|
|
155
|
+
contextMetricUsedVal: document.getElementById('context-metric-used-val'),
|
|
156
|
+
contextMetricLimitVal: document.getElementById('context-metric-limit-val'),
|
|
157
|
+
contextMetricFreeVal: document.getElementById('context-metric-free-val'),
|
|
158
|
+
contextMetricStatusVal: document.getElementById('context-metric-status-val'),
|
|
159
|
+
contextLimitSource: document.getElementById('context-limit-source'),
|
|
160
|
+
contextLimitOverrideInput: document.getElementById('context-limit-override-input'),
|
|
161
|
+
btnSaveContextLimitOverride: document.getElementById('btn-save-context-limit-override'),
|
|
162
|
+
contextMetricCachedReadVal: document.getElementById('context-metric-cached-read-val'),
|
|
163
|
+
contextMetricCachedWriteVal: document.getElementById('context-metric-cached-write-val'),
|
|
164
|
+
contextMetricTurnPromptVal: document.getElementById('context-metric-turn-prompt-val'),
|
|
165
|
+
contextMetricTurnCompletionVal: document.getElementById('context-metric-turn-completion-val'),
|
|
166
|
+
contextMetricTurnSpeedVal: document.getElementById('context-metric-turn-speed-val'),
|
|
167
|
+
contextMetricTurnLatencyVal: document.getElementById('context-metric-turn-latency-val'),
|
|
168
|
+
btnOpenSettings: document.getElementById('btn-open-settings'),
|
|
169
|
+
messagesList: document.getElementById('messages-list'),
|
|
170
|
+
welcomeBanner: document.getElementById('welcome-banner'),
|
|
171
|
+
chatForm: document.getElementById('chat-form'),
|
|
172
|
+
userInput: document.getElementById('user-input'),
|
|
173
|
+
btnSend: document.getElementById('btn-send'),
|
|
174
|
+
btnStopStream: document.getElementById('btn-stop-stream'),
|
|
175
|
+
generationStatus: document.getElementById('generation-status'),
|
|
176
|
+
btnOpenRag: document.getElementById('btn-open-rag'),
|
|
177
|
+
btnReasoning: document.getElementById('btn-reasoning'),
|
|
178
|
+
reasoningLabel: document.getElementById('reasoning-label'),
|
|
179
|
+
reasoningMenu: document.getElementById('reasoning-menu'),
|
|
180
|
+
reasoningIntensity: document.getElementById('reasoning-intensity'),
|
|
181
|
+
reasoningIntensityValue: document.getElementById('reasoning-intensity-value'),
|
|
182
|
+
btnCloseReasoning: document.getElementById('btn-close-reasoning'),
|
|
183
|
+
chkReasoningAgentCheckpoint: document.getElementById('chk-reasoning-agent-checkpoint'),
|
|
184
|
+
|
|
185
|
+
// Panel de Debug & Logs
|
|
186
|
+
btnToggleDebug: document.getElementById('btn-toggle-debug'),
|
|
187
|
+
debugPanel: document.getElementById('debug-panel'),
|
|
188
|
+
debugStatusIndicator: document.getElementById('debug-status-indicator'),
|
|
189
|
+
btnCopyDebug: document.getElementById('btn-copy-debug'),
|
|
190
|
+
btnClearDebug: document.getElementById('btn-clear-debug'),
|
|
191
|
+
btnToggleAutoscroll: document.getElementById('btn-toggle-autoscroll'),
|
|
192
|
+
btnCloseDebug: document.getElementById('btn-close-debug'),
|
|
193
|
+
debugLogContent: document.getElementById('debug-log-content'),
|
|
194
|
+
debugTabs: document.querySelectorAll('.debug-tab'),
|
|
195
|
+
chkEnableDebugMessages: document.getElementById('chk-enable-debug-messages'),
|
|
196
|
+
debugMessagesStatusBadge: document.getElementById('debug-messages-status-badge'),
|
|
197
|
+
|
|
198
|
+
// Modal de Depuración de Mensajes Salientes (Interceptor)
|
|
199
|
+
debugInterceptorDialog: document.getElementById('debug-interceptor-dialog'),
|
|
200
|
+
btnMaximizeDebugModal: document.getElementById('btn-maximize-debug-modal'),
|
|
201
|
+
debugModalEndpointBadge: document.getElementById('debug-modal-endpoint-badge'),
|
|
202
|
+
btnFormatDebugJson: document.getElementById('btn-format-debug-json'),
|
|
203
|
+
btnCopyDebugJson: document.getElementById('btn-copy-debug-json'),
|
|
204
|
+
txtDebugPayload: document.getElementById('txt-debug-payload'),
|
|
205
|
+
debugJsonError: document.getElementById('debug-json-error'),
|
|
206
|
+
btnDebugSendDisable: document.getElementById('btn-debug-send-disable'),
|
|
207
|
+
btnDebugSend: document.getElementById('btn-debug-send'),
|
|
208
|
+
|
|
209
|
+
// Adjuntos
|
|
210
|
+
btnAttachFile: document.getElementById('btn-attach-file'),
|
|
211
|
+
fileInput: document.getElementById('file-input'),
|
|
212
|
+
attachmentsContainer: document.getElementById('attachments-container'),
|
|
213
|
+
|
|
214
|
+
// Ayuda y documentación
|
|
215
|
+
welcomeHelpLink: document.getElementById('welcome-help-link'),
|
|
216
|
+
|
|
217
|
+
// Información sobre ejecución y almacenamiento
|
|
218
|
+
btnOpenExecutionInfo: document.getElementById('btn-open-execution-info'),
|
|
219
|
+
executionInfoDialog: document.getElementById('execution-info-dialog'),
|
|
220
|
+
executionStorageScope: document.getElementById('execution-storage-scope'),
|
|
221
|
+
btnCloseExecutionInfo: document.getElementById('btn-close-execution-info'),
|
|
222
|
+
btnCloseExecutionInfoFooter: document.getElementById('btn-close-execution-info-footer'),
|
|
223
|
+
|
|
224
|
+
// Modal de Configuración
|
|
225
|
+
settingsDialog: document.getElementById('settings-dialog'),
|
|
226
|
+
settingsActiveProfileName: document.getElementById('settings-active-profile-name'),
|
|
227
|
+
settingsForm: document.getElementById('settings-form'),
|
|
228
|
+
settingsSectionTitle: document.getElementById('settings-section-title'),
|
|
229
|
+
btnCloseSettings: document.getElementById('btn-close-settings'),
|
|
230
|
+
btnSaveSettings: document.getElementById('btn-save-settings'),
|
|
231
|
+
btnClearAllData: document.getElementById('btn-clear-all-data'),
|
|
232
|
+
btnToggleKey: document.getElementById('btn-toggle-key'),
|
|
233
|
+
profilesDialog: document.getElementById('profiles-dialog'),
|
|
234
|
+
profilesForm: document.getElementById('profiles-form'),
|
|
235
|
+
btnManageProfiles: document.getElementById('btn-manage-profiles'),
|
|
236
|
+
btnCloseProfiles: document.getElementById('btn-close-profiles'),
|
|
237
|
+
btnCancelProfiles: document.getElementById('btn-cancel-profiles'),
|
|
238
|
+
settingProfileName: document.getElementById('setting-profile-name'),
|
|
239
|
+
settingProfileDescription: document.getElementById('setting-profile-description'),
|
|
240
|
+
profileSelectHelper: document.getElementById('profile-select-helper'),
|
|
241
|
+
profileDatalist: document.getElementById('profile-datalist'),
|
|
242
|
+
btnNewProfile: document.getElementById('btn-new-profile'),
|
|
243
|
+
btnExportProfiles: document.getElementById('btn-export-profiles'),
|
|
244
|
+
btnImportProfiles: document.getElementById('btn-import-profiles'),
|
|
245
|
+
profilesImportInput: document.getElementById('profiles-import-input'),
|
|
246
|
+
btnSaveProfile: document.getElementById('btn-save-profile'),
|
|
247
|
+
btnDeleteProfile: document.getElementById('btn-delete-profile'),
|
|
248
|
+
profileActionFeedback: document.getElementById('profile-action-feedback'),
|
|
249
|
+
profileSaveQueryHint: document.getElementById('profile-save-query-hint'),
|
|
250
|
+
settingApiType: document.getElementById('setting-api-type'),
|
|
251
|
+
settingApiUrl: document.getElementById('setting-api-url'),
|
|
252
|
+
btnQueryServer: document.getElementById('btn-query-server'),
|
|
253
|
+
serverQueryStatus: document.getElementById('server-query-status'),
|
|
254
|
+
settingApiKey: document.getElementById('setting-api-key'),
|
|
255
|
+
settingApiKeyLocked: document.getElementById('setting-api-key-locked'),
|
|
256
|
+
apiKeyLockControl: document.getElementById('api-key-lock-control'),
|
|
257
|
+
apiKeyLockedStatus: document.getElementById('api-key-locked-status'),
|
|
258
|
+
settingModel: document.getElementById('setting-model'),
|
|
259
|
+
modelDatalist: document.getElementById('model-datalist'),
|
|
260
|
+
modelSelectHelper: document.getElementById('model-select-helper'),
|
|
261
|
+
btnWebllmParams: document.getElementById('btn-webllm-params'),
|
|
262
|
+
webllmParamsPanel: document.getElementById('webllm-params-panel'),
|
|
263
|
+
settingWebllmContextWindow: document.getElementById('setting-webllm-context-window'),
|
|
264
|
+
settingWebllmPrefillChunk: document.getElementById('setting-webllm-prefill-chunk'),
|
|
265
|
+
settingSystemPrompt: document.getElementById('setting-system-prompt'),
|
|
266
|
+
settingSystemDataPrompt: document.getElementById('setting-system-data-prompt'),
|
|
267
|
+
settingTemperature: document.getElementById('setting-temperature'),
|
|
268
|
+
temperatureVal: document.getElementById('temperature-val'),
|
|
269
|
+
settingMaxAgentTurns: document.getElementById('setting-max-agent-turns'),
|
|
270
|
+
maxAgentTurnsVal: document.getElementById('max-agent-turns-val'),
|
|
271
|
+
themeButtons: document.querySelectorAll('.btn-theme-toggle'),
|
|
272
|
+
langButtons: document.querySelectorAll('.btn-lang-toggle'),
|
|
273
|
+
btnRunInspector: document.getElementById('btn-run-inspector'),
|
|
274
|
+
inspectorResults: document.getElementById('inspector-results'),
|
|
275
|
+
agentToolsContainer: document.getElementById('agent-tools-container'),
|
|
276
|
+
mcpToolsContainer: document.getElementById('mcp-tools-container'),
|
|
277
|
+
mcpServersList: document.getElementById('mcp-servers-list'),
|
|
278
|
+
mcpStatusBadge: document.getElementById('mcp-status-badge'),
|
|
279
|
+
mcpStatusText: document.getElementById('mcp-status-text'),
|
|
280
|
+
mcpServerDetails: document.getElementById('mcp-server-details'),
|
|
281
|
+
mcpReconnectHint: document.getElementById('mcp-reconnect-hint'),
|
|
282
|
+
mcpTerminalCommand: document.getElementById('mcp-terminal-command'),
|
|
283
|
+
btnMcpCopyCmd: document.getElementById('btn-mcp-copy-cmd'),
|
|
284
|
+
mcpBootstrapCard: document.getElementById('mcp-bootstrap-card'),
|
|
285
|
+
// Fase 7: backdrop para drawer en móvil
|
|
286
|
+
sidebarBackdrop: document.getElementById('sidebar-backdrop'),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getConfiguredSystemPrompt(config = appConfig) {
|
|
291
|
+
if (Engine.getConfiguredSystemPrompt) return Engine.getConfiguredSystemPrompt(config);
|
|
292
|
+
return [config.systemPrompt, config.systemDataPrompt].map(value => String(value || '').trim()).filter(Boolean).join('\n\n');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isHttpExecution() {
|
|
296
|
+
if (UIShell.isHttpExecution) return UIShell.isHttpExecution();
|
|
297
|
+
return typeof window !== 'undefined' && ['http:', 'https:'].includes(window.location.protocol);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function updateExecutionInfo() {
|
|
301
|
+
if (UIShell.updateExecutionInfo) return UIShell.updateExecutionInfo(elements);
|
|
302
|
+
const httpExecution = isHttpExecution();
|
|
303
|
+
if (elements.executionStorageScope) {
|
|
304
|
+
elements.executionStorageScope.textContent = t(httpExecution ? 'execution_info_http' : 'execution_info_file');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function openExecutionInfo() {
|
|
309
|
+
if (UIShell.openExecutionInfo) return UIShell.openExecutionInfo(elements);
|
|
310
|
+
if (!elements.executionInfoDialog) return;
|
|
311
|
+
updateExecutionInfo();
|
|
312
|
+
if (!elements.executionInfoDialog.open) elements.executionInfoDialog.showModal();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function closeExecutionInfo() {
|
|
316
|
+
if (UIShell.closeExecutionInfo) return UIShell.closeExecutionInfo(elements);
|
|
317
|
+
if (elements.executionInfoDialog?.open) elements.executionInfoDialog.close();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
function applyTheme(theme) {
|
|
323
|
+
if (UISettings.applyTheme) {
|
|
324
|
+
UISettings.applyTheme(elements, appConfig, theme);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function applyLanguage(lang) {
|
|
329
|
+
if (UISettings.applyLanguage) {
|
|
330
|
+
UISettings.applyLanguage(elements, appConfig, lang, { updateReasoningUI });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function getConversationServiceOptions() {
|
|
335
|
+
return {
|
|
336
|
+
storage: Storage,
|
|
337
|
+
getRuntimeConfig,
|
|
338
|
+
language: appConfig.language || 'es',
|
|
339
|
+
getConfiguredSystemPrompt: (cfg) => getConfiguredSystemPrompt(cfg),
|
|
340
|
+
getChatHistory,
|
|
341
|
+
getCurrentSessionId,
|
|
342
|
+
getSavedSessions,
|
|
343
|
+
renderSessionMessages: (history) => renderSessionMessages(history),
|
|
344
|
+
renderSidebarChats: () => renderSidebarChats(),
|
|
345
|
+
onSessionReset: () => {
|
|
346
|
+
clearAttachedFiles();
|
|
347
|
+
closeReasoningMenu();
|
|
348
|
+
clearDebugLogs();
|
|
349
|
+
setDebugStatus('idle');
|
|
350
|
+
toggleDebugPanel(false);
|
|
351
|
+
resetTelemetryDisplay({ syncState: false });
|
|
352
|
+
},
|
|
353
|
+
resetComposerInput: () => {
|
|
354
|
+
if (elements.userInput) {
|
|
355
|
+
elements.userInput.value = '';
|
|
356
|
+
autoResizeTextarea();
|
|
357
|
+
elements.userInput.focus();
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
closeSidebar: () => closeSidebar()
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
function blockSessionTransitionIfBusy(messageKey) {
|
|
368
|
+
if (ConversationService.blockSessionTransitionIfBusy) {
|
|
369
|
+
return ConversationService.blockSessionTransitionIfBusy(messageKey);
|
|
370
|
+
}
|
|
371
|
+
const isBusy = State.isConversationBusy?.() === true;
|
|
372
|
+
if (!isBusy) return false;
|
|
373
|
+
ChatDialogs.alert(t(messageKey));
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ==========================================================================
|
|
378
|
+
// Modelos y Consulta al Servidor (API Query & Combobox)
|
|
379
|
+
// ==========================================================================
|
|
380
|
+
|
|
381
|
+
function loadCachedModels(targetConfig) {
|
|
382
|
+
if (UIInspector.loadCachedModels) {
|
|
383
|
+
const config = targetConfig || {
|
|
384
|
+
apiUrl: elements.settingApiUrl?.value || appConfig?.apiUrl,
|
|
385
|
+
apiType: elements.settingApiType?.value || appConfig?.apiType
|
|
386
|
+
};
|
|
387
|
+
const models = UIInspector.loadCachedModels(elements, config);
|
|
388
|
+
syncPublishedModelContextLimit();
|
|
389
|
+
return models;
|
|
390
|
+
}
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function syncPublishedModelContextLimit() {
|
|
395
|
+
const config = getRuntimeConfig();
|
|
396
|
+
let publishedLimit = null;
|
|
397
|
+
if (config.apiType === 'webllm') {
|
|
398
|
+
const customSize = Number(config.webllmConfig?.context_window_size);
|
|
399
|
+
if (Number.isFinite(customSize) && customSize > 0) {
|
|
400
|
+
publishedLimit = Math.floor(customSize);
|
|
401
|
+
} else {
|
|
402
|
+
publishedLimit = UIInspector.getModelContextLimit?.(config.model) || 4096;
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
publishedLimit = UIInspector.getModelContextLimit?.(config.model);
|
|
406
|
+
}
|
|
407
|
+
if (!publishedLimit || config.modelContextLimit === publishedLimit || !Config.updateRuntime) return false;
|
|
408
|
+
Config.updateRuntime({ modelContextLimit: publishedLimit });
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function handleQueryServer() {
|
|
413
|
+
if (UIInspector.handleQueryServer) {
|
|
414
|
+
const querySucceeded = await UIInspector.handleQueryServer(elements, appConfig);
|
|
415
|
+
if (querySucceeded) {
|
|
416
|
+
setProfileQueryState(true);
|
|
417
|
+
}
|
|
418
|
+
return querySucceeded;
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ==========================================================================
|
|
424
|
+
// Provider Inspector (Diagnóstico de Capacidades)
|
|
425
|
+
// ==========================================================================
|
|
426
|
+
|
|
427
|
+
async function handleRunInspector() {
|
|
428
|
+
if (UIInspector.handleRunInspector) {
|
|
429
|
+
await UIInspector.handleRunInspector(elements, appConfig);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ==========================================================================
|
|
434
|
+
// Control Dinámico de Nivel de Razonamiento (Thinking / CoT)
|
|
435
|
+
// ==========================================================================
|
|
436
|
+
|
|
437
|
+
function positionReasoningMenu() {
|
|
438
|
+
if (UIReasoning.positionReasoningMenu) {
|
|
439
|
+
UIReasoning.positionReasoningMenu(elements);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function toggleCheckpointAgent(enabled) {
|
|
444
|
+
const currentTools = (appConfig && appConfig.enabledTools) ? appConfig.enabledTools : {};
|
|
445
|
+
const updatedTools = { ...currentTools, agent_checkpoint: Boolean(enabled) };
|
|
446
|
+
if (Config.updateRuntime) {
|
|
447
|
+
Config.updateRuntime({ enabledTools: updatedTools });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function toggleReasoningMenu() {
|
|
452
|
+
if (UIReasoning.toggleReasoningMenu) {
|
|
453
|
+
UIReasoning.toggleReasoningMenu(elements, appConfig, selectReasoningLevel, toggleCheckpointAgent, selectReasoningTransport);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function selectReasoningLevel(level) {
|
|
458
|
+
if (UIReasoning.updateReasoningUI) UIReasoning.updateReasoningUI(elements, level);
|
|
459
|
+
if (Config.updateRuntime) Config.updateRuntime({ reasoningEffort: level });
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function selectReasoningTransport(reasoningTransport) {
|
|
463
|
+
if (Config.updateRuntime) Config.updateRuntime({ reasoningTransport });
|
|
464
|
+
closeReasoningMenu();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function updateReasoningUI(level) {
|
|
468
|
+
if (UIReasoning.updateReasoningUI) {
|
|
469
|
+
UIReasoning.updateReasoningUI(elements, level);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function closeReasoningMenu() {
|
|
474
|
+
if (UIReasoning.closeReasoningMenu) {
|
|
475
|
+
UIReasoning.closeReasoningMenu(elements);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ==========================================================================
|
|
480
|
+
// ==========================================================================
|
|
481
|
+
// Panel Lateral de Razonamiento, Streaming & Logs (Debug)
|
|
482
|
+
// ==========================================================================
|
|
483
|
+
|
|
484
|
+
function toggleDebugPanel(forceOpen) {
|
|
485
|
+
if (Debug.togglePanel) Debug.togglePanel(forceOpen);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function setDebugStatus(status, text) {
|
|
489
|
+
if (Debug.setStatus) Debug.setStatus(status, text);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function clearDebugLogs() {
|
|
493
|
+
if (Debug.clearLogs) Debug.clearLogs();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function copyDebugLogs() {
|
|
497
|
+
if (Debug.copyLogs) await Debug.copyLogs();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function addDebugLog(type, text, rawData) {
|
|
501
|
+
if (Debug.addLog) Debug.addLog(type, text, rawData);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function setGenerationStatus(update = {}) {
|
|
505
|
+
const genStatus = window.ChatUIGenerationStatus || GenerationStatus;
|
|
506
|
+
if (genStatus?.setStatus) {
|
|
507
|
+
return genStatus.setStatus(elements.generationStatus, update);
|
|
508
|
+
}
|
|
509
|
+
const raw = typeof update === 'string' ? { text: update } : (update || {});
|
|
510
|
+
const isGenerating = Boolean(State.get?.('streaming')?.isGenerating);
|
|
511
|
+
const phase = String(raw.phase || (raw.text || raw.message ? 'custom' : 'idle'));
|
|
512
|
+
if (!isGenerating && phase !== 'idle') return;
|
|
513
|
+
|
|
514
|
+
let next;
|
|
515
|
+
if (State.setGenerationStatus) {
|
|
516
|
+
next = State.setGenerationStatus(raw);
|
|
517
|
+
} else {
|
|
518
|
+
const ui = State.get?.('ui') || {};
|
|
519
|
+
const current = ui.generationStatus || { phase: 'idle', percent: null, startedAt: null };
|
|
520
|
+
const phaseChanged = phase !== current.phase;
|
|
521
|
+
next = phaseChanged
|
|
522
|
+
? { text: '', message: '', detail: '', percent: null, ...raw, phase, startedAt: Date.now() }
|
|
523
|
+
: { ...current, ...raw, phase, startedAt: current.startedAt || Date.now() };
|
|
524
|
+
State.set('ui', { ...ui, generationStatus: next });
|
|
525
|
+
}
|
|
526
|
+
genStatus.render?.(elements.generationStatus, next);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function clearGenerationStatus() {
|
|
530
|
+
const genStatus = window.ChatUIGenerationStatus || GenerationStatus;
|
|
531
|
+
if (genStatus?.clearStatus) {
|
|
532
|
+
return genStatus.clearStatus(elements.generationStatus);
|
|
533
|
+
}
|
|
534
|
+
if (State.clearGenerationStatus) {
|
|
535
|
+
State.clearGenerationStatus();
|
|
536
|
+
} else {
|
|
537
|
+
const ui = State.get?.('ui') || {};
|
|
538
|
+
State.set('ui', { ...ui, generationStatus: { phase: 'idle', percent: null, text: '', message: '', detail: '', startedAt: null } });
|
|
539
|
+
}
|
|
540
|
+
genStatus.render?.(elements.generationStatus, { phase: 'idle' });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function filterDebugLogs(tabId) {
|
|
544
|
+
if (Debug.filterLogs) Debug.filterLogs(tabId);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function syncDebugMessagesState(enabled, persist = true) {
|
|
548
|
+
const value = Boolean(enabled);
|
|
549
|
+
if (elements.chkEnableDebugMessages) {
|
|
550
|
+
elements.chkEnableDebugMessages.checked = value;
|
|
551
|
+
}
|
|
552
|
+
if (elements.debugMessagesStatusBadge) {
|
|
553
|
+
elements.debugMessagesStatusBadge.textContent = value ? 'ON' : 'OFF';
|
|
554
|
+
elements.debugMessagesStatusBadge.className = 'debug-status-pill ' + (value ? 'on' : 'off');
|
|
555
|
+
}
|
|
556
|
+
if (persist && Config.updateGeneral) Config.updateGeneral({ enableDebugMessages: value });
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function openDebugInterceptorModal({ endpoint, headers, payload }) {
|
|
560
|
+
if (Debug.openInterceptorModal) {
|
|
561
|
+
return Debug.openInterceptorModal({
|
|
562
|
+
endpoint,
|
|
563
|
+
headers,
|
|
564
|
+
payload,
|
|
565
|
+
onSyncDebugState: (enabled) => syncDebugMessagesState(enabled)
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
return Promise.resolve({ cancel: false, modifiedPayload: null });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function updateUIFromConfig() {
|
|
572
|
+
const config = getRuntimeConfig();
|
|
573
|
+
// El diálogo contiene un borrador de perfil. Las notificaciones de la
|
|
574
|
+
// configuración activa (telemetría, caché, etc.) no deben sobrescribirlo.
|
|
575
|
+
const profileEditorOpen = elements.profilesDialog?.open === true;
|
|
576
|
+
if (elements.activeProfileName) elements.activeProfileName.textContent = config.activeProfile?.name || 'Espejo';
|
|
577
|
+
if (elements.activeProfilePopover && !elements.activeProfilePopover.hidden) {
|
|
578
|
+
let profileList = [];
|
|
579
|
+
try { profileList = Profiles.list ? Profiles.list() : []; } catch (_) {}
|
|
580
|
+
UISettings.renderProfileMenu(elements, profileList, config.activeProfile?.id);
|
|
581
|
+
}
|
|
582
|
+
if (elements.settingsActiveProfileName) {
|
|
583
|
+
let mirrorName = 'Espejo';
|
|
584
|
+
try { mirrorName = Profiles.get?.(Profiles.READONLY_PROFILE_ID)?.name || 'Espejo'; } catch (_) {}
|
|
585
|
+
elements.settingsActiveProfileName.textContent = config.activeProfile?.name || mirrorName;
|
|
586
|
+
}
|
|
587
|
+
if (!profileEditorOpen) {
|
|
588
|
+
if (elements.settingApiType) {
|
|
589
|
+
elements.settingApiType.value = config.apiType || 'openai';
|
|
590
|
+
}
|
|
591
|
+
if (elements.settingApiUrl) {
|
|
592
|
+
elements.settingApiUrl.value = config.apiUrl || 'http://localhost:1234/v1';
|
|
593
|
+
}
|
|
594
|
+
if (elements.settingModel) {
|
|
595
|
+
elements.settingModel.value = config.model || '';
|
|
596
|
+
}
|
|
597
|
+
if (elements.modelSelectHelper && config.model) {
|
|
598
|
+
elements.modelSelectHelper.value = config.model;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
updateReasoningUI(config.reasoningEffort || 'medium');
|
|
602
|
+
applyTheme(config.theme);
|
|
603
|
+
applyLanguage(config.language || 'es');
|
|
604
|
+
|
|
605
|
+
syncDebugMessagesState(config.enableDebugMessages, false);
|
|
606
|
+
updateConnectionTokensBadge(null);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function getUITelemetry() {
|
|
610
|
+
return window.ChatUITelemetry || (typeof require !== 'undefined' ? (() => { try { return require('./ui-telemetry.js'); } catch (e) { return null; } })() : null);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function updateConnectionTokensBadge(stats, diagnostics, options = {}) {
|
|
614
|
+
const UITel = getUITelemetry();
|
|
615
|
+
if (!UITel || !elements.connectionTokensBadge) return;
|
|
616
|
+
|
|
617
|
+
if (stats) {
|
|
618
|
+
State.set('telemetry', prev => ({ ...(prev || {}), stats }));
|
|
619
|
+
}
|
|
620
|
+
if (diagnostics) {
|
|
621
|
+
State.set('telemetry', prev => ({ ...(prev || {}), diagnostics }));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const telState = State.get ? State.get('telemetry') : {};
|
|
625
|
+
const effectiveStats = stats || telState?.stats || null;
|
|
626
|
+
const effectiveDiag = diagnostics || telState?.diagnostics || null;
|
|
627
|
+
|
|
628
|
+
const runtimeCfg = (State.get ? State.get('config') : (window.ChatConfig ? window.ChatConfig.getConfig() : {})) || appConfig;
|
|
629
|
+
const CM = window.ChatContextManager || (typeof require !== 'undefined' ? (() => { try { return require('./context-manager.js'); } catch (e) { return null; } })() : null);
|
|
630
|
+
|
|
631
|
+
const vm = UITel.computeTelemetryViewModel({
|
|
632
|
+
stats: effectiveStats,
|
|
633
|
+
diagnostics: effectiveDiag,
|
|
634
|
+
config: runtimeCfg,
|
|
635
|
+
chatHistory: getChatHistory(),
|
|
636
|
+
contextManager: CM
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
UITel.renderTelemetry(elements, vm, options, t);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function resetTelemetryDisplay({ syncState = true } = {}) {
|
|
643
|
+
const UITel = getUITelemetry();
|
|
644
|
+
if (syncState && State.set) {
|
|
645
|
+
State.set('telemetry', { stats: null, diagnostics: null, lastTurnStats: null });
|
|
646
|
+
}
|
|
647
|
+
if (UITel && elements.connectionTokensBadge) {
|
|
648
|
+
const runtimeCfg = (State.get ? State.get('config') : (window.ChatConfig ? window.ChatConfig.getConfig() : {})) || appConfig;
|
|
649
|
+
const CM = window.ChatContextManager || (typeof require !== 'undefined' ? (() => { try { return require('./context-manager.js'); } catch (e) { return null; } })() : null);
|
|
650
|
+
UITel.resetTelemetry(elements, runtimeCfg, getChatHistory(), CM, t);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// field-sizing:content gestiona el auto-resize en CSS (Baseline 2024).
|
|
655
|
+
// Esta función solo actúa como fallback para navegadores sin soporte.
|
|
656
|
+
function autoResizeTextarea() {
|
|
657
|
+
if (UIComposer.autoResizeTextarea) {
|
|
658
|
+
return UIComposer.autoResizeTextarea(elements);
|
|
659
|
+
}
|
|
660
|
+
if (CSS && CSS.supports && CSS.supports('field-sizing', 'content')) return;
|
|
661
|
+
if (!elements.userInput) return;
|
|
662
|
+
if (!elements.userInput.value) {
|
|
663
|
+
elements.userInput.style.height = '';
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
elements.userInput.style.height = 'auto';
|
|
667
|
+
const newHeight = Math.min(elements.userInput.scrollHeight, 160);
|
|
668
|
+
elements.userInput.style.height = `${newHeight}px`;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function scrollToBottom() {
|
|
672
|
+
if (UIConversation.scrollToBottom) {
|
|
673
|
+
return UIConversation.scrollToBottom(elements.messagesList);
|
|
674
|
+
}
|
|
675
|
+
if (elements.messagesList) {
|
|
676
|
+
elements.messagesList.scrollTop = elements.messagesList.scrollHeight;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ==========================================================================
|
|
681
|
+
// Gestión de Archivos Adjuntos
|
|
682
|
+
// ==========================================================================
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
function clearAttachedFiles() {
|
|
686
|
+
if (UIComposer.clearAttachedFiles) {
|
|
687
|
+
return UIComposer.clearAttachedFiles(elements);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async function processFiles(files) {
|
|
692
|
+
if (UIComposer.processFiles) {
|
|
693
|
+
return UIComposer.processFiles(elements, files);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
// ==========================================================================
|
|
699
|
+
// Renderizado de Mensajes con Acciones y Estadísticas
|
|
700
|
+
// ==========================================================================
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
function removeMessage(wrapper) {
|
|
705
|
+
if (UIConversation.removeMessage) {
|
|
706
|
+
return UIConversation.removeMessage(wrapper, {
|
|
707
|
+
messagesList: elements.messagesList,
|
|
708
|
+
welcomeBanner: elements.welcomeBanner,
|
|
709
|
+
isBusy: () => State.isConversationBusy?.(),
|
|
710
|
+
removeTurn: (payload) => State.removeTurn?.(payload),
|
|
711
|
+
saveSession: () => saveCurrentSession(),
|
|
712
|
+
addDebugLog: (type, text) => (typeof addDebugLog === 'function' ? addDebugLog(type, text) : null)
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function appendUserMessage(text, originalPrompt, attachedImages, existingMsgId) {
|
|
718
|
+
if (UIConversation.appendUserMessage) {
|
|
719
|
+
return UIConversation.appendUserMessage(elements.messagesList, elements.welcomeBanner, {
|
|
720
|
+
text,
|
|
721
|
+
originalPrompt,
|
|
722
|
+
attachedImages,
|
|
723
|
+
existingMsgId
|
|
724
|
+
}, {
|
|
725
|
+
onReuse: (txt) => {
|
|
726
|
+
if (elements.userInput) {
|
|
727
|
+
elements.userInput.value = txt;
|
|
728
|
+
autoResizeTextarea();
|
|
729
|
+
elements.userInput.focus();
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
onDelete: (wrapper) => removeMessage(wrapper)
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function createAssistantMessagePlaceholder(existingMsgId) {
|
|
738
|
+
if (UIConversation.createAssistantMessagePlaceholder) {
|
|
739
|
+
return UIConversation.createAssistantMessagePlaceholder(elements.messagesList, existingMsgId, {
|
|
740
|
+
welcomeBanner: elements.welcomeBanner,
|
|
741
|
+
onBranch: (wrapper) => createConversationBranch(wrapper),
|
|
742
|
+
onDelete: (wrapper) => removeMessage(wrapper)
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ==========================================================================
|
|
748
|
+
// Envío de Mensaje y Streaming
|
|
749
|
+
// ==========================================================================
|
|
750
|
+
|
|
751
|
+
function getGenerationControllerOptions() {
|
|
752
|
+
return {
|
|
753
|
+
elements,
|
|
754
|
+
getRuntimeConfig,
|
|
755
|
+
getCurrentSessionId,
|
|
756
|
+
getChatHistory,
|
|
757
|
+
setGenerationStatus,
|
|
758
|
+
clearGenerationStatus,
|
|
759
|
+
showTypingIndicator,
|
|
760
|
+
removeTypingIndicator,
|
|
761
|
+
createAssistantMessagePlaceholder,
|
|
762
|
+
appendUserMessage,
|
|
763
|
+
attachListenersToContainer,
|
|
764
|
+
scrollToBottom,
|
|
765
|
+
updateConnectionTokensBadge,
|
|
766
|
+
addDebugLog,
|
|
767
|
+
setDebugStatus,
|
|
768
|
+
openDebugInterceptorModal,
|
|
769
|
+
saveCurrentSession,
|
|
770
|
+
clearAttachedFiles,
|
|
771
|
+
autoResizeTextarea,
|
|
772
|
+
closeReasoningMenu,
|
|
773
|
+
setAssistantGroupMessageIds
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async function handleSendMessage() {
|
|
778
|
+
if (GenerationController.handleSendMessage) {
|
|
779
|
+
return await GenerationController.handleSendMessage(getGenerationControllerOptions());
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
function handleStopGeneration() {
|
|
785
|
+
if (GenerationController.handleStopGeneration) {
|
|
786
|
+
return GenerationController.handleStopGeneration();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// ==========================================================================
|
|
791
|
+
// Modal de Configuración & Gestión de Perfiles
|
|
792
|
+
// ==========================================================================
|
|
793
|
+
|
|
794
|
+
function getProfilesHelperOptions() {
|
|
795
|
+
return {
|
|
796
|
+
getRuntimeConfig,
|
|
797
|
+
loadCachedModels,
|
|
798
|
+
resetTelemetryDisplay,
|
|
799
|
+
updateUIFromConfig,
|
|
800
|
+
updateReasoningUI
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Puebla el combobox auxiliar y el datalist con todos los perfiles disponibles.
|
|
806
|
+
*/
|
|
807
|
+
function populateProfileSelector(selectedProfileName) {
|
|
808
|
+
if (UIProfiles.populateProfileSelector) {
|
|
809
|
+
return UIProfiles.populateProfileSelector(elements, selectedProfileName);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function applyProfileToForm(profileData, profileId = null) {
|
|
814
|
+
if (UIProfiles.applyProfileToForm) {
|
|
815
|
+
return UIProfiles.applyProfileToForm(elements, profileData, profileId, getProfilesHelperOptions());
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function gatherCurrentFormConfig() {
|
|
820
|
+
if (UISettings.gatherCurrentFormConfig) {
|
|
821
|
+
return UISettings.gatherCurrentFormConfig(elements, appConfig);
|
|
822
|
+
}
|
|
823
|
+
return appConfig;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function showProfileFeedback(msg, type = 'success') {
|
|
827
|
+
if (UISettings.showProfileFeedback) {
|
|
828
|
+
UISettings.showProfileFeedback(elements, msg, type);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function saveCurrentSettings(closeModal = true) {
|
|
833
|
+
const newConfig = gatherCurrentFormConfig();
|
|
834
|
+
const savedConfig = Config.updateRuntime ? Config.updateRuntime(newConfig) : newConfig;
|
|
835
|
+
if (UISettings.setSettingsFormDirty) UISettings.setSettingsFormDirty(elements, false);
|
|
836
|
+
loadCachedModels();
|
|
837
|
+
|
|
838
|
+
const currentHistory = getChatHistory();
|
|
839
|
+
if (currentHistory.length > 0 && currentHistory[0].role === 'system') {
|
|
840
|
+
currentHistory[0].content = getConfiguredSystemPrompt(savedConfig);
|
|
841
|
+
if (State.replaceMessages) {
|
|
842
|
+
State.replaceMessages(currentHistory);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (typeof populateProfileSelector === 'function') {
|
|
847
|
+
populateProfileSelector(savedConfig.activeProfile?.id || '');
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (closeModal) {
|
|
851
|
+
closeSettingsPanelOnly(true);
|
|
852
|
+
} else {
|
|
853
|
+
showProfileFeedback(t('msg_profile_saved', { name: savedConfig.activeProfile?.name || 'actual' }) || 'Configuración actualizada.', 'success');
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
function syncProfileSaveState() {
|
|
860
|
+
if (UIProfiles.syncProfileSaveState) {
|
|
861
|
+
return UIProfiles.syncProfileSaveState(elements, getProfilesHelperOptions());
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async function handleSaveProfile() {
|
|
866
|
+
if (UIProfiles.handleSaveProfile) {
|
|
867
|
+
return UIProfiles.handleSaveProfile(elements, getProfilesHelperOptions());
|
|
868
|
+
}
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function isProfileQueryReady() {
|
|
873
|
+
if (UIProfiles.isProfileQueryReady) {
|
|
874
|
+
return UIProfiles.isProfileQueryReady(elements);
|
|
875
|
+
}
|
|
876
|
+
return elements.profilesDialog?.dataset.queryReady === 'true';
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function isProfileFormDirty() {
|
|
880
|
+
if (UIProfiles.isProfileFormDirty) {
|
|
881
|
+
return UIProfiles.isProfileFormDirty(elements, getProfilesHelperOptions());
|
|
882
|
+
}
|
|
883
|
+
return elements.profilesDialog?.dataset.profileDirty === 'true';
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function setProfileDirty(dirty = true) {
|
|
887
|
+
if (UIProfiles.setProfileDirty) {
|
|
888
|
+
return UIProfiles.setProfileDirty(elements, dirty);
|
|
889
|
+
}
|
|
890
|
+
if (elements.profilesDialog) {
|
|
891
|
+
if (!elements.profilesDialog.dataset) elements.profilesDialog.dataset = {};
|
|
892
|
+
elements.profilesDialog.dataset.profileDirty = String(Boolean(dirty));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function setProfileQueryState(ready) {
|
|
897
|
+
if (UIProfiles.setProfileQueryState) {
|
|
898
|
+
return UIProfiles.setProfileQueryState(elements, ready, getProfilesHelperOptions());
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function setSelectedProfileAsDefault(profile) {
|
|
903
|
+
if (UIProfiles.setSelectedProfileAsDefault) {
|
|
904
|
+
return UIProfiles.setSelectedProfileAsDefault(profile, getProfilesHelperOptions());
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function activateConnectionProfile(profileId) {
|
|
909
|
+
if (UIProfiles.activateConnectionProfile) {
|
|
910
|
+
return UIProfiles.activateConnectionProfile(profileId, getProfilesHelperOptions());
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
function openProfileMenu() {
|
|
916
|
+
if (UIProfiles.openProfileMenu) {
|
|
917
|
+
return UIProfiles.openProfileMenu(elements, getProfilesHelperOptions());
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function closeProfileMenu() {
|
|
922
|
+
if (UIProfiles.closeProfileMenu) {
|
|
923
|
+
return UIProfiles.closeProfileMenu(elements);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async function handleDeleteProfile() {
|
|
928
|
+
if (UIProfiles.handleDeleteProfile) {
|
|
929
|
+
return UIProfiles.handleDeleteProfile(elements, getProfilesHelperOptions());
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
async function handleDeleteProfileById(profileId) {
|
|
934
|
+
if (UIProfiles.handleDeleteProfileById) {
|
|
935
|
+
return UIProfiles.handleDeleteProfileById(profileId, elements, getProfilesHelperOptions());
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
async function handleNewProfile() {
|
|
940
|
+
if (UIProfiles.handleNewProfile) {
|
|
941
|
+
return UIProfiles.handleNewProfile(elements, getProfilesHelperOptions());
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
async function handleMenuNewProfile() {
|
|
946
|
+
if (UIProfiles.handleMenuNewProfile) {
|
|
947
|
+
return UIProfiles.handleMenuNewProfile(elements, getProfilesHelperOptions());
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async function handleExportProfiles() {
|
|
952
|
+
if (UIProfiles.handleExportProfiles) {
|
|
953
|
+
return UIProfiles.handleExportProfiles(elements);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
async function handleImportProfiles(event) {
|
|
958
|
+
if (UIProfiles.handleImportProfiles) {
|
|
959
|
+
return UIProfiles.handleImportProfiles(event, elements, getProfilesHelperOptions());
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function openSettingsSection(sectionId = 'tab-model') {
|
|
964
|
+
if (sectionId === 'rag-manage') {
|
|
965
|
+
if (window.ChatRagUI && typeof window.ChatRagUI.openRagModal === 'function') {
|
|
966
|
+
window.ChatRagUI.openRagModal('manage');
|
|
967
|
+
}
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
if (UISettings.openSettingsSection) {
|
|
971
|
+
UISettings.openSettingsSection(elements, getRuntimeConfig(), {
|
|
972
|
+
populateProfileSelector,
|
|
973
|
+
loadCachedModels,
|
|
974
|
+
updateReasoningUI
|
|
975
|
+
}, sectionId);
|
|
976
|
+
}
|
|
977
|
+
if (sectionId === 'tab-mcp') {
|
|
978
|
+
window.ChatUIMcp?.verifyActiveConnection?.().catch(() => {});
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function openProfilesModal(targetId = null) {
|
|
983
|
+
if (UIProfiles.openProfilesModal) {
|
|
984
|
+
return UIProfiles.openProfilesModal(elements, getProfilesHelperOptions(), targetId);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
function openNewProfileModal() {
|
|
989
|
+
if (UIProfiles.openNewProfileModal) {
|
|
990
|
+
return UIProfiles.openNewProfileModal(elements, getProfilesHelperOptions());
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function resetProfileFormToSelected() {
|
|
995
|
+
if (UIProfiles.resetProfileFormToSelected) {
|
|
996
|
+
return UIProfiles.resetProfileFormToSelected(elements, getProfilesHelperOptions());
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
async function closeProfilesModal(force = false) {
|
|
1001
|
+
if (UIProfiles.closeProfilesModal) {
|
|
1002
|
+
return UIProfiles.closeProfilesModal(elements, force, getProfilesHelperOptions());
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
async function closeSettingsModal(force = false) {
|
|
1007
|
+
let closed = true;
|
|
1008
|
+
if (UISettings.closeSettingsModal) {
|
|
1009
|
+
closed = await UISettings.closeSettingsModal(elements, force);
|
|
1010
|
+
}
|
|
1011
|
+
if (closed && UISidebar.setSidebarMode) {
|
|
1012
|
+
UISidebar.setSidebarMode(elements, 'chat');
|
|
1013
|
+
}
|
|
1014
|
+
return closed;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function closeSettingsPanelOnly(force = false) {
|
|
1018
|
+
let closed = true;
|
|
1019
|
+
if (UISettings.closeSettingsModal) {
|
|
1020
|
+
closed = await UISettings.closeSettingsModal(elements, force);
|
|
1021
|
+
}
|
|
1022
|
+
if (!closed) return false;
|
|
1023
|
+
if (UISidebar.setSidebarMode) {
|
|
1024
|
+
UISidebar.setSidebarMode(elements, 'settings');
|
|
1025
|
+
}
|
|
1026
|
+
if (UISidebar.isMobile && UISidebar.isMobile() && UISidebar.openSidebar) {
|
|
1027
|
+
UISidebar.openSidebar(elements);
|
|
1028
|
+
}
|
|
1029
|
+
return true;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function handleSaveSettings(e) {
|
|
1033
|
+
if (e && e.preventDefault) e.preventDefault();
|
|
1034
|
+
saveCurrentSettings(true);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function handleClearAllData() {
|
|
1038
|
+
if (UISettings.handleClearAllData) {
|
|
1039
|
+
UISettings.handleClearAllData();
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// ==========================================================================
|
|
1044
|
+
// Gestión de Múltiples Sesiones de Chat (Sidebar & IndexedDB Storage)
|
|
1045
|
+
// ==========================================================================
|
|
1046
|
+
|
|
1047
|
+
async function loadSessionsFromStorage() {
|
|
1048
|
+
if (ConversationService.loadSessionsFromStorage) {
|
|
1049
|
+
return await ConversationService.loadSessionsFromStorage(getConversationServiceOptions());
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
async function saveCurrentSession() {
|
|
1054
|
+
if (ConversationService.saveCurrentSession) {
|
|
1055
|
+
return await ConversationService.saveCurrentSession(getConversationServiceOptions());
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function renderSidebarChats(filterText = '') {
|
|
1060
|
+
if (UISidebar.renderSidebarChats) {
|
|
1061
|
+
UISidebar.renderSidebarChats(elements, getSavedSessions(), getCurrentSessionId(), {
|
|
1062
|
+
onSwitchSession: switchToSession,
|
|
1063
|
+
onRenameSession: renameSession,
|
|
1064
|
+
onDeleteSession: deleteSession,
|
|
1065
|
+
onExportSession: (sessionId, e) => {
|
|
1066
|
+
if (e && e.stopPropagation) e.stopPropagation();
|
|
1067
|
+
openExportModal(sessionId);
|
|
1068
|
+
}
|
|
1069
|
+
}, { groupByDate: true });
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
async function switchToSession(sessionId, opts = {}) {
|
|
1074
|
+
if (ConversationService.switchToSession) {
|
|
1075
|
+
return await ConversationService.switchToSession(sessionId, Object.assign({}, getConversationServiceOptions(), opts));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
async function createNewSession(opts = {}) {
|
|
1080
|
+
if (ConversationService.createNewSession) {
|
|
1081
|
+
return await ConversationService.createNewSession(Object.assign({}, getConversationServiceOptions(), opts));
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
function setAssistantGroupMessageIds(wrapper, history) {
|
|
1087
|
+
if (ConversationService.setAssistantGroupMessageIds) {
|
|
1088
|
+
return ConversationService.setAssistantGroupMessageIds(wrapper, history);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
async function createConversationBranch(wrapper) {
|
|
1094
|
+
if (ConversationService.createConversationBranch) {
|
|
1095
|
+
return await ConversationService.createConversationBranch(wrapper, getConversationServiceOptions());
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
async function deleteSession(sessionId, event) {
|
|
1100
|
+
if (ConversationService.deleteSession) {
|
|
1101
|
+
return await ConversationService.deleteSession(sessionId, event, getConversationServiceOptions());
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
async function deleteAllSessions() {
|
|
1106
|
+
if (ConversationService.deleteAllSessions) {
|
|
1107
|
+
return await ConversationService.deleteAllSessions(getConversationServiceOptions());
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
async function renameSession(sessionId, event) {
|
|
1112
|
+
if (ConversationService.renameSession) {
|
|
1113
|
+
return await ConversationService.renameSession(sessionId, event, getConversationServiceOptions());
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function toggleSidebar() {
|
|
1118
|
+
if (UISidebar.toggleSidebar) {
|
|
1119
|
+
UISidebar.toggleSidebar(elements);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
function closeSidebar() {
|
|
1125
|
+
if (UISidebar.closeSidebar) {
|
|
1126
|
+
UISidebar.closeSidebar(elements);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
function attachListenersToContainer(container) {
|
|
1132
|
+
if (UIConversation.attachListenersToContainer) {
|
|
1133
|
+
return UIConversation.attachListenersToContainer(container);
|
|
1134
|
+
}
|
|
1135
|
+
if (!container) return;
|
|
1136
|
+
if (Markdown.attachCopyCodeListeners) {
|
|
1137
|
+
Markdown.attachCopyCodeListeners(container);
|
|
1138
|
+
}
|
|
1139
|
+
if (Markdown.attachRunJsListeners) {
|
|
1140
|
+
Markdown.attachRunJsListeners(container, (code, outputEl) => {
|
|
1141
|
+
if (Sandbox && Sandbox.execute) {
|
|
1142
|
+
Sandbox.execute(code).then(res => {
|
|
1143
|
+
if (outputEl) {
|
|
1144
|
+
outputEl.textContent = res.success ? (res.result || res.logs.join('\n') || 'undefined') : `Error: ${res.error}`;
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
function renderSessionMessages(history) {
|
|
1153
|
+
if (UIConversation.renderSessionMessages) {
|
|
1154
|
+
return UIConversation.renderSessionMessages(elements, history, {
|
|
1155
|
+
onBranch: (wrapper) => createConversationBranch(wrapper),
|
|
1156
|
+
onDelete: (wrapper) => removeMessage(wrapper),
|
|
1157
|
+
onReuse: (txt) => {
|
|
1158
|
+
if (elements.userInput) {
|
|
1159
|
+
elements.userInput.value = txt;
|
|
1160
|
+
autoResizeTextarea();
|
|
1161
|
+
elements.userInput.focus();
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
resetTelemetry: () => resetTelemetryDisplay(),
|
|
1165
|
+
updateTelemetry: () => updateConnectionTokensBadge(null, null, { forcePopover: true })
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// ==========================================================================
|
|
1171
|
+
// Modal de Exportación e Importación de Conversaciones
|
|
1172
|
+
// ==========================================================================
|
|
1173
|
+
|
|
1174
|
+
function getExportTransferOptions() {
|
|
1175
|
+
return {
|
|
1176
|
+
getActiveSessionId: () => getCurrentSessionId(),
|
|
1177
|
+
getSavedSessions: () => getSavedSessions(),
|
|
1178
|
+
getHistory: () => getChatHistory(),
|
|
1179
|
+
getSession: async (id) => (Storage && Storage.getConversation) ? await Storage.getConversation(id) : null,
|
|
1180
|
+
getConfig: () => appConfig,
|
|
1181
|
+
getModel: () => appConfig.model,
|
|
1182
|
+
onSwitchSession: async (id) => await switchToSession(id)
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function getExportTargetSessionId() {
|
|
1187
|
+
if (UITransfer.getExportTargetSessionId) return UITransfer.getExportTargetSessionId(elements, getCurrentSessionId());
|
|
1188
|
+
return elements.exportModal?.dataset?.sessionId || getCurrentSessionId();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function openExportModal(targetSessionId = null) {
|
|
1192
|
+
if (UITransfer.openExportModal) {
|
|
1193
|
+
UITransfer.openExportModal(elements, targetSessionId, getCurrentSessionId());
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (elements.exportModal) {
|
|
1197
|
+
elements.exportModal.dataset.sessionId = targetSessionId || getCurrentSessionId();
|
|
1198
|
+
if (typeof elements.exportModal.showModal === 'function') {
|
|
1199
|
+
elements.exportModal.showModal();
|
|
1200
|
+
} else {
|
|
1201
|
+
elements.exportModal.style.display = 'block';
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function closeExportModal() {
|
|
1207
|
+
if (UITransfer.closeExportModal) {
|
|
1208
|
+
UITransfer.closeExportModal(elements);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (elements.exportModal) {
|
|
1212
|
+
delete elements.exportModal.dataset.sessionId;
|
|
1213
|
+
if (typeof elements.exportModal.close === 'function') {
|
|
1214
|
+
elements.exportModal.close();
|
|
1215
|
+
} else {
|
|
1216
|
+
elements.exportModal.style.display = 'none';
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
async function getSessionForExport() {
|
|
1222
|
+
if (UITransfer.resolveSessionForExport) {
|
|
1223
|
+
return await UITransfer.resolveSessionForExport(elements, getExportTransferOptions());
|
|
1224
|
+
}
|
|
1225
|
+
const id = getExportTargetSessionId();
|
|
1226
|
+
if (id === getCurrentSessionId()) {
|
|
1227
|
+
return { sess: getSavedSessions().find(s => s.id === id), history: getChatHistory() };
|
|
1228
|
+
}
|
|
1229
|
+
const conv = (Storage && Storage.getConversation) ? await Storage.getConversation(id) : null;
|
|
1230
|
+
return { sess: conv, history: conv?.history || [] };
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async function exportConversationAsMarkdown() {
|
|
1234
|
+
if (UITransfer.exportConversationAsMarkdown) {
|
|
1235
|
+
await UITransfer.exportConversationAsMarkdown(elements, getExportTransferOptions());
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
const { sess, history } = await getSessionForExport();
|
|
1239
|
+
const title = (sess && sess.title) || 'ZeroChat_Conversation';
|
|
1240
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
1241
|
+
const md = Export.buildMarkdownExport ? Export.buildMarkdownExport(history, { title, model: appConfig.model }) : '';
|
|
1242
|
+
if (Export.downloadFile) {
|
|
1243
|
+
Export.downloadFile(md, `${title.replace(/[^a-zA-Z0-9_-]/g, '_')}_${dateStr}.md`, 'text/markdown');
|
|
1244
|
+
}
|
|
1245
|
+
closeExportModal();
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
async function exportConversationAsJson() {
|
|
1249
|
+
if (UITransfer.exportConversationAsJson) {
|
|
1250
|
+
await UITransfer.exportConversationAsJson(elements, getExportTransferOptions());
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
const { sess, history } = await getSessionForExport();
|
|
1254
|
+
const title = (sess && sess.title) || 'ZeroChat_Conversation';
|
|
1255
|
+
const dateStr = new Date().toISOString().slice(0, 10);
|
|
1256
|
+
const jsonStr = Export.buildJsonExport ? Export.buildJsonExport(sess, history, appConfig) : '{}';
|
|
1257
|
+
if (Export.downloadFile) {
|
|
1258
|
+
Export.downloadFile(jsonStr, `${title.replace(/[^a-zA-Z0-9_-]/g, '_')}_${dateStr}.json`, 'application/json');
|
|
1259
|
+
}
|
|
1260
|
+
closeExportModal();
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
async function exportConversationAsPrint() {
|
|
1264
|
+
if (UITransfer.exportConversationAsPrint) {
|
|
1265
|
+
await UITransfer.exportConversationAsPrint(elements, getExportTransferOptions());
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
const targetId = getExportTargetSessionId();
|
|
1269
|
+
closeExportModal();
|
|
1270
|
+
if (targetId && targetId !== getCurrentSessionId()) {
|
|
1271
|
+
await switchToSession(targetId);
|
|
1272
|
+
}
|
|
1273
|
+
setTimeout(() => {
|
|
1274
|
+
window.print();
|
|
1275
|
+
}, 200);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
async function handleImportFileSelected(e) {
|
|
1279
|
+
const file = e.target.files && e.target.files[0];
|
|
1280
|
+
if (!file) return;
|
|
1281
|
+
|
|
1282
|
+
if (blockSessionTransitionIfBusy('chat_import_blocked_generating')) {
|
|
1283
|
+
if (elements.importJsonInput) elements.importJsonInput.value = '';
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const maxBytes = (typeof Attachments !== 'undefined' && Attachments?.MAX_FILE_SIZE) || (50 * 1024 * 1024);
|
|
1288
|
+
if (file.size > maxBytes) {
|
|
1289
|
+
await ChatDialogs.alert(t('err_file_too_large', { name: file.name, max: '50 MB' }), { type: 'error' });
|
|
1290
|
+
if (elements.importJsonInput) elements.importJsonInput.value = '';
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const reader = new FileReader();
|
|
1295
|
+
reader.onload = async function(evt) {
|
|
1296
|
+
try {
|
|
1297
|
+
const newSession = Export.parseImportedJson ? Export.parseImportedJson(evt.target.result, file.name.replace('.json', '')) : null;
|
|
1298
|
+
if (!newSession) throw new Error('Error al procesar el archivo');
|
|
1299
|
+
|
|
1300
|
+
Engine.ensureConversationDate(newSession.history, appConfig.language || 'es', newSession.createdAt);
|
|
1301
|
+
|
|
1302
|
+
if (State.importConversation) {
|
|
1303
|
+
const res = State.importConversation(newSession, newSession.history);
|
|
1304
|
+
if (!res.ok) {
|
|
1305
|
+
throw new Error(res.reason);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
renderSessionMessages(getChatHistory());
|
|
1310
|
+
await saveCurrentSession();
|
|
1311
|
+
ChatDialogs.alert(t('chat_imported_success'), { type: 'success' });
|
|
1312
|
+
} catch (err) {
|
|
1313
|
+
ChatDialogs.alert(t('chat_import_json_err', { err: err.message || err }), { type: 'error' });
|
|
1314
|
+
}
|
|
1315
|
+
if (elements.importJsonInput) elements.importJsonInput.value = '';
|
|
1316
|
+
};
|
|
1317
|
+
reader.readAsText(file);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// ==========================================================================
|
|
1321
|
+
// Pegado de Imágenes desde el Portapapeles (Ctrl + V)
|
|
1322
|
+
// ==========================================================================
|
|
1323
|
+
|
|
1324
|
+
function handlePasteEvent(e) {
|
|
1325
|
+
if (UIComposer.handlePasteEvent) {
|
|
1326
|
+
return UIComposer.handlePasteEvent(e, elements);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// ==========================================================================
|
|
1331
|
+
// Ajuste Dinámico de Altura de Viewport (Android / Tablets / iOS / Teclados)
|
|
1332
|
+
// ==========================================================================
|
|
1333
|
+
|
|
1334
|
+
function updateViewportHeight() {
|
|
1335
|
+
if (UIShell.updateViewportHeight) return UIShell.updateViewportHeight();
|
|
1336
|
+
let vh = window.innerHeight;
|
|
1337
|
+
if (window.visualViewport) {
|
|
1338
|
+
vh = window.visualViewport.height;
|
|
1339
|
+
}
|
|
1340
|
+
document.documentElement.style.setProperty('--app-height', `${vh}px`);
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
function setupViewportListeners() {
|
|
1344
|
+
if (UIShell.setupViewportListeners) {
|
|
1345
|
+
return UIShell.setupViewportListeners(elements, {
|
|
1346
|
+
onViewportChange: () => positionReasoningMenu()
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
updateViewportHeight();
|
|
1350
|
+
if (window.visualViewport) {
|
|
1351
|
+
window.visualViewport.addEventListener('resize', () => {
|
|
1352
|
+
updateViewportHeight();
|
|
1353
|
+
positionReasoningMenu();
|
|
1354
|
+
});
|
|
1355
|
+
window.visualViewport.addEventListener('scroll', () => {
|
|
1356
|
+
updateViewportHeight();
|
|
1357
|
+
positionReasoningMenu();
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
window.addEventListener('resize', () => {
|
|
1361
|
+
updateViewportHeight();
|
|
1362
|
+
positionReasoningMenu();
|
|
1363
|
+
});
|
|
1364
|
+
window.addEventListener('orientationchange', () => {
|
|
1365
|
+
setTimeout(() => { updateViewportHeight(); positionReasoningMenu(); }, 100);
|
|
1366
|
+
setTimeout(() => { updateViewportHeight(); positionReasoningMenu(); }, 300);
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
if (elements.userInput) {
|
|
1370
|
+
elements.userInput.addEventListener('focus', () => {
|
|
1371
|
+
setTimeout(() => {
|
|
1372
|
+
updateViewportHeight();
|
|
1373
|
+
positionReasoningMenu();
|
|
1374
|
+
if (elements.userInput) {
|
|
1375
|
+
elements.userInput.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
1376
|
+
}
|
|
1377
|
+
}, 150);
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// ==========================================================================
|
|
1383
|
+
// Fase 8 — Indicador de Escritura del Asistente
|
|
1384
|
+
// ==========================================================================
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
let typingIndicatorEl = null;
|
|
1388
|
+
|
|
1389
|
+
function showTypingIndicator() {
|
|
1390
|
+
if (UIConversation.showTypingIndicator) {
|
|
1391
|
+
return UIConversation.showTypingIndicator(elements.messagesList);
|
|
1392
|
+
}
|
|
1393
|
+
if (typingIndicatorEl) return;
|
|
1394
|
+
const wrapper = document.createElement('div');
|
|
1395
|
+
wrapper.id = 'typing-indicator-wrapper';
|
|
1396
|
+
wrapper.className = 'message-wrapper assistant';
|
|
1397
|
+
const row = document.createElement('div');
|
|
1398
|
+
row.className = 'message-row assistant';
|
|
1399
|
+
const indicator = document.createElement('div');
|
|
1400
|
+
indicator.className = 'typing-indicator';
|
|
1401
|
+
indicator.innerHTML = '<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>';
|
|
1402
|
+
row.appendChild(indicator);
|
|
1403
|
+
wrapper.appendChild(row);
|
|
1404
|
+
typingIndicatorEl = wrapper;
|
|
1405
|
+
if (elements.messagesList) {
|
|
1406
|
+
elements.messagesList.appendChild(wrapper);
|
|
1407
|
+
scrollToBottom();
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
function removeTypingIndicator() {
|
|
1412
|
+
if (UIConversation.removeTypingIndicator) {
|
|
1413
|
+
UIConversation.removeTypingIndicator();
|
|
1414
|
+
}
|
|
1415
|
+
if (typingIndicatorEl && typingIndicatorEl.parentNode) {
|
|
1416
|
+
typingIndicatorEl.parentNode.removeChild(typingIndicatorEl);
|
|
1417
|
+
}
|
|
1418
|
+
typingIndicatorEl = null;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// ==========================================================================
|
|
1422
|
+
// Fase 6 — View Transitions y Light-Dismiss
|
|
1423
|
+
// ==========================================================================
|
|
1424
|
+
|
|
1425
|
+
function setupLightDismissDialogs() {
|
|
1426
|
+
if (UIShell.setupLightDismissDialogs) {
|
|
1427
|
+
return UIShell.setupLightDismissDialogs(document, {
|
|
1428
|
+
onDismissProfiles: () => closeProfilesModal()
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
// Fallback para navegadores sin soporte de closedby="any"
|
|
1432
|
+
// Solo actúa si el atributo no está soportado nativamente
|
|
1433
|
+
if ('closedBy' in HTMLDialogElement.prototype) return;
|
|
1434
|
+
document.querySelectorAll('dialog[closedby="any"]:not(#notice-dialog)').forEach(dialog => {
|
|
1435
|
+
dialog.addEventListener('click', e => {
|
|
1436
|
+
// Si el clic fue directamente en el fondo del dialog (no en su contenido)
|
|
1437
|
+
if (e.target === dialog) {
|
|
1438
|
+
if (dialog.id === 'profiles-dialog') {
|
|
1439
|
+
closeProfilesModal();
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
dialog.close();
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// ==========================================================================
|
|
1449
|
+
// Escuchadores de Eventos
|
|
1450
|
+
// ==========================================================================
|
|
1451
|
+
|
|
1452
|
+
function setupEventListeners() {
|
|
1453
|
+
setupViewportListeners();
|
|
1454
|
+
|
|
1455
|
+
if (elements.btnOpenExecutionInfo) {
|
|
1456
|
+
elements.btnOpenExecutionInfo.addEventListener('click', openExecutionInfo);
|
|
1457
|
+
}
|
|
1458
|
+
if (elements.btnCloseExecutionInfo) {
|
|
1459
|
+
elements.btnCloseExecutionInfo.addEventListener('click', closeExecutionInfo);
|
|
1460
|
+
}
|
|
1461
|
+
if (elements.btnCloseExecutionInfoFooter) {
|
|
1462
|
+
elements.btnCloseExecutionInfoFooter.addEventListener('click', closeExecutionInfo);
|
|
1463
|
+
}
|
|
1464
|
+
if (elements.executionInfoDialog) {
|
|
1465
|
+
elements.executionInfoDialog.addEventListener('click', event => {
|
|
1466
|
+
if (event.target === elements.executionInfoDialog) closeExecutionInfo();
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
// Formulario de chat
|
|
1471
|
+
elements.chatForm.addEventListener('submit', function (e) {
|
|
1472
|
+
e.preventDefault();
|
|
1473
|
+
handleSendMessage();
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
// Tecla Enter y Pegado
|
|
1477
|
+
elements.userInput.addEventListener('keydown', function (e) {
|
|
1478
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1479
|
+
e.preventDefault();
|
|
1480
|
+
handleSendMessage();
|
|
1481
|
+
}
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
elements.userInput.addEventListener('input', autoResizeTextarea);
|
|
1485
|
+
elements.userInput.addEventListener('paste', handlePasteEvent);
|
|
1486
|
+
|
|
1487
|
+
// Botones de acción
|
|
1488
|
+
elements.btnStopStream.addEventListener('click', handleStopGeneration);
|
|
1489
|
+
|
|
1490
|
+
// Parada ordenada ante cierre de página o recarga durante generación
|
|
1491
|
+
if (typeof window !== 'undefined' && window.addEventListener) {
|
|
1492
|
+
const handleWindowUnload = () => {
|
|
1493
|
+
if (GenerationController?.isGenerating?.()) {
|
|
1494
|
+
handleStopGeneration();
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
window.addEventListener('beforeunload', handleWindowUnload);
|
|
1498
|
+
window.addEventListener('pagehide', handleWindowUnload);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
if (elements.btnOpenSettings) {
|
|
1504
|
+
elements.btnOpenSettings.addEventListener('click', () => {
|
|
1505
|
+
if (UISidebar.setSidebarMode) {
|
|
1506
|
+
UISidebar.setSidebarMode(elements, 'settings');
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
if (elements.btnSidebarBackToChats) {
|
|
1511
|
+
elements.btnSidebarBackToChats.addEventListener('click', () => {
|
|
1512
|
+
if (UISidebar.setSidebarMode) {
|
|
1513
|
+
UISidebar.setSidebarMode(elements, 'chat');
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
if (elements.btnCloseSidebarSettings) {
|
|
1518
|
+
elements.btnCloseSidebarSettings.addEventListener('click', closeSidebar);
|
|
1519
|
+
}
|
|
1520
|
+
if (elements.sidebarSettingsItems) {
|
|
1521
|
+
elements.sidebarSettingsItems.forEach(item => {
|
|
1522
|
+
const sectionId = item.dataset?.section || item.getAttribute('data-section');
|
|
1523
|
+
if (!sectionId) return;
|
|
1524
|
+
item.addEventListener('click', () => {
|
|
1525
|
+
openSettingsSection(sectionId);
|
|
1526
|
+
if (UISidebar.isMobile && UISidebar.isMobile()) {
|
|
1527
|
+
closeSidebar();
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
window.addEventListener('zerochat:languagechange', () => {
|
|
1533
|
+
updateExecutionInfo();
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
|
|
1537
|
+
// Barra Lateral de Chats (Sidebar)
|
|
1538
|
+
if (elements.btnToggleSidebar) {
|
|
1539
|
+
elements.btnToggleSidebar.addEventListener('click', toggleSidebar);
|
|
1540
|
+
}
|
|
1541
|
+
if (elements.activeProfileTrigger) {
|
|
1542
|
+
elements.activeProfileTrigger.addEventListener('click', () => {
|
|
1543
|
+
const isOpen = elements.activeProfileTrigger.getAttribute('aria-expanded') === 'true';
|
|
1544
|
+
if (isOpen) closeProfileMenu();
|
|
1545
|
+
else openProfileMenu();
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
if (elements.btnMenuNewProfile) {
|
|
1549
|
+
elements.btnMenuNewProfile.addEventListener('click', () => {
|
|
1550
|
+
handleMenuNewProfile();
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
if (elements.btnMenuExportProfiles) {
|
|
1554
|
+
elements.btnMenuExportProfiles.addEventListener('click', () => {
|
|
1555
|
+
handleExportProfiles();
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
if (elements.btnMenuImportProfiles) {
|
|
1559
|
+
elements.btnMenuImportProfiles.addEventListener('click', () => {
|
|
1560
|
+
elements.profilesImportInput?.click();
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
if (elements.btnMenuCloseProfiles) {
|
|
1564
|
+
elements.btnMenuCloseProfiles.addEventListener('click', () => {
|
|
1565
|
+
closeProfileMenu();
|
|
1566
|
+
elements.activeProfileTrigger?.focus();
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
if (elements.activeProfileList) {
|
|
1570
|
+
elements.activeProfileList.addEventListener('click', async (event) => {
|
|
1571
|
+
const actionBtn = event.target.closest('[data-profile-action]');
|
|
1572
|
+
if (actionBtn && elements.activeProfileList.contains(actionBtn)) {
|
|
1573
|
+
event.stopPropagation();
|
|
1574
|
+
const action = actionBtn.dataset.profileAction;
|
|
1575
|
+
const targetId = actionBtn.dataset.targetId;
|
|
1576
|
+
if (action === 'edit') {
|
|
1577
|
+
closeProfileMenu();
|
|
1578
|
+
openProfilesModal(targetId);
|
|
1579
|
+
} else if (action === 'delete') {
|
|
1580
|
+
const deleted = await handleDeleteProfileById(targetId);
|
|
1581
|
+
if (deleted) updateUIFromConfig();
|
|
1582
|
+
}
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
const option = event.target.closest('[data-profile-id]');
|
|
1587
|
+
if (!option || !elements.activeProfileList.contains(option)) return;
|
|
1588
|
+
if (!Profiles.get(option.dataset.profileId)) return;
|
|
1589
|
+
activateConnectionProfile(option.dataset.profileId);
|
|
1590
|
+
closeProfileMenu();
|
|
1591
|
+
elements.activeProfileTrigger.focus();
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
elements.activeProfileMenu?.addEventListener('keydown', event => {
|
|
1595
|
+
if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return;
|
|
1596
|
+
event.preventDefault();
|
|
1597
|
+
if (elements.activeProfilePopover.hidden) openProfileMenu();
|
|
1598
|
+
const buttons = [...elements.activeProfileList.querySelectorAll('button'), elements.btnEditProfiles].filter(Boolean);
|
|
1599
|
+
const index = buttons.indexOf(document.activeElement);
|
|
1600
|
+
const next = event.key === 'Home' ? 0 : event.key === 'End' ? buttons.length - 1
|
|
1601
|
+
: (index + (event.key === 'ArrowUp' ? -1 : 1) + buttons.length) % buttons.length;
|
|
1602
|
+
buttons[next]?.focus();
|
|
1603
|
+
});
|
|
1604
|
+
elements.activeProfileMenu?.addEventListener('focusout', event => {
|
|
1605
|
+
if (!elements.activeProfileMenu.contains(event.relatedTarget)) closeProfileMenu();
|
|
1606
|
+
});
|
|
1607
|
+
if (elements.btnEditProfiles) {
|
|
1608
|
+
elements.btnEditProfiles.addEventListener('click', () => {
|
|
1609
|
+
closeProfileMenu();
|
|
1610
|
+
openProfilesModal();
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
document.addEventListener('click', (event) => {
|
|
1614
|
+
if (elements.activeProfileMenu && !elements.activeProfileMenu.contains(event.target)) closeProfileMenu();
|
|
1615
|
+
});
|
|
1616
|
+
document.addEventListener('keydown', (event) => {
|
|
1617
|
+
if (event.key !== 'Escape' || elements.activeProfileTrigger?.getAttribute('aria-expanded') !== 'true') return;
|
|
1618
|
+
closeProfileMenu();
|
|
1619
|
+
elements.activeProfileTrigger.focus();
|
|
1620
|
+
});
|
|
1621
|
+
if (elements.btnCloseSidebar) {
|
|
1622
|
+
elements.btnCloseSidebar.addEventListener('click', closeSidebar);
|
|
1623
|
+
}
|
|
1624
|
+
if (elements.sidebarBackdrop) {
|
|
1625
|
+
elements.sidebarBackdrop.addEventListener('click', closeSidebar);
|
|
1626
|
+
}
|
|
1627
|
+
if (elements.btnSidebarNewChat) {
|
|
1628
|
+
elements.btnSidebarNewChat.addEventListener('click', createNewSession);
|
|
1629
|
+
}
|
|
1630
|
+
if (elements.btnSidebarNewTab) {
|
|
1631
|
+
elements.btnSidebarNewTab.addEventListener('click', updateNewTabLink);
|
|
1632
|
+
elements.btnSidebarNewTab.addEventListener('pointerdown', updateNewTabLink);
|
|
1633
|
+
}
|
|
1634
|
+
if (elements.sidebarSearchInput) {
|
|
1635
|
+
elements.sidebarSearchInput.addEventListener('input', () => {
|
|
1636
|
+
renderSidebarChats(elements.sidebarSearchInput.value);
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
if (elements.btnImportChatFile && elements.importJsonInput) {
|
|
1640
|
+
elements.btnImportChatFile.addEventListener('click', () => elements.importJsonInput.click());
|
|
1641
|
+
elements.importJsonInput.addEventListener('change', handleImportFileSelected);
|
|
1642
|
+
}
|
|
1643
|
+
if (elements.btnDeleteAllChats) {
|
|
1644
|
+
elements.btnDeleteAllChats.addEventListener('click', deleteAllSessions);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// Modal de Exportación (disparado desde cada chat en la barra lateral)
|
|
1648
|
+
if (elements.exportModal) {
|
|
1649
|
+
elements.exportModal.addEventListener('close', () => {
|
|
1650
|
+
delete elements.exportModal.dataset.sessionId;
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
if (elements.btnCloseExport) {
|
|
1654
|
+
elements.btnCloseExport.addEventListener('click', closeExportModal);
|
|
1655
|
+
}
|
|
1656
|
+
if (elements.btnCancelExport) {
|
|
1657
|
+
elements.btnCancelExport.addEventListener('click', closeExportModal);
|
|
1658
|
+
}
|
|
1659
|
+
if (elements.btnExportMarkdown) {
|
|
1660
|
+
elements.btnExportMarkdown.addEventListener('click', exportConversationAsMarkdown);
|
|
1661
|
+
}
|
|
1662
|
+
if (elements.btnExportJson) {
|
|
1663
|
+
elements.btnExportJson.addEventListener('click', exportConversationAsJson);
|
|
1664
|
+
}
|
|
1665
|
+
if (elements.btnExportPrint) {
|
|
1666
|
+
elements.btnExportPrint.addEventListener('click', exportConversationAsPrint);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// Razonamiento (Thinking)
|
|
1670
|
+
if (elements.btnReasoning) {
|
|
1671
|
+
elements.btnReasoning.addEventListener('click', (e) => {
|
|
1672
|
+
e.stopPropagation();
|
|
1673
|
+
toggleReasoningMenu();
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
document.addEventListener('click', (e) => {
|
|
1678
|
+
if (elements.reasoningMenu && elements.reasoningMenu.style.display !== 'none') {
|
|
1679
|
+
const isClickInsideMenu = elements.reasoningMenu.contains(e.target);
|
|
1680
|
+
const isClickInsideButton = elements.btnReasoning && elements.btnReasoning.contains(e.target);
|
|
1681
|
+
if (!isClickInsideMenu && !isClickInsideButton) {
|
|
1682
|
+
closeReasoningMenu();
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
// Panel de Debug & Logs
|
|
1688
|
+
if (elements.btnToggleDebug) {
|
|
1689
|
+
elements.btnToggleDebug.addEventListener('click', () => {
|
|
1690
|
+
toggleDebugPanel();
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
if (elements.btnCloseDebug) {
|
|
1695
|
+
elements.btnCloseDebug.addEventListener('click', () => {
|
|
1696
|
+
toggleDebugPanel(false);
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
if (elements.btnClearDebug) {
|
|
1701
|
+
elements.btnClearDebug.addEventListener('click', clearDebugLogs);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (elements.btnCopyDebug) {
|
|
1705
|
+
elements.btnCopyDebug.addEventListener('click', copyDebugLogs);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
if (elements.btnToggleAutoscroll) {
|
|
1709
|
+
elements.btnToggleAutoscroll.addEventListener('click', () => {
|
|
1710
|
+
isDebugAutoscroll = !isDebugAutoscroll;
|
|
1711
|
+
elements.btnToggleAutoscroll.classList.toggle('active', isDebugAutoscroll);
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (elements.debugTabs && elements.debugTabs.length > 0) {
|
|
1716
|
+
elements.debugTabs.forEach(tab => {
|
|
1717
|
+
tab.addEventListener('click', () => {
|
|
1718
|
+
elements.debugTabs.forEach(t => {
|
|
1719
|
+
t.classList.remove('active');
|
|
1720
|
+
t.setAttribute('aria-pressed', 'false');
|
|
1721
|
+
});
|
|
1722
|
+
tab.classList.add('active');
|
|
1723
|
+
tab.setAttribute('aria-pressed', 'true');
|
|
1724
|
+
const filter = tab.getAttribute('data-debug-tab') || 'all';
|
|
1725
|
+
filterDebugLogs(filter);
|
|
1726
|
+
});
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
if (elements.chkEnableDebugMessages) {
|
|
1731
|
+
elements.chkEnableDebugMessages.addEventListener('change', () => {
|
|
1732
|
+
syncDebugMessagesState(elements.chkEnableDebugMessages.checked);
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// Adjuntos de archivos
|
|
1737
|
+
elements.btnAttachFile.addEventListener('click', () => {
|
|
1738
|
+
elements.fileInput.click();
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
elements.fileInput.addEventListener('change', (e) => {
|
|
1742
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
1743
|
+
processFiles(Array.from(e.target.files));
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
// Soporte de Arrastrar y Soltar (Drag and Drop)
|
|
1748
|
+
elements.chatForm.addEventListener('dragover', (e) => {
|
|
1749
|
+
e.preventDefault();
|
|
1750
|
+
elements.chatForm.classList.add('drag-over');
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
elements.chatForm.addEventListener('dragleave', () => {
|
|
1754
|
+
elements.chatForm.classList.remove('drag-over');
|
|
1755
|
+
});
|
|
1756
|
+
|
|
1757
|
+
elements.chatForm.addEventListener('drop', (e) => {
|
|
1758
|
+
e.preventDefault();
|
|
1759
|
+
elements.chatForm.classList.remove('drag-over');
|
|
1760
|
+
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
1761
|
+
processFiles(Array.from(e.dataTransfer.files));
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
// Modal de Configuración & Perfiles
|
|
1766
|
+
if (elements.btnCloseSettings) {
|
|
1767
|
+
elements.btnCloseSettings.addEventListener('click', () => closeSettingsPanelOnly());
|
|
1768
|
+
}
|
|
1769
|
+
if (elements.settingsForm) {
|
|
1770
|
+
elements.settingsForm.addEventListener('submit', handleSaveSettings);
|
|
1771
|
+
}
|
|
1772
|
+
if (elements.settingsDialog) {
|
|
1773
|
+
const markSettingsModified = () => {
|
|
1774
|
+
if (UISettings.setSettingsFormDirty) UISettings.setSettingsFormDirty(elements, true);
|
|
1775
|
+
};
|
|
1776
|
+
elements.settingsDialog.addEventListener('input', markSettingsModified);
|
|
1777
|
+
elements.settingsDialog.addEventListener('change', markSettingsModified);
|
|
1778
|
+
elements.settingsDialog.addEventListener('click', (event) => {
|
|
1779
|
+
if (event.target.closest?.('#btn-mcp-clear-auths')) markSettingsModified();
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
if (elements.btnClearAllData) {
|
|
1783
|
+
elements.btnClearAllData.addEventListener('click', handleClearAllData);
|
|
1784
|
+
}
|
|
1785
|
+
if (elements.btnManageProfiles) {
|
|
1786
|
+
elements.btnManageProfiles.addEventListener('click', openProfilesModal);
|
|
1787
|
+
}
|
|
1788
|
+
if (elements.btnCloseProfiles) {
|
|
1789
|
+
elements.btnCloseProfiles.addEventListener('click', () => {
|
|
1790
|
+
closeProfilesModal();
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
if (elements.btnCancelProfiles) {
|
|
1794
|
+
elements.btnCancelProfiles.addEventListener('click', () => {
|
|
1795
|
+
closeProfilesModal();
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
if (elements.profilesDialog) {
|
|
1799
|
+
const markProfileModified = (e) => {
|
|
1800
|
+
if (e && (e.target === elements.profileSelectHelper || e.target === elements.profilesImportInput)) {
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
setProfileDirty(true);
|
|
1804
|
+
syncProfileSaveState();
|
|
1805
|
+
};
|
|
1806
|
+
elements.profilesDialog.addEventListener('input', markProfileModified);
|
|
1807
|
+
elements.profilesDialog.addEventListener('change', markProfileModified);
|
|
1808
|
+
elements.profilesDialog.addEventListener('cancel', function (e) {
|
|
1809
|
+
if (!isProfileQueryReady() && !isProfileFormDirty()) {
|
|
1810
|
+
setProfileQueryState(false);
|
|
1811
|
+
setProfileDirty(false);
|
|
1812
|
+
resetProfileFormToSelected();
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
e.preventDefault();
|
|
1816
|
+
closeProfilesModal();
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
if (elements.profileSelectHelper) {
|
|
1821
|
+
elements.profileSelectHelper.addEventListener('change', function () {
|
|
1822
|
+
const selectedId = this.value;
|
|
1823
|
+
const profile = Profiles.get ? Profiles.get(selectedId) : null;
|
|
1824
|
+
if (!profile) return;
|
|
1825
|
+
if (elements.settingProfileName) {
|
|
1826
|
+
elements.settingProfileName.value = profile.name;
|
|
1827
|
+
}
|
|
1828
|
+
if (elements.settingProfileDescription) {
|
|
1829
|
+
elements.settingProfileDescription.value = profile.description || '';
|
|
1830
|
+
}
|
|
1831
|
+
applyProfileToForm(profile.settings);
|
|
1832
|
+
setSelectedProfileAsDefault(profile);
|
|
1833
|
+
setProfileQueryState(false);
|
|
1834
|
+
setProfileDirty(false);
|
|
1835
|
+
if (typeof loadCachedModels === 'function') loadCachedModels();
|
|
1836
|
+
syncProfileSaveState();
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (elements.settingProfileName) {
|
|
1841
|
+
elements.settingProfileName.addEventListener('change', function () {
|
|
1842
|
+
const typedName = this.value.trim();
|
|
1843
|
+
if (!typedName) return;
|
|
1844
|
+
if (Profiles.findByName) {
|
|
1845
|
+
const profile = Profiles.findByName(typedName);
|
|
1846
|
+
if (profile) {
|
|
1847
|
+
if (elements.profileSelectHelper) {
|
|
1848
|
+
elements.profileSelectHelper.value = profile.id;
|
|
1849
|
+
}
|
|
1850
|
+
applyProfileToForm(profile.settings, profile.id);
|
|
1851
|
+
if (elements.settingProfileDescription) {
|
|
1852
|
+
elements.settingProfileDescription.value = profile.description || '';
|
|
1853
|
+
}
|
|
1854
|
+
setSelectedProfileAsDefault(profile);
|
|
1855
|
+
setProfileQueryState(false);
|
|
1856
|
+
setProfileDirty(false);
|
|
1857
|
+
if (typeof loadCachedModels === 'function') loadCachedModels();
|
|
1858
|
+
syncProfileSaveState();
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
if (elements.btnSaveProfile) {
|
|
1865
|
+
elements.btnSaveProfile.addEventListener('click', (e) => {
|
|
1866
|
+
e.preventDefault();
|
|
1867
|
+
handleSaveProfile().then(saved => { if (saved) closeProfilesModal(true); }).catch(error => showProfileFeedback(error.message, 'error'));
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (elements.btnNewProfile) {
|
|
1872
|
+
elements.btnNewProfile.addEventListener('click', handleNewProfile);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
if (elements.btnExportProfiles) {
|
|
1876
|
+
elements.btnExportProfiles.addEventListener('click', handleExportProfiles);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
if (elements.profilesImportInput) {
|
|
1880
|
+
elements.profilesImportInput.addEventListener('change', handleImportProfiles);
|
|
1881
|
+
}
|
|
1882
|
+
if (elements.btnImportProfiles && elements.profilesImportInput) {
|
|
1883
|
+
elements.btnImportProfiles.addEventListener('click', () => elements.profilesImportInput.click());
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
if (elements.btnDeleteProfile) {
|
|
1887
|
+
elements.btnDeleteProfile.addEventListener('click', (e) => {
|
|
1888
|
+
e.preventDefault();
|
|
1889
|
+
handleDeleteProfile();
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
if (elements.settingApiType) {
|
|
1894
|
+
elements.settingApiType.addEventListener('change', function () {
|
|
1895
|
+
setProfileQueryState(false);
|
|
1896
|
+
const val = this.value;
|
|
1897
|
+
const currentUrl = elements.settingApiUrl ? elements.settingApiUrl.value.trim() : '';
|
|
1898
|
+
|
|
1899
|
+
const knownEndpoints = Providers.registry?.getConnectionEndpoints?.() || [];
|
|
1900
|
+
const isDefaultOrEmpty = !currentUrl || knownEndpoints.includes(currentUrl);
|
|
1901
|
+
|
|
1902
|
+
if (isDefaultOrEmpty && elements.settingApiUrl) {
|
|
1903
|
+
const endpoint = Providers.registry?.get?.(val)?.getConnectionConfig?.().endpoint;
|
|
1904
|
+
if (endpoint) elements.settingApiUrl.value = endpoint;
|
|
1905
|
+
}
|
|
1906
|
+
UISettings.syncProviderFields?.(elements);
|
|
1907
|
+
if (typeof loadCachedModels === 'function') {
|
|
1908
|
+
loadCachedModels();
|
|
1909
|
+
}
|
|
1910
|
+
setProfileDirty(true);
|
|
1911
|
+
syncProfileSaveState();
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
[elements.settingApiUrl, elements.settingApiKey].forEach(input => {
|
|
1915
|
+
input?.addEventListener('input', () => setProfileQueryState(false));
|
|
1916
|
+
});
|
|
1917
|
+
|
|
1918
|
+
if (elements.btnQueryServer) {
|
|
1919
|
+
elements.btnQueryServer.addEventListener('click', (e) => {
|
|
1920
|
+
e.preventDefault();
|
|
1921
|
+
handleQueryServer();
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
if (elements.btnRunInspector) {
|
|
1926
|
+
elements.btnRunInspector.addEventListener('click', (e) => {
|
|
1927
|
+
e.preventDefault();
|
|
1928
|
+
handleRunInspector();
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
if (elements.modelSelectHelper) {
|
|
1933
|
+
elements.modelSelectHelper.addEventListener('change', function () {
|
|
1934
|
+
if (this.value) {
|
|
1935
|
+
elements.settingModel.value = this.value;
|
|
1936
|
+
}
|
|
1937
|
+
setProfileDirty(true);
|
|
1938
|
+
syncProfileSaveState();
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
if (elements.settingModel) {
|
|
1943
|
+
elements.settingModel.addEventListener('input', function () {
|
|
1944
|
+
const val = this.value.trim();
|
|
1945
|
+
if (elements.modelSelectHelper) {
|
|
1946
|
+
elements.modelSelectHelper.value = val;
|
|
1947
|
+
}
|
|
1948
|
+
setProfileDirty(true);
|
|
1949
|
+
syncProfileSaveState();
|
|
1950
|
+
});
|
|
1951
|
+
|
|
1952
|
+
elements.settingModel.addEventListener('change', function () {
|
|
1953
|
+
const val = this.value.trim();
|
|
1954
|
+
if (elements.modelSelectHelper) {
|
|
1955
|
+
elements.modelSelectHelper.value = val;
|
|
1956
|
+
}
|
|
1957
|
+
setProfileDirty(true);
|
|
1958
|
+
syncProfileSaveState();
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
if (elements.themeButtons && elements.themeButtons.length > 0) {
|
|
1963
|
+
elements.themeButtons.forEach(btn => {
|
|
1964
|
+
btn.addEventListener('click', function (e) {
|
|
1965
|
+
e.preventDefault();
|
|
1966
|
+
e.stopPropagation();
|
|
1967
|
+
const targetTheme = btn.getAttribute('data-theme') || 'light';
|
|
1968
|
+
applyTheme(targetTheme);
|
|
1969
|
+
if (Config.updateGeneral) Config.updateGeneral({ theme: targetTheme });
|
|
1970
|
+
});
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
if (elements.langButtons && elements.langButtons.length > 0) {
|
|
1975
|
+
elements.langButtons.forEach(btn => {
|
|
1976
|
+
btn.addEventListener('click', function (e) {
|
|
1977
|
+
e.preventDefault();
|
|
1978
|
+
e.stopPropagation();
|
|
1979
|
+
const targetLang = btn.getAttribute('data-lang') || 'es';
|
|
1980
|
+
applyLanguage(targetLang);
|
|
1981
|
+
if (Config.updateGeneral) Config.updateGeneral({ language: targetLang });
|
|
1982
|
+
});
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
elements.settingTemperature.addEventListener('input', function (e) {
|
|
1987
|
+
elements.temperatureVal.textContent = e.target.value;
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
if (elements.settingMaxAgentTurns && elements.maxAgentTurnsVal) {
|
|
1991
|
+
elements.settingMaxAgentTurns.addEventListener('input', function (e) {
|
|
1992
|
+
elements.maxAgentTurnsVal.textContent = e.target.value;
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
elements.btnToggleKey.addEventListener('click', function () {
|
|
1997
|
+
const isPass = elements.settingApiKey.type === 'password';
|
|
1998
|
+
elements.settingApiKey.type = isPass ? 'text' : 'password';
|
|
1999
|
+
elements.btnToggleKey.innerHTML = getMsgIcon(isPass ? 'eye-off' : 'eye', 15);
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
if (elements.btnWebllmParams && elements.webllmParamsPanel) {
|
|
2003
|
+
elements.btnWebllmParams.addEventListener('click', function () {
|
|
2004
|
+
const isHidden = elements.webllmParamsPanel.hidden;
|
|
2005
|
+
elements.webllmParamsPanel.hidden = !isHidden;
|
|
2006
|
+
elements.btnWebllmParams.classList.toggle('active', isHidden);
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
elements.settingsDialog.addEventListener('click', function (e) {
|
|
2011
|
+
if (e.target === elements.settingsDialog) {
|
|
2012
|
+
closeSettingsPanelOnly();
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
elements.settingsDialog.addEventListener('cancel', function (e) {
|
|
2016
|
+
if (!UISettings.isSettingsFormDirty || !UISettings.isSettingsFormDirty(elements)) return;
|
|
2017
|
+
e.preventDefault();
|
|
2018
|
+
closeSettingsPanelOnly();
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
const UITel = getUITelemetry();
|
|
2022
|
+
if (UITel && UITel.bindPopoverEvents) {
|
|
2023
|
+
UITel.bindPopoverEvents(elements, () => {
|
|
2024
|
+
updateConnectionTokensBadge(null, null, { forcePopover: true });
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
if (elements.btnSaveContextLimitOverride && elements.contextLimitOverrideInput) {
|
|
2028
|
+
elements.btnSaveContextLimitOverride.addEventListener('click', async function () {
|
|
2029
|
+
const UITel = getUITelemetry();
|
|
2030
|
+
const contextLimitOverride = UITel?.parseContextCapacity?.(elements.contextLimitOverrideInput.value);
|
|
2031
|
+
if (!contextLimitOverride) {
|
|
2032
|
+
await ChatDialogs.alert(t('context_limit_override_invalid'));
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const activeProfile = getRuntimeConfig().activeProfile;
|
|
2036
|
+
const profile = activeProfile && Profiles.get?.(activeProfile.id);
|
|
2037
|
+
if (!profile || !Profiles.save) {
|
|
2038
|
+
await ChatDialogs.alert(t('context_limit_override_profile_required'));
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (profile.settings.apiKeyLocked === true) {
|
|
2042
|
+
await ChatDialogs.alert(t('err_profile_read_only'));
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
try {
|
|
2046
|
+
const savedProfile = Profiles.save({ ...profile, settings: { ...profile.settings, contextLimitOverride } });
|
|
2047
|
+
activateConnectionProfile(savedProfile.id);
|
|
2048
|
+
updateConnectionTokensBadge(null, null, { forcePopover: true });
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
console.error('Could not save context limit override:', error);
|
|
2051
|
+
await ChatDialogs.alert(t('context_limit_override_save_error'));
|
|
2052
|
+
}
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function ensureModalsMarkup() {
|
|
2058
|
+
if (UISettings && typeof UISettings.ensureDialogMarkup === 'function') UISettings.ensureDialogMarkup();
|
|
2059
|
+
if (window.ChatUIMcp && typeof window.ChatUIMcp.ensureDialogMarkup === 'function') window.ChatUIMcp.ensureDialogMarkup();
|
|
2060
|
+
if (window.ChatRagUI && typeof window.ChatRagUI.ensureDialogMarkup === 'function') window.ChatRagUI.ensureDialogMarkup();
|
|
2061
|
+
if (window.ChatExport && typeof window.ChatExport.ensureDialogMarkup === 'function') window.ChatExport.ensureDialogMarkup();
|
|
2062
|
+
if (Debug && typeof Debug.ensureDialogMarkup === 'function') Debug.ensureDialogMarkup();
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
const HEARTBEAT_INTERVAL_MS = 10000;
|
|
2066
|
+
let heartbeatTimer = null;
|
|
2067
|
+
let activeHeartbeatTarget = null;
|
|
2068
|
+
let heartbeatCheckInFlight = false;
|
|
2069
|
+
|
|
2070
|
+
function buildHeartbeatUrl(host, port) {
|
|
2071
|
+
if (typeof window !== 'undefined' && window.location && window.location.protocol.startsWith('http')) {
|
|
2072
|
+
const locHost = window.location.hostname;
|
|
2073
|
+
const locPort = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
|
|
2074
|
+
const isSamePort = String(port) === String(locPort);
|
|
2075
|
+
const isLocalHostMatch = (locHost === host) ||
|
|
2076
|
+
(['127.0.0.1', 'localhost'].includes(locHost) && ['127.0.0.1', 'localhost'].includes(host));
|
|
2077
|
+
if (isSamePort && isLocalHostMatch) {
|
|
2078
|
+
return '/zerochat/heartbeat';
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
return `http://${host}:${port}/zerochat/heartbeat`;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
async function sendHeartbeatPing() {
|
|
2085
|
+
if (!activeHeartbeatTarget || typeof fetch !== 'function') return;
|
|
2086
|
+
const endpoint = buildHeartbeatUrl(activeHeartbeatTarget.host, activeHeartbeatTarget.port);
|
|
2087
|
+
try {
|
|
2088
|
+
const response = await fetch(endpoint, {
|
|
2089
|
+
method: 'GET',
|
|
2090
|
+
cache: 'no-store',
|
|
2091
|
+
mode: 'cors',
|
|
2092
|
+
headers: { 'X-ZeroChat-Token': activeHeartbeatTarget.token }
|
|
2093
|
+
});
|
|
2094
|
+
const payload = await response.json().catch(() => null);
|
|
2095
|
+
if (!response.ok || payload?.ok !== true) throw new Error(`Heartbeat HTTP ${response.status}`);
|
|
2096
|
+
} catch (_) {
|
|
2097
|
+
if (heartbeatCheckInFlight) return;
|
|
2098
|
+
heartbeatCheckInFlight = true;
|
|
2099
|
+
try {
|
|
2100
|
+
await window.ChatMCP?.manager?.verifyProxyConnection?.({ timeoutMs: 1500 });
|
|
2101
|
+
} finally {
|
|
2102
|
+
heartbeatCheckInFlight = false;
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function startServerHeartbeat(host, port, token) {
|
|
2108
|
+
if (!token || !host || !port) return;
|
|
2109
|
+
const targetChanged = !activeHeartbeatTarget
|
|
2110
|
+
|| activeHeartbeatTarget.host !== host
|
|
2111
|
+
|| activeHeartbeatTarget.port !== port
|
|
2112
|
+
|| activeHeartbeatTarget.token !== token;
|
|
2113
|
+
|
|
2114
|
+
activeHeartbeatTarget = { host, port, token };
|
|
2115
|
+
updateNewTabLink();
|
|
2116
|
+
|
|
2117
|
+
if (targetChanged) {
|
|
2118
|
+
sendHeartbeatPing();
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
if (!heartbeatTimer) {
|
|
2122
|
+
heartbeatTimer = setInterval(sendHeartbeatPing, HEARTBEAT_INTERVAL_MS);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
function stopServerHeartbeat() {
|
|
2127
|
+
if (heartbeatTimer) {
|
|
2128
|
+
clearInterval(heartbeatTimer);
|
|
2129
|
+
heartbeatTimer = null;
|
|
2130
|
+
}
|
|
2131
|
+
heartbeatCheckInFlight = false;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
function getNewTabUrl() {
|
|
2135
|
+
if (typeof window === 'undefined' || !window.location) return '';
|
|
2136
|
+
return (window.location.pathname || '') + (window.location.search || '') || window.location.href;
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
function updateNewTabLink() {
|
|
2140
|
+
if (!elements?.btnSidebarNewTab) return;
|
|
2141
|
+
const url = getNewTabUrl();
|
|
2142
|
+
if (url) {
|
|
2143
|
+
elements.btnSidebarNewTab.href = url;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
function init() {
|
|
2148
|
+
ensureModalsMarkup();
|
|
2149
|
+
cacheDomElements();
|
|
2150
|
+
if (Debug.setElements) Debug.setElements(elements);
|
|
2151
|
+
if (Debug.setRawLogsEnabled) Debug.setRawLogsEnabled(true);
|
|
2152
|
+
if (Debug.registerGlobalErrorHandlers && typeof window !== 'undefined') Debug.registerGlobalErrorHandlers(window);
|
|
2153
|
+
|
|
2154
|
+
if (State.subscribe) {
|
|
2155
|
+
const syncGenerationControls = (streamingState) => {
|
|
2156
|
+
const isGenerating = Boolean(streamingState.isGenerating);
|
|
2157
|
+
if (UIComposer.syncGenerationControls) {
|
|
2158
|
+
UIComposer.syncGenerationControls(elements, isGenerating, { clearGenerationStatus });
|
|
2159
|
+
} else {
|
|
2160
|
+
if (elements.btnSend) elements.btnSend.disabled = isGenerating;
|
|
2161
|
+
if (elements.btnStopStream) elements.btnStopStream.style.display = isGenerating ? 'inline-flex' : 'none';
|
|
2162
|
+
if (!isGenerating) clearGenerationStatus();
|
|
2163
|
+
}
|
|
2164
|
+
};
|
|
2165
|
+
State.subscribe('streaming', syncGenerationControls);
|
|
2166
|
+
syncGenerationControls(State.get?.('streaming') || {});
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
if (Config.subscribe) {
|
|
2170
|
+
Config.subscribe((nextConfig, previousConfig) => {
|
|
2171
|
+
if (previousConfig?.apiType && previousConfig.apiType !== nextConfig?.apiType) {
|
|
2172
|
+
Promise.resolve(Providers.registry?.get?.(previousConfig.apiType)?.deactivate?.())
|
|
2173
|
+
.catch(error => console.error('Provider cleanup failed:', error));
|
|
2174
|
+
}
|
|
2175
|
+
updateUIFromConfig();
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
loadCachedModels();
|
|
2180
|
+
updateUIFromConfig();
|
|
2181
|
+
loadSessionsFromStorage();
|
|
2182
|
+
setupEventListeners();
|
|
2183
|
+
|
|
2184
|
+
if (UISidebar.isMobile && UISidebar.isMobile() && UISidebar.closeSidebar) {
|
|
2185
|
+
UISidebar.closeSidebar(elements);
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
if (window.ChatRagUI && window.ChatRagUI.initRagUI) {
|
|
2189
|
+
window.ChatRagUI.initRagUI();
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
if (window.ChatUIMcp && window.ChatUIMcp.initMcpUI) {
|
|
2193
|
+
window.ChatUIMcp.initMcpUI({
|
|
2194
|
+
statusBadge: elements.mcpStatusBadge,
|
|
2195
|
+
statusText: elements.mcpStatusText,
|
|
2196
|
+
serverDetails: elements.mcpServerDetails,
|
|
2197
|
+
reconnectHint: elements.mcpReconnectHint,
|
|
2198
|
+
commandSnippet: elements.mcpTerminalCommand,
|
|
2199
|
+
btnCopyCmd: elements.btnMcpCopyCmd,
|
|
2200
|
+
bootstrapCard: elements.mcpBootstrapCard,
|
|
2201
|
+
toolsContainer: elements.mcpToolsContainer,
|
|
2202
|
+
serversList: elements.mcpServersList
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2205
|
+
// Sistema de importación de perfiles vía postMessage desde HTML exportado
|
|
2206
|
+
function setupProfileImportListener() {
|
|
2207
|
+
let importInProgress = false;
|
|
2208
|
+
|
|
2209
|
+
// Notificar al opener que estamos listos
|
|
2210
|
+
function notifyReady() {
|
|
2211
|
+
if (window.opener && !window.opener.closed) {
|
|
2212
|
+
try {
|
|
2213
|
+
window.opener.postMessage('zerochat_ready', '*');
|
|
2214
|
+
console.log('[Import Mode] Notified opener that we are ready');
|
|
2215
|
+
} catch (e) {
|
|
2216
|
+
console.warn('[Import Mode] Could not notify opener:', e);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
// Procesar importación
|
|
2222
|
+
async function handleImportMessage(event) {
|
|
2223
|
+
// Validar estructura del mensaje
|
|
2224
|
+
if (!event.data || event.data.type !== 'import_profiles') {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// Prevenir procesamiento múltiple
|
|
2229
|
+
if (importInProgress) {
|
|
2230
|
+
console.warn('[Import Mode] Import already in progress, ignoring duplicate message');
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
importInProgress = true;
|
|
2235
|
+
console.log('[Import Mode] Received import request');
|
|
2236
|
+
|
|
2237
|
+
try {
|
|
2238
|
+
const payload = event.data.payload;
|
|
2239
|
+
const metadata = event.data.metadata || {};
|
|
2240
|
+
|
|
2241
|
+
if (!payload || typeof payload !== 'string') {
|
|
2242
|
+
throw new Error('Invalid payload format');
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
console.log('[Import Mode] Payload size:', payload.length, 'bytes');
|
|
2246
|
+
console.log('[Import Mode] Metadata:', metadata);
|
|
2247
|
+
|
|
2248
|
+
// Descifrar y validar
|
|
2249
|
+
const ProfileBackup = window.ChatProfileBackup;
|
|
2250
|
+
if (!ProfileBackup?.decryptProfiles) {
|
|
2251
|
+
throw new Error('Profile decryption not available');
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
const imported = await ProfileBackup.decryptProfiles(payload);
|
|
2255
|
+
console.log('[Import Mode] Decrypted', imported.length, 'profiles');
|
|
2256
|
+
|
|
2257
|
+
if (!Array.isArray(imported) || imported.length === 0) {
|
|
2258
|
+
throw new Error('No valid profiles found in payload');
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// Confirmar con el usuario
|
|
2262
|
+
const Dialogs = window.ChatDialogs;
|
|
2263
|
+
const I18n = window.ChatI18n;
|
|
2264
|
+
const confirmed = await Dialogs.confirm(
|
|
2265
|
+
I18n.t('confirm_auto_import_profiles', { count: imported.length }) ||
|
|
2266
|
+
`¿Importar ${imported.length} perfil(es) de conexión?`
|
|
2267
|
+
);
|
|
2268
|
+
|
|
2269
|
+
if (!confirmed) {
|
|
2270
|
+
throw new Error('Import cancelled by user');
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// Importar
|
|
2274
|
+
const Profiles = window.ChatProfileRepository;
|
|
2275
|
+
const result = Profiles.mergeImported(imported);
|
|
2276
|
+
console.log('[Import Mode] Import result:', result);
|
|
2277
|
+
|
|
2278
|
+
// Actualizar configuración activa si es necesario
|
|
2279
|
+
const Config = window.ChatConfig;
|
|
2280
|
+
const activeId = Config?.getActive?.()?.activeProfile?.id;
|
|
2281
|
+
if (activeId && !Profiles.get(activeId)) {
|
|
2282
|
+
Config?.activateFallbackProfile?.();
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// Notificar éxito al opener
|
|
2286
|
+
if (event.source && !event.source.closed) {
|
|
2287
|
+
event.source.postMessage({
|
|
2288
|
+
type: 'import_result',
|
|
2289
|
+
success: true,
|
|
2290
|
+
result: {
|
|
2291
|
+
added: result.added,
|
|
2292
|
+
replaced: result.replaced,
|
|
2293
|
+
total: result.total
|
|
2294
|
+
}
|
|
2295
|
+
}, '*');
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
// Mostrar feedback al usuario
|
|
2299
|
+
await Dialogs.alert(
|
|
2300
|
+
I18n.t('msg_profiles_auto_imported', {
|
|
2301
|
+
added: result.added,
|
|
2302
|
+
replaced: result.replaced
|
|
2303
|
+
}) || `✅ ${result.added} perfil(es) añadido(s), ${result.replaced} actualizado(s).`
|
|
2304
|
+
);
|
|
2305
|
+
|
|
2306
|
+
// Limpiar hash mode=import
|
|
2307
|
+
if (window.location.hash.includes('mode=import')) {
|
|
2308
|
+
if (window.history && typeof window.history.replaceState === 'function') {
|
|
2309
|
+
window.history.replaceState(null, '', window.location.pathname + window.location.search);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// Quitar banner de espera
|
|
2314
|
+
const banner = document.getElementById('import-waiting-banner');
|
|
2315
|
+
if (banner) banner.remove();
|
|
2316
|
+
|
|
2317
|
+
// Restaurar UI
|
|
2318
|
+
if (elements.messagesList) elements.messagesList.style.display = '';
|
|
2319
|
+
if (elements.chatForm) elements.chatForm.style.display = '';
|
|
2320
|
+
|
|
2321
|
+
// Continuar con inicialización normal si no se hizo
|
|
2322
|
+
console.log('[Import Mode] Import completed successfully, resuming normal initialization');
|
|
2323
|
+
|
|
2324
|
+
// Inicializar la app normalmente si aún no lo está
|
|
2325
|
+
if (!document.documentElement.classList.contains('zerochat-ready')) {
|
|
2326
|
+
// Remover el flag de modo importación para que init() pueda ejecutarse
|
|
2327
|
+
window.location.hash = '';
|
|
2328
|
+
// Llamar a la función init que quedó pendiente
|
|
2329
|
+
// Esto requiere que ejecutemos la inicialización manualmente
|
|
2330
|
+
window.location.reload();
|
|
2331
|
+
} else {
|
|
2332
|
+
// Ya está inicializada, solo refrescar UI
|
|
2333
|
+
if (typeof UIProfiles?.populateProfileSelector === 'function') {
|
|
2334
|
+
const nextActive = Config?.getActive?.()?.activeProfile?.id || '';
|
|
2335
|
+
UIProfiles.populateProfileSelector(nextActive);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
console.error('[Import Mode] Profile import error:', error);
|
|
2341
|
+
|
|
2342
|
+
// Notificar error al opener
|
|
2343
|
+
if (event.source && !event.source.closed) {
|
|
2344
|
+
event.source.postMessage({
|
|
2345
|
+
type: 'import_result',
|
|
2346
|
+
success: false,
|
|
2347
|
+
error: error.message
|
|
2348
|
+
}, '*');
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// Mostrar error al usuario
|
|
2352
|
+
const Dialogs = window.ChatDialogs;
|
|
2353
|
+
const I18n = window.ChatI18n;
|
|
2354
|
+
await Dialogs.alert(
|
|
2355
|
+
I18n.t('err_auto_import_failed', { err: error.message }) ||
|
|
2356
|
+
`❌ Error al importar perfiles: ${error.message}`
|
|
2357
|
+
);
|
|
2358
|
+
|
|
2359
|
+
// Limpiar hash en caso de error también
|
|
2360
|
+
if (window.location.hash.includes('mode=import')) {
|
|
2361
|
+
if (window.history && typeof window.history.replaceState === 'function') {
|
|
2362
|
+
window.history.replaceState(null, '', window.location.pathname + window.location.search);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// Quitar banner de espera y restaurar UI
|
|
2367
|
+
const banner = document.getElementById('import-waiting-banner');
|
|
2368
|
+
if (banner) banner.remove();
|
|
2369
|
+
|
|
2370
|
+
if (elements.messagesList) elements.messagesList.style.display = '';
|
|
2371
|
+
if (elements.chatForm) elements.chatForm.style.display = '';
|
|
2372
|
+
|
|
2373
|
+
// Recargar para inicializar normalmente después del error
|
|
2374
|
+
console.log('[Import Mode] Import failed, reloading to initialize normally');
|
|
2375
|
+
window.location.reload();
|
|
2376
|
+
} finally {
|
|
2377
|
+
importInProgress = false;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
// Registrar listener
|
|
2382
|
+
window.addEventListener('message', handleImportMessage);
|
|
2383
|
+
|
|
2384
|
+
// Notificar que estamos listos si somos una ventana abierta para importar
|
|
2385
|
+
if (window.location.hash.includes('mode=import')) {
|
|
2386
|
+
console.log('[Import Mode] Detected import mode, will notify opener when ready');
|
|
2387
|
+
// Notificar inmediatamente si ya estamos listos
|
|
2388
|
+
if (document.readyState === 'complete') {
|
|
2389
|
+
notifyReady();
|
|
2390
|
+
} else {
|
|
2391
|
+
window.addEventListener('load', notifyReady, { once: true });
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
// Detectar modo importación y mostrar UI de espera
|
|
2397
|
+
function detectImportMode() {
|
|
2398
|
+
if (!window.location.hash.includes('mode=import')) {
|
|
2399
|
+
return false; // Flujo normal
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
console.log('[Import Mode] Import mode detected, showing waiting UI');
|
|
2403
|
+
|
|
2404
|
+
// Ocultar contenido normal temporalmente
|
|
2405
|
+
if (elements.messagesList) elements.messagesList.style.display = 'none';
|
|
2406
|
+
if (elements.chatForm) elements.chatForm.style.display = 'none';
|
|
2407
|
+
|
|
2408
|
+
// Mostrar banner de espera
|
|
2409
|
+
const banner = document.createElement('div');
|
|
2410
|
+
banner.id = 'import-waiting-banner';
|
|
2411
|
+
banner.style.cssText = `
|
|
2412
|
+
position: fixed;
|
|
2413
|
+
top: 0;
|
|
2414
|
+
left: 0;
|
|
2415
|
+
right: 0;
|
|
2416
|
+
bottom: 0;
|
|
2417
|
+
display: flex;
|
|
2418
|
+
align-items: center;
|
|
2419
|
+
justify-content: center;
|
|
2420
|
+
background: var(--color-bg-primary);
|
|
2421
|
+
z-index: 9999;
|
|
2422
|
+
`;
|
|
2423
|
+
banner.innerHTML = `
|
|
2424
|
+
<div style="text-align: center; max-width: 400px; padding: 40px;">
|
|
2425
|
+
<div style="font-size: 64px; margin-bottom: 20px;">📥</div>
|
|
2426
|
+
<h2 style="margin: 0 0 10px 0; font-size: 24px;">Importando perfiles...</h2>
|
|
2427
|
+
<p style="color: var(--color-text-secondary); margin: 0;">Esperando datos cifrados desde la ventana de exportación</p>
|
|
2428
|
+
</div>
|
|
2429
|
+
`;
|
|
2430
|
+
document.body.appendChild(banner);
|
|
2431
|
+
|
|
2432
|
+
// Configurar listener
|
|
2433
|
+
setupProfileImportListener();
|
|
2434
|
+
|
|
2435
|
+
return true; // Modo importación activado
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// Detectar modo importación ANTES de inicializar normalmente
|
|
2439
|
+
const isImportMode = detectImportMode();
|
|
2440
|
+
if (isImportMode) {
|
|
2441
|
+
console.log('[Import Mode] Skipping normal initialization, waiting for profile data');
|
|
2442
|
+
// NO continuar con la inicialización normal
|
|
2443
|
+
// El listener manejará todo y luego permitirá uso normal
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
// El fragmento se usa exclusivamente para el primer arranque desde zerochat.py.
|
|
2448
|
+
// Después la sesión se resuelve mediante una cookie del mismo origen.
|
|
2449
|
+
function persistBackendSessionFromLaunchFragment() {
|
|
2450
|
+
try {
|
|
2451
|
+
if (typeof window !== 'undefined' && window.location) {
|
|
2452
|
+
const hashRaw = (window.location.hash || '').replace(/^#/, '');
|
|
2453
|
+
const hashParams = new URLSearchParams(hashRaw);
|
|
2454
|
+
const token = hashParams.get('token');
|
|
2455
|
+
if (!token) return;
|
|
2456
|
+
const session = Storage.normalizeBackendSession?.({
|
|
2457
|
+
token,
|
|
2458
|
+
host: hashParams.get('host') || '127.0.0.1',
|
|
2459
|
+
port: hashParams.get('port') || 6388
|
|
2460
|
+
});
|
|
2461
|
+
if (session) {
|
|
2462
|
+
if (!Storage.setBackendSession?.(session)) {
|
|
2463
|
+
console.warn('No se pudo persistir la sesión inicial del backend.');
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
if (window.history && typeof window.history.replaceState === 'function') {
|
|
2467
|
+
window.history.replaceState(null, '', window.location.pathname + window.location.search);
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
} catch (err) {
|
|
2471
|
+
console.warn('Error leyendo la sesión inicial del backend:', err);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
persistBackendSessionFromLaunchFragment();
|
|
2476
|
+
const backendSession = Storage.getBackendSession?.();
|
|
2477
|
+
const effectiveToken = backendSession?.token || window.ChatMCP?.manager?.getSessionToken();
|
|
2478
|
+
if (effectiveToken && window.ChatMCP?.manager?.setSessionToken) {
|
|
2479
|
+
window.ChatMCP.manager.setSessionToken(effectiveToken);
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
const currentCfg = getRuntimeConfig();
|
|
2483
|
+
const targetPort = backendSession?.port || (currentCfg?.mcpPort || 6388);
|
|
2484
|
+
const targetHost = backendSession?.host || currentCfg?.mcpHost || '127.0.0.1';
|
|
2485
|
+
|
|
2486
|
+
if ((effectiveToken || currentCfg?.mcpAutoConnect) && window.ChatMCP?.manager?.connectProxy) {
|
|
2487
|
+
window.ChatMCP.manager.connectProxy({
|
|
2488
|
+
host: targetHost,
|
|
2489
|
+
port: targetPort,
|
|
2490
|
+
token: effectiveToken,
|
|
2491
|
+
silentOnFailure: !effectiveToken
|
|
2492
|
+
});
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
// Iniciar latido periódico al servidor local zerochat.py si hay token de sesión activo
|
|
2496
|
+
if (effectiveToken) {
|
|
2497
|
+
startServerHeartbeat(targetHost, targetPort, effectiveToken);
|
|
2498
|
+
}
|
|
2499
|
+
updateNewTabLink();
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
window.ChatApp = {
|
|
2503
|
+
toggleReasoningMenu,
|
|
2504
|
+
updateReasoningUI,
|
|
2505
|
+
toggleDebugPanel,
|
|
2506
|
+
addDebugLog,
|
|
2507
|
+
clearDebugLogs,
|
|
2508
|
+
setDebugStatus,
|
|
2509
|
+
setGenerationStatus,
|
|
2510
|
+
clearGenerationStatus,
|
|
2511
|
+
applyLanguage,
|
|
2512
|
+
switchToSession,
|
|
2513
|
+
createNewSession,
|
|
2514
|
+
createConversationBranch,
|
|
2515
|
+
deleteSession,
|
|
2516
|
+
renameSession,
|
|
2517
|
+
openExecutionInfo,
|
|
2518
|
+
exportConversationAsMarkdown,
|
|
2519
|
+
exportConversationAsJson,
|
|
2520
|
+
exportConversationAsPrint,
|
|
2521
|
+
startServerHeartbeat,
|
|
2522
|
+
stopServerHeartbeat,
|
|
2523
|
+
getNewTabUrl,
|
|
2524
|
+
updateNewTabLink
|
|
2525
|
+
};
|
|
2526
|
+
|
|
2527
|
+
// Fase 6: configurar light-dismiss fallback para navegadores sin closedby
|
|
2528
|
+
setupLightDismissDialogs();
|
|
2529
|
+
|
|
2530
|
+
// Registro del Service Worker para aceleración de arranque y soporte offline en móvil
|
|
2531
|
+
registerServiceWorker();
|
|
2532
|
+
|
|
2533
|
+
document.documentElement.classList.add('zerochat-ready');
|
|
2534
|
+
console.log('💬 ZeroChat initialized with autonomous tools and local Orama knowledge.');
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
function registerServiceWorker() {
|
|
2538
|
+
if (typeof window !== 'undefined' &&
|
|
2539
|
+
'serviceWorker' in navigator &&
|
|
2540
|
+
(location.protocol === 'http:' || location.protocol === 'https:')) {
|
|
2541
|
+
const register = () => {
|
|
2542
|
+
navigator.serviceWorker.register('./sw.js', { scope: './' })
|
|
2543
|
+
.catch(() => {});
|
|
2544
|
+
};
|
|
2545
|
+
if (document.readyState === 'complete') {
|
|
2546
|
+
register();
|
|
2547
|
+
} else {
|
|
2548
|
+
window.addEventListener('load', register, { once: true });
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
if (document.readyState === 'loading') {
|
|
2554
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
2555
|
+
} else {
|
|
2556
|
+
init();
|
|
2557
|
+
}
|
|
2558
|
+
})();
|