feat: locate the specific location of the document based on the coordinates of the chunk and add Upload to AssistantSetting (#92)

* feat: add Upload to AssistantSetting

* feat: locate the specific location of the document based on the coordinates of the chunk
This commit is contained in:
balibabu
2024-03-04 17:03:23 +08:00
committed by GitHub
parent 685b4d8a95
commit fae00827e6
12 changed files with 231 additions and 61 deletions

View File

@ -74,6 +74,7 @@ export interface IChunk {
docnm_kwd: string; docnm_kwd: string;
img_id: string; img_id: string;
important_kwd: any[]; important_kwd: any[];
positions: number[][];
} }
export interface ITestingChunk { export interface ITestingChunk {

View File

@ -8,6 +8,8 @@
@gray11: rgba(232, 232, 234, 1); @gray11: rgba(232, 232, 234, 1);
@purple: rgba(127, 86, 217, 1); @purple: rgba(127, 86, 217, 1);
@selectedBackgroundColor: rgba(239, 248, 255, 1); @selectedBackgroundColor: rgba(239, 248, 255, 1);
@blurBackground: rgba(22, 119, 255, 0.5);
@blurBackgroundHover: rgba(22, 119, 255, 0.2);
@fontSize12: 12px; @fontSize12: 12px;
@fontSize14: 14px; @fontSize14: 14px;

View File

@ -13,6 +13,28 @@
color: red; color: red;
font-style: normal; font-style: normal;
} }
caption {
color: @blurBackground;
font-size: 20px;
height: 50px;
line-height: 50px;
font-weight: 600;
margin-bottom: 10px;
}
th {
color: #fff;
background-color: @blurBackground;
}
td:hover {
background: @blurBackgroundHover;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
} }
.cardSelected { .cardSelected {

View File

@ -64,9 +64,7 @@ const ChunkCard = ({
onClick={handleContentClick} onClick={handleContentClick}
className={styles.content} className={styles.content}
dangerouslySetInnerHTML={{ __html: item.content_with_weight }} dangerouslySetInnerHTML={{ __html: item.content_with_weight }}
> ></section>
{/* {item.content_with_weight} */}
</section>
<div> <div>
<Switch checked={enabled} onChange={onChange} /> <Switch checked={enabled} onChange={onChange} />
</div> </div>

View File

@ -6,21 +6,27 @@ export const testHighlights = [
position: { position: {
boundingRect: { boundingRect: {
x1: 219.7, x1: 219.7,
// x1: 419.7,
y1: 204.3, y1: 204.3,
// y1: 304.3,
x2: 547.0, x2: 547.0,
// x2: 747.0,
y2: 264.0, y2: 264.0,
width: 849, // y2: 364.0,
height: 1200,
}, },
rects: [ rects: [
{ // {
x1: 219.7, // x1: 219.7,
y1: 204.3, // // x1: 419.7,
x2: 547.0, // y1: 204.3,
y2: 264.0, // // y1: 304.3,
width: 849, // x2: 547.0,
height: 1200, // // x2: 747.0,
}, // y2: 264.0,
// // y2: 364.0,
// width: 849,
// height: 1200,
// },
], ],
pageNumber: 9, pageNumber: 9,
}, },
@ -28,6 +34,56 @@ export const testHighlights = [
text: 'Flow or TypeScript?', text: 'Flow or TypeScript?',
emoji: '🔥', emoji: '🔥',
}, },
id: '8245652131754351', 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] } };
});

View File

@ -6,6 +6,9 @@
position: relative; position: relative;
:global(.PdfHighlighter) { :global(.PdfHighlighter) {
overflow-x: hidden; overflow-x: hidden;
// left: 0; }
:global(.Highlight--scrolledTo .Highlight__part) {
overflow-x: hidden;
background-color: rgba(255, 226, 143, 1);
} }
} }

View File

