Changeset 0.8.4 (#47)

This commit is contained in:
2026-02-22 11:23:29 +00:00
parent e78a27c06e
commit f9f362c900
7 changed files with 361 additions and 219 deletions

View File

@@ -130,32 +130,6 @@ function updateAvatarPreview(dataURI) {
}
}
// Preset avatar preview (admin form)
function updatePresetAvatarPreview(dataURI) {
const preview = document.getElementById('presetAvatarPreview');
const placeholder = document.getElementById('presetAvatarPlaceholder');
const removeBtn = document.getElementById('presetAvatarRemoveBtn');
if (!preview) return;
const existingImg = preview.querySelector('img');
if (dataURI) {
placeholder.style.display = 'none';
if (existingImg) {
existingImg.src = dataURI;
} else {
const img = document.createElement('img');
img.src = dataURI;
img.alt = 'Preset avatar';
preview.insertBefore(img, placeholder);
}
removeBtn.style.display = '';
} else {
if (existingImg) existingImg.remove();
placeholder.style.display = '';
removeBtn.style.display = 'none';
}
}
// ── Models ───────────────────────────────────
// Client-side known model capabilities — used when backend hasn't synced yet.
@@ -997,72 +971,16 @@ function initListeners() {
// Admin — models
document.getElementById('adminFetchModelsBtn')?.addEventListener('click', fetchAdminModels);
// Admin — presets
// Admin — presets (shared form)
document.getElementById('adminAddPresetBtn')?.addEventListener('click', () => {
const form = ensureAdminPresetForm();
if (!form) return;
_editingPresetId = null;
document.getElementById('adminCreatePresetBtn').textContent = 'Create';
document.getElementById('adminPresetName').value = '';
document.getElementById('adminPresetDesc').value = '';
document.getElementById('adminPresetPrompt').value = '';
document.getElementById('adminPresetIcon').value = '';
document.getElementById('adminPresetTemp').value = '';
document.getElementById('adminPresetMaxTokens').value = '';
updatePresetAvatarPreview(null);
form.setSubmitLabel('Create');
form.clearForm();
const f = document.getElementById('adminAddPresetForm');
f.style.display = f.style.display === 'none' ? '' : 'none';
});
document.getElementById('adminCancelPresetBtn')?.addEventListener('click', () => {
document.getElementById('adminAddPresetForm').style.display = 'none';
_editingPresetId = null;
document.getElementById('adminCreatePresetBtn').textContent = 'Create';
updatePresetAvatarPreview(null);
});
document.getElementById('adminCreatePresetBtn')?.addEventListener('click', createAdminPreset);
// Admin — preset avatar
document.getElementById('presetAvatarUploadBtn')?.addEventListener('click', () => {
document.getElementById('presetAvatarFileInput').click();
});
document.getElementById('presetAvatarFileInput')?.addEventListener('change', async function() {
const file = this.files[0];
if (!file) return;
if (!_editingPresetId) {
// For new presets: show preview, upload after create
const reader = new FileReader();
reader.onload = () => updatePresetAvatarPreview(reader.result);
reader.readAsDataURL(file);
this.value = '';
return;
}
// For existing presets: upload immediately
const reader = new FileReader();
reader.onload = async () => {
try {
const data = await API.adminUploadPresetAvatar(_editingPresetId, reader.result);
if (data.avatar) {
updatePresetAvatarPreview(data.avatar);
await UI.loadAdminPresets(true);
await fetchModels();
UI.toast('Preset avatar updated');
}
} catch (e) { UI.toast(e.message || 'Upload failed', 'error'); }
};
reader.readAsDataURL(file);
this.value = '';
});
document.getElementById('presetAvatarRemoveBtn')?.addEventListener('click', async () => {
if (!_editingPresetId) {
updatePresetAvatarPreview(null);
return;
}
try {
await API.adminDeletePresetAvatar(_editingPresetId);
updatePresetAvatarPreview(null);
await UI.loadAdminPresets(true);
await fetchModels();
UI.toast('Preset avatar removed');
} catch (e) { UI.toast(e.message || 'Remove failed', 'error'); }
});
// Admin — Teams
document.getElementById('adminAddTeamBtn')?.addEventListener('click', () => {
@@ -1138,38 +1056,41 @@ function initListeners() {
document.getElementById('settingsTeamCancelMember')?.addEventListener('click', () => {
document.getElementById('settingsTeamAddMember').style.display = 'none';
});
var _teamPresetForm = null;
document.getElementById('settingsTeamAddPresetBtn')?.addEventListener('click', () => {
document.getElementById('settingsTeamAddPreset').style.display = '';
const container = document.getElementById('settingsTeamAddPreset');
container.style.display = '';
if (!_teamPresetForm) {
_teamPresetForm = renderPresetForm(container, {
prefix: 'teamPreset',
showAvatar: true,
showProviderConfig: false,
onSubmit: async (vals) => {
const teamId = UI._managingTeamId;
if (!vals.name) return UI.toast('Name required', 'error');
if (!vals.base_model_id) return UI.toast('Select a base model', 'error');
try {
const result = await API.teamCreatePreset(teamId, vals);
// Upload pending avatar
const presetId = result?.id;
if (presetId && vals._pendingAvatar) {
try { await API.adminUploadPresetAvatar(presetId, vals._pendingAvatar); }
catch (e) { console.warn('Team preset avatar upload failed:', e.message); }
}
container.style.display = 'none';
_teamPresetForm.clearForm();
UI.toast('Team preset created');
await UI.loadTeamManagePresets(teamId);
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }
},
onCancel: () => { container.style.display = 'none'; _teamPresetForm.clearForm(); }
});
} else {
_teamPresetForm.clearForm();
}
UI.loadTeamPresetModelDropdown(UI._managingTeamId);
});
document.getElementById('settingsTeamCancelPreset')?.addEventListener('click', () => {
document.getElementById('settingsTeamAddPreset').style.display = 'none';
});
document.getElementById('settingsTeamCreatePresetBtn')?.addEventListener('click', async () => {
const teamId = UI._managingTeamId;
const name = document.getElementById('settingsTeamPresetName').value.trim();
const baseModelId = document.getElementById('settingsTeamPresetModel').value;
if (!name) return UI.toast('Name required', 'error');
if (!baseModelId) return UI.toast('Select a base model', 'error');
const preset = {
name,
base_model_id: baseModelId,
description: document.getElementById('settingsTeamPresetDesc').value.trim(),
icon: document.getElementById('settingsTeamPresetIcon').value.trim(),
system_prompt: document.getElementById('settingsTeamPresetPrompt').value,
temperature: parseFloat(document.getElementById('settingsTeamPresetTemp').value) || undefined,
max_tokens: parseInt(document.getElementById('settingsTeamPresetMaxTokens').value) || undefined,
};
try {
await API.teamCreatePreset(teamId, preset);
document.getElementById('settingsTeamAddPreset').style.display = 'none';
['settingsTeamPresetName','settingsTeamPresetDesc','settingsTeamPresetIcon','settingsTeamPresetPrompt','settingsTeamPresetTemp','settingsTeamPresetMaxTokens']
.forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });
UI.toast('Team preset created');
await UI.loadTeamManagePresets(teamId);
await fetchModels(); // refresh model selector
} catch (e) { UI.toast(e.message, 'error'); }
});
// Admin — banner controls
document.getElementById('adminBannerEnabled')?.addEventListener('change', (e) => {
@@ -1650,50 +1571,103 @@ async function bulkSetVisibility(visibility) {
// ── Admin Presets ────────────────────────────
var _adminPresetForm = null;
function ensureAdminPresetForm() {
if (_adminPresetForm) return _adminPresetForm;
const container = document.getElementById('adminAddPresetForm');
if (!container) return null;
_adminPresetForm = renderPresetForm(container, {
prefix: 'adminPreset',
showAvatar: true,
showProviderConfig: true,
onSubmit: (vals) => createAdminPreset(vals),
onCancel: () => {
container.style.display = 'none';
_editingPresetId = null;
_adminPresetForm.setSubmitLabel('Create');
_adminPresetForm.clearForm();
}
});
// Override avatar handlers for edit-mode (upload immediately for existing presets)
const fileInput = document.getElementById('adminPreset_avatarFileInput');
if (fileInput) {
// Remove default handler set by renderPresetForm, add edit-aware one
const newInput = fileInput.cloneNode(true);
fileInput.parentNode.replaceChild(newInput, fileInput);
newInput.addEventListener('change', async function() {
const file = this.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
if (_editingPresetId) {
try {
const data = await API.adminUploadPresetAvatar(_editingPresetId, reader.result);
if (data.avatar) {
_adminPresetForm.updateAvatarPreview(data.avatar);
await UI.loadAdminPresets(true);
await fetchModels();
UI.toast('Preset avatar updated');
}
} catch (e) { UI.toast(e.message || 'Upload failed', 'error'); }
} else {
_adminPresetForm.updateAvatarPreview(reader.result);
}
};
reader.readAsDataURL(file);
this.value = '';
});
document.getElementById('adminPreset_avatarUploadBtn')?.addEventListener('click', () => newInput.click());
}
const removeBtn = document.getElementById('adminPreset_avatarRemoveBtn');
if (removeBtn) {
const newBtn = removeBtn.cloneNode(true);
removeBtn.parentNode.replaceChild(newBtn, removeBtn);
newBtn.addEventListener('click', async () => {
if (_editingPresetId) {
try {
await API.adminDeletePresetAvatar(_editingPresetId);
_adminPresetForm.updateAvatarPreview(null);
await UI.loadAdminPresets(true);
await fetchModels();
UI.toast('Preset avatar removed');
} catch (e) { UI.toast(e.message || 'Remove failed', 'error'); }
} else {
_adminPresetForm.updateAvatarPreview(null);
}
});
}
return _adminPresetForm;
}
var _editingPresetId = null;
function editAdminPreset(id) {
const p = UI._presetCache?.[id];
if (!p) return;
_editingPresetId = id;
const form = document.getElementById('adminAddPresetForm');
form.style.display = '';
document.getElementById('adminPresetName').value = p.name || '';
document.getElementById('adminPresetIcon').value = p.icon || '';
document.getElementById('adminPresetDesc').value = p.description || '';
document.getElementById('adminPresetPrompt').value = p.system_prompt || '';
document.getElementById('adminPresetTemp').value = p.temperature != null ? p.temperature : '';
document.getElementById('adminPresetMaxTokens').value = p.max_tokens != null ? p.max_tokens : '';
// Set base model and config dropdowns if present
const modelSel = document.getElementById('adminPresetModel');
if (modelSel) modelSel.value = p.base_model_id || '';
const cfgSel = document.getElementById('adminPresetConfig');
if (cfgSel) cfgSel.value = p.api_config_id || '';
// Avatar preview
updatePresetAvatarPreview(p.avatar || null);
document.getElementById('adminCreatePresetBtn').textContent = 'Update';
ensureAdminPresetForm();
const form = _adminPresetForm;
if (!form) return;
document.getElementById('adminAddPresetForm').style.display = '';
form.setValues(p);
form.setSubmitLabel('Update');
}
async function createAdminPreset() {
const name = document.getElementById('adminPresetName').value.trim();
const baseModelId = document.getElementById('adminPresetModel').value;
if (!name || !baseModelId) { UI.toast('Name and base model are required', 'warning'); return; }
async function createAdminPreset(vals) {
if (!vals) {
if (_adminPresetForm) vals = _adminPresetForm.getValues();
else return;
}
if (!vals.name || !vals.base_model_id) { UI.toast('Name and base model are required', 'warning'); return; }
const preset = {
name,
base_model_id: baseModelId,
description: document.getElementById('adminPresetDesc').value.trim(),
icon: document.getElementById('adminPresetIcon').value.trim(),
system_prompt: document.getElementById('adminPresetPrompt').value,
name: vals.name,
base_model_id: vals.base_model_id,
description: vals.description || '',
system_prompt: vals.system_prompt || '',
};
const configId = document.getElementById('adminPresetConfig').value;
if (configId) preset.api_config_id = configId;
const temp = parseFloat(document.getElementById('adminPresetTemp').value);
if (!isNaN(temp)) preset.temperature = temp;
const maxTok = parseInt(document.getElementById('adminPresetMaxTokens').value);
if (!isNaN(maxTok) && maxTok > 0) preset.max_tokens = maxTok;
if (vals.api_config_id) preset.api_config_id = vals.api_config_id;
if (vals.temperature != null) preset.temperature = vals.temperature;
if (vals.max_tokens != null) preset.max_tokens = vals.max_tokens;
try {
let presetId = _editingPresetId;
@@ -1707,23 +1681,17 @@ async function createAdminPreset() {
}
// Upload pending avatar for new presets
const pendingImg = document.querySelector('#presetAvatarPreview img');
if (!_editingPresetId && presetId && pendingImg?.src?.startsWith('data:')) {
try {
await API.adminUploadPresetAvatar(presetId, pendingImg.src);
} catch (e) { console.warn('Preset avatar upload failed:', e.message); }
if (!_editingPresetId && presetId && vals._pendingAvatar) {
try { await API.adminUploadPresetAvatar(presetId, vals._pendingAvatar); }
catch (e) { console.warn('Preset avatar upload failed:', e.message); }
}
_editingPresetId = null;
document.getElementById('adminAddPresetForm').style.display = 'none';
document.getElementById('adminCreatePresetBtn').textContent = 'Create';
document.getElementById('adminPresetName').value = '';
document.getElementById('adminPresetDesc').value = '';
document.getElementById('adminPresetPrompt').value = '';
document.getElementById('adminPresetIcon').value = '';
document.getElementById('adminPresetTemp').value = '';
document.getElementById('adminPresetMaxTokens').value = '';
updatePresetAvatarPreview(null);
if (_adminPresetForm) {
_adminPresetForm.setSubmitLabel('Create');
_adminPresetForm.clearForm();
}
await UI.loadAdminPresets();
await fetchModels();
} catch (e) { UI.toast(e.message, 'error'); }