From da9c88131854537a81e0c3812d75351b093fdc88 Mon Sep 17 00:00:00 2001 From: JNLei <16848622+JNLei@users.noreply.github.com> Date: Sat, 10 May 2025 21:23:29 -0400 Subject: [PATCH 1/2] feat: make frontend modular and add config --- package-lock.json | 21 +- package.json | 4 +- src/app/[owner]/[repo]/page.tsx | 232 ++++------- src/app/page.tsx | 378 ++++-------------- src/app/types/types.ts | 23 ++ src/components/Markdown.tsx | 33 +- src/components/common/Footer.tsx | 41 ++ src/components/landing/AccessTokens.tsx | 168 ++++++++ src/components/landing/AdvancedOptions.tsx | 151 +++++++ .../landing/AdvancedOptionsModal.tsx | 47 +++ src/components/{ => wiki}/Ask.tsx | 15 +- src/components/wiki/AskSection.tsx | 81 ++++ src/components/wiki/WikiDocLoading.tsx | 82 ++++ src/config.ts | 83 ++++ src/messages/en.json | 7 +- src/messages/es.json | 7 +- src/messages/ja.json | 7 +- src/messages/kr.json | 7 +- src/messages/vi.json | 7 +- src/messages/zh.json | 7 +- src/utils/utils.ts | 47 +++ 21 files changed, 964 insertions(+), 484 deletions(-) create mode 100644 src/app/types/types.ts create mode 100644 src/components/common/Footer.tsx create mode 100644 src/components/landing/AccessTokens.tsx create mode 100644 src/components/landing/AdvancedOptions.tsx create mode 100644 src/components/landing/AdvancedOptionsModal.tsx rename src/components/{ => wiki}/Ask.tsx (98%) create mode 100644 src/components/wiki/AskSection.tsx create mode 100644 src/components/wiki/WikiDocLoading.tsx create mode 100644 src/config.ts create mode 100644 src/utils/utils.ts 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 3b4f500c..63d4916c 100644 --- a/src/app/[owner]/[repo]/page.tsx +++ b/src/app/[owner]/[repo]/page.tsx @@ -7,25 +7,14 @@ import { FaExclamationTriangle, FaBookOpen, FaWikipediaW, FaGithub, FaGitlab, Fa 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 { 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 = ` @@ -70,17 +59,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 => { @@ -169,7 +148,7 @@ export default function RepoWikiPage() { const { messages } = useLanguage(); // Initialize repo info - const repoInfo = useMemo(() => ({ + const repoInfo: RepoInfo = useMemo(() => ({ owner, repo, type: repoType, @@ -202,11 +181,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 @@ -1238,8 +1212,25 @@ IMPORTANT: } }; + const AskSectionComponent = () => { + return ( + + ) + } + return ( -
+
@@ -1259,69 +1250,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 ? (
@@ -1344,8 +1286,12 @@ IMPORTANT:
) : wikiStructure ? (
+ {/* Wiki Navigation */} -
+

{wikiStructure.title}

{wikiStructure.description}

@@ -1395,28 +1341,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 && (
@@ -1456,7 +1406,10 @@ IMPORTANT:
{/* Wiki Content */} -
+
{currentPageId && generatedPages[currentPageId] ? (

@@ -1520,44 +1473,15 @@ IMPORTANT:

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

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

- -
-
+ {config.askSection.position === 'bottom' && ( + + )} +
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index d7ffd660..7cf203d3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,7 +9,14 @@ import Mermaid from '../components/Mermaid'; import ModelConfigModal from '@/components/ModelConfigModal'; 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'); const SERVER_BASE_URL = process.env.NEXT_PUBLIC_SERVER_BASE_URL || 'http://localhost:8001'; // Define the demo mermaid charts outside the component const DEMO_FLOW_CHART = `graph TD @@ -40,44 +47,16 @@ const DEMO_SEQUENCE_CHART = `sequenceDiagram %% Add a note to make text more visible Note over User,GitHub: DeepWiki supports sequence diagrams for visualizing interactions`; -// 定义模型的接口 +// Define the model interface interface GeneratorModel { display_name: string; - // 如果模型对象中还有其他字段,可以在这里添加 + // If there are other fields in the model object, add them here } 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); const [generatorModelName, setGeneratorModelName] = useState('google'); @@ -88,13 +67,14 @@ export default function Home() { const [isSubmitting, setIsSubmitting] = useState(false); const [selectedLanguage, setSelectedLanguage] = useState(language); const [isModelConfigModalOpen, setIsModelConfigModalOpen] = useState(false); + const [isAdvancedOptionsModalOpen, setIsAdvancedOptionsModalOpen] = useState(false); // Sync the language context with the selectedLanguage state useEffect(() => { setLanguage(selectedLanguage); }, [selectedLanguage, setLanguage]); - // 获取可用的生成器模型列表 + // Fetch available generators useEffect(() => { const fetchGenerators = async () => { try { @@ -109,7 +89,7 @@ export default function Home() { const data = await response.json(); console.log('Response:', response.json); setAvailableModels(data); - // 如果模型列表不为空且当前选择的模型不在列表中,则选择默认模型 + // If the model list is not empty and the current selected model is not in the list, select the default model if (Object.keys(data).length > 0 && !data[generatorModelName]) { console.log('Selected model in fetch:', Object.keys(data)[0]); setGeneratorModelName(Object.keys(data)[0]); @@ -250,36 +230,64 @@ 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 ( -
-
+
+
-

{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 && ( @@ -293,232 +301,34 @@ 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 with improved UI and explanations */} -
-
- -
- - -
-
-
- -
-
- - - -
-
{messages.modelConfig?.availableModelTypes || "Available Model Types:"}
-
    -
  • Google: {messages.modelConfig?.googleRequiresShort || "Requires GOOGLE_API_KEY"}
  • -
  • Ollama: {messages.modelConfig?.ollamaRequiresShort || "Local models, requires Ollama running"}
  • -
  • OpenRouter: {messages.modelConfig?.openrouterRequiresShort || "Requires OPENROUTER_API_KEY"}
  • -
  • OpenAI: {messages.modelConfig?.openaiRequiresShort || "Requires OPENAI_API_KEY"}
  • -
-
-
-
-
- {/* Current model type indicator */} -
-
- {generatorModelName === 'google' ? 'Google API' : - generatorModelName === 'ollama' ? 'Local Ollama' : - generatorModelName === 'openrouter' ? 'OpenRouter API' : - 'API'} -
- {generatorModelName === 'ollama' && ( -
- ({messages.modelConfig?.requiresOllamaLocal || "Requires Ollama running locally"}) -
- )} -
- -
- - - - - {messages.modelConfig?.configuredIn || "Models are configured in"} api/config/generators.json. - {messages.modelConfig?.clickHere || "Click"} {messages.modelConfig?.toLearnHow || "to learn how to customize models."} - -
-
-
- - {/* 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)} + + ) + }
-
-
+
+
{/* Header section */}
@@ -527,13 +337,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)}

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

{t('home.enterRepoUrl')}

+

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

https://github.com/AsyncFuncAI/deepwiki-open
@@ -564,21 +374,21 @@ export default function Home() { -

{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)}

@@ -586,35 +396,23 @@ export default function Home() {
- +
{/* Model Configuration Modal */} setIsModelConfigModalOpen(false)} /> + + {/* Advanced Options Modal */} + {config.advancedOptions.position === 'modal' && ( + setIsAdvancedOptionsModalOpen(false)} + content={} + messages={messages} + /> + )}
); } diff --git a/src/app/types/types.ts b/src/app/types/types.ts new file mode 100644 index 00000000..386cd551 --- /dev/null +++ b/src/app/types/types.ts @@ -0,0 +1,23 @@ +// Wiki Interfaces +export interface WikiPage { + id: string; + title: string; + content: string; + filePaths: string[]; + importance: 'high' | 'medium' | 'low'; + relatedPages: string[]; +} + +export interface WikiStructure { + id: string; + title: string; + description: string; + pages: WikiPage[]; +} + +export type RepoInfo = { + owner: string; + repo: string; + type: string; + localPath: string | undefined; +} diff --git a/src/components/Markdown.tsx b/src/components/Markdown.tsx index 4f07d626..7ca7cd41 100644 --- a/src/components/Markdown.tsx +++ b/src/components/Markdown.tsx @@ -5,6 +5,9 @@ import rehypeRaw from 'rehype-raw'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { tomorrow } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import Mermaid from './Mermaid'; +import { getConfig } from '@/config'; + +const config = getConfig('wikiPage.wikiContent.sizes'); interface MarkdownProps { content: string; @@ -14,10 +17,10 @@ const Markdown: React.FC = ({ content }) => { // Define markdown components const MarkdownComponents: React.ComponentProps['components'] = { p({ children, ...props }: { children?: React.ReactNode }) { - return

