import type { MCPResource, MCPResourceInfo } from '$lib/types'; import { parseResourcePath } from '$lib/utils'; export interface ResourceTreeNode { name: string; resource?: MCPResourceInfo; children: Map; isFiltered?: boolean; } function resourceMatchesSearch(resource: MCPResource, query: string): boolean { return ( resource.title?.toLowerCase().includes(query) || resource.uri.toLowerCase().includes(query) ); } export function buildResourceTree( resourceList: MCPResource[], serverName: string, searchQuery?: string ): ResourceTreeNode { const root: ResourceTreeNode = { name: 'root', children: new Map() }; if (!searchQuery || !searchQuery.trim()) { for (const resource of resourceList) { const pathParts = parseResourcePath(resource.uri); let current = root; for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; if (!current.children.has(part)) { current.children.set(part, { name: part, children: new Map() }); } current = current.children.get(part)!; } const fileName = pathParts[pathParts.length - 1] || resource.name; current.children.set(resource.uri, { name: fileName, resource: { ...resource, serverName }, children: new Map() }); } return root; } const query = searchQuery.toLowerCase(); // Build tree with filtering for (const resource of resourceList) { if (!resourceMatchesSearch(resource, query)) continue; const pathParts = parseResourcePath(resource.uri); let current = root; for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; if (!current.children.has(part)) { current.children.set(part, { name: part, children: new Map(), isFiltered: true }); } current = current.children.get(part)!; } const fileName = pathParts[pathParts.length - 1] || resource.name; current.children.set(resource.uri, { name: fileName, resource: { ...resource, serverName }, children: new Map(), isFiltered: true }); } function cleanupEmptyFolders(node: ResourceTreeNode): boolean { if (node.resource) return true; const toDelete: string[] = []; for (const [name, child] of node.children.entries()) { if (!cleanupEmptyFolders(child)) { toDelete.push(name); } } for (const name of toDelete) { node.children.delete(name); } return node.children.size > 0; } cleanupEmptyFolders(root); return root; } export function countTreeResources(node: ResourceTreeNode): number { if (node.resource) return 1; let count = 0; for (const child of node.children.values()) { count += countTreeResources(child); } return count; } export function sortTreeChildren(children: ResourceTreeNode[]): ResourceTreeNode[] { return children.sort((a, b) => { const aIsFolder = !a.resource && a.children.size > 0; const bIsFolder = !b.resource && b.children.size > 0; if (aIsFolder && !bIsFolder) return -1; if (!aIsFolder && bIsFolder) return 1; return a.name.localeCompare(b.name); }); }