deepwiki-open / src /app /page.tsx
bhavinmatariya's picture
Initial complete codebase with Git LFS for binary files
8e0dd55
'use client';
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { FaWikipediaW, FaGithub, FaCoffee, FaTwitter } from 'react-icons/fa';
import ThemeToggle from '@/components/theme-toggle';
import Mermaid from '../components/Mermaid';
import ConfigurationModal from '@/components/ConfigurationModal';
import ProcessedProjects from '@/components/ProcessedProjects';
import { extractUrlPath, extractUrlDomain } from '@/utils/urlDecoder';
import { useProcessedProjects } from '@/hooks/useProcessedProjects';
import { useLanguage } from '@/contexts/LanguageContext';
// Define the demo mermaid charts outside the component
const DEMO_FLOW_CHART = `graph TD
A[Code Repository] --> B[DeepWiki]
B --> C[Architecture Diagrams]
B --> D[Component Relationships]
B --> E[Data Flow]
B --> F[Process Workflows]
style A fill:#f9d3a9,stroke:#d86c1f
style B fill:#d4a9f9,stroke:#6c1fd8
style C fill:#a9f9d3,stroke:#1fd86c
style D fill:#a9d3f9,stroke:#1f6cd8
style E fill:#f9a9d3,stroke:#d81f6c
style F fill:#d3f9a9,stroke:#6cd81f`;
const DEMO_SEQUENCE_CHART = `sequenceDiagram
participant User
participant DeepWiki
participant GitHub
User->>DeepWiki: Enter repository URL
DeepWiki->>GitHub: Request repository data
GitHub-->>DeepWiki: Return repository data
DeepWiki->>DeepWiki: Process and analyze code
DeepWiki-->>User: Display wiki with diagrams
%% Add a note to make text more visible
Note over User,GitHub: DeepWiki supports sequence diagrams for visualizing interactions`;
export default function Home() {
const router = useRouter();
const { language, setLanguage, messages, supportedLanguages } = useLanguage();
const { projects, isLoading: projectsLoading } = useProcessedProjects();
// Create a simple translation function
const t = (key: string, params: Record<string, string | number> = {}): string => {
// Split the key by dots to access nested properties
const keys = key.split('.');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let value: any = messages;
// Navigate through the nested properties
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
// Return the key if the translation is not found
return key;
}
}
// If the value is a string, replace parameters
if (typeof value === 'string') {
return Object.entries(params).reduce((acc: string, [paramKey, paramValue]) => {
return acc.replace(`{${paramKey}}`, String(paramValue));
}, value);
}
// Return the key if the value is not a string
return key;
};
const [repositoryInput, setRepositoryInput] = useState('https://github.com/AsyncFuncAI/deepwiki-open');
const REPO_CONFIG_CACHE_KEY = 'deepwikiRepoConfigCache';
const loadConfigFromCache = (repoUrl: string) => {
if (!repoUrl) return;
try {
const cachedConfigs = localStorage.getItem(REPO_CONFIG_CACHE_KEY);
if (cachedConfigs) {
const configs = JSON.parse(cachedConfigs);
const config = configs[repoUrl.trim()];
if (config) {
setSelectedLanguage(config.selectedLanguage || language);
setIsComprehensiveView(config.isComprehensiveView === undefined ? true : config.isComprehensiveView);
setProvider(config.provider || '');
setModel(config.model || '');
setIsCustomModel(config.isCustomModel || false);
setCustomModel(config.customModel || '');
setSelectedPlatform(config.selectedPlatform || 'github');
setExcludedDirs(config.excludedDirs || '');
setExcludedFiles(config.excludedFiles || '');
setIncludedDirs(config.includedDirs || '');
setIncludedFiles(config.includedFiles || '');
}
}
} catch (error) {
console.error('Error loading config from localStorage:', error);
}
};
const handleRepositoryInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newRepoUrl = e.target.value;
setRepositoryInput(newRepoUrl);
if (newRepoUrl.trim() === "") {
// Optionally reset fields if input is cleared
} else {
loadConfigFromCache(newRepoUrl);
}
};
useEffect(() => {
if (repositoryInput) {
loadConfigFromCache(repositoryInput);
}
}, []);
// Provider-based model selection state
const [provider, setProvider] = useState<string>('');
const [model, setModel] = useState<string>('');
const [isCustomModel, setIsCustomModel] = useState<boolean>(false);
const [customModel, setCustomModel] = useState<string>('');
// Wiki type state - default to comprehensive view
const [isComprehensiveView, setIsComprehensiveView] = useState<boolean>(true);
const [excludedDirs, setExcludedDirs] = useState('');
const [excludedFiles, setExcludedFiles] = useState('');
const [includedDirs, setIncludedDirs] = useState('');
const [includedFiles, setIncludedFiles] = useState('');
const [selectedPlatform, setSelectedPlatform] = useState<'github' | 'gitlab' | 'bitbucket'>('github');
const [accessToken, setAccessToken] = useState('');
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [selectedLanguage, setSelectedLanguage] = useState<string>(language);
// Authentication state
const [authRequired, setAuthRequired] = useState<boolean>(false);
const [authCode, setAuthCode] = useState<string>('');
const [isAuthLoading, setIsAuthLoading] = useState<boolean>(true);
// Sync the language context with the selectedLanguage state
useEffect(() => {
setLanguage(selectedLanguage);
}, [selectedLanguage, setLanguage]);
// Fetch authentication status on component mount
useEffect(() => {
const fetchAuthStatus = async () => {
try {
setIsAuthLoading(true);
const response = await fetch('/api/auth/status');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setAuthRequired(data.auth_required);
} catch (err) {
console.error("Failed to fetch auth status:", err);
// Assuming auth is required if fetch fails to avoid blocking UI for safety
setAuthRequired(true);
} finally {
setIsAuthLoading(false);
}
};
fetchAuthStatus();
}, []);
// Parse repository URL/input and extract owner and repo
const parseRepositoryInput = (input: string): {
owner: string,
repo: string,
type: string,
fullPath?: string,
localPath?: string
} | null => {
input = input.trim();
let owner = '', repo = '', type = 'github', fullPath;
let localPath: string | undefined;
// Handle Windows absolute paths (e.g., C:\path\to\folder)
const windowsPathRegex = /^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$/;
const customGitRegex = /^(?:https?:\/\/)?([^\/]+)\/(.+?)\/([^\/]+)(?:\.git)?\/?$/;
if (windowsPathRegex.test(input)) {
type = 'local';
localPath = input;
repo = input.split('\\').pop() || 'local-repo';
owner = 'local';
}
// Handle Unix/Linux absolute paths (e.g., /path/to/folder)
else if (input.startsWith('/')) {
type = 'local';
localPath = input;
repo = input.split('/').filter(Boolean).pop() || 'local-repo';
owner = 'local';
}
else if (customGitRegex.test(input)) {
// Detect repository type based on domain
const domain = extractUrlDomain(input);
if (domain?.includes('github.com')) {
type = 'github';
} else if (domain?.includes('gitlab.com') || domain?.includes('gitlab.')) {
type = 'gitlab';
} else if (domain?.includes('bitbucket.org') || domain?.includes('bitbucket.')) {
type = 'bitbucket';
} else {
type = 'web'; // fallback for other git hosting services
}
fullPath = extractUrlPath(input)?.replace(/\.git$/, '');
const parts = fullPath?.split('/') ?? [];
if (parts.length >= 2) {
repo = parts[parts.length - 1] || '';
owner = parts[parts.length - 2] || '';
}
}
// Unsupported URL formats
else {
console.error('Unsupported URL format:', input);
return null;
}
if (!owner || !repo) {
return null;
}
// Clean values
owner = owner.trim();
repo = repo.trim();
// Remove .git suffix if present
if (repo.endsWith('.git')) {
repo = repo.slice(0, -4);
}
return { owner, repo, type, fullPath, localPath };
};
// State for configuration modal
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Parse repository input to validate
const parsedRepo = parseRepositoryInput(repositoryInput);
if (!parsedRepo) {
setError('Invalid repository format. Use "owner/repo", GitHub/GitLab/BitBucket URL, or a local folder path like "/path/to/folder" or "C:\\path\\to\\folder".');
return;
}
// If valid, open the configuration modal
setError(null);
setIsConfigModalOpen(true);
};
const validateAuthCode = async () => {
try {
if(authRequired) {
if(!authCode) {
return false;
}
const response = await fetch('/api/auth/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({'code': authCode})
});
if (!response.ok) {
return false;
}
const data = await response.json();
return data.success || false;
}
} catch {
return false;
}
return true;
};
const handleGenerateWiki = async () => {
// Check authorization code
const validation = await validateAuthCode();
if(!validation) {
setError(`Failed to validate the authorization code`);
console.error(`Failed to validate the authorization code`);
setIsConfigModalOpen(false);
return;
}
// Prevent multiple submissions
if (isSubmitting) {
console.log('Form submission already in progress, ignoring duplicate click');
return;
}
try {
const currentRepoUrl = repositoryInput.trim();
if (currentRepoUrl) {
const existingConfigs = JSON.parse(localStorage.getItem(REPO_CONFIG_CACHE_KEY) || '{}');
const configToSave = {
selectedLanguage,
isComprehensiveView,
provider,
model,
isCustomModel,
customModel,
selectedPlatform,
excludedDirs,
excludedFiles,
includedDirs,
includedFiles,
};
existingConfigs[currentRepoUrl] = configToSave;
localStorage.setItem(REPO_CONFIG_CACHE_KEY, JSON.stringify(existingConfigs));
}
} catch (error) {
console.error('Error saving config to localStorage:', error);
}
setIsSubmitting(true);
// Parse repository input
const parsedRepo = parseRepositoryInput(repositoryInput);
if (!parsedRepo) {
setError('Invalid repository format. Use "owner/repo", GitHub/GitLab/BitBucket URL, or a local folder path like "/path/to/folder" or "C:\\path\\to\\folder".');
setIsSubmitting(false);
return;
}
const { owner, repo, type, localPath } = parsedRepo;
// Store tokens in query params if they exist
const params = new URLSearchParams();
if (accessToken) {
params.append('token', accessToken);
}
// Always include the type parameter
params.append('type', (type == 'local' ? type : selectedPlatform) || 'github');
// Add local path if it exists
if (localPath) {
params.append('local_path', encodeURIComponent(localPath));
} else {
params.append('repo_url', encodeURIComponent(repositoryInput));
}
// Add model parameters
params.append('provider', provider);
params.append('model', model);
if (isCustomModel && customModel) {
params.append('custom_model', customModel);
}
// Add file filters configuration
if (excludedDirs) {
params.append('excluded_dirs', excludedDirs);
}
if (excludedFiles) {
params.append('excluded_files', excludedFiles);
}
if (includedDirs) {
params.append('included_dirs', includedDirs);
}
if (includedFiles) {
params.append('included_files', includedFiles);
}
// Add language parameter
params.append('language', selectedLanguage);
// Add comprehensive parameter
params.append('comprehensive', isComprehensiveView.toString());
const queryString = params.toString() ? `?${params.toString()}` : '';
// Navigate to the dynamic route
router.push(`/${owner}/${repo}${queryString}`);
// The isSubmitting state will be reset when the component unmounts during navigation
};
return (
<div className="h-screen paper-texture p-4 md:p-8 flex flex-col">
<header className="max-w-6xl mx-auto mb-6 h-fit w-full">
<div
className="flex flex-col md:flex-row md:items-center md:justify-between gap-4 bg-[var(--card-bg)] rounded-lg shadow-custom border border-[var(--border-color)] p-4">
<div className="flex items-center">
<div className="bg-[var(--accent-primary)] p-2 rounded-lg mr-3">
<FaWikipediaW className="text-2xl text-white" />
</div>
<div className="mr-6">
<h1 className="text-xl md:text-2xl font-bold text-[var(--accent-primary)]">{t('common.appName')}</h1>
<div className="flex flex-wrap items-baseline gap-x-2 md:gap-x-3 mt-0.5">
<p className="text-xs text-[var(--muted)] whitespace-nowrap">{t('common.tagline')}</p>
<div className="hidden md:inline-block">
<Link href="/wiki/projects"
className="text-xs font-medium text-[var(--accent-primary)] hover:text-[var(--highlight)] hover:underline whitespace-nowrap">
{t('nav.wikiProjects')}
</Link>
</div>
</div>
</div>
</div>
<form onSubmit={handleFormSubmit} className="flex flex-col gap-3 w-full max-w-3xl">
{/* Repository URL input and submit button */}
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative flex-1">
<input
type="text"
value={repositoryInput}
onChange={handleRepositoryInputChange}
placeholder={t('form.repoPlaceholder') || "owner/repo, GitHub/GitLab/BitBucket URL, or local folder path"}
className="input-japanese block w-full pl-10 pr-3 py-2.5 border-[var(--border-color)] rounded-lg bg-transparent text-[var(--foreground)] focus:outline-none focus:border-[var(--accent-primary)]"
/>
{error && (
<div className="text-[var(--highlight)] text-xs mt-1">
{error}
</div>
)}
</div>
<button
type="submit"
className="btn-japanese px-6 py-2.5 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isSubmitting}
>
{isSubmitting ? t('common.processing') : t('common.generateWiki')}
</button>
</div>
</form>
{/* Configuration Modal */}
<ConfigurationModal
isOpen={isConfigModalOpen}
onClose={() => setIsConfigModalOpen(false)}
repositoryInput={repositoryInput}
selectedLanguage={selectedLanguage}
setSelectedLanguage={setSelectedLanguage}
supportedLanguages={supportedLanguages}
isComprehensiveView={isComprehensiveView}
setIsComprehensiveView={setIsComprehensiveView}
provider={provider}
setProvider={setProvider}
model={model}
setModel={setModel}
isCustomModel={isCustomModel}
setIsCustomModel={setIsCustomModel}
customModel={customModel}
setCustomModel={setCustomModel}
selectedPlatform={selectedPlatform}
setSelectedPlatform={setSelectedPlatform}
accessToken={accessToken}
setAccessToken={setAccessToken}
excludedDirs={excludedDirs}
setExcludedDirs={setExcludedDirs}
excludedFiles={excludedFiles}
setExcludedFiles={setExcludedFiles}
includedDirs={includedDirs}
setIncludedDirs={setIncludedDirs}
includedFiles={includedFiles}
setIncludedFiles={setIncludedFiles}
onSubmit={handleGenerateWiki}
isSubmitting={isSubmitting}
authRequired={authRequired}
authCode={authCode}
setAuthCode={setAuthCode}
isAuthLoading={isAuthLoading}
/>
</div>
</header>
<main className="flex-1 max-w-6xl mx-auto w-full overflow-y-auto">
<div
className="min-h-full flex flex-col items-center p-8 pt-10 bg-[var(--card-bg)] rounded-lg shadow-custom card-japanese">
{/* Conditionally show processed projects or welcome content */}
{!projectsLoading && projects.length > 0 ? (
<div className="w-full">
{/* Header section for existing projects */}
<div className="flex flex-col items-center w-full max-w-2xl mb-8 mx-auto">
<div className="flex flex-col sm:flex-row items-center mb-6 gap-4">
<div className="relative">
<div className="absolute -inset-1 bg-[var(--accent-primary)]/20 rounded-full blur-md"></div>
<FaWikipediaW className="text-5xl text-[var(--accent-primary)] relative z-10" />
</div>
<div className="text-center sm:text-left">
<h2 className="text-2xl font-bold text-[var(--foreground)] font-serif mb-1">{t('projects.existingProjects')}</h2>
<p className="text-[var(--accent-primary)] text-sm max-w-md">{t('projects.browseExisting')}</p>
</div>
</div>
</div>
{/* Show processed projects */}
<ProcessedProjects
showHeader={false}
maxItems={6}
messages={messages}
className="w-full"
/>
</div>
) : (
<>
{/* Header section */}
<div className="flex flex-col items-center w-full max-w-2xl mb-8">
<div className="flex flex-col sm:flex-row items-center mb-6 gap-4">
<div className="relative">
<div className="absolute -inset-1 bg-[var(--accent-primary)]/20 rounded-full blur-md"></div>
<FaWikipediaW className="text-5xl text-[var(--accent-primary)] relative z-10" />
</div>
<div className="text-center sm:text-left">
<h2 className="text-2xl font-bold text-[var(--foreground)] font-serif mb-1">{t('home.welcome')}</h2>
<p className="text-[var(--accent-primary)] text-sm max-w-md">{t('home.welcomeTagline')}</p>
</div>
</div>
<p className="text-[var(--foreground)] text-center mb-8 text-lg leading-relaxed">
{t('home.description')}
</p>
</div>
{/* Quick Start section - redesigned for better spacing */}
<div
className="w-full max-w-2xl mb-10 bg-[var(--accent-primary)]/5 border border-[var(--accent-primary)]/20 rounded-lg p-5">
<h3 className="text-sm font-semibold text-[var(--accent-primary)] mb-3 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{t('home.quickStart')}
</h3>
<p className="text-sm text-[var(--foreground)] mb-3">{t('home.enterRepoUrl')}</p>
<div className="grid grid-cols-1 gap-3 text-xs text-[var(--muted)]">
<div
className="bg-[var(--background)]/70 p-3 rounded border border-[var(--border-color)] font-mono overflow-x-hidden whitespace-nowrap"
>https://github.com/AsyncFuncAI/deepwiki-open
</div>
<div
className="bg-[var(--background)]/70 p-3 rounded border border-[var(--border-color)] font-mono overflow-x-hidden whitespace-nowrap"
>https://gitlab.com/gitlab-org/gitlab
</div>
<div
className="bg-[var(--background)]/70 p-3 rounded border border-[var(--border-color)] font-mono overflow-x-hidden whitespace-nowrap"
>AsyncFuncAI/deepwiki-open
</div>
<div
className="bg-[var(--background)]/70 p-3 rounded border border-[var(--border-color)] font-mono overflow-x-hidden whitespace-nowrap"
>https://bitbucket.org/atlassian/atlaskit
</div>
</div>
</div>
{/* Visualization section - improved for better visibility */}
<div
className="w-full max-w-2xl mb-8 bg-[var(--background)]/70 rounded-lg p-6 border border-[var(--border-color)]">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-2 mb-4">
<svg xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 text-[var(--accent-primary)] flex-shrink-0 mt-0.5 sm:mt-0" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<h3 className="text-base font-semibold text-[var(--foreground)] font-serif">{t('home.advancedVisualization')}</h3>
</div>
<p className="text-sm text-[var(--foreground)] mb-5 leading-relaxed">
{t('home.diagramDescription')}
</p>
{/* Diagrams with improved layout */}
<div className="grid grid-cols-1 gap-6">
<div className="bg-[var(--card-bg)] p-4 rounded-lg border border-[var(--border-color)] shadow-custom">
<h4 className="text-sm font-medium text-[var(--foreground)] mb-3 font-serif">{t('home.flowDiagram')}</h4>
<Mermaid chart={DEMO_FLOW_CHART} />
</div>
<div className="bg-[var(--card-bg)] p-4 rounded-lg border border-[var(--border-color)] shadow-custom">
<h4 className="text-sm font-medium text-[var(--foreground)] mb-3 font-serif">{t('home.sequenceDiagram')}</h4>
<Mermaid chart={DEMO_SEQUENCE_CHART} />
</div>
</div>
</div>
</>
)}
</div>
</main>
<footer className="max-w-6xl mx-auto mt-8 flex flex-col gap-4 w-full">
<div
className="flex flex-col sm:flex-row justify-between items-center gap-4 bg-[var(--card-bg)] rounded-lg p-4 border border-[var(--border-color)] shadow-custom">
<p className="text-[var(--muted)] text-sm font-serif">{t('footer.copyright')}</p>
<div className="flex items-center gap-6">
<div className="flex items-center space-x-5">
<a href="https://github.com/AsyncFuncAI/deepwiki-open" target="_blank" rel="noopener noreferrer"
className="text-[var(--muted)] hover:text-[var(--accent-primary)] transition-colors">
<FaGithub className="text-xl" />
</a>
<a href="https://buymeacoffee.com/sheing" target="_blank" rel="noopener noreferrer"
className="text-[var(--muted)] hover:text-[var(--accent-primary)] transition-colors">
<FaCoffee className="text-xl" />
</a>
<a href="https://x.com/sashimikun_void" target="_blank" rel="noopener noreferrer"
className="text-[var(--muted)] hover:text-[var(--accent-primary)] transition-colors">
<FaTwitter className="text-xl" />
</a>
</div>
<ThemeToggle />
</div>
</div>
</footer>
</div>
);
}