@ -1,16 +1,15 @@
import { Spin } from 'antd'; import { Spin } from 'antd';
import { useRef, useState } from 'react'; import { useEffect, useRef } from 'react';
import type { NewHighlight } from 'react-pdf-highlighter';
import { import {
AreaHighlight, AreaHighlight,
Highlight, Highlight,
NewHighlight,
PdfHighlighter, PdfHighlighter,
PdfLoader, PdfLoader,
Popup, Popup,
Tip, Tip,
} from 'react-pdf-highlighter'; } from 'react-pdf-highlighter';
import { useGetSelectedChunk } from '../../hooks'; import { useGetChunkHighlights, useGetSelectedChunk } from '../../hooks';
import { testHighlights } from './hightlights';
import { useGetDocumentUrl } from './hooks'; import { useGetDocumentUrl } from './hooks';
import styles from './index.less'; import styles from './index.less';
@ -36,7 +35,9 @@ const Preview = ({ selectedChunkId }: IProps) => {
const url = useGetDocumentUrl(); const url = useGetDocumentUrl();
const selectedChunk = useGetSelectedChunk(selectedChunkId); const selectedChunk = useGetSelectedChunk(selectedChunkId);
const [state, setState] = useState<any>(testHighlights); // const [state, setState] = useState<any>(testHighlights);
const state = useGetChunkHighlights(selectedChunkId);
const ref = useRef((highlight: any) => {}); const ref = useRef((highlight: any) => {});
const parseIdFromHash = () => const parseIdFromHash = () =>
@ -67,7 +68,7 @@ const Preview = ({ selectedChunkId }: IProps) => {
console.log('Saving highlight', highlight); console.log('Saving highlight', highlight);
setState([{ ...highlight, id: getNextId() }, ...highlights]); // setState([{ ...highlight, id: getNextId() }, ...highlights]);
}; };
const updateHighlight = ( const updateHighlight = (
@ -77,29 +78,31 @@ const Preview = ({ selectedChunkId }: IProps) => {
) => { ) => {
console.log('Updating highlight', highlightId, position, content); console.log('Updating highlight', highlightId, position, content);
setState( // setState(
state.map((h: any) => { // state.map((h: any) => {
const { // const {
id, // id,
position: originalPosition, // position: originalPosition,
content: originalContent, // content: originalContent,
...rest // ...rest
} = h; // } = h;
return id === highlightId // return id === highlightId
? { // ? {
id, // id,
position: { ...originalPosition, ...position }, // position: { ...originalPosition, ...position },
content: { ...originalContent, ...content }, // content: { ...originalContent, ...content },
...rest, // ...rest,
} // }
: h; // : h;
}), // }),
); // );
}; };
// useEffect(() => { useEffect(() => {
// ref.current(testHighlights[0]); if (state.length > 0) {
// }, [selectedChunk]); ref.current(state[0]);
}
}, [state]);
return ( return (
<div className={styles.documentContainer}> <div className={styles.documentContainer}>

View File

@ -1,6 +1,8 @@
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge'; import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
import { useCallback, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter';
import { useSelector } from 'umi'; import { useSelector } from 'umi';
import { v4 as uuid } from 'uuid';
export const useSelectDocumentInfo = () => { export const useSelectDocumentInfo = () => {
const documentInfo: IKnowledgeFile = useSelector( const documentInfo: IKnowledgeFile = useSelector(
@ -28,5 +30,46 @@ export const useHandleChunkCardClick = () => {
export const useGetSelectedChunk = (selectedChunkId: string) => { export const useGetSelectedChunk = (selectedChunkId: string) => {
const chunkList: IChunk[] = useSelectChunkList(); const chunkList: IChunk[] = useSelectChunkList();
return chunkList.find((x) => x.chunk_id === selectedChunkId); return (
chunkList.find((x) => x.chunk_id === selectedChunkId) ?? ({} as IChunk)
);
};
export const useGetChunkHighlights = (
selectedChunkId: string,
): IHighlight[] => {
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
const highlights: IHighlight[] = useMemo(() => {
return Array.isArray(selectedChunk?.positions)
? selectedChunk?.positions?.map((x) => {
const actualPositions = x.map((y, index) =>
index !== 0 ? y / 0.7 : y,
);
const boundingRect = {
width: 849,
height: 1200,
x1: actualPositions[1],
x2: actualPositions[2],
y1: actualPositions[3],
y2: actualPositions[4],
};
return {
id: uuid(),
comment: {
text: '',
emoji: '',
},
content: { text: selectedChunk.content_with_weight },
position: {
boundingRect: boundingRect,
rects: [boundingRect],
pageNumber: x[0],
},
};
})
: [];
}, [selectedChunk]);
return highlights;
}; };

View File

@ -23,7 +23,7 @@ import {
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { PaginationProps } from 'antd/lib'; import { PaginationProps } from 'antd/lib';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Link, useDispatch, useNavigate, useSelector } from 'umi'; import { useDispatch, useNavigate, useSelector } from 'umi';
import CreateEPModal from './createEFileModal'; import CreateEPModal from './createEFileModal';
import styles from './index.less'; import styles from './index.less';
import ParsingActionCell from './parsing-action-cell'; import ParsingActionCell from './parsing-action-cell';
@ -144,19 +144,22 @@ const KnowledgeFile = () => {
}); });
}, [dispatch]); }, [dispatch]);
const linkToUploadPage = useCallback(() => {
navigate(`/knowledge/dataset/upload?id=${knowledgeBaseId}`);
}, [navigate, knowledgeBaseId]);
const actionItems: MenuProps['items'] = useMemo(() => { const actionItems: MenuProps['items'] = useMemo(() => {
return [ return [
{ {
key: '1', key: '1',
onClick: linkToUploadPage,
label: ( label: (
<div> <div>
<Button type="link"> <Button type="link">
<Link to={`/knowledge/dataset/upload?id=${knowledgeBaseId}`}> <Space>
<Space> <FileTextOutlined />
<FileTextOutlined /> Local files
Local files </Space>
</Space>
</Link>
</Button> </Button>
</div> </div>
), ),
@ -164,9 +167,10 @@ const KnowledgeFile = () => {
{ type: 'divider' }, { type: 'divider' },
{ {
key: '2', key: '2',
onClick: showCEFModal,
label: ( label: (
<div> <div>
<Button type="link" onClick={showCEFModal}> <Button type="link">
<FileOutlined /> <FileOutlined />
Create empty file Create empty file
</Button> </Button>
@ -175,7 +179,7 @@ const KnowledgeFile = () => {
// disabled: true, // disabled: true,
}, },
]; ];
}, [knowledgeBaseId, showCEFModal]); }, [linkToUploadPage, showCEFModal]);
const toChunk = (id: string) => { const toChunk = (id: string) => {
navigate( navigate(

View File

@ -1,9 +1,10 @@
import { Form, Input, Select } from 'antd'; import { Form, Input, Select, Upload } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { ISegmentedContentProps } from '../interface'; import { ISegmentedContentProps } from '../interface';
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
import { PlusOutlined } from '@ant-design/icons';
import styles from './index.less'; import styles from './index.less';
const AssistantSetting = ({ show }: ISegmentedContentProps) => { const AssistantSetting = ({ show }: ISegmentedContentProps) => {
@ -13,6 +14,13 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
value: x.id, value: x.id,
})); }));
const normFile = (e: any) => {
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
return ( return (
<section <section
className={classNames({ className={classNames({
@ -26,8 +34,22 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
> >
<Input placeholder="e.g. Resume Jarvis" /> <Input placeholder="e.g. Resume Jarvis" />
</Form.Item> </Form.Item>
<Form.Item name={'icon'} label="Assistant avatar"> <Form.Item
<Input /> name="icon"
label="Assistant avatar"
valuePropName="fileList"
getValueFromEvent={normFile}
>
<Upload
listType="picture-card"
maxCount={1}
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
>
<button style={{ border: 0, background: 'none' }} type="button">
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</button>
</Upload>
</Form.Item> </Form.Item>
<Form.Item name={'language'} label="Language" initialValue={'Chinese'}> <Form.Item name={'language'} label="Language" initialValue={'Chinese'}>
<Select <Select

View File

@ -1,6 +1,6 @@
import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg'; import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg';
import { IModalManagerChildrenProps } from '@/components/modal-manager'; import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { Divider, Flex, Form, Modal, Segmented } from 'antd'; import { Divider, Flex, Form, Modal, Segmented, UploadFile } from 'antd';
import { SegmentedValue } from 'antd/es/segmented'; import { SegmentedValue } from 'antd/es/segmented';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
@ -67,6 +67,14 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
...excludeUnEnabledVariables(values), ...excludeUnEnabledVariables(values),
]); ]);
const emptyResponse = nextValues.prompt_config?.empty_response ?? ''; const emptyResponse = nextValues.prompt_config?.empty_response ?? '';
const fileList = values.icon;
let icon;
if (Array.isArray(fileList) && fileList.length > 0) {
icon = fileList[0].thumbUrl;
}
const finalValues = { const finalValues = {
dialog_id: id, dialog_id: id,
...nextValues, ...nextValues,
@ -75,6 +83,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
parameters: promptEngineRef.current, parameters: promptEngineRef.current,
empty_response: emptyResponse, empty_response: emptyResponse,
}, },
icon,
}; };
console.info(promptEngineRef.current); console.info(promptEngineRef.current);
console.info(nextValues); console.info(nextValues);
@ -112,7 +121,13 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => {
); );
useEffect(() => { useEffect(() => {
form.setFieldsValue(currentDialog); const icon = currentDialog.icon;
let fileList: UploadFile[] = [];
if (icon) {
fileList = [{ uid: '1', name: 'file', thumbUrl: icon, status: 'done' }];
}
form.setFieldsValue({ ...currentDialog, icon: fileList });
}, [currentDialog, form]); }, [currentDialog, form]);
return ( return (

View File

@ -2,6 +2,7 @@ import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg';
import { useSetModalState } from '@/hooks/commonHooks'; import { useSetModalState } from '@/hooks/commonHooks';
import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons'; import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons';
import { import {
Avatar,
Button, Button,
Card, Card,
Divider, Divider,
@ -208,8 +209,8 @@ const Chat = () => {
onClick={handleDialogCardClick(x.id)} onClick={handleDialogCardClick(x.id)}
> >
<Flex justify="space-between" align="center"> <Flex justify="space-between" align="center">
<Space> <Space size={15}>
{x.icon} <Avatar src={x.icon} shape={'square'} />
<section> <section>
<b>{x.name}</b> <b>{x.name}</b>
<div>{x.description}</div> <div>{x.description}</div>