diff --git a/package-lock.json b/package-lock.json index dbfbf1c4..61eb8aae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "deepwiki-open", "version": "0.1.0", "dependencies": { + "clsx": "^2.1.1", "mermaid": "^11.4.1", "next": "15.3.1", "next-intl": "^4.1.0", @@ -19,7 +20,8 @@ "react-syntax-highlighter": "^15.6.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", - "svg-pan-zoom": "^3.6.2" + "svg-pan-zoom": "^3.6.2", + "tailwind-merge": "^3.2.0" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -2678,6 +2680,14 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -8458,6 +8468,15 @@ "resolved": "https://registry.npmjs.org/svg-pan-zoom/-/svg-pan-zoom-3.6.2.tgz", "integrity": "sha512-JwnvRWfVKw/Xzfe6jriFyfey/lWJLq4bUh2jwoR5ChWQuQoOH8FEh1l/bEp46iHHKHEJWIyFJETbazraxNWECg==" }, + "node_modules/tailwind-merge": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz", + "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", diff --git a/package.json b/package.json index 286b9df3..746b4aca 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "clsx": "^2.1.1", "mermaid": "^11.4.1", "next": "15.3.1", "next-intl": "^4.1.0", @@ -20,7 +21,8 @@ "react-syntax-highlighter": "^15.6.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", - "svg-pan-zoom": "^3.6.2" + "svg-pan-zoom": "^3.6.2", + "tailwind-merge": "^3.2.0" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/src/app/[owner]/[repo]/page.tsx b/src/app/[owner]/[repo]/page.tsx index fc3e16f5..5f5d4733 100644 --- a/src/app/[owner]/[repo]/page.tsx +++ b/src/app/[owner]/[repo]/page.tsx @@ -7,26 +7,15 @@ import { FaExclamationTriangle, FaBookOpen, FaGithub, FaGitlab, FaBitbucket, FaD import Link from 'next/link'; import ThemeToggle from '@/components/theme-toggle'; import Markdown from '@/components/Markdown'; -import Ask from '@/components/Ask'; +import Ask from '@/components/wiki/Ask'; import ModelSelectionModal from '@/components/ModelSelectionModal'; import { useLanguage } from '@/contexts/LanguageContext'; - -// Wiki Interfaces -interface WikiPage { - id: string; - title: string; - content: string; - filePaths: string[]; - importance: 'high' | 'medium' | 'low'; - relatedPages: string[]; -} - -interface WikiStructure { - id: string; - title: string; - description: string; - pages: WikiPage[]; -} +import Footer from '@/components/common/Footer'; +import { RepoInfo, WikiPage, WikiStructure } from '@/app/types/types'; +import { cn, getRepoUrl } from '@/utils/utils'; +import AskSection from '@/components/wiki/AskSection'; +import WikiDocLoading from '@/components/wiki/WikiDocLoading'; +import { getConfig } from '@/config'; // Add CSS styles for wiki with Japanese aesthetic const wikiStyles = ` @@ -71,17 +60,7 @@ const wikiStyles = ` } `; -// Helper functions for token handling and API requests -const getRepoUrl = (owner: string, repo: string, repoType: string, localPath?: string): string => { - if (repoType === 'local' && localPath) { - return localPath; - } - return repoType === 'github' - ? `https://github.com/${owner}/${repo}` - : repoType === 'gitlab' - ? `https://gitlab.com/${owner}/${repo}` - : `https://bitbucket.org/${owner}/${repo}`; -}; +const config = getConfig('wikiPage'); // Helper function to generate cache key for localStorage const getCacheKey = (owner: string, repo: string, repoType: string, language: string): string => { @@ -194,7 +173,7 @@ export default function RepoWikiPage() { const { messages } = useLanguage(); // Initialize repo info - const repoInfo = useMemo(() => ({ + const repoInfo: RepoInfo = useMemo(() => ({ owner, repo, type: repoType, @@ -226,6 +205,8 @@ export default function RepoWikiPage() { const excludedFiles = searchParams.get('excluded_files') || ''; const [modelExcludedDirs, setModelExcludedDirs] = useState(excludedDirs); const [modelExcludedFiles, setModelExcludedFiles] = useState(excludedFiles); + const [isModelSelectionModalOpen, setIsModelSelectionModalOpen] = useState(false); + // Using useRef for activeContentRequests to maintain a single instance across renders // This map tracks which pages are currently being processed to prevent duplicate requests // Note: In a multi-threaded environment, additional synchronization would be needed, @@ -238,11 +219,6 @@ export default function RepoWikiPage() { // Create a flag to ensure the effect only runs once const effectRan = React.useRef(false); - // State for Ask section visibility - const [isAskSectionVisible, setIsAskSectionVisible] = useState(true); - - // Memoize repo info to avoid triggering updates in callbacks - // Add useEffect to handle scroll reset useEffect(() => { // Scroll to top when currentPageId changes @@ -1291,10 +1267,27 @@ IMPORTANT: } }; - const [isModelSelectionModalOpen, setIsModelSelectionModalOpen] = useState(false); + const AskSectionComponent = () => { + return ( + + ) + } return ( -
+
@@ -1307,69 +1300,20 @@ IMPORTANT:
-
- {isLoading ? ( -
-
-
-
-
-
-
-
-
-

- {loadingMessage || messages.common?.loading || 'Loading...'} - {isExporting && (messages.loading?.preparingDownload || ' Please wait while we prepare your download...')} -

+ {config.askSection.position === 'top' && ( + + )} - {/* Progress bar for page generation */} - {wikiStructure && ( -
-
-
-
-

- {language === 'ja' - ? `${wikiStructure.pages.length}ページ中${wikiStructure.pages.length - pagesInProgress.size}ページ完了` - : messages.repoPage?.pagesCompleted - ? messages.repoPage.pagesCompleted - .replace('{completed}', (wikiStructure.pages.length - pagesInProgress.size).toString()) - .replace('{total}', wikiStructure.pages.length.toString()) - : `${wikiStructure.pages.length - pagesInProgress.size} of ${wikiStructure.pages.length} pages completed`} -

- - {/* Show list of in-progress pages */} - {pagesInProgress.size > 0 && ( -
-

- {messages.repoPage?.currentlyProcessing || 'Currently processing:'} -

-
    - {Array.from(pagesInProgress).slice(0, 3).map(pageId => { - const page = wikiStructure.pages.find(p => p.id === pageId); - return page ?
  • {page.title}
  • : null; - })} - {pagesInProgress.size > 3 && ( -
  • - {language === 'ja' - ? `...他に${pagesInProgress.size - 3}ページ` - : messages.repoPage?.andMorePages - ? messages.repoPage.andMorePages.replace('{count}', (pagesInProgress.size - 3).toString()) - : `...and ${pagesInProgress.size - 3} more`} -
  • - )} -
-
- )} -
- )} -
+
+ {isLoading ? ( + ) : error ? (
@@ -1392,8 +1336,12 @@ IMPORTANT:
) : wikiStructure ? (
+ {/* Wiki Navigation */} -
+

{wikiStructure.title}

{wikiStructure.description}

@@ -1443,28 +1391,32 @@ IMPORTANT:
{/* Export buttons */} - {Object.keys(generatedPages).length > 0 && ( + {Object.keys(generatedPages).length > 0 && (config.exportWiki.markdown || config.exportWiki.json) && (

{messages.repoPage?.exportWiki || 'Export Wiki'}

-
- - +
+ {config.exportWiki.markdown && ( + + )} + {config.exportWiki.json && ( + + )}
{exportError && (
@@ -1504,7 +1456,10 @@ IMPORTANT:
{/* Wiki Content */} -
+
{currentPageId && generatedPages[currentPageId] ? (

@@ -1568,69 +1523,15 @@ IMPORTANT:

) : null} -
- -
- {/* Only show Ask component when wiki is successfully generated */} - {wikiStructure && Object.keys(generatedPages).length > 0 && !isLoading && ( -
- - {isAskSectionVisible && ( - - )} -
+ {config.askSection.position === 'embed' && ( + )} -
-

- {messages.footer?.copyright || 'DeepWiki - Generate Wiki from GitHub/Gitlab/Bitbucket repositories'} -

- -
-
+
- setIsModelSelectionModalOpen(false)} - provider={selectedProviderState} - setProvider={setSelectedProviderState} - model={selectedModelState} - setModel={setSelectedModelState} - isCustomModel={isCustomSelectedModelState} - setIsCustomModel={setIsCustomSelectedModelState} - customModel={customSelectedModelState} - setCustomModel={setCustomSelectedModelState} - showFileFilters={true} - excludedDirs={modelExcludedDirs} - setExcludedDirs={setModelExcludedDirs} - excludedFiles={modelExcludedFiles} - setExcludedFiles={setModelExcludedFiles} - onApply={confirmRefresh} - /> + {config.askSection.position === 'bottom' && ( + + )} +
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 158623f0..d4512e35 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,12 +3,18 @@ import React, { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; -import { FaWikipediaW, FaGithub, FaGitlab, FaBitbucket, FaCoffee, FaTwitter} from 'react-icons/fa'; -import ThemeToggle from '@/components/theme-toggle'; +import { FaWikipediaW, FaCog } from 'react-icons/fa'; import Mermaid from '../components/Mermaid'; -import UserSelector from '@/components/UserSelector'; - import { useLanguage } from '@/contexts/LanguageContext'; +import { cn, t } from '@/utils/utils'; +import Footer from '@/components/common/Footer'; +import AdvancedOptions from '@/components/landing/AdvancedOptions'; +import AccessTokens from '@/components/landing/AccessTokens'; +import { getConfig } from '@/config'; +import AdvancedOptionsModal from '@/components/landing/AdvancedOptionsModal'; + + +const config = getConfig('landingPage'); // Define the demo mermaid charts outside the component const DEMO_FLOW_CHART = `graph TD @@ -43,34 +49,6 @@ export default function Home() { const router = useRouter(); const {language, setLanguage, messages} = useLanguage(); - // Create a simple translation function - const t = (key: string, params: Record = {}): 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 [showTokenInputs, setShowTokenInputs] = useState(false); @@ -87,6 +65,7 @@ export default function Home() { const [error, setError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [selectedLanguage, setSelectedLanguage] = useState(language); + const [isAdvancedOptionsModalOpen, setIsAdvancedOptionsModalOpen] = useState(false); // Sync the language context with the selectedLanguage state useEffect(() => { @@ -237,6 +216,42 @@ export default function Home() { // The isSubmitting state will be reset when the component unmounts during navigation }; + const AdvancedOptionsComponent = () => { + return ( + <> + {/* Advanced options section with improved layout */} + + + {/* Access tokens button */} + + + ) + } + return (
@@ -247,28 +262,28 @@ export default function Home() {
-

{t('common.appName')}

+

{t('common.appName', messages)}

-

{t('common.tagline')}

+

{t('common.tagline', messages)}

- {t('nav.wikiProjects')} + {t('nav.wikiProjects', messages)}
-
+ {/* Repository URL input and submit button */} -
-
+
+
setRepositoryInput(e.target.value)} - placeholder={t('form.repoPlaceholder') || "owner/repo, GitHub/GitLab/BitBucket URL, or local folder path"} + placeholder={t('form.repoPlaceholder', messages) || "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 && ( @@ -282,163 +297,27 @@ export default function Home() { 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')} + {isSubmitting ? t('common.processing', messages) : t('common.generateWiki', messages)}
- {/* Advanced options section with improved layout */} -
- {/* Language selection */} -
- - -
- - {/* Model options */} -
- -
-
- - {/* Access tokens button */} -
- - {showTokenInputs && ( - <> -
setShowTokenInputs(false)}/> -
-
-
-

- - - - {t('form.accessToken')} -

- -
- -
- -
- - - -
-
- -
- - setAccessToken(e.target.value)} - placeholder={t('form.tokenPlaceholder', {platform: selectedPlatform})} - className="input-japanese block w-full px-3 py-2 rounded-md bg-transparent text-[var(--foreground)] focus:outline-none focus:border-[var(--accent-primary)] text-sm" - /> -
- - - - {t('form.tokenSecurityNote')} -
-
-
-
- - )} -
+ + {t('form.advancedOptions', messages)} + + ) + }
@@ -455,13 +334,13 @@ export default function Home() {
-

{t('home.welcome')}

-

{t('home.welcomeTagline')}

+

{t('home.welcome', messages)}

+

{t('home.welcomeTagline', messages)}

- {t('home.description')} + {t('home.description', messages)}

@@ -474,9 +353,9 @@ export default function Home() { - {t('home.quickStart')} + {t('home.quickStart', messages)} -

{t('home.enterRepoUrl')}

+

{t('home.enterRepoUrl', messages)}

-

{t('home.advancedVisualization')}

+

{t('home.advancedVisualization', messages)}

- {t('home.diagramDescription')} + {t('home.diagramDescription', messages)}

{/* Diagrams with improved layout */}
-

{t('home.flowDiagram')}

+

{t('home.flowDiagram', messages)}

-

{t('home.sequenceDiagram')}

+

{t('home.sequenceDiagram', messages)}

@@ -529,30 +408,18 @@ export default function Home() {
- +