feat(ui): add phase-1 skills toggles in settings (v1.7.5)
This commit is contained in:
2
app.py
2
app.py
@@ -56,7 +56,7 @@ syslog_handler.setFormatter(
|
|||||||
log.addHandler(syslog_handler)
|
log.addHandler(syslog_handler)
|
||||||
|
|
||||||
# --- Configuration ---
|
# --- Configuration ---
|
||||||
VERSION = "1.7.4"
|
VERSION = "1.7.5"
|
||||||
OLLAMA_BASE = "http://localhost:11434"
|
OLLAMA_BASE = "http://localhost:11434"
|
||||||
SEARXNG_BASE = "http://localhost:8888"
|
SEARXNG_BASE = "http://localhost:8888"
|
||||||
BASE_DIR = Path(__file__).parent
|
BASE_DIR = Path(__file__).parent
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ⚡ JarvisChat v1.7.4
|
# ⚡ JarvisChat v1.7.5
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ body { font-family: var(--font-body); background: var(--bg-primary); color: var(
|
|||||||
.memory-item .memory-delete { color:var(--danger); cursor:pointer; opacity:0.5; }
|
.memory-item .memory-delete { color:var(--danger); cursor:pointer; opacity:0.5; }
|
||||||
.memory-item .memory-delete:hover { opacity:1; }
|
.memory-item .memory-delete:hover { opacity:1; }
|
||||||
.memory-stats { font-size:11px; color:var(--text-muted); margin-bottom:10px; font-family:var(--font-mono); }
|
.memory-stats { font-size:11px; color:var(--text-muted); margin-bottom:10px; font-family:var(--font-mono); }
|
||||||
|
.skills-status { font-size:11px; color:var(--text-muted); margin-bottom:10px; font-family:var(--font-mono); }
|
||||||
|
.skill-item { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:8px 10px; background:var(--bg-tertiary); border-radius:var(--radius); margin-bottom:6px; }
|
||||||
|
.skill-meta { min-width:0; }
|
||||||
|
.skill-name { font-size:12px; color:var(--text-primary); font-family:var(--font-mono); margin-bottom:3px; }
|
||||||
|
.skill-desc { font-size:11px; color:var(--text-muted); line-height:1.4; }
|
||||||
|
.skill-risk { display:inline-block; margin-left:8px; padding:1px 6px; border-radius:10px; font-size:10px; text-transform:uppercase; border:1px solid var(--border); color:var(--text-secondary); }
|
||||||
|
.skill-item.disabled .skill-meta { opacity:0.6; }
|
||||||
|
|
||||||
.chat-container { flex:1; overflow-y:auto; padding:20px; display:flex; flex-direction:column; gap:16px; }
|
.chat-container { flex:1; overflow-y:auto; padding:20px; display:flex; flex-direction:column; gap:16px; }
|
||||||
.welcome-screen { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; color:var(--text-muted); text-align:center; gap:12px; }
|
.welcome-screen { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; color:var(--text-muted); text-align:center; gap:12px; }
|
||||||
@@ -289,6 +296,16 @@ body { font-family: var(--font-body); background: var(--bg-primary); color: var(
|
|||||||
<button class="btn-small btn-save" onclick="addPreset()">+ Add Preset</button>
|
<button class="btn-small btn-save" onclick="addPreset()">+ Add Preset</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-section">
|
||||||
|
<h3>Skills (Phase 1)</h3>
|
||||||
|
<p class="desc">Toggle built-in local skills used for tool-aware prompt injection. Master toggle disables all skills globally.</p>
|
||||||
|
<div class="toggle-row">
|
||||||
|
<span class="toggle-label">Enable skills framework</span>
|
||||||
|
<div class="toggle-switch on" id="skillsMasterToggle" onclick="toggleSkillsMaster()"></div>
|
||||||
|
</div>
|
||||||
|
<div class="skills-status" id="skillsStatus">Loading skills...</div>
|
||||||
|
<div id="skillsList"></div>
|
||||||
|
</div>
|
||||||
<div class="modal-section">
|
<div class="modal-section">
|
||||||
<h3>General</h3>
|
<h3>General</h3>
|
||||||
<div class="toggle-row">
|
<div class="toggle-row">
|
||||||
@@ -345,7 +362,9 @@ let abortController = null;
|
|||||||
let profileEnabled = true;
|
let profileEnabled = true;
|
||||||
let searchEnabled = true;
|
let searchEnabled = true;
|
||||||
let memoryEnabled = true;
|
let memoryEnabled = true;
|
||||||
|
let skillsEnabled = true;
|
||||||
let presets = [];
|
let presets = [];
|
||||||
|
let skillsRegistry = [];
|
||||||
let modelContextSize = 8192;
|
let modelContextSize = 8192;
|
||||||
let cachedProfile = '';
|
let cachedProfile = '';
|
||||||
let conversationHistory = [];
|
let conversationHistory = [];
|
||||||
@@ -559,6 +578,7 @@ async function initializeMainApp() {
|
|||||||
appInitialized = true;
|
appInitialized = true;
|
||||||
await loadModels();
|
await loadModels();
|
||||||
await loadSettings();
|
await loadSettings();
|
||||||
|
await loadSkills();
|
||||||
await loadProfile();
|
await loadProfile();
|
||||||
await loadPresets();
|
await loadPresets();
|
||||||
await loadConversations();
|
await loadConversations();
|
||||||
@@ -677,9 +697,11 @@ async function loadSettings() {
|
|||||||
profileEnabled = s.profile_enabled !== 'false';
|
profileEnabled = s.profile_enabled !== 'false';
|
||||||
searchEnabled = s.search_enabled !== 'false';
|
searchEnabled = s.search_enabled !== 'false';
|
||||||
memoryEnabled = s.memory_enabled !== 'false';
|
memoryEnabled = s.memory_enabled !== 'false';
|
||||||
|
skillsEnabled = s.skills_enabled !== 'false';
|
||||||
updateProfileUI();
|
updateProfileUI();
|
||||||
updateSearchUI();
|
updateSearchUI();
|
||||||
updateMemoryUI();
|
updateMemoryUI();
|
||||||
|
updateSkillsUI();
|
||||||
if (s.default_model) {
|
if (s.default_model) {
|
||||||
document.getElementById('modelSelect').value = s.default_model;
|
document.getElementById('modelSelect').value = s.default_model;
|
||||||
document.getElementById('defaultModelSetting').value = s.default_model;
|
document.getElementById('defaultModelSetting').value = s.default_model;
|
||||||
@@ -689,7 +711,7 @@ async function loadSettings() {
|
|||||||
|
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
if (currentRole !== 'admin') { requireAdminNotice(); return; }
|
if (currentRole !== 'admin') { requireAdminNotice(); return; }
|
||||||
await authFetch('/api/settings', { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ profile_enabled: profileEnabled ? 'true' : 'false', search_enabled: searchEnabled ? 'true' : 'false', memory_enabled: memoryEnabled ? 'true' : 'false' }) });
|
await authFetch('/api/settings', { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ profile_enabled: profileEnabled ? 'true' : 'false', search_enabled: searchEnabled ? 'true' : 'false', memory_enabled: memoryEnabled ? 'true' : 'false', skills_enabled: skillsEnabled ? 'true' : 'false' }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveDefaultModel() {
|
async function saveDefaultModel() {
|
||||||
@@ -759,6 +781,92 @@ function updateMemoryUI() {
|
|||||||
if (toggle) toggle.className = 'toggle-switch' + (memoryEnabled ? ' on' : '');
|
if (toggle) toggle.className = 'toggle-switch' + (memoryEnabled ? ' on' : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSkillsUI() {
|
||||||
|
const master = document.getElementById('skillsMasterToggle');
|
||||||
|
if (master) master.className = 'toggle-switch' + (skillsEnabled ? ' on' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSkills() {
|
||||||
|
try {
|
||||||
|
const resp = await authFetch('/api/skills');
|
||||||
|
const data = await resp.json();
|
||||||
|
skillsRegistry = data.skills || [];
|
||||||
|
renderSkills();
|
||||||
|
} catch(e) {
|
||||||
|
const status = document.getElementById('skillsStatus');
|
||||||
|
if (status) status.textContent = 'Unable to load skills';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSkills() {
|
||||||
|
const container = document.getElementById('skillsList');
|
||||||
|
const status = document.getElementById('skillsStatus');
|
||||||
|
if (!container || !status) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
if (!skillsRegistry.length) {
|
||||||
|
status.textContent = 'No skills registered';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabledCount = skillsRegistry.filter(s => s.enabled).length;
|
||||||
|
status.textContent = `${enabledCount}/${skillsRegistry.length} skills enabled${skillsEnabled ? '' : ' (master toggle OFF)'}`;
|
||||||
|
|
||||||
|
skillsRegistry.forEach(skill => {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'skill-item' + (skillsEnabled ? '' : ' disabled');
|
||||||
|
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'skill-meta';
|
||||||
|
|
||||||
|
const name = document.createElement('div');
|
||||||
|
name.className = 'skill-name';
|
||||||
|
const risk = (skill.risk || 'low').toUpperCase();
|
||||||
|
name.innerHTML = `${skill.name} <span class="skill-risk">${risk}</span>`;
|
||||||
|
|
||||||
|
const desc = document.createElement('div');
|
||||||
|
desc.className = 'skill-desc';
|
||||||
|
desc.textContent = `${skill.key} - ${skill.description || ''}`;
|
||||||
|
|
||||||
|
meta.appendChild(name);
|
||||||
|
meta.appendChild(desc);
|
||||||
|
|
||||||
|
const toggle = document.createElement('div');
|
||||||
|
const active = !!skill.enabled && skillsEnabled;
|
||||||
|
toggle.className = 'toggle-switch' + (active ? ' on' : '');
|
||||||
|
toggle.title = skillsEnabled ? `Toggle ${skill.name}` : 'Enable skills framework first';
|
||||||
|
toggle.addEventListener('click', () => toggleSkill(skill.key));
|
||||||
|
|
||||||
|
row.appendChild(meta);
|
||||||
|
row.appendChild(toggle);
|
||||||
|
container.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleSkillsMaster() {
|
||||||
|
if (currentRole !== 'admin') { requireAdminNotice(); return; }
|
||||||
|
skillsEnabled = !skillsEnabled;
|
||||||
|
updateSkillsUI();
|
||||||
|
renderSkills();
|
||||||
|
await saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleSkill(skillKey) {
|
||||||
|
if (currentRole !== 'admin') { requireAdminNotice(); return; }
|
||||||
|
if (!skillsEnabled) return;
|
||||||
|
const skill = skillsRegistry.find(s => s.key === skillKey);
|
||||||
|
if (!skill) return;
|
||||||
|
|
||||||
|
const nextEnabled = !skill.enabled;
|
||||||
|
await authFetch(`/api/skills/${encodeURIComponent(skillKey)}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ enabled: nextEnabled })
|
||||||
|
});
|
||||||
|
skill.enabled = nextEnabled;
|
||||||
|
renderSkills();
|
||||||
|
}
|
||||||
|
|
||||||
function updateTokenCount() {
|
function updateTokenCount() {
|
||||||
const text = document.getElementById('profileEditor').value;
|
const text = document.getElementById('profileEditor').value;
|
||||||
cachedProfile = text;
|
cachedProfile = text;
|
||||||
@@ -861,6 +969,7 @@ function openSettings() {
|
|||||||
document.getElementById('settingsModal').classList.add('visible');
|
document.getElementById('settingsModal').classList.add('visible');
|
||||||
loadProfile();
|
loadProfile();
|
||||||
loadMemoryStats();
|
loadMemoryStats();
|
||||||
|
loadSkills();
|
||||||
}
|
}
|
||||||
function closeSettings() { document.getElementById('settingsModal').classList.remove('visible'); }
|
function closeSettings() { document.getElementById('settingsModal').classList.remove('visible'); }
|
||||||
document.getElementById('settingsModal').addEventListener('click', e => { if (e.target.id === 'settingsModal') closeSettings(); });
|
document.getElementById('settingsModal').addEventListener('click', e => { if (e.target.id === 'settingsModal') closeSettings(); });
|
||||||
|
|||||||
Reference in New Issue
Block a user