Skip to content

Commit 35dacd7

Browse files
committed
feat: Add sidebarTitle and SDK suffix for cleaner SEO and navigation
- Add sidebarTitle field for clean sidebar navigation - Update title suffix to use 'SDK' for clarity - Use bullet separator (•) instead of pipe - Keep friendly_title for SEO optimization
1 parent 58e4296 commit 35dacd7

File tree

2 files changed

+241
-38
lines changed

2 files changed

+241
-38
lines changed

praisonai_tools/docs_generator/generate_rust_docs.py

Lines changed: 145 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@ def escape_mdx(text: str) -> str:
2222
return text.replace('<', '&lt;').replace('>', '&gt;').replace('{', '&#123;').replace('}', '&#125;')
2323

2424

25-
def sanitize_description(text: str) -> str:
26-
"""Sanitize description for frontmatter."""
25+
def sanitize_description(text: str, max_len: int = 150) -> str:
26+
"""Sanitize description for frontmatter. Truncates at word boundary."""
2727
if not text:
2828
return ""
29-
return text.replace('"', "'").replace('\n', ' ')[:150]
29+
clean = text.replace('"', "'").replace('\n', ' ').strip()
30+
if len(clean) <= max_len:
31+
return clean
32+
# Truncate at word boundary
33+
truncated = clean[:max_len].rsplit(' ', 1)[0]
34+
return truncated + "..."
3035

3136

3237
# Icon map
@@ -38,6 +43,110 @@ def sanitize_description(text: str) -> str:
3843
"commands": "terminal", "default": "file-code"
3944
}
4045

46+
# Friendly title mappings for modules
47+
MODULE_TITLES = {
48+
"agent": "Agent",
49+
"builder": "Agent Builder",
50+
"config": "Configuration",
51+
"error": "Error Handling",
52+
"llm": "LLM Providers",
53+
"memory": "Memory",
54+
"tools": "Tools",
55+
"workflows": "Workflows",
56+
"praisonai_derive": "Derive Macros",
57+
"chat": "Chat Command",
58+
"prompt": "Prompt Command",
59+
"run": "Run Command",
60+
}
61+
62+
# Rich descriptions for modules (used when docstring is empty or as fallback)
63+
MODULE_DESCRIPTIONS = {
64+
"agent": "Core AI Agent implementation for building intelligent agents in Rust",
65+
"builder": "Fluent builder pattern for constructing Rust AI agents",
66+
"config": "Configuration types for PraisonAI Rust AI agents",
67+
"error": "Error handling utilities for Rust AI agent operations",
68+
"llm": "LLM provider abstractions for Rust AI agents (OpenAI, Anthropic, Ollama)",
69+
"memory": "Memory and conversation history for Rust AI agents",
70+
"tools": "Tool system for extending Rust AI agent capabilities",
71+
"workflows": "Multi-agent workflow patterns for Rust AI orchestration",
72+
"praisonai_derive": "Procedural macros for defining Rust AI agent tools",
73+
"chat": "Interactive chat command for Rust AI agents",
74+
"prompt": "Single-shot prompt execution for Rust AI agents",
75+
"run": "Workflow execution command for Rust AI agents",
76+
}
77+
78+
# Abbreviations to preserve in titles
79+
ABBREVIATIONS = {"llm", "api", "cli", "id", "url", "http", "ai", "io"}
80+
81+
82+
def friendly_title(name: str, page_type: str = "class") -> str:
83+
"""Convert a name to a friendly, human-readable title.
84+
85+
Args:
86+
name: The raw name (e.g., "AgentBuilder", "praisonai_derive")
87+
page_type: One of "module", "class", "function"
88+
"""
89+
# Check for explicit module title mapping
90+
if page_type == "module" and name.lower() in MODULE_TITLES:
91+
return MODULE_TITLES[name.lower()]
92+
93+
# For functions, add parentheses
94+
if page_type == "function":
95+
# Special case for macros
96+
if name == "tool":
97+
return "#[tool] Macro"
98+
return f"{name}()"
99+
100+
# Convert snake_case to Title Case
101+
if "_" in name:
102+
parts = name.split("_")
103+
titled_parts = []
104+
for part in parts:
105+
if part.lower() in ABBREVIATIONS:
106+
titled_parts.append(part.upper())
107+
else:
108+
titled_parts.append(part.capitalize())
109+
return " ".join(titled_parts)
110+
111+
# Convert PascalCase to Title Case with spaces
112+
# e.g., "AgentBuilder" -> "Agent Builder", "LlmConfig" -> "LLM Config"
113+
result = []
114+
current_word = []
115+
116+
for i, char in enumerate(name):
117+
if char.isupper():
118+
# Check if this is part of an abbreviation
119+
if current_word:
120+
word = "".join(current_word)
121+
# If previous chars form an abbreviation, keep them together
122+
if word.lower() in ABBREVIATIONS:
123+
result.append(word.upper())
124+
else:
125+
result.append(word)
126+
current_word = []
127+
current_word.append(char)
128+
else:
129+
current_word.append(char)
130+
131+
# Don't forget the last word
132+
if current_word:
133+
word = "".join(current_word)
134+
if word.lower() in ABBREVIATIONS:
135+
result.append(word.upper())
136+
else:
137+
result.append(word)
138+
139+
# Join and fix common patterns
140+
title = " ".join(result)
141+
142+
# Fix cases like "L L M" -> "LLM"
143+
for abbr in ABBREVIATIONS:
144+
spaced = " ".join(abbr.upper())
145+
if spaced in title:
146+
title = title.replace(spaced, abbr.upper())
147+
148+
return title
149+
41150

