muxmind-ai 2.1.1
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.
- package/.env.example +11 -0
- package/README.md +81 -0
- package/assets/favicon.png +0 -0
- package/assets/muxmind-logo-full.png +0 -0
- package/assets/muxmind-logo-icon.png +0 -0
- package/bin/cli.js +98 -0
- package/index.html +293 -0
- package/package.json +47 -0
- package/server.js +265 -0
- package/src/api-manager.js +227 -0
- package/src/auth.js +159 -0
- package/src/config.js +267 -0
- package/src/file-parser.js +122 -0
- package/src/image-engine.js +73 -0
- package/src/router.js +317 -0
- package/src/tts-engine.js +88 -0
- package/src/ui-render.js +91 -0
- package/src-client/app.js +1306 -0
- package/src-client/i18n.js +229 -0
- package/style.css +948 -0
package/.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# MuxMind AI — Local Environment Configuration
|
|
2
|
+
# Copy this file to ".env" and adjust as needed.
|
|
3
|
+
# NOTE: Provider API keys are NOT set here — they are entered by each user
|
|
4
|
+
# directly into the in-browser API Vault (stored in localStorage only).
|
|
5
|
+
|
|
6
|
+
PORT=8080
|
|
7
|
+
|
|
8
|
+
# Set to "off" to disable Node cluster mode and run a single process
|
|
9
|
+
# (useful for debugging).
|
|
10
|
+
MUXMIND_CLUSTER=on
|
|
11
|
+
MUXMIND_PASSWORD=muxmind2026
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# MuxMind AI — v2.0
|
|
2
|
+
|
|
3
|
+
Multi-provider AI orchestration: 12 free/paid providers, a smart single-
|
|
4
|
+
model-per-reply router with automatic failover, persistent local chat
|
|
5
|
+
history, an image generation studio, full Arabic/English UI (with RTL),
|
|
6
|
+
in-app password management, and a multi-core Node.js backend.
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install
|
|
12
|
+
cp .env.example .env
|
|
13
|
+
npm start
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then open **http://localhost:8080**. First login uses password
|
|
17
|
+
`muxmind2026` unless you set `MUXMIND_PASSWORD` in `.env` before first
|
|
18
|
+
boot — either way, change it any time from **Settings → Change password**
|
|
19
|
+
once you're signed in.
|
|
20
|
+
|
|
21
|
+
## How It Works
|
|
22
|
+
|
|
23
|
+
1. **API Vault** — Click "🔐 API Vault" in the top bar. Add a provider
|
|
24
|
+
(OpenAI, Anthropic, Groq, Gemini, OpenRouter, Mistral, DeepSeek, xAI,
|
|
25
|
+
Cohere, Together AI, Cerebras, Perplexity), give it a label, paste your
|
|
26
|
+
key. The app immediately runs a live health-check (a real completion
|
|
27
|
+
probe, not just a listing call) and keeps only the models that actually
|
|
28
|
+
respond — the Smart Token Saver only ever routes to these proven-live
|
|
29
|
+
models, never a guessed one.
|
|
30
|
+
2. **Keys stay local** — Every key is stored exclusively in your browser's
|
|
31
|
+
`localStorage`. Nothing is written to disk on the server (except the
|
|
32
|
+
hashed login password, see below) and nothing is committed to the repo.
|
|
33
|
+
3. **One model per reply** — Each answer is streamed start-to-finish from a
|
|
34
|
+
single chosen model. If that model errors out *before* producing any
|
|
35
|
+
text, the router fails over cleanly to the next-best live candidate —
|
|
36
|
+
answers are never stitched together out of multiple models.
|
|
37
|
+
4. **Chat history** — Every conversation is auto-saved to the left sidebar
|
|
38
|
+
(local browser storage), searchable, renameable by first message, and
|
|
39
|
+
deletable individually or all at once from Settings.
|
|
40
|
+
5. **Image Studio** — Generate images from any OpenAI or Gemini key already
|
|
41
|
+
in your Vault, from the 🎨 rail icon.
|
|
42
|
+
6. **File attachments** — `.js .html .css .py .pdf .txt .png .jpg .json .md`
|
|
43
|
+
are supported via the 📎 button; click any attached file chip (before or
|
|
44
|
+
after sending) to open it in the in-app viewer.
|
|
45
|
+
7. **Language** — Toggle English/Arabic from Settings; Arabic switches the
|
|
46
|
+
whole UI to RTL.
|
|
47
|
+
8. **TTS** — Cycle the 🔇/🔊 button to hear responses read aloud in English
|
|
48
|
+
or Arabic via the browser's Web Speech API.
|
|
49
|
+
|
|
50
|
+
## Project Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
muxmind-ai/
|
|
54
|
+
├── package.json
|
|
55
|
+
├── server.js # Clustered Express backend
|
|
56
|
+
├── index.html # App shell markup
|
|
57
|
+
├── style.css # Glassmorphism styling (dark/light/RTL)
|
|
58
|
+
├── src-client/
|
|
59
|
+
│ ├── app.js # Client application logic
|
|
60
|
+
│ └── i18n.js # EN/AR dictionary + RTL switcher
|
|
61
|
+
└── src/
|
|
62
|
+
├── config.js # Provider catalog & fallback chain
|
|
63
|
+
├── api-manager.js # Vault health-check logic
|
|
64
|
+
├── router.js # Single-model-per-reply routing + failover
|
|
65
|
+
├── image-engine.js # Image generation (OpenAI/Gemini)
|
|
66
|
+
├── auth.js # Login + in-app password change (hashed)
|
|
67
|
+
├── tts-engine.js # Speech job builder (AR/EN)
|
|
68
|
+
├── file-parser.js # Upload parsing/validation
|
|
69
|
+
└── ui-render.js # Task tracker + SVG status icon helpers
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- The server never reads provider keys from `process.env` for chat
|
|
75
|
+
requests — only `PORT`, `MUXMIND_CLUSTER`, and the *initial* login
|
|
76
|
+
password seed are read from `.env`.
|
|
77
|
+
- The login password is stored as a salted hash in a local, gitignored
|
|
78
|
+
`.muxmind-auth.json` file, created on first boot. Changing it from
|
|
79
|
+
Settings updates that file directly — no restart needed.
|
|
80
|
+
- Set `MUXMIND_CLUSTER=off` in `.env` to run a single process while
|
|
81
|
+
debugging.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MuxMind AI — CLI launcher
|
|
4
|
+
* Entry point when the package is installed globally and run as
|
|
5
|
+
* `muxmind-ai` (or via `npx muxmind-ai`). Starts the local server and
|
|
6
|
+
* opens the app in the user's default browser.
|
|
7
|
+
*
|
|
8
|
+
* Flags:
|
|
9
|
+
* --port <n> Port to listen on (default 8080, or $PORT)
|
|
10
|
+
* --no-open Don't auto-open the browser
|
|
11
|
+
* --cluster Use one worker process per CPU core (default: off,
|
|
12
|
+
* single process — simplest for a personal/local tool)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { spawn } = require('child_process');
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
|
|
22
|
+
function getFlagValue(flag) {
|
|
23
|
+
const idx = args.indexOf(flag);
|
|
24
|
+
if (idx === -1) return null;
|
|
25
|
+
return args[idx + 1];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const port = getFlagValue('--port') || process.env.PORT || '8080';
|
|
29
|
+
const shouldOpen = !args.includes('--no-open');
|
|
30
|
+
const useCluster = args.includes('--cluster');
|
|
31
|
+
|
|
32
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
33
|
+
console.log(`
|
|
34
|
+
MuxMind AI — Multi-Provider AI Orchestration Engine
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
muxmind-ai [options]
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--port <n> Port to run on (default: 8080)
|
|
41
|
+
--no-open Don't automatically open your browser
|
|
42
|
+
--cluster Run one worker per CPU core (default: single process)
|
|
43
|
+
-h, --help Show this help message
|
|
44
|
+
|
|
45
|
+
On first run, a login screen appears. Default password: muxmind2026
|
|
46
|
+
(set MUXMIND_PASSWORD in your environment before first run to choose your
|
|
47
|
+
own, or change it later from the in-app Settings page.)
|
|
48
|
+
`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const serverPath = path.join(__dirname, '..', 'server.js');
|
|
53
|
+
|
|
54
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
env: {
|
|
57
|
+
...process.env,
|
|
58
|
+
PORT: port,
|
|
59
|
+
MUXMIND_CLUSTER: useCluster ? 'on' : 'off',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const url = `http://localhost:${port}`;
|
|
64
|
+
|
|
65
|
+
if (shouldOpen) {
|
|
66
|
+
// Give the server a moment to bind before opening the browser.
|
|
67
|
+
setTimeout(() => openBrowser(url), 900);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`\n[MuxMind AI] Starting on ${url} ...\n`);
|
|
71
|
+
|
|
72
|
+
function openBrowser(target) {
|
|
73
|
+
const platform = process.platform;
|
|
74
|
+
let cmd, cmdArgs;
|
|
75
|
+
|
|
76
|
+
if (platform === 'darwin') {
|
|
77
|
+
cmd = 'open';
|
|
78
|
+
cmdArgs = [target];
|
|
79
|
+
} else if (platform === 'win32') {
|
|
80
|
+
cmd = 'cmd';
|
|
81
|
+
cmdArgs = ['/c', 'start', '""', target];
|
|
82
|
+
} else {
|
|
83
|
+
cmd = 'xdg-open';
|
|
84
|
+
cmdArgs = [target];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
spawn(cmd, cmdArgs, { stdio: 'ignore', detached: true }).unref();
|
|
89
|
+
} catch {
|
|
90
|
+
// Non-fatal — just means the user opens the URL manually.
|
|
91
|
+
console.log(`[MuxMind AI] Open ${target} in your browser to continue.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
96
|
+
|
|
97
|
+
process.on('SIGINT', () => { child.kill('SIGINT'); });
|
|
98
|
+
process.on('SIGTERM', () => { child.kill('SIGTERM'); });
|
package/index.html
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" dir="ltr">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>MuxMind AI — Enterprise Orchestration Engine</title>
|
|
7
|
+
<link rel="icon" type="image/png" href="assets/favicon.png" />
|
|
8
|
+
<link rel="stylesheet" href="style.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
|
|
12
|
+
<!-- ===================== LOGIN SCREEN ===================== -->
|
|
13
|
+
<div class="login-screen" id="login-screen">
|
|
14
|
+
<div class="login-card">
|
|
15
|
+
<div class="logo-wrap logo-chip" id="login-logo" style="width:72px;height:72px;padding:12px;"></div>
|
|
16
|
+
<h1 data-i18n="login.title">MuxMind AI</h1>
|
|
17
|
+
<p data-i18n="login.subtitle">Enterprise Orchestration Engine — sign in to continue</p>
|
|
18
|
+
<form class="login-form" id="login-form">
|
|
19
|
+
<input type="password" id="login-password" data-i18n-placeholder="login.password" placeholder="Password" autocomplete="current-password" required />
|
|
20
|
+
<button type="submit" data-i18n="login.unlock">Unlock</button>
|
|
21
|
+
<div class="login-error" id="login-error"></div>
|
|
22
|
+
</form>
|
|
23
|
+
<div class="login-hint" data-i18n="login.hint">
|
|
24
|
+
First run only: default password is <strong>muxmind2026</strong>. Change it any time from Settings once signed in.
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<!-- ===================== APP SHELL ===================== -->
|
|
30
|
+
<div class="app-shell hidden" id="app-shell">
|
|
31
|
+
|
|
32
|
+
<!-- ===================== LEFT ICON RAIL ===================== -->
|
|
33
|
+
<nav class="rail">
|
|
34
|
+
<div class="rail-logo logo-chip" id="rail-logo" style="width:34px;height:34px;padding:5px;"></div>
|
|
35
|
+
<button class="rail-btn active" id="rail-chat" title="Chat" data-page="chat" data-i18n-title="rail.chat">💬</button>
|
|
36
|
+
<button class="rail-btn" id="rail-image" title="Image Studio" data-page="image" data-i18n-title="rail.image">🎨</button>
|
|
37
|
+
<button class="rail-btn" id="rail-providers" title="Providers" data-page="providers" data-i18n-title="rail.providers">🔌</button>
|
|
38
|
+
<button class="rail-btn" id="rail-map" title="Network Map" data-page="map" data-i18n-title="rail.map">🗺️</button>
|
|
39
|
+
<div class="rail-spacer"></div>
|
|
40
|
+
<button class="rail-btn" id="rail-settings" title="Settings" data-page="settings" data-i18n-title="rail.settings">⚙️</button>
|
|
41
|
+
<button class="rail-btn" id="rail-logout" title="Log out" data-i18n-title="rail.logout">🔒</button>
|
|
42
|
+
</nav>
|
|
43
|
+
|
|
44
|
+
<!-- ===================== CHAT HISTORY RAIL ===================== -->
|
|
45
|
+
<aside class="history-rail" id="history-rail">
|
|
46
|
+
<div class="history-header">
|
|
47
|
+
<h3 data-i18n="history.title">Chats</h3>
|
|
48
|
+
</div>
|
|
49
|
+
<div style="padding:0 12px 8px;">
|
|
50
|
+
<button class="btn-new-chat" id="btn-new-chat">➕ <span data-i18n="history.new">New chat</span></button>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="history-search">
|
|
53
|
+
<input type="text" id="history-search-input" data-i18n-placeholder="history.search" placeholder="Search chats..." />
|
|
54
|
+
</div>
|
|
55
|
+
<div class="history-list" id="history-list"></div>
|
|
56
|
+
</aside>
|
|
57
|
+
|
|
58
|
+
<div class="page-container">
|
|
59
|
+
|
|
60
|
+
<!-- ===================== TOP NAVIGATION BAR ===================== -->
|
|
61
|
+
<header class="top-nav">
|
|
62
|
+
<div class="brand" style="display:flex;align-items:center;gap:8px;">
|
|
63
|
+
<button class="btn-toggle-history" id="btn-toggle-history" title="Toggle chat list">☰</button>
|
|
64
|
+
MuxMind AI <span style="color:var(--text-muted); font-weight:400; font-size:0.75rem;">v2.0</span>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="nav-controls">
|
|
68
|
+
<select id="model-selector-dropdown" title="Model / Routing Mode">
|
|
69
|
+
<option value="auto" data-i18n="nav.smartRouting">🧠 Smart Auto-Routing</option>
|
|
70
|
+
</select>
|
|
71
|
+
|
|
72
|
+
<button id="btn-tts-toggle" class="nav-btn" title="Text-to-Speech" data-state="mute">🔇 Mute</button>
|
|
73
|
+
|
|
74
|
+
<span id="status-badge">[Engine: Idle | Savings: 0%]</span>
|
|
75
|
+
|
|
76
|
+
<button id="btn-api-manager" class="nav-btn">🔐 <span data-i18n="nav.vault">API Vault</span></button>
|
|
77
|
+
<button id="btn-clear-chat" class="icon-btn" title="Clear chat">🗑️</button>
|
|
78
|
+
</div>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<!-- ===================== CHAT PAGE ===================== -->
|
|
82
|
+
<div class="page-view active" id="page-chat">
|
|
83
|
+
<div class="chat-area">
|
|
84
|
+
<div id="chat-messages" class="chat-messages">
|
|
85
|
+
<div class="empty-state" id="empty-state">
|
|
86
|
+
<div class="glow-title">MuxMind AI</div>
|
|
87
|
+
<p data-i18n="chat.emptyHint">Configure your provider keys in the API Vault, then start chatting.<br/>
|
|
88
|
+
The smart router picks the best model for each reply and fails over automatically if a provider goes down.</p>
|
|
89
|
+
</div>
|
|
90
|
+
<button class="jump-latest" id="jump-latest" data-i18n="chat.jumpLatest">↓ New messages</button>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="input-toolbar">
|
|
94
|
+
<div class="file-chip-row" id="file-chip-row"></div>
|
|
95
|
+
|
|
96
|
+
<div class="input-wrap">
|
|
97
|
+
<div class="toolbar-icons">
|
|
98
|
+
<button id="btn-attach" title="Attach file">📎</button>
|
|
99
|
+
<input type="file" id="file-upload-input" multiple hidden
|
|
100
|
+
accept=".js,.html,.css,.py,.pdf,.txt,.png,.jpg,.jpeg,.json,.md" />
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<textarea id="user-prompt-input" rows="1" data-i18n-placeholder="chat.placeholder" placeholder="Ask MuxMind anything..."></textarea>
|
|
104
|
+
|
|
105
|
+
<div class="toolbar-icons">
|
|
106
|
+
<button id="btn-mic" title="Voice input" data-state="idle">🎤</button>
|
|
107
|
+
<button id="btn-stop" title="Stop generation">⏹️</button>
|
|
108
|
+
<button id="btn-send" title="Send">➤</button>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div class="compression-row">
|
|
113
|
+
<span data-i18n="chat.tokenSaver">Smart Token Saver</span>
|
|
114
|
+
<input type="range" id="token-compression-slider" min="0" max="99" value="50" step="1" />
|
|
115
|
+
<span id="compression-value">Balanced (50%)</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<!-- ===================== IMAGE STUDIO PAGE ===================== -->
|
|
122
|
+
<div class="page-view" id="page-image">
|
|
123
|
+
<div class="page-scroll">
|
|
124
|
+
<div class="page-header">
|
|
125
|
+
<h1 data-i18n="image.title">🎨 Image Studio</h1>
|
|
126
|
+
<p data-i18n="image.subtitle">Generate images using an OpenAI or Gemini key from your Vault.</p>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="imagegen-panel">
|
|
129
|
+
<form class="imagegen-form" id="imagegen-form">
|
|
130
|
+
<select id="imagegen-provider"></select>
|
|
131
|
+
<textarea id="imagegen-prompt" data-i18n-placeholder="image.promptPlaceholder" placeholder="Describe the image you want..." rows="1"></textarea>
|
|
132
|
+
<button type="submit" id="imagegen-submit" data-i18n="image.generate">Generate</button>
|
|
133
|
+
</form>
|
|
134
|
+
<div id="imagegen-results" class="imagegen-results"></div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
<!-- ===================== PROVIDERS PAGE ===================== -->
|
|
140
|
+
<div class="page-view" id="page-providers">
|
|
141
|
+
<div class="page-scroll">
|
|
142
|
+
<div class="page-header">
|
|
143
|
+
<h1 data-i18n="providers.title">🔌 Providers</h1>
|
|
144
|
+
<p data-i18n="providers.subtitle">Where to get an API key for each provider MuxMind can orchestrate — free and paid. Add the key in the Vault once you have it.</p>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="provider-grid" id="provider-grid"></div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- ===================== NETWORK MAP PAGE ===================== -->
|
|
151
|
+
<div class="page-view" id="page-map">
|
|
152
|
+
<div class="map-page">
|
|
153
|
+
<div class="map-canvas-wrap" id="map-canvas-wrap">
|
|
154
|
+
<div id="map-logo-host" style="position:absolute; z-index:2;"></div>
|
|
155
|
+
<svg id="map-svg" width="100%" height="100%" style="position:absolute; inset:0;"></svg>
|
|
156
|
+
<div class="map-empty-state" id="map-empty-state">
|
|
157
|
+
<div class="map-empty-icon" id="map-empty-logo"></div>
|
|
158
|
+
<div data-i18n="map.empty">No providers connected yet. Add a key in the API Vault and it will appear here automatically.</div>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="map-legend" id="map-legend" style="display:none;">
|
|
161
|
+
<div class="map-legend-item"><span class="map-legend-dot" style="background:var(--accent-green);"></span> <span data-i18n="map.active">Active provider</span></div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- ===================== SETTINGS PAGE ===================== -->
|
|
168
|
+
<div class="page-view" id="page-settings">
|
|
169
|
+
<div class="page-scroll">
|
|
170
|
+
<div class="page-header">
|
|
171
|
+
<h1 data-i18n="settings.title">⚙️ Settings</h1>
|
|
172
|
+
<p data-i18n="settings.subtitle">Appearance, language, security, and session preferences.</p>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="settings-section">
|
|
176
|
+
<h3 data-i18n="settings.appearance">Appearance</h3>
|
|
177
|
+
<div class="settings-row">
|
|
178
|
+
<div class="settings-row-label"><strong data-i18n="settings.theme">Theme</strong><span data-i18n="settings.themeDesc">Switch between dark and light interface.</span></div>
|
|
179
|
+
<div class="theme-toggle" id="theme-toggle">
|
|
180
|
+
<button data-theme-choice="dark" id="theme-btn-dark">🌙 <span data-i18n="settings.dark">Dark</span></button>
|
|
181
|
+
<button data-theme-choice="light" id="theme-btn-light">☀️ <span data-i18n="settings.light">Light</span></button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="settings-row">
|
|
185
|
+
<div class="settings-row-label"><strong data-i18n="settings.language">Language</strong><span data-i18n="settings.languageDesc">Switch the interface language.</span></div>
|
|
186
|
+
<div class="lang-toggle" id="lang-toggle">
|
|
187
|
+
<button data-lang-choice="en" id="lang-btn-en">English</button>
|
|
188
|
+
<button data-lang-choice="ar" id="lang-btn-ar">العربية</button>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div class="settings-section">
|
|
194
|
+
<h3 data-i18n="settings.security">Security</h3>
|
|
195
|
+
<div class="settings-row">
|
|
196
|
+
<div class="settings-row-label"><strong data-i18n="settings.session">Session</strong><span data-i18n="settings.sessionDesc">You're currently signed in on this device.</span></div>
|
|
197
|
+
<button class="settings-btn danger" id="btn-settings-logout" data-i18n="rail.logout">Log out</button>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="settings-row" style="flex-direction:column; align-items:flex-start;">
|
|
200
|
+
<div class="settings-row-label"><strong data-i18n="settings.changePassword">Change password</strong><span data-i18n="settings.changePasswordDesc">Update your login password right here — no server restart needed.</span></div>
|
|
201
|
+
<form class="password-form" id="password-form">
|
|
202
|
+
<input type="password" id="pw-current" data-i18n-placeholder="settings.currentPassword" placeholder="Current password" autocomplete="current-password" required />
|
|
203
|
+
<input type="password" id="pw-new" data-i18n-placeholder="settings.newPassword" placeholder="New password (min 6 chars)" autocomplete="new-password" required minlength="6" />
|
|
204
|
+
<input type="password" id="pw-confirm" data-i18n-placeholder="settings.confirmPassword" placeholder="Confirm new password" autocomplete="new-password" required minlength="6" />
|
|
205
|
+
<button type="submit" data-i18n="settings.updatePassword">Update Password</button>
|
|
206
|
+
<div class="form-msg" id="pw-form-msg"></div>
|
|
207
|
+
</form>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="settings-row">
|
|
210
|
+
<div class="settings-row-label"><strong data-i18n="settings.clearVault">Clear local vault</strong><span data-i18n="settings.clearVaultDesc">Removes all saved provider keys from this browser.</span></div>
|
|
211
|
+
<button class="settings-btn danger" id="btn-clear-vault" data-i18n="settings.clearVaultBtn">Clear Vault</button>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="settings-row">
|
|
214
|
+
<div class="settings-row-label"><strong data-i18n="settings.clearHistory">Clear chat history</strong><span data-i18n="settings.clearHistoryDesc">Deletes all saved conversations from this browser.</span></div>
|
|
215
|
+
<button class="settings-btn danger" id="btn-clear-history" data-i18n="settings.clearHistoryBtn">Clear History</button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div class="settings-section">
|
|
220
|
+
<h3 data-i18n="settings.smartRouter">Smart Router</h3>
|
|
221
|
+
<div class="settings-row">
|
|
222
|
+
<div class="settings-row-label"><strong data-i18n="settings.singleModel">Single model per reply</strong><span data-i18n="settings.singleModelDesc">Each answer is generated by one model start-to-finish — never a mix of models in the same reply.</span></div>
|
|
223
|
+
<span style="color:var(--accent-green); font-size:0.8rem;">● <span data-i18n="settings.enabled">Enabled</span></span>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="settings-row">
|
|
226
|
+
<div class="settings-row-label"><strong data-i18n="settings.autoTier">Auto tier-matching</strong><span data-i18n="settings.autoTierDesc">Router estimates task complexity and prefers a matching model tier automatically.</span></div>
|
|
227
|
+
<span style="color:var(--accent-green); font-size:0.8rem;">● <span data-i18n="settings.enabled">Enabled</span></span>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="settings-row">
|
|
230
|
+
<div class="settings-row-label"><strong data-i18n="settings.liveOnly">Live models only</strong><span data-i18n="settings.liveOnlyDesc">Only keys that pass a real completion probe are ever used to route a request.</span></div>
|
|
231
|
+
<span style="color:var(--accent-green); font-size:0.8rem;">● <span data-i18n="settings.enabled">Enabled</span></span>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="settings-row">
|
|
234
|
+
<div class="settings-row-label"><strong data-i18n="settings.adaptiveFallback">Adaptive fallback</strong><span data-i18n="settings.adaptiveFallbackDesc">If a model fails before producing output, MuxMind fails over to the next best live model.</span></div>
|
|
235
|
+
<span style="color:var(--accent-green); font-size:0.8rem;">● <span data-i18n="settings.enabled">Enabled</span></span>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<!-- ===================== SIDE VAULT PANEL ===================== -->
|
|
244
|
+
<div class="vault-overlay" id="vault-overlay"></div>
|
|
245
|
+
<aside class="vault-panel" id="vault-panel">
|
|
246
|
+
<div class="vault-header">
|
|
247
|
+
<h2 data-i18n="vault.title">🔐 API Key Vault</h2>
|
|
248
|
+
<button class="icon-btn" id="btn-vault-close">✕</button>
|
|
249
|
+
</div>
|
|
250
|
+
<div class="vault-body">
|
|
251
|
+
<div class="vault-note" data-i18n="vault.note">
|
|
252
|
+
Keys are stored <strong>only</strong> in this browser's local storage. Each key is validated with a
|
|
253
|
+
real completion probe (not just a listing check) so only genuinely callable models appear —
|
|
254
|
+
this eliminates false "404" model entries. Nothing is committed to the repo or sent anywhere except
|
|
255
|
+
directly to the provider you choose.
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<form class="vault-form" id="vault-form">
|
|
259
|
+
<label for="vault-provider" data-i18n="vault.provider">Provider</label>
|
|
260
|
+
<select id="vault-provider"></select>
|
|
261
|
+
|
|
262
|
+
<label for="vault-label" data-i18n="vault.label">Custom Label</label>
|
|
263
|
+
<input type="text" id="vault-label" data-i18n-placeholder="vault.labelPlaceholder" placeholder="e.g. My Groq Key" />
|
|
264
|
+
|
|
265
|
+
<label for="vault-key" data-i18n="vault.key">API Key</label>
|
|
266
|
+
<input type="password" id="vault-key" data-i18n-placeholder="vault.keyPlaceholder" placeholder="Paste your API key" required />
|
|
267
|
+
|
|
268
|
+
<button type="submit" data-i18n="vault.save">➕ Save & Smart-Validate</button>
|
|
269
|
+
</form>
|
|
270
|
+
|
|
271
|
+
<div id="vault-entries"></div>
|
|
272
|
+
</div>
|
|
273
|
+
</aside>
|
|
274
|
+
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<!-- ===================== FILE VIEWER MODAL ===================== -->
|
|
278
|
+
<div class="modal-overlay" id="file-viewer-overlay">
|
|
279
|
+
<div class="file-viewer-modal">
|
|
280
|
+
<div class="file-viewer-header">
|
|
281
|
+
<h3 id="file-viewer-title">File</h3>
|
|
282
|
+
<button class="icon-btn" id="file-viewer-close">✕</button>
|
|
283
|
+
</div>
|
|
284
|
+
<div class="file-viewer-body" id="file-viewer-body"></div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div class="toast" id="toast"></div>
|
|
289
|
+
|
|
290
|
+
<script src="src-client/i18n.js"></script>
|
|
291
|
+
<script src="src-client/app.js"></script>
|
|
292
|
+
</body>
|
|
293
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "muxmind-ai",
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "MuxMind AI - Multi-Provider AI Orchestration Engine with smart routing, failover, voice input, and image generation.",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"muxmind-ai": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/",
|
|
13
|
+
"src-client/",
|
|
14
|
+
"assets/",
|
|
15
|
+
"server.js",
|
|
16
|
+
"index.html",
|
|
17
|
+
"style.css",
|
|
18
|
+
".env.example",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"start": "node server.js",
|
|
23
|
+
"dev": "node server.js"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"express": "^4.19.2",
|
|
27
|
+
"cors": "^2.8.5",
|
|
28
|
+
"dotenv": "^16.4.5",
|
|
29
|
+
"express-rate-limit": "^7.4.0",
|
|
30
|
+
"multer": "^1.4.5-lts.1"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"ai",
|
|
37
|
+
"chat",
|
|
38
|
+
"llm",
|
|
39
|
+
"openai",
|
|
40
|
+
"anthropic",
|
|
41
|
+
"gemini",
|
|
42
|
+
"multi-provider",
|
|
43
|
+
"cli",
|
|
44
|
+
"orchestration"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT"
|
|
47
|
+
}
|