diff --git a/apps/__init__.py b/apps/__init__.py index e6d75bd95..48863b74c 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -26,7 +26,7 @@ app.json fields (all optional): { "icon": "🏠", # Emoji icon for menu bar (default: "📦") "label": "Custom Label", # Display label (default: title-cased app name) - "facets": {}, # Facet options: {"disabled": true} to hide, {"muted": true} to show disabled facets + "facets": {}, # Facet options: {"disabled": true} to hide facet bar "date_nav": true, # Show date navigation bar (default: false) "allow_future_dates": true # Allow future dates in month picker (default: false) } @@ -68,7 +68,6 @@ class App: # Facet configuration (optional, default {}) # Options: # - disabled: If true, facets bar is hidden for this app - # - muted: Include facets marked as disabled in facet.json facets_config: dict = field(default_factory=dict) # Date navigation (renders date nav below facet bar) @@ -81,10 +80,6 @@ class App: """Check if facets are enabled for this app.""" return not self.facets_config.get("disabled", False) - def show_muted_facets(self) -> bool: - """Check if muted/disabled facets should be shown.""" - return self.facets_config.get("muted", False) - def date_nav_enabled(self) -> bool: """Check if date nav is enabled for this app.""" return self.date_nav diff --git a/apps/settings/app.json b/apps/settings/app.json index f23432896..56edf1cbc 100644 --- a/apps/settings/app.json +++ b/apps/settings/app.json @@ -1,7 +1,4 @@ { "icon": "⚙️", - "label": "Settings", - "facets": { - "muted": true - } + "label": "Settings" } diff --git a/apps/settings/routes.py b/apps/settings/routes.py index fe963a6fd..987358963 100644 --- a/apps/settings/routes.py +++ b/apps/settings/routes.py @@ -1213,6 +1213,28 @@ def update_observe() -> Any: return jsonify({"error": str(e)}), 500 +@settings_bp.route("/api/facets/muted") +def get_muted_facets() -> Any: + """List muted facets.""" + try: + from think.facets import get_facets + + facets = get_facets() + muted = [ + { + "name": name, + "title": data.get("title", name), + "color": data.get("color", ""), + "emoji": data.get("emoji", ""), + } + for name, data in facets.items() + if data.get("muted", False) + ] + return jsonify({"facets": muted}) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + @settings_bp.route("/api/facet", methods=["POST"]) def create_facet() -> Any: """Create a new facet. diff --git a/apps/settings/workspace.html b/apps/settings/workspace.html index 729699b28..1a513d5f1 100644 --- a/apps/settings/workspace.html +++ b/apps/settings/workspace.html @@ -1617,6 +1617,7 @@ input:checked + .slider:before { Storage Support Agent Identity + Facets Appearance @@ -1642,6 +1643,7 @@ input:checked + .slider:before { Sync Storage Support + Facets facet @@ -2310,6 +2312,16 @@ input:checked + .slider:before { + + + Facets + Manage muted facets. Muted facets are hidden from the facet bar but retain all their data. + + + Loading... + + + Agent Identity @@ -2532,7 +2544,7 @@ let insightsData = null; let keyValidationData = {}; // ========== NAVIGATION ========== -const VALID_SECTIONS = ['profile', 'agent', 'providers', 'apikeys', 'transcription', 'observer', 'vision', 'insights', 'security', 'sync', 'storage', 'support', 'facet-appearance', 'facet-activities', 'facet-activity']; +const VALID_SECTIONS = ['profile', 'agent', 'providers', 'apikeys', 'transcription', 'observer', 'vision', 'insights', 'security', 'sync', 'storage', 'support', 'facets', 'facet-appearance', 'facet-activities', 'facet-activity']; function switchSection(sectionId, updateHash = true) { // Validate section exists @@ -2558,6 +2570,10 @@ function switchSection(sectionId, updateHash = true) { section.classList.toggle('active', section.id === 'section-' + sectionId); }); + if (sectionId === 'facets') { + loadMutedFacets(); + } + // Update URL hash if (updateHash) { history.replaceState(null, '', '#' + sectionId); @@ -2577,6 +2593,91 @@ function switchSection(sectionId, updateHash = true) { } } +// ========== MUTED FACETS ========== +async function loadMutedFacets() { + const container = document.getElementById('mutedFacetsList'); + try { + const response = await fetch('/app/settings/api/facets/muted'); + const data = await response.json(); + if (data.error) throw new Error(data.error); + + const facets = data.facets || []; + if (facets.length === 0) { + container.innerHTML = 'No muted facets.'; + return; + } + + container.innerHTML = ''; + facets.forEach(facet => { + const row = document.createElement('div'); + row.className = 'settings-field'; + row.style.display = 'flex'; + row.style.alignItems = 'center'; + row.style.justifyContent = 'space-between'; + + const info = document.createElement('div'); + info.style.display = 'flex'; + info.style.alignItems = 'center'; + info.style.gap = '0.5em'; + + if (facet.emoji) { + const emoji = document.createElement('span'); + emoji.textContent = facet.emoji; + info.appendChild(emoji); + } + + const title = document.createElement('span'); + title.textContent = facet.title; + info.appendChild(title); + + if (facet.color) { + const swatch = document.createElement('span'); + swatch.style.display = 'inline-block'; + swatch.style.width = '12px'; + swatch.style.height = '12px'; + swatch.style.borderRadius = '50%'; + swatch.style.backgroundColor = facet.color; + swatch.style.marginLeft = '0.25em'; + info.appendChild(swatch); + } + + const btn = document.createElement('button'); + btn.className = 'setup-btn'; + btn.style.fontSize = '0.85em'; + btn.style.padding = '0.3em 0.8em'; + btn.textContent = 'Unmute'; + btn.onclick = async () => { + btn.disabled = true; + btn.textContent = 'Unmuting...'; + try { + const res = await fetch(`/app/settings/api/facet/${facet.name}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ muted: false }) + }); + const result = await res.json(); + if (result.error) throw new Error(result.error); + row.remove(); + if (container.children.length === 0) { + container.innerHTML = 'No muted facets.'; + } + notifySuccess('Unmuted', `${facet.title} will appear in the facet bar on reload.`); + } catch (err) { + btn.disabled = false; + btn.textContent = 'Unmute'; + notifyError('Error', 'Failed to unmute facet'); + } + }; + + row.appendChild(info); + row.appendChild(btn); + container.appendChild(row); + }); + } catch (err) { + container.innerHTML = 'Failed to load muted facets.'; + } +} + // Desktop nav clicks document.querySelectorAll('.settings-nav-item').forEach(btn => { btn.addEventListener('click', () => switchSection(btn.dataset.section)); diff --git a/convey/apps.py b/convey/apps.py index e28bbf519..dd277f244 100644 --- a/convey/apps.py +++ b/convey/apps.py @@ -12,15 +12,8 @@ from flask import Flask, g, request, url_for from apps import AppRegistry -def _get_facets_data(include_muted: bool = False) -> list[dict]: - """Get facets data for templates. - - Args: - include_muted: If True, include facets marked as disabled - - Returns: - List of facet dicts with name, title, color, emoji, and muted status - """ +def _get_facets_data() -> list[dict]: + """Get active facets data for templates.""" from think.facets import get_facets from .config import apply_facet_order, load_convey_config @@ -29,10 +22,7 @@ def _get_facets_data(include_muted: bool = False) -> list[dict]: facets_list = [] for name, data in all_facets.items(): - is_muted = data.get("muted", False) - - # Skip muted facets unless explicitly requested - if is_muted and not include_muted: + if data.get("muted", False): continue facets_list.append( @@ -41,7 +31,6 @@ def _get_facets_data(include_muted: bool = False) -> list[dict]: "title": data.get("title", name), "color": data.get("color", ""), "emoji": data.get("emoji", ""), - "muted": is_muted, # Include muted status for styling } ) @@ -286,12 +275,7 @@ def register_app_context(app: Flask, registry: AppRegistry) -> None: ): day = path_parts[3] - # Determine if current app wants muted facets shown - include_muted = False - if current_app_name: - include_muted = registry.apps[current_app_name].show_muted_facets() - - facets = _get_facets_data(include_muted=include_muted) + facets = _get_facets_data() selected_facet = _get_selected_facet() # Build apps dict for menu-bar diff --git a/convey/static/app.css b/convey/static/app.css index cf149a598..f68b822f4 100644 --- a/convey/static/app.css +++ b/convey/static/app.css @@ -1075,25 +1075,6 @@ body.menu-all .menu-bar { opacity: 0.5; } -/* Muted facets - visually distinct but still interactive */ -.facet-pill.muted { - opacity: 0.6; - font-style: italic; - border-style: dashed; -} - -.facet-pill.muted:hover { - opacity: 0.8; -} - -.facet-pill.muted.selected { - opacity: 1; - font-style: italic; - border-style: dashed; - background: color-mix(in srgb, var(--pill-color) 75%, white); - border-color: var(--pill-color); -} - /* App Bar (bottom) */ .app-bar { position: fixed; diff --git a/convey/static/app.js b/convey/static/app.js index 82ff37096..578cd15a6 100644 --- a/convey/static/app.js +++ b/convey/static/app.js @@ -151,11 +151,6 @@ const pill = document.createElement('button'); pill.className = 'facet-pill'; - // Add muted class if facet is muted - if (facet.muted) { - pill.classList.add('muted'); - } - if (facet.emoji) { const emojiContainer = document.createElement('div'); emojiContainer.className = 'emoji-container';
Manage muted facets. Muted facets are hidden from the facet bar but retain all their data.
Loading...
No muted facets.
Failed to load muted facets.