浏览代码

新增 知识库可查看 PDF

sunsheng 1 月之前
父节点
当前提交
d8626909dc
共有 2 个文件被更改,包括 449 次插入2 次删除
  1. 364 0
      src/pages/deepseek/knowledgeLib/detail/drawerIndex.tsx
  2. 85 2
      src/pages/deepseek/questionAnswer/info/index.tsx

+ 364 - 0
src/pages/deepseek/knowledgeLib/detail/drawerIndex.tsx

@@ -0,0 +1,364 @@
+import * as React from 'react';
+import { generatePath, useParams, useLocation } from 'react-router-dom';
+import { observer } from 'mobx-react';
+
+import config, { getHeaders } from '@/apis/config';
+
+import {
+  Button,
+  Table,
+  TableColumnsType,
+  Modal,
+  TablePaginationConfig,
+  Upload,
+  UploadProps,
+  message,
+  Spin
+} from 'antd';
+import { EditOutlined, DeleteOutlined, InboxOutlined, PlusOutlined, ArrowLeftOutlined } from '@ant-design/icons';
+import InfoModal from './components/InfoModal';
+import InfoModalSetting from './components/InfoModalSetting';
+import router from '@/router';
+import { Record } from './types';
+import dayjs from 'dayjs';
+import axios from 'axios';
+import LocalStorage from '@/LocalStorage';
+import store from './store';
+import './style.less';
+
+const { Dragger } = Upload;
+interface Props{
+    drawerItem: any;
+}
+
+const KnowledgeLibInfo : React.FC<Props> = ({drawerItem}:Props) => {
+  const params = {
+    knowledgeId: drawerItem.value,
+    ...drawerItem,
+  }
+
+  const {
+    state,
+    init,
+    onClickModify,
+    onClickDelete,
+    onChangePagination,
+    onClickDocumentDetail,
+    infoModalOnClickConfirm,
+    infoModalOnClickCancel,
+    infoModalSettingOnClickConfirm,
+    infoModalSettingOnClickCancel,
+    onClickSettings,
+    reset
+  } = store;
+  const {
+    knowledge_id,
+    listLoading,
+    page,
+    list,
+    infoModalOpen,
+    infoModalId,
+    infoModalSettingOpen,
+    infoModalSettingId,
+    knowledgeDetail,
+  } = state;
+  const location = useLocation();
+  const [ uploadLoading, setUploadLoading ] = React.useState( false );
+
+
+  const [ fileList, setFileList ] = React.useState<any[]>( [] );
+  const [ uploading, setUploading ] = React.useState( false );
+
+  const [ sListFlag, setSListFlag ] = React.useState<boolean>();
+  const [ cUpdateFlag, setCUpdateFlag ] = React.useState<boolean>();
+  const [ detailFlag, setDetailFlag ] = React.useState<boolean>();
+  const [ deleteFlag, setDeleteFlag ] = React.useState<boolean>();
+  const [ createFlag, setCreateFlag ] = React.useState<boolean>();
+  const [userInfoAll, setUserInfoAll] = React.useState<any>({});
+
+  const props : UploadProps = {
+    name: 'files',
+    multiple: true,
+    action: '/api/deepseek/api/uploadDocument/' + params.knowledgeId,
+    headers: getHeaders(),
+    beforeUpload( file, fileList ) {
+      setUploadLoading( true );
+      // const allowedExtensions = ['md', 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'docx', 'xlsx', 'pptx', 'eml', 'csv', 'tar', 'gz', 'bz2', 'zip', 'rar', 'jar'];
+
+      const allowedExtensions = [ 'txt', 'pdf', 'jpg', 'png', 'jpeg', 'doc', 'docx', 'ppt', 'pptx' ];
+
+      // 检查文件类型
+      for ( const file of fileList ) {
+        const fileExt = file.name.split( '.' ).pop()?.toLowerCase();
+        if ( !fileExt || !allowedExtensions.includes( fileExt ) ) {
+          message.error( `不支持 ${ fileExt } 格式的文件上传` );
+          setUploadLoading( false );
+          return Upload.LIST_IGNORE;
+        }
+      }
+
+      // 检查文件大小
+      let totalSize = 0;
+      for ( const file of fileList ) {
+        const fileExt = file.name.split( '.' ).pop()?.toLowerCase();
+        const fileSizeMB = file.size / 1024 / 1024;
+
+        if ( fileSizeMB > 30 ) {
+          message.error( '单个文件不能大于30M' );
+          setUploadLoading( false );
+          return Upload.LIST_IGNORE;
+        }
+
+        if ( [ 'jpg', 'png', 'jpeg' ].includes( fileExt! ) && fileSizeMB > 5 ) {
+          message.error( '单张图片不能大于5M' );
+          setUploadLoading( false );
+          return Upload.LIST_IGNORE;
+        }
+
+        totalSize += fileSizeMB;
+      }
+
+      if ( totalSize > 125 ) {
+        message.error( '文件总大小超过125M' );
+        setUploadLoading( false );
+        return Upload.LIST_IGNORE;
+      }
+
+    },
+    onChange( info ) {
+      const { status } = info.file;
+
+      if ( status !== 'uploading' ) {
+        console.log( status, 'status--uploading' );
+      }
+      if ( status === 'done' ) {
+        console.log( status, 'status--done' );
+        console.info( info.file.response, 'info.file.response.data' );
+        if ( info.file.response.code === 200 && info.file.response.data === 1 ) {
+          message.success( `${ info.file.name } file uploaded successfully.` );
+          init( params.knowledgeId );
+        }
+        setUploadLoading( false );
+      } else if ( status === 'error' ) {
+        console.log( status, 'status--error' );
+        message.error( `${ info.file.name } file upload failed.` );
+        setUploadLoading( false );
+      }
+    },
+    onDrop( e ) {
+      console.log( 'Dropped files', e.dataTransfer.files );
+    },
+  };
+
+  const handleUpload = async () => {
+    if ( fileList.length === 0 ) return;
+
+    setUploading( true );
+    const formData = new FormData();
+
+    // 添加所有文件
+    fileList.forEach( file => {
+      if ( file.originFileObj ) {
+        formData.append( 'files', file.originFileObj );
+      }
+    } );
+
+    try {
+      const res = await axios.post( '/api/deepseek/api/uploadDocument/' + params.knowledgeId, formData, {
+        headers: { 'Content-Type': 'multipart/form-data' }
+      } );
+
+      message.success( `${ fileList.length }个文件上传成功` );
+      setFileList( [] );
+    } catch ( err ) {
+      message.error( '上传失败' );
+    } finally {
+      setUploading( false );
+    }
+  };
+
+  React.useEffect( () => {
+    init( params.knowledgeId );
+    const cList = LocalStorage.getStatusFlag( 'deepseek:slice:list' );
+    setSListFlag( cList );
+    const cDetail = LocalStorage.getStatusFlag( 'deepseek:config:update' );
+    setCUpdateFlag( cDetail );
+    const detail = LocalStorage.getStatusFlag( 'deepseek:document:detail' );
+    setDetailFlag( detail );
+    const deleteF = LocalStorage.getStatusFlag( 'deepseek:document:delete' );
+    setDeleteFlag( deleteF );
+    const createF = LocalStorage.getStatusFlag( 'deepseek:document:create' );
+    setCreateFlag( createF );
+    setUserInfoAll(LocalStorage.getUserInfo());
+    return () => reset();
+  }, [params.knowledgeId] );
+
+  const columns : TableColumnsType<Record> = [
+    {
+      title: '序号',
+      dataIndex: 'index',
+      width: 80,
+      render: ( text, record, index ) => {
+        return index + 1;
+      }
+    },
+    {
+      title: '文件名',
+      dataIndex: 'name',
+      width: 300,
+      sorter: (a, b) => a.name.localeCompare(b.name),
+      render: ( text, record ) => {
+        return (
+            `${ text }`
+        )
+      }
+    },
+    {
+      title: '文件大小',
+      dataIndex: 'length',
+      width: 100,
+      render: ( text ) => {
+        if ( text ) {
+          const size = ( text / 1024 / 1024 ).toFixed( 2 );
+          return `${ size } M`;
+        } else {
+          return '--'
+        }
+      }
+    },
+    {
+      title: '字符数量',
+      dataIndex: 'wordNum',
+      width: 100,
+      render: ( text ) => {
+        if ( text ) {
+          return `${ text }`;
+        } else {
+          return '--'
+        }
+      }
+    },
+    {
+      title: '分段',
+      dataIndex: 'sliceTotal',
+      width: 100,
+      render: ( text ) => {
+        if ( text ) {
+          return `${ text }`;
+        } else {
+          return '--';
+        }
+      }
+    },
+    {
+      title: '上传时间',
+      dataIndex: 'createTime',
+      width: 180,
+      render: ( text ) => {
+        if ( text ) {
+          return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
+        } else {
+          return '--';
+        }
+      }
+    },
+    {
+      title: '更新时间',
+      dataIndex: 'updateTime',
+      width: 180,
+      render: ( text ) => {
+        if ( text ) {
+          return dayjs( text ).format( 'YYYY-MM-DD HH:mm:ss' );
+        } else {
+          return '--';
+        }
+      }
+    },
+    {
+      title: '操作',
+      dataIndex: 'operation',
+      width: 80,
+      fixed: 'right',
+      render: ( text, record:any ) => {
+        return (
+            <>
+              {
+                  <a
+                      style={ { marginRight: 16 } }
+                      onClick={ () => {
+                        // window.location= record.url
+                        window.open(`${record.url}`, '_blank');
+                        // window.open(`https://10.1.28.14:9000/deepseek-doc/%E6%95%B0%E5%AD%97%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E5%B9%B3%E5%8F%B0%E7%94%A8%E6%88%B7%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C_%E9%83%A8%E5%88%863.pdf`, '_blank');
+                      } }
+                  >
+                    查看
+                  </a>
+              }
+            </>
+        )
+      }
+    }
+  ];
+
+  const paginationConfig : TablePaginationConfig = {
+    // 显示数据总量
+    showTotal: ( total : number ) => {
+      return `共 ${ total } 条`;
+    },
+    // 展示分页条数切换
+    showSizeChanger: true,
+    // 指定每页显示条数
+    pageSizeOptions: [ '10', '20', '50', '100' ],
+    // 快速跳转至某页
+    showQuickJumper: true,
+    current: page.page,
+    pageSize: page.size,
+    total: page.total,
+    onChange: async ( page, pageSize ) => {
+      await onChangePagination( page, pageSize );
+    },
+  };
+
+  return (
+      <div className='knowledgeLibInfo'>
+        <Spin spinning={ uploadLoading || listLoading }>
+          {
+              <>
+                <div className='knowledgeLibInfo-table'>
+                  <Table
+                      scroll={ { x: 'max-content' } }
+                      rowKey={ ( record ) => record.documentId }
+                      loading={ listLoading }
+                      columns={ columns }
+                      dataSource={ list }
+                      pagination={ paginationConfig }
+                  />
+                </div>
+
+                {
+                    infoModalOpen &&
+                    <InfoModal
+                        id={ infoModalId }
+                        open={ infoModalOpen }
+                        onClickConfirm={ infoModalOnClickConfirm }
+                        onClickCancel={ infoModalOnClickCancel }
+                    />
+                }
+
+                {
+                    infoModalSettingOpen &&
+                    <InfoModalSetting
+                        id={ infoModalSettingId }
+                        open={ infoModalSettingOpen }
+                        onClickConfirm={ infoModalSettingOnClickConfirm }
+                        onClickCancel={ infoModalSettingOnClickCancel }
+                    />
+                }
+              </>
+          }
+        </Spin>
+      </div>
+  );
+};
+
+export default observer( KnowledgeLibInfo );

