Back to .md Directory

TODO: Update Tag Filtering Logic in Featured Agents

This document outlines the steps to refine the tag filtering functionality in `ai-agent-hub-web/components/featured-agents.tsx` by utilizing a tag alias map. The goal is to reduce redundancy in displayed filter tags and improve user experience.

May 2, 2026
0 downloads
0 views
ai agent llm claude openai cursor automation
View source

TODO: Update Tag Filtering Logic in Featured Agents

This document outlines the steps to refine the tag filtering functionality in ai-agent-hub-web/components/featured-agents.tsx by utilizing a tag alias map. The goal is to reduce redundancy in displayed filter tags and improve user experience.

1. Manually Review and Refine tagAliasMap

The automatically generated tag_aliases_proposal.ts provides a starting point. It needs careful manual review to ensure accuracy and completeness.

File to review: tag_aliases_proposal.ts

Content of tag_aliases_proposal.ts (as of generation):

// Generated by script. Please review and refine manually.
// This is a best-effort attempt and may require significant curation.
const tagAliasMap: Record<string, string[]> = {
  "12-factor": ["12-factor-agents"],
  "3d": ["3d-engine", "3d-model", "3d-models"],
  "a2a": ["a2a-protocol", "a2a-server"],
  "actor": ["actor-framework", "actors"],
  "aea": ["open-aea"],
  "agent": ["agent-based", "agent-framework", "agent-runtime", "agents", "multi-agent"],
  "agent-monitoring": ["ai-agent-monitoring"],
  "agent-tools": ["ai-agent-tools"],
  "agentic": ["agentic-framework"],
  "agentic-ai": ["agentic-ai-cli"],
  "agents-sdk": ["ai-agents-sdk"],
  "ai": ["ai-agent", "ai-agents", "ai-framework", "ai-os", "ai-platform", "ai-runtime", "gen-ai"],
  "ai-agent-framework": ["aiagentframework"],
  "ai-agents-framework": ["aiagentsframework"],
  "ai-directory": ["ai-directories"],
  "aiagent": ["aiagents"],
  "api": ["api-platform", "apis"],
  "api-gateway": ["apigateway"],
  "api-management": ["apimanagement"],
  "app": ["apps"],
  "application": ["llm-application"],
  "art": ["ai-art"],
  "assistant": ["ai-assistant", "assistants"],
  "automatic": ["automatic-api"],
  "automation": ["automation-framework", "web-automation"],
  "autonomous": ["autonomous-agent", "autonomous-agents"],
  "autonomous agent": ["autonomous agents"],
  "autonomy": ["open-autonomy"],
  "awesome-list": ["awesome-lists"],
  "azd-templates": ["ai-azd-templates"],
  "azure-openai": ["azureopenai"],
  "baby-agi": ["babyagi"],
  "benchmark": ["ai-benchmark", "benchmark-framework"],
  "bot": ["bot-framework"],
  "chat": ["ai-chat"],
  "chatbot": ["chatbot-framework", "chatbots"],
  "chatgpt": ["chatgpt-api", "chatgpt-app"],
  "chest-xray": ["chest-xrays"],
  "claude": ["claude-api"],
  "cli": ["cli-app"],
  "cloud-native": ["cloudnative"],
  "cloudflare": ["cloudflare-agents"],
  "cloudflare-worker": ["cloudflare-workers"],
  "code-review": ["ai-code-review"],
  "coding": ["ai-coding", "coding-agents"],
  "command-line": ["command-line-tool"],
  "community": ["community-driven"],
  "component": ["components"],
  "container": ["containers"],
  "conversational": ["conversational-agents"],
  "course": ["courses"],
  "crawler": ["crawlers", "web-crawler"],
  "crawling": ["web-crawling"],
  "crdt": ["crdts"],
  "cryptocurrency": ["cryptocurrencies"],
  "cursor-ai": ["cursorai"],
  "dashboard": ["dashboards"],
  "data": ["data-driven"],
  "data-analysis": ["ai-data-analysis"],
  "data-flow": ["dataflow"],
  "database": ["databases"],
  "deployment": ["llm-deployment"],
  "design": ["design-system"],
  "desktop": ["desktop-agent", "desktop-app", "web-desktop"],
  "developer-tools": ["ai-developer-tools"],
  "development": ["ai-development"],
  "distributed": ["distributed-system"],
  "ecommerce": ["ecommerce-platform"],
  "elevenlabs": ["elevenlabs-api"],
  "email": ["emails"],
  "embedding": ["embeddings"],
  "embodied": ["embodied-agent", "embodied-agents"],
  "engineering": ["ai-engineering"],
  "etl": ["etl-framework"],
  "evaluation": ["ai-evaluation", "evaluation-framework", "evaluation-metrics", "llm-evaluation"],
  "fine-tuning": ["finetuning"],
  "foundation-model": ["foundation-models"],
  "framwork": ["ai-framwork"],
  "full-stack": ["fullstack"],
  "function-call": ["llm-function-call"],
  "function-calling": ["llm-function-calling"],
  "game": ["game-engine", "games"],
  "gateway": ["ai-gateway", "llm-gateway"],
  "gemini": ["gemini-api"],
  "genai": ["genai-agents"],
  "generative": ["generative-agents", "generative-model", "generative-models"],
  "generative-ai": ["generativeai"],
  "github": ["github-api", "github-models"],
  "godot": ["godot-engine"],
  "golang-whatsapp": ["golang-whatsapp-api"],
  "gpt": ["auto-gpt", "gpts"],
  "gpt-3": ["gpt3"],
  "gpt-4": ["gpt-4-api", "gpt4"],
  "gpt-4o": ["gpt-4o-api", "gpt4o"],
  "graph-rag": ["graphrag"],
  "guardrails": ["llm-guardrails"],
  "gui": ["gui-agents"],
  "inference": ["inference-ui", "llm-inference"],
  "integration": ["integration-framework", "integrations"],
  "javascript": ["javascript-sdk"],
  "job": ["jobs"],
  "knowledge": ["knowledge-base", "knowledge-graph"],
  "knowlege": ["knowlege-base"],
  "lab": ["labs"],
  "langchain": ["langchain-agent", "langchain-agents", "langchain-app"],
  "language model": ["language models"],
  "language-model": ["language-models"],
  "large-language-model": ["large-language-models"],
  "list": ["ai-list"],
  "live-chat": ["livechat"],
  "llama": ["llama-api"],
  "llama-index": ["llamaindex"],
  "llm": ["llm-agent", "llm-agents", "llm-apps", "llm-chain", "llm-framework", "llm-interface", "llm-ops", "llms"],
  "local-ai": ["localai"],
  "local-llm": ["localllm"],
  "low-code": ["low-code-platform"],
  "machine-learning": ["machinelearning"],
  "mcp": ["mcp-agent", "mcp-client", "mcp-framework", "mcp-server", "mcps"],
  "memory": ["ai-memory"],
  "microservice": ["microservices"],
  "midjourney": ["midjourney-app"],
  "mind-map": ["mindmap"],
  "ml": ["ml-ops"],
  "ml-engineering": ["mlengineering"],
  "model-context-protocol": ["modelcontextprotocol"],
  "multi-agent-system": ["multi-agent-systems"],
  "multi-ai-agent": ["multi-ai-agents"],
  "music": ["ai-music"],
  "native": ["ai-native"],
  "news": ["news-agent"],
  "newsletter": ["ai-newsletter", "newsletters"],
  "nlp": ["natural-language-processing"],
  "observability": ["llm-observability"],
  "obsidian-md": ["obsidianmd"],
  "ollama": ["ollama-client", "ollama-ui"],
  "open-source": ["open-source-gpt", "opensource"],
  "openai": ["openai-agents", "openai-api"],
  "openai-assistants": ["openai-assistants-api"],
  "openapi-generator": ["openapi-generators"],
  "operating-system": ["operating-systems"],
  "os": ["llm-os"],
  "paper": ["paper-agent", "papers"],
  "payment": ["payments"],
  "playground": ["ai-playground"],
  "plugin": ["plugins"],
  "pocket-flow": ["pocketflow"],
  "prisma": ["prisma-client"],
  "privacy": ["llm-privacy"],
  "productivity": ["productivity-app"],
  "prompt": ["prompts"],
  "proxy": ["proxies", "proxy-server"],
  "python": ["python-library", "python-sdk"],
  "question-answering": ["question-answering-system"],
  "rag": ["llm-rag", "retrieval-augmented-generation"],
  "real-time": ["real-time-data"],
  "realtime": ["realtime-api"],
  "reasoning": ["llm-reasoning", "reasoning-agent"],
  "red-team": ["redteam"],
  "research": ["ai-research"],
  "resources": ["dev-resources"],
  "rest": ["rest-api"],
  "routing": ["llm-routing"],
  "saleor": ["saleor-app"],
  "scraping": ["ai-scraping", "web-scraping"],
  "search": ["search-agent", "search-engine"],
  "security": ["ai-security", "llm-security"],
  "server": ["servers"],
  "serving": ["llm-serving"],
  "shadcn": ["shadcn-ui"],
  "shell": ["shell-gpt"],
  "simulation": ["simulation-framework"],
  "smart-glasses": ["smartglasses"],
  "spreadsheet": ["spreadsheets"],
  "sql": ["sql-server"],
  "starter": ["starter-kit"],
  "structured-output": ["structured-outputs"],
  "summer-school": ["summer-schools"],
  "suno-ai": ["suno-ai-api"],
  "survey": ["surveys"],
  "swarm": ["ai-swarm", "swarms"],
  "swe": ["swe-agent"],
  "t3": ["t3-stack"],
  "tavily": ["tavily-api"],
  "team": ["teams"],
  "tensorflow": ["tensorflow-library"],
  "terminal": ["terminal-app"],
  "testing": ["llm-testing"],
  "text-to-speech": ["text-to-speech-app"],
  "tool": ["tools"],
  "toolkit": ["ai-toolkit", "toolkits"],
  "tracing": ["llm-tracing"],
  "training": ["llm-training"],
  "transformer": ["transformers"],
  "tutorial": ["tutorials"],
  "ui": ["llm-ui"],
  "unified": ["unified-api"],
  "unofficial-api": ["unofficial-apis"],
  "vector": ["vector-db"],
  "vector-search": ["vectorsearch"],
  "vertex-ai": ["vertexai"],
  "virtual-assistant": ["virtualassistant"],
  "virtualization": ["virtualization-framework"],
  "voice": ["voice-agent", "voice-agents"],
  "voice-assistant": ["ai-voice-assistant"],
  "vulcan-sql": ["vulcansql"],
  "wearable": ["wearables"],
  "web": ["web-agent", "web-data"],
  "webui": ["llm-webui", "open-webui"],
  "whatsapp": ["whatsapp-api"],
  "workflow": ["ai-workflow", "workflow-engine", "workflows"],
};