{children}

; + return

{children}

; }, h1({ children, ...props }: { children?: React.ReactNode }) { - return

{children}

; + return

{children}

; }, h2({ children, ...props }: { children?: React.ReactNode }) { // Special styling for ReAct headings @@ -26,7 +29,7 @@ const Markdown: React.FC = ({ content }) => { if (text.includes('Thought') || text.includes('Action') || text.includes('Observation') || text.includes('Answer')) { return (

= ({ content }) => { ); } } - return

{children}

; + return

{children}

; }, h3({ children, ...props }: { children?: React.ReactNode }) { - return

{children}

; + return

{children}

; }, h4({ children, ...props }: { children?: React.ReactNode }) { - return

{children}

; + return

{children}

; }, ul({ children, ...props }: { children?: React.ReactNode }) { - return
    {children}
; + return
    {children}
; }, ol({ children, ...props }: { children?: React.ReactNode }) { - return
    {children}
; + return
    {children}
; }, li({ children, ...props }: { children?: React.ReactNode }) { - return
  • {children}
  • ; + return
  • {children}
  • ; }, a({ children, href, ...props }: { children?: React.ReactNode; href?: string }) { return ( = ({ content }) => { blockquote({ children, ...props }: { children?: React.ReactNode }) { return (
    {children} @@ -83,7 +86,7 @@ const Markdown: React.FC = ({ content }) => { table({ children, ...props }: { children?: React.ReactNode }) { return (
    - +
    {children}
    @@ -138,7 +141,7 @@ const Markdown: React.FC = ({ content }) => { // Handle code blocks if (!inline && match) { return ( -
    +
    {match[1]} + {showTokenInputs && ( + <> +
    setShowTokenInputs(false)} /> +
    +
    + +
    +
    + + )} +
    + ); +} + +function PlatformAccessToken({ + showTokenInputs, + setShowTokenInputs, + selectedPlatform, + setSelectedPlatform, + accessToken, + setAccessToken, + messages, +}: PlatformAccessTokenProps) { + return ( + <> +
    +

    + + {t('form.accessToken', messages)} +

    + {config.position === 'embed' && ( + + )} +
    + +
    + +
    + + + +
    +
    + +
    + + setAccessToken(e.target.value)} + placeholder={t('form.tokenPlaceholder', messages, { platform: selectedPlatform.charAt(0).toUpperCase() + selectedPlatform.slice(1) })} + 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', messages)} +
    +
    + + ); +} diff --git a/src/components/landing/AdvancedOptions.tsx b/src/components/landing/AdvancedOptions.tsx new file mode 100644 index 00000000..506ca852 --- /dev/null +++ b/src/components/landing/AdvancedOptions.tsx @@ -0,0 +1,151 @@ +import { cn, t } from "@/utils/utils"; +import { Messages } from "next-intl"; +import { FaCog } from "react-icons/fa"; +import { getConfig } from "@/config"; +import { IoLanguageOutline } from "react-icons/io5"; +import { MdSelectAll } from "react-icons/md"; + +const config = getConfig('landingPage.advancedOptions'); + +export default function AdvancedOptions({ + selectedLanguage, + setSelectedLanguage, + generatorModelName, + setGeneratorModelName, + availableModels, + setIsModelConfigModalOpen, + messages, +}: { + selectedLanguage: string; + setSelectedLanguage: (language: string) => void; + generatorModelName: string; + setGeneratorModelName: (modelName: string) => void; + availableModels: Record; + setIsModelConfigModalOpen: (open: boolean) => void; + messages: Messages; +}) { + return ( +
    + {/* Language selection */} +
    + + +
    + + {/* Model options with improved UI and explanations */} +
    +
    + +
    + + +
    +
    +
    + +
    +
    + + + +
    +
    {messages.modelConfig?.availableModelTypes || "Available Model Types:"}
    +
      +
    • Google: {messages.modelConfig?.googleRequiresShort || "Requires GOOGLE_API_KEY"}
    • +
    • Ollama: {messages.modelConfig?.ollamaRequiresShort || "Local models, requires Ollama running"}
    • +
    • OpenRouter: {messages.modelConfig?.openrouterRequiresShort || "Requires OPENROUTER_API_KEY"}
    • +
    • OpenAI: {messages.modelConfig?.openaiRequiresShort || "Requires OPENAI_API_KEY"}
    • +
    +
    +
    +
    +
    + {/* Current model type indicator */} +
    +
    + {generatorModelName === 'google' ? 'Google API' : + generatorModelName === 'ollama' ? 'Local Ollama' : + generatorModelName === 'openrouter' ? 'OpenRouter API' : + 'API'} +
    + {generatorModelName === 'ollama' && ( +
    + ({messages.modelConfig?.requiresOllamaLocal || "Requires Ollama running locally"}) +
    + )} +
    + +
    + + + + + {messages.modelConfig?.configuredIn || "Models are configured in"} api/config/generators.json. + {messages.modelConfig?.clickHere || "Click"} {messages.modelConfig?.toLearnHow || "to learn how to customize models."} + +
    +
    +
    + ); +} \ No newline at end of file diff --git a/src/components/landing/AdvancedOptionsModal.tsx b/src/components/landing/AdvancedOptionsModal.tsx new file mode 100644 index 00000000..be0e6332 --- /dev/null +++ b/src/components/landing/AdvancedOptionsModal.tsx @@ -0,0 +1,47 @@ +import { Messages } from "next-intl"; + +export default function AdvancedOptionsModal({ + isOpen, + onClose, + content, + messages, +}: { + isOpen: boolean; + onClose: () => void; + content: React.ReactNode; + messages: Messages; +}) { + if (!isOpen) return null; + + return ( + <> + {/* Modal backdrop */} +
    +
    +
    +

    {messages.form.advancedOptions || "Advanced Options"}

    + +
    + {content} +
    + +
    +
    + + ) +} diff --git a/src/components/Ask.tsx b/src/components/wiki/Ask.tsx similarity index 98% rename from src/components/Ask.tsx rename to src/components/wiki/Ask.tsx index a39090b3..917d07f3 100644 --- a/src/components/Ask.tsx +++ b/src/components/wiki/Ask.tsx @@ -2,8 +2,10 @@ import React, { useState, useRef, useEffect } from 'react'; import { FaArrowRight, FaChevronLeft, FaChevronRight } from 'react-icons/fa'; -import Markdown from './Markdown'; +import Markdown from '../Markdown'; import { useLanguage } from '@/contexts/LanguageContext'; +import { getConfig } from '@/config'; +import { cn } from '@/utils/utils'; interface Message { role: 'user' | 'assistant' | 'system'; @@ -24,11 +26,12 @@ interface AskProps { bitbucketToken?: string; generatorModelName?: string; language?: string; + isAskSectionVisible?: boolean; } -// const SERVER_BASE_URL = process.env.NEXT_PUBLIC_SERVER_BASE_URL || 'http://localhost:8001'; +const config = getConfig('wikiPage.askSection'); -const Ask: React.FC = ({ repoUrl, githubToken, gitlabToken, bitbucketToken, generatorModelName, language = 'en' }) => { +const Ask: React.FC = ({ repoUrl, githubToken, gitlabToken, bitbucketToken, generatorModelName, language = 'en', isAskSectionVisible }) => { const [question, setQuestion] = useState(''); const [response, setResponse] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -487,8 +490,10 @@ const Ask: React.FC = ({ repoUrl, githubToken, gitlabToken, bitbucketT } }; + if (!isAskSectionVisible) return null; + return ( -
    +
    {/* Input area */}
    @@ -557,7 +562,7 @@ const Ask: React.FC = ({ repoUrl, githubToken, gitlabToken, bitbucketT
    diff --git a/src/components/wiki/AskSection.tsx b/src/components/wiki/AskSection.tsx new file mode 100644 index 00000000..6e1e95ad --- /dev/null +++ b/src/components/wiki/AskSection.tsx @@ -0,0 +1,81 @@ +import { RepoInfo, WikiPage, WikiStructure } from "@/app/types/types"; +import { useState } from "react"; +import { FaChevronDown, FaChevronUp } from "react-icons/fa"; +import Ask from "@/components/wiki/Ask"; +import { cn, getRepoUrl } from "@/utils/utils"; +import { Messages } from "next-intl"; +import { getConfig } from "@/config"; + +const config = getConfig('wikiPage.askSection'); + +export default function AskSection({ + wikiStructure, + generatedPages, + isLoading, + messages, + repoInfo, + githubToken, + gitlabToken, + bitbucketToken, + generatorModelName, + language, +}: { + wikiStructure?: WikiStructure + generatedPages: Record + isLoading: boolean + messages: Messages + repoInfo: RepoInfo + githubToken: string + gitlabToken: string + bitbucketToken: string + generatorModelName: string + language: string +}) { + if (!config.enabled) { + return null; + } + + const [isAskSectionVisible, setIsAskSectionVisible] = useState(config.defaultState === 'open' || !config.collapsible); + + return ( +
    + {/* Only show Ask component when wiki is successfully generated */} + {wikiStructure && Object.keys(generatedPages).length > 0 && !isLoading && ( +
    + + +
    + )} +
    + ); +} \ No newline at end of file diff --git a/src/components/wiki/WikiDocLoading.tsx b/src/components/wiki/WikiDocLoading.tsx new file mode 100644 index 00000000..7de2786f --- /dev/null +++ b/src/components/wiki/WikiDocLoading.tsx @@ -0,0 +1,82 @@ +import { WikiPage, WikiStructure } from "@/app/types/types" +import { Messages } from "next-intl" + +export default function WikiDocLoading({ + loadingMessage, + isExporting, + messages, + wikiStructure, + pagesInProgress, + language, +}: { + loadingMessage?: string + isExporting: boolean + messages: Messages + wikiStructure?: WikiStructure + pagesInProgress: Set + language: string +}) { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +

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

    + + {/* 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: WikiPage) => 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`} +
    • + )} +
    +
    + )} +
    + )} +
    + ); +} \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..56730ad0 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,83 @@ +export const config = { + landingPage: { + advancedOptions: { + /* + position: modal | embed + */ + position: 'modal' + } + }, + wikiPage: { + askSection: { + /* + enabled: true | false + */ + enabled: true, + /* + position: bottom | top | embed + */ + position: 'bottom', + /* + collapsible: true | false + */ + collapsible: true, + /* + defaultState: open | closed + */ + defaultState: 'open', + }, + exportWiki:{ + /* + markdown: true | false + */ + markdown: true, + /* + json: true | false + */ + json: true + }, + wikiContent: { + sizes: { + /* + Sizes for the markdown content using tailwindcss classes (https://tailwindcss.com/docs/font-size#using-a-custom-value) + Default sizes are: + p: 'xs', + h1: 'base', + h2: 'sm', + h3: 'sm', + h4: 'xs', + ul: 'xs', + ol: 'xs', + li: 'xs', + a: 'xs', + blockquote: 'xs', + table: 'xs', + codeBlock: 'xs', + codeInline: 'xs' + */ + p: 'xs', + h1: 'base', + h2: 'sm', + h3: 'sm', + h4: 'xs', + ul: 'xs', + ol: 'xs', + li: 'xs', + a: 'xs', + blockquote: 'xs', + table: 'xs', + codeBlock: 'xs', + codeInline: 'xs' + } + } + } +} + +export const getConfig = (path: string) => { + const keys = path.split('.'); + let result: any = config; + for (const key of keys) { + result = result[key]; + } + return result; +} diff --git a/src/messages/en.json b/src/messages/en.json index 212ac911..33943a7d 100644 --- a/src/messages/en.json +++ b/src/messages/en.json @@ -47,7 +47,8 @@ "selectPlatform": "Select Platform", "personalAccessToken": "{platform} Personal Access Token", "tokenPlaceholder": "Enter your {platform} token", - "tokenSecurityNote": "Token is stored in memory only and never persisted." + "tokenSecurityNote": "Token is stored in memory only and never persisted.", + "advancedOptions": "Advanced Options" }, "footer": { "copyright": "DeepWiki - AI-powered documentation for code repositories" @@ -69,8 +70,8 @@ "errorMessageDefault": "Please check that your repository exists and is public. Valid formats are \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", or local folder paths like \"C:\\\\path\\\\to\\\\folder\" or \"/path/to/folder\".", "backToHome": "Back to Home", "exportWiki": "Export Wiki", - "exportAsMarkdown": "Export as Markdown", - "exportAsJson": "Export as JSON", + "exportAsMarkdown": "Markdown", + "exportAsJson": "JSON", "pages": "Pages", "relatedFiles": "Related Files:", "relatedPages": "Related Pages:", diff --git a/src/messages/es.json b/src/messages/es.json index 7305cd27..aa49750f 100644 --- a/src/messages/es.json +++ b/src/messages/es.json @@ -47,7 +47,8 @@ "selectPlatform": "Seleccionar Plataforma", "personalAccessToken": "Token de Acceso Personal de {platform}", "tokenPlaceholder": "Ingresa tu token de {platform}", - "tokenSecurityNote": "El token solo se almacena en memoria y nunca se persiste." + "tokenSecurityNote": "El token solo se almacena en memoria y nunca se persiste.", + "advancedOptions": "Opciones Avanzadas" }, "footer": { "copyright": "DeepWiki - Documentación impulsada por IA para repositorios de código" @@ -69,8 +70,8 @@ "errorMessageDefault": "Por favor, verifica que tu repositorio existe y es público. Los formatos válidos son \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", o rutas de carpetas locales como \"C:\\\\path\\\\to\\\\folder\" o \"/path/to/folder\".", "backToHome": "Volver al Inicio", "exportWiki": "Exportar Wiki", - "exportAsMarkdown": "Exportar como Markdown", - "exportAsJson": "Exportar como JSON", + "exportAsMarkdown": "Markdown", + "exportAsJson": "JSON", "pages": "Páginas", "relatedFiles": "Archivos Relacionados:", "relatedPages": "Páginas Relacionadas:", diff --git a/src/messages/ja.json b/src/messages/ja.json index b9ead461..f7e87af1 100644 --- a/src/messages/ja.json +++ b/src/messages/ja.json @@ -47,7 +47,8 @@ "selectPlatform": "プラットフォームを選択", "personalAccessToken": "{platform}個人アクセストークン", "tokenPlaceholder": "{platform}トークンを入力してください", - "tokenSecurityNote": "トークンはメモリ内にのみ保存され、永続化されることはありません。" + "tokenSecurityNote": "トークンはメモリ内にのみ保存され、永続化されることはありません。", + "advancedOptions": "高度なオプション" }, "footer": { "copyright": "DeepWiki - コードリポジトリのためのAI駆動ドキュメンテーション" @@ -69,8 +70,8 @@ "errorMessageDefault": "リポジトリが存在し、公開されていることを確認してください。有効な形式は「owner/repo」、「https://github.com/owner/repo」、「https://gitlab.com/owner/repo」、「https://bitbucket.org/owner/repo」、またはローカルフォルダパス(例: 「C:\\\\path\\\\to\\\\folder」、「/path/to/folder」)です。", "backToHome": "ホームに戻る", "exportWiki": "Wikiをエクスポート", - "exportAsMarkdown": "Markdownとしてエクスポート", - "exportAsJson": "JSONとしてエクスポート", + "exportAsMarkdown": "Markdown", + "exportAsJson": "JSON", "pages": "ページ", "relatedFiles": "関連ファイル:", "relatedPages": "関連ページ:", diff --git a/src/messages/kr.json b/src/messages/kr.json index dfb9aaac..26594cf7 100644 --- a/src/messages/kr.json +++ b/src/messages/kr.json @@ -47,7 +47,8 @@ "selectPlatform": "플랫폼 선택", "personalAccessToken": "{platform} 개인 액세스 토큰", "tokenPlaceholder": "{platform} 토큰을 입력하세요", - "tokenSecurityNote": "토큰은 메모리에만 저장되며, 영구적으로 보존되지 않습니다." + "tokenSecurityNote": "토큰은 메모리에만 저장되며, 영구적으로 보존되지 않습니다.", + "advancedOptions": "고급 옵션" }, "footer": { "copyright": "DeepWiki - 코드 저장소를 위한 AI 기반 문서화" @@ -69,8 +70,8 @@ "errorMessageDefault": "저장소가 존재하며 공개 상태인지 확인해 주세요. 유효한 형식은 \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\" 또는 로컬 폴더 경로 \"C:\\\\path\\\\to\\\\folder\" 혹은 \"/path/to/folder\" 입니다.", "backToHome": "홈으로 돌아가기", "exportWiki": "위키 내보내기", - "exportAsMarkdown": "마크다운으로 내보내기", - "exportAsJson": "JSON으로 내보내기", + "exportAsMarkdown": "마크다운", + "exportAsJson": "JSON", "pages": "페이지", "relatedFiles": "관련 파일:", "relatedPages": "관련 페이지:", diff --git a/src/messages/vi.json b/src/messages/vi.json index fe4a7f25..224fb56c 100644 --- a/src/messages/vi.json +++ b/src/messages/vi.json @@ -47,7 +47,8 @@ "selectPlatform": "Chọn nền tảng", "personalAccessToken": "Token truy cập cá nhân {platform}", "tokenPlaceholder": "Nhập token {platform} của bạn", - "tokenSecurityNote": "Token chỉ được lưu trong bộ nhớ và không bao giờ được lưu trữ vĩnh viễn." + "tokenSecurityNote": "Token chỉ được lưu trong bộ nhớ và không bao giờ được lưu trữ vĩnh viễn.", + "advancedOptions": "Tùy chọn nâng cao" }, "footer": { "copyright": "DeepWiki - Tài liệu hỗ trợ bởi AI cho repository" @@ -69,8 +70,8 @@ "errorMessageDefault": "Vui lòng kiểm tra xem repository có tồn tại và công khai hay không. Các định dạng hợp lệ là \"owner/repo\", \"https://github.com/owner/repo\", \"https://gitlab.com/owner/repo\", \"https://bitbucket.org/owner/repo\", hoặc các đường dẫn thư mục cục bộ như \"C:\\\\path\\\\to\\\\folder\" hoặc \"/path/to/folder\".", "backToHome": "Quay lại trang chủ", "exportWiki": "Xuất Wiki", - "exportAsMarkdown": "Xuất dưới dạng Markdown", - "exportAsJson": "Xuất dưới dạng JSON", + "exportAsMarkdown": "Markdown", + "exportAsJson": "JSON", "pages": "Trang", "relatedFiles": "Tệp liên quan:", "relatedPages": "Trang liên quan:", diff --git a/src/messages/zh.json b/src/messages/zh.json index 9af990da..d58e2945 100644 --- a/src/messages/zh.json +++ b/src/messages/zh.json @@ -47,7 +47,8 @@ "selectPlatform": "选择平台", "personalAccessToken": "{platform}个人访问令牌", "tokenPlaceholder": "输入您的{platform}令牌", - "tokenSecurityNote": "令牌仅存储在内存中,从不持久化。" + "tokenSecurityNote": "令牌仅存储在内存中,从不持久化。", + "advancedOptions": "高级选项" }, "footer": { "copyright": "DeepWiki - 为代码仓库提供AI驱动的文档" @@ -69,8 +70,8 @@ "errorMessageDefault": "请检查您的仓库是否存在且为公开的。有效格式为\"owner/repo\"、\"https://github.com/owner/repo\"、\"https://gitlab.com/owner/repo\"、\"https://bitbucket.org/owner/repo\",或本地文件夹路径如\"C:\\\\path\\\\to\\\\folder\"或\"/path/to/folder\"。", "backToHome": "返回首页", "exportWiki": "导出Wiki", - "exportAsMarkdown": "导出为Markdown", - "exportAsJson": "导出为JSON", + "exportAsMarkdown": "Markdown", + "exportAsJson": "JSON", "pages": "页面", "relatedFiles": "相关文件:", "relatedPages": "相关页面:", diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 00000000..b1d43ed6 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,47 @@ +import { Messages } from "next-intl"; +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +// Create a simple translation function +export const t = (key: string, messages: Messages, 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; +}; + +// Helper functions for token handling and API requests +export 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}`; +}; From f00cdfe0e23dd4115fd29b5a15c89528c2c9a726 Mon Sep 17 00:00:00 2001 From: JNLei <16848622+JNLei@users.noreply.github.com> Date: Sat, 10 May 2025 21:44:28 -0400 Subject: [PATCH 2/2] Solve linting issues --- src/app/page.tsx | 10 ++-------- src/app/types/types.ts | 6 ++++++ src/components/common/Footer.tsx | 2 -- src/components/landing/AccessTokens.tsx | 1 - src/components/landing/AdvancedOptions.tsx | 3 ++- src/components/wiki/AskSection.tsx | 4 ++-- src/config.ts | 4 +++- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 7cf203d3..d6b5e99d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,8 +3,7 @@ import React, { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; -import { FaWikipediaW, FaGithub, FaGitlab, FaBitbucket, FaCoffee, FaTwitter, FaCog } from 'react-icons/fa'; -import ThemeToggle from '@/components/theme-toggle'; +import { FaWikipediaW, FaCog } from 'react-icons/fa'; import Mermaid from '../components/Mermaid'; import ModelConfigModal from '@/components/ModelConfigModal'; @@ -15,6 +14,7 @@ import AdvancedOptions from '@/components/landing/AdvancedOptions'; import AccessTokens from '@/components/landing/AccessTokens'; import { getConfig } from '@/config'; import AdvancedOptionsModal from '@/components/landing/AdvancedOptionsModal'; +import { GeneratorModel } from './types/types'; const config = getConfig('landingPage'); const SERVER_BASE_URL = process.env.NEXT_PUBLIC_SERVER_BASE_URL || 'http://localhost:8001'; @@ -47,12 +47,6 @@ const DEMO_SEQUENCE_CHART = `sequenceDiagram %% Add a note to make text more visible Note over User,GitHub: DeepWiki supports sequence diagrams for visualizing interactions`; -// Define the model interface -interface GeneratorModel { - display_name: string; - // If there are other fields in the model object, add them here -} - export default function Home() { const router = useRouter(); const { language, setLanguage, messages } = useLanguage(); diff --git a/src/app/types/types.ts b/src/app/types/types.ts index 386cd551..8e7430c8 100644 --- a/src/app/types/types.ts +++ b/src/app/types/types.ts @@ -15,6 +15,12 @@ export interface WikiStructure { pages: WikiPage[]; } +// Define the model interface +export interface GeneratorModel { + display_name: string; + // If there are other fields in the model object, add them here +} + export type RepoInfo = { owner: string; repo: string; diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx index 110bb5ed..bf6a099e 100644 --- a/src/components/common/Footer.tsx +++ b/src/components/common/Footer.tsx @@ -1,6 +1,4 @@ import ThemeToggle from "@/components/theme-toggle"; -import { t } from "@/utils/utils"; -import { Messages } from "next-intl"; import { FaGithub, FaTwitter } from "react-icons/fa"; import { FaCoffee } from "react-icons/fa"; diff --git a/src/components/landing/AccessTokens.tsx b/src/components/landing/AccessTokens.tsx index 6a148935..30c967aa 100644 --- a/src/components/landing/AccessTokens.tsx +++ b/src/components/landing/AccessTokens.tsx @@ -71,7 +71,6 @@ export default function AccessTokens({ } function PlatformAccessToken({ - showTokenInputs, setShowTokenInputs, selectedPlatform, setSelectedPlatform, diff --git a/src/components/landing/AdvancedOptions.tsx b/src/components/landing/AdvancedOptions.tsx index 506ca852..19e333ae 100644 --- a/src/components/landing/AdvancedOptions.tsx +++ b/src/components/landing/AdvancedOptions.tsx @@ -4,6 +4,7 @@ import { FaCog } from "react-icons/fa"; import { getConfig } from "@/config"; import { IoLanguageOutline } from "react-icons/io5"; import { MdSelectAll } from "react-icons/md"; +import { GeneratorModel } from "@/app/types/types"; const config = getConfig('landingPage.advancedOptions'); @@ -20,7 +21,7 @@ export default function AdvancedOptions({ setSelectedLanguage: (language: string) => void; generatorModelName: string; setGeneratorModelName: (modelName: string) => void; - availableModels: Record; + availableModels: {[key: string]: GeneratorModel}; setIsModelConfigModalOpen: (open: boolean) => void; messages: Messages; }) { diff --git a/src/components/wiki/AskSection.tsx b/src/components/wiki/AskSection.tsx index 6e1e95ad..663849fa 100644 --- a/src/components/wiki/AskSection.tsx +++ b/src/components/wiki/AskSection.tsx @@ -31,11 +31,11 @@ export default function AskSection({ generatorModelName: string language: string }) { + const [isAskSectionVisible, setIsAskSectionVisible] = useState(config.defaultState === 'open' || !config.collapsible); + if (!config.enabled) { return null; } - - const [isAskSectionVisible, setIsAskSectionVisible] = useState(config.defaultState === 'open' || !config.collapsible); return (
    { const keys = path.split('.'); - let result: any = config; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let result: {[key: string]: any} = config; for (const key of keys) { result = result[key]; }