import React, { useState } from "react"; import { FolderOpen, Folder, FileCode, FileText, Terminal, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; interface LSWidgetProps { path: string; result?: any; } export const LSWidget: React.FC = ({ path, result }) => { // If we have a result, show it using the LSResultWidget if (result) { let resultContent = ''; if (typeof result.content === 'string') { resultContent = result.content; } else if (result.content && typeof result.content === 'object') { if (result.content.text) { resultContent = result.content.text; } else if (Array.isArray(result.content)) { resultContent = result.content .map((c: any) => (typeof c === 'string' ? c : c.text || JSON.stringify(c))) .join('\n'); } else { resultContent = JSON.stringify(result.content, null, 2); } } return (
Directory contents for: {path}
{resultContent && }
); } return (
Listing directory: {path} {!result && (
Loading...
)}
); }; interface LSResultWidgetProps { content: string; } export const LSResultWidget: React.FC = ({ content }) => { const [expandedDirs, setExpandedDirs] = useState>(new Set()); // Parse the directory tree structure const parseDirectoryTree = (rawContent: string) => { const lines = rawContent.split('\n'); const entries: Array<{ path: string; name: string; type: 'file' | 'directory'; level: number; }> = []; let currentPath: string[] = []; for (const line of lines) { // Skip NOTE section and everything after it if (line.startsWith('NOTE:')) { break; } // Skip empty lines if (!line.trim()) continue; // Calculate indentation level const indent = line.match(/^(\s*)/)?.[1] || ''; const level = Math.floor(indent.length / 2); // Extract the entry name const entryMatch = line.match(/^\s*-\s+(.+?)(\/$)?$/); if (!entryMatch) continue; const fullName = entryMatch[1]; const isDirectory = line.trim().endsWith('/'); const name = isDirectory ? fullName : fullName; // Update current path based on level currentPath = currentPath.slice(0, level); currentPath.push(name); entries.push({ path: currentPath.join('/'), name, type: isDirectory ? 'directory' : 'file', level, }); } return entries; }; const entries = parseDirectoryTree(content); const toggleDirectory = (path: string) => { setExpandedDirs(prev => { const next = new Set(prev); if (next.has(path)) { next.delete(path); } else { next.add(path); } return next; }); }; // Group entries by parent for collapsible display const getChildren = (parentPath: string, parentLevel: number) => { return entries.filter(e => { if (e.level !== parentLevel + 1) return false; const parentParts = parentPath.split('/').filter(Boolean); const entryParts = e.path.split('/').filter(Boolean); // Check if this entry is a direct child of the parent if (entryParts.length !== parentParts.length + 1) return false; // Check if all parent parts match for (let i = 0; i < parentParts.length; i++) { if (parentParts[i] !== entryParts[i]) return false; } return true; }); }; const renderEntry = (entry: typeof entries[0], isRoot = false) => { const hasChildren = entry.type === 'directory' && entries.some(e => e.path.startsWith(entry.path + '/') && e.level === entry.level + 1); const isExpanded = expandedDirs.has(entry.path) || isRoot; const getIcon = () => { if (entry.type === 'directory') { return isExpanded ? : ; } // File type icons based on extension const ext = entry.name.split('.').pop()?.toLowerCase(); switch (ext) { case 'rs': return ; case 'toml': case 'yaml': case 'yml': case 'json': return ; case 'md': return ; case 'js': case 'jsx': case 'ts': case 'tsx': return ; case 'py': return ; case 'go': return ; case 'sh': case 'bash': return ; default: return ; } }; return (
entry.type === 'directory' && hasChildren && toggleDirectory(entry.path)} > {entry.type === 'directory' && hasChildren && ( )} {(!hasChildren || entry.type !== 'directory') && (
)} {getIcon()} {entry.name}
{entry.type === 'directory' && hasChildren && isExpanded && (
{getChildren(entry.path, entry.level).map(child => renderEntry(child))}
)}
); }; // Get root entries const rootEntries = entries.filter(e => e.level === 0); return (
{rootEntries.map(entry => renderEntry(entry, true))}
); };