mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-02 10:42:36 +08:00
### 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:
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user