export default tagAliasMap;

Action:

  • Go through the tagAliasMap.
  • Add, remove, or modify entries to ensure semantic groupings are correct and useful.
  • For example, ensure "llm" is the primary tag for "large language model", "llms", etc.
  • Consider edge cases and desired specificity.

2. Integrate tagAliasMap into featured-agents.tsx

Modify the component to use the curated tagAliasMap.

File to edit: ai-agent-hub-web/components/featured-agents.tsx

Steps:

  1. Import tagAliasMap:

    import tagAliasMap from "@/path/to/your/tag_aliases_proposal"; // Adjust path as necessary
    

    (Note: After review, you might rename tag_aliases_proposal.ts to something like tag-alias-map.ts and place it in a suitable directory, e.g., ai-agent-hub-web/lib/).

  2. Update tagsPerCategory Calculation:

    • When calculating tagsPerCategory, transform the raw tags from agents into their primary display versions using tagAliasMap.
    • The goal is to have tagsPerCategory[category.id] contain only the keys from tagAliasMap (primary tags) plus any tags not found in the map (which are considered primary by default).

    Current Logic (Simplified):

    const tagsPerCategory = useMemo(() => {
      const calculatedTags: Record<string, string[]> = {};
      categories.forEach(category => {
        const allTagsForCategory = new Set<string>();
        category.agents.forEach(agent => {
          agent.tags.forEach(tag => allTagsForCategory.add(tag));
        });
        calculatedTags[category.id] = Array.from(allTagsForCategory).sort();
      });
      return calculatedTags;
    }, [categories]);
    

    Proposed Update Snippet (Conceptual):

    // Helper to get primary tag or original if not in map
    const getPrimaryTag = (tag: string): string => {
      for (const [primary, aliases] of Object.entries(tagAliasMap)) {
        if (aliases.includes(tag.toLowerCase()) || primary === tag.toLowerCase()) {
          return primary;
        }
      }
      return tag; // Return original (lowercased) if no mapping found
    };
    
    const tagsPerCategory = useMemo(() => {
      const calculatedTags: Record<string, string[]> = {};
      categories.forEach(category => {
        const allDisplayTagsForCategory = new Set<string>();
        category.agents.forEach(agent => {
          agent.tags.forEach(rawTag => {
            allDisplayTagsForCategory.add(getPrimaryTag(rawTag));
          });
        });
        calculatedTags[category.id] = Array.from(allDisplayTagsForCategory).sort();
      });
      return calculatedTags;
    }, [categories, tagAliasMap]); // Add tagAliasMap to dependencies
    

    Self-correction: The getPrimaryTag helper above is not quite right for only showing keys. The logic should be: iterate agent tags, find their primary mapping if one exists, otherwise use the tag itself. Then collect unique primary tags.

    Revised Proposed Update Snippet (Conceptual):

    const getPrimaryDisplayTag = (rawTag: string): string => {
      const lowerRawTag = rawTag.toLowerCase();
      for (const [primary, aliases] of Object.entries(tagAliasMap)) {
        if (primary === lowerRawTag || aliases.includes(lowerRawTag)) {
          return primary; // Return the key (primary tag)
        }
      }
      return lowerRawTag; // If not in map (neither key nor alias), use the lowercased original tag
    };
    
    const tagsPerCategory = useMemo(() => {
      const calculatedTags: Record<string, string[]> = {};
      categories.forEach(category => {
        const displayablePrimaryTags = new Set<string>();
        category.agents.forEach(agent => {
          agent.tags.forEach(rawTag => {
            displayablePrimaryTags.add(getPrimaryDisplayTag(rawTag));
          });
        });
        calculatedTags[category.id] = Array.from(displayablePrimaryTags).sort();
      });
      return calculatedTags;
    }, [categories]); // Assuming tagAliasMap is imported and stable
    
  3. Update Agent Filtering Logic:

    • When filtering agents based on selectedFilterTags (which now contain primary tags), the agent's tags must also be mapped to their primary versions before checking for inclusion.

    Current Logic (Simplified):

    const filteredAgents = currentCategorySelectedTags.length === 0
      ? category.agents
      : category.agents.filter(agent =>
        agent.tags && agent.tags.some(agentTag => currentCategorySelectedTags.includes(agentTag))
      );
    

    Proposed Update Snippet (Conceptual):

    const filteredAgents = currentCategorySelectedTags.length === 0
      ? category.agents
      : category.agents.filter(agent => {
          if (!agent.tags) return false;
          // Get primary display tags for this agent
          const agentPrimaryTags = new Set(agent.tags.map(rawTag => getPrimaryDisplayTag(rawTag)));
          // Check if any of the agent's primary tags are in the selected filter tags
          return currentCategorySelectedTags.some(selectedPrimaryTag => agentPrimaryTags.has(selectedPrimaryTag));
        }
      );
    
  4. Ensure handleTagClick and clearCategoryFilters work with primary tags.

    • handleTagClick should receive the primary tag when a tag button (which now displays a primary tag) is clicked. This should be inherently handled if categoryUniqueTags (which populates the buttons) is correctly derived from primary tags.

