From c209096fd40ea66443f3cad584fa6c1daafac9cd Mon Sep 17 00:00:00 2001 From: Siphon880gh Date: Sun, 29 Jun 2025 03:26:18 -0700 Subject: [PATCH] feat: Add category filter to search interface --- api_server.py | 56 +++++++++- create_categories.py | 19 ++++ static/index.html | 244 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 293 insertions(+), 26 deletions(-) diff --git a/api_server.py b/api_server.py index 1a5e1a8..9272436 100644 --- a/api_server.py +++ b/api_server.py @@ -361,13 +361,63 @@ async def get_integrations(): @app.get("/api/categories") async def get_categories(): - """Get available service categories for filtering.""" + """Get available workflow categories for filtering.""" try: - categories = db.get_service_categories() - return {"categories": categories} + # Try to load from the generated unique categories file + categories_file = Path("context/unique_categories.json") + if categories_file.exists(): + with open(categories_file, 'r', encoding='utf-8') as f: + categories = json.load(f) + return {"categories": categories} + else: + # Fallback: extract categories from search_categories.json + search_categories_file = Path("context/search_categories.json") + if search_categories_file.exists(): + with open(search_categories_file, 'r', encoding='utf-8') as f: + search_data = json.load(f) + + unique_categories = set() + for item in search_data: + if item.get('category'): + unique_categories.add(item['category']) + else: + unique_categories.add('Uncategorized') + + categories = sorted(list(unique_categories)) + return {"categories": categories} + else: + # Last resort: return basic categories + return {"categories": ["Uncategorized"]} + except Exception as e: + print(f"Error loading categories: {e}") raise HTTPException(status_code=500, detail=f"Error fetching categories: {str(e)}") +@app.get("/api/category-mappings") +async def get_category_mappings(): + """Get filename to category mappings for client-side filtering.""" + try: + search_categories_file = Path("context/search_categories.json") + if not search_categories_file.exists(): + return {"mappings": {}} + + with open(search_categories_file, 'r', encoding='utf-8') as f: + search_data = json.load(f) + + # Convert to a simple filename -> category mapping + mappings = {} + for item in search_data: + filename = item.get('filename') + category = item.get('category') or 'Uncategorized' + if filename: + mappings[filename] = category + + return {"mappings": mappings} + + except Exception as e: + print(f"Error loading category mappings: {e}") + raise HTTPException(status_code=500, detail=f"Error fetching category mappings: {str(e)}") + @app.get("/api/workflows/category/{category}", response_model=SearchResponse) async def search_workflows_by_category( category: str, diff --git a/create_categories.py b/create_categories.py index c534fa7..374b6a4 100644 --- a/create_categories.py +++ b/create_categories.py @@ -75,6 +75,25 @@ def main(): print(f"Generated search_categories.json with {len(search_categories)} entries") + # Generate unique categories list for API + unique_categories = set() + for item in search_categories: + if item['category']: + unique_categories.add(item['category']) + + # Always include 'Uncategorized' for workflows without categories + unique_categories.add('Uncategorized') + + # Sort categories alphabetically + categories_list = sorted(list(unique_categories)) + + # Write unique categories to a separate file for API consumption + categories_output_path = Path("context/unique_categories.json") + with open(categories_output_path, 'w', encoding='utf-8') as f: + json.dump(categories_list, f, indent=2, ensure_ascii=False) + + print(f"Generated unique_categories.json with {len(categories_list)} categories") + # Print some statistics categorized = sum(1 for item in search_categories if item['category']) uncategorized = len(search_categories) - categorized diff --git a/static/index.html b/static/index.html index 967b8f3..132991e 100644 --- a/static/index.html +++ b/static/index.html @@ -302,6 +302,16 @@ font-weight: 500; } + .category-badge { + background: var(--bg-tertiary); + color: var(--text-secondary); + padding: 0.125rem 0.375rem; + border-radius: 0.25rem; + font-size: 0.75rem; + border: 1px solid var(--border); + font-weight: 500; + } + .workflow-title { font-size: 1.25rem; font-weight: 600; @@ -623,6 +633,14 @@ +
+ + +
+
${this.escapeHtml(workflow.trigger_type)} @@ -1090,12 +1286,14 @@ this.elements.modalDescription.textContent = workflow.description; // Update stats + const category = this.getWorkflowCategory(workflow.filename); this.elements.modalStats.innerHTML = `
Status: ${workflow.active ? 'Active' : 'Inactive'}
Trigger: ${workflow.trigger_type}
Complexity: ${workflow.complexity}
Nodes: ${workflow.node_count}
+
Category: ${this.escapeHtml(category)}
`;