aboutsummaryrefslogtreecommitdiff
path: root/modules/core/module-loader.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-05-25 18:02:02 +0200
committerAgando <roduccomm@gmail.com>2026-05-25 18:02:02 +0200
commit4baa484b84c5184286cac2743575251454a5ee2b (patch)
treecb9002f93644f363d0f35831dc97e6492bbb0ed6 /modules/core/module-loader.js
auto: 2026-05-25_18:02 changes
Diffstat (limited to 'modules/core/module-loader.js')
-rw-r--r--modules/core/module-loader.js178
1 files changed, 178 insertions, 0 deletions
diff --git a/modules/core/module-loader.js b/modules/core/module-loader.js
new file mode 100644
index 0000000..850d627
--- /dev/null
+++ b/modules/core/module-loader.js
@@ -0,0 +1,178 @@
+(function() {
+ 'use strict';
+
+ const BASE = 'modules/core/';
+
+ const FOUNDATION = [
+ 'virtual-fs.js',
+ 'module-manager.js',
+ 'ollama-manager.js',
+ 'terminal.js'
+ ];
+
+ const BUILTIN = [
+ 'module-system.js',
+ 'module-fs.js',
+ 'module-modmgr.js',
+ 'module-ollama.js',
+ 'module-my.js',
+ 'module-searxng.js',
+ 'module-servermon.js',
+ 'module-ollamamodels.js',
+ 'module-config.js',
+ 'module-simplegui.js',
+ 'module-io.js',
+ 'module-dynamicprompts.js',
+ 'module-aiassist.js',
+ 'module-aiasm.js',
+ 'module-smartctx.js',
+ 'module-duckduckgo.js',
+ 'module-fetcher.js',
+ 'module-skills.js',
+ 'secret-vault.js',
+ 'module-chatstudio.js'
+ ];
+
+ function getSelectedModules() {
+ try {
+ const saved = localStorage.getItem('webx11_startup_modules');
+ if (!saved) return null;
+ const parsed = JSON.parse(saved);
+ if (Array.isArray(parsed)) return parsed;
+ } catch (e) {}
+ return null;
+ }
+
+ function isSelected(name, selected) {
+ if (!selected) return true;
+ return selected.includes(name);
+ }
+
+ function loadScript(url, filename) {
+ return new Promise((resolve) => {
+ const existing = document.querySelector(`script[src="${url}"]`);
+ if (existing) { resolve(); return; }
+ const el = document.createElement('script');
+ el.src = url + (url.includes('?') ? '&t=' : '?t=') + Date.now();
+ el.onload = () => resolve();
+ el.onerror = () => {
+ console.warn(`[ModuleLoader] Failed to load: ${filename || url} — continuing`);
+ // Log to terminal if available
+ if (typeof window !== 'undefined' && window.webx11 && window.webx11.log) {
+ window.webx11.log('WARN', 'Loader', 'Failed to load: ' + (filename || url));
+ }
+ resolve();
+ };
+ document.body.appendChild(el);
+ });
+ }
+
+ function setSplash(phase, current, total, filename) {
+ const fill = document.getElementById('splash-progress-fill');
+ const phaseEl = document.getElementById('splash-phase');
+ const fileEl = document.getElementById('splash-filename');
+ if (fill && total > 0) {
+ fill.style.width = Math.round((current / total) * 100) + '%';
+ }
+ if (phaseEl) phaseEl.textContent = phase;
+ if (fileEl) fileEl.textContent = filename || '';
+ }
+
+ function hideSplash() {
+ const overlay = document.getElementById('splash-overlay');
+ if (overlay) {
+ overlay.classList.add('hidden');
+ setTimeout(() => { overlay.style.display = 'none'; }, 350);
+ }
+ }
+
+ function showSplashError(msg) {
+ const el = document.getElementById('splash-error');
+ const fill = document.getElementById('splash-progress-fill');
+ if (el) { el.textContent = msg; el.classList.add('show'); }
+ if (fill) fill.style.background = 'var(--error-color)';
+ }
+
+ function reloadWithSelection() {
+ location.reload();
+ }
+
+ window.__moduleLoaderReload = reloadWithSelection;
+
+ const CACHE = '?v=18';
+
+ async function init() {
+ try {
+ const selected = getSelectedModules();
+ const total = FOUNDATION.length + BUILTIN.length;
+ let count = 0;
+
+ // Phase 1: Foundation (always load)
+ setSplash('Loading foundation layer (1/3)', 0, total, '');
+ for (const mod of FOUNDATION) {
+ count++;
+ setSplash('Loading foundation layer (1/3)', count, total, mod);
+ await loadScript(BASE + mod + CACHE, mod);
+ }
+
+ // Phase 2: Built-in modules (filtered)
+ setSplash('Loading built-in modules (2/3)', count, total, '');
+ for (const mod of BUILTIN) {
+ count++;
+ if (!isSelected(mod, selected)) {
+ console.log(`[ModuleLoader] Skipped: ${mod} (not in startup selection)`);
+ setSplash('Loading built-in modules (2/3)', count, total, '⏭ ' + mod);
+ await new Promise(r => setTimeout(r, 20));
+ continue;
+ }
+ setSplash('Loading built-in modules (2/3)', count, total, mod);
+ await loadScript(BASE + mod + CACHE, mod);
+ }
+
+ // Phase 3: Initialize terminal
+ setSplash('Initializing terminal (3/3)', total, total, '');
+ const webx11 = new WebX11Terminal();
+ window.webx11 = webx11;
+
+ // Auto-connect to Ollama after init
+ setTimeout(async () => {
+ try {
+ if (webx11.ollamaManager && !webx11.ollamaManager.connected) {
+ webx11.log('INFO', 'System', 'WebX11 v4.2 started');
+ webx11.log('INFO', 'System', 'Default model: ' + webx11.ollamaManager.currentModel);
+ await webx11.ollamaManager.connect();
+ if (typeof webx11.updateOllamaStatus === 'function') {
+ webx11.updateOllamaStatus();
+ }
+ webx11.print('🤖 Default model: ' + webx11.ollamaManager.currentModel, 'info');
+ }
+ } catch (e) {
+ // Silently ignore — user can manually connect
+ }
+ }, 500);
+
+ // Phase 4: Ext modules (filtered)
+ if (typeof webx11._loadExtModules === 'function') {
+ const extKeys = ['calc', 'voice', 'cron', 'theme', 'wpm', 'mindmap', 'webdav', 'skill-pack-core', 'editor', 'diff', 'alias', 'finder', 'filemgr'];
+ const extSelected = selected
+ ? extKeys.filter(k => isSelected(k + '.js', selected))
+ : extKeys;
+ if (extSelected.length > 0) {
+ webx11._loadExtModules(extSelected);
+ }
+ }
+
+ hideSplash();
+ console.log('[ModuleLoader] All modules loaded successfully');
+ } catch (err) {
+ console.error('[ModuleLoader] Fatal error:', err);
+ showSplashError('Loading failed — ' + err.message);
+ }
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();