42151
def get_icon(name: str) -> str:
43152
"""Get icon for module name."""
@@ -47,17 +156,21 @@ def get_icon(name: str) -> str:
47156
def generate_module_page(info, output_dir: Path, dry_run: bool = False) -> str:
48157
"""Generate a module hub page."""
49158
short_name = info.short_name or info.name.split('.')[-1]
50-
desc = sanitize_description(info.docstring) or f"Rust module {short_name}"
159+
title = friendly_title(short_name, "module")
160+
title_suffix = " • Rust AI Agent SDK"
161+
# Use docstring if available, otherwise use our rich descriptions
162+
desc = sanitize_description(info.docstring) or MODULE_DESCRIPTIONS.get(short_name.lower(), f"Rust AI Agent SDK - {title}")
51163

52164
content = f'''---
53-
title: "{short_name}"
165+
title: "{title}{title_suffix}"
166+
sidebarTitle: "{title}"
54167
description: "{desc}"
55168
icon: "{get_icon(short_name)}"
56169
---
57170
58-
# {info.name}
171+
# {short_name}
59172
60-
<Badge color="orange">Rust SDK</Badge>
173+
<Badge color="orange">Rust AI Agent SDK</Badge>
61174
62175
{escape_mdx(info.docstring) if info.docstring else ""}
63176
@@ -72,17 +185,19 @@ def generate_module_page(info, output_dir: Path, dry_run: bool = False) -> str:
72185
if info.classes:
73186
content += "## Types\n\n<CardGroup cols={2}>\n"
74187
for cls in info.classes:
188+
cls_title = friendly_title(cls.name, "class")
75189
cls_desc = sanitize_description(cls.docstring) or "Type definition."
76-
content += f' <Card title="{cls.name}" icon="brackets-curly" href="../classes/{cls.name}">\n'
190+
content += f' <Card title="{cls_title}" icon="brackets-curly" href="../classes/{cls.name}">\n'
77191
content += f" {cls_desc}\n"
78192
content += " </Card>\n"
79193
content += "</CardGroup>\n\n"
80194

81195
if info.functions:
82196
content += "## Functions\n\n<CardGroup cols={2}>\n"
83197
for func in info.functions:
198+
func_title = friendly_title(func.name, "function")
84199
func_desc = sanitize_description(func.docstring) or "Function definition."
85-
content += f' <Card title="{func.name}()" icon="function" href="../functions/{func.name}">\n'
200+
content += f' <Card title="{func_title}" icon="function" href="../functions/{func.name}">\n'
86201
content += f" {func_desc}\n"
87202
content += " </Card>\n"
88203
content += "</CardGroup>\n\n"
@@ -98,19 +213,25 @@ def generate_module_page(info, output_dir: Path, dry_run: bool = False) -> str:
98213
def generate_class_page(cls, module_info, output_dir: Path, dry_run: bool = False) -> str:
99214
"""Generate a class/struct page."""
100215
short_name = module_info.short_name or module_info.name.split('.')[-1]
101-
desc = sanitize_description(cls.docstring) or f"Class {cls.name}"
216+
module_title = friendly_title(short_name, "module")
217+
title = friendly_title(cls.name, "class")
218+
title_suffix = " • Rust AI Agent SDK"
219+
# Include original name in description for searchability
220+
base_desc = sanitize_description(cls.docstring) if cls.docstring else f"{cls.name} struct for Rust AI agents"
221+
desc = f"{base_desc}" if cls.name in base_desc else f"{cls.name}: {base_desc}"
102222

103223
content = f'''---
104-
title: "{cls.name}"
224+
title: "{title}{title_suffix}"
225+
sidebarTitle: "{title}"
105226
description: "{desc}"
106227
icon: "brackets-curly"
107228
---
108229
109230
# {cls.name}
110231
111-
> Defined in the [**{short_name}**](../modules/{short_name}) module.
232+
> Defined in the [**{module_title}**](../modules/{short_name}) module.
112233
113-
<Badge color="orange">Rust SDK</Badge>
234+
<Badge color="orange">Rust AI Agent SDK</Badge>
114235
115236
{escape_mdx(cls.docstring) if cls.docstring else ""}
116237
@@ -156,21 +277,27 @@ def generate_class_page(cls, module_info, output_dir: Path, dry_run: bool = Fals
156277
def generate_function_page(func, module_info, output_dir: Path, dry_run: bool = False) -> str:
157278
"""Generate a function page."""
158279
short_name = module_info.short_name or module_info.name.split('.')[-1]
159-
desc = sanitize_description(func.docstring) or f"Function {func.name}"
280+
module_title = friendly_title(short_name, "module")
281+
title = friendly_title(func.name, "function")
282+
title_suffix = " • Rust AI Agent SDK"
283+
# Include original name in description for searchability
284+
base_desc = sanitize_description(func.docstring) if func.docstring else f"{func.name} function for Rust AI agents"
285+
desc = f"{base_desc}" if func.name in base_desc else f"{func.name}: {base_desc}"
160286
async_prefix = "async " if hasattr(func, 'is_async') and func.is_async else ""
161287
ret_type = func.return_type if hasattr(func, 'return_type') and func.return_type else "()"
162288

163289
content = f'''---
164-
title: "{func.name}"
290+
title: "{title}{title_suffix}"
291+
sidebarTitle: "{title}"
165292
description: "{desc}"
166293
icon: "function"
167294
---
168295
169-
# {func.name}()
296+
# {func.name}
170297
171-
> Defined in the [**{short_name}**](../modules/{short_name}) module.
298+
> Defined in the [**{module_title}**](../modules/{short_name}) module.
172299
173-
<Badge color="orange">Rust SDK</Badge>
300+
<Badge color="orange">Rust AI Agent SDK</Badge>
174301
175302
```rust
176303
{async_prefix}fn {func.name}({func.signature}) -> {ret_type}

0 commit comments

Comments
 (0)