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,825 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de Interfaz de Usuario para Configuración, Perfiles y Herramientas.
|
|
3
|
+
* ZeroChat - js/ui-settings.js
|
|
4
|
+
*/
|
|
5
|
+
(function (root, factory) {
|
|
6
|
+
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
7
|
+
module.exports = factory(require('./defaults.js'));
|
|
8
|
+
} else {
|
|
9
|
+
root.ChatUISettings = factory(root.ChatDefaults);
|
|
10
|
+
}
|
|
11
|
+
})(typeof self !== 'undefined' ? self : this, function (Defaults) {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const DEFAULT_THEME = Defaults.DEFAULT_THEME;
|
|
15
|
+
|
|
16
|
+
function resolveDep(globalName, relPath) {
|
|
17
|
+
if (typeof window !== 'undefined' && window[globalName]) return window[globalName];
|
|
18
|
+
if (typeof globalThis !== 'undefined' && globalThis[globalName]) return globalThis[globalName];
|
|
19
|
+
if (typeof require !== 'undefined') { try { return require(relPath); } catch (e) { return null; } }
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getI18n = () => resolveDep('ChatI18n', './i18n.js');
|
|
24
|
+
const getStorage = () => resolveDep('ChatStorage', './cookies.js') || (typeof globalThis !== 'undefined' ? globalThis.Storage : null);
|
|
25
|
+
const getAgentCore = () => resolveDep('ChatAgentCore', './agent-core.js');
|
|
26
|
+
const getMarkdown = () => resolveDep('ChatMarkdown', './markdown.js');
|
|
27
|
+
const getProviders = () => resolveDep('ChatProviders', './providers.js');
|
|
28
|
+
const getDataResetService = () => resolveDep('ChatDataResetService', './data-reset-service.js');
|
|
29
|
+
const getDialogs = () => resolveDep('ChatDialogs', './ui-dialogs.js');
|
|
30
|
+
|
|
31
|
+
function t(key, params) {
|
|
32
|
+
const I18n = getI18n();
|
|
33
|
+
if (I18n && typeof I18n.t === 'function') return I18n.t(key, params);
|
|
34
|
+
return key;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function escapeHtml(str) {
|
|
38
|
+
const Markdown = getMarkdown();
|
|
39
|
+
if (Markdown && typeof Markdown.escapeHtml === 'function') {
|
|
40
|
+
return Markdown.escapeHtml(str);
|
|
41
|
+
}
|
|
42
|
+
return String(str || '').replace(/[&<>"']/g, (m) => {
|
|
43
|
+
switch (m) {
|
|
44
|
+
case '&': return '&';
|
|
45
|
+
case '<': return '<';
|
|
46
|
+
case '>': return '>';
|
|
47
|
+
case '"': return '"';
|
|
48
|
+
case "'": return ''';
|
|
49
|
+
default: return m;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function applyTheme(elements, appConfig, theme) {
|
|
55
|
+
const doc = (typeof document !== 'undefined') ? document : null;
|
|
56
|
+
const root = doc ? doc.documentElement : null;
|
|
57
|
+
const effective = (theme === 'dark') ? 'dark' : 'light';
|
|
58
|
+
if (root) {
|
|
59
|
+
if (effective === 'dark') {
|
|
60
|
+
root.setAttribute('data-theme', 'dark');
|
|
61
|
+
} else {
|
|
62
|
+
root.removeAttribute('data-theme');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (elements?.themeButtons && elements.themeButtons.length > 0) {
|
|
67
|
+
elements.themeButtons.forEach(btn => {
|
|
68
|
+
if (btn.getAttribute('data-theme') === effective) {
|
|
69
|
+
btn.classList.add('active');
|
|
70
|
+
} else {
|
|
71
|
+
btn.classList.remove('active');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return effective;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function applyLanguage(elements, appConfig, lang, callbacks = {}) {
|
|
79
|
+
const target = (lang === 'en') ? 'en' : 'es';
|
|
80
|
+
const I18n = getI18n();
|
|
81
|
+
if (I18n?.setLanguage) {
|
|
82
|
+
I18n.setLanguage(target, true);
|
|
83
|
+
}
|
|
84
|
+
if (elements?.langButtons && elements.langButtons.length > 0) {
|
|
85
|
+
elements.langButtons.forEach(btn => {
|
|
86
|
+
if (btn.getAttribute('data-lang') === target) {
|
|
87
|
+
btn.classList.add('active');
|
|
88
|
+
} else {
|
|
89
|
+
btn.classList.remove('active');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (typeof callbacks.updateReasoningUI === 'function' && appConfig) {
|
|
95
|
+
callbacks.updateReasoningUI(appConfig.reasoningEffort);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (elements?.settingSystemPrompt) {
|
|
99
|
+
elements.settingSystemPrompt.setAttribute('placeholder', t('field_system_prompt_placeholder'));
|
|
100
|
+
}
|
|
101
|
+
if (elements?.settingSystemDataPrompt) {
|
|
102
|
+
elements.settingSystemDataPrompt.setAttribute('placeholder', t('field_system_data_prompt_placeholder'));
|
|
103
|
+
}
|
|
104
|
+
const welcomeHelp = elements?.welcomeHelpLink || (typeof document !== 'undefined' ? document.getElementById('welcome-help-link') : null);
|
|
105
|
+
if (welcomeHelp) {
|
|
106
|
+
welcomeHelp.href = (target === 'en')
|
|
107
|
+
? 'help/en/index.html'
|
|
108
|
+
: 'help/index.html';
|
|
109
|
+
}
|
|
110
|
+
return target;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function renderAgentToolsUI(container, currentEnabledTools = {}) {
|
|
114
|
+
if (!container) return;
|
|
115
|
+
const AgentCore = getAgentCore();
|
|
116
|
+
const allTools = (AgentCore?.registry && typeof AgentCore.registry.listToolsForUI === 'function')
|
|
117
|
+
? AgentCore.registry.listToolsForUI()
|
|
118
|
+
: [];
|
|
119
|
+
const tools = allTools.filter(t => t.category !== 'mcp');
|
|
120
|
+
|
|
121
|
+
container.innerHTML = '';
|
|
122
|
+
const doc = container.ownerDocument || (typeof document !== 'undefined' ? document : null);
|
|
123
|
+
if (!doc) return;
|
|
124
|
+
|
|
125
|
+
tools.forEach(tool => {
|
|
126
|
+
const isChecked = currentEnabledTools[tool.id] !== undefined
|
|
127
|
+
? currentEnabledTools[tool.id] !== false
|
|
128
|
+
: (currentEnabledTools[tool.name] !== undefined ? currentEnabledTools[tool.name] !== false : tool.defaultEnabled !== false);
|
|
129
|
+
|
|
130
|
+
const title = t(tool.titleKey) || tool.titleFallback || tool.name;
|
|
131
|
+
const desc = t(tool.descKey) || tool.descFallback || '';
|
|
132
|
+
|
|
133
|
+
const card = doc.createElement('div');
|
|
134
|
+
card.className = 'setting-toggle-card';
|
|
135
|
+
card.innerHTML = `
|
|
136
|
+
<div class="toggle-card-info">
|
|
137
|
+
<div class="toggle-card-title">
|
|
138
|
+
<span data-i18n="${escapeHtml(tool.titleKey)}">${escapeHtml(title)}</span>
|
|
139
|
+
</div>
|
|
140
|
+
<p class="toggle-card-desc" data-i18n="${escapeHtml(tool.descKey)}">${escapeHtml(desc)}</p>
|
|
141
|
+
</div>
|
|
142
|
+
<label class="switch">
|
|
143
|
+
<input type="checkbox" class="agent-tool-checkbox" data-tool-id="${escapeHtml(tool.id)}" ${isChecked ? 'checked' : ''}>
|
|
144
|
+
<span class="slider"></span>
|
|
145
|
+
</label>
|
|
146
|
+
`;
|
|
147
|
+
container.appendChild(card);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function gatherEnabledToolsFromUI(container) {
|
|
152
|
+
const map = {};
|
|
153
|
+
if (!container) return map;
|
|
154
|
+
container.querySelectorAll('.agent-tool-checkbox').forEach(cb => {
|
|
155
|
+
const tid = cb.getAttribute('data-tool-id');
|
|
156
|
+
if (tid) map[tid] = cb.checked;
|
|
157
|
+
});
|
|
158
|
+
return map;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function applyProfileToForm(elements, profileData) {
|
|
162
|
+
if (!elements || !profileData) return;
|
|
163
|
+
|
|
164
|
+
if (elements.settingApiType && profileData.apiType !== undefined) {
|
|
165
|
+
elements.settingApiType.value = profileData.apiType;
|
|
166
|
+
}
|
|
167
|
+
if (elements.settingApiUrl) {
|
|
168
|
+
const apiType = elements.settingApiType?.value || profileData.apiType || 'openai';
|
|
169
|
+
const defaultEndpoint = getProviders()?.registry?.get?.(apiType)?.getConnectionConfig?.().endpoint || 'http://localhost:1234/v1';
|
|
170
|
+
elements.settingApiUrl.value = (profileData.apiUrl !== undefined && profileData.apiUrl !== '') ? profileData.apiUrl : defaultEndpoint;
|
|
171
|
+
}
|
|
172
|
+
if (elements.settingApiKeyLocked) elements.settingApiKeyLocked.checked = profileData.apiKeyLocked === true;
|
|
173
|
+
if (elements.profilesDialog) elements.profilesDialog.dataset.profileLocked = String(profileData.apiKeyLocked === true);
|
|
174
|
+
if (elements.apiKeyLockControl) elements.apiKeyLockControl.hidden = profileData.apiKeyLocked === true;
|
|
175
|
+
if (elements.apiKeyLockedStatus) elements.apiKeyLockedStatus.hidden = profileData.apiKeyLocked !== true;
|
|
176
|
+
if (elements.settingModel && profileData.model !== undefined) {
|
|
177
|
+
elements.settingModel.value = profileData.model;
|
|
178
|
+
}
|
|
179
|
+
if (elements.modelSelectHelper && profileData.model !== undefined) {
|
|
180
|
+
elements.modelSelectHelper.value = profileData.model;
|
|
181
|
+
}
|
|
182
|
+
if (elements.settingSystemPrompt && profileData.systemPrompt !== undefined) {
|
|
183
|
+
elements.settingSystemPrompt.value = profileData.systemPrompt;
|
|
184
|
+
}
|
|
185
|
+
if (elements.settingSystemDataPrompt && profileData.systemDataPrompt !== undefined) {
|
|
186
|
+
elements.settingSystemDataPrompt.value = profileData.systemDataPrompt;
|
|
187
|
+
}
|
|
188
|
+
if (elements.settingTemperature && profileData.temperature !== undefined) {
|
|
189
|
+
elements.settingTemperature.value = profileData.temperature;
|
|
190
|
+
if (elements.temperatureVal) {
|
|
191
|
+
elements.temperatureVal.textContent = profileData.temperature;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (elements.settingMaxAgentTurns && profileData.maxAgentTurns !== undefined) {
|
|
195
|
+
elements.settingMaxAgentTurns.value = profileData.maxAgentTurns;
|
|
196
|
+
if (elements.maxAgentTurnsVal) {
|
|
197
|
+
elements.maxAgentTurnsVal.textContent = profileData.maxAgentTurns;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (elements.agentToolsContainer) {
|
|
201
|
+
renderAgentToolsUI(elements.agentToolsContainer, profileData.enabledTools || {});
|
|
202
|
+
}
|
|
203
|
+
if (elements.settingEnableRawLogs && profileData.enableRawLogs !== undefined) {
|
|
204
|
+
elements.settingEnableRawLogs.checked = profileData.enableRawLogs === true;
|
|
205
|
+
}
|
|
206
|
+
if (elements.settingEnableContextCache && profileData.enableContextCache !== undefined) {
|
|
207
|
+
elements.settingEnableContextCache.checked = profileData.enableContextCache !== false;
|
|
208
|
+
}
|
|
209
|
+
const webllmCfg = profileData.webllmConfig || {};
|
|
210
|
+
const setParamSelect = (el, val) => {
|
|
211
|
+
if (!el) return;
|
|
212
|
+
const cleanVal = (val && val !== 'default') ? String(val) : 'default';
|
|
213
|
+
el.value = cleanVal;
|
|
214
|
+
if (!el.value) el.value = 'default';
|
|
215
|
+
};
|
|
216
|
+
setParamSelect(elements.settingWebllmContextWindow, webllmCfg.context_window_size);
|
|
217
|
+
setParamSelect(elements.settingWebllmPrefillChunk, webllmCfg.prefill_chunk_size);
|
|
218
|
+
if (elements?.webllmParamsPanel) elements.webllmParamsPanel.hidden = true;
|
|
219
|
+
if (elements?.btnWebllmParams?.classList) elements.btnWebllmParams.classList.remove('active');
|
|
220
|
+
syncProviderFields(elements);
|
|
221
|
+
syncApiKeyLock(elements);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function syncApiKeyLock(elements, readOnly = false) {
|
|
225
|
+
const locked = elements?.profilesDialog?.dataset.profileLocked === 'true';
|
|
226
|
+
elements?.profilesDialog?.querySelectorAll('.modal-body input, .modal-body textarea, .modal-body select, .modal-body button').forEach(control => {
|
|
227
|
+
if (control === elements.profileSelectHelper || control.hasAttribute('data-profile-global-action')) return;
|
|
228
|
+
control.disabled = readOnly || locked || (control === elements.settingApiKey && control.closest('.api-key-field')?.hidden === true);
|
|
229
|
+
});
|
|
230
|
+
if (elements?.settingApiKeyLocked) elements.settingApiKeyLocked.disabled = readOnly || locked;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function syncProviderFields(elements) {
|
|
234
|
+
const providerId = elements?.settingApiType?.value || 'openai';
|
|
235
|
+
const adapter = getProviders()?.registry?.get?.(providerId);
|
|
236
|
+
const connection = adapter?.getConnectionConfig?.() || {};
|
|
237
|
+
const isWebLLM = providerId === 'webllm';
|
|
238
|
+
if (elements?.settingApiUrl) {
|
|
239
|
+
if (connection.endpoint) {
|
|
240
|
+
elements.settingApiUrl.placeholder = connection.endpoint;
|
|
241
|
+
}
|
|
242
|
+
if (connection.endpointReadOnly && connection.endpoint) {
|
|
243
|
+
elements.settingApiUrl.value = connection.endpoint;
|
|
244
|
+
} else if (!elements.settingApiUrl.value.trim() && connection.endpoint) {
|
|
245
|
+
elements.settingApiUrl.value = connection.endpoint;
|
|
246
|
+
}
|
|
247
|
+
elements.settingApiUrl.readOnly = connection.endpointReadOnly === true;
|
|
248
|
+
elements.settingApiUrl.required = connection.endpointReadOnly !== true;
|
|
249
|
+
}
|
|
250
|
+
if (elements?.settingApiKey) elements.settingApiKey.disabled = connection.credentials === false;
|
|
251
|
+
const apiKeyField = elements?.settingApiKey?.closest?.('.api-key-field');
|
|
252
|
+
if (apiKeyField) apiKeyField.hidden = connection.credentials === false;
|
|
253
|
+
const apiKeyHintText = apiKeyField?.querySelector?.('#api-key-hint-text');
|
|
254
|
+
const apiKeyFreeHelpLink = apiKeyField?.querySelector?.('#api-key-free-help-link');
|
|
255
|
+
const freeTierHelp = {
|
|
256
|
+
openrouter: 'help/openrouter-free.html',
|
|
257
|
+
gemini: 'help/gemini-free.html'
|
|
258
|
+
};
|
|
259
|
+
if (apiKeyHintText) {
|
|
260
|
+
const hintKey = ['openai', 'ollama'].includes(providerId)
|
|
261
|
+
? 'field_api_key_hint'
|
|
262
|
+
: 'field_api_key_required_hint';
|
|
263
|
+
apiKeyHintText.dataset.i18n = hintKey;
|
|
264
|
+
apiKeyHintText.textContent = t(hintKey);
|
|
265
|
+
}
|
|
266
|
+
if (apiKeyFreeHelpLink) {
|
|
267
|
+
const helpUrl = freeTierHelp[providerId];
|
|
268
|
+
apiKeyFreeHelpLink.hidden = !helpUrl;
|
|
269
|
+
if (helpUrl) apiKeyFreeHelpLink.href = helpUrl;
|
|
270
|
+
}
|
|
271
|
+
const field = typeof elements?.settingApiUrl?.closest === 'function'
|
|
272
|
+
? elements.settingApiUrl.closest('.form-field') : null;
|
|
273
|
+
const hint = field?.querySelector('.webllm-local-hint') || elements?.webllmLocalHint;
|
|
274
|
+
if (hint) hint.hidden = !isWebLLM;
|
|
275
|
+
const helpLink = field?.querySelector('.webllm-help-link') || elements?.webllmHelpLink || (typeof document !== 'undefined' ? document.getElementById('webllm-help-link') : null);
|
|
276
|
+
if (helpLink) helpLink.hidden = providerId !== 'webllm';
|
|
277
|
+
|
|
278
|
+
const btnWebllmParams = elements?.btnWebllmParams || (typeof document !== 'undefined' ? document.getElementById('btn-webllm-params') : null);
|
|
279
|
+
const webllmParamsPanel = elements?.webllmParamsPanel || (typeof document !== 'undefined' ? document.getElementById('webllm-params-panel') : null);
|
|
280
|
+
if (btnWebllmParams) btnWebllmParams.hidden = !isWebLLM;
|
|
281
|
+
if (webllmParamsPanel && !isWebLLM) {
|
|
282
|
+
webllmParamsPanel.hidden = true;
|
|
283
|
+
if (btnWebllmParams) btnWebllmParams.classList.remove('active');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function gatherCurrentFormConfig(elements, appConfig) {
|
|
288
|
+
const profileName = (elements?.settingProfileName && elements.settingProfileName.value.trim())
|
|
289
|
+
? elements.settingProfileName.value.trim()
|
|
290
|
+
: ((elements?.profileSelectHelper && elements.profileSelectHelper.value)
|
|
291
|
+
? elements.profileSelectHelper.value
|
|
292
|
+
: (appConfig?.activeProfile?.name || 'Local chat'));
|
|
293
|
+
const selectedModel = elements?.settingModel ? elements.settingModel.value.trim() : '';
|
|
294
|
+
const getParamVal = (el, fallback) => {
|
|
295
|
+
const v = el?.value ? el.value.trim() : '';
|
|
296
|
+
return v || fallback || 'default';
|
|
297
|
+
};
|
|
298
|
+
const prevWebllmConfig = appConfig?.webllmConfig || {};
|
|
299
|
+
const webllmConfig = {
|
|
300
|
+
context_window_size: getParamVal(elements?.settingWebllmContextWindow, prevWebllmConfig.context_window_size),
|
|
301
|
+
prefill_chunk_size: getParamVal(elements?.settingWebllmPrefillChunk, prevWebllmConfig.prefill_chunk_size)
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
activeProfileName: profileName,
|
|
305
|
+
apiUrl: elements?.settingApiUrl ? elements.settingApiUrl.value.trim() : (appConfig?.apiUrl || 'http://localhost:1234/v1'),
|
|
306
|
+
apiType: elements?.settingApiType ? elements.settingApiType.value : (appConfig?.apiType || 'openai'),
|
|
307
|
+
model: selectedModel,
|
|
308
|
+
webllmConfig,
|
|
309
|
+
// El límite publicado pertenece a la conexión en ejecución, no al formulario.
|
|
310
|
+
modelContextLimit: null,
|
|
311
|
+
contextLimitOverride: appConfig?.contextLimitOverride || null,
|
|
312
|
+
systemPrompt: elements?.settingSystemPrompt ? elements.settingSystemPrompt.value.trim() : (appConfig?.systemPrompt || ''),
|
|
313
|
+
systemDataPrompt: elements?.settingSystemDataPrompt ? elements.settingSystemDataPrompt.value.trim() : (appConfig?.systemDataPrompt || ''),
|
|
314
|
+
temperature: elements?.settingTemperature ? elements.settingTemperature.value : (appConfig?.temperature || '0.7'),
|
|
315
|
+
reasoningEffort: appConfig?.reasoningEffort || 'medium',
|
|
316
|
+
reasoningTransport: appConfig?.reasoningTransport || 'auto',
|
|
317
|
+
maxAgentTurns: elements?.settingMaxAgentTurns ? Number(elements.settingMaxAgentTurns.value) : (appConfig?.maxAgentTurns || 15),
|
|
318
|
+
modelReasoningConfig: appConfig?.modelReasoningConfig || null,
|
|
319
|
+
theme: appConfig?.theme || DEFAULT_THEME,
|
|
320
|
+
language: appConfig?.language || 'es',
|
|
321
|
+
enabledTools: {
|
|
322
|
+
...gatherEnabledToolsFromUI(elements?.agentToolsContainer),
|
|
323
|
+
...gatherEnabledToolsFromUI(elements?.mcpToolsContainer)
|
|
324
|
+
},
|
|
325
|
+
enableRawLogs: elements?.settingEnableRawLogs ? elements.settingEnableRawLogs.checked : Boolean(appConfig?.enableRawLogs),
|
|
326
|
+
enableDebugMessages: Boolean(appConfig?.enableDebugMessages),
|
|
327
|
+
enableContextCache: elements?.settingEnableContextCache ? elements.settingEnableContextCache.checked : (appConfig?.enableContextCache !== false),
|
|
328
|
+
activeRagBranchId: appConfig?.activeRagBranchId || '',
|
|
329
|
+
activeRagBranchIds: Array.isArray(appConfig?.activeRagBranchIds) ? [...appConfig.activeRagBranchIds] : []
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function showProfileFeedback(elements, msg, type = 'success') {
|
|
334
|
+
if (!elements || !elements.profileActionFeedback) return;
|
|
335
|
+
elements.profileActionFeedback.style.display = 'block';
|
|
336
|
+
elements.profileActionFeedback.className = `server-query-status status-${type}`;
|
|
337
|
+
elements.profileActionFeedback.textContent = msg;
|
|
338
|
+
const timer = setTimeout(() => {
|
|
339
|
+
if (elements.profileActionFeedback) {
|
|
340
|
+
elements.profileActionFeedback.style.display = 'none';
|
|
341
|
+
}
|
|
342
|
+
}, 4000);
|
|
343
|
+
if (typeof timer?.unref === 'function') timer.unref();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const SECTION_TITLES = {
|
|
347
|
+
'tab-model': 'tab_model',
|
|
348
|
+
'tab-agent': 'tab_agent',
|
|
349
|
+
'tab-mcp': 'tab_mcp',
|
|
350
|
+
'tab-permissions': 'tab_permissions',
|
|
351
|
+
'tab-inspector': 'tab_inspector',
|
|
352
|
+
'model': 'tab_model',
|
|
353
|
+
'agent': 'tab_agent',
|
|
354
|
+
'mcp': 'tab_mcp',
|
|
355
|
+
'permissions': 'tab_permissions',
|
|
356
|
+
'inspector': 'tab_inspector'
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
function normalizeSectionId(sectionId) {
|
|
360
|
+
if (!sectionId) return 'tab-model';
|
|
361
|
+
if (SECTION_TITLES[sectionId]) {
|
|
362
|
+
return sectionId.startsWith('tab-') ? sectionId : 'tab-' + sectionId;
|
|
363
|
+
}
|
|
364
|
+
const candidate = sectionId.startsWith('tab-') ? sectionId : 'tab-' + sectionId;
|
|
365
|
+
return SECTION_TITLES[candidate] ? candidate : 'tab-model';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function openSettingsSection(elements, appConfig, callbacks = {}, sectionId = 'tab-model') {
|
|
369
|
+
ensureDialogMarkup();
|
|
370
|
+
if (!elements || !elements.settingsDialog) return;
|
|
371
|
+
const preservePendingChanges = elements.settingsDialog.open && isSettingsFormDirty(elements);
|
|
372
|
+
if (elements.settingsActiveProfileName) {
|
|
373
|
+
elements.settingsActiveProfileName.textContent = appConfig?.activeProfile?.name || 'Espejo';
|
|
374
|
+
}
|
|
375
|
+
if (!preservePendingChanges && elements.settingSystemDataPrompt) elements.settingSystemDataPrompt.value = appConfig?.systemDataPrompt || '';
|
|
376
|
+
|
|
377
|
+
if (!preservePendingChanges) {
|
|
378
|
+
applyTheme(elements, appConfig, appConfig?.theme || DEFAULT_THEME);
|
|
379
|
+
applyLanguage(elements, appConfig, appConfig?.language || 'es', callbacks);
|
|
380
|
+
|
|
381
|
+
if (elements.agentToolsContainer) {
|
|
382
|
+
renderAgentToolsUI(elements.agentToolsContainer, appConfig?.enabledTools || {});
|
|
383
|
+
}
|
|
384
|
+
if (elements.settingEnableRawLogs) {
|
|
385
|
+
elements.settingEnableRawLogs.checked = appConfig?.enableRawLogs === true;
|
|
386
|
+
}
|
|
387
|
+
if (elements.settingMaxAgentTurns) {
|
|
388
|
+
elements.settingMaxAgentTurns.value = appConfig?.maxAgentTurns || 15;
|
|
389
|
+
}
|
|
390
|
+
if (elements.maxAgentTurnsVal) {
|
|
391
|
+
elements.maxAgentTurnsVal.textContent = appConfig?.maxAgentTurns || 15;
|
|
392
|
+
}
|
|
393
|
+
if (elements.settingEnableContextCache) {
|
|
394
|
+
elements.settingEnableContextCache.checked = appConfig?.enableContextCache !== false;
|
|
395
|
+
}
|
|
396
|
+
if (elements.mcpHostInput) {
|
|
397
|
+
elements.mcpHostInput.value = appConfig?.mcpHost || '127.0.0.1';
|
|
398
|
+
}
|
|
399
|
+
if (elements.mcpPortInput) {
|
|
400
|
+
elements.mcpPortInput.value = appConfig?.mcpPort || 6388;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const targetId = normalizeSectionId(sectionId);
|
|
405
|
+
const doc = elements.settingsDialog?.ownerDocument || (typeof document !== 'undefined' ? document : null);
|
|
406
|
+
|
|
407
|
+
const settingsSections = elements.settingsDialog?.querySelectorAll
|
|
408
|
+
? elements.settingsDialog.querySelectorAll('.settings-section-pane')
|
|
409
|
+
: elements.settingsSections;
|
|
410
|
+
|
|
411
|
+
if (settingsSections && settingsSections.length > 0) {
|
|
412
|
+
settingsSections.forEach(section => section.classList.remove('active'));
|
|
413
|
+
const targetPane = doc ? doc.getElementById(targetId) : null;
|
|
414
|
+
if (targetPane) {
|
|
415
|
+
targetPane.classList.add('active');
|
|
416
|
+
} else if (settingsSections[0]) {
|
|
417
|
+
settingsSections[0].classList.add('active');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const titleEl = elements.settingsSectionTitle || (doc ? doc.getElementById('settings-section-title') : null);
|
|
422
|
+
if (titleEl) {
|
|
423
|
+
const titleKey = SECTION_TITLES[targetId] || 'tab_connection';
|
|
424
|
+
titleEl.setAttribute('data-i18n', titleKey);
|
|
425
|
+
titleEl.textContent = t(titleKey);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const Sidebar = resolveDep('ChatUISidebar', './ui-sidebar.js');
|
|
429
|
+
if (Sidebar && typeof Sidebar.setActiveSettingsSection === 'function') {
|
|
430
|
+
Sidebar.setActiveSettingsSection(elements, targetId);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (typeof elements.settingsDialog.showModal === 'function') {
|
|
434
|
+
elements.settingsDialog.showModal();
|
|
435
|
+
}
|
|
436
|
+
if (!preservePendingChanges) setSettingsFormDirty(elements, false);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function setSettingsFormDirty(elements, dirty) {
|
|
440
|
+
if (elements?.settingsDialog?.dataset) {
|
|
441
|
+
elements.settingsDialog.dataset.settingsDirty = String(Boolean(dirty));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function isSettingsFormDirty(elements) {
|
|
446
|
+
return elements?.settingsDialog?.dataset?.settingsDirty === 'true';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async function closeSettingsModal(elements, force = false) {
|
|
450
|
+
if (!force && isSettingsFormDirty(elements)) {
|
|
451
|
+
const Dialogs = getDialogs();
|
|
452
|
+
const discard = Dialogs?.confirm
|
|
453
|
+
? await Dialogs.confirm(t('confirm_settings_unsaved_changes'))
|
|
454
|
+
: false;
|
|
455
|
+
if (!discard) return false;
|
|
456
|
+
}
|
|
457
|
+
if (elements?.settingsDialog && typeof elements.settingsDialog.close === 'function') {
|
|
458
|
+
elements.settingsDialog.close();
|
|
459
|
+
}
|
|
460
|
+
setSettingsFormDirty(elements, false);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function handleClearAllData() {
|
|
465
|
+
const ResetService = getDataResetService();
|
|
466
|
+
if (ResetService?.resetAllData) {
|
|
467
|
+
return await ResetService.resetAllData();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function getSettingsDialogHTML() {
|
|
472
|
+
return `<div class="modal-header settings-section-header">
|
|
473
|
+
<div class="modal-title">
|
|
474
|
+
<h3 id="settings-section-title" data-i18n="tab_model">Modelo</h3>
|
|
475
|
+
</div>
|
|
476
|
+
<div class="settings-header-actions">
|
|
477
|
+
<button type="submit" form="settings-form" id="btn-save-settings" class="btn-primary btn-save-header" data-i18n-title="btn_save" title="Guardar configuración" aria-label="Guardar">
|
|
478
|
+
<svg class="ui-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-save"></use></svg>
|
|
479
|
+
<span data-i18n="btn_save_short">Guardar</span>
|
|
480
|
+
</button>
|
|
481
|
+
<button type="button" id="btn-close-settings" class="btn-close" data-i18n-aria="modal_close_aria" aria-label="Cerrar modal">
|
|
482
|
+
<svg class="ui-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-close"></use></svg>
|
|
483
|
+
</button>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<form id="settings-form" class="settings-form-wrapper">
|
|
488
|
+
<div class="modal-body">
|
|
489
|
+
<!-- Sección: Modelo y Prompt -->
|
|
490
|
+
<div id="tab-model" class="settings-section-pane active">
|
|
491
|
+
<div class="form-field">
|
|
492
|
+
<label for="setting-system-data-prompt">
|
|
493
|
+
<strong data-i18n="field_system_data_prompt">Perfil del sistema · datos ZeroChat</strong>
|
|
494
|
+
<span class="label-hint" data-i18n="field_system_data_prompt_hint">Instrucciones internas de ZeroChat. Es mejor no modificarlas; añade lo que veas necesario.</span>
|
|
495
|
+
</label>
|
|
496
|
+
<textarea
|
|
497
|
+
id="setting-system-data-prompt"
|
|
498
|
+
rows="5"
|
|
499
|
+
data-i18n-placeholder="field_system_data_prompt_placeholder"
|
|
500
|
+
placeholder="Instrucciones internas de ZeroChat..."
|
|
501
|
+
></textarea>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div class="cookie-notice">
|
|
505
|
+
<svg class="ui-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-help-circle"></use></svg>
|
|
506
|
+
<span><strong data-i18n="model_cache_title">Caché de contexto automática</strong><br><span data-i18n="model_cache_desc">El cliente aplica automáticamente las marcas de caché que admita el proveedor. En servidores locales la caché KV se administra en el servidor.</span></span>
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
<div class="cookie-notice">
|
|
510
|
+
<svg class="ui-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-help-circle"></use></svg>
|
|
511
|
+
<span data-i18n-html="cookie_notice">Toda la configuración se almacena localmente de forma persistente (compatible tanto con <strong>file://</strong> como con servidores web).</span>
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
|
|
515
|
+
<!-- Sección: Modo Agente y Herramientas -->
|
|
516
|
+
<div id="tab-agent" class="settings-section-pane">
|
|
517
|
+
<div class="settings-section-intro">
|
|
518
|
+
<p data-i18n="agent_intro">Configura las herramientas agénticas que se transmiten al modelo.</p>
|
|
519
|
+
</div>
|
|
520
|
+
|
|
521
|
+
<div class="form-field" style="margin-bottom: 1.25rem;">
|
|
522
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.35rem;">
|
|
523
|
+
<label for="setting-max-agent-turns" style="margin-bottom: 0;">
|
|
524
|
+
<strong data-i18n="field_max_agent_turns">Límite de turnos agénticos</strong>
|
|
525
|
+
</label>
|
|
526
|
+
<span id="max-agent-turns-val" style="font-size: 0.85rem; font-weight: 600; color: var(--color-primary, #2563eb);">15</span>
|
|
527
|
+
</div>
|
|
528
|
+
<span class="label-hint" data-i18n="field_max_agent_turns_hint">Número máximo de pasos o llamadas consecutivas a herramientas antes de forzar la síntesis final (entre 5 y 35).</span>
|
|
529
|
+
<div style="display: flex; align-items: center; gap: 0.75rem; margin-top: 0.4rem;">
|
|
530
|
+
<input type="range" id="setting-max-agent-turns" min="5" max="35" step="1" value="15" style="flex: 1;">
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
|
|
534
|
+
<!-- Contenedor dinámico de herramientas agénticas registradas -->
|
|
535
|
+
<div id="agent-tools-container" class="agent-tools-container"></div>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<!-- Sección: Servidor Local (MCP) -->
|
|
539
|
+
<div id="tab-mcp" class="settings-section-pane">
|
|
540
|
+
<!-- Estado del servidor local -->
|
|
541
|
+
<div class="mcp-status-card">
|
|
542
|
+
<div class="mcp-status-header">
|
|
543
|
+
<div class="mcp-title-group">
|
|
544
|
+
<span class="mcp-header-icon">
|
|
545
|
+
<svg class="ui-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-server"></use></svg>
|
|
546
|
+
</span>
|
|
547
|
+
<strong data-i18n="mcp_connection_title">Servidor Local</strong>
|
|
548
|
+
<span id="mcp-server-details" class="mcp-server-details" style="display: none;"></span>
|
|
549
|
+
</div>
|
|
550
|
+
|
|
551
|
+
<!-- Indicador de Estado -->
|
|
552
|
+
<div class="mcp-status-actions">
|
|
553
|
+
<span id="mcp-status-badge" class="mcp-status-badge mcp-status-disconnected">
|
|
554
|
+
<span class="mcp-status-dot"></span>
|
|
555
|
+
<span id="mcp-status-text">Desconectado</span>
|
|
556
|
+
</span>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
<div id="mcp-bootstrap-card" class="mcp-bootstrap-card">
|
|
562
|
+
<div class="mcp-bootstrap-header">
|
|
563
|
+
<svg class="ui-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-terminal"></use></svg>
|
|
564
|
+
<strong data-i18n="mcp_bootstrap_title">MCP requiere el servidor local</strong>
|
|
565
|
+
</div>
|
|
566
|
+
<p class="label-hint mcp-section-hint" data-i18n="mcp_bootstrap_desc">Para usar MCP, inicia ZeroChat desde zerochat.py en un terminal. La aplicación se conectará automáticamente cuando el servidor esté disponible.</p>
|
|
567
|
+
<p id="mcp-reconnect-hint" class="label-hint mcp-section-hint" data-i18n="mcp_reconnect_after_restart">Si el servidor se ha reiniciado, recarga esta página (F5) para volver a conectar.</p>
|
|
568
|
+
<div class="mcp-cmd-row">
|
|
569
|
+
<pre class="mcp-command-box mcp-cmd-box-flex"><code id="mcp-terminal-command">curl -sL https://albalday.github.io/zerochat/zerochat.py -o zerochat.py && python3 zerochat.py</code></pre>
|
|
570
|
+
<button type="button" id="btn-mcp-copy-cmd" class="btn-secondary btn-copy-mcp-cmd" data-i18n-title="mcp_btn_copy_cmd" title="Copiar comando">
|
|
571
|
+
<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-copy"></use></svg>
|
|
572
|
+
<span data-i18n="mcp_btn_copy_cmd">Copiar comando</span>
|
|
573
|
+
</button>
|
|
574
|
+
</div>
|
|
575
|
+
<a href="help/mcp.html" target="_blank" rel="noopener noreferrer" class="mcp-bootstrap-help" data-i18n="mcp_bootstrap_help">Abrir ayuda de MCP</a>
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
<!-- Tarjeta de Servidores MCP Disponibles -->
|
|
579
|
+
<div id="mcp-servers-card" class="mcp-status-card" style="margin-top: 1rem;">
|
|
580
|
+
<div class="mcp-status-header">
|
|
581
|
+
<div class="mcp-title-group">
|
|
582
|
+
<span class="mcp-header-icon">
|
|
583
|
+
<svg class="ui-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-cpu"></use></svg>
|
|
584
|
+
</span>
|
|
585
|
+
<strong data-i18n="mcp_servers_section_title">Servidores MCP Disponibles</strong>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
<p class="label-hint mcp-section-hint" data-i18n="mcp_servers_section_desc">Servicios MCP externos configurados en el servidor local listos para arrancar de forma individual.</p>
|
|
589
|
+
<div id="mcp-servers-list" class="mcp-servers-list"></div>
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<!-- Contenedor dinámico de herramientas MCP registradas -->
|
|
593
|
+
<div id="mcp-tools-container" class="mcp-tools-container" style="margin-top: 1rem;"></div>
|
|
594
|
+
</div>
|
|
595
|
+
|
|
596
|
+
<!-- Sección: Permisos de ejecución MCP -->
|
|
597
|
+
<div id="tab-permissions" class="settings-section-pane">
|
|
598
|
+
<div class="mcp-security-card">
|
|
599
|
+
<div class="mcp-security-header">
|
|
600
|
+
<span class="mcp-security-icon">
|
|
601
|
+
<svg class="ui-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
|
602
|
+
</span>
|
|
603
|
+
<div>
|
|
604
|
+
<strong data-i18n="mcp_security_section_title">Seguridad y Autorización de Ejecución</strong>
|
|
605
|
+
<p class="label-hint mcp-section-hint" data-i18n="mcp_security_desc">Controla cuándo se ejecutan las herramientas del servidor MCP en tu sistema local.</p>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
<div class="mcp-policy-options">
|
|
610
|
+
<label class="mcp-policy-option">
|
|
611
|
+
<input type="radio" name="mcp-global-policy" value="ask" id="mcp-policy-ask" checked>
|
|
612
|
+
<div class="mcp-policy-text">
|
|
613
|
+
<strong data-i18n="mcp_security_policy_ask">Pedir autorización antes de ejecutar (Recomendado)</strong>
|
|
614
|
+
<p class="label-hint" data-i18n="mcp_security_policy_ask_hint">El chat te pedirá confirmar cada comando o herramienta MCP no autorizada previamente.</p>
|
|
615
|
+
</div>
|
|
616
|
+
</label>
|
|
617
|
+
<label class="mcp-policy-option">
|
|
618
|
+
<input type="radio" name="mcp-global-policy" value="allow_all" id="mcp-policy-allow-all">
|
|
619
|
+
<div class="mcp-policy-text">
|
|
620
|
+
<strong data-i18n="mcp_security_policy_allow_all">Todo autorizado (Modo sin restricciones)</strong>
|
|
621
|
+
<p class="label-hint" data-i18n="mcp_security_policy_allow_all_hint">Ejecuta inmediatamente cualquier herramienta MCP sin pausas de confirmación.</p>
|
|
622
|
+
</div>
|
|
623
|
+
</label>
|
|
624
|
+
</div>
|
|
625
|
+
|
|
626
|
+
<div class="mcp-saved-auths-section">
|
|
627
|
+
<div class="mcp-saved-auths-header">
|
|
628
|
+
<span class="label-hint" data-i18n="mcp_security_saved_auths_title">Herramientas con Permiso Recordado:</span>
|
|
629
|
+
<button type="button" id="btn-mcp-clear-auths" class="btn-text-action btn-mcp-clear-auths" data-i18n="mcp_security_btn_clear_all">Restablecer todas</button>
|
|
630
|
+
</div>
|
|
631
|
+
<div id="mcp-saved-auths-list" class="mcp-saved-auths-list"></div>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
|
|
636
|
+
<!-- Sección: Inspector de Proveedor -->
|
|
637
|
+
<div id="tab-inspector" class="settings-section-pane">
|
|
638
|
+
<div class="inspector-intro-card">
|
|
639
|
+
<div class="inspector-intro-header">
|
|
640
|
+
<span class="inspector-intro-icon">
|
|
641
|
+
<svg class="ui-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-search"></use></svg>
|
|
642
|
+
</span>
|
|
643
|
+
<div>
|
|
644
|
+
<strong data-i18n="tab_inspector">Inspector de Proveedor</strong>
|
|
645
|
+
<p class="label-hint" style="margin-top: 0.2rem;" data-i18n="inspector_desc">
|
|
646
|
+
Analiza el endpoint configurado para determinar con precisión qué capacidades soporta, distinguiendo entre capacidades declaradas, inferidas y comprobadas mediante pruebas activas seguras.
|
|
647
|
+
</p>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
<button type="button" id="btn-run-inspector" class="btn-primary btn-inspector-run" data-i18n-title="btn_run_inspector" title="Ejecutar Diagnóstico de Capacidades" style="margin-top: 0.85rem; width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem; min-height: 40px; padding: 0.6rem;">
|
|
651
|
+
<span class="inspector-btn-icon">
|
|
652
|
+
<svg class="ui-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-zap"></use></svg>
|
|
653
|
+
</span>
|
|
654
|
+
<span class="inspector-btn-text" data-i18n="btn_diagnose_short">Diagnóstico</span>
|
|
655
|
+
</button>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
<div id="inspector-results" class="inspector-results-container" style="display: none; margin-top: 1rem;">
|
|
659
|
+
<!-- Renderizado dinámico de informe de capacidades -->
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
</form>`;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function renderProfileMenu(elements, profiles, activeId) {
|
|
667
|
+
const list = elements.activeProfileList;
|
|
668
|
+
if (!list) return;
|
|
669
|
+
const doc = list.ownerDocument || (typeof document !== 'undefined' ? document : null);
|
|
670
|
+
if (!doc) return;
|
|
671
|
+
const descriptionFor = profile => profile.id === 'profile:mirror' ? t('profile_mirror_description') : profile.description;
|
|
672
|
+
list.replaceChildren();
|
|
673
|
+
profiles.forEach(profile => {
|
|
674
|
+
const item = doc.createElement('div');
|
|
675
|
+
item.className = 'header-profile-item' + (profile.id === activeId ? ' active' : '');
|
|
676
|
+
|
|
677
|
+
const option = doc.createElement('button');
|
|
678
|
+
option.type = 'button';
|
|
679
|
+
option.className = 'header-profile-option' + (profile.id === activeId ? ' active' : '');
|
|
680
|
+
option.dataset.profileId = profile.id;
|
|
681
|
+
option.setAttribute('role', 'menuitemradio');
|
|
682
|
+
option.setAttribute('aria-checked', String(profile.id === activeId));
|
|
683
|
+
option.setAttribute('title', profile.name);
|
|
684
|
+
|
|
685
|
+
const name = doc.createElement('span');
|
|
686
|
+
name.className = 'header-profile-option-name';
|
|
687
|
+
name.textContent = profile.name;
|
|
688
|
+
option.appendChild(name);
|
|
689
|
+
|
|
690
|
+
const descText = descriptionFor(profile);
|
|
691
|
+
if (descText) {
|
|
692
|
+
const description = doc.createElement('span');
|
|
693
|
+
description.className = 'header-profile-option-description';
|
|
694
|
+
description.textContent = descText;
|
|
695
|
+
option.appendChild(description);
|
|
696
|
+
}
|
|
697
|
+
item.appendChild(option);
|
|
698
|
+
|
|
699
|
+
const actions = doc.createElement('div');
|
|
700
|
+
actions.className = 'header-profile-item-actions';
|
|
701
|
+
|
|
702
|
+
const editBtn = doc.createElement('button');
|
|
703
|
+
editBtn.type = 'button';
|
|
704
|
+
editBtn.className = 'btn-profile-item-action btn-profile-item-edit';
|
|
705
|
+
editBtn.dataset.profileAction = 'edit';
|
|
706
|
+
editBtn.dataset.targetId = profile.id;
|
|
707
|
+
const editTitle = t('btn_edit_profile_title') || 'Editar este perfil';
|
|
708
|
+
editBtn.title = editTitle;
|
|
709
|
+
editBtn.setAttribute('aria-label', `${editTitle}: ${profile.name}`);
|
|
710
|
+
editBtn.innerHTML = '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-edit"></use></svg>';
|
|
711
|
+
actions.appendChild(editBtn);
|
|
712
|
+
|
|
713
|
+
if (profile.id !== 'profile:mirror') {
|
|
714
|
+
const deleteBtn = doc.createElement('button');
|
|
715
|
+
deleteBtn.type = 'button';
|
|
716
|
+
deleteBtn.className = 'btn-profile-item-action btn-profile-item-delete';
|
|
717
|
+
deleteBtn.dataset.profileAction = 'delete';
|
|
718
|
+
deleteBtn.dataset.targetId = profile.id;
|
|
719
|
+
const deleteTitle = t('btn_delete_profile_item_title') || t('btn_delete_profile_title') || 'Eliminar este perfil';
|
|
720
|
+
deleteBtn.title = deleteTitle;
|
|
721
|
+
deleteBtn.setAttribute('aria-label', `${deleteTitle}: ${profile.name}`);
|
|
722
|
+
deleteBtn.innerHTML = '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-trash"></use></svg>';
|
|
723
|
+
actions.appendChild(deleteBtn);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
item.appendChild(actions);
|
|
727
|
+
list.appendChild(item);
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function syncProfileEditor(elements, readOnly, canSave = null) {
|
|
732
|
+
const locked = elements.profilesDialog?.dataset.profileLocked === 'true';
|
|
733
|
+
if (elements.btnDeleteProfile) elements.btnDeleteProfile.disabled = readOnly;
|
|
734
|
+
const saveAllowed = canSave !== null ? Boolean(canSave) : (elements.profilesDialog?.dataset.queryReady === 'true');
|
|
735
|
+
if (elements.btnSaveProfile) elements.btnSaveProfile.disabled = readOnly || locked || !saveAllowed;
|
|
736
|
+
syncApiKeyLock(elements, readOnly);
|
|
737
|
+
if (elements.profileSaveQueryHint) {
|
|
738
|
+
if (readOnly) {
|
|
739
|
+
elements.profileSaveQueryHint.textContent = t('err_profile_read_only');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function getProfilesDialogHTML() {
|
|
745
|
+
return `<div class="modal-header settings-section-header">
|
|
746
|
+
<div class="modal-title">
|
|
747
|
+
<h3 data-i18n="profiles_modal_title">Perfil</h3>
|
|
748
|
+
</div>
|
|
749
|
+
<div class="settings-header-actions">
|
|
750
|
+
<label id="api-key-lock-control" class="profile-lock-control" for="setting-api-key-locked"><input type="checkbox" id="setting-api-key-locked"><span data-i18n="profile_api_key_lock">Bloquear cambios</span></label>
|
|
751
|
+
<span id="api-key-locked-status" class="profile-lock-status" hidden data-i18n="profile_api_key_locked">Cambios bloqueados</span>
|
|
752
|
+
<button type="button" id="btn-save-profile" class="btn-primary btn-save-header" data-i18n-title="btn_save_profile_title" title="Guardar todos los valores actuales en este perfil" disabled>
|
|
753
|
+
<svg class="ui-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-save"></use></svg>
|
|
754
|
+
<span data-i18n="btn_save_short">Guardar</span>
|
|
755
|
+
</button>
|
|
756
|
+
<button id="btn-close-profiles" type="button" class="btn-close" data-i18n-aria="modal_close_aria" aria-label="Cerrar modal"><svg class="ui-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-close"></use></svg></button>
|
|
757
|
+
</div>
|
|
758
|
+
</div>
|
|
759
|
+
<form id="profiles-form" class="settings-form-wrapper">
|
|
760
|
+
<div class="modal-body">
|
|
761
|
+
<input type="hidden" id="profile-select-helper">
|
|
762
|
+
<div id="profile-action-feedback" class="server-query-status" style="display: none;"></div>
|
|
763
|
+
|
|
764
|
+
<div class="form-field"><label for="setting-profile-name"><strong data-i18n="field_profile_name_label">Nombre del Perfil</strong></label><input type="text" id="setting-profile-name" data-i18n-placeholder="field_profile_placeholder" placeholder="Nombre del perfil (ej: LM Studio, Ollama, OpenRouter...)" autocomplete="off"></div>
|
|
765
|
+
<div class="form-field"><label for="setting-profile-description"><strong data-i18n="field_profile_description_label">Descripción</strong></label><textarea id="setting-profile-description" rows="2" data-i18n-placeholder="field_profile_description_placeholder" placeholder="Describe brevemente este perfil..."></textarea></div>
|
|
766
|
+
|
|
767
|
+
<div class="form-field"><label for="setting-api-type"><strong data-i18n="field_api_type">Tipo de Interfaz / Protocolo</strong></label><select id="setting-api-type" class="combobox-select-helper" style="width: 100%; max-width: 100%;"><option value="openai" selected>OpenAI / LM Studio / LocalAI / vLLM (/v1)</option><option value="ollama">Ollama (/api/tags)</option><option value="openrouter">OpenRouter (/api/v1)</option><option value="claude">Anthropic Claude (/v1)</option><option value="gemini">Google Gemini (OpenAI compat)</option><option value="webllm">WebLLM (WebGPU local)</option><option value="mirror" data-i18n="profile_mirror_type">Espejo (sin red)</option></select></div>
|
|
768
|
+
<div class="form-field"><label for="setting-api-url"><strong data-i18n="field_api_url">URL del Servidor / Endpoint de Chat</strong></label><div class="input-with-button-wrapper"><input type="url" id="setting-api-url" placeholder="http://localhost:1234/v1" required><button type="button" id="btn-query-server" class="btn-query-server" data-i18n-title="btn_query_title" title="Consultar modelos disponibles y capacidades de la API en el servidor"><svg class="ui-icon query-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-search"></use></svg><span class="query-btn-text" data-i18n="btn_query_text">Query</span></button></div><div class="webllm-info-bar"><span class="label-hint webllm-local-hint" hidden data-i18n="webllm_local_hint">Ejecución local en este navegador.</span><a id="webllm-help-link" class="webllm-help-link" href="help/webllm.html" target="_blank" rel="noopener noreferrer" hidden><svg class="ui-icon" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-help-circle"></use></svg><span data-i18n="webllm_help_link">Guía de configuración WebLLM</span><svg class="ui-icon" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-external-link"></use></svg></a></div><div id="server-query-status" class="server-query-status" style="display: none;"></div><span id="profile-save-query-hint" class="label-hint" data-i18n="profile_save_changes_required">Realiza algún cambio en el perfil para habilitar el guardado.</span></div>
|
|
769
|
+
<div class="form-field api-key-field"><label for="setting-api-key"><strong data-i18n="field_api_key">Clave de API (API Key)</strong><span class="label-hint"><span id="api-key-hint-text" data-i18n="field_api_key_hint">Opcional si usas un servidor local (LM Studio / Ollama / LocalAI).</span> <a id="api-key-free-help-link" href="" target="_blank" rel="noopener noreferrer" data-i18n="field_api_key_free_help" hidden>Cómo obtener una API key gratuita</a></span></label><div class="input-password-wrapper"><input type="password" id="setting-api-key" placeholder="sk-..." autocomplete="off"><button type="button" id="btn-toggle-key" class="btn-toggle-visibility" data-i18n-title="btn_toggle_key_title" title="Mostrar/Ocultar clave"><svg class="ui-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-eye"></use></svg></button></div></div>
|
|
770
|
+
<div class="form-field"><label for="setting-model"><strong data-i18n="field_model">Nombre del Modelo</strong><span class="label-hint" data-i18n="field_model_hint">Selecciona de la lista del servidor o escribe cualquier nombre personalizado.</span></label><div class="combobox-wrapper"><input type="text" id="setting-model" list="model-datalist" data-i18n-placeholder="field_model_placeholder" placeholder="Escribe o pulsa Query para consultar modelos..." autocomplete="off"><select id="model-select-helper" class="combobox-select-helper" title="Seleccionar modelo de la lista"><option value="" disabled selected data-i18n="model_select_default">▾ Elegir modelo detectado...</option></select><datalist id="model-datalist"></datalist><button type="button" id="btn-webllm-params" class="btn-webllm-params" data-i18n-title="btn_webllm_params_title" title="Parámetros avanzados de rendimiento WebLLM" hidden><svg class="ui-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-settings"></use></svg></button></div><div id="webllm-params-panel" class="webllm-params-panel" hidden><div class="webllm-params-title"><svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"><use href="#icon-settings"></use></svg><span data-i18n="webllm_params_title">Parámetros de ejecución WebGPU</span></div><div class="webllm-params-grid"><div class="webllm-param-item"><label for="setting-webllm-context-window"><span data-i18n="field_webllm_context_window">Ventana de contexto (context_window_size)</span><select id="setting-webllm-context-window" class="combobox-select-helper"><option value="default" selected data-i18n="webllm_opt_default">Por defecto del modelo</option><option value="2048">2048 (2K)</option><option value="4096">4096 (4K)</option><option value="8192">8192 (8K)</option><option value="16384">16384 (16K)</option><option value="32768">32768 (32K)</option></select></label></div><div class="webllm-param-item"><label for="setting-webllm-prefill-chunk"><span data-i18n="field_webllm_prefill_chunk">Bloque de prefill (prefill_chunk_size)</span><select id="setting-webllm-prefill-chunk" class="combobox-select-helper"><option value="default" selected data-i18n="webllm_opt_default">Por defecto del modelo</option><option value="512">512</option><option value="1024">1024</option><option value="2048">2048</option><option value="4096">4096</option></select></label></div></div></div></div>
|
|
771
|
+
|
|
772
|
+
<div class="form-field">
|
|
773
|
+
<label for="setting-system-prompt"><strong data-i18n="field_system_prompt">Prompt del Sistema (Opcional)</strong><span class="label-hint" data-i18n="field_system_prompt_hint">Instrucciones base personalizadas que guían el comportamiento del asistente (opcional).</span></label>
|
|
774
|
+
<textarea id="setting-system-prompt" rows="4" data-i18n-placeholder="field_system_prompt_placeholder" placeholder="Escribe aquí tus instrucciones personalizadas para el modelo (opcional)..."></textarea>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="form-field" style="margin-bottom: 0;">
|
|
777
|
+
<label for="setting-temperature"><strong><span data-i18n="field_temperature">Temperatura</span>: <span id="temperature-val">0.7</span></strong><span class="label-hint" data-i18n="field_temperature_hint">Controla la creatividad de las respuestas (0 = determinista/preciso, 1 = creativo).</span></label>
|
|
778
|
+
<input type="range" id="setting-temperature" min="0" max="1.5" step="0.1" value="0.7">
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
</form>`;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function ensureDialogMarkup() {
|
|
785
|
+
if (typeof document === 'undefined') return;
|
|
786
|
+
const settingsDialog = document.getElementById('settings-dialog');
|
|
787
|
+
if (settingsDialog && !settingsDialog.firstElementChild) {
|
|
788
|
+
settingsDialog.innerHTML = getSettingsDialogHTML();
|
|
789
|
+
}
|
|
790
|
+
const profilesDialog = document.getElementById('profiles-dialog');
|
|
791
|
+
if (profilesDialog && !profilesDialog.firstElementChild) {
|
|
792
|
+
profilesDialog.innerHTML = getProfilesDialogHTML();
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (typeof document !== 'undefined') {
|
|
797
|
+
if (document.readyState === 'loading') {
|
|
798
|
+
document.addEventListener('DOMContentLoaded', ensureDialogMarkup);
|
|
799
|
+
} else {
|
|
800
|
+
ensureDialogMarkup();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
applyTheme,
|
|
806
|
+
renderProfileMenu,
|
|
807
|
+
syncProfileEditor,
|
|
808
|
+
applyLanguage,
|
|
809
|
+
renderAgentToolsUI,
|
|
810
|
+
gatherEnabledToolsFromUI,
|
|
811
|
+
applyProfileToForm,
|
|
812
|
+
syncProviderFields,
|
|
813
|
+
syncApiKeyLock,
|
|
814
|
+
gatherCurrentFormConfig,
|
|
815
|
+
showProfileFeedback,
|
|
816
|
+
openSettingsSection,
|
|
817
|
+
closeSettingsModal,
|
|
818
|
+
setSettingsFormDirty,
|
|
819
|
+
isSettingsFormDirty,
|
|
820
|
+
handleClearAllData,
|
|
821
|
+
ensureDialogMarkup,
|
|
822
|
+
getSettingsDialogHTML,
|
|
823
|
+
getProfilesDialogHTML
|
|
824
|
+
};
|
|
825
|
+
});
|