Changeset 0.13.0 (#64)
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
========================================== */
|
||||
|
||||
:root {
|
||||
/* ── Core palette ────────────────────────── */
|
||||
--bg: #0e0e10;
|
||||
--bg-surface: #18181b;
|
||||
--bg-raised: #222227;
|
||||
@@ -23,6 +24,27 @@
|
||||
--warning: #eab308;
|
||||
--purple: #a78bfa;
|
||||
--purple-dim: rgba(167,139,250,0.10);
|
||||
|
||||
/* ── Contrast text for solid-color backgrounds ── */
|
||||
--text-on-color: #fff;
|
||||
--text-on-warning: #000;
|
||||
|
||||
/* ── Light variants (badge/pill text on dim backgrounds) ── */
|
||||
--accent-light: #93c5fd;
|
||||
--danger-light: #f87171;
|
||||
--success-light: #4ade80;
|
||||
--warning-light: #fbbf24;
|
||||
|
||||
/* ── Dim variants (badge/pill backgrounds) ── */
|
||||
--danger-dim: rgba(239,68,68,0.2);
|
||||
--success-dim: rgba(34,197,94,0.2);
|
||||
--warning-dim: rgba(234,179,8,0.2);
|
||||
|
||||
/* ── Surfaces ────────────────────────────── */
|
||||
--overlay: rgba(0,0,0,0.6);
|
||||
--glass: rgba(255,255,255,0.03);
|
||||
|
||||
/* ── Layout ──────────────────────────────── */
|
||||
--radius: 8px;
|
||||
--radius-lg: 12px;
|
||||
--sidebar-w: 260px;
|
||||
@@ -406,7 +428,7 @@ a:hover { text-decoration: underline; }
|
||||
.messages { flex: 1; overflow-y: auto; scroll-behavior: smooth; }
|
||||
|
||||
.message { padding: 1.25rem 0; }
|
||||
.message:not(:last-child) { border-bottom: 1px solid rgba(255,255,255,0.03); }
|
||||
.message:not(:last-child) { border-bottom: 1px solid var(--glass); }
|
||||
|
||||
.msg-inner { max-width: 768px; margin: 0 auto; padding: 0 1.5rem; display: flex; gap: 1rem; }
|
||||
|
||||
@@ -506,7 +528,7 @@ a:hover { text-decoration: underline; }
|
||||
.msg-edit-cancel:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.msg-edit-submit {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
color: var(--text-on-color);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.msg-edit-submit:hover { background: var(--accent-hover); }
|
||||
@@ -701,7 +723,7 @@ a:hover { text-decoration: underline; }
|
||||
|
||||
/* Thinking blocks */
|
||||
.thinking-block {
|
||||
border: 1px solid rgba(108,159,255,0.12); border-radius: var(--radius);
|
||||
border: 1px solid var(--accent-dim); border-radius: var(--radius);
|
||||
margin: 0.5rem 0; background: rgba(108,159,255,0.03);
|
||||
}
|
||||
.thinking-block summary {
|
||||
@@ -831,15 +853,15 @@ a:hover { text-decoration: underline; }
|
||||
.context-warning-icon { flex-shrink: 0; }
|
||||
.context-warning-text { flex: 1; }
|
||||
.context-warning-dismiss { background: none; border: none; color: var(--text-3); cursor: pointer; padding: 0 2px; font-size: 0.85rem; }
|
||||
.context-warning-dismiss:hover { color: var(--text-1); }
|
||||
.context-warning-dismiss:hover { color: var(--text); }
|
||||
.context-warning-action {
|
||||
flex-shrink: 0; padding: 3px 10px; border-radius: 6px; border: 1px solid var(--border);
|
||||
background: var(--bg-surface); color: var(--text-1); cursor: pointer;
|
||||
background: var(--bg-surface); color: var(--text); cursor: pointer;
|
||||
font-size: 0.75rem; white-space: nowrap; transition: all var(--transition);
|
||||
}
|
||||
.context-warning-action:hover { background: var(--accent); color: #fff; border-color: var(--accent); }
|
||||
.context-warning-action:hover { background: var(--accent); color: var(--text-on-color); border-color: var(--accent); }
|
||||
.context-warning-action:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.context-warning-action:disabled:hover { background: var(--bg-surface); color: var(--text-1); border-color: var(--border); }
|
||||
.context-warning-action:disabled:hover { background: var(--bg-surface); color: var(--text); border-color: var(--border); }
|
||||
/* Summary message node */
|
||||
.message-summary {
|
||||
border: 1px dashed color-mix(in srgb, var(--accent) 40%, transparent);
|
||||
@@ -855,7 +877,7 @@ a:hover { text-decoration: underline; }
|
||||
background: none; border: none; color: var(--accent); cursor: pointer;
|
||||
font-size: 0.75rem; padding: 0; text-decoration: underline;
|
||||
}
|
||||
.message-summary .summary-toggle:hover { color: var(--text-1); }
|
||||
.message-summary .summary-toggle:hover { color: var(--text); }
|
||||
.msg-dimmed { opacity: 0.5; }
|
||||
.message-summary-divider {
|
||||
text-align: center; padding: 8px 0; margin: 4px 0;
|
||||
@@ -918,7 +940,7 @@ a:hover { text-decoration: underline; }
|
||||
font-size: 12px; max-width: 220px; position: relative;
|
||||
}
|
||||
.att-chip.att-ready { border-color: color-mix(in srgb, var(--success) 40%, var(--border)); }
|
||||
.att-chip.att-error { border-color: color-mix(in srgb, var(--error) 40%, var(--border)); }
|
||||
.att-chip.att-error { border-color: color-mix(in srgb, var(--danger) 40%, var(--border)); }
|
||||
.att-chip.att-pending { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
|
||||
|
||||
.att-thumb {
|
||||
@@ -1005,7 +1027,7 @@ a:hover { text-decoration: underline; }
|
||||
}
|
||||
.lightbox-close {
|
||||
position: absolute; top: 16px; right: 20px;
|
||||
background: rgba(255,255,255,0.15); border: none; color: #fff;
|
||||
background: rgba(255,255,255,0.15); border: none; color: var(--text-on-color);
|
||||
font-size: 22px; width: 40px; height: 40px; border-radius: 50%;
|
||||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||||
transition: background 0.15s;
|
||||
@@ -1032,7 +1054,7 @@ a:hover { text-decoration: underline; }
|
||||
|
||||
button { font-family: var(--font); cursor: pointer; }
|
||||
.btn-primary {
|
||||
background: var(--accent); color: #fff; border: none;
|
||||
background: var(--accent); color: var(--text-on-color); border: none;
|
||||
padding: 8px 16px; border-radius: var(--radius);
|
||||
font-weight: 500; font-size: 13px;
|
||||
transition: background var(--transition);
|
||||
@@ -1046,8 +1068,8 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
font-size: 12px; transition: all var(--transition);
|
||||
}
|
||||
.btn-small:hover { background: var(--bg-hover); color: var(--text); }
|
||||
.btn-small.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
.btn-danger { background: var(--danger); color: #fff; border: none; border-radius: var(--radius); }
|
||||
.btn-small.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--text-on-color); }
|
||||
.btn-danger { background: var(--danger); color: var(--text-on-color); border: none; border-radius: var(--radius); }
|
||||
|
||||
/* ── Auth Splash ─────────────────────────── */
|
||||
|
||||
@@ -1147,7 +1169,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.auth-error { color: var(--danger); font-size: 0.78rem; min-height: 1.2em; margin: 0.5rem 0; }
|
||||
.splash-error { color: var(--danger); font-size: 0.78rem; text-align: center; margin-top: 0.5rem; line-height: 1.5; }
|
||||
.splash-error strong { display: block; font-size: 0.85rem; margin-bottom: 0.25rem; }
|
||||
.splash-error-hint { color: var(--text-muted); font-size: 0.72rem; }
|
||||
.splash-error-hint { color: var(--text-3); font-size: 0.72rem; }
|
||||
.splash-error-hint a { color: var(--accent); text-decoration: underline; cursor: pointer; }
|
||||
.auth-actions { margin-top: 1.5rem; }
|
||||
.auth-footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--border); text-align: center; }
|
||||
@@ -1200,14 +1222,26 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
|
||||
/* ── Forms ────────────────────────────────── */
|
||||
|
||||
.form-group { margin-bottom: 0.75rem; }
|
||||
.form-group label { display: block; font-size: 13px; color: var(--text-2); margin-bottom: 4px; font-weight: 500; }
|
||||
.form-group input, .form-group select, .form-group textarea {
|
||||
width: 100%; background: var(--bg-raised); border: 1px solid var(--border);
|
||||
/* Base form element styling — ensures selects/inputs match the dark theme
|
||||
even when not wrapped in .form-group (e.g. role config, inline toolbars) */
|
||||
select, input[type="text"], input[type="email"], input[type="password"],
|
||||
input[type="number"], input[type="search"], textarea {
|
||||
background: var(--bg-raised); border: 1px solid var(--border);
|
||||
color: var(--text); padding: 8px 12px; border-radius: var(--radius);
|
||||
font-family: var(--font); font-size: 13px;
|
||||
transition: border-color var(--transition);
|
||||
}
|
||||
select:focus, input[type="text"]:focus, input[type="email"]:focus,
|
||||
input[type="password"]:focus, textarea:focus {
|
||||
outline: none; border-color: var(--accent);
|
||||
}
|
||||
select option { background: var(--bg-surface); color: var(--text); }
|
||||
|
||||
.form-group { margin-bottom: 0.75rem; }
|
||||
.form-group label { display: block; font-size: 13px; color: var(--text-2); margin-bottom: 4px; font-weight: 500; }
|
||||
.form-group input, .form-group select, .form-group textarea {
|
||||
width: 100%;
|
||||
}
|
||||
.form-group input:focus, .form-group select:focus, .form-group textarea:focus {
|
||||
outline: none; border-color: var(--accent);
|
||||
}
|
||||
@@ -1233,7 +1267,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
/* ── Modal ────────────────────────────────── */
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.6);
|
||||
position: fixed; inset: 0; background: var(--overlay);
|
||||
display: none; align-items: center; justify-content: center;
|
||||
z-index: 1000; backdrop-filter: blur(2px);
|
||||
padding: 2rem; /* keeps modal away from viewport edges at any zoom */
|
||||
@@ -1324,7 +1358,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.settings-tabs .settings-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
|
||||
.settings-notice {
|
||||
background: rgba(234,179,8,0.08); border: 1px solid rgba(234,179,8,0.2);
|
||||
background: rgba(234,179,8,0.08); border: 1px solid var(--warning-dim);
|
||||
border-radius: var(--radius); padding: 10px 14px; font-size: 13px;
|
||||
color: var(--warning); display: flex; align-items: center; gap: 8px;
|
||||
margin-top: 1rem;
|
||||
@@ -1340,17 +1374,66 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.provider-actions { display: flex; align-items: center; gap: 8px; }
|
||||
.badge-global { font-size: 10px; color: var(--text-3); background: var(--bg-raised); padding: 2px 8px; border-radius: 4px; }
|
||||
|
||||
/* Admin tabs */
|
||||
.admin-tabs { }
|
||||
/* Admin panel (fullscreen) */
|
||||
.admin-panel {
|
||||
position: fixed; z-index: 1000;
|
||||
top: var(--banner-top-height); right: 0;
|
||||
bottom: var(--banner-bottom-height); left: 0;
|
||||
background: var(--bg); display: flex; flex-direction: column;
|
||||
}
|
||||
.admin-topbar {
|
||||
display: flex; align-items: center; padding: 0 1rem; height: 48px;
|
||||
border-bottom: 1px solid var(--border); background: var(--bg-surface); flex-shrink: 0;
|
||||
}
|
||||
.admin-back {
|
||||
background: none; border: none; color: var(--text-3); cursor: pointer;
|
||||
font-size: 14px; padding: 4px 10px; border-radius: 4px; font-family: var(--font);
|
||||
}
|
||||
.admin-back:hover { color: var(--accent); background: var(--bg-raised); }
|
||||
.admin-title { font-size: 16px; font-weight: 600; margin-left: 1rem; flex: 1; }
|
||||
.admin-user { font-size: 12px; color: var(--text-3); }
|
||||
|
||||
.admin-categories {
|
||||
display: flex; gap: 0; border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-surface); padding: 0 1rem; flex-shrink: 0;
|
||||
}
|
||||
.admin-cat {
|
||||
background: none; border: none; border-bottom: 2px solid transparent;
|
||||
color: var(--text-3); padding: 10px 20px; cursor: pointer;
|
||||
font-size: 14px; font-weight: 500; font-family: var(--font);
|
||||
transition: color 0.15s, border-color 0.15s; outline: none;
|
||||
}
|
||||
.admin-cat:hover { color: var(--text-2); }
|
||||
.admin-cat.active { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
|
||||
.admin-body { display: flex; flex: 1; overflow: hidden; }
|
||||
|
||||
.admin-sidebar {
|
||||
width: 160px; flex-shrink: 0; border-right: 1px solid var(--border);
|
||||
background: var(--bg-surface); padding: 0.75rem 0; overflow-y: auto;
|
||||
}
|
||||
.admin-sidebar button {
|
||||
display: block; width: 100%; text-align: left; background: none; border: none;
|
||||
padding: 8px 20px; color: var(--text-3); cursor: pointer;
|
||||
font-size: 13px; font-family: var(--font); border-left: 3px solid transparent;
|
||||
transition: color 0.1s, background 0.1s;
|
||||
}
|
||||
.admin-sidebar button:hover { color: var(--text-2); background: var(--bg-raised); }
|
||||
.admin-sidebar button.active { color: var(--accent); border-left-color: var(--accent); background: var(--bg-raised); }
|
||||
|
||||
.admin-main { flex: 1; overflow-y: auto; padding: 1.5rem 2rem; }
|
||||
.admin-section-content { /* replaces .admin-tab-content */ }
|
||||
|
||||
/* Admin shared */
|
||||
/* admin-tab: still used by team admin modal */
|
||||
.admin-tab {
|
||||
padding: 10px 14px; background: none; border: none; color: var(--text-3);
|
||||
cursor: pointer; font-size: 13px; border-bottom: 2px solid transparent;
|
||||
white-space: nowrap; flex-shrink: 0; transition: color var(--transition);
|
||||
outline: none;
|
||||
outline: none; font-family: var(--font);
|
||||
}
|
||||
.admin-tab:hover { color: var(--text-2); }
|
||||
.admin-tab:focus { color: var(--text-3); }
|
||||
.admin-tab.active, .admin-tab.active:focus { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
.admin-tab.active { color: var(--accent); border-bottom-color: var(--accent); }
|
||||
|
||||
.admin-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 1rem; }
|
||||
.admin-hint { font-size: 12px; color: var(--text-3); }
|
||||
@@ -1387,7 +1470,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.admin-model-toggle { background: none; border: 1px solid var(--border); border-radius: 4px; padding: 4px 10px; font-size: 12px; cursor: pointer; color: var(--text-2); min-width: 82px; text-align: center; }
|
||||
.admin-model-toggle:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.admin-model-toggle.enabled { border-color: var(--success); color: var(--success); }
|
||||
.admin-model-toggle.team { border-color: #60a5fa; color: #60a5fa; }
|
||||
.admin-model-toggle.team { border-color: var(--accent); color: var(--accent); }
|
||||
.model-list-item.model-hidden { opacity: 0.5; }
|
||||
.model-list-item.model-hidden .model-name { text-decoration: line-through; }
|
||||
|
||||
@@ -1411,7 +1494,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.admin-preset-row .preset-actions { display: flex; gap: 6px; align-items: center; flex-shrink: 0; }
|
||||
.admin-preset-row .btn-delete { background: none; border: none; color: var(--text-3); cursor: pointer; font-size: 16px; padding: 2px 6px; }
|
||||
.admin-preset-row .btn-delete:hover { color: var(--danger); }
|
||||
.badge-user { font-size: 10px; padding: 1px 6px; border-radius: 3px; background: var(--accent-bg); color: var(--accent); }
|
||||
.badge-user { font-size: 10px; padding: 1px 6px; border-radius: 3px; background: var(--accent-dim); color: var(--accent); }
|
||||
|
||||
/* Stats cards */
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
|
||||
@@ -1464,15 +1547,15 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.model-list-item .model-provider { font-size: 12px; color: var(--text-3); }
|
||||
.model-list-item .model-caps-inline { display: flex; gap: 3px; }
|
||||
|
||||
.badge-admin { background: var(--accent); color: #fff; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-admin { background: var(--accent); color: var(--text-on-color); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-user { background: var(--bg-raised); color: var(--text-3); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-inactive { background: var(--danger); color: #fff; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-pending { background: rgba(234,179,8,0.85); color: #000; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-team { background: rgba(59,130,246,0.2); color: #93c5fd; font-size: 10px; padding: 1px 8px; border-radius: 4px; margin-left: 2px; }
|
||||
.admin-approve-form { padding: 10px 12px; margin: -4px 0 8px; background: rgba(255,255,255,0.03); border-radius: 0 0 8px 8px; border-top: 1px solid var(--border); }
|
||||
.badge-inactive { background: var(--danger); color: var(--text-on-color); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-pending { background: var(--warning); color: var(--text-on-warning); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-team { background: var(--accent-dim); color: var(--accent-light); font-size: 10px; padding: 1px 8px; border-radius: 4px; margin-left: 2px; }
|
||||
.admin-approve-form { padding: 10px 12px; margin: -4px 0 8px; background: var(--glass); border-radius: 0 0 8px 8px; border-top: 1px solid var(--border); }
|
||||
.admin-approve-form .checkbox-label { display: block; margin: 2px 0; }
|
||||
.btn-approve { background: rgba(34,197,94,0.15); color: #4ade80; border: 1px solid rgba(34,197,94,0.3); border-radius: 4px; padding: 3px 10px; cursor: pointer; font-size: 12px; }
|
||||
.team-card { padding: 8px 10px; background: rgba(255,255,255,0.03); border-radius: 6px; margin-bottom: 6px; }
|
||||
.btn-approve { background: var(--success-dim); color: var(--success-light); border: 1px solid rgba(34,197,94,0.3); border-radius: 4px; padding: 3px 10px; cursor: pointer; font-size: 12px; }
|
||||
.team-card { padding: 8px 10px; background: var(--glass); border-radius: 6px; margin-bottom: 6px; }
|
||||
.team-card-info { display: flex; align-items: center; gap: 8px; }
|
||||
.team-admin-back {
|
||||
cursor: pointer; margin-right: 4px; opacity: 0.5;
|
||||
@@ -1480,10 +1563,10 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
}
|
||||
.team-admin-back:hover { opacity: 1; }
|
||||
.team-tab-content { padding: 0; }
|
||||
.badge-private { background: rgba(139,92,246,0.85); color: #fff; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-success { background: rgba(34,197,94,0.2); color: #4ade80; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-warning { background: rgba(234,179,8,0.2); color: #fbbf24; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-danger { background: rgba(239,68,68,0.2); color: #f87171; font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-private { background: var(--purple); color: var(--text-on-color); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-success { background: var(--success-dim); color: var(--success-light); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-warning { background: var(--warning-dim); color: var(--warning-light); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.badge-danger { background: var(--danger-dim); color: var(--danger-light); font-size: 10px; padding: 1px 8px; border-radius: 4px; }
|
||||
.admin-user-row.user-inactive { opacity: 0.7; }
|
||||
.loading { color: var(--text-3); font-size: 13px; padding: 0.5rem; }
|
||||
.error-hint { color: var(--danger); font-size: 13px; padding: 0.5rem; }
|
||||
@@ -1687,8 +1770,8 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.notes-editor input, .notes-editor textarea {
|
||||
width: 100%; background: var(--bg-surface); color: var(--text);
|
||||
border: 1px solid var(--border); border-radius: var(--radius);
|
||||
padding: 6px 10px; font-family: var(--font); outline: none;
|
||||
transition: border-color var(--transition);
|
||||
padding: 6px 10px; font-family: var(--font); font-size: var(--msg-font, 14px);
|
||||
outline: none; transition: border-color var(--transition);
|
||||
}
|
||||
.notes-editor input:focus, .notes-editor textarea:focus {
|
||||
border-color: var(--accent);
|
||||
@@ -1730,7 +1813,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
/* ── Command Palette ─────────────────────── */
|
||||
|
||||
.cmd-palette-overlay {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.5);
|
||||
position: fixed; inset: 0; background: var(--overlay);
|
||||
display: none; align-items: flex-start; justify-content: center;
|
||||
z-index: 2000; backdrop-filter: blur(4px);
|
||||
padding-top: min(20vh, 160px);
|
||||
@@ -1817,7 +1900,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.confirm-overlay {
|
||||
position: fixed; inset: 0; z-index: 9999;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(0,0,0,0.55);
|
||||
background: var(--overlay);
|
||||
animation: fade-in 0.12s ease;
|
||||
}
|
||||
.confirm-dialog {
|
||||
@@ -1876,7 +1959,7 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.mobile-menu-btn:hover { color: var(--text); }
|
||||
.sidebar-overlay {
|
||||
display: none; position: fixed; inset: 0; z-index: 99;
|
||||
background: rgba(0,0,0,0.5);
|
||||
background: var(--overlay);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -1891,4 +1974,9 @@ button { font-family: var(--font); cursor: pointer; }
|
||||
.modal { width: 100%; }
|
||||
.tab-arrow { width: 36px; font-size: 20px; } /* larger touch target */
|
||||
.modal-tabs { padding: 0 12px; }
|
||||
.admin-sidebar { width: 48px; padding: 0.5rem 0; }
|
||||
.admin-sidebar button { padding: 8px 0; text-align: center; font-size: 11px; border-left: none; border-bottom: 2px solid transparent; }
|
||||
.admin-sidebar button.active { border-bottom-color: var(--accent); border-left: none; }
|
||||
.admin-main { padding: 1rem; }
|
||||
.admin-cat { padding: 10px 12px; font-size: 13px; }
|
||||
}
|
||||
|
||||
177
src/index.html
177
src/index.html
@@ -425,7 +425,7 @@
|
||||
<button class="btn-small" onclick="bulkSetUserModelVisibility(false)" title="Hide all models from selector">Hide All</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="section-hint" style="margin-bottom:8px">Toggle visibility in your model selector. Hidden models are still accessible through presets.</p>
|
||||
<p class="section-hint" style="margin-bottom:8px">Toggle visibility in your model selector. Hidden models are still accessible through personas.</p>
|
||||
<div id="userModelList" class="model-list-grid"><div class="empty-hint">Loading models...</div></div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -433,7 +433,7 @@
|
||||
<div class="settings-tab-content" id="settingsPersonasTab" style="display:none">
|
||||
<section class="settings-section">
|
||||
<h3 style="font-size:14px;margin-bottom:4px">My Personas</h3>
|
||||
<p class="section-hint" style="margin-bottom:8px">Personal model presets with custom system prompts and settings.</p>
|
||||
<p class="section-hint" style="margin-bottom:8px">Personal model personas with custom system prompts and settings.</p>
|
||||
<div id="userPresetList"><div class="empty-hint">Loading...</div></div>
|
||||
<div id="userAddPresetForm" style="display:none;margin-top:8px" class="admin-inline-form"></div>
|
||||
<button class="btn-small" id="userAddPresetBtn" style="margin-top:6px">+ New Persona</button>
|
||||
@@ -491,7 +491,7 @@
|
||||
<div class="modal-tabs admin-tabs" id="teamAdminTabs">
|
||||
<button class="admin-tab active" data-ttab="members" onclick="UI.switchTeamTab('members')">Members</button>
|
||||
<button class="admin-tab" data-ttab="providers" onclick="UI.switchTeamTab('providers')">Providers</button>
|
||||
<button class="admin-tab" data-ttab="presets" onclick="UI.switchTeamTab('presets')">Presets</button>
|
||||
<button class="admin-tab" data-ttab="presets" onclick="UI.switchTeamTab('presets')">Personas</button>
|
||||
<button class="admin-tab" data-ttab="usage" onclick="UI.switchTeamTab('usage')">Usage</button>
|
||||
<button class="admin-tab" data-ttab="activity" onclick="UI.switchTeamTab('activity')">Activity</button>
|
||||
</div>
|
||||
@@ -558,25 +558,24 @@
|
||||
</div>
|
||||
|
||||
<!-- ── Admin Modal ─────────────────────────── -->
|
||||
<div class="modal-overlay" id="adminModal">
|
||||
<div class="modal modal-wide">
|
||||
<div class="modal-header"><h2>Admin Panel</h2><button class="modal-close" id="adminCloseBtn">✕</button></div>
|
||||
<div class="modal-tabs admin-tabs">
|
||||
<button class="admin-tab active" data-tab="users">Users</button>
|
||||
<button class="admin-tab" data-tab="providers">Providers</button>
|
||||
<button class="admin-tab" data-tab="models">Models</button>
|
||||
<button class="admin-tab" data-tab="presets">Presets</button>
|
||||
<button class="admin-tab" data-tab="teams">Teams</button>
|
||||
<button class="admin-tab" data-tab="settings">Settings</button>
|
||||
<button class="admin-tab" data-tab="roles">Roles</button>
|
||||
<button class="admin-tab" data-tab="usage">Usage</button>
|
||||
<button class="admin-tab" data-tab="extensions">Extensions</button>
|
||||
<button class="admin-tab" data-tab="storage">Storage</button>
|
||||
<button class="admin-tab" data-tab="stats">Stats</button>
|
||||
<button class="admin-tab" data-tab="audit">Audit</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="admin-tab-content" id="adminUsersTab">
|
||||
<!-- ── Admin Panel (fullscreen overlay) ──── -->
|
||||
<div class="admin-panel" id="adminPanel" style="display:none">
|
||||
<div class="admin-topbar">
|
||||
<button class="admin-back" id="adminBackBtn">← Chat</button>
|
||||
<h1 class="admin-title">Admin Panel</h1>
|
||||
<span class="admin-user" id="adminCurrentUser"></span>
|
||||
</div>
|
||||
<div class="admin-categories" id="adminCategories">
|
||||
<button class="admin-cat active" data-cat="people">People</button>
|
||||
<button class="admin-cat" data-cat="ai">AI</button>
|
||||
<button class="admin-cat" data-cat="system">System</button>
|
||||
<button class="admin-cat" data-cat="monitoring">Monitoring</button>
|
||||
</div>
|
||||
<div class="admin-body">
|
||||
<nav class="admin-sidebar" id="adminSidebar"></nav>
|
||||
<main class="admin-main" id="adminMain">
|
||||
<!-- People -->
|
||||
<div class="admin-section-content" id="adminUsersTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddUserBtn">+ Add User</button>
|
||||
</div>
|
||||
@@ -593,32 +592,7 @@
|
||||
</div>
|
||||
<div id="adminUserList"></div>
|
||||
</div>
|
||||
<div class="admin-tab-content" id="adminProvidersTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddProviderBtn">+ Add Global Provider</button>
|
||||
</div>
|
||||
<div id="adminAddProviderForm" style="display:none" class="admin-inline-form">
|
||||
</div>
|
||||
<div id="adminProviderList"></div>
|
||||
</div>
|
||||
<div class="admin-tab-content" id="adminModelsTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminFetchModelsBtn">⟳ Fetch Models</button>
|
||||
<button class="btn-small" onclick="bulkSetVisibility('enabled')">All Enabled</button>
|
||||
<button class="btn-small" onclick="bulkSetVisibility('team')">All Team</button>
|
||||
<button class="btn-small" onclick="bulkSetVisibility('disabled')">All Disabled</button>
|
||||
<span class="admin-hint" id="adminModelsHint"></span>
|
||||
</div>
|
||||
<div id="adminModelList"></div>
|
||||
</div>
|
||||
<div class="admin-tab-content" id="adminPresetsTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddPresetBtn">+ New Global Preset</button>
|
||||
</div>
|
||||
<div id="adminAddPresetForm" style="display:none" class="admin-inline-form"></div>
|
||||
<div id="adminPresetList"></div>
|
||||
</div>
|
||||
<div class="admin-tab-content" id="adminTeamsTab" style="display:none">
|
||||
<div class="admin-section-content" id="adminTeamsTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddTeamBtn">+ New Team</button>
|
||||
</div>
|
||||
@@ -630,7 +604,6 @@
|
||||
<div class="form-row"><button class="btn-small btn-primary" id="adminCreateTeamBtn">Create</button><button class="btn-small" id="adminCancelTeamBtn">Cancel</button></div>
|
||||
</div>
|
||||
<div id="adminTeamList"></div>
|
||||
<!-- Team detail / member management (shown when editing a team) -->
|
||||
<div id="adminTeamDetail" style="display:none">
|
||||
<button class="notes-back-btn" id="adminTeamBackBtn">← Back to teams</button>
|
||||
<h3 id="adminTeamDetailName" style="margin:8px 0 12px;font-size:15px"></h3>
|
||||
@@ -653,7 +626,42 @@
|
||||
<div id="adminMemberList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-tab-content" id="adminSettingsTab" style="display:none">
|
||||
<div class="admin-section-content" id="adminRolesTab" style="display:none">
|
||||
<p style="color:var(--text-muted);margin:0 0 12px">Configure system model roles. Each role has a primary and optional fallback model.</p>
|
||||
<div id="adminRolesContent">
|
||||
<div class="loading">Loading roles...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI -->
|
||||
<div class="admin-section-content" id="adminProvidersTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddProviderBtn">+ Add Global Provider</button>
|
||||
</div>
|
||||
<div id="adminAddProviderForm" style="display:none" class="admin-inline-form">
|
||||
</div>
|
||||
<div id="adminProviderList"></div>
|
||||
</div>
|
||||
<div class="admin-section-content" id="adminModelsTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminFetchModelsBtn">⟳ Fetch Models</button>
|
||||
<button class="btn-small" onclick="bulkSetVisibility('enabled')">All Enabled</button>
|
||||
<button class="btn-small" onclick="bulkSetVisibility('team')">All Team</button>
|
||||
<button class="btn-small" onclick="bulkSetVisibility('disabled')">All Disabled</button>
|
||||
<span class="admin-hint" id="adminModelsHint"></span>
|
||||
</div>
|
||||
<div id="adminModelList"></div>
|
||||
</div>
|
||||
<div class="admin-section-content" id="adminPresetsTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminAddPresetBtn">+ New Global Persona</button>
|
||||
</div>
|
||||
<div id="adminAddPresetForm" style="display:none" class="admin-inline-form"></div>
|
||||
<div id="adminPresetList"></div>
|
||||
</div>
|
||||
|
||||
<!-- System -->
|
||||
<div class="admin-section-content" id="adminSettingsTab" style="display:none">
|
||||
<section class="settings-section">
|
||||
<h3>System Prompt</h3>
|
||||
<p class="section-hint" style="margin-bottom:6px">Injected into every conversation before user/preset prompts. Users cannot override or disable this.</p>
|
||||
@@ -678,9 +686,9 @@
|
||||
<p class="section-hint">When disabled, users can only use admin-configured global providers.</p>
|
||||
</section>
|
||||
<section class="settings-section">
|
||||
<h3>User Presets</h3>
|
||||
<label class="checkbox-label"><input type="checkbox" id="adminUserPresetsToggle" checked> Allow users to create personal presets</label>
|
||||
<p class="section-hint">When disabled, users can only use admin-created global presets.</p>
|
||||
<h3>User Personas</h3>
|
||||
<label class="checkbox-label"><input type="checkbox" id="adminUserPresetsToggle" checked> Allow users to create personal personas</label>
|
||||
<p class="section-hint">When disabled, users can only use admin-created global personas.</p>
|
||||
</section>
|
||||
<section class="settings-section">
|
||||
<h3>Default Model</h3>
|
||||
@@ -718,38 +726,8 @@
|
||||
</section>
|
||||
<button class="btn-primary btn-small" id="adminSaveSettings">Save Settings</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Roles Tab ─────────────────── -->
|
||||
<div class="admin-tab-content" id="adminRolesTab" style="display:none">
|
||||
<p style="color:var(--text-muted);margin:0 0 12px">Configure system model roles. Each role has a primary and optional fallback model.</p>
|
||||
<div id="adminRolesContent">
|
||||
<div class="loading">Loading roles...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Usage Tab ─────────────────── -->
|
||||
<div class="admin-tab-content" id="adminUsageTab" style="display:none">
|
||||
<div class="admin-toolbar" style="margin-bottom:12px">
|
||||
<select id="usagePeriod" style="min-width:100px">
|
||||
<option value="7d">Last 7 days</option>
|
||||
<option value="30d" selected>Last 30 days</option>
|
||||
<option value="90d">Last 90 days</option>
|
||||
</select>
|
||||
<select id="usageGroupBy" style="min-width:100px">
|
||||
<option value="model">By Model</option>
|
||||
<option value="user">By User</option>
|
||||
<option value="day">By Day</option>
|
||||
<option value="provider">By Provider</option>
|
||||
</select>
|
||||
<button class="btn-secondary btn-small" id="usageRefreshBtn">Refresh</button>
|
||||
</div>
|
||||
<div id="adminUsageTotals"></div>
|
||||
<div id="adminUsageResults"></div>
|
||||
<h3 style="margin-top:16px;font-size:14px">Model Pricing</h3>
|
||||
<div id="adminPricingTable"></div>
|
||||
</div>
|
||||
|
||||
<div class="admin-tab-content" id="adminExtensionsTab" style="display:none">
|
||||
<div class="admin-section-content" id="adminStorageTab" style="display:none"><div id="adminStorageContent"></div></div>
|
||||
<div class="admin-section-content" id="adminExtensionsTab" style="display:none">
|
||||
<div class="admin-toolbar">
|
||||
<button class="btn-small btn-primary" id="adminInstallExtBtn">+ Install Extension</button>
|
||||
</div>
|
||||
@@ -776,9 +754,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-tab-content" id="adminStorageTab" style="display:none"><div id="adminStorageContent"></div></div>
|
||||
<div class="admin-tab-content" id="adminStatsTab" style="display:none"><div id="adminStats"></div></div>
|
||||
<div class="admin-tab-content" id="adminAuditTab" style="display:none">
|
||||
|
||||
<!-- Monitoring -->
|
||||
<div class="admin-section-content" id="adminUsageTab" style="display:none">
|
||||
<div class="admin-toolbar" style="margin-bottom:12px">
|
||||
<select id="usagePeriod" style="min-width:100px">
|
||||
<option value="7d">Last 7 days</option>
|
||||
<option value="30d" selected>Last 30 days</option>
|
||||
<option value="90d">Last 90 days</option>
|
||||
</select>
|
||||
<select id="usageGroupBy" style="min-width:100px">
|
||||
<option value="model">By Model</option>
|
||||
<option value="user">By User</option>
|
||||
<option value="day">By Day</option>
|
||||
<option value="provider">By Provider</option>
|
||||
</select>
|
||||
<button class="btn-secondary btn-small" id="usageRefreshBtn">Refresh</button>
|
||||
</div>
|
||||
<div id="adminUsageTotals"></div>
|
||||
<div id="adminUsageResults"></div>
|
||||
<h3 style="margin-top:16px;font-size:14px">Model Pricing</h3>
|
||||
<div id="adminPricingTable"></div>
|
||||
</div>
|
||||
<div class="admin-section-content" id="adminAuditTab" style="display:none">
|
||||
<div class="admin-toolbar" style="margin-bottom:8px">
|
||||
<select id="auditFilterAction" style="min-width:140px"><option value="">All actions</option></select>
|
||||
<select id="auditFilterResource" style="min-width:120px">
|
||||
@@ -798,7 +796,8 @@
|
||||
<button class="btn-small" id="auditNextBtn">Next →</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-section-content" id="adminStatsTab" style="display:none"><div id="adminStats"></div></div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// ── Admin Actions ────────────────────────────
|
||||
|
||||
function _adminScroll() { return document.querySelector('#adminModal .modal-body'); }
|
||||
function _adminScroll() { return document.getElementById('adminMain'); }
|
||||
function _restoreScroll(el, pos) { if (el) requestAnimationFrame(() => { el.scrollTop = pos; }); }
|
||||
async function toggleUserActive(id, active) {
|
||||
try {
|
||||
@@ -403,10 +403,17 @@ async function settingsDeleteTeamPreset(teamId, presetId, name) {
|
||||
// ── Admin Listeners (extracted from initListeners) ──
|
||||
|
||||
function _initAdminListeners() {
|
||||
// Admin modal (elements may not exist for non-admin users)
|
||||
document.getElementById('adminCloseBtn')?.addEventListener('click', UI.closeAdmin);
|
||||
document.querySelectorAll('.admin-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => UI.switchAdminTab(tab.dataset.tab));
|
||||
// Admin panel navigation (elements may not exist for non-admin users)
|
||||
document.getElementById('adminBackBtn')?.addEventListener('click', UI.closeAdmin);
|
||||
document.querySelectorAll('.admin-cat').forEach(btn => {
|
||||
btn.addEventListener('click', () => UI.switchAdminCategory(btn.dataset.cat));
|
||||
});
|
||||
// Esc closes admin panel
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && document.getElementById('adminPanel')?.style.display === 'flex') {
|
||||
UI.closeAdmin();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
document.getElementById('adminSaveSettings')?.addEventListener('click', handleSaveAdminSettings);
|
||||
|
||||
|
||||
@@ -1,14 +1,131 @@
|
||||
// ==========================================
|
||||
// Chat Switchboard – UI Admin
|
||||
// ==========================================
|
||||
// Extends UI with admin modal tabs: users, stats, roles,
|
||||
// usage, audit, providers, models, presets, teams, settings.
|
||||
// Fullscreen admin panel with category + section navigation.
|
||||
// All loadAdmin*() functions unchanged — same endpoints, same DOM IDs.
|
||||
|
||||
// ── Category → Section mapping ────────────
|
||||
const ADMIN_SECTIONS = {
|
||||
people: ['users', 'teams'],
|
||||
ai: ['providers', 'models', 'presets', 'roles'],
|
||||
system: ['settings', 'storage', 'extensions'],
|
||||
monitoring: ['usage', 'audit', 'stats'],
|
||||
};
|
||||
|
||||
const ADMIN_LABELS = {
|
||||
users: 'Users', teams: 'Teams',
|
||||
providers: 'Providers', models: 'Models', presets: 'Personas', roles: 'Roles',
|
||||
settings: 'Settings', storage: 'Storage', extensions: 'Extensions',
|
||||
usage: 'Usage', audit: 'Audit', stats: 'Stats',
|
||||
};
|
||||
|
||||
// Section → loader mapping (reuses all existing loadAdmin* functions)
|
||||
const ADMIN_LOADERS = {
|
||||
users: () => UI.loadAdminUsers(),
|
||||
teams: () => UI.loadAdminTeams(),
|
||||
roles: () => UI.loadAdminRoles(),
|
||||
providers: () => UI.loadAdminProviders(),
|
||||
models: () => UI.loadAdminModels(),
|
||||
presets: () => UI.loadAdminPresets(),
|
||||
settings: () => UI.loadAdminSettings(),
|
||||
storage: () => typeof loadAdminStorage === 'function' ? loadAdminStorage() : null,
|
||||
extensions: () => UI.loadAdminExtensions(),
|
||||
usage: () => UI.loadAdminUsage(),
|
||||
audit: () => UI.loadAuditLog(),
|
||||
stats: () => UI.loadAdminStats(),
|
||||
};
|
||||
|
||||
// Find which category a section belongs to
|
||||
function _adminCatForSection(section) {
|
||||
for (const [cat, sections] of Object.entries(ADMIN_SECTIONS)) {
|
||||
if (sections.includes(section)) return cat;
|
||||
}
|
||||
return 'people';
|
||||
}
|
||||
|
||||
Object.assign(UI, {
|
||||
// ── Admin Modal ──────────────────────────
|
||||
// ── Admin Panel State ─────────────────────
|
||||
_adminCat: 'people',
|
||||
_adminSection: 'users',
|
||||
|
||||
openAdmin() { openModal('adminModal'); UI.switchAdminTab('users'); },
|
||||
closeAdmin() { closeModal('adminModal'); },
|
||||
// ── Open / Close ──────────────────────────
|
||||
openAdmin(cat, section) {
|
||||
UI._adminCat = cat || 'people';
|
||||
UI._adminSection = section || ADMIN_SECTIONS[UI._adminCat][0];
|
||||
const panel = document.getElementById('adminPanel');
|
||||
panel.style.display = 'flex';
|
||||
// Show current user email
|
||||
const userEl = document.getElementById('adminCurrentUser');
|
||||
if (userEl && API.user) userEl.textContent = API.user.email || API.user.username || '';
|
||||
UI._renderAdminNav();
|
||||
UI._switchAdminSection(UI._adminSection);
|
||||
},
|
||||
|
||||
closeAdmin() {
|
||||
document.getElementById('adminPanel').style.display = 'none';
|
||||
},
|
||||
|
||||
// ── Navigate to specific section ──────────
|
||||
// Called from command palette, deep links, etc.
|
||||
openAdminSection(section) {
|
||||
const cat = _adminCatForSection(section);
|
||||
UI.openAdmin(cat, section);
|
||||
},
|
||||
|
||||
// ── Category switch ───────────────────────
|
||||
switchAdminCategory(cat) {
|
||||
UI._adminCat = cat;
|
||||
UI._adminSection = ADMIN_SECTIONS[cat][0];
|
||||
UI._renderAdminNav();
|
||||
UI._switchAdminSection(UI._adminSection);
|
||||
},
|
||||
|
||||
// ── Render navigation state ───────────────
|
||||
_renderAdminNav() {
|
||||
// Highlight active category
|
||||
document.querySelectorAll('.admin-cat').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.cat === UI._adminCat);
|
||||
});
|
||||
// Populate sidebar
|
||||
const sidebar = document.getElementById('adminSidebar');
|
||||
sidebar.innerHTML = ADMIN_SECTIONS[UI._adminCat].map(sec =>
|
||||
`<button class="${sec === UI._adminSection ? 'active' : ''}" data-section="${sec}">${ADMIN_LABELS[sec]}</button>`
|
||||
).join('');
|
||||
// Wire sidebar clicks
|
||||
sidebar.querySelectorAll('button').forEach(btn => {
|
||||
btn.addEventListener('click', () => UI._switchAdminSection(btn.dataset.section));
|
||||
});
|
||||
},
|
||||
|
||||
// ── Section switch (shows content + lazy-loads) ──
|
||||
async _switchAdminSection(section) {
|
||||
UI._adminSection = section;
|
||||
// Update sidebar highlight
|
||||
document.querySelectorAll('.admin-sidebar button').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.section === section);
|
||||
});
|
||||
// Hide all, show target
|
||||
document.querySelectorAll('.admin-section-content').forEach(c => c.style.display = 'none');
|
||||
const panel = document.getElementById(`admin${section.charAt(0).toUpperCase() + section.slice(1)}Tab`);
|
||||
if (panel) panel.style.display = '';
|
||||
// Lazy-load data
|
||||
await ADMIN_LOADERS[section]?.();
|
||||
},
|
||||
|
||||
// ── Backward compat: switchAdminTab still works ──
|
||||
// (called by some inline onclick handlers and admin-handlers.js)
|
||||
async switchAdminTab(tab) {
|
||||
const cat = _adminCatForSection(tab);
|
||||
if (document.getElementById('adminPanel').style.display !== 'flex') {
|
||||
UI.openAdmin(cat, tab);
|
||||
} else {
|
||||
if (cat !== UI._adminCat) {
|
||||
UI._adminCat = cat;
|
||||
UI._renderAdminNav();
|
||||
}
|
||||
await UI._switchAdminSection(tab);
|
||||
}
|
||||
},
|
||||
|
||||
openTeamAdmin() {
|
||||
const adminTeams = (UI._myTeams || []).filter(t => t.my_role === 'admin');
|
||||
@@ -59,26 +176,6 @@ Object.assign(UI, {
|
||||
if (tab === 'activity') UI.loadTeamAuditLog(1);
|
||||
},
|
||||
|
||||
async switchAdminTab(tab) {
|
||||
document.querySelectorAll('#adminModal .admin-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));
|
||||
document.querySelectorAll('.admin-tab-content').forEach(c => c.style.display = 'none');
|
||||
const panel = document.getElementById(`admin${tab.charAt(0).toUpperCase() + tab.slice(1)}Tab`);
|
||||
if (panel) panel.style.display = '';
|
||||
|
||||
if (tab === 'users') await this.loadAdminUsers();
|
||||
if (tab === 'stats') await this.loadAdminStats();
|
||||
if (tab === 'audit') await this.loadAuditLog();
|
||||
if (tab === 'providers') await this.loadAdminProviders();
|
||||
if (tab === 'models') await this.loadAdminModels();
|
||||
if (tab === 'presets') await this.loadAdminPresets();
|
||||
if (tab === 'teams') await this.loadAdminTeams();
|
||||
if (tab === 'settings') await this.loadAdminSettings();
|
||||
if (tab === 'roles') await this.loadAdminRoles();
|
||||
if (tab === 'usage') await this.loadAdminUsage();
|
||||
if (tab === 'extensions') await this.loadAdminExtensions();
|
||||
if (tab === 'storage' && typeof loadAdminStorage === 'function') await loadAdminStorage();
|
||||
},
|
||||
|
||||
async loadAdminUsers(quiet) {
|
||||
const el = document.getElementById('adminUserList');
|
||||
if (!quiet) el.innerHTML = '<div class="loading">Loading...</div>';
|
||||
|
||||
@@ -441,14 +441,14 @@ function renderRoleConfig(containerEl, opts = {}) {
|
||||
// Reload after short delay so dropdowns reflect saved state
|
||||
setTimeout(() => refresh(), 500);
|
||||
} catch (e) {
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--error)'; }
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--danger)'; }
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTest(roleId) {
|
||||
if (!opts.onTest) return;
|
||||
const statusEl = containerEl.querySelector(`[data-role-status="${roleId}"]`);
|
||||
if (statusEl) { statusEl.textContent = 'Testing...'; statusEl.style.color = 'var(--text-muted)'; }
|
||||
if (statusEl) { statusEl.textContent = 'Testing...'; statusEl.style.color = 'var(--text-3)'; }
|
||||
try {
|
||||
const result = await opts.onTest(roleId);
|
||||
if (statusEl) {
|
||||
@@ -457,7 +457,7 @@ function renderRoleConfig(containerEl, opts = {}) {
|
||||
statusEl.style.color = 'var(--success)';
|
||||
}
|
||||
} catch (e) {
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--error)'; }
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--danger)'; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ Object.assign(UI, {
|
||||
|
||||
applyAppearance(scale, msgFont) {
|
||||
const z = scale === 100 ? '' : scale / 100;
|
||||
// Zoom content areas + modals (but NOT .app or banners)
|
||||
document.querySelectorAll('.sidebar, .chat-area, .modal-overlay').forEach(el => el.style.zoom = z);
|
||||
// Zoom content areas + modals + panels (but NOT .app or banners)
|
||||
document.querySelectorAll('.sidebar, .chat-area, .modal-overlay, .side-panel, .admin-panel').forEach(el => el.style.zoom = z);
|
||||
const splash = document.getElementById('splashGate');
|
||||
if (splash) splash.style.zoom = z;
|
||||
document.documentElement.style.setProperty('--msg-font', msgFont + 'px');
|
||||
|
||||
2582
src/js/ui.js
2582
src/js/ui.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user