Changeset 0.25.0 (#160)
This commit is contained in:
@@ -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 */ }
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user