// ── 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 ? `` : ''; const labelExpand = tools.length > 1 ? ` data-expand="${cat}"` : ''; html += `
${meta.icon} ${meta.label} ${chevron}
`; // Expanded children if (expanded && tools.length > 1) { for (const t of tools) { const on = !_disabled.has(t.name); html += `
${t.displayName}
`; } } } 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 }; })();