* feat: set chunk to available state * feat: select all chunktags/v0.1.0
| // .eslintrc.js | |||||
| module.exports = { | |||||
| // Umi 项目 | |||||
| extends: [require.resolve('umi/eslint'), 'plugin:react-hooks/recommended'], | |||||
| }; |
| "@types/lodash": "^4.14.202", | "@types/lodash": "^4.14.202", | ||||
| "@types/react": "^18.0.33", | "@types/react": "^18.0.33", | ||||
| "@types/react-dom": "^18.0.11", | "@types/react-dom": "^18.0.11", | ||||
| "@umijs/lint": "^4.1.1", | |||||
| "@umijs/plugins": "^4.1.0", | "@umijs/plugins": "^4.1.0", | ||||
| "cross-env": "^7.0.3", | "cross-env": "^7.0.3", | ||||
| "prettier": "^3.2.4", | "prettier": "^3.2.4", | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@umijs/lint": { | "node_modules/@umijs/lint": { | ||||
| "version": "4.1.0", | |||||
| "resolved": "https://registry.npmmirror.com/@umijs/lint/-/lint-4.1.0.tgz", | |||||
| "integrity": "sha512-drXkAeBJGMLrPr/dDiOZ2Z+3VKkAf53MzoOIhwHy5atq+PFNG9Y7e6YuWrK3qVF75zg9culQzlHTvinCjDK97Q==", | |||||
| "version": "4.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/@umijs/lint/-/lint-4.1.1.tgz", | |||||
| "integrity": "sha512-fy2edKuYw42eM3LuH/2AiH0ZKdembFx3SR8dIGKxf7BmEQOSfUhskLiNGE8tSRubCiVzGUWvZQDw1YQcU0bsHg==", | |||||
| "dev": true, | |||||
| "dependencies": { | "dependencies": { | ||||
| "@babel/core": "7.23.6", | "@babel/core": "7.23.6", | ||||
| "@babel/eslint-parser": "7.23.3", | "@babel/eslint-parser": "7.23.3", | ||||
| "@stylelint/postcss-css-in-js": "^0.38.0", | "@stylelint/postcss-css-in-js": "^0.38.0", | ||||
| "@typescript-eslint/eslint-plugin": "^5.62.0", | "@typescript-eslint/eslint-plugin": "^5.62.0", | ||||
| "@typescript-eslint/parser": "^5.62.0", | "@typescript-eslint/parser": "^5.62.0", | ||||
| "@umijs/babel-preset-umi": "4.1.0", | |||||
| "@umijs/babel-preset-umi": "4.1.1", | |||||
| "eslint-plugin-jest": "27.2.3", | "eslint-plugin-jest": "27.2.3", | ||||
| "eslint-plugin-react": "7.33.2", | "eslint-plugin-react": "7.33.2", | ||||
| "eslint-plugin-react-hooks": "4.6.0", | "eslint-plugin-react-hooks": "4.6.0", | ||||
| "version": "7.23.6", | "version": "7.23.6", | ||||
| "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.6.tgz", | "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.6.tgz", | ||||
| "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", | "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", | ||||
| "dev": true, | |||||
| "dependencies": { | "dependencies": { | ||||
| "@ampproject/remapping": "^2.2.0", | "@ampproject/remapping": "^2.2.0", | ||||
| "@babel/code-frame": "^7.23.5", | "@babel/code-frame": "^7.23.5", | ||||
| "node": ">=6.9.0" | "node": ">=6.9.0" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/@umijs/lint/node_modules/@babel/runtime": { | |||||
| "version": "7.23.6", | |||||
| "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.6.tgz", | |||||
| "integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==", | |||||
| "dev": true, | |||||
| "dependencies": { | |||||
| "regenerator-runtime": "^0.14.0" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=6.9.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/@umijs/lint/node_modules/@umijs/babel-preset-umi": { | |||||
| "version": "4.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/@umijs/babel-preset-umi/-/babel-preset-umi-4.1.1.tgz", | |||||
| "integrity": "sha512-6pYZnF03euAJGZN3VLe8PKKRNMH6Zxj4GKNooLvJ0Wz0eMufmYDcA4CpbR6h8i1JpgcQ0Sngr8bqHLb7oMqrvw==", | |||||
| "dev": true, | |||||
| "dependencies": { | |||||
| "@babel/runtime": "7.23.6", | |||||
| "@bloomberg/record-tuple-polyfill": "0.0.4", | |||||
| "@umijs/bundler-utils": "4.1.1", | |||||
| "@umijs/utils": "4.1.1", | |||||
| "core-js": "3.34.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/@umijs/lint/node_modules/@umijs/bundler-utils": { | |||||
| "version": "4.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/@umijs/bundler-utils/-/bundler-utils-4.1.1.tgz", | |||||
| "integrity": "sha512-k1I1tjDePgB1XqpQHZiLJ/5gS4EykY8hqqzEzD1CSbd5KFE614+q6W/gcpFZ0YLJDWY1GdjOYpRokvuI/MSRfg==", | |||||
| "dev": true, | |||||
| "dependencies": { | |||||
| "@umijs/utils": "4.1.1", | |||||
| "esbuild": "0.17.19", | |||||
| "regenerate": "^1.4.2", | |||||
| "regenerate-unicode-properties": "10.1.1", | |||||
| "spdy": "^4.0.2" | |||||
| } | |||||
| }, | |||||
| "node_modules/@umijs/lint/node_modules/@umijs/utils": { | |||||
| "version": "4.1.1", | |||||
| "resolved": "https://registry.npmmirror.com/@umijs/utils/-/utils-4.1.1.tgz", | |||||
| "integrity": "sha512-hbnbJR3RA7fu4E7q4JFZ47XMYArr6Zn5bftr8YZ+o6hzJlomr4gzoOXE+XxM7rVMK4AFZoc+QZgNTJyISd08Pg==", | |||||
| "dev": true, | |||||
| "dependencies": { | |||||
| "chokidar": "3.5.3", | |||||
| "pino": "7.11.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/@umijs/mfsu": { | "node_modules/@umijs/mfsu": { | ||||
| "version": "4.1.0", | "version": "4.1.0", | ||||
| "resolved": "https://registry.npmmirror.com/@umijs/mfsu/-/mfsu-4.1.0.tgz", | "resolved": "https://registry.npmmirror.com/@umijs/mfsu/-/mfsu-4.1.0.tgz", | ||||
| "qs": "^6.9.1" | "qs": "^6.9.1" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/umi/node_modules/@babel/core": { | |||||
| "version": "7.23.6", | |||||
| "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.23.6.tgz", | |||||
| "integrity": "sha512-FxpRyGjrMJXh7X3wGLGhNDCRiwpWEF74sKjTLDJSG5Kyvow3QZaG0Adbqzi9ZrVjTWpsX+2cxWXD71NMg93kdw==", | |||||
| "dependencies": { | |||||
| "@ampproject/remapping": "^2.2.0", | |||||
| "@babel/code-frame": "^7.23.5", | |||||
| "@babel/generator": "^7.23.6", | |||||
| "@babel/helper-compilation-targets": "^7.23.6", | |||||
| "@babel/helper-module-transforms": "^7.23.3", | |||||
| "@babel/helpers": "^7.23.6", | |||||
| "@babel/parser": "^7.23.6", | |||||
| "@babel/template": "^7.22.15", | |||||
| "@babel/traverse": "^7.23.6", | |||||
| "@babel/types": "^7.23.6", | |||||
| "convert-source-map": "^2.0.0", | |||||
| "debug": "^4.1.0", | |||||
| "gensync": "^1.0.0-beta.2", | |||||
| "json5": "^2.2.3", | |||||
| "semver": "^6.3.1" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=6.9.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/umi/node_modules/@babel/runtime": { | "node_modules/umi/node_modules/@babel/runtime": { | ||||
| "version": "7.23.6", | "version": "7.23.6", | ||||
| "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.6.tgz", | "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.6.tgz", | ||||
| "node": ">=6.9.0" | "node": ">=6.9.0" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/umi/node_modules/@umijs/lint": { | |||||
| "version": "4.1.0", | |||||
| "resolved": "https://registry.npmmirror.com/@umijs/lint/-/lint-4.1.0.tgz", | |||||
| "integrity": "sha512-drXkAeBJGMLrPr/dDiOZ2Z+3VKkAf53MzoOIhwHy5atq+PFNG9Y7e6YuWrK3qVF75zg9culQzlHTvinCjDK97Q==", | |||||
| "dependencies": { | |||||
| "@babel/core": "7.23.6", | |||||
| "@babel/eslint-parser": "7.23.3", | |||||
| "@stylelint/postcss-css-in-js": "^0.38.0", | |||||
| "@typescript-eslint/eslint-plugin": "^5.62.0", | |||||
| "@typescript-eslint/parser": "^5.62.0", | |||||
| "@umijs/babel-preset-umi": "4.1.0", | |||||
| "eslint-plugin-jest": "27.2.3", | |||||
| "eslint-plugin-react": "7.33.2", | |||||
| "eslint-plugin-react-hooks": "4.6.0", | |||||
| "postcss": "^8.4.21", | |||||
| "postcss-syntax": "0.36.2", | |||||
| "stylelint-config-standard": "25.0.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/umi/node_modules/fast-glob": { | "node_modules/umi/node_modules/fast-glob": { | ||||
| "version": "3.3.2", | "version": "3.3.2", | ||||
| "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz", | "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz", |
| "build": "umi build", | "build": "umi build", | ||||
| "dev": "cross-env PORT=9000 umi dev", | "dev": "cross-env PORT=9000 umi dev", | ||||
| "postinstall": "umi setup", | "postinstall": "umi setup", | ||||
| "lint": "umi lint --eslint-only", | |||||
| "setup": "umi setup", | "setup": "umi setup", | ||||
| "start": "npm run dev" | "start": "npm run dev" | ||||
| }, | }, | ||||
| "@types/lodash": "^4.14.202", | "@types/lodash": "^4.14.202", | ||||
| "@types/react": "^18.0.33", | "@types/react": "^18.0.33", | ||||
| "@types/react-dom": "^18.0.11", | "@types/react-dom": "^18.0.11", | ||||
| "@umijs/lint": "^4.1.1", | |||||
| "@umijs/plugins": "^4.1.0", | "@umijs/plugins": "^4.1.0", | ||||
| "cross-env": "^7.0.3", | "cross-env": "^7.0.3", | ||||
| "prettier": "^3.2.4", | "prettier": "^3.2.4", |
| import React, { ReactNode } from "react"; | |||||
| import { Inspector } from "react-dev-inspector"; | |||||
| import React, { ReactNode } from 'react'; | |||||
| import { Inspector } from 'react-dev-inspector'; | |||||
| export function rootContainer(container: ReactNode) { | export function rootContainer(container: ReactNode) { | ||||
| return React.createElement(Inspector, null, container); | return React.createElement(Inspector, null, container); |
| export interface BaseState { | export interface BaseState { | ||||
| pagination: Pagination; | pagination: Pagination; | ||||
| searchString: string; | |||||
| } | } |
| chat_id: string; | chat_id: string; | ||||
| speech2text_id: string; | speech2text_id: string; | ||||
| } | } | ||||
| export interface IChunk { | |||||
| available_int: number; // Whether to enable, 0: not enabled, 1: enabled | |||||
| chunk_id: string; | |||||
| content_with_weight: string; | |||||
| doc_id: string; | |||||
| docnm_kwd: string; | |||||
| img_id: string; | |||||
| important_kwd: any[]; | |||||
| } |
| .image { | |||||
| width: 100px !important; | |||||
| min-width: 100px; | |||||
| } | |||||
| .imagePreview { | |||||
| width: 600px; | |||||
| } |
| import { IChunk } from '@/interfaces/database/knowledge'; | |||||
| import { api_host } from '@/utils/api'; | |||||
| import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd'; | |||||
| import { useDispatch } from 'umi'; | |||||
| import { useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| interface IProps { | |||||
| item: IChunk; | |||||
| checked: boolean; | |||||
| handleCheckboxClick: (chunkId: string, checked: boolean) => void; | |||||
| } | |||||
| interface IImage { | |||||
| id: string; | |||||
| className: string; | |||||
| } | |||||
| // Pass onMouseEnter and onMouseLeave to img tag using props | |||||
| const Image = ({ id, className, ...props }: IImage) => { | |||||
| return ( | |||||
| <img | |||||
| {...props} | |||||
| src={`${api_host}/document/image/${id}`} | |||||
| alt="" | |||||
| className={className} | |||||
| /> | |||||
| ); | |||||
| }; | |||||
| const ChunkCard = ({ item, checked, handleCheckboxClick }: IProps) => { | |||||
| const dispatch = useDispatch(); | |||||
| const available = Number(item.available_int); | |||||
| const [enabled, setEnabled] = useState(available === 1); | |||||
| const switchChunk = () => { | |||||
| dispatch({ | |||||
| type: 'chunkModel/switch_chunk', | |||||
| payload: { | |||||
| chunk_ids: [item.chunk_id], | |||||
| available_int: available === 0 ? 1 : 0, | |||||
| doc_id: item.doc_id, | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const onChange = (checked: boolean) => { | |||||
| setEnabled(checked); | |||||
| switchChunk(); | |||||
| }; | |||||
| const handleCheck: CheckboxProps['onChange'] = (e) => { | |||||
| handleCheckboxClick(item.chunk_id, e.target.checked); | |||||
| }; | |||||
| return ( | |||||
| <div> | |||||
| <Card> | |||||
| <Flex gap={'middle'} justify={'space-between'}> | |||||
| <Checkbox onChange={handleCheck} checked={checked}></Checkbox> | |||||
| {item.img_id && ( | |||||
| <Popover | |||||
| placement="topRight" | |||||
| content={ | |||||
| <Image id={item.img_id} className={styles.imagePreview}></Image> | |||||
| } | |||||
| > | |||||
| <img | |||||
| src={`${api_host}/document/image/${item.img_id}`} | |||||
| alt="" | |||||
| className={styles.image} | |||||
| /> | |||||
| <Image id={item.img_id} className={styles.image}></Image> | |||||
| </Popover> | |||||
| )} | |||||
| <section>{item.content_with_weight}</section> | |||||
| <div> | |||||
| <Switch checked={enabled} onChange={onChange} /> | |||||
| </div> | |||||
| </Flex> | |||||
| </Card> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default ChunkCard; |
| import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; | import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; | ||||
| import { KnowledgeRouteKey } from '@/constants/knowledge'; | |||||
| import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; | |||||
| import { | import { | ||||
| ArrowLeftOutlined, | ArrowLeftOutlined, | ||||
| CheckCircleOutlined, | CheckCircleOutlined, | ||||
| PlusOutlined, | PlusOutlined, | ||||
| SearchOutlined, | SearchOutlined, | ||||
| } from '@ant-design/icons'; | } from '@ant-design/icons'; | ||||
| import { Button, Checkbox, Flex, Menu, MenuProps, Popover, Space } from 'antd'; | |||||
| import { useMemo } from 'react'; | |||||
| import { | |||||
| Button, | |||||
| Checkbox, | |||||
| Flex, | |||||
| Menu, | |||||
| MenuProps, | |||||
| Popover, | |||||
| Radio, | |||||
| RadioChangeEvent, | |||||
| Space, | |||||
| } from 'antd'; | |||||
| import { useCallback, useMemo } from 'react'; | |||||
| import { Link, useDispatch, useSelector } from 'umi'; | |||||
| import { ChunkModelState } from '../../model'; | |||||
| interface IProps { | |||||
| checked: boolean; | |||||
| getChunkList: () => void; | |||||
| selectAllChunk: (checked: boolean) => void; | |||||
| } | |||||
| const ChunkToolBar = ({ getChunkList, selectAllChunk, checked }: IProps) => { | |||||
| const { documentInfo, available }: ChunkModelState = useSelector( | |||||
| (state: any) => state.chunkModel, | |||||
| ); | |||||
| const dispatch = useDispatch(); | |||||
| const knowledgeBaseId = useKnowledgeBaseId(); | |||||
| const handleSelectAllCheck = useCallback( | |||||
| (e: any) => { | |||||
| // console.info(e.target.checked); | |||||
| selectAllChunk(e.target.checked); | |||||
| }, | |||||
| [selectAllChunk], | |||||
| ); | |||||
| const ChunkToolBar = () => { | |||||
| const items: MenuProps['items'] = useMemo(() => { | const items: MenuProps['items'] = useMemo(() => { | ||||
| return [ | return [ | ||||
| { | { | ||||
| key: '1', | key: '1', | ||||
| label: ( | label: ( | ||||
| <> | <> | ||||
| <Checkbox> | |||||
| <Checkbox onChange={handleSelectAllCheck} checked={checked}> | |||||
| <b>Select All</b> | <b>Select All</b> | ||||
| </Checkbox> | </Checkbox> | ||||
| </> | </> | ||||
| ), | ), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, []); | |||||
| }, [checked, handleSelectAllCheck]); | |||||
| const content = ( | const content = ( | ||||
| <Menu style={{ width: 200 }} items={items} selectable={false} /> | <Menu style={{ width: 200 }} items={items} selectable={false} /> | ||||
| ); | ); | ||||
| const handleFilterChange = (e: RadioChangeEvent) => { | |||||
| dispatch({ type: 'chunkModel/setAvailable', payload: e.target.value }); | |||||
| getChunkList(); | |||||
| }; | |||||
| const filterContent = ( | |||||
| <Radio.Group onChange={handleFilterChange} value={available}> | |||||
| <Space direction="vertical"> | |||||
| <Radio value={undefined}>All</Radio> | |||||
| <Radio value={1}>Enabled</Radio> | |||||
| <Radio value={0}>Disabled</Radio> | |||||
| </Space> | |||||
| </Radio.Group> | |||||
| ); | |||||
| return ( | return ( | ||||
| <Flex justify="space-between" align="center"> | <Flex justify="space-between" align="center"> | ||||
| <Space> | |||||
| <ArrowLeftOutlined /> | |||||
| <Space size={'middle'}> | |||||
| <Link | |||||
| to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`} | |||||
| > | |||||
| <ArrowLeftOutlined /> | |||||
| </Link> | |||||
| <FilePdfOutlined /> | <FilePdfOutlined /> | ||||
| xxx.pdf | |||||
| {documentInfo.name} | |||||
| </Space> | </Space> | ||||
| <Space> | <Space> | ||||
| {/* <Select | |||||
| defaultValue="lucy" | |||||
| style={{ width: 100 }} | |||||
| popupMatchSelectWidth={false} | |||||
| optionRender={() => null} | |||||
| dropdownRender={(menu) => ( | |||||
| <div style={{ width: 300 }}> | |||||
| {menu} | |||||
| <Menu | |||||
| // onClick={onClick} | |||||
| style={{ width: 256 }} | |||||
| // defaultSelectedKeys={['1']} | |||||
| // defaultOpenKeys={['sub1']} | |||||
| // mode="inline" | |||||
| items={actionItems} | |||||
| /> | |||||
| </div> | |||||
| )} | |||||
| ></Select> */} | |||||
| <Popover content={content} placement="bottomLeft" arrow={false}> | |||||
| <Popover content={content} placement="bottom" arrow={false}> | |||||
| <Button> | <Button> | ||||
| Bulk | Bulk | ||||
| <DownOutlined /> | <DownOutlined /> | ||||
| </Button> | </Button> | ||||
| </Popover> | </Popover> | ||||
| <Button icon={<SearchOutlined />} /> | <Button icon={<SearchOutlined />} /> | ||||
| <Button icon={<FilterIcon />} /> | |||||
| <Popover content={filterContent} placement="bottom" arrow={false}> | |||||
| <Button icon={<FilterIcon />} /> | |||||
| </Popover> | |||||
| <Button icon={<DeleteOutlined />} /> | <Button icon={<DeleteOutlined />} /> | ||||
| <Button icon={<PlusOutlined />} type="primary" /> | <Button icon={<PlusOutlined />} type="primary" /> | ||||
| </Space> | </Space> |
| .chunkPage { | .chunkPage { | ||||
| padding: 24px; | |||||
| padding: 24px; | |||||
| display: flex; | |||||
| height: calc(100vh - 112px); | |||||
| flex-direction: column; | |||||
| display: flex; | |||||
| // height: calc(100vh - 112px); | |||||
| flex-direction: column; | |||||
| .filter { | |||||
| margin: 10px 0; | |||||
| display: flex; | |||||
| height: 32px; | |||||
| justify-content: space-between; | |||||
| } | |||||
| .filter { | |||||
| margin: 10px 0; | |||||
| display: flex; | |||||
| height: 32px; | |||||
| justify-content: space-between; | |||||
| } | |||||
| .pageContent { | |||||
| flex: 1; | |||||
| width: 100%; | |||||
| padding-right: 12px; | |||||
| overflow-y: auto; | |||||
| .pageContent { | |||||
| flex: 1; | |||||
| width: 100%; | |||||
| padding-right: 12px; | |||||
| overflow-y: auto; | |||||
| .spin { | |||||
| min-height: 400px; | |||||
| } | |||||
| .spin { | |||||
| min-height: 400px; | |||||
| } | } | ||||
| } | |||||
| .pageFooter { | |||||
| height: 32px; | |||||
| } | |||||
| .pageFooter { | |||||
| height: 32px; | |||||
| } | |||||
| } | } | ||||
| .container { | .container { | ||||
| height: 100px; | |||||
| height: 100px; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| justify-content: space-between; | |||||
| .content { | |||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | |||||
| justify-content: space-between; | justify-content: space-between; | ||||
| .content { | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| .context { | |||||
| flex: 1; | |||||
| // width: 207px; | |||||
| height: 88px; | |||||
| overflow: hidden; | |||||
| } | |||||
| .context { | |||||
| flex: 1; | |||||
| // width: 207px; | |||||
| height: 88px; | |||||
| overflow: hidden; | |||||
| } | } | ||||
| } | |||||
| .footer { | |||||
| height: 20px; | |||||
| .footer { | |||||
| height: 20px; | |||||
| .text { | |||||
| margin-left: 10px; | |||||
| } | |||||
| .text { | |||||
| margin-left: 10px; | |||||
| } | } | ||||
| } | |||||
| } | } | ||||
| .card { | .card { | ||||
| :global { | |||||
| .ant-card-body { | |||||
| padding: 10px; | |||||
| margin: 0; | |||||
| } | |||||
| margin-bottom: 10px; | |||||
| :global { | |||||
| .ant-card-body { | |||||
| padding: 10px; | |||||
| margin: 0; | |||||
| } | } | ||||
| cursor: pointer; | |||||
| margin-bottom: 10px; | |||||
| } | |||||
| } | |||||
| cursor: pointer; | |||||
| } |
| import { api_host } from '@/utils/api'; | |||||
| import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; | import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; | ||||
| import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons'; | |||||
| import type { PaginationProps } from 'antd'; | import type { PaginationProps } from 'antd'; | ||||
| import { | |||||
| Button, | |||||
| Card, | |||||
| Col, | |||||
| Input, | |||||
| Pagination, | |||||
| Popconfirm, | |||||
| Row, | |||||
| Select, | |||||
| Spin, | |||||
| Switch, | |||||
| } from 'antd'; | |||||
| import { Button, Input, Pagination, Space, Spin } from 'antd'; | |||||
| import { debounce } from 'lodash'; | import { debounce } from 'lodash'; | ||||
| import React, { useCallback, useEffect, useState } from 'react'; | import React, { useCallback, useEffect, useState } from 'react'; | ||||
| import { useDispatch, useSearchParams, useSelector } from 'umi'; | import { useDispatch, useSearchParams, useSelector } from 'umi'; | ||||
| import CreateModal from './components/createModal'; | import CreateModal from './components/createModal'; | ||||
| import ChunkCard from './components/chunk-card'; | |||||
| import ChunkToolBar from './components/chunk-toolbar'; | import ChunkToolBar from './components/chunk-toolbar'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import { ChunkModelState } from './model'; | |||||
| interface PayloadType { | interface PayloadType { | ||||
| doc_id: string; | doc_id: string; | ||||
| keywords?: string; | keywords?: string; | ||||
| available_int?: number; | |||||
| } | } | ||||
| const Chunk = () => { | const Chunk = () => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const chunkModel = useSelector((state: any) => state.chunkModel); | |||||
| const chunkModel: ChunkModelState = useSelector( | |||||
| (state: any) => state.chunkModel, | |||||
| ); | |||||
| const [keywords, SetKeywords] = useState(''); | const [keywords, SetKeywords] = useState(''); | ||||
| const [available_int, setAvailableInt] = useState(-1); | |||||
| const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]); | |||||
| const [searchParams] = useSearchParams(); | const [searchParams] = useSearchParams(); | ||||
| const [pagination, setPagination] = useState({ page: 1, size: 30 }); | |||||
| const { data = [], total, chunk_id, isShowCreateModal } = chunkModel; | |||||
| const { | |||||
| data = [], | |||||
| total, | |||||
| chunk_id, | |||||
| isShowCreateModal, | |||||
| pagination, | |||||
| } = chunkModel; | |||||
| const effects = useSelector((state: any) => state.loading.effects); | const effects = useSelector((state: any) => state.loading.effects); | ||||
| const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [ | const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [ | ||||
| 'create_hunk', | 'create_hunk', | ||||
| ]); | ]); | ||||
| const documentId: string = searchParams.get('doc_id') || ''; | const documentId: string = searchParams.get('doc_id') || ''; | ||||
| const getChunkList = (value?: string) => { | |||||
| const getChunkList = () => { | |||||
| const payload: PayloadType = { | const payload: PayloadType = { | ||||
| doc_id: documentId, | doc_id: documentId, | ||||
| keywords: value || keywords, | |||||
| available_int, | |||||
| }; | }; | ||||
| if (payload.available_int === -1) { | |||||
| delete payload.available_int; | |||||
| } | |||||
| dispatch({ | dispatch({ | ||||
| type: 'chunkModel/chunk_list', | type: 'chunkModel/chunk_list', | ||||
| payload: { | payload: { | ||||
| ...payload, | ...payload, | ||||
| ...pagination, | |||||
| }, | }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| const confirm = async (id: string) => { | const confirm = async (id: string) => { | ||||
| const retcode = await dispatch<any>({ | const retcode = await dispatch<any>({ | ||||
| type: 'chunkModel/rm_chunk', | type: 'chunkModel/rm_chunk', | ||||
| getChunkList(); | getChunkList(); | ||||
| }; | }; | ||||
| const onShowSizeChange: PaginationProps['onShowSizeChange'] = ( | |||||
| const onPaginationChange: PaginationProps['onShowSizeChange'] = ( | |||||
| page, | page, | ||||
| size, | size, | ||||
| ) => { | ) => { | ||||
| setPagination({ page, size }); | |||||
| }; | |||||
| const switchChunk = async (id: string, available_int: boolean) => { | |||||
| const retcode = await dispatch<any>({ | |||||
| type: 'chunkModel/switch_chunk', | |||||
| setSelectedChunkIds([]); | |||||
| dispatch({ | |||||
| type: 'chunkModel/setPagination', | |||||
| payload: { | payload: { | ||||
| chunk_ids: [id], | |||||
| available_int: Number(available_int), | |||||
| doc_id: documentId, | |||||
| current: page, | |||||
| pageSize: size, | |||||
| }, | }, | ||||
| }); | }); | ||||
| retcode === 0 && getChunkList(); | |||||
| getChunkList(); | |||||
| }; | }; | ||||
| const selectAllChunk = useCallback( | |||||
| (checked: boolean) => { | |||||
| setSelectedChunkIds(checked ? data.map((x) => x.chunk_id) : []); | |||||
| // setSelectedChunkIds((previousIds) => { | |||||
| // return checked ? [...previousIds, ...data.map((x) => x.chunk_id)] : []; | |||||
| // }); | |||||
| }, | |||||
| [data], | |||||
| ); | |||||
| const handleSingleCheckboxClick = useCallback( | |||||
| (chunkId: string, checked: boolean) => { | |||||
| setSelectedChunkIds((previousIds) => { | |||||
| const idx = previousIds.findIndex((x) => x === chunkId); | |||||
| const nextIds = [...previousIds]; | |||||
| if (checked && idx === -1) { | |||||
| nextIds.push(chunkId); | |||||
| } else if (!checked && idx !== -1) { | |||||
| nextIds.splice(idx, 1); | |||||
| } | |||||
| return nextIds; | |||||
| }); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getChunkList(); | getChunkList(); | ||||
| }, [documentId, available_int, pagination]); | |||||
| return () => { | |||||
| dispatch({ | |||||
| type: 'chunkModel/resetFilter', // TODO: need to reset state uniformly | |||||
| }); | |||||
| }; | |||||
| }, [documentId]); | |||||
| const debounceChange = debounce(getChunkList, 300); | const debounceChange = debounce(getChunkList, 300); | ||||
| const debounceCallback = useCallback( | const debounceCallback = useCallback( | ||||
| const handleInputChange = ( | const handleInputChange = ( | ||||
| e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, | e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, | ||||
| ) => { | ) => { | ||||
| setSelectedChunkIds([]); | |||||
| const value = e.target.value; | const value = e.target.value; | ||||
| SetKeywords(value); | SetKeywords(value); | ||||
| debounceCallback(value); | debounceCallback(value); | ||||
| }; | }; | ||||
| const handleSelectChange = (value: number) => { | |||||
| setAvailableInt(value); | |||||
| }; | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <div className={styles.chunkPage}> | <div className={styles.chunkPage}> | ||||
| <ChunkToolBar></ChunkToolBar> | |||||
| <ChunkToolBar | |||||
| getChunkList={getChunkList} | |||||
| selectAllChunk={selectAllChunk} | |||||
| checked={selectedChunkIds.length === data.length} | |||||
| ></ChunkToolBar> | |||||
| <div className={styles.filter}> | <div className={styles.filter}> | ||||
| <div> | <div> | ||||
| <Input | <Input | ||||
| allowClear | allowClear | ||||
| onChange={handleInputChange} | onChange={handleInputChange} | ||||
| /> | /> | ||||
| <Select | |||||
| showSearch | |||||
| placeholder="是否启用" | |||||
| optionFilterProp="children" | |||||
| value={available_int} | |||||
| onChange={handleSelectChange} | |||||
| style={{ width: 220 }} | |||||
| options={[ | |||||
| { | |||||
| value: -1, | |||||
| label: '全部', | |||||
| }, | |||||
| { | |||||
| value: 1, | |||||
| label: '启用', | |||||
| }, | |||||
| { | |||||
| value: 0, | |||||
| label: '未启用', | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| </div> | </div> | ||||
| <Button | <Button | ||||
| onClick={() => { | onClick={() => { | ||||
| </div> | </div> | ||||
| <div className={styles.pageContent}> | <div className={styles.pageContent}> | ||||
| <Spin spinning={loading} className={styles.spin} size="large"> | <Spin spinning={loading} className={styles.spin} size="large"> | ||||
| <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }}> | |||||
| {data.map((item: any) => { | |||||
| return ( | |||||
| <Col | |||||
| className="gutter-row" | |||||
| key={item.chunk_id} | |||||
| xs={24} | |||||
| sm={12} | |||||
| md={12} | |||||
| lg={8} | |||||
| > | |||||
| <Card | |||||
| className={styles.card} | |||||
| onClick={() => { | |||||
| handleEditchunk(item.chunk_id); | |||||
| }} | |||||
| > | |||||
| <img | |||||
| style={{ width: '50px' }} | |||||
| src={`${api_host}/document/image/${item.img_id}`} | |||||
| alt="" | |||||
| /> | |||||
| <div className={styles.container}> | |||||
| <div className={styles.content}> | |||||
| <span className={styles.context}> | |||||
| {item.content_ltks} | |||||
| </span> | |||||
| <span className={styles.delete}> | |||||
| <Switch | |||||
| size="small" | |||||
| defaultValue={item.available_int == '1'} | |||||
| onChange={(checked: boolean, e: any) => { | |||||
| e.stopPropagation(); | |||||
| e.nativeEvent.stopImmediatePropagation(); | |||||
| switchChunk(item.chunk_id, checked); | |||||
| }} | |||||
| /> | |||||
| </span> | |||||
| </div> | |||||
| <div className={styles.footer}> | |||||
| <span className={styles.text}> | |||||
| <MinusSquareOutlined /> | |||||
| {item.doc_num}文档 | |||||
| </span> | |||||
| <span className={styles.text}> | |||||
| <MinusSquareOutlined /> | |||||
| {item.chunk_num}个 | |||||
| </span> | |||||
| <span className={styles.text}> | |||||
| <MinusSquareOutlined /> | |||||
| {item.token_num}千字符 | |||||
| </span> | |||||
| <span style={{ float: 'right' }}> | |||||
| <Popconfirm | |||||
| title="Delete the task" | |||||
| description="Are you sure to delete this task?" | |||||
| onConfirm={(e: any) => { | |||||
| e.stopPropagation(); | |||||
| e.nativeEvent.stopImmediatePropagation(); | |||||
| console.log(confirm); | |||||
| confirm(item.chunk_id); | |||||
| }} | |||||
| okText="Yes" | |||||
| cancelText="No" | |||||
| > | |||||
| <DeleteOutlined | |||||
| onClick={(e) => { | |||||
| e.stopPropagation(); | |||||
| e.nativeEvent.stopImmediatePropagation(); | |||||
| }} | |||||
| /> | |||||
| </Popconfirm> | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| </Card> | |||||
| </Col> | |||||
| ); | |||||
| })} | |||||
| </Row> | |||||
| <Space direction="vertical" size={'middle'}> | |||||
| {data.map((item) => ( | |||||
| <ChunkCard | |||||
| item={item} | |||||
| key={item.chunk_id} | |||||
| checked={selectedChunkIds.some((x) => x === item.chunk_id)} | |||||
| handleCheckboxClick={handleSingleCheckboxClick} | |||||
| ></ChunkCard> | |||||
| ))} | |||||
| </Space> | |||||
| </Spin> | </Spin> | ||||
| </div> | </div> | ||||
| <div className={styles.pageFooter}> | <div className={styles.pageFooter}> | ||||
| showLessItems | showLessItems | ||||
| showQuickJumper | showQuickJumper | ||||
| showSizeChanger | showSizeChanger | ||||
| onChange={onShowSizeChange} | |||||
| defaultPageSize={30} | |||||
| pageSizeOptions={[30, 60, 90]} | |||||
| defaultCurrent={pagination.page} | |||||
| onChange={onPaginationChange} | |||||
| defaultPageSize={10} | |||||
| pageSizeOptions={[10, 30, 60, 90]} | |||||
| defaultCurrent={pagination.current} | |||||
| total={total} | total={total} | ||||
| /> | /> | ||||
| </div> | </div> |
| import { BaseState } from '@/interfaces/common'; | |||||
| import { IKnowledgeFile } from '@/interfaces/database/knowledge'; | |||||
| import kbService from '@/services/kbService'; | import kbService from '@/services/kbService'; | ||||
| import { message } from 'antd'; | |||||
| // import { delay } from '@/utils/storeUtil'; | |||||
| import { DvaModel } from 'umi'; | import { DvaModel } from 'umi'; | ||||
| export interface ChunkModelState { | |||||
| export interface ChunkModelState extends BaseState { | |||||
| data: any[]; | data: any[]; | ||||
| total: number; | total: number; | ||||
| isShowCreateModal: boolean; | isShowCreateModal: boolean; | ||||
| chunk_id: string; | chunk_id: string; | ||||
| doc_id: string; | doc_id: string; | ||||
| chunkInfo: any; | chunkInfo: any; | ||||
| documentInfo: Partial<IKnowledgeFile>; | |||||
| available?: number; | |||||
| } | } | ||||
| const model: DvaModel<ChunkModelState> = { | const model: DvaModel<ChunkModelState> = { | ||||
| chunk_id: '', | chunk_id: '', | ||||
| doc_id: '', | doc_id: '', | ||||
| chunkInfo: {}, | chunkInfo: {}, | ||||
| documentInfo: {}, | |||||
| pagination: { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| searchString: '', | |||||
| available: undefined, // set to undefined to select all | |||||
| }, | }, | ||||
| reducers: { | reducers: { | ||||
| updateState(state, { payload }) { | updateState(state, { payload }) { | ||||
| ...payload, | ...payload, | ||||
| }; | }; | ||||
| }, | }, | ||||
| setAvailable(state, { payload }) { | |||||
| return { ...state, available: payload }; | |||||
| }, | |||||
| setSearchString(state, { payload }) { | |||||
| return { ...state, searchString: payload }; | |||||
| }, | |||||
| setPagination(state, { payload }) { | |||||
| return { ...state, pagination: { ...state.pagination, ...payload } }; | |||||
| }, | |||||
| resetFilter(state, { payload }) { | |||||
| return { | |||||
| ...state, | |||||
| pagination: { | |||||
| current: 1, | |||||
| pageSize: 10, | |||||
| }, | |||||
| searchString: '', | |||||
| available: undefined, | |||||
| }; | |||||
| }, | |||||
| }, | }, | ||||
| // subscriptions: { | |||||
| // setup({ dispatch, history }) { | |||||
| // history.listen(location => { | |||||
| // console.log(location) | |||||
| // }); | |||||
| // } | |||||
| // }, | |||||
| effects: { | effects: { | ||||
| *chunk_list({ payload = {} }, { call, put }) { | |||||
| const { data, response } = yield call(kbService.chunk_list, payload); | |||||
| const { retcode, data: res, retmsg } = data; | |||||
| *chunk_list({ payload = {} }, { call, put, select }) { | |||||
| const { available, searchString, pagination }: ChunkModelState = | |||||
| yield select((state: any) => state.chunkModel); | |||||
| const { data } = yield call(kbService.chunk_list, { | |||||
| ...payload, | |||||
| available_int: available, | |||||
| keywords: searchString, | |||||
| page: pagination.current, | |||||
| size: pagination.pageSize, | |||||
| }); | |||||
| const { retcode, data: res } = data; | |||||
| if (retcode === 0) { | if (retcode === 0) { | ||||
| console.log(res); | |||||
| yield put({ | yield put({ | ||||
| type: 'updateState', | type: 'updateState', | ||||
| payload: { | payload: { | ||||
| data: res.chunks, | data: res.chunks, | ||||
| total: res.total, | total: res.total, | ||||
| documentInfo: res.doc, | |||||
| }, | }, | ||||
| }); | }); | ||||
| } | } | ||||
| }, | }, | ||||
| *switch_chunk({ payload = {} }, { call, put }) { | *switch_chunk({ payload = {} }, { call, put }) { | ||||
| const { data, response } = yield call(kbService.switch_chunk, payload); | |||||
| const { retcode, data: res, retmsg } = data; | |||||
| const { data } = yield call(kbService.switch_chunk, payload); | |||||
| const { retcode } = data; | |||||
| if (retcode === 0) { | |||||
| message.success('Modified successfully !'); | |||||
| } | |||||
| return retcode; | return retcode; | ||||
| }, | }, | ||||
| *rm_chunk({ payload = {} }, { call, put }) { | *rm_chunk({ payload = {} }, { call, put }) { |
| data: IKnowledgeFile[]; | data: IKnowledgeFile[]; | ||||
| total: number; | total: number; | ||||
| currentRecord: Nullable<IKnowledgeFile>; | currentRecord: Nullable<IKnowledgeFile>; | ||||
| searchString: string; | |||||
| } | } | ||||
| const model: DvaModel<KFModelState> = { | const model: DvaModel<KFModelState> = { |
| UserOutlined, | UserOutlined, | ||||
| } from '@ant-design/icons'; | } from '@ant-design/icons'; | ||||
| import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd'; | import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd'; | ||||
| import { MouseEvent } from 'react'; | |||||
| import { useDispatch, useNavigate } from 'umi'; | import { useDispatch, useNavigate } from 'umi'; | ||||
| import showDeleteConfirm from '@/components/deleting-confirm'; | import showDeleteConfirm from '@/components/deleting-confirm'; | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const removeKnowledge = () => { | |||||
| return dispatch({ | |||||
| type: 'knowledgeModel/rmKb', | |||||
| payload: { | |||||
| kb_id: item.id, | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const handleDelete = () => { | const handleDelete = () => { | ||||
| showDeleteConfirm({ onOk: removeKnowledge }); | showDeleteConfirm({ onOk: removeKnowledge }); | ||||
| }; | }; | ||||
| } | } | ||||
| }; | }; | ||||
| const removeKnowledge = () => { | |||||
| return dispatch({ | |||||
| type: 'knowledgeModel/rmKb', | |||||
| payload: { | |||||
| kb_id: item.id, | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| const handleCardClick = (e: MouseEvent<HTMLElement>) => { | |||||
| const handleCardClick = () => { | |||||
| navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${item.id}`); | navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${item.id}`); | ||||
| }; | }; | ||||
| (effectName) => effects[`${namespace}/${effectName}`], | (effectName) => effects[`${namespace}/${effectName}`], | ||||
| ); | ); | ||||
| }; | }; | ||||
| export const delay = (ms: number) => | |||||
| new Promise((resolve) => { | |||||
| setTimeout(resolve, ms); | |||||
| }); |