index.tsx 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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 { 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);
  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. <Spin spinning={loading}>
  121. <div className='page-body p-4 md:p-6 h-[90vh] overflow-auto'>
  122. {/* <Button
  123. type="primary"
  124. className="flex items-center"
  125. icon={<PlusOutlined />}
  126. onClick={() => {
  127. setIsAddShow(true);
  128. }}
  129. >
  130. Create
  131. </Button> */}
  132. <div className='flex justify-between items-center mb-6'>
  133. <div className='flex items-center gap-4'>
  134. <Input
  135. variant='filled'
  136. prefix={<SearchOutlined />}
  137. placeholder={t('please_enter_the_keywords')}
  138. onChange={debounce(onSearch, 300)}
  139. allowClear
  140. className='w-[230px] h-[40px] border-1 border-white backdrop-filter backdrop-blur-lg bg-white bg-opacity-30 dark:border-[#6f7f95] dark:bg-[#6f7f95] dark:bg-opacity-60'
  141. />
  142. </div>
  143. <div className='flex items-center gap-4'>
  144. <Button
  145. className='border-none text-white bg-button-gradient'
  146. icon={<PlusOutlined />}
  147. onClick={() => {
  148. setIsAddShow(true);
  149. }}
  150. >
  151. {t('create_knowledge')}
  152. </Button>
  153. </div>
  154. </div>
  155. <div className='flex flex-wrap mt-4 mx-[-8px]'>
  156. {spaceList?.map((space: ISpace) => (
  157. <BlurredCard
  158. onClick={() => {
  159. setCurrentSpace(space);
  160. setIsPanelShow(true);
  161. localStorage.setItem('cur_space_id', JSON.stringify(space.id));
  162. }}
  163. description={space.desc}
  164. name={space.name}
  165. key={space.id}
  166. logo={
  167. space.domain_type === 'FinancialReport'
  168. ? '/models/fin_report.jpg'
  169. : space.vector_type === 'KnowledgeGraph'
  170. ? '/models/knowledge-graph.png'
  171. : space.vector_type === 'FullText'
  172. ? '/models/knowledge-full-text.jpg'
  173. : '/models/knowledge-default.jpg'
  174. }
  175. RightTop={
  176. <InnerDropdown
  177. menu={{
  178. items: [
  179. {
  180. key: 'del',
  181. label: (
  182. <span className='text-red-400' onClick={() => showDeleteConfirm(space)}>
  183. {t('Delete')}
  184. </span>
  185. ),
  186. },
  187. ],
  188. }}
  189. />
  190. }
  191. rightTopHover={false}
  192. Tags={
  193. <div className='flex item-center'>
  194. <Tag>
  195. <span className='flex items-center gap-1'>
  196. <ReadOutlined className='mt-[1px]' />
  197. {space.docs}
  198. </span>
  199. </Tag>
  200. <Tag>
  201. <span className='flex items-center gap-1'>{space.domain_type || 'Normal'}</span>
  202. </Tag>
  203. {space.vector_type ? (
  204. <Tag>
  205. <span className='flex items-center gap-1'>{space.vector_type}</span>
  206. </Tag>
  207. ) : null}
  208. </div>
  209. }
  210. LeftBottom={
  211. <div className='flex gap-2'>
  212. <span>{space.owner}</span>
  213. <span>•</span>
  214. {space?.gmt_modified && <span>{moment(space?.gmt_modified).fromNow() + ' ' + t('update')}</span>}
  215. </div>
  216. }
  217. RightBottom={
  218. <ChatButton
  219. text={t('start_chat')}
  220. onClick={() => {
  221. handleChat(space);
  222. }}
  223. />
  224. }
  225. />
  226. ))}
  227. </div>
  228. </div>
  229. <Modal
  230. className='h-5/6 overflow-hidden'
  231. open={isPanelShow}
  232. width={'70%'}
  233. onCancel={() => setIsPanelShow(false)}
  234. footer={null}
  235. destroyOnClose={true}
  236. >
  237. <DocPanel space={currentSpace!} onAddDoc={onAddDoc} onDeleteDoc={getSpaces} addStatus={addStatus} />
  238. </Modal>
  239. <Modal
  240. title={t('New_knowledge_base')}
  241. centered
  242. open={isAddShow}
  243. destroyOnClose={true}
  244. onCancel={() => {
  245. setIsAddShow(false);
  246. }}
  247. width={1000}
  248. afterClose={() => {
  249. setActiveStep(0);
  250. getSpaces();
  251. }}
  252. footer={null}
  253. >
  254. <Steps current={activeStep} items={addKnowledgeSteps} />
  255. {activeStep === 0 && <SpaceForm handleStepChange={handleStepChange} spaceConfig={spaceConfig} />}
  256. {activeStep === 1 && <DocTypeForm handleStepChange={handleStepChange} />}
  257. <DocUploadForm
  258. className={classNames({ hidden: activeStep !== 2 })}
  259. spaceName={spaceName}
  260. docType={docType}
  261. handleStepChange={handleStepChange}
  262. />
  263. {activeStep === 3 && (
  264. <Segmentation
  265. spaceName={spaceName}
  266. docType={docType}
  267. uploadFiles={files}
  268. handleStepChange={handleStepChange}
  269. />
  270. )}
  271. </Modal>
  272. </Spin>
  273. </ConstructLayout>
  274. );
  275. };
  276. export default Knowledge;