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,804 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Módulo de Interfaz de Usuario para Consulta de Modelos e Inspector de Servidor.
|
|
3
|
+
* ZeroChat - js/ui-inspector.js
|
|
4
|
+
*/
|
|
5
|
+
(function (root, factory) {
|
|
6
|
+
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
7
|
+
module.exports = factory();
|
|
8
|
+
} else {
|
|
9
|
+
root.ChatUIInspector = factory();
|
|
10
|
+
}
|
|
11
|
+
})(typeof self !== 'undefined' ? self : this, function () {
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
let discoveredModels = [];
|
|
15
|
+
const MODEL_CACHE_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
function resolveDep(globalName, relPath) {
|
|
18
|
+
if (typeof window !== 'undefined' && window[globalName]) return window[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 getApi = () => resolveDep('ChatAPI', './api.js');
|
|
25
|
+
const getStorage = () => resolveDep('ChatStorage', './cookies.js');
|
|
26
|
+
const getMarkdown = () => resolveDep('ChatMarkdown', './markdown.js');
|
|
27
|
+
const getDebug = () => resolveDep('ChatDebug', './debug.js');
|
|
28
|
+
const getIcons = () => resolveDep('ChatIcons', './icons.js');
|
|
29
|
+
const getDialogs = () => resolveDep('ChatDialogs', './ui-dialogs.js');
|
|
30
|
+
const getState = () => resolveDep('ChatState', './state.js');
|
|
31
|
+
const getProviders = () => resolveDep('ChatProviders', './providers.js');
|
|
32
|
+
|
|
33
|
+
function t(key, params) {
|
|
34
|
+
const I18n = getI18n();
|
|
35
|
+
if (I18n && typeof I18n.t === 'function') return I18n.t(key, params);
|
|
36
|
+
return key;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function escapeHtml(str) {
|
|
40
|
+
const Markdown = getMarkdown();
|
|
41
|
+
if (Markdown && typeof Markdown.escapeHtml === 'function') {
|
|
42
|
+
return Markdown.escapeHtml(str);
|
|
43
|
+
}
|
|
44
|
+
return String(str || '').replace(/[&<>"']/g, (m) => {
|
|
45
|
+
switch (m) {
|
|
46
|
+
case '&': return '&';
|
|
47
|
+
case '<': return '<';
|
|
48
|
+
case '>': return '>';
|
|
49
|
+
case '"': return '"';
|
|
50
|
+
case "'": return ''';
|
|
51
|
+
default: return m;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function addDebugLog(type, text, rawData) {
|
|
57
|
+
const Debug = getDebug();
|
|
58
|
+
if (Debug && typeof Debug.addLog === 'function') {
|
|
59
|
+
Debug.addLog(type, text, rawData);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getWebLLMState() {
|
|
64
|
+
return getState()?.get?.('ui')?.webllm || { catalog: [], contextKey: '', operation: null };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function setWebLLMState(update) {
|
|
68
|
+
const State = getState();
|
|
69
|
+
if (!State?.set) return { ...getWebLLMState(), ...update };
|
|
70
|
+
State.set('ui', ui => ({
|
|
71
|
+
...ui,
|
|
72
|
+
webllm: { ...(ui.webllm || { catalog: [], contextKey: '', operation: null }), ...update }
|
|
73
|
+
}));
|
|
74
|
+
return getWebLLMState();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getEditorContextKey(elements) {
|
|
78
|
+
return [
|
|
79
|
+
elements?.profileSelectHelper?.value || '',
|
|
80
|
+
elements?.settingApiType?.value || '',
|
|
81
|
+
elements?.settingApiUrl?.value || ''
|
|
82
|
+
].join('|');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function updateWebLLMModel(modelId, status) {
|
|
86
|
+
const state = getWebLLMState();
|
|
87
|
+
const catalog = state.catalog.map(model => model?.id === modelId
|
|
88
|
+
? { ...model, details: { ...(model.details || {}), webllmCache: status } }
|
|
89
|
+
: model);
|
|
90
|
+
setWebLLMState({ catalog });
|
|
91
|
+
return catalog;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getOllamaConnectionHelp(apiType, error) {
|
|
95
|
+
const type = String(apiType || '').trim().toLowerCase();
|
|
96
|
+
const message = String(error?.message || error || '');
|
|
97
|
+
const isBrowserNetworkError = /networkerror|failed to fetch|load failed|network request failed/i.test(message);
|
|
98
|
+
return type === 'ollama' && isBrowserNetworkError ? t('err_ollama_origins') : '';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getConnectionCacheKey(connection = {}) {
|
|
102
|
+
const apiType = String(connection.apiType || 'openai').trim().toLowerCase();
|
|
103
|
+
const apiUrl = String(connection.apiUrl || '').trim().replace(/\/+$/, '').toLowerCase();
|
|
104
|
+
return apiUrl ? `${apiType}:${apiUrl}` : '';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getWebLLMCompletedModelIds() {
|
|
108
|
+
const Providers = getProviders();
|
|
109
|
+
const adapter = Providers?.registry?.get?.('webllm');
|
|
110
|
+
if (typeof adapter?.getCompletedModelIds === 'function') {
|
|
111
|
+
return adapter.getCompletedModelIds();
|
|
112
|
+
}
|
|
113
|
+
const WebLLM = (typeof globalThis !== 'undefined' && globalThis.ChatWebLLM) || resolveDep('ChatWebLLM', './providers-webllm.js');
|
|
114
|
+
return WebLLM?.getCompletedModelIds?.() || [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sortWebLLMModels(models) {
|
|
118
|
+
if (!Array.isArray(models)) return [];
|
|
119
|
+
const completed = new Set(getWebLLMCompletedModelIds());
|
|
120
|
+
return [...models].sort((a, b) => {
|
|
121
|
+
const aId = typeof a === 'string' ? a : (a?.id || a?.name || '');
|
|
122
|
+
const bId = typeof b === 'string' ? b : (b?.id || b?.name || '');
|
|
123
|
+
const aCached = (a?.details?.webllmCache === 'cached' || completed.has(aId)) ? 1 : 0;
|
|
124
|
+
const bCached = (b?.details?.webllmCache === 'cached' || completed.has(bId)) ? 1 : 0;
|
|
125
|
+
return bCached - aCached;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function loadCachedModels(elements, appConfig) {
|
|
130
|
+
discoveredModels = [];
|
|
131
|
+
try {
|
|
132
|
+
const Storage = getStorage();
|
|
133
|
+
const cached = Storage?.getStorageItem ? Storage.getStorageItem('cached_models') : null;
|
|
134
|
+
const apiType = String(appConfig?.apiType || elements?.settingApiType?.value || 'openai').trim().toLowerCase();
|
|
135
|
+
const isWebLLM = apiType === 'webllm';
|
|
136
|
+
const cacheKey = getConnectionCacheKey(appConfig || { apiType, apiUrl: elements?.settingApiUrl?.value });
|
|
137
|
+
|
|
138
|
+
if (cached) {
|
|
139
|
+
const document = JSON.parse(cached);
|
|
140
|
+
if (document?.version !== MODEL_CACHE_VERSION || !document.connections || typeof document.connections !== 'object') {
|
|
141
|
+
Storage?.deleteStorageItem?.('cached_models');
|
|
142
|
+
return discoveredModels;
|
|
143
|
+
}
|
|
144
|
+
discoveredModels = cacheKey ? (document.connections[cacheKey] || []) : [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isWebLLM) {
|
|
148
|
+
const completed = getWebLLMCompletedModelIds();
|
|
149
|
+
const existingIds = new Set(discoveredModels.map(m => typeof m === 'string' ? m : (m?.id || m?.name || '')));
|
|
150
|
+
completed.forEach(id => {
|
|
151
|
+
if (!existingIds.has(id)) {
|
|
152
|
+
discoveredModels.push({
|
|
153
|
+
id,
|
|
154
|
+
name: id,
|
|
155
|
+
details: {
|
|
156
|
+
webllmCache: 'cached',
|
|
157
|
+
loaded_context_length: 4096,
|
|
158
|
+
max_context_length: 4096
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
existingIds.add(id);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
discoveredModels = sortWebLLMModels(discoveredModels);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Array.isArray(discoveredModels) && discoveredModels.length > 0) {
|
|
168
|
+
populateModelList(elements, appConfig, discoveredModels, false);
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
discoveredModels = [];
|
|
172
|
+
console.warn('No se pudieron cargar modelos de caché:', e);
|
|
173
|
+
}
|
|
174
|
+
return discoveredModels;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function saveCachedModels(models, connection) {
|
|
178
|
+
discoveredModels = Array.isArray(models) ? models : [];
|
|
179
|
+
try {
|
|
180
|
+
const Storage = getStorage();
|
|
181
|
+
const cacheKey = getConnectionCacheKey(connection);
|
|
182
|
+
if (Storage?.setStorageItem && cacheKey) {
|
|
183
|
+
const cached = Storage.getStorageItem?.('cached_models');
|
|
184
|
+
let document = { version: MODEL_CACHE_VERSION, connections: {} };
|
|
185
|
+
try {
|
|
186
|
+
const parsed = cached ? JSON.parse(cached) : null;
|
|
187
|
+
if (parsed?.version === MODEL_CACHE_VERSION && parsed.connections && typeof parsed.connections === 'object') {
|
|
188
|
+
document = parsed;
|
|
189
|
+
}
|
|
190
|
+
} catch (_) {}
|
|
191
|
+
document.connections[cacheKey] = discoveredModels;
|
|
192
|
+
Storage.setStorageItem('cached_models', JSON.stringify(document));
|
|
193
|
+
}
|
|
194
|
+
} catch (e) {}
|
|
195
|
+
return discoveredModels;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getCachedModels() {
|
|
199
|
+
return discoveredModels;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getModelContextLimit(model) {
|
|
203
|
+
const selected = String(model || '').trim();
|
|
204
|
+
const entry = discoveredModels.find(item => String(item?.id || item?.name || '').trim() === selected);
|
|
205
|
+
const details = entry?.details || entry || {};
|
|
206
|
+
const loaded = Number(details.loaded_context_length);
|
|
207
|
+
const maximum = Number(details.max_context_length);
|
|
208
|
+
if (Number.isFinite(loaded) && loaded > 0) return Math.floor(loaded);
|
|
209
|
+
if (Number.isFinite(maximum) && maximum > 0) return Math.floor(maximum);
|
|
210
|
+
if (details.webllmCache || selected.includes('-MLC')) return 4096;
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function populateModelList(elements, appConfig, models, selectFirstIfEmpty = false) {
|
|
215
|
+
const apiType = String(elements?.settingApiType?.value || appConfig?.apiType || '').trim().toLowerCase();
|
|
216
|
+
let modelList = Array.isArray(models) ? models : [];
|
|
217
|
+
|
|
218
|
+
if (apiType === 'webllm') {
|
|
219
|
+
const completed = new Set(getWebLLMCompletedModelIds());
|
|
220
|
+
modelList = modelList.filter(m => {
|
|
221
|
+
const id = (typeof m === 'string' ? m : (m?.id || m?.name || '')).trim();
|
|
222
|
+
return m?.details?.webllmCache === 'cached' || completed.has(id);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const doc = elements?.modelDatalist?.ownerDocument || elements?.modelSelectHelper?.ownerDocument || (typeof document !== 'undefined' ? document : null);
|
|
227
|
+
if (!doc) return;
|
|
228
|
+
|
|
229
|
+
if (elements?.modelDatalist) {
|
|
230
|
+
elements.modelDatalist.innerHTML = '';
|
|
231
|
+
modelList.forEach(m => {
|
|
232
|
+
const id = (typeof m === 'string' ? m : (m.id || m.name || '')).trim();
|
|
233
|
+
if (id) {
|
|
234
|
+
const opt = doc.createElement('option');
|
|
235
|
+
opt.value = id;
|
|
236
|
+
const contextLimit = getModelContextLimit(id);
|
|
237
|
+
if (contextLimit) opt.dataset.contextLength = String(contextLimit);
|
|
238
|
+
elements.modelDatalist.appendChild(opt);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (elements?.modelSelectHelper) {
|
|
244
|
+
elements.modelSelectHelper.innerHTML = '';
|
|
245
|
+
const defaultOpt = doc.createElement('option');
|
|
246
|
+
defaultOpt.value = '';
|
|
247
|
+
defaultOpt.disabled = true;
|
|
248
|
+
defaultOpt.selected = true;
|
|
249
|
+
defaultOpt.textContent = t('model_select_count', { count: modelList.length });
|
|
250
|
+
elements.modelSelectHelper.appendChild(defaultOpt);
|
|
251
|
+
|
|
252
|
+
const currentVal = elements.settingModel ? elements.settingModel.value.trim() : (appConfig?.model || '');
|
|
253
|
+
|
|
254
|
+
modelList.forEach(m => {
|
|
255
|
+
const id = (typeof m === 'string' ? m : (m.id || m.name || '')).trim();
|
|
256
|
+
if (id) {
|
|
257
|
+
const opt = doc.createElement('option');
|
|
258
|
+
opt.value = id;
|
|
259
|
+
opt.textContent = id;
|
|
260
|
+
const contextLimit = getModelContextLimit(id);
|
|
261
|
+
if (contextLimit) opt.dataset.contextLength = String(contextLimit);
|
|
262
|
+
if (currentVal && currentVal === id) {
|
|
263
|
+
opt.selected = true;
|
|
264
|
+
defaultOpt.selected = false;
|
|
265
|
+
}
|
|
266
|
+
elements.modelSelectHelper.appendChild(opt);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (elements?.settingModel) {
|
|
272
|
+
const currentVal = elements.settingModel.value.trim();
|
|
273
|
+
const firstId = (typeof modelList[0] === 'string' ? modelList[0] : (modelList[0]?.id || modelList[0]?.name || '')).trim();
|
|
274
|
+
if (apiType === 'webllm') {
|
|
275
|
+
const isCurrentDownloaded = modelList.some(m => {
|
|
276
|
+
const id = (typeof m === 'string' ? m : (m?.id || m?.name || '')).trim();
|
|
277
|
+
return id === currentVal;
|
|
278
|
+
});
|
|
279
|
+
if (!isCurrentDownloaded) {
|
|
280
|
+
elements.settingModel.value = firstId || '';
|
|
281
|
+
if (elements.modelSelectHelper) elements.modelSelectHelper.value = firstId || '';
|
|
282
|
+
}
|
|
283
|
+
} else if (selectFirstIfEmpty && !currentVal && firstId) {
|
|
284
|
+
elements.settingModel.value = firstId;
|
|
285
|
+
if (elements.modelSelectHelper) elements.modelSelectHelper.value = firstId;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function renderWebLLMModels(elements, rawModels = getWebLLMState().catalog) {
|
|
291
|
+
const status = elements?.serverQueryStatus;
|
|
292
|
+
if (!status || !Array.isArray(rawModels)) return;
|
|
293
|
+
const models = sortWebLLMModels(rawModels);
|
|
294
|
+
const operation = getWebLLMState().operation;
|
|
295
|
+
status.querySelector?.('.webllm-model-list')?.remove();
|
|
296
|
+
const doc = status.ownerDocument;
|
|
297
|
+
const list = doc.createElement('div');
|
|
298
|
+
list.className = 'webllm-model-list';
|
|
299
|
+
models.forEach(model => {
|
|
300
|
+
const id = String(model?.id || '').trim();
|
|
301
|
+
if (!id) return;
|
|
302
|
+
const row = doc.createElement('div');
|
|
303
|
+
row.className = 'webllm-model-row';
|
|
304
|
+
const state = model?.details?.webllmCache;
|
|
305
|
+
const label = doc.createElement('span');
|
|
306
|
+
const stateLabel = text => `${id} · ${text}${formatWebLLMVram(model)}`;
|
|
307
|
+
const stateKey = state === 'cached' ? 'webllm_model_cached'
|
|
308
|
+
: (state === 'incomplete' ? 'webllm_model_incomplete' : (state === 'unknown' ? 'webllm_model_unknown' : 'webllm_model_missing'));
|
|
309
|
+
label.textContent = stateLabel(t(stateKey));
|
|
310
|
+
row.appendChild(label);
|
|
311
|
+
const progressBar = doc.createElement('progress');
|
|
312
|
+
progressBar.className = 'webllm-model-progress';
|
|
313
|
+
progressBar.max = 100;
|
|
314
|
+
progressBar.hidden = true;
|
|
315
|
+
row.appendChild(progressBar);
|
|
316
|
+
const createAction = (icon, labelKey, handler, disabled = false) => {
|
|
317
|
+
const button = doc.createElement('button');
|
|
318
|
+
button.type = 'button';
|
|
319
|
+
button.className = 'webllm-model-action';
|
|
320
|
+
const labelText = t(labelKey);
|
|
321
|
+
button.title = labelText;
|
|
322
|
+
button.setAttribute('aria-label', labelText);
|
|
323
|
+
button.innerHTML = getIcons()?.get?.(icon, { size: 14 }) || labelText;
|
|
324
|
+
button.disabled = disabled;
|
|
325
|
+
button.addEventListener('click', handler);
|
|
326
|
+
return button;
|
|
327
|
+
};
|
|
328
|
+
const actions = doc.createElement('div');
|
|
329
|
+
actions.className = 'webllm-model-actions';
|
|
330
|
+
const downloadModel = async event => {
|
|
331
|
+
const button = event.currentTarget;
|
|
332
|
+
const API = getApi();
|
|
333
|
+
const activeOperation = getWebLLMState().operation;
|
|
334
|
+
if (activeOperation?.modelId === id) {
|
|
335
|
+
button.disabled = true;
|
|
336
|
+
label.textContent = stateLabel(t('webllm_cancelling'));
|
|
337
|
+
await API.cancelLocalModelOperation?.(id, elements?.settingApiType?.value);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const contextKey = getEditorContextKey(elements);
|
|
341
|
+
const operationId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
342
|
+
setWebLLMState({ operation: { id: operationId, modelId: id, contextKey } });
|
|
343
|
+
list.querySelectorAll('button').forEach(action => { action.disabled = true; });
|
|
344
|
+
button.disabled = false;
|
|
345
|
+
button.title = t('webllm_cancel');
|
|
346
|
+
button.setAttribute('aria-label', t('webllm_cancel'));
|
|
347
|
+
button.innerHTML = getIcons()?.get?.('stop', { size: 14 }) || t('webllm_cancel');
|
|
348
|
+
label.textContent = stateLabel(t('webllm_preparing'));
|
|
349
|
+
progressBar.hidden = false;
|
|
350
|
+
progressBar.removeAttribute('value');
|
|
351
|
+
addDebugLog('system', `[WebLLM] Iniciando descarga/preparación de modelo ${id}`);
|
|
352
|
+
const update = progress => {
|
|
353
|
+
const current = getWebLLMState().operation;
|
|
354
|
+
if (current?.id !== operationId || getEditorContextKey(elements) !== contextKey) return;
|
|
355
|
+
const feedback = parseWebLLMProgress(progress);
|
|
356
|
+
label.textContent = stateLabel(feedback.text);
|
|
357
|
+
if (feedback.percent === null) progressBar.removeAttribute('value');
|
|
358
|
+
else progressBar.value = feedback.percent;
|
|
359
|
+
};
|
|
360
|
+
try {
|
|
361
|
+
const complete = await API.downloadLocalModel(id, update, elements?.settingApiType?.value);
|
|
362
|
+
if (!complete) throw new Error(t('webllm_model_incomplete'));
|
|
363
|
+
addDebugLog('system', `[WebLLM] Modelo ${id} descargado y preparado con éxito`);
|
|
364
|
+
if (getWebLLMState().operation?.id === operationId && getEditorContextKey(elements) === contextKey) {
|
|
365
|
+
const catalog = updateWebLLMModel(id, 'cached');
|
|
366
|
+
setWebLLMState({ operation: null });
|
|
367
|
+
refreshSelectableModels(elements, catalog);
|
|
368
|
+
renderWebLLMModels(elements, catalog);
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const errorMsg = formatWebLLMErrorMessage(error, 'webllm_model_incomplete');
|
|
372
|
+
addDebugLog('error', `[WebLLM] Error al preparar modelo ${id}: ${errorMsg}`);
|
|
373
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
374
|
+
console.error(`[WebLLM] Error al preparar modelo ${id}:`, error);
|
|
375
|
+
}
|
|
376
|
+
if (getWebLLMState().operation?.id === operationId && getEditorContextKey(elements) === contextKey) {
|
|
377
|
+
const catalog = updateWebLLMModel(id, 'incomplete');
|
|
378
|
+
setWebLLMState({ operation: null });
|
|
379
|
+
renderWebLLMModels(elements, catalog);
|
|
380
|
+
const currentRow = Array.from(elements.serverQueryStatus.querySelectorAll('.webllm-model-row'))
|
|
381
|
+
.find(item => item.querySelector('span')?.textContent?.startsWith(`${id} ·`));
|
|
382
|
+
if (currentRow) currentRow.querySelector('span').textContent = stateLabel(errorMsg);
|
|
383
|
+
}
|
|
384
|
+
} finally {
|
|
385
|
+
if (getWebLLMState().operation?.id === operationId) setWebLLMState({ operation: null });
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const deleteModel = async event => {
|
|
389
|
+
const button = event.currentTarget;
|
|
390
|
+
const Dialogs = getDialogs();
|
|
391
|
+
if (!Dialogs?.confirm || !await Dialogs.confirm(t('confirm_webllm_delete', { model: id }))) return;
|
|
392
|
+
const contextKey = getEditorContextKey(elements);
|
|
393
|
+
const operationId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
394
|
+
setWebLLMState({ operation: { id: operationId, modelId: id, contextKey } });
|
|
395
|
+
list.querySelectorAll('button').forEach(action => { action.disabled = true; });
|
|
396
|
+
button.dataset.loading = 'true';
|
|
397
|
+
button.innerHTML = getIcons()?.get?.('spinner', { size: 14 }) || t('webllm_deleting');
|
|
398
|
+
const API = getApi();
|
|
399
|
+
label.textContent = stateLabel(t('webllm_deleting'));
|
|
400
|
+
progressBar.hidden = false;
|
|
401
|
+
progressBar.removeAttribute('value');
|
|
402
|
+
addDebugLog('system', `[WebLLM] Eliminando modelo ${id} del almacenamiento local`);
|
|
403
|
+
try {
|
|
404
|
+
const removed = await API.deleteLocalModel(id, elements?.settingApiType?.value);
|
|
405
|
+
if (!removed) throw new Error(t('webllm_delete_failed'));
|
|
406
|
+
addDebugLog('system', `[WebLLM] Modelo ${id} eliminado con éxito`);
|
|
407
|
+
if (getWebLLMState().operation?.id === operationId && getEditorContextKey(elements) === contextKey) {
|
|
408
|
+
const catalog = updateWebLLMModel(id, 'missing');
|
|
409
|
+
setWebLLMState({ operation: null });
|
|
410
|
+
refreshSelectableModels(elements, catalog);
|
|
411
|
+
renderWebLLMModels(elements, catalog);
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
const errorMsg = formatWebLLMErrorMessage(error, 'webllm_delete_failed');
|
|
415
|
+
addDebugLog('error', `[WebLLM] Error al eliminar modelo ${id}: ${errorMsg}`);
|
|
416
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
417
|
+
console.error(`[WebLLM] Error al eliminar modelo ${id}:`, error);
|
|
418
|
+
}
|
|
419
|
+
if (getEditorContextKey(elements) === contextKey) {
|
|
420
|
+
label.textContent = stateLabel(errorMsg);
|
|
421
|
+
progressBar.hidden = true;
|
|
422
|
+
button.disabled = false;
|
|
423
|
+
delete button.dataset.loading;
|
|
424
|
+
}
|
|
425
|
+
} finally {
|
|
426
|
+
if (getWebLLMState().operation?.id === operationId) setWebLLMState({ operation: null });
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
const renderActions = status => {
|
|
430
|
+
if (status === 'cached') {
|
|
431
|
+
actions.replaceChildren(createAction('trash', 'webllm_delete', deleteModel, !!operation));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
actions.replaceChildren(
|
|
435
|
+
createAction('download', 'webllm_download', downloadModel, !!operation),
|
|
436
|
+
createAction('trash', 'webllm_delete', deleteModel, !!operation || status !== 'incomplete')
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
renderActions(state === 'cached' ? 'cached' : 'missing');
|
|
440
|
+
row.appendChild(actions);
|
|
441
|
+
list.appendChild(row);
|
|
442
|
+
});
|
|
443
|
+
status.appendChild(list);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function parseWebLLMProgress(progress) {
|
|
447
|
+
const rawText = String(progress?.detail || progress?.text || '').trim();
|
|
448
|
+
const percentMatch = rawText.match(/(\d+(?:\.\d+)?)%\s+completed/i);
|
|
449
|
+
const shaderMatch = rawText.match(/Loading GPU shader modules\s*\[(\d+)\/(\d+)\]/i);
|
|
450
|
+
const elapsedMatch = rawText.match(/(\d+(?:\.\d+)?)\s+secs?\s+elapsed/i);
|
|
451
|
+
const explicitPercent = progress?.percent === null || progress?.percent === undefined ? NaN : Number(progress.percent);
|
|
452
|
+
const percent = Number.isFinite(explicitPercent)
|
|
453
|
+
? Math.min(100, Math.max(0, explicitPercent))
|
|
454
|
+
: (percentMatch ? Math.min(100, Math.max(0, Number(percentMatch[1]))) : null);
|
|
455
|
+
const elapsed = elapsedMatch ? t('webllm_elapsed_seconds', { seconds: Math.round(Number(elapsedMatch[1])) }) : '';
|
|
456
|
+
const loadingMatch = rawText.match(/Loading model from cache\[(\d+)\/(\d+)\]/i);
|
|
457
|
+
const phase = progress?.phase || '';
|
|
458
|
+
const text = phase === 'starting' ? t('webllm_starting')
|
|
459
|
+
: (phase === 'ready' ? t('webllm_ready')
|
|
460
|
+
: (shaderMatch
|
|
461
|
+
? t('webllm_gpu_shaders', { current: shaderMatch[1], total: shaderMatch[2], percent: percent === null ? '' : ` · ${Math.round(percent)} %`, elapsed: elapsed ? ` · ${elapsed}` : '' })
|
|
462
|
+
: (loadingMatch ? t('webllm_loading_model', { current: loadingMatch[1], total: loadingMatch[2], percent: percent === null ? '' : ` · ${Math.round(percent)} %`, elapsed: elapsed ? ` · ${elapsed}` : '' })
|
|
463
|
+
: (phase === 'loading' ? t('webllm_loading_parameters') : t('webllm_preparing')))));
|
|
464
|
+
return { text, percent: Number.isFinite(percent) ? percent : null };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function formatWebLLMVram(model) {
|
|
468
|
+
const megabytes = Number(model?.details?.webllmVramMB);
|
|
469
|
+
if (!Number.isFinite(megabytes) || megabytes <= 0) return '';
|
|
470
|
+
const size = megabytes >= 1024
|
|
471
|
+
? `${(megabytes / 1024).toFixed(1).replace(/\.0$/, '')} GB`
|
|
472
|
+
: `${Math.round(megabytes)} MB`;
|
|
473
|
+
return ` · ${t('webllm_vram_required', { size })}`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function formatWebLLMErrorMessage(error, fallbackKey = 'webllm_model_incomplete') {
|
|
477
|
+
if (!error) return t(fallbackKey);
|
|
478
|
+
let msg = typeof error === 'string' ? error : (error.message || error.error?.message || error.error || '');
|
|
479
|
+
if (typeof msg === 'string') {
|
|
480
|
+
msg = msg.trim().replace(/^Error:\s*/i, '').trim();
|
|
481
|
+
}
|
|
482
|
+
if (msg && msg !== '[object Object]') return msg;
|
|
483
|
+
if (error && typeof error === 'object' && error.cause) {
|
|
484
|
+
const causeMsg = formatWebLLMErrorMessage(error.cause, '');
|
|
485
|
+
if (causeMsg && causeMsg !== '[object Object]') return causeMsg;
|
|
486
|
+
}
|
|
487
|
+
return t(fallbackKey);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function refreshSelectableModels(elements, models) {
|
|
491
|
+
const sorted = sortWebLLMModels(models);
|
|
492
|
+
const completed = new Set(getWebLLMCompletedModelIds());
|
|
493
|
+
const downloaded = sorted.filter(m => {
|
|
494
|
+
const id = typeof m === 'string' ? m : (m?.id || m?.name || '');
|
|
495
|
+
return m?.details?.webllmCache === 'cached' || completed.has(id);
|
|
496
|
+
});
|
|
497
|
+
if (elements?.settingModel && !downloaded.some(model => (typeof model === 'string' ? model : model?.id || model?.name) === elements.settingModel.value.trim())) {
|
|
498
|
+
if (downloaded.length > 0) {
|
|
499
|
+
elements.settingModel.value = typeof downloaded[0] === 'string' ? downloaded[0] : (downloaded[0]?.id || downloaded[0]?.name || '');
|
|
500
|
+
} else {
|
|
501
|
+
elements.settingModel.value = '';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
populateModelList(elements, null, downloaded, !elements?.settingModel?.value);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function handleQueryServer(elements, appConfig) {
|
|
508
|
+
if (!elements || !elements.btnQueryServer) return false;
|
|
509
|
+
|
|
510
|
+
let apiUrl = (elements.settingApiUrl ? elements.settingApiUrl.value : appConfig?.apiUrl || '').trim();
|
|
511
|
+
const apiKey = (elements.settingApiKey?.value || '').trim();
|
|
512
|
+
const apiType = (elements.settingApiType ? elements.settingApiType.value : appConfig?.apiType || 'openai').trim();
|
|
513
|
+
|
|
514
|
+
if (!apiUrl) {
|
|
515
|
+
const defaultEndpoint = getProviders()?.registry?.get?.(apiType)?.getConnectionConfig?.().endpoint || elements?.settingApiUrl?.placeholder;
|
|
516
|
+
if (defaultEndpoint) {
|
|
517
|
+
apiUrl = defaultEndpoint.trim();
|
|
518
|
+
if (elements?.settingApiUrl) {
|
|
519
|
+
elements.settingApiUrl.value = apiUrl;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (!apiUrl) {
|
|
525
|
+
if (elements.serverQueryStatus) {
|
|
526
|
+
elements.serverQueryStatus.style.display = 'block';
|
|
527
|
+
elements.serverQueryStatus.className = 'server-query-status status-error';
|
|
528
|
+
elements.serverQueryStatus.textContent = t('err_invalid_url');
|
|
529
|
+
}
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
elements.btnQueryServer.disabled = true;
|
|
534
|
+
elements.btnQueryServer.classList.add('loading');
|
|
535
|
+
const queryText = elements.btnQueryServer.querySelector('.query-btn-text');
|
|
536
|
+
if (queryText) queryText.textContent = t('btn_querying_text');
|
|
537
|
+
|
|
538
|
+
if (elements.serverQueryStatus) {
|
|
539
|
+
elements.serverQueryStatus.style.display = 'block';
|
|
540
|
+
elements.serverQueryStatus.className = 'server-query-status status-loading';
|
|
541
|
+
elements.serverQueryStatus.textContent = t('err_connecting_models', { url: apiUrl });
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
try {
|
|
545
|
+
const API = getApi();
|
|
546
|
+
if (!API?.fetchServerModels) {
|
|
547
|
+
throw new Error('API query function not available.');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
addDebugLog('network', `Consultando modelos en ${apiUrl} [${apiType}]`);
|
|
551
|
+
addDebugLog('raw', `>>> OUTGOING GET/POST ${apiUrl} (fetchServerModels)`);
|
|
552
|
+
const res = await API.fetchServerModels(apiUrl, apiKey, apiType);
|
|
553
|
+
addDebugLog('raw', `<<< INCOMING (fetchServerModels):\n${JSON.stringify(res, null, 2)}`);
|
|
554
|
+
|
|
555
|
+
if (res.success && res.models && res.models.length > 0) {
|
|
556
|
+
const locallyManaged = getProviders()?.registry?.get?.(apiType)?.getConnectionConfig?.().localModelManagement === true;
|
|
557
|
+
const sortedModels = locallyManaged ? sortWebLLMModels(res.models) : res.models;
|
|
558
|
+
saveCachedModels(sortedModels, { apiUrl, apiType });
|
|
559
|
+
if (locallyManaged) refreshSelectableModels(elements, sortedModels);
|
|
560
|
+
else populateModelList(elements, appConfig, sortedModels, true);
|
|
561
|
+
|
|
562
|
+
if (elements.serverQueryStatus) {
|
|
563
|
+
elements.serverQueryStatus.className = 'server-query-status status-success';
|
|
564
|
+
elements.serverQueryStatus.textContent = t('msg_models_success_text', { count: res.count, endpoint: res.endpoint });
|
|
565
|
+
if (locallyManaged) {
|
|
566
|
+
const contextKey = getEditorContextKey(elements);
|
|
567
|
+
const current = getWebLLMState();
|
|
568
|
+
setWebLLMState({
|
|
569
|
+
catalog: sortedModels,
|
|
570
|
+
contextKey,
|
|
571
|
+
operation: current.operation?.contextKey === contextKey ? current.operation : null
|
|
572
|
+
});
|
|
573
|
+
renderWebLLMModels(elements, sortedModels);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
} else {
|
|
578
|
+
throw new Error(res.error || 'Server did not return a valid models list.');
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
console.error('Error querying server models:', err);
|
|
582
|
+
if (elements.serverQueryStatus) {
|
|
583
|
+
elements.serverQueryStatus.className = 'server-query-status status-error';
|
|
584
|
+
const ollamaHelp = getOllamaConnectionHelp(apiType, err);
|
|
585
|
+
elements.serverQueryStatus.innerHTML = ollamaHelp || t('err_api_connect', { err: escapeHtml(err.message || String(err)) });
|
|
586
|
+
}
|
|
587
|
+
return false;
|
|
588
|
+
} finally {
|
|
589
|
+
elements.btnQueryServer.disabled = false;
|
|
590
|
+
elements.btnQueryServer.classList.remove('loading');
|
|
591
|
+
if (queryText) queryText.textContent = t('btn_query_text');
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function getBadgeClass(status) {
|
|
596
|
+
switch (status) {
|
|
597
|
+
case 'confirmed': return 'cap-badge cap-badge-confirmed';
|
|
598
|
+
case 'inferred': return 'cap-badge cap-badge-inferred';
|
|
599
|
+
case 'declared': return 'cap-badge cap-badge-declared';
|
|
600
|
+
case 'unsupported': return 'cap-badge cap-badge-unsupported';
|
|
601
|
+
default: return 'cap-badge cap-badge-unknown';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function getBadgeIcon(status) {
|
|
606
|
+
switch (status) {
|
|
607
|
+
case 'confirmed': return '✓';
|
|
608
|
+
case 'inferred': return '✦';
|
|
609
|
+
case 'declared': return 'ℹ';
|
|
610
|
+
case 'unsupported': return '✕';
|
|
611
|
+
default: return '?';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function getStatusLabel(status) {
|
|
616
|
+
switch (status) {
|
|
617
|
+
case 'confirmed': return t('inspector_status_confirmed');
|
|
618
|
+
case 'inferred': return t('inspector_status_inferred');
|
|
619
|
+
case 'declared': return t('inspector_status_declared');
|
|
620
|
+
case 'unsupported': return t('inspector_status_unsupported');
|
|
621
|
+
default: return t('inspector_status_unknown');
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function renderInspectorReport(elements, report) {
|
|
626
|
+
if (!elements || !elements.inspectorResults || !report) return;
|
|
627
|
+
|
|
628
|
+
if (report.success === false || report.connected === false) {
|
|
629
|
+
elements.inspectorResults.innerHTML = `
|
|
630
|
+
<div class="server-query-status status-error" style="display: block;">
|
|
631
|
+
${escapeHtml(report.error || t('inspector_conn_failed') || 'Fallo de conexión: No se pudo conectar con el servidor.')}
|
|
632
|
+
</div>
|
|
633
|
+
`;
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const p = report.provider || {};
|
|
638
|
+
const ep = report.endpoint || {};
|
|
639
|
+
const m = report.model || {};
|
|
640
|
+
const caps = report.capabilities || {};
|
|
641
|
+
|
|
642
|
+
const icons = getIcons();
|
|
643
|
+
const capIcons = {
|
|
644
|
+
streaming: icons?.get?.('radio', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.93 19.07a10 10 0 0 1 0-14.14"></path><path d="M7.76 16.24a6 6 0 0 1 0-8.48"></path><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.48"></path><path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path></svg>',
|
|
645
|
+
tools: icons?.get?.('settings', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>',
|
|
646
|
+
vision: icons?.get?.('eye', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>',
|
|
647
|
+
reasoning: icons?.get?.('brain', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 4.44-2.04zm5 0a2.5 2.5 0 0 0-2.5 2.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-4.44-2.04z"></path></svg>',
|
|
648
|
+
jsonMode: icons?.get?.('code', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>',
|
|
649
|
+
promptCaching: icons?.get?.('zap', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>',
|
|
650
|
+
embeddings: icons?.get?.('hash', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line></svg>',
|
|
651
|
+
modelListing: icons?.get?.('bot', { size: 14 }) || '<svg class="ui-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="12" x="3" y="10" rx="2"></rect><circle cx="12" cy="4" r="2"></circle><line x1="12" y1="6" x2="12" y2="10"></line><circle cx="8" cy="15" r="1"></circle><circle cx="16" cy="15" r="1"></circle></svg>'
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
const capKeys = [
|
|
655
|
+
{ key: 'streaming', title: t('inspector_cap_streaming'), icon: capIcons.streaming },
|
|
656
|
+
{ key: 'tools', title: t('inspector_cap_tools'), icon: capIcons.tools },
|
|
657
|
+
{ key: 'vision', title: t('inspector_cap_vision'), icon: capIcons.vision },
|
|
658
|
+
{ key: 'reasoning', title: t('inspector_cap_reasoning'), icon: capIcons.reasoning },
|
|
659
|
+
{ key: 'jsonMode', title: t('inspector_cap_jsonMode'), icon: capIcons.jsonMode },
|
|
660
|
+
{ key: 'promptCaching', title: t('inspector_cap_promptCaching'), icon: capIcons.promptCaching },
|
|
661
|
+
{ key: 'embeddings', title: t('inspector_cap_embeddings'), icon: capIcons.embeddings },
|
|
662
|
+
{ key: 'modelListing', title: t('inspector_cap_modelListing'), icon: capIcons.modelListing }
|
|
663
|
+
];
|
|
664
|
+
|
|
665
|
+
let cardsHtml = '';
|
|
666
|
+
capKeys.forEach(item => {
|
|
667
|
+
const c = caps[item.key] || { status: 'unknown', detail: '' };
|
|
668
|
+
const badgeCls = getBadgeClass(c.status);
|
|
669
|
+
const badgeIcon = getBadgeIcon(c.status);
|
|
670
|
+
const statusLabel = getStatusLabel(c.status);
|
|
671
|
+
|
|
672
|
+
cardsHtml += `
|
|
673
|
+
<div class="inspector-cap-card">
|
|
674
|
+
<div class="cap-card-header">
|
|
675
|
+
<span class="cap-card-title" style="display: inline-flex; align-items: center; gap: 0.35rem;">${item.icon} ${item.title}</span>
|
|
676
|
+
<span class="${badgeCls}">${badgeIcon} ${statusLabel}</span>
|
|
677
|
+
</div>
|
|
678
|
+
<div class="cap-card-detail">${escapeHtml(c.detail || '')}</div>
|
|
679
|
+
</div>
|
|
680
|
+
`;
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const modelInfoText = m.totalDiscovered > 0
|
|
684
|
+
? (t('inspector_discovered_models', { count: m.totalDiscovered }) || `${m.totalDiscovered} modelo(s) descubierto(s)`)
|
|
685
|
+
: (m.selected ? (t('inspector_model_selected', { model: m.selected }) || `Modelo: ${m.selected}`) : (t('inspector_no_models') || 'Sin modelos listados'));
|
|
686
|
+
|
|
687
|
+
const metaProvider = t('inspector_meta_provider') || 'Proveedor';
|
|
688
|
+
const metaEndpoint = t('inspector_meta_endpoint') || 'Endpoint Chat';
|
|
689
|
+
const metaModels = t('inspector_meta_models') || 'Modelos';
|
|
690
|
+
const metaLatency = t('inspector_meta_latency') || 'Latencia Diagnóstico';
|
|
691
|
+
const unknownText = t('inspector_unknown') || 'Desconocido';
|
|
692
|
+
|
|
693
|
+
elements.inspectorResults.innerHTML = `
|
|
694
|
+
<div class="inspector-header-meta">
|
|
695
|
+
<div class="inspector-meta-item">
|
|
696
|
+
<span class="meta-label">${escapeHtml(metaProvider)}</span>
|
|
697
|
+
<span class="meta-value">${escapeHtml(p.label || p.id || unknownText)}</span>
|
|
698
|
+
</div>
|
|
699
|
+
<div class="inspector-meta-item">
|
|
700
|
+
<span class="meta-label">${escapeHtml(metaEndpoint)}</span>
|
|
701
|
+
<span class="meta-value" style="font-family: monospace; font-size: 0.775rem;">${escapeHtml(ep.normalized || ep.raw || '')}</span>
|
|
702
|
+
</div>
|
|
703
|
+
<div class="inspector-meta-item">
|
|
704
|
+
<span class="meta-label">${escapeHtml(metaModels)}</span>
|
|
705
|
+
<span class="meta-value">${escapeHtml(modelInfoText)}</span>
|
|
706
|
+
</div>
|
|
707
|
+
<div class="inspector-meta-item">
|
|
708
|
+
<span class="meta-label">${escapeHtml(metaLatency)}</span>
|
|
709
|
+
<span class="meta-value">${report.inspectionTimeMs || 0} ms</span>
|
|
710
|
+
</div>
|
|
711
|
+
</div>
|
|
712
|
+
|
|
713
|
+
<div class="inspector-cap-grid">
|
|
714
|
+
${cardsHtml}
|
|
715
|
+
</div>
|
|
716
|
+
`;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async function handleRunInspector(elements, appConfig) {
|
|
720
|
+
if (!elements || !elements.btnRunInspector || !elements.inspectorResults) return;
|
|
721
|
+
|
|
722
|
+
const apiUrl = elements.settingApiUrl ? elements.settingApiUrl.value.trim() : (appConfig?.apiUrl || '');
|
|
723
|
+
const apiType = elements.settingApiType ? elements.settingApiType.value : (appConfig?.apiType || 'openai');
|
|
724
|
+
const apiKey = (elements.settingApiKey?.value || '').trim();
|
|
725
|
+
const model = elements.settingModel ? elements.settingModel.value.trim() : (appConfig?.model || '');
|
|
726
|
+
|
|
727
|
+
if (!apiUrl) {
|
|
728
|
+
if (elements.inspectorResults) {
|
|
729
|
+
elements.inspectorResults.style.display = 'block';
|
|
730
|
+
elements.inspectorResults.innerHTML = `
|
|
731
|
+
<div class="server-query-status status-error" style="display: block;">
|
|
732
|
+
${escapeHtml(t('err_invalid_url') || 'Por favor, introduce una URL de servidor válida.')}
|
|
733
|
+
</div>
|
|
734
|
+
`;
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
elements.btnRunInspector.disabled = true;
|
|
740
|
+
const btnText = elements.btnRunInspector.querySelector('.inspector-btn-text');
|
|
741
|
+
const originalText = btnText ? btnText.textContent : '';
|
|
742
|
+
if (btnText) btnText.textContent = t('btn_running_inspector');
|
|
743
|
+
|
|
744
|
+
elements.inspectorResults.style.display = 'block';
|
|
745
|
+
elements.inspectorResults.innerHTML = `
|
|
746
|
+
<div style="padding: 1.5rem; text-align: center; color: var(--text-muted);">
|
|
747
|
+
<span class="query-icon" style="display:inline-flex; align-items: center; justify-content: center; animation: spin 1s linear infinite;"><svg class="ui-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg></span>
|
|
748
|
+
<p style="margin-top: 0.5rem; font-size: 0.85rem;">${t('btn_running_inspector')}</p>
|
|
749
|
+
</div>
|
|
750
|
+
`;
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
const API = getApi();
|
|
754
|
+
if (!API?.inspectProvider) {
|
|
755
|
+
throw new Error(t('inspector_module_unavailable') || 'Módulo de inspección no disponible.');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
addDebugLog('network', `Ejecutando Provider Inspector en ${apiUrl} [${apiType}]`);
|
|
759
|
+
const report = await API.inspectProvider({ apiUrl, apiType, apiKey, model });
|
|
760
|
+
|
|
761
|
+
if (report && (report.success === false || report.connected === false)) {
|
|
762
|
+
throw new Error(report.error || t('inspector_conn_failed') || 'Fallo de conexión con el servidor.');
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
renderInspectorReport(elements, report);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
console.error('Error in Provider Inspector:', err);
|
|
768
|
+
const ollamaHelp = getOllamaConnectionHelp(apiType, err);
|
|
769
|
+
elements.inspectorResults.innerHTML = `
|
|
770
|
+
<div class="server-query-status status-error" style="display: block;">
|
|
771
|
+
${ollamaHelp || escapeHtml(err.message || String(err))}
|
|
772
|
+
</div>
|
|
773
|
+
`;
|
|
774
|
+
} finally {
|
|
775
|
+
elements.btnRunInspector.disabled = false;
|
|
776
|
+
if (btnText) btnText.textContent = originalText;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
loadCachedModels,
|
|
782
|
+
saveCachedModels,
|
|
783
|
+
getConnectionCacheKey,
|
|
784
|
+
getCachedModels,
|
|
785
|
+
getModelContextLimit,
|
|
786
|
+
populateModelList,
|
|
787
|
+
renderWebLLMModels,
|
|
788
|
+
getWebLLMState,
|
|
789
|
+
setWebLLMState,
|
|
790
|
+
updateWebLLMModel,
|
|
791
|
+
formatWebLLMVram,
|
|
792
|
+
formatWebLLMErrorMessage,
|
|
793
|
+
parseWebLLMProgress,
|
|
794
|
+
handleQueryServer,
|
|
795
|
+
handleRunInspector,
|
|
796
|
+
renderInspectorReport,
|
|
797
|
+
getOllamaConnectionHelp,
|
|
798
|
+
getBadgeClass,
|
|
799
|
+
getBadgeIcon,
|
|
800
|
+
getStatusLabel,
|
|
801
|
+
sortWebLLMModels,
|
|
802
|
+
getWebLLMCompletedModelIds
|
|
803
|
+
};
|
|
804
|
+
});
|