mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
feat: set width of chunk text to 100% and add Skeleton to Preview of document and remove react-pdf (#94)
* feat: remove react-pdf * feat: add Skeleton to Preview of document * feat: set width of chunk text to 100%
This commit is contained in:
@ -1,15 +1,14 @@
|
||||
import { Divider, Layout, theme } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Outlet } from 'umi';
|
||||
import '../locales/config';
|
||||
import Header from './components/header';
|
||||
|
||||
import styles from './index.less';
|
||||
|
||||
const { Content } = Layout;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = theme.useToken();
|
||||
@ -25,6 +24,7 @@ const App: React.FC = () => {
|
||||
background: colorBgContainer,
|
||||
borderRadius: borderRadiusLG,
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
|
||||
@ -13,6 +13,9 @@
|
||||
color: red;
|
||||
font-style: normal;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
caption {
|
||||
color: @blurBackground;
|
||||
|
||||
@ -1,89 +0,0 @@
|
||||
export const testHighlights = [
|
||||
{
|
||||
content: {
|
||||
text: '实验证明,由氧氯化锆锂和高镍三元正极组成的全固态锂电池展示了极为优异的性能:在12 分钟快速充电的条件下,该电池仍然成功地在室温稳定循环2000 圈以上。',
|
||||
},
|
||||
position: {
|
||||
boundingRect: {
|
||||
x1: 219.7,
|
||||
// x1: 419.7,
|
||||
y1: 204.3,
|
||||
// y1: 304.3,
|
||||
x2: 547.0,
|
||||
// x2: 747.0,
|
||||
y2: 264.0,
|
||||
// y2: 364.0,
|
||||
},
|
||||
rects: [
|
||||
// {
|
||||
// x1: 219.7,
|
||||
// // x1: 419.7,
|
||||
// y1: 204.3,
|
||||
// // y1: 304.3,
|
||||
// x2: 547.0,
|
||||
// // x2: 747.0,
|
||||
// y2: 264.0,
|
||||
// // y2: 364.0,
|
||||
// width: 849,
|
||||
// height: 1200,
|
||||
// },
|
||||
],
|
||||
pageNumber: 9,
|
||||
},
|
||||
comment: {
|
||||
text: 'Flow or TypeScript?',
|
||||
emoji: '🔥',
|
||||
},
|
||||
id: 'jsdlihdkghergjl',
|
||||
},
|
||||
{
|
||||
content: {
|
||||
text: '图2:乘联会预计6 月新能源乘用车厂商批发销量74 万辆,环比增长10%,同比增长30%。',
|
||||
},
|
||||
position: {
|
||||
boundingRect: {
|
||||
x1: 219.0,
|
||||
x2: 546.0,
|
||||
y1: 616.0,
|
||||
y2: 674.7,
|
||||
},
|
||||
rects: [],
|
||||
pageNumber: 6,
|
||||
},
|
||||
comment: {
|
||||
text: 'Flow or TypeScript?',
|
||||
emoji: '🔥',
|
||||
},
|
||||
id: 'bfdbtymkhjildbfghserrgrt',
|
||||
},
|
||||
{
|
||||
content: {
|
||||
text: '图2:乘联会预计6 月新能源乘用车厂商批发销量74 万辆,环比增长10%,同比增长30%。',
|
||||
},
|
||||
position: {
|
||||
boundingRect: {
|
||||
x1: 73.7,
|
||||
x2: 391.7,
|
||||
y1: 570.3,
|
||||
y2: 676.3,
|
||||
},
|
||||
rects: [],
|
||||
pageNumber: 1,
|
||||
},
|
||||
comment: {
|
||||
text: '',
|
||||
emoji: '',
|
||||
},
|
||||
id: 'fgnhxdvsesgmghyu',
|
||||
},
|
||||
].map((x) => {
|
||||
const boundingRect = x.position.boundingRect;
|
||||
const ret: any = {
|
||||
width: 849,
|
||||
height: 1200,
|
||||
};
|
||||
Object.entries(boundingRect).forEach(([key, value]) => {
|
||||
ret[key] = value / 0.7;
|
||||
});
|
||||
return { ...x, position: { ...x.position, boundingRect: ret, rects: [ret] } };
|
||||
});
|
||||
@ -1,59 +0,0 @@
|
||||
import { useGetKnowledgeSearchParams } from '@/hooks/knowledgeHook';
|
||||
import { api_host } from '@/utils/api';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Document, Page, pdfjs } from 'react-pdf';
|
||||
|
||||
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
|
||||
import 'react-pdf/dist/esm/Page/TextLayer.css';
|
||||
import { useDocumentResizeObserver, useHighlightText } from './hooks';
|
||||
|
||||
import { Spin } from 'antd';
|
||||
import { useGetSelectedChunk } from '../../hooks';
|
||||
import styles from './index.less';
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.js',
|
||||
import.meta.url,
|
||||
).toString();
|
||||
|
||||
interface IProps {
|
||||
selectedChunkId: string;
|
||||
}
|
||||
|
||||
const DocumentPreview = ({ selectedChunkId }: IProps) => {
|
||||
const [numPages, setNumPages] = useState<number>();
|
||||
const { documentId } = useGetKnowledgeSearchParams();
|
||||
const { containerWidth, setContainerRef } = useDocumentResizeObserver();
|
||||
const selectedChunk = useGetSelectedChunk(selectedChunkId);
|
||||
console.info(selectedChunk?.content_with_weight);
|
||||
const textRenderer = useHighlightText(selectedChunk?.content_with_weight);
|
||||
|
||||
function onDocumentLoadSuccess({ numPages }: { numPages: number }): void {
|
||||
setNumPages(numPages);
|
||||
}
|
||||
|
||||
const url = useMemo(() => {
|
||||
return `${api_host}/document/get/${documentId}`;
|
||||
}, [documentId]);
|
||||
|
||||
return (
|
||||
<div ref={setContainerRef} className={styles.documentContainer}>
|
||||
<Document
|
||||
file={url}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
loading={<Spin></Spin>}
|
||||
>
|
||||
{Array.from(new Array(numPages), (el, index) => (
|
||||
<Page
|
||||
key={`page_${index + 1}`}
|
||||
pageNumber={index + 1}
|
||||
width={containerWidth}
|
||||
customTextRenderer={textRenderer}
|
||||
/>
|
||||
))}
|
||||
</Document>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentPreview;
|
||||
@ -1,15 +1,14 @@
|
||||
import { Spin } from 'antd';
|
||||
import { Skeleton } from 'antd';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
AreaHighlight,
|
||||
Highlight,
|
||||
NewHighlight,
|
||||
IHighlight,
|
||||
PdfHighlighter,
|
||||
PdfLoader,
|
||||
Popup,
|
||||
Tip,
|
||||
} from 'react-pdf-highlighter';
|
||||
import { useGetChunkHighlights, useGetSelectedChunk } from '../../hooks';
|
||||
import { useGetChunkHighlights } from '../../hooks';
|
||||
import { useGetDocumentUrl } from './hooks';
|
||||
|
||||
import styles from './index.less';
|
||||
@ -18,8 +17,6 @@ interface IProps {
|
||||
selectedChunkId: string;
|
||||
}
|
||||
|
||||
const getNextId = () => String(Math.random()).slice(2);
|
||||
|
||||
const HighlightPopup = ({
|
||||
comment,
|
||||
}: {
|
||||
@ -33,70 +30,10 @@ const HighlightPopup = ({
|
||||
|
||||
const Preview = ({ selectedChunkId }: IProps) => {
|
||||
const url = useGetDocumentUrl();
|
||||
const selectedChunk = useGetSelectedChunk(selectedChunkId);
|
||||
|
||||
// const [state, setState] = useState<any>(testHighlights);
|
||||
const state = useGetChunkHighlights(selectedChunkId);
|
||||
const ref = useRef<(highlight: IHighlight) => void>(() => {});
|
||||
|
||||
const ref = useRef((highlight: any) => {});
|
||||
|
||||
const parseIdFromHash = () =>
|
||||
document.location.hash.slice('#highlight-'.length);
|
||||
|
||||
const resetHash = () => {
|
||||
document.location.hash = '';
|
||||
};
|
||||
|
||||
const getHighlightById = (id: string) => {
|
||||
const highlights = state;
|
||||
|
||||
return highlights.find((highlight: any) => highlight.id === id);
|
||||
};
|
||||
|
||||
// let scrollViewerTo = (highlight: any) => {};
|
||||
|
||||
let scrollToHighlightFromHash = () => {
|
||||
const highlight = getHighlightById(parseIdFromHash());
|
||||
|
||||
if (highlight) {
|
||||
ref.current(highlight);
|
||||
}
|
||||
};
|
||||
|
||||
const addHighlight = (highlight: NewHighlight) => {
|
||||
const highlights = state;
|
||||
|
||||
console.log('Saving highlight', highlight);
|
||||
|
||||
// setState([{ ...highlight, id: getNextId() }, ...highlights]);
|
||||
};
|
||||
|
||||
const updateHighlight = (
|
||||
highlightId: string,
|
||||
position: Object,
|
||||
content: Object,
|
||||
) => {
|
||||
console.log('Updating highlight', highlightId, position, content);
|
||||
|
||||
// setState(
|
||||
// state.map((h: any) => {
|
||||
// const {
|
||||
// id,
|
||||
// position: originalPosition,
|
||||
// content: originalContent,
|
||||
// ...rest
|
||||
// } = h;
|
||||
// return id === highlightId
|
||||
// ? {
|
||||
// id,
|
||||
// position: { ...originalPosition, ...position },
|
||||
// content: { ...originalContent, ...content },
|
||||
// ...rest,
|
||||
// }
|
||||
// : h;
|
||||
// }),
|
||||
// );
|
||||
};
|
||||
const resetHash = () => {};
|
||||
|
||||
useEffect(() => {
|
||||
if (state.length > 0) {
|
||||
@ -106,35 +43,16 @@ const Preview = ({ selectedChunkId }: IProps) => {
|
||||
|
||||
return (
|
||||
<div className={styles.documentContainer}>
|
||||
<PdfLoader url={url} beforeLoad={<Spin />}>
|
||||
<PdfLoader url={url} beforeLoad={<Skeleton active />}>
|
||||
{(pdfDocument) => (
|
||||
<PdfHighlighter
|
||||
pdfDocument={pdfDocument}
|
||||
enableAreaSelection={(event) => event.altKey}
|
||||
onScrollChange={resetHash}
|
||||
// pdfScaleValue="page-width"
|
||||
|
||||
scrollRef={(scrollTo) => {
|
||||
// scrollViewerTo = scrollTo;
|
||||
ref.current = scrollTo;
|
||||
|
||||
scrollToHighlightFromHash();
|
||||
}}
|
||||
onSelectionFinished={(
|
||||
position,
|
||||
content,
|
||||
hideTipAndSelection,
|
||||
transformSelection,
|
||||
) => (
|
||||
<Tip
|
||||
onOpen={transformSelection}
|
||||
onConfirm={(comment) => {
|
||||
addHighlight({ content, position, comment });
|
||||
|
||||
hideTipAndSelection();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
onSelectionFinished={() => null}
|
||||
highlightTransform={(
|
||||
highlight,
|
||||
index,
|
||||
@ -158,13 +76,7 @@ const Preview = ({ selectedChunkId }: IProps) => {
|
||||
<AreaHighlight
|
||||
isScrolledTo={isScrolledTo}
|
||||
highlight={highlight}
|
||||
onChange={(boundingRect) => {
|
||||
updateHighlight(
|
||||
highlight.id,
|
||||
{ boundingRect: viewportToScaled(boundingRect) },
|
||||
{ image: screenshot(boundingRect) },
|
||||
);
|
||||
}}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -172,7 +84,7 @@ const Preview = ({ selectedChunkId }: IProps) => {
|
||||
<Popup
|
||||
popupContent={<HighlightPopup {...highlight} />}
|
||||
onMouseOver={(popupContent) =>
|
||||
setTip(highlight, (highlight: any) => popupContent)
|
||||
setTip(highlight, () => popupContent)
|
||||
}
|
||||
onMouseOut={hideTip}
|
||||
key={index}
|
||||
|
||||
@ -288,7 +288,7 @@ const KnowledgeFile = () => {
|
||||
dataSource={data}
|
||||
loading={loading}
|
||||
pagination={pagination}
|
||||
scroll={{ scrollToFirstRowOnChange: true, x: true, y: 'fill' }}
|
||||
scroll={{ scrollToFirstRowOnChange: true, x: 1300, y: 'fill' }}
|
||||
/>
|
||||
<CreateEPModal getKfList={getKfList} kb_id={knowledgeBaseId} />
|
||||
<SegmentSetModal
|
||||
|
||||
@ -1,5 +1,17 @@
|
||||
.popover-content {
|
||||
width: 300px;
|
||||
.popoverContent {
|
||||
width: 40vw;
|
||||
|
||||
.popoverContentItem {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.popoverContentText {
|
||||
white-space: pre-line;
|
||||
.popoverContentErrorLabel {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.operationIcon {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { ReactComponent as RefreshIcon } from '@/assets/svg/refresh.svg';
|
||||
import { ReactComponent as RunIcon } from '@/assets/svg/run.svg';
|
||||
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd';
|
||||
import { RunningStatus, RunningStatusMap } from '../constant';
|
||||
|
||||
import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd';
|
||||
import reactStringReplace from 'react-string-replace';
|
||||
import { useDispatch } from 'umi';
|
||||
import { RunningStatus, RunningStatusMap } from '../constant';
|
||||
import styles from './index.less';
|
||||
|
||||
const iconMap = {
|
||||
@ -35,17 +35,27 @@ const PopoverContent = ({ record }: IProps) => {
|
||||
{
|
||||
key: 'progress_msg',
|
||||
label: 'Progress Msg',
|
||||
children: record.progress_msg,
|
||||
children: reactStringReplace(
|
||||
record.progress_msg.trim(),
|
||||
/(\[ERROR\].+\s)/g,
|
||||
(match, i) => {
|
||||
return (
|
||||
<span key={i} className={styles.popoverContentErrorLabel}>
|
||||
{match}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex vertical className={styles['popover-content']}>
|
||||
{items.map((x) => {
|
||||
<Flex vertical className={styles.popoverContent}>
|
||||
{items.map((x, idx) => {
|
||||
return (
|
||||
<div key={x.key}>
|
||||
<div key={x.key} className={idx < 2 ? styles.popoverContentItem : ''}>
|
||||
<b>{x.label}:</b>
|
||||
<p>{x.children}</p>
|
||||
<div className={styles.popoverContentText}>{x.children}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.contentWrapper {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
// @import '~@/less/variable.less';
|
||||
|
||||
.knowledge {
|
||||
padding: 48px 60px;
|
||||
padding: 48px 0;
|
||||
}
|
||||
|
||||
.topWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding-bottom: 72px;
|
||||
padding: 0 60px 72px;
|
||||
|
||||
.title {
|
||||
font-family: Inter;
|
||||
@ -41,3 +41,7 @@
|
||||
.topButton();
|
||||
}
|
||||
}
|
||||
.knowledgeCardContainer {
|
||||
padding: 0 60px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@ -1,21 +1,25 @@
|
||||
import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
|
||||
import ModalManager from '@/components/modal-manager';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Space } from 'antd';
|
||||
import { Button, Empty, Flex, Space } from 'antd';
|
||||
import KnowledgeCard from './knowledge-card';
|
||||
import KnowledgeCreatingModal from './knowledge-creating-modal';
|
||||
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
||||
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
||||
import styles from './index.less';
|
||||
|
||||
const Knowledge = () => {
|
||||
const data = useFetchKnowledgeList();
|
||||
const list = useFetchKnowledgeList();
|
||||
const userInfo = useSelectUserInfo();
|
||||
|
||||
return (
|
||||
<div className={styles.knowledge}>
|
||||
<Flex className={styles.knowledge} vertical flex={1}>
|
||||
<div className={styles.topWrapper}>
|
||||
<div>
|
||||
<span className={styles.title}>Welcome back, Zing</span>
|
||||
<span className={styles.title}>
|
||||
Welcome back, {userInfo.nickname}
|
||||
</span>
|
||||
<p className={styles.description}>
|
||||
Which database are we going to use today?
|
||||
</p>
|
||||
@ -46,12 +50,22 @@ const Knowledge = () => {
|
||||
</ModalManager>
|
||||
</Space>
|
||||
</div>
|
||||
<Flex gap="large" wrap="wrap">
|
||||
{data.map((item: any) => {
|
||||
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
|
||||
})}
|
||||
<Flex
|
||||
gap="large"
|
||||
wrap="wrap"
|
||||
flex={1}
|
||||
// justify="center"
|
||||
className={styles.knowledgeCardContainer}
|
||||
>
|
||||
{list.length > 0 ? (
|
||||
list.map((item: any) => {
|
||||
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
|
||||
})
|
||||
) : (
|
||||
<Empty></Empty>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user