Changeset 0.10.1 (#57)

This commit is contained in:
2026-02-24 16:51:08 +00:00
parent ea03f956ca
commit 13772ebd6c
11 changed files with 472 additions and 159 deletions

View File

@@ -95,6 +95,10 @@
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<span>Admin Panel</span>
</button>
<button class="flyout-item" id="menuTeamAdmin" style="display:none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<span>Team Management</span>
</button>
<button class="flyout-item" id="menuDebug">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 11V6a2 2 0 0 0-2-2h-1a2 2 0 0 0-2 2"/><path d="M9 6a2 2 0 0 0-2 2v3"/><path d="M4 14h4"/><path d="M16 14h4"/><path d="M12 18v-6"/><circle cx="12" cy="14" r="6"/></svg>
<span>Debug Log</span>
@@ -161,12 +165,21 @@
<!-- Side Panel (preview + notes) -->
<aside class="side-panel" id="sidePanel">
<div class="side-panel-resize" id="sidePanelResize"></div>
<div class="side-panel-header">
<div class="side-panel-tabs" id="sidePanelTabs">
<button class="side-panel-tab active" data-tab="preview" onclick="switchSidePanelTab('preview')">Preview</button>
<button class="side-panel-tab" data-tab="notes" onclick="switchSidePanelTab('notes')">Notes</button>
</div>
<button class="side-panel-close" onclick="closeSidePanel()" title="Close panel"></button>
<div class="side-panel-actions">
<button class="side-panel-action-btn" id="sidePanelClearBtn" onclick="clearPreview()" title="Clear preview" style="display:none">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>
</button>
<button class="side-panel-action-btn" id="sidePanelFullscreenBtn" onclick="toggleSidePanelFullscreen()" title="Toggle fullscreen">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
</button>
<button class="side-panel-close" onclick="closeSidePanel()" title="Close panel"></button>
</div>
</div>
<div class="side-panel-body">
<!-- Preview tab -->
@@ -321,8 +334,8 @@
<button class="settings-tab" data-stab="appearance" onclick="UI.switchSettingsTab('appearance')">Appearance</button>
<button class="settings-tab" data-stab="providers" onclick="UI.switchSettingsTab('providers')" id="settingsProvidersTabBtn">Providers</button>
<button class="settings-tab" data-stab="models" onclick="UI.switchSettingsTab('models')">Models</button>
<button class="settings-tab" data-stab="usage" onclick="UI.switchSettingsTab('usage')">Usage</button>
<button class="settings-tab" data-stab="teams" onclick="UI.switchSettingsTab('teams')" id="settingsTeamsTabBtn" style="display:none">Teams</button>
<button class="settings-tab" data-stab="personas" onclick="UI.switchSettingsTab('personas')" id="settingsPersonasTabBtn">Personas</button>
<button class="settings-tab" data-stab="usage" onclick="UI.switchSettingsTab('usage')" id="settingsUsageTabBtn">Usage</button>
</div>
<div class="modal-body">
<!-- General Tab -->
@@ -414,12 +427,15 @@
<p class="section-hint" style="margin-bottom:8px">Toggle visibility in your model selector. Hidden models are still accessible through presets.</p>
<div id="userModelList" class="model-list-grid"><div class="empty-hint">Loading models...</div></div>
</section>
</div>
<!-- Personas Tab (hidden if admin disables user presets) -->
<div class="settings-tab-content" id="settingsPersonasTab" style="display:none">
<section class="settings-section">
<h3 style="font-size:14px;margin-bottom:4px">My Presets</h3>
<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>
<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 Preset</button>
<button class="btn-small" id="userAddPresetBtn" style="margin-top:6px">+ New Persona</button>
</section>
</div>
<!-- Usage Tab -->
@@ -442,102 +458,114 @@
<div id="myUsageResults"></div>
</section>
</div>
<!-- Teams Tab (visible to team admins) -->
<div class="settings-tab-content" id="settingsTeamsTab" style="display:none">
<div id="settingsTeamPicker"></div>
<div id="settingsTeamManage" style="display:none">
<button class="notes-back-btn" id="settingsTeamBackBtn">← Back to teams</button>
<h3 id="settingsTeamManageName" style="margin:8px 0 4px;font-size:15px"></h3>
<!-- Members -->
<section class="settings-section">
<h4 style="font-size:13px;margin-bottom:6px">Members</h4>
<div id="settingsTeamMembers"></div>
<div id="settingsTeamAddMember" style="display:none;margin-top:8px" class="admin-inline-form">
<div class="form-row">
<div class="form-group"><label>User</label><select id="settingsTeamMemberUser"></select></div>
<div class="form-group"><label>Role</label><select id="settingsTeamMemberRole"><option value="member">Member</option><option value="admin">Team Admin</option></select></div>
</div>
<div class="form-row"><button class="btn-small btn-primary" id="settingsTeamAddMemberSubmit">Add</button><button class="btn-small" id="settingsTeamCancelMember">Cancel</button></div>
</div>
<button class="btn-small" id="settingsTeamAddMemberBtn" style="margin-top:6px">+ Add Member</button>
</section>
<!-- Team Providers -->
<section class="settings-section">
<h4 style="font-size:13px;margin-bottom:4px">Providers</h4>
<div id="settingsTeamProviders"></div>
<div id="settingsTeamEditProvider" style="display:none;margin-top:8px" class="admin-inline-form">
<div class="form-grid-2col">
<div class="form-group"><label>Name</label><input id="teamProviderEditName"></div>
<div class="form-group"><label>Endpoint</label><input id="teamProviderEditEndpoint"></div>
<div class="form-group"><label>API Key</label><input id="teamProviderEditKey" type="password" placeholder="(unchanged)"></div>
<div class="form-group"><label>Default Model</label><input id="teamProviderEditDefault" placeholder="optional"></div>
</div>
<div style="margin:6px 0"><label><input type="checkbox" id="teamProviderEditPrivate"> Private provider (data stays on-prem)</label></div>
<div class="form-row">
<button class="btn-small btn-primary" id="settingsTeamEditProviderSubmit">Update</button>
<button class="btn-small" id="settingsTeamCancelEditProvider">Cancel</button>
</div>
</div>
<div id="settingsTeamAddProvider" style="display:none;margin-top:8px" class="admin-inline-form">
<div class="form-grid-2col">
<div class="form-group"><label>Name</label><input id="teamProviderName" placeholder="e.g. Team OpenAI"></div>
<div class="form-group"><label>Provider</label><select id="teamProviderType"></select></div>
<div class="form-group"><label>Endpoint</label><input id="teamProviderEndpoint" placeholder="https://api.openai.com/v1"></div>
<div class="form-group"><label>API Key</label><input id="teamProviderKey" type="password" placeholder="sk-..."></div>
</div>
<div style="margin:6px 0"><label><input type="checkbox" id="teamProviderPrivate"> Private provider (data stays on-prem)</label></div>
<div class="form-row">
<button class="btn-small btn-primary" id="settingsTeamAddProviderSubmit">Add Provider</button>
<button class="btn-small" id="settingsTeamCancelProvider">Cancel</button>
</div>
</div>
<button class="btn-small" id="settingsTeamAddProviderBtn" style="margin-top:6px">+ Add Provider</button>
</section>
</div>
<div class="modal-footer"><button class="btn-primary" id="settingsSaveBtn">Save Settings</button></div>
</div>
</div>
<!-- Team Presets -->
<section class="settings-section">
<h4 style="font-size:13px;margin-bottom:6px">Team Presets</h4>
<div id="settingsTeamPresets"></div>
<div id="settingsTeamAddPreset" style="display:none;margin-top:8px" class="admin-inline-form"></div>
<button class="btn-small" id="settingsTeamAddPresetBtn" style="margin-top:6px">+ New Team Preset</button>
</section>
<!-- Team Usage (team admins only — usage against team-owned providers) -->
<section class="settings-section">
<h4 style="font-size:13px;margin-bottom:6px">Usage</h4>
<div class="form-row" style="gap:6px;margin-bottom:8px">
<select id="teamUsagePeriod" style="font-size:12px;padding:3px 6px" onchange="UI.loadTeamUsage()">
<option value="7d">7 days</option>
<option value="30d" selected>30 days</option>
<option value="90d">90 days</option>
</select>
<select id="teamUsageGroupBy" style="font-size:12px;padding:3px 6px" onchange="UI.loadTeamUsage()">
<option value="model">By Model</option>
<option value="user">By User</option>
<option value="day">By Day</option>
</select>
<!-- ── Team Management Modal ─────────────────── -->
<div class="modal-overlay" id="teamAdminModal">
<div class="modal modal-wide">
<div class="modal-header">
<h2 id="teamAdminTitle">Team Management</h2>
<button class="modal-close" id="teamAdminCloseBtn"></button>
</div>
<!-- Team picker (shown when user admins multiple teams) -->
<div id="teamAdminPicker" style="display:none">
<div class="modal-body" id="teamAdminPickerList"></div>
</div>
<!-- Tabbed management (shown after selecting a team) -->
<div id="teamAdminContent" style="display:none">
<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="usage" onclick="UI.switchTeamTab('usage')">Usage</button>
<button class="admin-tab" data-ttab="activity" onclick="UI.switchTeamTab('activity')">Activity</button>
</div>
<div class="modal-body">
<!-- Members tab -->
<div class="team-tab-content" id="teamTabMembers">
<div id="settingsTeamMembers"></div>
<div id="settingsTeamAddMember" style="display:none;margin-top:8px" class="admin-inline-form">
<div class="form-row">
<div class="form-group"><label>User</label><select id="settingsTeamMemberUser"></select></div>
<div class="form-group"><label>Role</label><select id="settingsTeamMemberRole"><option value="member">Member</option><option value="admin">Team Admin</option></select></div>
</div>
<div id="settingsTeamUsageTotals"></div>
<div id="settingsTeamUsageResults"></div>
</section>
<!-- Team Activity (team admins only — audit scoped to team) -->
<section class="settings-section">
<h4 style="font-size:13px;margin-bottom:6px">Activity Log</h4>
<div class="audit-filters" style="margin-bottom:8px">
<select id="teamAuditFilterAction" style="font-size:12px;padding:3px 6px" onchange="UI.loadTeamAuditLog()">
<option value="">All actions</option>
</select>
<div class="form-row"><button class="btn-small btn-primary" id="settingsTeamAddMemberSubmit">Add</button><button class="btn-small" id="settingsTeamCancelMember">Cancel</button></div>
</div>
<button class="btn-small" id="settingsTeamAddMemberBtn" style="margin-top:6px">+ Add Member</button>
</div>
<!-- Providers tab -->
<div class="team-tab-content" id="teamTabProviders" style="display:none">
<div id="settingsTeamProviders"></div>
<div id="settingsTeamEditProvider" style="display:none;margin-top:8px" class="admin-inline-form">
<div class="form-grid-2col">
<div class="form-group"><label>Name</label><input id="teamProviderEditName"></div>
<div class="form-group"><label>Endpoint</label><input id="teamProviderEditEndpoint"></div>
<div class="form-group"><label>API Key</label><input id="teamProviderEditKey" type="password" placeholder="(unchanged)"></div>
<div class="form-group"><label>Default Model</label><input id="teamProviderEditDefault" placeholder="optional"></div>
</div>
<div id="settingsTeamAudit" style="max-height:300px;overflow-y:auto"></div>
<div id="teamAuditPagination" class="audit-pagination" style="display:none">
<button class="btn-small" id="teamAuditPrevBtn" onclick="UI.loadTeamAuditLog(UI._teamAuditPage - 1)">← Prev</button>
<span id="teamAuditPageInfo" style="font-size:11px;color:var(--text-3)"></span>
<button class="btn-small" id="teamAuditNextBtn" onclick="UI.loadTeamAuditLog(UI._teamAuditPage + 1)">Next →</button>
<div style="margin:6px 0"><label><input type="checkbox" id="teamProviderEditPrivate"> Private provider (data stays on-prem)</label></div>
<div class="form-row">
<button class="btn-small btn-primary" id="settingsTeamEditProviderSubmit">Update</button>
<button class="btn-small" id="settingsTeamCancelEditProvider">Cancel</button>
</div>
</section>
</div>
<div id="settingsTeamAddProvider" style="display:none;margin-top:8px" class="admin-inline-form">
<div class="form-grid-2col">
<div class="form-group"><label>Name</label><input id="teamProviderName" placeholder="e.g. Team OpenAI"></div>
<div class="form-group"><label>Provider</label><select id="teamProviderType"></select></div>
<div class="form-group"><label>Endpoint</label><input id="teamProviderEndpoint" placeholder="https://api.openai.com/v1"></div>
<div class="form-group"><label>API Key</label><input id="teamProviderKey" type="password" placeholder="sk-..."></div>
</div>
<div style="margin:6px 0"><label><input type="checkbox" id="teamProviderPrivate"> Private provider (data stays on-prem)</label></div>
<div class="form-row">
<button class="btn-small btn-primary" id="settingsTeamAddProviderSubmit">Add Provider</button>
<button class="btn-small" id="settingsTeamCancelProvider">Cancel</button>
</div>
</div>
<button class="btn-small" id="settingsTeamAddProviderBtn" style="margin-top:6px">+ Add Provider</button>
</div>
<!-- Presets tab -->
<div class="team-tab-content" id="teamTabPresets" style="display:none">
<div id="settingsTeamPresets"></div>
<div id="settingsTeamAddPreset" style="display:none;margin-top:8px" class="admin-inline-form"></div>
<button class="btn-small" id="settingsTeamAddPresetBtn" style="margin-top:6px">+ New Team Preset</button>
</div>
<!-- Usage tab -->
<div class="team-tab-content" id="teamTabUsage" style="display:none">
<div class="form-row" style="gap:6px;margin-bottom:8px">
<select id="teamUsagePeriod" style="font-size:12px;padding:3px 6px" onchange="UI.loadTeamUsage()">
<option value="7d">7 days</option>
<option value="30d" selected>30 days</option>
<option value="90d">90 days</option>
</select>
<select id="teamUsageGroupBy" style="font-size:12px;padding:3px 6px" onchange="UI.loadTeamUsage()">
<option value="model">By Model</option>
<option value="user">By User</option>
<option value="day">By Day</option>
</select>
</div>
<div id="settingsTeamUsageTotals"></div>
<div id="settingsTeamUsageResults"></div>
</div>
<!-- Activity tab -->
<div class="team-tab-content" id="teamTabActivity" style="display:none">
<div class="audit-filters" style="margin-bottom:8px">
<select id="teamAuditFilterAction" style="font-size:12px;padding:3px 6px" onchange="UI.loadTeamAuditLog()">
<option value="">All actions</option>
</select>
</div>
<div id="settingsTeamAudit" style="max-height:400px;overflow-y:auto"></div>
<div id="teamAuditPagination" class="audit-pagination" style="display:none">
<button class="btn-small" id="teamAuditPrevBtn" onclick="UI.loadTeamAuditLog(UI._teamAuditPage - 1)">← Prev</button>
<span id="teamAuditPageInfo" style="font-size:11px;color:var(--text-3)"></span>
<button class="btn-small" id="teamAuditNextBtn" onclick="UI.loadTeamAuditLog(UI._teamAuditPage + 1)">Next →</button>
</div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn-primary" id="settingsSaveBtn">Save Settings</button></div>
</div>
</div>
@@ -647,6 +675,13 @@
</div>
</div>
<div class="admin-tab-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>
<div class="form-group">
<textarea id="adminSystemPrompt" rows="4" placeholder="e.g. You are a helpful assistant for Acme Corp. Always respond professionally."></textarea>
</div>
</section>
<section class="settings-section">
<h3>Registration</h3>
<label class="checkbox-label"><input type="checkbox" id="adminRegToggle" checked> Allow new user registration</label>