Sync cezen-portal/ from live working copy (V1.0 GA polish + search-icon ordering fix)
Brings the ISO-packaged portal copy current with the live cezen-portal/ working directory, which had drifted across the entire V1.0 GA polish initiative (Phases 1-10) plus this session's topnav icon-order bugfix. - 52 existing files updated, 2 new pages added (about.html, health.html) - cgit/cezen-portal/ is now byte-identical to cezen-portal/ - Backend files (rag_ingest.py, cezen_license.py) already in sync; main.py intentionally left as-is (cgit is ahead there with workstation-category commercial-fit logic not yet backported to the live working copy) Next: rebuild the ISO (bash autoinstall/build-iso.sh) to bake this in.
This commit is contained in:
1 parent
7540da04a8
commit
b3eec34158
57 files changed
+2832
-1354
No files matched your search
+62
-53
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Model Router — Nexus One AI</title>
|
||||
<link rel="stylesheet" href="style.css?v=4">
|
||||
<link rel="stylesheet" href="style.css?v=12">
|
||||
<style>
|
||||
.mr-content { max-width:1060px; margin:0 auto; padding:32px 40px 60px; }
|
||||
@media(max-width:768px){ .mr-content { padding:20px 16px 48px; } }
|
||||
@@ -80,7 +80,8 @@
|
||||
|
||||
/* ── Fallback / cloud ── */
|
||||
.mr-fallback-row { display:flex; align-items:center; gap:14px; padding:14px 18px; border:1px solid var(--bdr); border-radius:10px; background:var(--bg); margin-bottom:10px; }
|
||||
.mr-fallback-icon { font-size:20px; }
|
||||
.mr-fallback-icon { width:20px; height:20px; color:var(--text-secondary); }
|
||||
.mr-fallback-icon svg { width:100%; height:100%; }
|
||||
.mr-fallback-info { flex:1; }
|
||||
.mr-fallback-label { font-size:13px; font-weight:600; color:var(--ink); }
|
||||
.mr-fallback-sub { font-size:12px; color:var(--lt); margin-top:2px; }
|
||||
@@ -117,7 +118,8 @@
|
||||
.mr-flow { display:flex; align-items:center; gap:0; overflow-x:auto; padding:8px 0 16px; }
|
||||
.mr-flow-node { background:var(--navy2); border:1px solid var(--bdr); border-radius:10px; padding:10px 16px; text-align:center; flex-shrink:0; min-width:90px; }
|
||||
.mr-flow-node.active { border-color:var(--purple); background:rgba(124,58,237,.06); }
|
||||
.mr-flow-node-icon { font-size:20px; margin-bottom:4px; }
|
||||
.mr-flow-node-icon { width:20px; height:20px; margin:0 auto 4px; color:var(--text-secondary); }
|
||||
.mr-flow-node-icon svg { width:100%; height:100%; }
|
||||
.mr-flow-node-label { font-size:11px; font-weight:700; color:var(--ink); }
|
||||
.mr-flow-node-sub { font-size:10px; color:var(--lt); margin-top:2px; }
|
||||
.mr-flow-arrow { color:var(--xlt); font-size:18px; padding:0 6px; flex-shrink:0; }
|
||||
@@ -157,6 +159,7 @@
|
||||
<a href="dashboard.html">Dashboard</a>
|
||||
<a href="analytics.html">Usage Analytics</a>
|
||||
<a href="audit.html">Governance & Audit</a>
|
||||
<a href="health.html">Health Center</a>
|
||||
<a href="feedback.html">Feedback & Ratings</a>
|
||||
<span class="nav-drop-cat">MANAGE /</span>
|
||||
<a href="users.html">Users</a>
|
||||
@@ -196,7 +199,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<a href="notifications.html" style="position:relative">🔔</a>
|
||||
<a href="notifications.html" class="nav-icon-btn" style="position:relative" aria-label="Notifications">
|
||||
<span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg></span>
|
||||
</a>
|
||||
<span class="badge" data-brand="tier">Basic Tier</span>
|
||||
<div id="nav-org-logo" class="nav-org-logo"></div>
|
||||
</header>
|
||||
@@ -211,61 +216,61 @@
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="mr-stats" id="stats-row">
|
||||
<div class="mr-stat"><div class="mr-stat-label">📡 Routes Active</div><div class="mr-stat-val" id="s-active">—</div><div class="mr-stat-sub">of <span id="s-total">—</span> configured</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">🚀 Requests Routed</div><div class="mr-stat-val" id="s-routed">—</div><div class="mr-stat-sub">last 24 hours</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">⚡ Avg Latency Saved</div><div class="mr-stat-val" id="s-latency">—</div><div class="mr-stat-sub">vs. always using large model</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">☁️ Cloud Fallback</div><div class="mr-stat-val" id="s-cloud">—</div><div class="mr-stat-sub" id="s-cloud-sub">—</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">Routes Active</div><div class="mr-stat-val" id="s-active">—</div><div class="mr-stat-sub">of <span id="s-total">—</span> configured</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">Requests Routed</div><div class="mr-stat-val" id="s-routed">—</div><div class="mr-stat-sub">last 24 hours</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">Avg Latency Saved</div><div class="mr-stat-val" id="s-latency">—</div><div class="mr-stat-sub">vs. always using large model</div></div>
|
||||
<div class="mr-stat"><div class="mr-stat-label">Cloud Fallback</div><div class="mr-stat-val" id="s-cloud">—</div><div class="mr-stat-sub" id="s-cloud-sub">—</div></div>
|
||||
</div>
|
||||
|
||||
<!-- Request flow diagram -->
|
||||
<div class="mr-card" style="margin-bottom:20px">
|
||||
<div class="mr-card-header">
|
||||
<span style="font-size:18px">🔀</span>
|
||||
<span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M16 3h5v5"/><path d="M4 20 21 3"/><path d="M21 16v5h-5"/><path d="M15 15 21 21"/><path d="M4 4h5l6 6"/></svg></span>
|
||||
<div class="mr-card-title">How Routing Works</div>
|
||||
</div>
|
||||
<div class="mr-card-body" style="padding:16px 22px">
|
||||
<div class="mr-flow">
|
||||
<div class="mr-flow-node active"><div class="mr-flow-node-icon">💬</div><div class="mr-flow-node-label">Request</div><div class="mr-flow-node-sub">User query</div></div>
|
||||
<div class="mr-flow-arrow">→</div>
|
||||
<div class="mr-flow-node active"><div class="mr-flow-node-icon">🔀</div><div class="mr-flow-node-label">Router</div><div class="mr-flow-node-sub">Match rules</div></div>
|
||||
<div class="mr-flow-arrow">→</div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon">🖼️</div><div class="mr-flow-node-label">Vision</div><div class="mr-flow-node-sub">Images/docs</div></div>
|
||||
<div class="mr-flow-node active"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></div><div class="mr-flow-node-label">Request</div><div class="mr-flow-node-sub">User query</div></div>
|
||||
<div class="mr-flow-arrow"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg></span></div>
|
||||
<div class="mr-flow-node active"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M16 3h5v5"/><path d="M4 20 21 3"/><path d="M21 16v5h-5"/><path d="M15 15 21 21"/><path d="M4 4h5l6 6"/></svg></div><div class="mr-flow-node-label">Router</div><div class="mr-flow-node-sub">Match rules</div></div>
|
||||
<div class="mr-flow-arrow"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg></span></div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="8.5" cy="10.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg></div><div class="mr-flow-node-label">Vision</div><div class="mr-flow-node-sub">Images/docs</div></div>
|
||||
<div class="mr-flow-arrow" style="color:var(--bdr)">·</div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon">🧠</div><div class="mr-flow-node-label">Reasoning</div><div class="mr-flow-node-sub">Complex tasks</div></div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M9.5 2a3.5 3.5 0 0 0-3.4 2.7A3.5 3.5 0 0 0 3 8.2a3.5 3.5 0 0 0 1.2 2.6A3.5 3.5 0 0 0 4 12a3.5 3.5 0 0 0 2.1 3.2A3.5 3.5 0 0 0 9.5 18H10v4"/><path d="M14.5 2a3.5 3.5 0 0 1 3.4 2.7A3.5 3.5 0 0 1 21 8.2a3.5 3.5 0 0 1-1.2 2.6A3.5 3.5 0 0 1 20 12a3.5 3.5 0 0 1-2.1 3.2A3.5 3.5 0 0 1 14.5 18H14v4"/><path d="M10 10h4"/><path d="M9 14h6"/></svg></div><div class="mr-flow-node-label">Reasoning</div><div class="mr-flow-node-sub">Complex tasks</div></div>
|
||||
<div class="mr-flow-arrow" style="color:var(--bdr)">·</div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon">⚡</div><div class="mr-flow-node-label">Fast</div><div class="mr-flow-node-sub">Simple Q&A</div></div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M13 2 3 14h7l-1 8 10-12h-7z"/></svg></div><div class="mr-flow-node-label">Fast</div><div class="mr-flow-node-sub">Simple Q&A</div></div>
|
||||
<div class="mr-flow-arrow" style="color:var(--bdr)">·</div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon">📚</div><div class="mr-flow-node-label">Embedding</div><div class="mr-flow-node-sub">RAG / search</div></div>
|
||||
<div class="mr-flow-node"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg></div><div class="mr-flow-node-label">Embedding</div><div class="mr-flow-node-sub">RAG / search</div></div>
|
||||
<div class="mr-flow-arrow" style="color:var(--bdr)">·</div>
|
||||
<div class="mr-flow-node" style="border-style:dashed;opacity:.6"><div class="mr-flow-node-icon">☁️</div><div class="mr-flow-node-label">Cloud</div><div class="mr-flow-node-sub">Fallback only</div></div>
|
||||
<div class="mr-flow-node" style="border-style:dashed;opacity:.6"><div class="mr-flow-node-icon icon-svg"><svg viewBox="0 0 24 24"><path d="M7 18a4 4 0 1 1 .8-7.9A5 5 0 0 1 17 9a4 4 0 1 1 0 9Z"/></svg></div><div class="mr-flow-node-label">Cloud</div><div class="mr-flow-node-sub">Fallback only</div></div>
|
||||
</div>
|
||||
<p style="font-size:12px;color:var(--lt);margin:0">Rules are evaluated in priority order (top → bottom). The first matching rule wins. Requests that match no rule go to the Default model.</p>
|
||||
<p style="font-size:12px;color:var(--lt);margin:0">Rules are evaluated in priority order from top to bottom. The first matching rule wins. Requests that match no rule go to the Default model.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Routing rules -->
|
||||
<div class="mr-card" style="margin-bottom:20px">
|
||||
<div class="mr-card-header">
|
||||
<span style="font-size:18px">📋</span>
|
||||
<span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9 3h6"/><path d="M10 7h4"/><rect x="5" y="5" width="14" height="16" rx="2"/></svg></span>
|
||||
<div class="mr-card-title">Routing Rules</div>
|
||||
<span style="font-size:12px;color:var(--lt)">Drag to reorder priority</span>
|
||||
<button class="mr-btn primary" style="margin-left:8px" onclick="saveRoutes()">💾 Save Rules</button>
|
||||
<button class="mr-btn primary" style="margin-left:8px" onclick="saveRoutes()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 21h14"/><path d="M19 21V7l-3-3H5v17"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span><span>Save Rules</span></span></button>
|
||||
</div>
|
||||
<div class="mr-card-body">
|
||||
<div id="routes-list"><!-- injected --></div>
|
||||
<button class="mr-add-route" onclick="addRoute()">+ Add Routing Rule</button>
|
||||
<button class="mr-add-route" onclick="addRoute()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></span><span>Add Routing Rule</span></span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fallback & cloud -->
|
||||
<div class="mr-card" style="margin-bottom:20px">
|
||||
<div class="mr-card-header">
|
||||
<span style="font-size:18px">🔩</span>
|
||||
<span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-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 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.01A1.65 1.65 0 0 0 10.5 3.1V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h.01a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.01a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
|
||||
<div class="mr-card-title">Fallback & Cloud Settings</div>
|
||||
</div>
|
||||
<div class="mr-card-body">
|
||||
<div class="mr-fallback-row">
|
||||
<div class="mr-fallback-icon">🏠</div>
|
||||
<div class="mr-fallback-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 11.5 12 4l9 7.5"/><path d="M5 10v10h14V10"/></svg></div>
|
||||
<div class="mr-fallback-info">
|
||||
<div class="mr-fallback-label">Default (Local) Model</div>
|
||||
<div class="mr-fallback-sub">Used when no routing rule matches. Always stays on-premises.</div>
|
||||
@@ -276,7 +281,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mr-fallback-row">
|
||||
<div class="mr-fallback-icon">☁️</div>
|
||||
<div class="mr-fallback-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 18a4 4 0 1 1 .8-7.9A5 5 0 0 1 17 9a4 4 0 1 1 0 9Z"/></svg></div>
|
||||
<div class="mr-fallback-info">
|
||||
<div class="mr-fallback-label">Cloud Fallback</div>
|
||||
<div class="mr-fallback-sub">Route to a cloud provider when local models are overloaded or unavailable. <strong>Disabled by default</strong> — enable only if data privacy policy permits.</div>
|
||||
@@ -288,7 +293,7 @@
|
||||
</div>
|
||||
|
||||
<div id="cloud-settings" style="display:none;margin-top:12px;padding:16px;background:rgba(217,119,6,.04);border:1px solid rgba(217,119,6,.2);border-radius:10px">
|
||||
<div style="font-size:12px;font-weight:700;color:#D97706;margin-bottom:12px">⚠️ Cloud fallback will send queries outside your on-premises environment. Ensure this complies with your data privacy policy.</div>
|
||||
<div style="font-size:12px;font-weight:700;color:#D97706;margin-bottom:12px">Cloud fallback will send queries outside your on-premises environment. Ensure this complies with your data privacy policy.</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px">
|
||||
<div class="mr-field">
|
||||
<label>Provider</label>
|
||||
@@ -319,7 +324,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mr-fallback-row" style="margin-top:10px">
|
||||
<div class="mr-fallback-icon">🔒</div>
|
||||
<div class="mr-fallback-icon icon-svg" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></div>
|
||||
<div class="mr-fallback-info">
|
||||
<div class="mr-fallback-label">PII Shield</div>
|
||||
<div class="mr-fallback-sub">Detect and redact personally identifiable information before routing to any cloud provider.</div>
|
||||
@@ -331,7 +336,7 @@
|
||||
</div>
|
||||
|
||||
<div style="margin-top:14px;display:flex;gap:10px">
|
||||
<button class="mr-btn primary" onclick="saveFallback()">💾 Save Fallback Settings</button>
|
||||
<button class="mr-btn primary" onclick="saveFallback()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 21h14"/><path d="M19 21V7l-3-3H5v17"/><path d="M9 10h6"/><path d="M9 14h6"/></svg></span><span>Save Fallback Settings</span></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -339,7 +344,7 @@
|
||||
<!-- Live test panel -->
|
||||
<div class="mr-card">
|
||||
<div class="mr-card-header">
|
||||
<span style="font-size:18px">🧪</span>
|
||||
<span class="icon-svg icon-md" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M9 3v5l-5 9a2 2 0 0 0 1.75 3h12.5A2 2 0 0 0 20 17l-5-9V3"/><path d="M9 8h6"/></svg></span>
|
||||
<div class="mr-card-title">Live Routing Test</div>
|
||||
<span style="font-size:12px;color:var(--lt)">See which route would be selected for a given input</span>
|
||||
</div>
|
||||
@@ -362,7 +367,7 @@
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="mr-btn primary" style="align-self:flex-start" onclick="testRoute()">▶ Test Routing</button>
|
||||
<button class="mr-btn primary" style="align-self:flex-start" onclick="testRoute()"><span class="icon-label"><span class="icon-svg icon-sm" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m8 5 11 7-11 7z"/></svg></span><span>Test Routing</span></span></button>
|
||||
</div>
|
||||
|
||||
<div class="mr-test-result" id="test-result">
|
||||
@@ -380,6 +385,18 @@ const _API = '/api';
|
||||
let routes = [];
|
||||
let models = [];
|
||||
let routeCounter = 0;
|
||||
function routerIconSvg(name, sizeClass = 'icon-md') {
|
||||
const icons = {
|
||||
route:'<svg viewBox="0 0 24 24"><path d="M16 3h5v5"/><path d="M4 20 21 3"/><path d="M21 16v5h-5"/><path d="M15 15 21 21"/><path d="M4 4h5l6 6"/></svg>',
|
||||
plus:'<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>',
|
||||
up:'<svg viewBox="0 0 24 24"><path d="m18 15-6-6-6 6"/></svg>',
|
||||
down:'<svg viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>',
|
||||
trash:'<svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/></svg>',
|
||||
chevronRight:'<svg viewBox="0 0 24 24"><path d="m9 6 6 6-6 6"/></svg>',
|
||||
drag:'<svg viewBox="0 0 24 24"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>'
|
||||
};
|
||||
return `<span class="icon-svg ${sizeClass}" aria-hidden="true">${icons[name] || icons.route}</span>`;
|
||||
}
|
||||
|
||||
const CONDITION_TYPES = {
|
||||
keyword: { label:'Contains keyword', placeholder:'e.g. invoice, contract' },
|
||||
@@ -437,7 +454,7 @@ function renderRoutes() {
|
||||
document.getElementById('routes-list').innerHTML = routes.map((r,i) => `
|
||||
<div class="mr-route ${r.enabled ? 'active-route' : ''}" id="route-${r._id}">
|
||||
<div class="mr-route-header" onclick="toggleRoute(${r._id})">
|
||||
<span class="mr-route-drag" title="Drag to reorder">⋮⋮</span>
|
||||
<span class="mr-route-drag" title="Drag to reorder">${routerIconSvg('drag', 'icon-sm')}</span>
|
||||
<div class="mr-route-priority">${i+1}</div>
|
||||
<div class="mr-route-name">${esc(r.name)}</div>
|
||||
<div class="mr-route-model">${esc(r.model)}</div>
|
||||
@@ -445,7 +462,7 @@ function renderRoutes() {
|
||||
<input type="checkbox" ${r.enabled?'checked':''} onchange="toggleEnabled(${r._id},this.checked)">
|
||||
<div class="mr-route-toggle-track"></div>
|
||||
</label>
|
||||
<span class="mr-route-chevron">▶</span>
|
||||
<span class="mr-route-chevron">${routerIconSvg('chevronRight', 'icon-sm')}</span>
|
||||
</div>
|
||||
<div class="mr-route-body">
|
||||
<div class="mr-field-grid">
|
||||
@@ -460,17 +477,17 @@ function renderRoutes() {
|
||||
</select>
|
||||
</div>
|
||||
<div class="mr-field">
|
||||
<label>Max Tokens</label>
|
||||
<label>Max Tokens <span class="cezen-help" tabindex="0" data-tip="The maximum length of the model's response for queries routed here, in tokens (roughly 3/4 of a word each). Higher allows longer answers but costs more time/VRAM.">?</span></label>
|
||||
<input type="number" value="${r.max_tokens||2048}" min="128" max="32768" onchange="updateRoute(${r._id},'max_tokens',parseInt(this.value))">
|
||||
</div>
|
||||
<div class="mr-field">
|
||||
<label>Temperature</label>
|
||||
<label>Temperature <span class="cezen-help" tabindex="0" data-tip="Controls randomness: near 0 gives focused, deterministic answers (good for facts/code); near 1 gives more varied, creative answers (good for brainstorming/writing).">?</span></label>
|
||||
<input type="number" value="${r.temperature??0.7}" min="0" max="1" step="0.1" onchange="updateRoute(${r._id},'temperature',parseFloat(this.value))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mr-field" style="margin-bottom:10px">
|
||||
<label>Match Conditions <span style="color:var(--lt);font-weight:400">— ALL conditions must match (AND logic)</span></label>
|
||||
<label>Match Conditions <span style="color:var(--lt);font-weight:400">— ALL conditions must match (AND logic)</span> <span class="cezen-help" tabindex="0" data-tip="Routes are checked top to bottom; the first route whose conditions all match wins. Put your most specific routes near the top and a no-conditions catch-all route at the bottom.">?</span></label>
|
||||
<div class="mr-conditions" id="conds-${r._id}">
|
||||
${(r.conditions||[]).map((c,ci) => renderCondChip(r._id, c, ci)).join('')}
|
||||
</div>
|
||||
@@ -479,7 +496,7 @@ function renderRoutes() {
|
||||
${Object.entries(CONDITION_TYPES).map(([k,v])=>`<option value="${k}">${v.label}</option>`).join('')}
|
||||
</select>
|
||||
<input type="text" id="cond-val-${r._id}" placeholder="value…" style="padding:6px 10px;border:1px solid var(--bdr);border-radius:8px;font-family:inherit;font-size:12px;color:var(--ink);flex:1;min-width:120px;background:var(--navy2)">
|
||||
<button class="mr-add-cond" onclick="addCondition(${r._id})">+ Add condition</button>
|
||||
<button class="mr-add-cond" onclick="addCondition(${r._id})"><span class="icon-label">${routerIconSvg('plus', 'icon-sm')}<span>Add condition</span></span></button>
|
||||
</div>
|
||||
<div class="mr-field-hint">No conditions = always match (use as catch-all at the bottom of the list)</div>
|
||||
</div>
|
||||
@@ -490,9 +507,9 @@ function renderRoutes() {
|
||||
</div>
|
||||
|
||||
<div class="mr-route-actions">
|
||||
<button class="mr-btn ghost" onclick="moveRoute(${r._id},-1)">↑ Move Up</button>
|
||||
<button class="mr-btn ghost" onclick="moveRoute(${r._id},1)">↓ Move Down</button>
|
||||
<button class="mr-btn danger" onclick="deleteRoute(${r._id})">🗑 Delete</button>
|
||||
<button class="mr-btn ghost" onclick="moveRoute(${r._id},-1)"><span class="icon-label">${routerIconSvg('up', 'icon-sm')}<span>Move Up</span></span></button>
|
||||
<button class="mr-btn ghost" onclick="moveRoute(${r._id},1)"><span class="icon-label">${routerIconSvg('down', 'icon-sm')}<span>Move Down</span></span></button>
|
||||
<button class="mr-btn danger" onclick="deleteRoute(${r._id})"><span class="icon-label">${routerIconSvg('trash', 'icon-sm')}<span>Delete</span></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
@@ -540,7 +557,7 @@ function toggleEnabled(id, val) {
|
||||
function addCondition(routeId) {
|
||||
const type = document.getElementById(`cond-type-${routeId}`)?.value;
|
||||
const val = document.getElementById(`cond-val-${routeId}`)?.value.trim();
|
||||
if (!val) { alert('Enter a condition value'); return; }
|
||||
if (!val) { showToast('Enter a condition value before saving this rule.', 'warn'); return; }
|
||||
const r = routes.find(r => r._id === routeId);
|
||||
if (!r) return;
|
||||
r.conditions.push({ type, value: val });
|
||||
@@ -615,7 +632,7 @@ function testRoute() {
|
||||
const contentType = document.getElementById('test-content-type').value;
|
||||
const userRole = document.getElementById('test-user-role').value;
|
||||
|
||||
if (!prompt) { alert('Enter a prompt to test'); return; }
|
||||
if (!prompt) { showToast('Enter a prompt to test.', 'warn'); return; }
|
||||
|
||||
const tokenEst = Math.round(prompt.split(/\s+/).length * 1.3);
|
||||
let matched = null;
|
||||
@@ -648,7 +665,7 @@ function testRoute() {
|
||||
<div class="mr-result-route">
|
||||
<div class="mr-result-row">
|
||||
<div class="mr-result-key">Status</div>
|
||||
<div class="mr-result-val"><span class="mr-result-badge matched">✓ Rule matched</span></div>
|
||||
<div class="mr-result-val"><span class="mr-result-badge matched">Rule matched</span></div>
|
||||
</div>
|
||||
<div class="mr-result-row">
|
||||
<div class="mr-result-key">Route</div>
|
||||
@@ -683,7 +700,7 @@ function testRoute() {
|
||||
<div class="mr-result-route">
|
||||
<div class="mr-result-row">
|
||||
<div class="mr-result-key">Status</div>
|
||||
<div class="mr-result-val"><span class="mr-result-badge default">→ Default model</span></div>
|
||||
<div class="mr-result-val"><span class="mr-result-badge default">Default model</span></div>
|
||||
</div>
|
||||
<div class="mr-result-row">
|
||||
<div class="mr-result-key">Model</div>
|
||||
@@ -693,7 +710,7 @@ function testRoute() {
|
||||
<div class="mr-result-key">Reason</div>
|
||||
<div class="mr-result-val" style="color:var(--lt)">No active rule matched</div>
|
||||
</div>
|
||||
${cloudEnabled ? `<div class="mr-result-row"><div class="mr-result-key">Cloud</div><div class="mr-result-val"><span class="mr-result-badge fallback">☁ Fallback enabled</span></div></div>` : ''}
|
||||
${cloudEnabled ? `<div class="mr-result-row"><div class="mr-result-key">Cloud</div><div class="mr-result-val"><span class="mr-result-badge fallback">Fallback enabled</span></div></div>` : ''}
|
||||
<div style="margin-top:10px;font-size:12px;color:var(--lt)">
|
||||
Add a rule or adjust conditions to route this type of query to a specific model.
|
||||
</div>
|
||||
@@ -747,14 +764,6 @@ async function loadModels() {
|
||||
} catch(_) { models = ['llama3','llama3:70b','llama3:8b','llava','codellama','nomic-embed-text']; }
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
const t = document.createElement('div');
|
||||
t.textContent = msg;
|
||||
t.style.cssText = 'position:fixed;bottom:24px;right:24px;background:#F0FDFA;color:#6D28D9;border:1px solid #99F6E4;border-radius:10px;padding:11px 18px;font-size:13px;font-weight:600;z-index:9999;box-shadow:0 4px 16px rgba(0,0,0,.1)';
|
||||
document.body.appendChild(t);
|
||||
setTimeout(()=>t.remove(), 2500);
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
@@ -772,6 +781,6 @@ function esc(s) {
|
||||
</script>
|
||||
|
||||
<script src="auth.js"></script>
|
||||
<script src="branding.js"></script>
|
||||
<script src="branding.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user