Changeset 0.22.7 (#149)

This commit is contained in:
2026-03-04 10:44:42 +00:00
parent d8e0664fa3
commit 389e47b0f9
62 changed files with 6820 additions and 1476 deletions

View File

@@ -2,7 +2,7 @@
// Chat Switchboard Admin Handlers
// ==========================================
// Admin actions: user management, roles, model visibility,
// presets, team management, global providers.
// personas, team management, global providers.
// ── Admin Actions ────────────────────────────
@@ -155,7 +155,7 @@ async function toggleUserModelVisibility(modelId, currentlyHidden) {
}
async function bulkSetUserModelVisibility(show) {
const models = App.models.filter(m => !m.isPreset);
const models = App.models.filter(m => !m.isPersona);
if (!models.length) return;
const shouldHide = !show;
// Only update models not already in the desired state
@@ -172,41 +172,41 @@ async function bulkSetUserModelVisibility(show) {
} catch (e) { UI.toast(e.message, 'error'); }
}
async function deleteUserPreset(id, name) {
if (!await showConfirm(`Delete preset "${name}"?`)) return;
async function deleteUserPersona(id, name) {
if (!await showConfirm(`Delete persona "${name}"?`)) return;
try {
await API.deleteUserPreset(id);
UI.toast('Preset deleted');
await UI.loadUserPresets();
await API.deleteUserPersona(id);
UI.toast('Persona deleted');
await UI.loadUserPersonas();
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
// ── Admin Presets ────────────────────────────
// ── Admin Personas ────────────────────────────
var _adminPresetForm = null;
function ensureAdminPresetForm() {
if (_adminPresetForm) return _adminPresetForm;
const container = document.getElementById('adminAddPresetForm');
var _adminPersonaForm = null;
function ensureAdminPersonaForm() {
if (_adminPersonaForm) return _adminPersonaForm;
const container = document.getElementById('adminAddPersonaForm');
if (!container) return null;
_adminPresetForm = renderPresetForm(container, {
prefix: 'adminPreset',
_adminPersonaForm = renderPersonaForm(container, {
prefix: 'adminPersona',
showAvatar: true,
showProviderConfig: true,
showKBPicker: true,
kbScope: 'admin',
onSubmit: (vals) => createAdminPreset(vals),
onSubmit: (vals) => createAdminPersona(vals),
onCancel: () => {
container.style.display = 'none';
_editingPresetId = null;
_adminPresetForm.setSubmitLabel('Create');
_adminPresetForm.clearForm();
_editingPersonaId = null;
_adminPersonaForm.setSubmitLabel('Create');
_adminPersonaForm.clearForm();
}
});
// Override avatar handlers for edit-mode (upload immediately for existing presets)
const fileInput = document.getElementById('adminPreset_avatarFileInput');
// Override avatar handlers for edit-mode (upload immediately for existing personas)
const fileInput = document.getElementById('adminPersona_avatarFileInput');
if (fileInput) {
// Remove default handler set by renderPresetForm, add edit-aware one
// Remove default handler set by renderPersonaForm, add edit-aware one
const newInput = fileInput.cloneNode(true);
fileInput.parentNode.replaceChild(newInput, fileInput);
newInput.addEventListener('change', async function() {
@@ -214,55 +214,55 @@ function ensureAdminPresetForm() {
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
if (_editingPresetId) {
if (_editingPersonaId) {
try {
const data = await API.adminUploadPresetAvatar(_editingPresetId, reader.result);
const data = await API.adminUploadPersonaAvatar(_editingPersonaId, reader.result);
if (data.avatar) {
_adminPresetForm.updateAvatarPreview(data.avatar);
await UI.loadAdminPresets(true);
_adminPersonaForm.updateAvatarPreview(data.avatar);
await UI.loadAdminPersonas(true);
await fetchModels();
UI.toast('Preset avatar updated');
UI.toast('Persona avatar updated');
}
} catch (e) { UI.toast(e.message || 'Upload failed', 'error'); }
} else {
_adminPresetForm.updateAvatarPreview(reader.result);
_adminPersonaForm.updateAvatarPreview(reader.result);
}
};
reader.readAsDataURL(file);
this.value = '';
});
document.getElementById('adminPreset_avatarUploadBtn')?.addEventListener('click', () => newInput.click());
document.getElementById('adminPersona_avatarUploadBtn')?.addEventListener('click', () => newInput.click());
}
const removeBtn = document.getElementById('adminPreset_avatarRemoveBtn');
const removeBtn = document.getElementById('adminPersona_avatarRemoveBtn');
if (removeBtn) {
const newBtn = removeBtn.cloneNode(true);
removeBtn.parentNode.replaceChild(newBtn, removeBtn);
newBtn.addEventListener('click', async () => {
if (_editingPresetId) {
if (_editingPersonaId) {
try {
await API.adminDeletePresetAvatar(_editingPresetId);
_adminPresetForm.updateAvatarPreview(null);
await UI.loadAdminPresets(true);
await API.adminDeletePersonaAvatar(_editingPersonaId);
_adminPersonaForm.updateAvatarPreview(null);
await UI.loadAdminPersonas(true);
await fetchModels();
UI.toast('Preset avatar removed');
UI.toast('Persona avatar removed');
} catch (e) { UI.toast(e.message || 'Remove failed', 'error'); }
} else {
_adminPresetForm.updateAvatarPreview(null);
_adminPersonaForm.updateAvatarPreview(null);
}
});
}
return _adminPresetForm;
return _adminPersonaForm;
}
var _editingPresetId = null;
function editAdminPreset(id) {
const p = UI._presetCache?.[id];
var _editingPersonaId = null;
function editAdminPersona(id) {
const p = UI._personaCache?.[id];
if (!p) return;
_editingPresetId = id;
ensureAdminPresetForm();
const form = _adminPresetForm;
_editingPersonaId = id;
ensureAdminPersonaForm();
const form = _adminPersonaForm;
if (!form) return;
document.getElementById('adminAddPresetForm').style.display = '';
document.getElementById('adminAddPersonaForm').style.display = '';
form.setValues(p);
form.setSubmitLabel('Update');
// Load KB bindings (v0.17.0)
@@ -271,73 +271,73 @@ function editAdminPreset(id) {
}
}
async function createAdminPreset(vals) {
async function createAdminPersona(vals) {
if (!vals) {
if (_adminPresetForm) vals = _adminPresetForm.getValues();
if (_adminPersonaForm) vals = _adminPersonaForm.getValues();
else return;
}
if (!vals.name || !vals.base_model_id) { UI.toast('Name and base model are required', 'warning'); return; }
const preset = {
const persona = {
name: vals.name,
base_model_id: vals.base_model_id,
description: vals.description || '',
system_prompt: vals.system_prompt || '',
};
if (vals.provider_config_id) preset.provider_config_id = vals.provider_config_id;
if (vals.temperature != null) preset.temperature = vals.temperature;
if (vals.max_tokens != null) preset.max_tokens = vals.max_tokens;
if (vals.provider_config_id) persona.provider_config_id = vals.provider_config_id;
if (vals.temperature != null) persona.temperature = vals.temperature;
if (vals.max_tokens != null) persona.max_tokens = vals.max_tokens;
try {
let presetId = _editingPresetId;
if (_editingPresetId) {
await API.adminUpdatePreset(_editingPresetId, preset);
UI.toast('Preset updated', 'success');
let personaId = _editingPersonaId;
if (_editingPersonaId) {
await API.adminUpdatePersona(_editingPersonaId, preset);
UI.toast('Persona updated', 'success');
} else {
const result = await API.adminCreatePreset(preset);
presetId = result?.id || result?.preset?.id;
UI.toast('Preset created', 'success');
const result = await API.adminCreatePersona(preset);
personaId = result?.id || result?.preset?.id;
UI.toast('Persona created', 'success');
}
// Upload pending avatar for new presets
if (!_editingPresetId && presetId && vals._pendingAvatar) {
try { await API.adminUploadPresetAvatar(presetId, vals._pendingAvatar); }
catch (e) { console.warn('Preset avatar upload failed:', e.message); }
// Upload pending avatar for new personas
if (!_editingPersonaId && personaId && vals._pendingAvatar) {
try { await API.adminUploadPersonaAvatar(personaId, vals._pendingAvatar); }
catch (e) { console.warn('Persona avatar upload failed:', e.message); }
}
// Save KB bindings (v0.17.0)
if (presetId && vals._kbValues && _adminPresetForm?.kbPicker) {
try { await API.adminSetPresetKBs(presetId, vals._kbValues); }
catch (e) { console.warn('Preset KB binding failed:', e.message); }
if (personaId && vals._kbValues && _adminPersonaForm?.kbPicker) {
try { await API.adminSetPresetKBs(personaId, vals._kbValues); }
catch (e) { console.warn('Persona KB binding failed:', e.message); }
}
_editingPresetId = null;
document.getElementById('adminAddPresetForm').style.display = 'none';
if (_adminPresetForm) {
_adminPresetForm.setSubmitLabel('Create');
_adminPresetForm.clearForm();
_editingPersonaId = null;
document.getElementById('adminAddPersonaForm').style.display = 'none';
if (_adminPersonaForm) {
_adminPersonaForm.setSubmitLabel('Create');
_adminPersonaForm.clearForm();
}
await UI.loadAdminPresets();
await UI.loadAdminPersonas();
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
async function toggleAdminPreset(id, active) {
async function toggleAdminPersona(id, active) {
try {
const el = _adminScroll(), pos = el?.scrollTop || 0;
await API.adminUpdatePreset(id, { is_active: active });
await UI.loadAdminPresets(true);
await API.adminUpdatePersona(id, { is_active: active });
await UI.loadAdminPersonas(true);
_restoreScroll(el, pos);
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
async function deleteAdminPreset(id, name) {
if (!await showConfirm(`Delete preset "${name}"?`)) return;
async function deleteAdminPersona(id, name) {
if (!await showConfirm(`Delete persona "${name}"?`)) return;
try {
await API.adminDeletePreset(id);
UI.toast('Preset deleted', 'success');
await UI.loadAdminPresets();
await API.adminDeletePersona(id);
UI.toast('Persona deleted', 'success');
await UI.loadAdminPersonas();
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
@@ -400,12 +400,12 @@ async function settingsRemoveTeamMember(teamId, memberId, email) {
} catch (e) { UI.toast(e.message, 'error'); }
}
async function settingsDeleteTeamPreset(teamId, presetId, name) {
if (!await showConfirm(`Delete team preset "${name}"?`)) return;
async function settingsDeleteTeamPersona(teamId, personaId, name) {
if (!await showConfirm(`Delete team persona "${name}"?`)) return;
try {
await API.teamDeletePreset(teamId, presetId);
UI.toast('Preset deleted');
await UI.loadTeamManagePresets(teamId);
await API.teamDeletePreset(teamId, personaId);
UI.toast('Persona deleted');
await UI.loadTeamManagePersonas(teamId);
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
}
@@ -483,14 +483,14 @@ function _initAdminListeners() {
// Admin — models
document.getElementById('adminFetchModelsBtn')?.addEventListener('click', fetchAdminModels);
// Admin — presets (shared form)
document.getElementById('adminAddPresetBtn')?.addEventListener('click', () => {
const form = ensureAdminPresetForm();
// Admin — personas (shared form)
document.getElementById('adminAddPersonaBtn')?.addEventListener('click', () => {
const form = ensureAdminPersonaForm();
if (!form) return;
_editingPresetId = null;
_editingPersonaId = null;
form.setSubmitLabel('Create');
form.clearForm();
const f = document.getElementById('adminAddPresetForm');
const f = document.getElementById('adminAddPersonaForm');
f.style.display = f.style.display === 'none' ? '' : 'none';
});