import { useState, useEffect } from 'react'; import { marked } from 'marked'; export default function App() { // State management const [repoUrl, setRepoUrl] = useState(''); const [generatedRepoUrl, setGeneratedRepoUrl] = useState(''); const [docs, setDocs] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [progress, setProgress] = useState(null); const [statusMessage, setStatusMessage] = useState(''); const [expandedFiles, setExpandedFiles] = useState({}); // Track which files are expanded const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState(null); const [searchLoading, setSearchLoading] = useState(false); const [searchError, setSearchError] = useState(null); const [jsZipLoaded, setJsZipLoaded] = useState(false); const [readme, setReadme] = useState(null); const [readmeLoading, setReadmeLoading] = useState(false); const [readmeError, setReadmeError] = useState(null); const [gitignore, setGitignore] = useState(null); const [gitignoreLoading, setGitignoreLoading] = useState(false); const [gitignoreError, setGitignoreError] = useState(null); const API_BASE = import.meta.env.PROD ? '' : 'http://localhost:8000'; // Load JSZip library on mount useEffect(() => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'; script.onload = () => setJsZipLoaded(true); document.head.appendChild(script); }, []); // Utility: Copy to clipboard const copyToClipboard = (text, filename) => { navigator.clipboard.writeText(text).then(() => { alert(`Copied ${filename} to clipboard!`); }).catch(() => { alert('Failed to copy to clipboard'); }); }; // Utility: Download single markdown file const downloadFile = (content, filename) => { const blob = new Blob([content], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${filename}.md`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; // Utility: Download all docs as ZIP const downloadAllAsZip = async () => { if (!docs || docs.length === 0 || !window.JSZip) { alert('JSZip library not loaded or no docs available'); return; } try { const zip = new window.JSZip(); docs.forEach(file => { zip.file(`${file.filename}.md`, file.documentation); }); const blob = await zip.generateAsync({ type: 'blob' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `repoanalyzer-${new Date().getTime()}.zip`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (err) { console.error('Failed to download ZIP:', err); alert('Failed to create ZIP file'); } }; // Toggle accordion const toggleFile = (filename) => { setExpandedFiles(prev => ({ ...prev, [filename]: !prev[filename] })); }; const handleGenerateDocs = async () => { if (!repoUrl.trim()) { setError('Please enter a GitHub repository URL'); return; } setLoading(true); setError(null); setDocs(null); setProgress(null); setStatusMessage(''); setExpandedFiles({}); try { const response = await fetch(`${API_BASE}/generate-docs`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ repo_url: repoUrl }), }); if (!response.ok) { throw new Error('Failed to generate documentation'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); const processedFiles = []; let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); for (let i = 0; i < lines.length - 1; i++) { const line = lines[i].trim(); if (!line) continue; try { const data = JSON.parse(line); if (data.status === 'file_processed') { processedFiles.push(data.file); setProgress({ current: data.current, total: data.total, }); setStatusMessage(`Processed ${data.current}/${data.total} files`); } else if (data.status === 'rate_limit') { setStatusMessage(data.message); } else if (data.status === 'complete') { setProgress(null); setStatusMessage(''); setDocs(processedFiles); setGeneratedRepoUrl(repoUrl); } else if (data.status === 'error') { throw new Error(data.message); } } catch (parseError) { console.error('Failed to parse line:', line, parseError); } } buffer = lines[lines.length - 1]; } if (buffer.trim()) { try { const data = JSON.parse(buffer); if (data.status === 'error') { throw new Error(data.message); } } catch (parseError) { console.error('Failed to parse final buffer:', buffer, parseError); } } } catch (err) { setError(err.message || 'An error occurred'); setDocs(null); setProgress(null); } finally { setLoading(false); setStatusMessage(''); } }; const handleSearch = async () => { // Validate that docs have been generated if (!generatedRepoUrl) { setSearchError('Please generate documentation for a repository first'); return; } if (!searchQuery.trim()) { setSearchError('Please enter a search question'); return; } setSearchLoading(true); setSearchError(null); setSearchResults(null); try { const response = await fetch(`${API_BASE}/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ repo_url: generatedRepoUrl, question: searchQuery, }), }); if (!response.ok) { throw new Error('Failed to search documentation'); } const data = await response.json(); if (data.status === 'success') { setSearchResults(data.results); } else { setSearchError(data.message || 'Error searching documentation'); } } catch (err) { setSearchError(err.message || 'An error occurred'); } finally { setSearchLoading(false); } }; const handleGenerateReadme = async () => { if (!generatedRepoUrl) { setReadmeError('Please generate documentation first'); return; } setReadmeLoading(true); setReadmeError(null); setReadme(null); try { const response = await fetch(`${API_BASE}/generate-readme`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ repo_url: generatedRepoUrl, }), }); if (!response.ok) { throw new Error('Failed to generate README'); } const data = await response.json(); if (data.status === 'success') { setReadme(data.readme); } else { setReadmeError(data.message || 'Error generating README'); } } catch (err) { setReadmeError(err.message || 'An error occurred'); } finally { setReadmeLoading(false); } }; const handleGenerateGitignore = async () => { if (!generatedRepoUrl) { setGitignoreError('Please generate documentation first'); return; } setGitignoreLoading(true); setGitignoreError(null); setGitignore(null); try { const response = await fetch(`${API_BASE}/generate-gitignore`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ repo_url: generatedRepoUrl, }), }); if (!response.ok) { throw new Error('Failed to generate .gitignore'); } const data = await response.json(); if (data.status === 'success') { setGitignore(data.gitignore); } else { setGitignoreError(data.message || 'Error generating .gitignore'); } } catch (err) { setGitignoreError(err.message || 'An error occurred'); } finally { setGitignoreLoading(false); } }; return (
{/* Header */}

RepoAnalyzer

AI-powered code documentation

{/* Main Content */}
{/* Input Section */}

Generate Documentation

setRepoUrl(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleGenerateDocs()} placeholder="https://github.com/user/repo" className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-transparent outline-none transition text-gray-900" />
{/* Progress Bar */} {progress && (

{statusMessage || `Processing ${progress.current}/${progress.total} files...`}

)} {/* Error Alert */} {error && (
{error}
)}
{/* Generated Docs Section */} {docs && docs.length > 0 && (

Generated Documentation

{docs.map((file, index) => (
{/* Accordion Header */} {/* Accordion Content */} {expandedFiles[file.filename] && (
{/* Markdown Content */}
{/* Action Buttons */}
)}
))}
)} {/* README Section */} {readme && (
{/* README Header */}

README.md

{/* README Content */}
{/* README Actions */}
{readmeError && (
{readmeError}
)}
)} {/* .gitignore Section */} {gitignore && (
{/* Gitignore Header */}

.gitignore

{/* Gitignore Content */}
{gitignore}
{/* Gitignore Actions */}
{gitignoreError && (
{gitignoreError}
)}
)} {/* Search Section */}

Search Documentation