This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/tools-toggle.js
2026-03-12 15:47:22 +00:00

245 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── tools-toggle.js ─────────────────────────
// Tools toggle popup: category-based enable/disable of server-side tools.
// Categories expand to show individual tool toggles.
// State persisted in localStorage, sent as disabled_tools[] in completion requests.
'use strict';
const ToolsToggle = (() => {
const STORAGE_KEY = 'cs-disabled-tools';
// Category display config
const CATEGORY_META = {
search: { label: 'Web Search', icon: '🔍' },
knowledge: { label: 'Knowledge Bases', icon: '📚' },
notes: { label: 'Notes', icon: '📝' },
utilities: { label: 'Utilities', icon: '🧮' },
workspace: { label: 'Workspace', icon: '🗂️' },
context: { label: 'Context', icon: '📎' },
memory: { label: 'Memory', icon: '🧠' },
browser: { label: 'Extensions', icon: '🧩' },
};
// Cached tool manifest from GET /api/v1/tools
let _tools = []; // [{name, display_name, description, category}]
let _categories = {}; // {category: [{name, displayName}]}
let _disabled = new Set();
let _expanded = new Set(); // which categories are expanded
// ── Public API ──────────────────────────
/** Initialize: load disabled set from storage, fetch tool manifest. */
async function init() {
_loadDisabled();
try {
const resp = await API.getTools();
_tools = resp.tools || [];
_buildCategories();
_render();
_show();
} catch (e) {
console.warn('Tools toggle: failed to load tools', e);
}
_wireEvents();
}
/** Returns array of disabled tool names for the completion request. */
function getDisabledTools() {
return [..._disabled];
}
/** Refresh tool list (e.g. after browser extension connects). */
async function refresh() {
try {
const resp = await API.getTools();
_tools = resp.tools || [];
_buildCategories();
_render();
} catch (e) { /* silent */ }
}
// ── Internal ────────────────────────────
function _loadDisabled() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
_disabled = raw ? new Set(JSON.parse(raw)) : new Set();
} catch { _disabled = new Set(); }
}
function _saveDisabled() {
localStorage.setItem(STORAGE_KEY, JSON.stringify([..._disabled]));
_updateBtnState();
}
function _buildCategories() {
_categories = {};
for (const t of _tools) {
const cat = t.category || 'other';
if (!_categories[cat]) _categories[cat] = [];
_categories[cat].push({
name: t.name,
displayName: t.display_name || t.name,
});
}
}
/** Returns 'all' | 'none' | 'partial' for a category's enabled state. */
function _categoryState(cat) {
const tools = _categories[cat] || [];
if (tools.length === 0) return 'none';
const enabledCount = tools.filter(t => !_disabled.has(t.name)).length;
if (enabledCount === tools.length) return 'all';
if (enabledCount === 0) return 'none';
return 'partial';
}
function _toggleCategory(cat) {
const tools = _categories[cat] || [];
const state = _categoryState(cat);
if (state === 'all') {
// All on → all off
tools.forEach(t => _disabled.add(t.name));
} else {
// Partial or none → all on
tools.forEach(t => _disabled.delete(t.name));
}
_saveDisabled();
_render();
}
function _toggleTool(name) {
if (_disabled.has(name)) {
_disabled.delete(name);
} else {
_disabled.add(name);
}
_saveDisabled();
_render();
}
function _toggleExpand(cat) {
if (_expanded.has(cat)) {
_expanded.delete(cat);
} else {
_expanded.add(cat);
}
_render();
}
function _show() {
const wrap = document.querySelector('.tools-toggle-wrap');
if (wrap && _tools.length > 0) {
wrap.style.display = '';
_updateBtnState();
}
}
function _updateBtnState() {
const btn = document.getElementById('toolsToggleBtn');
if (!btn) return;
btn.classList.toggle('has-disabled', _disabled.size > 0);
const count = _disabled.size;
btn.title = count > 0 ? `Tools (${count} disabled)` : 'Tools';
}
function _render() {
const popup = document.getElementById('toolsPopup');
if (!popup) return;
const cats = Object.keys(_categories);
const order = Object.keys(CATEGORY_META);
cats.sort((a, b) => {
const ai = order.indexOf(a), bi = order.indexOf(b);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
let html = '';
for (const cat of cats) {
const meta = CATEGORY_META[cat] || { label: cat, icon: '🔧' };
const state = _categoryState(cat);
const tools = _categories[cat];
const expanded = _expanded.has(cat);
const switchClass = state === 'all' ? 'on' : state === 'partial' ? 'on partial' : '';
const chevron = tools.length > 1
? `<span class="tools-popup-expand ${expanded ? 'open' : ''}" data-expand="${cat}"></span>`
: '';
const labelExpand = tools.length > 1 ? ` data-expand="${cat}"` : '';
html += `<div class="tools-popup-category" data-category="${cat}">
<span class="tools-popup-switch ${switchClass}" data-switch="${cat}"><span class="tools-popup-switch-knob"></span></span>
<span class="tools-popup-label"${labelExpand}>${meta.icon} ${meta.label}</span>
${chevron}
</div>`;
// Expanded children
if (expanded && tools.length > 1) {
for (const t of tools) {
const on = !_disabled.has(t.name);
html += `<div class="tools-popup-tool" data-tool="${t.name}">
<span class="tools-popup-switch ${on ? 'on' : ''}" data-tool-switch="${t.name}"><span class="tools-popup-switch-knob"></span></span>
<span class="tools-popup-label">${t.displayName}</span>
</div>`;
}
}
}
popup.innerHTML = html;
}
function _wireEvents() {
const btn = document.getElementById('toolsToggleBtn');
const popup = document.getElementById('toolsPopup');
if (!btn || !popup) return;
btn.addEventListener('click', (e) => {
e.stopPropagation();
popup.classList.toggle('open');
});
popup.addEventListener('click', (e) => {
e.stopPropagation();
// Toggle switch on category row
const catSwitch = e.target.closest('[data-switch]');
if (catSwitch) {
_toggleCategory(catSwitch.dataset.switch);
return;
}
// Toggle switch on individual tool row
const toolSwitch = e.target.closest('[data-tool-switch]');
if (toolSwitch) {
_toggleTool(toolSwitch.dataset.toolSwitch);
return;
}
// Expand/collapse: chevron or label click on multi-tool categories
const expander = e.target.closest('[data-expand]');
if (expander) {
_toggleExpand(expander.dataset.expand);
return;
}
// Individual tool label (no expand, just toggle)
const toolRow = e.target.closest('.tools-popup-tool');
if (toolRow) {
_toggleTool(toolRow.dataset.tool);
return;
}
// Single-tool category label click (no chevron) — toggle
const catRow = e.target.closest('.tools-popup-category');
if (catRow) {
_toggleCategory(catRow.dataset.category);
return;
}
});
// Close on outside click
document.addEventListener('click', () => popup.classList.remove('open'));
}
return { init, getDisabledTools, refresh };
})();