* feat: add Upload to AssistantSetting * feat: locate the specific location of the document based on the coordinates of the chunktags/v0.1.0
| @@ -74,6 +74,7 @@ export interface IChunk { | |||
| docnm_kwd: string; | |||
| img_id: string; | |||
| important_kwd: any[]; | |||
| positions: number[][]; | |||
| } | |||
| export interface ITestingChunk { | |||
| @@ -8,6 +8,8 @@ | |||
| @gray11: rgba(232, 232, 234, 1); | |||
| @purple: rgba(127, 86, 217, 1); | |||
| @selectedBackgroundColor: rgba(239, 248, 255, 1); | |||
| @blurBackground: rgba(22, 119, 255, 0.5); | |||
| @blurBackgroundHover: rgba(22, 119, 255, 0.2); | |||
| @fontSize12: 12px; | |||
| @fontSize14: 14px; | |||
| @@ -13,6 +13,28 @@ | |||
| color: red; | |||
| 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 { | |||
| @@ -64,9 +64,7 @@ const ChunkCard = ({ | |||
| onClick={handleContentClick} | |||
| className={styles.content} | |||
| dangerouslySetInnerHTML={{ __html: item.content_with_weight }} | |||
| > | |||
| {/* {item.content_with_weight} */} | |||
| </section> | |||
| ></section> | |||
| <div> | |||
| <Switch checked={enabled} onChange={onChange} /> | |||
| </div> | |||
| @@ -6,21 +6,27 @@ export const testHighlights = [ | |||
| position: { | |||
| boundingRect: { | |||
| x1: 219.7, | |||
| // x1: 419.7, | |||
| y1: 204.3, | |||
| // y1: 304.3, | |||
| x2: 547.0, | |||
| // x2: 747.0, | |||
| y2: 264.0, | |||
| width: 849, | |||
| height: 1200, | |||
| // y2: 364.0, | |||
| }, | |||
| rects: [ | |||
| { | |||
| x1: 219.7, | |||
| y1: 204.3, | |||
| x2: 547.0, | |||
| y2: 264.0, | |||
| width: 849, | |||
| height: 1200, | |||
| }, | |||
| // { | |||
| // 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, | |||
| }, | |||
| @@ -28,6 +34,56 @@ export const testHighlights = [ | |||
| text: 'Flow or TypeScript?', | |||
| 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] } }; | |||
| }); | |||
| @@ -6,6 +6,9 @@ | |||
| position: relative; | |||
| :global(.PdfHighlighter) { | |||
| overflow-x: hidden; | |||
| // left: 0; | |||
| } | |||
| :global(.Highlight--scrolledTo .Highlight__part) { | |||
| overflow-x: hidden; | |||
| background-color: rgba(255, 226, 143, 1); | |||
| } | |||
| } | |||
| @@ -1,16 +1,15 @@ | |||
| import { Spin } from 'antd'; | |||
| import { useRef, useState } from 'react'; | |||
| import type { NewHighlight } from 'react-pdf-highlighter'; | |||
| import { useEffect, useRef } from 'react'; | |||
| import { | |||
| AreaHighlight, | |||
| Highlight, | |||
| NewHighlight, | |||
| PdfHighlighter, | |||
| PdfLoader, | |||
| Popup, | |||
| Tip, | |||
| } from 'react-pdf-highlighter'; | |||
| import { useGetSelectedChunk } from '../../hooks'; | |||
| import { testHighlights } from './hightlights'; | |||
| import { useGetChunkHighlights, useGetSelectedChunk } from '../../hooks'; | |||
| import { useGetDocumentUrl } from './hooks'; | |||
| import styles from './index.less'; | |||
| @@ -36,7 +35,9 @@ const Preview = ({ selectedChunkId }: IProps) => { | |||
| const url = useGetDocumentUrl(); | |||
| 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 parseIdFromHash = () => | |||
| @@ -67,7 +68,7 @@ const Preview = ({ selectedChunkId }: IProps) => { | |||
| console.log('Saving highlight', highlight); | |||
| setState([{ ...highlight, id: getNextId() }, ...highlights]); | |||
| // setState([{ ...highlight, id: getNextId() }, ...highlights]); | |||
| }; | |||
| const updateHighlight = ( | |||
| @@ -77,29 +78,31 @@ const Preview = ({ selectedChunkId }: IProps) => { | |||
| ) => { | |||
| 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; | |||
| }), | |||
| ); | |||
| // 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; | |||
| // }), | |||
| // ); | |||
| }; | |||
| // useEffect(() => { | |||
| // ref.current(testHighlights[0]); | |||
| // }, [selectedChunk]); | |||
| useEffect(() => { | |||
| if (state.length > 0) { | |||
| ref.current(state[0]); | |||
| } | |||
| }, [state]); | |||
| return ( | |||
| <div className={styles.documentContainer}> | |||
| @@ -1,6 +1,8 @@ | |||
| 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 { v4 as uuid } from 'uuid'; | |||
| export const useSelectDocumentInfo = () => { | |||
| const documentInfo: IKnowledgeFile = useSelector( | |||
| @@ -28,5 +30,46 @@ export const useHandleChunkCardClick = () => { | |||
| export const useGetSelectedChunk = (selectedChunkId: string) => { | |||
| 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; | |||
| }; | |||
| @@ -23,7 +23,7 @@ import { | |||
| import type { ColumnsType } from 'antd/es/table'; | |||
| import { PaginationProps } from 'antd/lib'; | |||
| 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 styles from './index.less'; | |||
| import ParsingActionCell from './parsing-action-cell'; | |||
| @@ -144,19 +144,22 @@ const KnowledgeFile = () => { | |||
| }); | |||
| }, [dispatch]); | |||
| const linkToUploadPage = useCallback(() => { | |||
| navigate(`/knowledge/dataset/upload?id=${knowledgeBaseId}`); | |||
| }, [navigate, knowledgeBaseId]); | |||
| const actionItems: MenuProps['items'] = useMemo(() => { | |||
| return [ | |||
| { | |||
| key: '1', | |||
| onClick: linkToUploadPage, | |||
| label: ( | |||
| <div> | |||
| <Button type="link"> | |||
| <Link to={`/knowledge/dataset/upload?id=${knowledgeBaseId}`}> | |||
| <Space> | |||
| <FileTextOutlined /> | |||
| Local files | |||
| </Space> | |||
| </Link> | |||
| <Space> | |||
| <FileTextOutlined /> | |||
| Local files | |||
| </Space> | |||
| </Button> | |||
| </div> | |||
| ), | |||
| @@ -164,9 +167,10 @@ const KnowledgeFile = () => { | |||
| { type: 'divider' }, | |||
| { | |||
| key: '2', | |||
| onClick: showCEFModal, | |||
| label: ( | |||
| <div> | |||
| <Button type="link" onClick={showCEFModal}> | |||
| <Button type="link"> | |||
| <FileOutlined /> | |||
| Create empty file | |||
| </Button> | |||
| @@ -175,7 +179,7 @@ const KnowledgeFile = () => { | |||
| // disabled: true, | |||
| }, | |||
| ]; | |||
| }, [knowledgeBaseId, showCEFModal]); | |||
| }, [linkToUploadPage, showCEFModal]); | |||
| const toChunk = (id: string) => { | |||
| navigate( | |||
| @@ -1,9 +1,10 @@ | |||
| import { Form, Input, Select } from 'antd'; | |||
| import { Form, Input, Select, Upload } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { ISegmentedContentProps } from '../interface'; | |||
| import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| import styles from './index.less'; | |||
| const AssistantSetting = ({ show }: ISegmentedContentProps) => { | |||
| @@ -13,6 +14,13 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { | |||
| value: x.id, | |||
| })); | |||
| const normFile = (e: any) => { | |||
| if (Array.isArray(e)) { | |||
| return e; | |||
| } | |||
| return e?.fileList; | |||
| }; | |||
| return ( | |||
| <section | |||
| className={classNames({ | |||
| @@ -26,8 +34,22 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { | |||
| > | |||
| <Input placeholder="e.g. Resume Jarvis" /> | |||
| </Form.Item> | |||
| <Form.Item name={'icon'} label="Assistant avatar"> | |||
| <Input /> | |||
| <Form.Item | |||
| 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 name={'language'} label="Language" initialValue={'Chinese'}> | |||
| <Select | |||
| @@ -1,6 +1,6 @@ | |||
| import { ReactComponent as ChatConfigurationAtom } from '@/assets/svg/chat-configuration-atom.svg'; | |||
| 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 omit from 'lodash/omit'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| @@ -67,6 +67,14 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| ...excludeUnEnabledVariables(values), | |||
| ]); | |||
| 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 = { | |||
| dialog_id: id, | |||
| ...nextValues, | |||
| @@ -75,6 +83,7 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| parameters: promptEngineRef.current, | |||
| empty_response: emptyResponse, | |||
| }, | |||
| icon, | |||
| }; | |||
| console.info(promptEngineRef.current); | |||
| console.info(nextValues); | |||
| @@ -112,7 +121,13 @@ const ChatConfigurationModal = ({ visible, hideModal, id }: IProps) => { | |||
| ); | |||
| 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]); | |||
| return ( | |||
| @@ -2,6 +2,7 @@ import { ReactComponent as ChatAppCube } from '@/assets/svg/chat-app-cube.svg'; | |||
| import { useSetModalState } from '@/hooks/commonHooks'; | |||
| import { DeleteOutlined, EditOutlined, FormOutlined } from '@ant-design/icons'; | |||
| import { | |||
| Avatar, | |||
| Button, | |||
| Card, | |||
| Divider, | |||
| @@ -208,8 +209,8 @@ const Chat = () => { | |||
| onClick={handleDialogCardClick(x.id)} | |||
| > | |||
| <Flex justify="space-between" align="center"> | |||
| <Space> | |||
| {x.icon} | |||
| <Space size={15}> | |||
| <Avatar src={x.icon} shape={'square'} /> | |||
| <section> | |||
| <b>{x.name}</b> | |||
| <div>{x.description}</div> | |||