Skip to main content
Glama
App.tsx•6.18 kB
import React, { useState, useCallback, useRef, useEffect } from 'react'; import ActivityBar from './components/ActivityBar.js'; import FileExplorer from './components/FileExplorer.js'; import PackageManager from './components/PackageManager.js'; import CodeEditor from './components/CodeEditor.js'; import BottomPanel from './components/BottomPanel.js'; import { VFS } from './lib/vfs.js'; import { useTool } from '@modelcontextprotocol/sdk/react'; import SourceControlPanel from './components/SourceControlPanel.js'; export type View = 'files' | 'packages' | 'source-control'; export type File = { path: string; content: string; isDirty?: boolean }; export type BottomPanelView = 'terminal' | 'preview'; const App = () => { const [activeView, setActiveView] = useState<View>('files'); const [sidebarWidth, setSidebarWidth] = useState(250); const [terminalHeight, setTerminalHeight] = useState(200); const [isResizingSidebar, setIsResizingSidebar] = useState(false); const [isResizingTerminal, setIsResizingTerminal] = useState(false); const [openFiles, setOpenFiles] = useState<File[]>([]); const [activeFile, setActiveFile] = useState<string | null>(null); const [terminalOutput, setTerminalOutput] = useState<string[]>(['Welcome to PyForge! All command output from the UI will appear here. For an interactive shell, use the Terminal tab below.']); const [isLoading, setIsLoading] = useState(false); const [activeBottomView, setActiveBottomView] = useState<BottomPanelView>('terminal'); const [previewContent, setPreviewContent] = useState<string | null>(null); const [refreshFileTreeKey, setRefreshFileTreeKey] = useState(0); const runCommandTool = useTool('run_bash_command'); const vfs = React.useMemo(() => new VFS(runCommandTool), [runCommandTool]); const addToTerminal = useCallback((output: string) => { // Add a timestamp to distinguish logs const timestamp = new Date().toLocaleTimeString(); setTerminalOutput(prev => [...prev, `[${timestamp}] ${output}`]); }, []); const refreshFileTree = useCallback(() => { setRefreshFileTreeKey(k => k + 1); }, []); const openFile = useCallback(async (path: string) => { const existingFile = openFiles.find(f => f.path === path); if (existingFile) { setActiveFile(path); return; } setIsLoading(true); addToTerminal(`Opening file: ${path}`); try { const content = await vfs.readFile(path); setOpenFiles(prev => [...prev, { path, content }]); setActiveFile(path); addToTerminal(`Successfully opened ${path}`); } catch (error) { addToTerminal(`Error opening file ${path}: ${error instanceof Error ? error.message : String(error)}`); } finally { setIsLoading(false); } }, [openFiles, vfs, addToTerminal]); const handleMouseMove = useCallback((e: MouseEvent) => { if (isResizingSidebar) { setSidebarWidth(e.clientX); } if (isResizingTerminal) { setTerminalHeight(window.innerHeight - e.clientY); } }, [isResizingSidebar, isResizingTerminal]); const handleMouseUp = useCallback(() => { setIsResizingSidebar(false); setIsResizingTerminal(false); }, []); useEffect(() => { if (isResizingSidebar || isResizingTerminal) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; }, [isResizingSidebar, isResizingTerminal, handleMouseMove, handleMouseUp]); return ( <div style={styles.appContainer}> <ActivityBar activeView={activeView} setActiveView={setActiveView} /> <div style={{ ...styles.sidebar, width: sidebarWidth }}> {activeView === 'files' && <FileExplorer vfs={vfs} onOpenFile={openFile} addToTerminal={addToTerminal} refreshKey={refreshFileTreeKey} />} {activeView === 'packages' && <PackageManager addToTerminal={addToTerminal} />} {activeView === 'source-control' && <SourceControlPanel addToTerminal={addToTerminal} refreshFileTree={refreshFileTree} />} </div> <div style={styles.sidebarResizer} onMouseDown={() => setIsResizingSidebar(true)} /> <div style={styles.mainContent}> <CodeEditor openFiles={openFiles} setOpenFiles={setOpenFiles} activeFile={activeFile} setActiveFile={setActiveFile} addToTerminal={addToTerminal} vfs={vfs} setPreviewContent={setPreviewContent} setActiveBottomView={setActiveBottomView} /> <div style={styles.terminalResizer} onMouseDown={() => setIsResizingTerminal(true)} /> <div style={{ ...styles.terminalContainer, height: terminalHeight }}> <BottomPanel // FIX: Pass the correct state for the bottom panel view. activeView={activeBottomView} setActiveView={setActiveBottomView} terminalOutput={terminalOutput} setTerminalOutput={setTerminalOutput} htmlContent={previewContent} /> </div> </div> </div> ); }; const styles: { [key: string]: React.CSSProperties } = { appContainer: { display: 'flex', height: '100vh', width: '100vw', backgroundColor: 'var(--background-primary)', }, sidebar: { height: '100%', backgroundColor: 'var(--background-secondary)', display: 'flex', flexDirection: 'column', color: 'var(--text-primary)', borderRight: '1px solid var(--border-color)', }, sidebarResizer: { width: '5px', cursor: 'col-resize', backgroundColor: 'var(--border-color)', zIndex: 10, }, mainContent: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', }, terminalResizer: { height: '5px', cursor: 'row-resize', backgroundColor: 'var(--border-color)', zIndex: 10, }, terminalContainer: { width: '100%', backgroundColor: 'var(--background-secondary)', }, }; export default App;

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