3. Testing

Thoroughly test the updated filtering logic.

  1. Manual Testing:

    • Verify that the displayed filter tags for each category are indeed the primary tags (keys from tagAliasMap or unmapped tags).
    • Check that selecting a primary filter tag correctly filters agents that have either that primary tag OR any of its alias tags (values from tagAliasMap).
    • Test with multiple selected filter tags (OR logic should still apply to primary tags).
    • Test categories with many tags, few tags, and no tags.
    • Test the "Show all / Collapse" functionality for tags.
    • Test clearing filters.
  2. Unit Testing (Conceptual):

    • Goal: Ensure that the UI only displays primary tags for selection and that filtering correctly uses the alias map.

    • Test Case 1: tagsPerCategory generation

      • Given: A mock list of categories with agents having various tags (including aliases).
      • And: A defined tagAliasMap.
      • When: tagsPerCategory is computed.
      • Then: The resulting list of unique tags for a category should only contain primary tags (keys from the map or unmapped tags), and no alias tags directly.
      • Example:
        // Mock data
        const mockAgents = [
          { tags: ["llm-agent", "fast"] }, // "llm-agent" is an alias for "llm"
          { tags: ["llm", "slow"] },       // "llm" is a primary tag
          { tags: ["experimental"] }     // "experimental" is not in map
        ];
        const mockTagAliasMap = { "llm": ["llm-agent", "large-language-model"] };
        
        // Simplified conceptual test logic
        const primaryTags = getUniquePrimaryTagsForCategory(mockAgents, mockTagAliasMap);
        expect(primaryTags).toContain("llm");
        expect(primaryTags).toContain("fast");
        expect(primaryTags).toContain("slow");
        expect(primaryTags).toContain("experimental");
        expect(primaryTags).not.toContain("llm-agent");
        expect(primaryTags.length).toBe(4); // Or whatever the expected count of unique primary tags is
        
    • Test Case 2: Agent filtering with aliasing

      • Given: A list of mock agents, some with primary tags, some with alias tags.
      • And: A defined tagAliasMap.
      • When: A primary tag is selected for filtering (e.g., "llm").
      • Then: Agents containing either the primary tag "llm" OR any of its aliases (e.g., "llm-agent") should be included in the filtered results.
      • Example:
        const agent1 = { name: "Agent A", tags: ["llm-agent", "data"] }; // Alias
        const agent2 = { name: "Agent B", tags: ["llm", "visualization"] }; // Primary
        const agent3 = { name: "Agent C", tags: ["ai", "automation"] }; // Different tag
        const mockAgents = [agent1, agent2, agent3];
        const mockTagAliasMap = { "llm": ["llm-agent", "llms"] };
        const selectedFilters = ["llm"];
        
        const filtered = filterAgentsWithAliases(mockAgents, selectedFilters, mockTagAliasMap);
        
        expect(filtered).toContain(agent1);
        expect(filtered).toContain(agent2);
        expect(filtered).not.toContain(agent3);
        
        (Note: filterAgentsWithAliases would encapsulate the filtering logic from featured-agents.tsx)

4. Code Cleanup and Final Review

  • Move the finalized tagAliasMap to a sensible location (e.g., ai-agent-hub-web/lib/tag-alias-map.ts).
  • Update import paths.
  • Remove any console logs or temporary code.
  • Perform a final code review.

Related Documents