Skip to main content
Glama
SourceControlPanel.tsx•6.78 kB
import React, { useState, useEffect, useCallback } from 'react'; import { useTool } from '@modelcontextprotocol/sdk/react'; import Spinner from './Spinner.js'; interface SourceControlPanelProps { addToTerminal: (output: string) => void; refreshFileTree: () => void; } type RepoStatus = { isRepo: boolean; branch?: string; remoteUrl?: string; files?: string[]; }; const SourceControlPanel: React.FC<SourceControlPanelProps> = ({ addToTerminal, refreshFileTree }) => { const [status, setStatus] = useState<RepoStatus | null>(null); const [commitMessage, setCommitMessage] = useState(''); const [authorName, setAuthorName] = useState('PyForge User'); const [authorEmail, setAuthorEmail] = useState('user@pyforge.dev'); const { call: getStatus, isPending: isFetchingStatus } = useTool('github_get_status'); const { call: commitAndPush, isPending: isPushing } = useTool('github_commit_and_push'); const fetchStatus = useCallback(async () => { const result = await getStatus({}); try { const textResponse = result?.content?.[0]?.type === 'text' ? result.content[0].text : '{}'; const parsedStatus = JSON.parse(textResponse); setStatus(parsedStatus); } catch (e) { setStatus({ isRepo: false }); addToTerminal(`Could not parse git status: ${e}`); } }, [getStatus, addToTerminal]); useEffect(() => { fetchStatus(); }, [fetchStatus]); const handleCommitAndPush = async () => { if (!commitMessage.trim()) { alert('Please provide a commit message.'); return; } if (!status?.isRepo || !status.branch) { alert('Not in a valid git repository.'); return; } addToTerminal(`Committing and pushing to ${status.branch}...`); const result = await commitAndPush({ branch: status.branch, commitMessage, authorName, authorEmail, }); const output = result?.content?.[0]?.type === 'text' ? result.content[0].text : 'Commit and push failed.'; addToTerminal(output); if(output.startsWith('Successfully')) { setCommitMessage(''); fetchStatus(); // Refresh status after push refreshFileTree(); // Refresh file explorer } }; const isLoading = isFetchingStatus || isPushing; const renderContent = () => { if (isFetchingStatus && !status) { return <div style={styles.section}><Spinner /></div>; } if (!status?.isRepo) { return ( <div style={styles.section}> <h3 style={styles.listHeader}>No Repository Found</h3> <p style={styles.helpText}>The workspace is not a git repository.</p> <p style={styles.helpText}>Please use the terminal to clone a repository into the workspace root:</p> <pre style={styles.codeBlock}>git clone [repository_url] .</pre> <button onClick={fetchStatus} disabled={isLoading} style={styles.button}> {isFetchingStatus ? <Spinner size={16} /> : 'Refresh Status'} </button> </div> ); } return ( <div style={styles.section}> <div style={styles.repoInfo}> <p style={styles.helpText}><strong>Branch:</strong> {status.branch}</p> <p style={styles.helpText}><strong>Remote:</strong> {status.remoteUrl}</p> </div> <h3 style={styles.listHeader}>Changes ({status.files?.length || 0})</h3> {(status.files && status.files.length > 0) ? ( <ul style={styles.fileList}> {status.files.map(file => <li key={file} style={styles.fileItem}>{file}</li>)} </ul> ) : <p style={styles.helpText}>No changes detected.</p>} <div style={{ borderTop: '1px solid var(--border-color)', paddingTop: '15px'}}> <h3 style={styles.listHeader}>Commit & Push</h3> <div style={styles.formGroup}> <label style={styles.label}>Commit Message</label> <textarea value={commitMessage} onChange={e => setCommitMessage(e.target.value)} style={{...styles.input, height: '60px'}} placeholder="Your commit message..."/> </div> <div style={styles.formGroup}> <label style={styles.label}>Author Name</label> <input type="text" value={authorName} onChange={e => setAuthorName(e.target.value)} style={styles.input} /> </div> <div style={styles.formGroup}> <label style={styles.label}>Author Email</label> <input type="email" value={authorEmail} onChange={e => setAuthorEmail(e.target.value)} style={styles.input} /> </div> <button onClick={handleCommitAndPush} disabled={isPushing} style={styles.button}> {isPushing ? <Spinner size={16} /> : `Commit & Push to '${status.branch}'`} </button> </div> </div> ); }; return ( <div style={styles.container}> <div style={styles.header}>SOURCE CONTROL</div> {renderContent()} </div> ); }; const styles: { [key: string]: React.CSSProperties } = { container: { display: 'flex', flexDirection: 'column', height: '100%', color: 'var(--text-primary)' }, header: { padding: '10px', fontWeight: 'bold', borderBottom: '1px solid var(--border-color)', flexShrink: 0 }, section: { padding: '10px', display: 'flex', flexDirection: 'column', gap: '15px', overflowY: 'auto', flex: 1 }, formGroup: { display: 'flex', flexDirection: 'column', gap: '5px', marginBottom: '10px' }, label: { fontSize: '12px', color: 'var(--text-secondary)' }, input: { flex: 1, padding: '5px', backgroundColor: 'var(--background-tertiary)', border: '1px solid var(--border-color)', color: 'var(--text-primary)', borderRadius: '3px', resize: 'vertical' }, button: { padding: '8px 10px', backgroundColor: 'var(--accent-primary)', color: 'white', border: 'none', borderRadius: '3px', cursor: 'pointer', minWidth: '70px', display: 'flex', justifyContent: 'center', alignItems: 'center' }, listHeader: { marginTop: 0, fontSize: '14px', color: 'var(--text-primary)' }, helpText: { fontSize: '12px', color: 'var(--text-secondary)', margin: 0, wordBreak: 'break-word' }, repoInfo: { display: 'flex', flexDirection: 'column', gap: '5px', padding: '10px', backgroundColor: 'var(--background-tertiary)', borderRadius: '3px' }, codeBlock: { padding: '10px', backgroundColor: 'var(--background-primary)', borderRadius: '3px', color: 'var(--text-secondary)', fontSize: '12px', fontFamily: 'monospace' }, fileList: { listStyle: 'none', padding: '0 0 0 10px', margin: 0, maxHeight: '150px', overflowY: 'auto' }, fileItem: { padding: '2px 0', fontSize: '13px', fontFamily: 'monospace', color: 'var(--text-secondary)' }, }; export default SourceControlPanel;

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