Feat: Refactoring the documentation page using shadcn. #10427 (#12376)

### What problem does this PR solve?

Feat: Refactoring the documentation page using shadcn. #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-12-31 19:00:37 +08:00
committed by GitHub
parent 96810b7d97
commit 10c28c5ecd
11 changed files with 204 additions and 386 deletions

View File

@ -0,0 +1,93 @@
import React, { useSyncExternalStore } from 'react';
export interface AnchorItem {
key: string;
href: string;
title: string;
children?: AnchorItem[];
}
interface SimpleAnchorProps {
items: AnchorItem[];
className?: string;
style?: React.CSSProperties;
}
// Subscribe to URL hash changes
const subscribeHash = (callback: () => void) => {
window.addEventListener('hashchange', callback);
return () => window.removeEventListener('hashchange', callback);
};
const getHash = () => window.location.hash;
const Anchor: React.FC<SimpleAnchorProps> = ({
items,
className = '',
style = {},
}) => {
// Sync with URL hash changes, to highlight the active item
const hash = useSyncExternalStore(subscribeHash, getHash);
// Handle menu item click
const handleClick = (
e: React.MouseEvent<HTMLAnchorElement>,
href: string,
) => {
e.preventDefault();
const targetId = href.replace('#', '');
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Update URL hash (triggers hashchange event)
window.location.hash = href;
// Smooth scroll to target
targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
};
if (items.length === 0) return null;
return (
<nav className={className} style={style}>
<ul className="list-none p-0 m-0">
{items.map((item) => (
<li key={item.key} className="mb-2">
<a
href={item.href}
onClick={(e) => handleClick(e, item.href)}
className={`block px-3 py-1.5 no-underline rounded cursor-pointer transition-all duration-300 hover:text-accent-primary/70 ${
hash === item.href
? 'text-accent-primary bg-accent-primary-5'
: 'text-text-secondary bg-transparent'
}`}
>
{item.title}
</a>
{item.children && item.children.length > 0 && (
<ul className="list-none p-0 ml-4 mt-1">
{item.children.map((child) => (
<li key={child.key} className="mb-1">
<a
href={child.href}
onClick={(e) => handleClick(e, child.href)}
className={`block px-3 py-1 text-sm no-underline rounded cursor-pointer transition-all duration-300 hover:text-accent-primary/70 ${
hash === child.href
? 'text-accent-primary bg-accent-primary-5'
: 'text-text-secondary bg-transparent'
}`}
>
{child.title}
</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
);
};
export default Anchor;

View File

@ -1,52 +1,26 @@
import { useIsDarkTheme } from '@/components/theme-provider';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import { useSetModalState } from '@/hooks/common-hooks';
import { LangfuseCard } from '@/pages/user-setting/setting-model/langfuse';
import apiDoc from '@parent/docs/references/http_api_reference.md';
import MarkdownPreview from '@uiw/react-markdown-preview';
import { Button, Card, Flex, Space } from 'antd';
import ChatApiKeyModal from '../chat-api-key-modal';
import { usePreviewChat } from '../hooks';
import BackendServiceApi from './backend-service-api';
import MarkdownToc from './markdown-toc';
const ApiContent = ({
id,
idKey,
hideChatPreviewCard = false,
}: {
id?: string;
idKey: string;
hideChatPreviewCard?: boolean;
}) => {
const { t } = useTranslate('chat');
const ApiContent = ({ id, idKey }: { id?: string; idKey: string }) => {
const {
visible: apiKeyVisible,
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
// const { embedVisible, hideEmbedModal, showEmbedModal, embedToken } =
// useShowEmbedModal(idKey);
const { handlePreview } = usePreviewChat(idKey);
const isDarkTheme = useIsDarkTheme();
return (
<div className="pb-2">
<Flex vertical gap={'middle'}>
<section className="flex flex-col gap-2 pb-5">
<BackendServiceApi show={showApiKeyModal}></BackendServiceApi>
{!hideChatPreviewCard && (
<Card title={`${name} Web App`}>
<Flex gap={8} vertical>
<Space size={'middle'}>
<Button onClick={handlePreview}>{t('preview')}</Button>
{/* <Button onClick={() => showEmbedModal(id)}>
{t('embedded')}
</Button> */}
</Space>
</Flex>
</Card>
)}
<div style={{ position: 'relative' }}>
<MarkdownToc content={apiDoc} />
</div>
@ -54,7 +28,8 @@ const ApiContent = ({
source={apiDoc}
wrapperElement={{ 'data-color-mode': isDarkTheme ? 'dark' : 'light' }}
></MarkdownPreview>
</Flex>
</section>
<LangfuseCard></LangfuseCard>
{apiKeyVisible && (
<ChatApiKeyModal
hideModal={hideApiKeyModal}
@ -62,14 +37,6 @@ const ApiContent = ({
idKey={idKey}
></ChatApiKeyModal>
)}
{/* {embedVisible && (
<EmbedModal
token={embedToken}
visible={embedVisible}
hideModal={hideEmbedModal}
></EmbedModal>
)} */}
<LangfuseCard></LangfuseCard>
</div>
);
};

View File

@ -1,33 +1,28 @@
import { Button, Card, Flex, Space, Typography } from 'antd';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { CopyToClipboardWithText } from '@/components/copy-to-clipboard';
import { useTranslate } from '@/hooks/common-hooks';
import styles from './index.less';
const { Paragraph } = Typography;
const BackendServiceApi = ({ show }: { show(): void }) => {
const { t } = useTranslate('chat');
return (
<Card
title={
<Space size={'large'}>
<span>RAGFlow API</span>
<Button onClick={show} type="primary">
{t('apiKey')}
</Button>
</Space>
}
>
<Flex gap={8} align="center">
<b>{t('backendServiceApi')}</b>
<Paragraph
copyable={{ text: `${location.origin}` }}
className={styles.apiLinkText}
>
{location.origin}
</Paragraph>
</Flex>
<Card>
<CardHeader>
<div className="flex items-center gap-4">
<CardTitle>RAGFlow API</CardTitle>
<Button onClick={show}>{t('apiKey')}</Button>
</div>
</CardHeader>
<CardContent>
<div className="flex items-center gap-2">
<b className="font-semibold">{t('backendServiceApi')}</b>
<CopyToClipboardWithText
text={location.origin}
></CopyToClipboardWithText>
</div>
</CardContent>
</Card>
);
};

View File

@ -1,31 +0,0 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { Modal } from 'antd';
import ApiContent from './api-content';
const ChatOverviewModal = ({
visible,
hideModal,
id,
idKey,
}: IModalProps<any> & { id: string; name?: string; idKey: string }) => {
const { t } = useTranslate('chat');
return (
<>
<Modal
title={t('overview')}
open={visible}
onCancel={hideModal}
cancelButtonProps={{ style: { display: 'none' } }}
onOk={hideModal}
width={'100vw'}
okText={t('close', { keyPrefix: 'common' })}
>
<ApiContent id={id} idKey={idKey}></ApiContent>
</Modal>
</>
);
};
export default ChatOverviewModal;

View File

@ -1,21 +1,27 @@
import { Anchor } from 'antd';
import type { AnchorLinkItemProps } from 'antd/es/anchor/Anchor';
import React, { useEffect, useState } from 'react';
import Anchor, { AnchorItem } from './anchor';
interface MarkdownTocProps {
content: string;
}
const MarkdownToc: React.FC<MarkdownTocProps> = ({ content }) => {
const [items, setItems] = useState<AnchorLinkItemProps[]>([]);
const [items, setItems] = useState<AnchorItem[]>([]);
useEffect(() => {
const generateTocItems = () => {
const headings = document.querySelectorAll(
'.wmde-markdown h2, .wmde-markdown h3',
);
const tocItems: AnchorLinkItemProps[] = [];
let currentH2Item: AnchorLinkItemProps | null = null;
// If headings haven't rendered yet, wait for next frame
if (headings.length === 0) {
requestAnimationFrame(generateTocItems);
return;
}
const tocItems: AnchorItem[] = [];
let currentH2Item: AnchorItem | null = null;
headings.forEach((heading) => {
const title = heading.textContent || '';
@ -23,7 +29,7 @@ const MarkdownToc: React.FC<MarkdownTocProps> = ({ content }) => {
const isH2 = heading.tagName.toLowerCase() === 'h2';
if (id && title) {
const item: AnchorLinkItemProps = {
const item: AnchorItem = {
key: id,
href: `#${id}`,
title,
@ -48,7 +54,10 @@ const MarkdownToc: React.FC<MarkdownTocProps> = ({ content }) => {
setItems(tocItems.slice(1));
};
setTimeout(generateTocItems, 100);
// Use requestAnimationFrame to ensure execution after DOM rendering
requestAnimationFrame(() => {
requestAnimationFrame(generateTocItems);
});
}, [content]);
return (
@ -56,7 +65,7 @@ const MarkdownToc: React.FC<MarkdownTocProps> = ({ content }) => {
className="markdown-toc bg-bg-base text-text-primary shadow shadow-text-secondary"
style={{
position: 'fixed',
right: 20,
right: 30,
top: 100,
bottom: 150,
width: 200,
@ -66,7 +75,7 @@ const MarkdownToc: React.FC<MarkdownTocProps> = ({ content }) => {
zIndex: 1000,
}}
>
<Anchor items={items} affix={false} />
<Anchor items={items} />
</div>
);
};