Fix category display and tool counts in web UI
- Categories API now includes dynamic categories from database (categories used by tools but not in predefined list) - Add total_tools to categories API meta for accurate All Tools count - Fix web routes to use total_tools instead of summing category counts - Dynamic categories get auto-generated descriptions This fixes: - "All Tools" showing 2 instead of 4 - Categories like "code-analysis" and "education" not appearing - Incorrect category counts in sidebar Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c89bd44be8
commit
19e5be7e5a
|
|
@ -850,17 +850,26 @@ def create_app() -> Flask:
|
||||||
categories_yaml = get_repo_dir() / "categories" / "categories.yaml"
|
categories_yaml = get_repo_dir() / "categories" / "categories.yaml"
|
||||||
if categories_yaml.exists():
|
if categories_yaml.exists():
|
||||||
categories_payload = yaml.safe_load(categories_yaml.read_text(encoding="utf-8")) or {}
|
categories_payload = yaml.safe_load(categories_yaml.read_text(encoding="utf-8")) or {}
|
||||||
categories = (categories_payload or {}).get("categories", [])
|
predefined_categories = (categories_payload or {}).get("categories", [])
|
||||||
|
|
||||||
|
# Get counts for all categories in the database
|
||||||
counts = query_all(
|
counts = query_all(
|
||||||
g.db,
|
g.db,
|
||||||
"SELECT category, COUNT(DISTINCT owner || '/' || name) AS total FROM tools GROUP BY category",
|
"SELECT category, COUNT(DISTINCT owner || '/' || name) AS total FROM tools GROUP BY category",
|
||||||
)
|
)
|
||||||
count_map = {row["category"]: row["total"] for row in counts}
|
count_map = {row["category"]: row["total"] for row in counts}
|
||||||
|
|
||||||
|
# Calculate total tools across all categories
|
||||||
|
total_tools = sum(row["total"] for row in counts)
|
||||||
|
|
||||||
|
# Build data from predefined categories
|
||||||
|
predefined_names = set()
|
||||||
data = []
|
data = []
|
||||||
for cat in categories:
|
for cat in predefined_categories:
|
||||||
name = cat.get("name")
|
name = cat.get("name")
|
||||||
if not name:
|
if not name:
|
||||||
continue
|
continue
|
||||||
|
predefined_names.add(name)
|
||||||
data.append({
|
data.append({
|
||||||
"name": name,
|
"name": name,
|
||||||
"description": cat.get("description"),
|
"description": cat.get("description"),
|
||||||
|
|
@ -868,6 +877,18 @@ def create_app() -> Flask:
|
||||||
"tool_count": count_map.get(name, 0),
|
"tool_count": count_map.get(name, 0),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Add any categories from database that aren't in predefined list
|
||||||
|
for category_name, count in count_map.items():
|
||||||
|
if category_name and category_name not in predefined_names:
|
||||||
|
# Auto-generate display info for dynamic categories
|
||||||
|
display_name = category_name.replace("-", " ").title()
|
||||||
|
data.append({
|
||||||
|
"name": category_name,
|
||||||
|
"description": f"Tools in the {display_name} category",
|
||||||
|
"icon": None,
|
||||||
|
"tool_count": count,
|
||||||
|
})
|
||||||
|
|
||||||
reverse = order == "desc"
|
reverse = order == "desc"
|
||||||
if sort == "tool_count":
|
if sort == "tool_count":
|
||||||
data.sort(key=lambda item: item["tool_count"], reverse=reverse)
|
data.sort(key=lambda item: item["tool_count"], reverse=reverse)
|
||||||
|
|
@ -879,7 +900,10 @@ def create_app() -> Flask:
|
||||||
end = start + per_page
|
end = start + per_page
|
||||||
sliced = data[start:end]
|
sliced = data[start:end]
|
||||||
|
|
||||||
response = jsonify({"data": sliced, "meta": paginate(page, per_page, total)})
|
meta = paginate(page, per_page, total)
|
||||||
|
meta["total_tools"] = total_tools # Add total tools count to meta
|
||||||
|
|
||||||
|
response = jsonify({"data": sliced, "meta": meta})
|
||||||
response.headers["Cache-Control"] = "max-age=3600"
|
response.headers["Cache-Control"] = "max-age=3600"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,10 +65,11 @@ def _render_readme(readme: Optional[str]) -> str:
|
||||||
return Markup("<pre class=\"whitespace-pre-wrap\">") + escaped + Markup("</pre>")
|
return Markup("<pre class=\"whitespace-pre-wrap\">") + escaped + Markup("</pre>")
|
||||||
|
|
||||||
|
|
||||||
def _load_categories() -> List[SimpleNamespace]:
|
def _load_categories() -> Tuple[List[SimpleNamespace], int]:
|
||||||
|
"""Load categories with their tool counts. Returns (categories, total_tools)."""
|
||||||
status, payload = _api_get("/api/v1/categories", params={"per_page": 100})
|
status, payload = _api_get("/api/v1/categories", params={"per_page": 100})
|
||||||
if status != 200:
|
if status != 200:
|
||||||
return []
|
return [], 0
|
||||||
categories = []
|
categories = []
|
||||||
for item in payload.get("data", []):
|
for item in payload.get("data", []):
|
||||||
name = item.get("name")
|
name = item.get("name")
|
||||||
|
|
@ -81,7 +82,9 @@ def _load_categories() -> List[SimpleNamespace]:
|
||||||
description=item.get("description"),
|
description=item.get("description"),
|
||||||
icon=item.get("icon"),
|
icon=item.get("icon"),
|
||||||
))
|
))
|
||||||
return categories
|
# Get total tools from meta (includes all categories, even dynamic ones)
|
||||||
|
total_tools = payload.get("meta", {}).get("total_tools", sum(c.count for c in categories))
|
||||||
|
return categories, total_tools
|
||||||
|
|
||||||
|
|
||||||
def _load_publisher(slug: str) -> Optional[Dict[str, Any]]:
|
def _load_publisher(slug: str) -> Optional[Dict[str, Any]]:
|
||||||
|
|
@ -233,9 +236,7 @@ def _render_tools(category_override: Optional[str] = None):
|
||||||
return render_template("errors/500.html"), 500
|
return render_template("errors/500.html"), 500
|
||||||
|
|
||||||
meta = payload.get("meta", {})
|
meta = payload.get("meta", {})
|
||||||
categories = _load_categories()
|
categories, all_tools_total = _load_categories()
|
||||||
# Sum all category counts for the "All Tools" total
|
|
||||||
all_tools_total = sum(c.count for c in categories)
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"pages/tools.html",
|
"pages/tools.html",
|
||||||
tools=payload.get("data", []),
|
tools=payload.get("data", []),
|
||||||
|
|
@ -320,13 +321,14 @@ def search():
|
||||||
active_min_downloads=min_downloads,
|
active_min_downloads=min_downloads,
|
||||||
)
|
)
|
||||||
|
|
||||||
categories = sorted(_load_categories(), key=lambda c: c.count, reverse=True)[:8]
|
categories, _ = _load_categories()
|
||||||
|
popular_categories = sorted(categories, key=lambda c: c.count, reverse=True)[:8]
|
||||||
return render_template(
|
return render_template(
|
||||||
"pages/search.html",
|
"pages/search.html",
|
||||||
query="",
|
query="",
|
||||||
results=[],
|
results=[],
|
||||||
pagination=None,
|
pagination=None,
|
||||||
popular_categories=categories,
|
popular_categories=popular_categories,
|
||||||
facets={},
|
facets={},
|
||||||
active_categories=[],
|
active_categories=[],
|
||||||
active_tags=[],
|
active_tags=[],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue