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 && (
)}
{/* 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 Content */}
{/* README Actions */}
{readmeError && (
)}
)}
{/* .gitignore Section */}
{gitignore && (
{/* Gitignore Header */}
{/* Gitignore Content */}
{gitignore}
{/* Gitignore Actions */}
{gitignoreError && (
)}
)}
{/* Search Section */}
Search Documentation
{searchError && (
)}
{/* Search Results Section */}
{searchResults && searchResults.length > 0 && (
Search Results
{searchResults.length} result{searchResults.length !== 1 ? 's' : ''}
{searchResults.map((result, index) => (
{result.filename}
Relevance: {(1 / (1 + result.distance)).toFixed(2)}
))}
)}
{/* Empty State */}
{!docs && !searchResults && !loading && (
No documentation yet
Enter a GitHub repository URL above to get started
)}
{/* Footer */}
);
}