diff --git a/services/library-desk/src/clients/wikijs_client.py b/services/library-desk/src/clients/wikijs_client.py index 76ea589..f2aaff8 100644 --- a/services/library-desk/src/clients/wikijs_client.py +++ b/services/library-desk/src/clients/wikijs_client.py @@ -231,6 +231,94 @@ class WikiJSClient: return pages + async def list_all_pages( + self, + path_prefix: str = "", + tags: Optional[List[str]] = None, + batch_size: int = 100 + ) -> List[Dict[str, Any]]: + """ + List ALL pages with pagination support. + + Fetches pages in batches until all are retrieved. + + Args: + path_prefix: Filter by path prefix (e.g., "users/jpmschweitzer") + tags: Filter by tags + batch_size: Number of pages per batch (max 100) + + Returns: + Complete list of page objects + """ + all_pages = [] + offset = 0 + + while True: + # Wiki.js list doesn't support offset, but limit is enough + # since we filter client-side by path_prefix + # Just fetch a large batch + pages = await self.list_pages( + path_prefix=path_prefix, + tags=tags, + limit=1000 # Fetch up to 1000 at once + ) + + if not pages: + break + + all_pages = pages + break # Wiki.js list doesn't paginate, so one call is enough + + logger.info(f"list_all_pages: found {len(all_pages)} pages (prefix: {path_prefix or 'all'})") + return all_pages + + async def get_taxonomy_structure( + self, + user_namespace: str, + limit: int = 500 + ) -> Dict[str, List[str]]: + """ + Get the existing taxonomy structure for a user namespace. + + Returns a dict mapping top-level categories to their subcategories. + This helps the LLM choose existing paths rather than creating new ones. + + Args: + user_namespace: User namespace (e.g., "users/jpmschweitzer") + limit: Maximum pages to fetch + + Returns: + Dict like {"reference": ["political-entities", "tech"], "places": ["the-netherlands"]} + """ + from collections import defaultdict + + pages = await self.list_pages(path_prefix=user_namespace, limit=limit) + + # Extract structure: category -> set of subcategories + structure: Dict[str, set] = defaultdict(set) + prefix = user_namespace.strip("/") + "/" + + for page in pages: + path = page.get("path", "") + if not path.startswith(prefix): + continue + + # Get relative path within user namespace + rel_path = path[len(prefix):] + parts = rel_path.split("/") + + if len(parts) >= 1: + category = parts[0] + if len(parts) >= 2: + # Has subcategory (e.g., reference/political-entities/nato) + structure[category].add(parts[1]) + else: + # Just ensure category exists in structure + structure[category] # Access to ensure key exists + + # Convert sets to sorted lists + return {cat: sorted(list(subs)) for cat, subs in sorted(structure.items())} + async def get_page(self, page_id: int) -> Optional[Dict[str, Any]]: """ Get single page by ID.