+ 85 - 2
src/pages/deepseek/questionAnswer/info/index.tsx

@@ -7,16 +7,17 @@ import {
     Radio, Switch, Row, Col, Slider, Space, RadioChangeEvent,
     Spin, message, Typography, Tooltip,
     Cascader,
-    Tag, Modal, Table,TablePaginationConfig
+    Tag, Modal, Table,TablePaginationConfig,Drawer
 } from 'antd';
 import type { TableProps } from 'antd';
 
-import { PlusCircleOutlined, MinusCircleOutlined, ArrowLeftOutlined, InfoCircleOutlined,CloseCircleOutlined } from '@ant-design/icons';
+import { PlusCircleOutlined, MinusCircleOutlined, ArrowLeftOutlined, InfoCircleOutlined,CloseCircleOutlined,LinkOutlined } from '@ant-design/icons';
 import { apis } from '@/apis';
 import router from '@/router';
 import LocalStorage from '@/LocalStorage';
 import Chat from '@/components/chat';
 import store from './store';
+import DrawerIndex from '@/pages/deepseek/knowledgeLib/detail/drawerIndex'
 
 const { TextArea } = Input;
 const FormItem = Form.Item;
@@ -113,6 +114,7 @@ const QuestionAnswerInfo: React.FC = () => {
     type KnowledgeList = {
         label: string,
         value: string,
+        createBy: string,
     }[];
 
     type AppTypeList = {
@@ -123,6 +125,49 @@ const QuestionAnswerInfo: React.FC = () => {
             value: string,
         }[],
     }[];
+    const tagRender = (props:any) => {
+        const { label, value, closable, onClose } = props;
+        return (
+            <Tag
+                color="blue"
+                closable={closable}
+                onClose={onClose} // 保留原有的删除事件
+                style={{ marginRight: 4 }}
+            >
+                {label}
+                {/* 在删除按钮后添加自定义图标 */}
+                <LinkOutlined style={{ 
+                    marginLeft: 4, 
+                    fontSize: 12, 
+                    cursor: 'pointer',
+                }}
+                 onMouseDown={(e) => {
+                    e.stopPropagation();
+                    e.preventDefault();
+                 }}
+                 onMouseUp={(e) => {
+                    e.stopPropagation();
+                    e.preventDefault();
+                 }}
+                // 自定义图标点击事件
+                onClick={(e) => {
+                    // 阻止事件冒泡到Tag,避免触发删除
+                    e.stopPropagation();
+                    e.preventDefault();
+                    knowledgeList.forEach((item) => {
+                        if (item.value === value) {
+                            // router.navigate({ pathname: `/deepseek/knowledgeLib/${value}/${item.createBy}`,},);
+                            setDrawerItem(item);
+                            setOpenDrawer(true)
+                            e.stopPropagation();
+                        }
+                    });
+                    // console.log('点击了额外图标,当前选项值:', value,props);
+                    // 这里可以添加你的业务逻辑,如:打开详情、编辑等
+                }} />
+            </Tag>
+        );
+    };
 
     const [step, setStep] = React.useState(1);
     const [pageLoading, setPageLoading] = React.useState(false);
@@ -318,6 +363,7 @@ const QuestionAnswerInfo: React.FC = () => {
                     appProId: info.appProId?.join('-'),// 项目
                     typeId: info.typeId, //应用类型
                     visible: info.visible || '0', //是否公开
+                    sort: info.sort || null, //显示顺序
                     param_desc: data_info.param_desc, //回答风格
                     show_recall_result: data_info.show_recall_result, //是否展示召回结果
                     recall_method: data_info.recall_method, //召回方式
@@ -353,6 +399,7 @@ const QuestionAnswerInfo: React.FC = () => {
                     return {
                         label: item.name,
                         value: item.knowledgeId,
+                        createBy: item.createBy,
                     }
                 });
 
@@ -544,6 +591,7 @@ const QuestionAnswerInfo: React.FC = () => {
                 max_token: values.max_token, //应用最大token
                 typeId: values.typeId, // 应用类型
                 visible: values.visible, // 是否公开
+                sort: values.sort||null, // 显示顺序
                 vipList: vipList, // vip用户列表
                 appProId: values?.appProId?.[0]?.split('-') ?? null,// 项目
                 userId: userId, // 用户id
@@ -713,6 +761,33 @@ const QuestionAnswerInfo: React.FC = () => {
     /*
         选择VIP用户弹窗end
     */
+   /*
+    查看引用知识库抽屉start
+   */        
+    const [openDrawer, setOpenDrawer] = React.useState(false);
+    const [drawerItem,setDrawerItem] = React.useState<any>({});
+    const onCloseDrawer = () => {
+        setOpenDrawer(false);
+    }
+
+    const DrawerDetail = ()=>{
+        return (
+             <Drawer
+                title={drawerItem?.label}
+                width={'80%'}
+                closable={{ 'aria-label': 'Close Button' }}
+                onClose={onCloseDrawer}
+                open={openDrawer}
+                style={{zIndex:11111}}
+            >
+                <DrawerIndex drawerItem={drawerItem}></DrawerIndex>
+            </Drawer>
+        )
+    }
+
+   /*
+    查看引用知识库抽屉end
+   */
     return (
         <>
             <div className='questionAnswerInfo'>
@@ -800,6 +875,12 @@ const QuestionAnswerInfo: React.FC = () => {
                                     }
                                 </Select>
                             </FormItem>
+                            <FormItem
+                                label='显示顺序'
+                                name='sort'
+                            >
+                                <InputNumber placeholder="请输入显示顺序" value={''} className='form-element-standard' style={{ height: '48px',lineHeight:'48px'}}/>
+                            </FormItem>
                             {/* VIP用户 */}
                             {visibleFlag==1&&<FormItem
                                 label='指定用户'
@@ -1013,6 +1094,7 @@ const QuestionAnswerInfo: React.FC = () => {
                                                         showSearch={true}
                                                         className='form-element-select'
                                                         placeholder='请选择需要引用的知识库'
+                                                        tagRender={tagRender}
                                                     >
                                                         {
                                                             knowledgeList.map((item, index) => {
@@ -1280,6 +1362,7 @@ const QuestionAnswerInfo: React.FC = () => {
                 </Spin>
             </div >
             {isModalOpen && vipModal()}
+            {DrawerDetail()}
         </>
     );
 };