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

@@ -15,22 +15,22 @@ function avatarHTML(role, avatarDataURI) {
return role === 'user' ? '👤' : '🤖';
}
// Look up the current model/preset avatar for assistant messages.
// Look up the current model/persona avatar for assistant messages.
function assistantAvatarURI(msg) {
// Try to find preset avatar from the model that generated this message
const modelId = msg?.model || msg?.preset_id;
// Try to find persona avatar from the model that generated this message
const modelId = msg?.model || msg?.persona_id;
if (modelId) {
const m = App.models.find(x => x.id === modelId || x.presetId === modelId);
if (m?.presetAvatar) return m.presetAvatar;
const m = App.models.find(x => x.id === modelId || x.personaId === modelId);
if (m?.personaAvatar) return m.personaAvatar;
}
return null;
}
// ── Shared Preset Form Component ────────────
// Renders a preset creation/edit form into a container element.
// ── Shared Persona Form Component ────────────
// Renders a persona creation/edit form into a container element.
// Options: { prefix, showAvatar, showProviderConfig, onSubmit, onCancel }
// Returns: { getValues(), setValues(preset), clearForm(), container }
function renderPresetForm(containerEl, options = {}) {
// Returns: { getValues(), setValues(persona), clearForm(), container }
function renderPersonaForm(containerEl, options = {}) {
const pfx = options.prefix || 'pf';
const showAvatar = options.showAvatar !== false;
const showConfig = !!options.showProviderConfig;
@@ -164,7 +164,7 @@ function renderPresetForm(containerEl, options = {}) {
if (existing) { existing.src = dataURI; }
else {
const img = document.createElement('img');
img.src = dataURI; img.alt = 'Preset avatar';
img.src = dataURI; img.alt = 'Persona avatar';
preview.insertBefore(img, placeholder);
}
if (removeBtn) removeBtn.style.display = '';
@@ -597,7 +597,7 @@ const UI = {
const label = modelName || 'Assistant';
const currentModel = App.findModel(App.settings.model);
const streamAvatar = currentModel?.presetAvatar || null;
const streamAvatar = currentModel?.personaAvatar || null;
// Multi-model state: when model_start events arrive, we create
// separate message bubbles per model. Without them, single-bubble.
@@ -836,10 +836,10 @@ const UI = {
menu.innerHTML = '<div class="model-dropdown-item" style="color:var(--text-3);cursor:default">No models loaded</div>';
UI.setModelValue('', 'No models loaded');
} else {
const globalPresets = App.models.filter(m => m.isPreset && m.presetScope === 'global');
const teamPresets = App.models.filter(m => m.isPreset && m.presetScope === 'team');
const personalPresets = App.models.filter(m => m.isPreset && m.presetScope === 'personal');
const models = App.models.filter(m => !m.isPreset && !m.hidden);
const globalPersonas = App.models.filter(m => m.isPersona && m.personaScope === 'global');
const teamPersonas = App.models.filter(m => m.isPersona && m.personaScope === 'team');
const personalPresets = App.models.filter(m => m.isPersona && m.personaScope === 'personal');
const models = App.models.filter(m => !m.isPersona && !m.hidden);
const addGroup = (label, items) => {
if (items.length === 0) return;
@@ -850,22 +850,22 @@ const UI = {
items.forEach(m => menu.appendChild(UI._createDropdownItem(m)));
};
addGroup('⚡ Presets', globalPresets);
addGroup('⚡ Personas', globalPersonas);
// Group team presets by team name
// Group team personas by team name
const teamGroups = {};
teamPresets.forEach(m => {
const tn = m.presetTeamName || 'Team';
teamPersonas.forEach(m => {
const tn = m.personaTeamName || 'Team';
(teamGroups[tn] = teamGroups[tn] || []).push(m);
});
Object.keys(teamGroups).sort().forEach(tn => {
addGroup(`👥 ${tn}`, teamGroups[tn]);
});
addGroup('🔧 My Presets', personalPresets);
addGroup('🔧 My Personas', personalPresets);
// No team model groups — team providers are for preset building only.
// Team members access team models through curated presets.
// No team model groups — team providers are for persona building only.
// Team members access team models through curated personas.
addGroup('Models', models.filter(m => m.source !== 'personal'));
addGroup('🔑 My Providers', models.filter(m => m.source === 'personal'));
@@ -909,7 +909,7 @@ const UI = {
const div = document.createElement('div');
div.className = 'model-dropdown-item';
div.dataset.value = m.id;
const avatar = m.presetAvatar ? `<img src="${m.presetAvatar}" class="dropdown-avatar" alt="">` : '';
const avatar = m.personaAvatar ? `<img src="${m.personaAvatar}" class="dropdown-avatar" alt="">` : '';
const teamBadge = m.source === 'team' && m.teamName ? `<span class="badge-team" style="font-size:9px;padding:0 4px">👥 ${esc(m.teamName)}</span>` : '';
div.innerHTML = `${avatar}<span class="item-label">${esc(m.name || m.id)}</span>${teamBadge}${m.provider ? `<span class="item-provider">${esc(m.provider)}</span>` : ''}`;
div.addEventListener('click', () => {
@@ -1027,7 +1027,7 @@ const UI = {
if (on) {
const container = document.getElementById('chatMessages');
const currentModel = App.findModel(App.settings.model);
const typingAvatar = currentModel?.presetAvatar || null;
const typingAvatar = currentModel?.personaAvatar || null;
const typing = document.createElement('div');
typing.id = 'typingIndicator';
typing.className = 'message assistant';
@@ -1084,7 +1084,7 @@ const UI = {
UI.loadProfileIntoSettings();
UI.loadMyTeams();
UI.checkUserProvidersAllowed();
UI.checkUserPresetsAllowed();
UI.checkUserPersonasAllowed();
UI.switchSettingsTab('general');
openModal('settingsModal');
},
@@ -1101,7 +1101,7 @@ const UI = {
UI.checkUserProvidersAllowed();
}
if (tab === 'models') UI.loadUserModels();
if (tab === 'personas') { UI.loadUserPresets(); UI.checkUserPresetsAllowed(); }
if (tab === 'personas') { UI.loadUserPersonas(); UI.checkUserPersonasAllowed(); }
if (tab === 'usage') UI.loadMyUsage();
if (tab === 'roles') UI.loadUserRoles();
if (tab === 'knowledgeBases') {