Changeset 0.25.0 (#160)

This commit is contained in:
2026-03-08 16:54:17 +00:00
parent 937be26578
commit 2b01d540d6
63 changed files with 6942 additions and 2773 deletions

View File

@@ -78,6 +78,11 @@ function renderPersonaForm(containerEl, options = {}) {
<textarea id="${pfx}_memoryExtractionPrompt" rows="2" placeholder="Override the default extraction prompt for this persona..."></textarea>
</div>
</div>
<div class="form-group tool-grants-section" style="border-top:1px solid var(--border);padding-top:8px;margin-top:4px">
<label class="checkbox-label" style="font-weight:500"><input type="checkbox" id="${pfx}_toolsAll" checked> All tools (inherit from context)</label>
<p class="form-hint" style="margin:2px 0 6px">When unchecked, restrict this persona to only the selected tools below.</p>
<div id="${pfx}_toolsList" class="tool-grants-list" style="display:none;max-height:200px;overflow-y:auto;padding:4px 0"></div>
</div>
<div class="form-row">
<button class="btn-small btn-primary" id="${pfx}_submitBtn">Create</button>
<button class="btn-small" id="${pfx}_cancelBtn">Cancel</button>
@@ -127,6 +132,18 @@ function renderPersonaForm(containerEl, options = {}) {
if (options.onSubmit) options.onSubmit(form.getValues());
});
// v0.25.0: Tool grants — toggle "All tools" checkbox shows/hides tool list
const toolsAllCb = document.getElementById(`${pfx}_toolsAll`);
const toolsListEl = document.getElementById(`${pfx}_toolsList`);
if (toolsAllCb && toolsListEl) {
toolsAllCb.addEventListener('change', () => {
toolsListEl.style.display = toolsAllCb.checked ? 'none' : '';
if (!toolsAllCb.checked && toolsListEl.children.length === 0) {
form.loadToolList();
}
});
}
const form = {
getValues() {
const v = {
@@ -152,6 +169,14 @@ function renderPersonaForm(containerEl, options = {}) {
if (memCb) v.memory_enabled = memCb.checked;
const memPrompt = document.getElementById(`${pfx}_memoryExtractionPrompt`)?.value?.trim();
if (memPrompt) v.memory_extraction_prompt = memPrompt;
// v0.25.0: Tool grants
const toolsAllCb = document.getElementById(`${pfx}_toolsAll`);
if (toolsAllCb && !toolsAllCb.checked) {
const checked = document.querySelectorAll(`#${pfx}_toolsList input[type="checkbox"]:checked`);
v.tool_grants = Array.from(checked).map(cb => cb.value);
} else {
v.tool_grants = []; // empty = inherit all
}
return v;
},
setValues(p) {
@@ -168,6 +193,8 @@ function renderPersonaForm(containerEl, options = {}) {
// Memory fields (v0.18.0)
if (el('memoryEnabled')) el('memoryEnabled').checked = p.memory_enabled !== false;
if (el('memoryExtractionPrompt')) el('memoryExtractionPrompt').value = p.memory_extraction_prompt || '';
// v0.25.0: Tool grants — load and apply
if (p.id) form.loadToolGrants(p.id);
},
clearForm() {
['name','handle','desc','prompt','temp','maxTokens'].forEach(f => {
@@ -209,6 +236,63 @@ function renderPersonaForm(containerEl, options = {}) {
},
getModelSelect() { return document.getElementById(`${pfx}_model`); },
getConfigSelect() { return showConfig ? document.getElementById(`${pfx}_config`) : null; },
// v0.25.0: Tool grants management
async loadToolList() {
const listEl = document.getElementById(`${pfx}_toolsList`);
if (!listEl) return;
try {
const resp = await API._get('/api/v1/tools');
const tools = resp.tools || resp.data || resp || [];
listEl.innerHTML = '';
// Group by category
const groups = {};
tools.forEach(t => {
const cat = t.category || 'other';
(groups[cat] = groups[cat] || []).push(t);
});
Object.keys(groups).sort().forEach(cat => {
const hdr = document.createElement('div');
hdr.className = 'tool-grants-category';
hdr.textContent = cat.charAt(0).toUpperCase() + cat.slice(1);
listEl.appendChild(hdr);
groups[cat].forEach(t => {
const label = document.createElement('label');
label.className = 'tool-grants-item';
label.innerHTML = `<input type="checkbox" value="${t.name}"> ${t.display_name || t.name}`;
listEl.appendChild(label);
});
});
} catch (e) {
listEl.innerHTML = '<div class="form-hint">Failed to load tools</div>';
}
},
async loadToolGrants(personaId) {
const toolsAllCb = document.getElementById(`${pfx}_toolsAll`);
const listEl = document.getElementById(`${pfx}_toolsList`);
if (!toolsAllCb || !listEl) return;
try {
const resp = await API._get(`/api/v1/personas/${personaId}/tool-grants`);
const grants = resp.data || [];
if (grants.length === 0) {
toolsAllCb.checked = true;
listEl.style.display = 'none';
} else {
toolsAllCb.checked = false;
listEl.style.display = '';
await form.loadToolList();
const grantSet = new Set(grants);
listEl.querySelectorAll('input[type="checkbox"]').forEach(cb => {
cb.checked = grantSet.has(cb.value);
});
}
} catch (_) {
// Endpoint may not exist yet — default to all tools
toolsAllCb.checked = true;
listEl.style.display = 'none';
}
},
};
return form;
}
@@ -247,8 +331,10 @@ const UI = {
},
restoreSidebar() {
const el = document.getElementById('sidebar');
if (!el) return; // not all surfaces have a sidebar (e.g. editor)
if (window.innerWidth <= 768 || localStorage.getItem('sb_sidebar') === '1') {
document.getElementById('sidebar').classList.add('collapsed');
el.classList.add('collapsed');
}
},
@@ -279,7 +365,8 @@ const UI = {
},
closeUserMenu() {
document.getElementById('userFlyout').classList.remove('open');
const el = document.getElementById('userFlyout');
if (el) el.classList.remove('open');
},
// ── Sidebar Section Collapse ──────────────
@@ -1191,6 +1278,7 @@ const UI = {
const letter = (name[0] || '?').toUpperCase();
const avatarEl = document.getElementById('userAvatar');
const letterEl = document.getElementById('avatarLetter');
if (!avatarEl || !letterEl) return; // not all surfaces have user menu DOM
// Show avatar image or initial letter
let existingImg = avatarEl.querySelector('.user-avatar-img');
if (API.user?.avatar) {
@@ -1214,11 +1302,13 @@ const UI = {
letterEl.style.display = '';
letterEl.textContent = letter;
}
document.getElementById('userName').textContent = name;
const nameEl = document.getElementById('userName');
if (nameEl) nameEl.textContent = name;
},
showAdminButton(show) {
document.getElementById('menuAdmin').style.display = show ? '' : 'none';
const adminEl = document.getElementById('menuAdmin');
if (adminEl) adminEl.style.display = show ? '' : 'none';
var dbg = document.getElementById('menuDebug');
if (dbg) dbg.style.display = show ? '' : 'none';
},
@@ -1402,5 +1492,43 @@ const UI = {
const el = document.getElementById('chatMessages');
el.scrollTop = el.scrollHeight;
}
},
// ── Appearance (scale + font) ────────────
// Shared across ALL surfaces. Previously in ui-settings.js
// which only loaded on chat/settings — editor surface was skipped.
applyAppearance(scale, msgFont) {
const z = scale === 100 ? '' : scale / 100;
// Zoom content areas — covers both chat and editor surfaces
document.querySelectorAll(
'.sidebar, .workspace-primary, .workspace-secondary, ' +
'.modal-overlay, .admin-panel, ' +
'.surface-editor, .surface-notes'
).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');
localStorage.setItem('cs-appearance', JSON.stringify({ scale, msgFont }));
// Recheck tab overflow after layout settles
requestAnimationFrame(() => {
document.querySelectorAll('.modal-overlay.active .modal-tabs').forEach(t => {
if (typeof checkTabsOverflow === 'function') checkTabsOverflow(t);
});
});
},
restoreAppearance() {
try {
const prefs = JSON.parse(localStorage.getItem('cs-appearance') || '{}');
const scale = prefs.scale || 100;
const msgFont = prefs.msgFont || 14;
if (scale !== 100 || msgFont !== 14) {
UI.applyAppearance(scale, msgFont);
} else {
// Still set the CSS variable for default
document.documentElement.style.setProperty('--msg-font', msgFont + 'px');
}
} catch (_) { /* corrupt localStorage — ignore */ }
}
};