Skip to main content
Glama
FileExplorer.tsx•4.11 kB
import React, { useState, useEffect, useCallback } from 'react'; import { FileIcon, FolderIcon, PythonIcon, NewFileIcon } from './icons.js'; import type { VFS, FileSystemEntry } from '../lib/vfs.js'; import Spinner from './Spinner.js'; interface FileExplorerProps { onOpenFile: (path: string) => void; addToTerminal: (output: string) => void; vfs: VFS; refreshKey?: number; } const FileTree: React.FC<{ entries: FileSystemEntry[]; onOpenFile: (path: string) => void; level?: number }> = ({ entries, onOpenFile, level = 0 }) => { return ( <div> {entries.sort((a,b) => Number(b.isDirectory) - Number(a.isDirectory) || a.name.localeCompare(b.name)).map(entry => ( <div key={entry.path}> <div style={{ ...styles.fileItem, paddingLeft: `${10 + level * 15}px` }} onClick={() => !entry.isDirectory && onOpenFile(entry.path)}> <span style={styles.icon}>{entry.isDirectory ? <FolderIcon /> : entry.name.endsWith('.py') ? <PythonIcon /> : <FileIcon />}</span> {entry.name} </div> </div> ))} </div> ); }; const FileExplorer: React.FC<FileExplorerProps> = ({ onOpenFile, addToTerminal, vfs, refreshKey }) => { const [fileTree, setFileTree] = useState<FileSystemEntry[] | null>(null); const [isLoading, setIsLoading] = useState(true); const fetchFiles = useCallback(async () => { setIsLoading(true); try { // Always list files from the root of the workspace const tree = await vfs.getTree('/workspace'); setFileTree(tree); } catch (e) { addToTerminal(`Error fetching file tree: ${e instanceof Error ? e.message : String(e)}`); // If workspace doesn't exist, create it if (e instanceof Error && (e.message.includes('No such file or directory') || e.message.includes('cannot access'))) { addToTerminal('Workspace not found. Creating /workspace directory...'); try { // A safe way to trigger directory creation if needed. The backend handles creation. await vfs.writeFile('/workspace/.pyforge_init', ''); fetchFiles(); // retry fetching } catch (createErr) { addToTerminal(`Failed to create workspace: ${createErr}`); } } } finally { setIsLoading(false); } }, [vfs, addToTerminal]); useEffect(() => { fetchFiles(); }, [fetchFiles, refreshKey]); const createNewFile = async () => { const fileName = prompt("Enter new file name (within /workspace):", "new_file.py"); if (fileName) { const path = `/workspace/${fileName.replace(/^\//, '')}`; // Ensure it's inside workspace try { await vfs.writeFile(path, '# New file created in PyForge'); addToTerminal(`Created file: ${path}`); fetchFiles(); onOpenFile(path); } catch (e) { addToTerminal(`Error creating file: ${e instanceof Error ? e.message : String(e)}`); } } }; return ( <div style={styles.explorerContainer}> <div style={styles.header}> <span>EXPLORER</span> <button style={styles.headerButton} onClick={createNewFile} title="New File"> <NewFileIcon/> </button> </div> <div style={styles.fileList}> {isLoading && <Spinner />} {fileTree && <FileTree entries={fileTree} onOpenFile={onOpenFile} />} </div> </div> ); }; const styles: { [key: string]: React.CSSProperties } = { explorerContainer: { display: 'flex', flexDirection: 'column', height: '100%' }, header: { padding: '10px', fontWeight: 'bold', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid var(--border-color)', flexShrink: 0 }, headerButton: { background: 'none', border: 'none', color: 'var(--text-primary)', cursor: 'pointer' }, fileList: { overflowY: 'auto', flex: 1, padding: '5px 0' }, fileItem: { display: 'flex', alignItems: 'center', padding: '4px 10px', cursor: 'pointer', fontSize: 'var(--font-size-small)' }, icon: { marginRight: '5px', display: 'flex', alignItems: 'center' }, }; export default FileExplorer;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pythondev-pro/egw_writings_mcp_server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server