index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import { ChatContext } from '@/app/chat-context';
  2. import { apiInterceptors, delSpace, getSpaceConfig, getSpaceList, newDialogue } from '@/client/api';
  3. import DocPanel from '@/components/knowledge/doc-panel';
  4. import DocTypeForm from '@/components/knowledge/doc-type-form';
  5. import DocUploadForm from '@/components/knowledge/doc-upload-form';
  6. import Segmentation from '@/components/knowledge/segmentation';
  7. import SpaceForm from '@/components/knowledge/space-form';
  8. import BlurredCard, { ChatButton, InnerDropdown } from '@/new-components/common/blurredCard';
  9. import ConstructLayout from '@/new-components/layout/Construct';
  10. import { File, ISpace, IStorage, StepChangeParams } from '@/types/knowledge';
  11. import { PlusOutlined, ReadOutlined, SearchOutlined, WarningOutlined } from '@ant-design/icons';
  12. import { Avatar, Button, Input, Modal, Spin, Steps, Tag } from 'antd';
  13. import classNames from 'classnames';
  14. import { debounce } from 'lodash';
  15. import moment from 'moment';
  16. import { useRouter } from 'next/router';
  17. import { useContext, useEffect, useState } from 'react';
  18. import { useTranslation } from 'react-i18next';
  19. const Knowledge = () => {
  20. const { setCurrentDialogInfo } = useContext(ChatContext);
  21. const [spaceList, setSpaceList] = useState<Array<ISpace> | null>([]);
  22. const [isAddShow, setIsAddShow] = useState<boolean>(false);
  23. const [isPanelShow, setIsPanelShow] = useState<boolean>(false);
  24. const [currentSpace, setCurrentSpace] = useState<ISpace>();
  25. const [activeStep, setActiveStep] = useState<number>(0);
  26. const [spaceName, setSpaceName] = useState<string>('');
  27. const [files, setFiles] = useState<Array<File>>([]);
  28. const [docType, setDocType] = useState<string>('');
  29. const [addStatus, setAddStatus] = useState<string>('');
  30. const [loading, setLoading] = useState<boolean>(false);
  31. const [spaceConfig, setSpaceConfig] = useState<IStorage | null>(null);
  32. const { t } = useTranslation();
  33. const addKnowledgeSteps = [
  34. { title: t('Knowledge_Space_Config') },
  35. { title: t('Choose_a_Datasource_type') },
  36. { title: t('Upload') },
  37. { title: t('Segmentation') },
  38. ];
  39. const router = useRouter();
  40. async function getSpaces(params?: any) {
  41. setLoading(true);
  42. const [_, data] = await apiInterceptors(getSpaceList({ ...params }));
  43. setLoading(false);
  44. setSpaceList([...data, ...data, ...data, ...data, ...data, ...data, ...data,]);
  45. }
  46. async function getSpaceConfigs() {
  47. const [_, data] = await apiInterceptors(getSpaceConfig());
  48. if (!data) return null;
  49. setSpaceConfig(data.storage);
  50. }
  51. useEffect(() => {
  52. getSpaces();
  53. getSpaceConfigs();
  54. }, []);
  55. const handleChat = async (space: ISpace) => {
  56. const [_, data] = await apiInterceptors(
  57. newDialogue({
  58. chat_mode: 'chat_knowledge',
  59. }),
  60. );
  61. // 知识库对话都默认私有知识库应用下
  62. if (data?.conv_uid) {
  63. setCurrentDialogInfo?.({
  64. chat_scene: data.chat_mode,
  65. app_code: data.chat_mode,
  66. });
  67. localStorage.setItem(
  68. 'cur_dialog_info',
  69. JSON.stringify({
  70. chat_scene: data.chat_mode,
  71. app_code: data.chat_mode,
  72. }),
  73. );
  74. router.push(`/chat?scene=chat_knowledge&id=${data?.conv_uid}&knowledge_id=${space.name}`);
  75. }
  76. };
  77. const handleStepChange = ({ label, spaceName, docType, files }: StepChangeParams) => {
  78. if (label === 'finish') {
  79. setIsAddShow(false);
  80. getSpaces();
  81. setSpaceName('');
  82. setDocType('');
  83. setAddStatus('finish');
  84. localStorage.removeItem('cur_space_id');
  85. } else if (label === 'forward') {
  86. activeStep === 0 && getSpaces();
  87. setActiveStep(step => step + 1);
  88. } else {
  89. setActiveStep(step => step - 1);
  90. }
  91. files && setFiles(files);
  92. spaceName && setSpaceName(spaceName);
  93. docType && setDocType(docType);
  94. };
  95. function onAddDoc(spaceName: string) {
  96. setSpaceName(spaceName);
  97. setActiveStep(1);
  98. setIsAddShow(true);
  99. setAddStatus('start');
  100. }
  101. const showDeleteConfirm = (space: ISpace) => {
  102. Modal.confirm({
  103. title: t('Tips'),
  104. icon: <WarningOutlined />,
  105. content: `${t('Del_Knowledge_Tips')}?`,
  106. okText: 'Yes',
  107. okType: 'danger',
  108. cancelText: 'No',
  109. async onOk() {
  110. await apiInterceptors(delSpace({ name: space?.name }));
  111. getSpaces();
  112. },
  113. });
  114. };
  115. const onSearch = async (e: any) => {
  116. getSpaces({ name: e.target.value });
  117. };
  118. return (
  119. <ConstructLayout>
  120. <div className='page-body h-[90vh] overflow-aut mr-8 bg-[#fff]'>
  121. <div className='mt-2 rounded-[10px] flex h-16 justify-between items-center px-4'>
  122. <Input
  123. variant='filled'
  124. prefix={<SearchOutlined />}
  125. placeholder={t('please_enter_the_keywords')}
  126. allowClear
  127. className='w-[400px] h-[40px]
  128. border-1 border-[#d1d1d1]
  129. backdrop-filter
  130. backdrop-blur-lg
  131. dark:border-[#6f7f95]
  132. dark:bg-[#6f7f95]
  133. dark:bg-opacity-60'
  134. />
  135. <span className='flex gap-2 items-center'>
  136. <Avatar className='bg-gradient-to-tr from-[#31afff] to-[#1677ff] cursor-pointer'>
  137. </Avatar>
  138. <span
  139. >
  140. admin
  141. </span>
  142. </span>
  143. </div>
  144. <div className='flex justify-between items-center mb-6'>
  145. <div className='flex items-center gap-4'>
  146. </div>
  147. <div className='flex items-center gap-4'>
  148. <Button
  149. className='border-none text-white bg-button-gradient'
  150. onClick={() => {
  151. setIsAddShow(true);
  152. }}
  153. >
  154. 创建知识库
  155. </Button>
  156. </div>
  157. </div>
  158. <div className='rounded-[10px] mx-4 w-full mr-14 flex flex-wrap pb-12 bg-slate-200 p-4 border-1 mb-2'>
  159. {spaceList?.map((space: ISpace) => (
  160. <BlurredCard
  161. style={{ height: '200px!important'}}
  162. onClick={() => {
  163. setCurrentSpace(space);
  164. setIsPanelShow(true);
  165. localStorage.setItem('cur_space_id', JSON.stringify(space.id));
  166. }}
  167. description={space.desc}
  168. name={space.name}
  169. key={space.id}
  170. logo={
  171. space.domain_type === 'FinancialReport'
  172. ? '/models/fin_report.jpg'
  173. : space.vector_type === 'KnowledgeGraph'
  174. ? '/models/knowledge-graph.png'
  175. : space.vector_type === 'FullText'
  176. ? '/models/knowledge-full-text.jpg'
  177. : '/models/knowledge-default.jpg'
  178. }
  179. RightTop={
  180. <InnerDropdown
  181. menu={{
  182. items: [
  183. {
  184. key: 'del',
  185. label: (
  186. <span className='text-red-400' onClick={() => showDeleteConfirm(space)}>
  187. {t('Delete')}
  188. </span>
  189. ),
  190. },
  191. ],
  192. }}
  193. />
  194. }
  195. rightTopHover={false}
  196. Tags={
  197. <div className='flex item-center'>
  198. <Tag>
  199. <span className='flex items-center gap-1'>
  200. <ReadOutlined className='mt-[1px]' />
  201. {space.docs}
  202. </span>
  203. </Tag>
  204. <Tag>
  205. <span className='flex items-center gap-1'>{space.domain_type || 'Normal'}</span>
  206. </Tag>
  207. {space.vector_type ? (
  208. <Tag>
  209. <span className='flex items-center gap-1'>{space.vector_type}</span>
  210. </Tag>
  211. ) : null}
  212. </div>
  213. }
  214. LeftBottom={
  215. <div className='flex gap-2'>
  216. <span>{space.owner}</span>
  217. <span>•</span>
  218. {space?.gmt_modified && <span>{moment(space?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
  219. </div>
  220. }
  221. RightBottom={
  222. <ChatButton
  223. text={t('start_chat')}
  224. onClick={() => {
  225. handleChat(space);
  226. }}
  227. />
  228. }
  229. />
  230. ))}
  231. </div>
  232. </div>
  233. <Modal
  234. className='h-5/6 overflow-hidden'
  235. open={isPanelShow}
  236. width={'70%'}
  237. onCancel={() => setIsPanelShow(false)}
  238. footer={null}
  239. destroyOnClose={true}
  240. >
  241. <DocPanel space={currentSpace!} onAddDoc={onAddDoc} onDeleteDoc={getSpaces} addStatus={addStatus} />
  242. </Modal>
  243. <Modal
  244. title={t('New_knowledge_base')}
  245. centered
  246. open={isAddShow}
  247. destroyOnClose={true}
  248. onCancel={() => {
  249. setIsAddShow(false);
  250. }}
  251. width={1000}
  252. afterClose={() => {
  253. setActiveStep(0);
  254. getSpaces();
  255. }}
  256. footer={null}
  257. >
  258. <Steps current={activeStep} items={addKnowledgeSteps} />
  259. {activeStep === 0 && <SpaceForm handleStepChange={handleStepChange} spaceConfig={spaceConfig} />}
  260. {activeStep === 1 && <DocTypeForm handleStepChange={handleStepChange} />}
  261. <DocUploadForm
  262. className={classNames({ hidden: activeStep !== 2 })}
  263. spaceName={spaceName}
  264. docType={docType}
  265. handleStepChange={handleStepChange}
  266. />
  267. {activeStep === 3 && (
  268. <Segmentation
  269. spaceName={spaceName}
  270. docType={docType}
  271. uploadFiles={files}
  272. handleStepChange={handleStepChange}
  273. />
  274. )}
  275. </Modal>
  276. </ConstructLayout>
  277. );
  278. };
  279. export default Knowledge;