Przeglądaj źródła

知识接口联调

S0025136190 9 miesięcy temu
rodzic
commit
04a87ae1dc

+ 136 - 0
src/apis/index.ts

@@ -1,3 +1,4 @@
+import { create } from 'domain';
 import api from './api';
 
 // Api参数类型
@@ -22,6 +23,57 @@ export type CreateOrModifyKnowledgeLibApiParams = {
     description: string,
 };
 
+export type FetchAppIndexParams = {
+    pageNumber: number,
+    pageSize: number,
+};
+
+export type CreateOrModifyApplicationApiParams = {
+    name: string, 
+    desc: string,  
+    prompt: string,
+    temperature: number, 
+    top_p: number,  
+    knowledge_ids: string[], 
+    param_desc: string, 
+    max_token: number, 
+    knowledge_info: { 
+        model: string, 
+        knowledge_ids: string[], 
+        slice_config_type: string, 
+        recall_method: string, 
+        slice_count: number, 
+        rerank_status: number, 
+        rerank_model_name: string, 
+        show_recall_result: boolean, 
+        recall_slice_splicing_method: string,
+    },
+    questionList :[
+        {
+            question: string,
+        }
+    ]
+};
+
+export type FetchDocumentLibListApiParams = {
+    knowledge_id: string,
+    page: number,
+    size: number,
+}
+
+export type ModifyDocumentApiParams = {
+    id: string,
+    name: string,
+    url: string,
+    length: number,
+    sentence_size: number,
+    knowledge_type: number,
+    custom_separator: string[],
+    embedding_stat: number,
+    word_num: number,
+    parse_image: number
+};
+
 // Api函数类型
 export type LoginApi = (data: LoginApiParams) => Promise<any>;
 export type LogoutApi = () => Promise<any>;
@@ -32,6 +84,18 @@ export type CreateKnowledgeLibApi = (data: CreateOrModifyKnowledgeLibApiParams)
 export type ModifyKnowledgeLibApi = (knowledgeId: string, data: CreateOrModifyKnowledgeLibApiParams) => Promise<any>;
 export type DeleteKnowledgeLibApi = (knowledgeId: string) => Promise<any>;
 export type FetchKnowledgeLibDetailApi = (knowledgeId: string) => Promise<any>;
+export type FetchAppIndexApi = (data: FetchAppIndexParams) => Promise<any>;
+export type FetchApplicationDetailApi = (appId: string) => Promise<any>;
+export type FetchModelListApi = () => Promise<any>;
+export type FetchKnowledgeApiListApi = () => Promise<any>;
+export type ModifyApplicationLibApi = (appId: string, data: CreateOrModifyApplicationApiParams) => Promise<any>;
+export type DeleteApplicationLibApi = (appId: string) => Promise<any>;
+export type CreateApplicationLibApi = (data: CreateOrModifyApplicationApiParams) => Promise<any>;
+export type uploadFileLibApi = (konwledgeId: string) => Promise<any>;
+export type FetchDocumentLibListApi = (data: FetchDocumentLibListApiParams) => Promise<any>;
+export type FetchDocumentLibApi = (id: string) => Promise<any>;
+export type ModifyDocumentLibApi = (id: string, data: ModifyDocumentApiParams) => Promise<any>;
+export type DeleteDocumentLibApi = (id: string) => Promise<any>;
 
 // 登录
 const loginApi: LoginApi = async (data) => {
@@ -78,6 +142,66 @@ const fetchKnowledgeLibDetailApi: FetchKnowledgeLibDetailApi = async (knowledgeI
     return api.get(`bigmodel/api/detailKnowledge/${knowledgeId}`);
 };
 
+// 获取首页信息
+const fetchIndexApi: FetchAppIndexApi = async (data) => {
+    return api.post('/bigmodel/api/index', data);
+};
+
+// 编辑应用
+const fetchApplicationDetailApi: FetchApplicationDetailApi = async (appId) => {
+    return api.get(`bigmodel/api/selectApplication/${appId}`);
+};
+
+// 模型列表
+const fetchModelListApi: FetchModelListApi = async () => {
+    return api.get('/bigmodel/api/model/list');
+};
+
+// 知识库列表
+const fetchKnowledgeListApi: FetchKnowledgeApiListApi = async () => {
+    return api.get('/bigmodel/api/applicationList');
+};
+
+// 编辑应用
+const modifyApplicationLibApi: ModifyApplicationLibApi = async (appId, data) => {
+    return api.put(`bigmodel/api/updateApplication/${appId}`, data);
+};
+
+// 删除应用
+const deleteApplicationLibApi: DeleteApplicationLibApi = async (appId) => {
+    return api.delete(`bigmodel/api/delApplication/${appId}`);
+};
+
+// 创建应用
+const createApplicationLibApi: CreateApplicationLibApi = async (data) => {
+    return api.post('bigmodel/api/createApplication', data);
+};
+
+// 上传知识文件
+const uploadFileLibApi: uploadFileLibApi = async (konwledgeId) => {
+    return api.post(`bigmodel/api/uploadDocument/${konwledgeId}`);
+};
+
+// 获取知识列表
+const fetchDocumentLibListApi: FetchDocumentLibListApi = async (data) => {
+    return api.post('bigmodel/api/documentList', data);
+};
+
+// 知识详情
+const fetchDocumentLibApi: FetchDocumentLibApi = async (id) => {
+    return api.get(`bigmodel/api/documentDetail/${id}`);
+};
+
+// 修改知识
+const modifyDocumentApi: ModifyDocumentLibApi = async (id, data) => {
+    return api.put(`bigmodel/api/updateDocument/${id}`, data);
+};
+
+// 删除知识文件
+const deleteDocumentApi: DeleteDocumentLibApi = async (id) => {
+    return api.delete(`bigmodel/api/delDocument/${id}`);
+};
+
 export const apis = {
     login: loginApi,
     logout: logoutApi,
@@ -88,4 +212,16 @@ export const apis = {
     modifyKnowledgeLib: modifyKnowledgeLibApi,
     deleteKnowledgeLib: deleteKnowledgeLibApi,
     fetchKnowledgeLibDetail: fetchKnowledgeLibDetailApi,
+    fetchIndexCount: fetchIndexApi,
+    fetchApplicationDetail: fetchApplicationDetailApi,
+    fetchModelList: fetchModelListApi,
+    fetchKnowledgeList: fetchKnowledgeListApi,
+    modifyApplicationApi: modifyApplicationLibApi,
+    deleteApplicationApi: deleteApplicationLibApi,
+    createApplicationApi: createApplicationLibApi,
+    uploadFileLibApi: uploadFileLibApi,
+    fetchDocumentLibListApi: fetchDocumentLibListApi,
+    fetchDocumentDetailLibApi: fetchDocumentLibApi,
+    modifyDocumentLibApi: modifyDocumentApi,
+    deleteDocumentLibApi: deleteDocumentApi,
 };

+ 117 - 0
src/pages/knowledgeLib/detail/components/InfoModal.tsx

@@ -0,0 +1,117 @@
+import * as React from 'react';
+import { Modal, Spin, Form, Input, Select, message } from 'antd';
+import { apis, ModifyDocumentApiParams } from '@/apis';
+
+const FormItem = Form.Item;
+const { Option } = Select;
+
+interface Props {
+    id: string,
+    open: boolean,
+    onClickConfirm: (id: string, data: ModifyDocumentApiParams) => Promise<any>,
+    onClickCancel: () => void,
+};
+
+const InfoModal: React.FC<Props> = (props: Props) => {
+    const {
+        id,
+        open,
+        onClickConfirm,
+        onClickCancel
+    } = props;
+
+    const [form] = Form.useForm();
+
+    const [loading, setLoading] = React.useState<boolean>(false);
+
+    // 获取知识详情
+    const fetchDetail = async () => {
+        try {
+            const res = await apis.fetchDocumentDetailLibApi(props.id);
+            const { id, name, knowledge_type, custom_separator, sentence_size } = res.data;
+            form.setFieldsValue({
+                id: id,
+                name: name,
+                knowledge_type: knowledge_type,
+                custom_separator: custom_separator,
+                sentence_size: sentence_size,
+            });
+        } catch (error: any) {
+            message.error(error.msg);
+        }
+    };
+
+    const init = async () => {
+        setLoading(true);
+        if (props.id) {
+            await fetchDetail();
+        }
+        setLoading(false);
+    };
+
+    React.useEffect(() => {
+        init();
+    }, []);
+
+    // 点击确定
+    const handleClickConfirm = () => {
+        form.validateFields().then(async (values) => {
+            const data = { ...values };
+            if(values.knowledge_type === '5'){
+                data.custom_separator = [ '"\\n"'];
+                data.sentence_size = 300;
+            }else{
+                data.sentence_size = 20;
+                data.custom_separator = [ '"\\n"'];
+            }
+            console.log(data, 'data');
+            await onClickConfirm(props.id, data);
+        }).catch((error) => {
+            console.error(error);
+        });
+    };
+
+    return (
+        <Modal
+            width={500}
+            title='修改知识数据'
+            destroyOnClose={true}
+            maskClosable={false}
+            centered={true}
+            open={open}
+            onOk={handleClickConfirm}
+            onCancel={onClickCancel}
+        >
+            <Spin spinning={loading}>
+                <Form form={form} layout='vertical'>
+                    <FormItem
+                        label='文件名称'
+                        name='name'
+                    >
+                        <Input placeholder='请输入知识库名称' readOnly/>
+                    </FormItem>
+                    <FormItem
+                        label='知识类型'
+                        name='knowledge_type'
+                        rules={[{ required: true, message: '知识类型不能为空' }]}
+                    >
+                        <Select
+                            style={{ width: '100%' }}
+                            placeholder='请选择知识类型'
+                            allowClear={true}
+                        >
+                            <Option value='1'>1:文章知识:支持pdf,url,docx</Option>
+                            <Option value='2'>2:问答知识-文档:支持pdf,url,docx</Option>
+                            <Option value='3'>3:问答知识-表格:支持xlsx</Option>
+                            <Option value='4'>4:商品库-表格:支持xlsx</Option>
+                            <Option value='5'>5:自定义:支持pdf,url,docx</Option>
+                        </Select>
+                    </FormItem>
+                    <div style={{ width: '100%', height: 10 }}></div>
+                </Form>
+            </Spin>
+        </Modal>
+    );
+};
+
+export default InfoModal;

+ 229 - 2
src/pages/knowledgeLib/detail/index.tsx

@@ -3,27 +3,254 @@ import { useParams } from 'react-router-dom';
 import { observer } from 'mobx-react';
 import store from './store';
 import './style.less';
+import { Button, Table, TableColumnsType, Modal, TablePaginationConfig, Upload, UploadProps, message } from 'antd';
+import { EditOutlined, DeleteOutlined, InboxOutlined, PlusOutlined } from '@ant-design/icons';
+import InfoModal from './components/InfoModal';
+import { Record } from './types';
+import dayjs from 'dayjs';
+
+const { Dragger } = Upload;
+
+
 
 const KnowledgeLibInfo: React.FC = () => {
     const {
         state,
         init,
+        onClickModify,
+        onClickDelete,
+        onChangePagination,
+        onClickDocumentDetail,
+        infoModalOnClickConfirm,
+        infoModalOnClickCancel,
         reset
     } = store;
     const {
-        pageLoading
+        knowledge_id,
+        listLoading,
+        page,
+        list,
+        infoModalOpen,
+        infoModalId,
     } = state;
 
     const params = useParams();
 
+    const props: UploadProps = {
+        name: 'file',
+        multiple: true,
+        action: '/api/bigmodel/api/uploadDocument/' + params.knowledgeId,
+        onChange(info) {
+            const { status } = info.file;
+            if (status !== 'uploading') {
+                console.log(info.file, info.fileList);
+            }
+            if (status === 'done') {
+                message.success(`${info.file.name} file uploaded successfully.`);
+                init(params.knowledgeId);
+            } else if (status === 'error') {
+                message.error(`${info.file.name} file upload failed.`);
+            }
+        },
+        onDrop(e) {
+            console.log('Dropped files', e.dataTransfer.files);
+        },
+    };
+
     React.useEffect(() => {
         init(params.knowledgeId);
         return () => reset();
     }, []);
 
+    const columns: TableColumnsType<Record> = [
+        {
+            title: '序号',
+            dataIndex: 'index',
+            width: 80,
+            render: (text, record, index) => {
+                return index + 1;
+            }
+        },
+        {
+            title: '文件名',
+            dataIndex: 'name',
+            render: (text, record) => {
+                return (
+                    `${text}`
+                )
+            }
+        },
+        {
+            title: '文件大小',
+            dataIndex: 'length',
+            render: (text) => {
+                if(text){
+                    const size = (text / 1024 / 1024).toFixed(2);
+                    return `${size}M`;
+                }
+            }
+        },
+        {
+            title: '字符数量',
+            dataIndex: 'word_num',
+            render: (text) => {
+                return `${text}`;
+            }
+        },
+        {
+            title: '分段',
+            dataIndex: 'custom_separator',
+            width: 200,
+            render: (text) => {
+                return `${text}`;
+            }
+        },
+        {
+            title: '上传时间',
+            dataIndex: 'create_time',
+            width: 200,
+            render: (text) => {
+                if (text) {
+                    return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
+                } else {
+                    return '--';
+                }
+            }
+        },
+        {
+            title: '更新时间',
+            dataIndex: 'update_time',
+            width: 200,
+            render: (text) => {
+                if (text) {
+                    return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
+                } else {
+                    return '--';
+                }
+            }
+        },
+        {
+            title: '操作',
+            dataIndex: 'operation',
+            width: 150,
+            fixed: 'right',
+            render: (text, record) => {
+                return (
+                    <>
+                        <a
+                            style={{ marginRight: 16 }}
+                            onClick={() => {
+                                onClickModify(record.id);
+                            }}
+                        >
+                            <EditOutlined />
+                        </a >
+                        <a
+                            className='text-error'
+                            onClick={() => {
+                                Modal.confirm({
+                                    title: '删除',
+                                    content: `确定删除知识文件:${record.name}吗?`,
+                                    okType: 'danger',
+                                    onOk: async () => {
+                                        await onClickDelete(record.id);
+                                    }
+                                });
+                            }}
+                        >
+                            <DeleteOutlined />
+                        </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'>
-            创建/修改-知识库
+            <div className='knowledgeLibList-operation'>
+                {
+                    page.total === 0 &&
+                    <>
+                        {/* <Button
+                            type='primary'
+                            icon={<FileOutlined />}
+                            onClick={() => {
+                                onClickDocumentDetail(params.knowledgeId)
+                            }}
+                        >
+                            文档
+                        </Button> */}
+                        <div style={{ marginTop: 20, width: '100%', height: '200px' }}>
+                            <Dragger {...props}>
+                                <p className="ant-upload-drag-icon">
+                                    <InboxOutlined />
+                                </p>
+                                <p >
+                                    点击上传,或拖放文件到此处
+                                </p >
+                                <p className="ant-upload-hint">
+                                    仅支持上传pdf文档,文档大小不超过50M.
+                                </p>
+                            </Dragger>
+                        </div>
+                    </>
+
+                }
+            </div>
+            {
+                page.total > 0 &&
+                <>
+                    <div style={{ justifyContent: 'space-between', alignItems: 'center', display: 'flex' }}>
+                        <div>
+
+                        </div>
+                        <div>
+                            <Upload {...props}>
+                                <Button type='primary' icon={<PlusOutlined />}>上传知识文件</Button>
+                            </Upload>
+                        </div>
+                    </div>
+
+
+                    <Table
+                        scroll={{ x: 'max-content' }}
+                        rowKey={(record) => record.id}
+                        loading={listLoading}
+                        columns={columns}
+                        dataSource={list}
+                        pagination={paginationConfig}
+                    />
+                    {
+                        infoModalOpen &&
+                        <InfoModal
+                            id={infoModalId}
+                            open={infoModalOpen}
+                            onClickConfirm={infoModalOnClickConfirm}
+                            onClickCancel={infoModalOnClickCancel}
+                        />
+                    }
+                </>
+            }
         </div>
     );
 };

+ 148 - 12
src/pages/knowledgeLib/detail/store.ts

@@ -1,44 +1,180 @@
-import { makeAutoObservable } from 'mobx';
-import { apis } from '@/apis';
-import { State, ReadonlyState, StateAction, KnowledgeLibInfoStore } from './types';
+import { action, makeAutoObservable } from 'mobx';
+import { message } from 'antd';
+import { apis, ModifyDocumentApiParams } from '@/apis';
+import { State, ReadonlyState, StateAction, DocumentLibInfoStore } from './types';
 
 // 定义状态
 const stateGenerator = (): ReadonlyState => ({
-    pageLoading: false,
+    knowledge_id: '',
+    listLoading: false,
+    list: [],
+    infoModalId: '',
+    infoModalOpen: false,
+    page: {
+        page: 1,
+        size: 10,
+        total: 0,
+    },
 });
 
 // 修改状态
 const stateActionsGenerator = (state: State): StateAction => {
     return {
-        setPageLoading: (loading) => {
-            state.pageLoading = loading;
+        setknowledge_id(knowledge_id) {
+            state.knowledge_id = knowledge_id;
+        },
+        setListLoading: (loading) => {
+            state.listLoading = loading;
+        },
+        setList: (list) => {
+            state.list = list;
+        },
+        setInfoModalId: (id) => {
+            state.infoModalId = id;
+        },
+        setInfoModalOpen: (open) => {
+            state.infoModalOpen = open;
+        },
+        setPage: (page) => {
+            state.page = page;
         },
     };
 };
 
 // 使用仓库
-const useKnowledgeLibInfoStore = (): KnowledgeLibInfoStore => {
+const useKnowledgeLibInfoStore = (): DocumentLibInfoStore => {
     const state = makeAutoObservable(stateGenerator());
     const actions = stateActionsGenerator(state);
 
     const api = {
+        // 获取知识列表
+        fetchDocumentLibList: async () => {
+            actions.setListLoading(true);
+            try {
+                console.log('state.knowledge_id', state.knowledge_id);
+                const data = {
+                    knowledge_id: state.knowledge_id,
+                    page: state.page.page,
+                    size: state.page.size,
+                };
+                const res = await apis.fetchDocumentLibListApi(data);
+                actions.setList(res.data.list);
+                actions.setPage({
+                    ...state.page,
+                    total: res.data.total,
+                });
+            } catch (error: any) {
+                console.error(error);
+            } finally {
+                actions.setListLoading(false);
+            }
+        },
+        // 修改知识
+        modifyDocumentLib: async (id: string, data: ModifyDocumentApiParams) => {
+            try {
+                await apis.modifyDocumentLibApi(id, data);
+                // 获取知识列表
+                api.fetchDocumentLibList();
+                message.success('修改成功');
+            } catch (error: any) {
+                message.error(error.msg);
+            }
+        },
+        // 删除知识
+        deleteDocumentLib: async (id: string) => {
+            try {
+                await apis.deleteDocumentLibApi(id);
+                // 获取知识列表
+                api.fetchDocumentLibList();
+                message.success('删除成功');
+            } catch (error: any) {
+                message.error(error.msg);
+            }
+        },
+    }
 
+    // 更改分页
+    const onChangePagination: DocumentLibInfoStore['onChangePagination'] = async (page, size) => {
+        actions.setPage({
+            ...state.page,
+            page: page,
+            size: size,
+        });
+        // 获取知识列表
+        api.fetchDocumentLibList();
     }
 
+    // 点击上传文件
+    const onClickDocumentDetail: DocumentLibInfoStore['onClickDocumentDetail'] = () => {
+        api.fetchDocumentLibList();
+    }
+
+    // 点击修改
+    const onClickModify: DocumentLibInfoStore['onClickModify'] = (id) => {
+        actions.setInfoModalId(id);
+        actions.setInfoModalOpen(true);
+    }
+
+    // 信息弹出层-点击确定
+    const infoModalOnClickConfirm: DocumentLibInfoStore['infoModalOnClickConfirm'] = async (id, data) => {
+        const initialInfoModalOpen = stateGenerator().infoModalOpen;
+
+        actions.setInfoModalOpen(initialInfoModalOpen);
+
+        if (id) {
+            // 修改知识数据
+            api.modifyDocumentLib(id, data);
+        }
+    }
+
+    // 信息弹出层-点击取消
+    const infoModalOnClickCancel: DocumentLibInfoStore['infoModalOnClickCancel'] = () => {
+        const initialInfoModalOpen = stateGenerator().infoModalOpen;
+
+        actions.setInfoModalOpen(initialInfoModalOpen);
+    }
+
+    // 点击删除
+    const onClickDelete: DocumentLibInfoStore['onClickDelete'] = async (id) => {
+        // 删除知识文件
+        await api.deleteDocumentLib(id);
+    }
+
+
     // 初始渲染
-    const init: KnowledgeLibInfoStore['init'] = (knowledgeId) => {
-        console.log(knowledgeId, 'knowledgeId');
+    const init: DocumentLibInfoStore['init'] = (id) => {
+        // 获取知识列表
+        if(id){
+            actions.setknowledge_id(id);
+            api.fetchDocumentLibList();
+        }
     }
 
     // 状态重置
-    const reset: KnowledgeLibInfoStore['reset'] = () => {
-        const initialPageLoading = stateGenerator().pageLoading;
+    const reset: DocumentLibInfoStore['reset'] = () => {
+        const initialListLoading = stateGenerator().listLoading;
+        const initialList = stateGenerator().list;
+        const initialInfoModalId = stateGenerator().infoModalId;
+        const initialInfoModalOpen = stateGenerator().infoModalOpen;
+        const initialPage = stateGenerator().page;
+        const knowledge_id = stateGenerator().knowledge_id;
 
-        actions.setPageLoading(initialPageLoading);
+        actions.setListLoading(initialListLoading);
+        actions.setList(initialList);
+        actions.setInfoModalId(initialInfoModalId);
+        actions.setInfoModalOpen(initialInfoModalOpen);
+        actions.setPage(initialPage);
+        actions.setknowledge_id(knowledge_id);
     }
 
     return {
         state,
+        onChangePagination,
+        onClickDocumentDetail,
+        onClickModify,
+        infoModalOnClickConfirm,
+        infoModalOnClickCancel,
+        onClickDelete,
         init,
         reset
     };

+ 38 - 3
src/pages/knowledgeLib/detail/types.ts

@@ -1,6 +1,30 @@
+import { ModifyDocumentApiParams } from '@/apis';
+
+export type Record = {
+    id: string,
+    name: string,
+    url: string,
+    length: number,
+    sentence_size: number,
+    knowledge_type: number,
+    custom_separator: string[],
+    embedding_stat: number,
+    word_num: number,
+    parse_image: number
+};
+
 // 定义状态
 export type State = {
-    pageLoading: boolean,
+    knowledge_id: string,
+    listLoading: boolean,
+    list: Record[],
+    infoModalId: string,
+    infoModalOpen: boolean,
+    page: {
+        page: number,
+        size: number,
+        total: number,
+    },
 };
 
 // 只读状态
@@ -8,12 +32,23 @@ export type ReadonlyState = Readonly<State>;
 
 // 修改状态
 export type StateAction = {
-    setPageLoading: (loading: boolean) => void,
+    setknowledge_id(knowledge_id: string): unknown;
+    setListLoading: (loading: State['listLoading']) => void,
+    setList: (list: State['list']) => void,
+    setInfoModalId: (id: State['infoModalId']) => void,
+    setInfoModalOpen: (open: State['infoModalOpen']) => void,
+    setPage: (page: State['page']) => void,
 };
 
 // 仓库类型
-export type KnowledgeLibInfoStore = {
+export type DocumentLibInfoStore = {
     state: ReadonlyState,
     init: (knowledgeId?: string) => void,
+    onChangePagination: (page: number, size: number) => Promise<any>,
+    onClickDocumentDetail: (knowledgeId?: string) => void,
+    onClickModify: (id: string) => void,
+    infoModalOnClickConfirm: (id: string, data: ModifyDocumentApiParams) => Promise<any>,
+    infoModalOnClickCancel: () => void,
+    onClickDelete: (id: string) => Promise<any>,
     reset: () => void,
 };

+ 1 - 2
src/pages/knowledgeLib/list/index.tsx

@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
 import { Button, Table, TableColumnsType, Modal, TablePaginationConfig } from 'antd';
 import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
 import InfoModal from './components/InfoModal';
-import router from '@/router';
 import dayjs from 'dayjs';
 import store from './store';
 import { Record } from './types';
@@ -101,7 +100,7 @@ const KnowledgeLibList: React.FC = () => {
         {
             title: '操作',
             dataIndex: 'operation',
-            width: 100,
+            width: 150,
             fixed: 'right',
             render: (text, record) => {
                 return (

+ 613 - 12
src/pages/questionAnswer/info/index.tsx

@@ -1,26 +1,627 @@
 import * as React from 'react';
+import { useLocation } from 'react-router-dom';
 import { observer } from 'mobx-react';
-import store from './store';
 import './style.less';
+import {
+    Button, Input, Form, Divider, Splitter, Select, InputNumber, InputNumberProps,
+    Radio, Switch, Row, Col, Slider, Space, RadioChangeEvent,
+    Spin
+} from 'antd';
+import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
+import { apis } from '@/apis';
+import router from '@/router';
+
+const { TextArea } = Input;
+const FormItem = Form.Item;
+const { Option } = Select;
+const MAX_COUNT = 10;
+const Index = 1;
 
 const QuestionAnswerInfo: React.FC = () => {
-    const {
-        state,
-        init,
-        reset
-    } = store;
-    const {
-        pageLoading
-    } = state;
+
+    const [form] = Form.useForm();
+
+    // top_p
+    const [topPValue, setTopPValue] = React.useState(0.1);
+    const TopPDecimalStep: React.FC = () => {
+
+        const onChange: InputNumberProps['onChange'] = (value) => {
+            if (Number.isNaN(value)) {
+                return;
+            }
+            setTopPValue(value as number);
+        };
+
+        return (
+            <Row>
+                <Col span={12}>
+                    <Slider
+                        min={0}
+                        max={1}
+                        onChange={onChange}
+                        value={typeof topPValue === 'number' ? topPValue : 0}
+                        step={0.1}
+                    />
+                </Col>
+                <Col span={4}>
+                    <InputNumber
+                        min={0}
+                        max={1}
+                        style={{ margin: '0 16px' }}
+                        step={0.01}
+                        value={topPValue}
+                        onChange={onChange}
+                    />
+                </Col>
+            </Row>
+        );
+    };
+    const [tempValue, setTempValue] = React.useState(0.01);
+    // temperature
+    const TempDecimalStep: React.FC = () => {
+
+
+        const onChange: InputNumberProps['onChange'] = (value) => {
+            if (Number.isNaN(value)) {
+                return;
+            }
+            setTempValue(value as number);
+        };
+
+        return (
+            <Row>
+                <Col span={12}>
+                    <Slider
+                        min={0}
+                        max={1}
+                        onChange={onChange}
+                        value={typeof tempValue === 'number' ? tempValue : 0}
+                        step={0.01}
+                    />
+                </Col>
+                <Col span={4}>
+                    <InputNumber
+                        min={0}
+                        max={1}
+                        style={{ margin: '0 16px' }}
+                        step={0.01}
+                        value={tempValue}
+                        onChange={onChange}
+                    />
+                </Col>
+            </Row>
+        );
+    };
+
+    type ModelList = {
+        label: string,
+        value: string,
+    }[];
+    type KnowledgeList = {
+        label: string,
+        value: string,
+    }[];
+
+    const [step, setStep] = React.useState(1);
+    const [pageLoading, setPageLoading] = React.useState(false);
+    const [modelList, setModelList] = React.useState<ModelList>([]);
+    const [knowledgeList, setKnowledgeList] = React.useState<KnowledgeList>([]);
+    const [isVisible, setIsVisible] = React.useState(false);
+    const [isVisibleCus, setIsVisibleCus] = React.useState(false);
+    const [isVisibleSlice, setIsVisibleSlice] = React.useState(false);
+    const [isVisibleRerank, setIsVisibleRerank] = React.useState(false);
+
+    const style: React.CSSProperties = {
+        display: 'flex',
+        flexDirection: 'column',
+        gap: 8,
+    };
+
+    // const suffix = (
+    //     <>
+    //         <span>
+    //             {konwValue.length} / {MAX_COUNT}
+    //         </span>
+    //         <DownOutlined />
+    //     </>
+    // );
+
+    const location = useLocation();
+
+    const init = async (id: string) => {
+        await Promise.all([
+            api.fetchKnowlegde(),
+            api.fetchModelList(),
+        ])
+        if (id) {
+            await api.fetchDetail(id);
+        }
+    }
 
     React.useEffect(() => {
-        init();
-        return () => reset();
+        const id = location?.state?.id;
+        init(id)
     }, []);
 
+    // 初始化一个空数组来存储input框的值
+    const [inputs, setInputs] = React.useState([{ question: '' }]);
+
+    // 添加新的input框的函数
+    const handleAddInput = () => {
+        // setInputs([...inputs, { question: '' }]);
+        setInputs([...inputs]);
+    };
+
+    // 移除最后一个input框的函数
+    const handleRemoveInput = () => {
+        setInputs(inputs.slice(0, inputs.length - 1));
+    };
+
+    const onChange: InputNumberProps['onChange'] = (value) => {
+        console.log('changed', value);
+    };
+
+    const onChangeShow = (checked: boolean) => {
+        console.log(`switch to ${checked}`);
+    };
+
+    const onChangeModel = (checked: boolean) => {
+        setIsVisibleRerank(!isVisibleRerank);
+    };
+
+    const onChangeCount = (value: string) => {
+        if (value === 'fixed') {
+            setIsVisibleSlice(!isVisibleSlice);
+        } else {
+            setIsVisibleSlice(false);
+        }
+    };
+
+    // 召回方式
+    const onChangeRecallMethod = (e: RadioChangeEvent) => {
+
+    };
+
+    // 获取应用详情
+    const api = {
+        fetchDetail: async (app_id: string) => {
+            setPageLoading(true);
+            try {
+                const res = await apis.fetchApplicationDetail(app_id)
+                const jsonObj = JSON.parse(res.data.detail.extraInput);
+                const sd = res.data.questionlist.map((item: any) => {
+                    return {
+                        "question": item.question,
+                    }
+                });
+                // const index_type = jsonObj.recall_index_type_list.map((item: any) => {
+                //     return {
+                //         "knowledge_id": item.knowledge_id,
+                //         "index_type_id": item.index_type_id,
+                //     }
+                // });
+                const info = res.data.detail;
+                if (jsonObj.rerank_status === 1) {
+                    setIsVisibleRerank(!isVisibleRerank) //模型
+                }
+                if (info.paramDesc === 'custom') {
+                    setIsVisibleCus(!isVisibleCus);    //自定义回答风格
+                }
+                if (jsonObj.slice_config_type === 'fixed') {
+                    setIsVisibleSlice(!isVisibleSlice);
+                }
+                setTopPValue(info.topP as number);
+                setTempValue(info.temperature as number);
+
+                form.setFieldsValue({
+                    name: info.name,  //应用名称
+                    desc: info.desc,  //应用描述
+                    questionList: sd, //问题列表
+                    prompt: info.prompt, //应用提示语
+                    max_token: info.maxToken, //应用最大token
+                    top_p: info.topP, //topP
+                    temperature: info.temperature, //温度
+                    updateDate: info.updateDate, // 更新时间
+                    param_desc: info.paramDesc, //回答风格
+                    knowledge_ids: jsonObj.knowledge_ids, //知识库id
+                    model: jsonObj.model, //模型名称
+                    slice_config_type: jsonObj.slice_config_type, //切片类型
+                    recall_method: jsonObj.recall_method, //召回方式
+                    slice_count: jsonObj.slice_count, //切片数量
+                    rerank_model_name: jsonObj.rerank_model_name, //模型名称
+                    recall_slice_splicing_method: jsonObj.recall_slice_splicing_method,
+                    rerank_status: jsonObj.rerank_status, //开启rerank
+                    show_recall_result: jsonObj.show_recall_result, //是否展示召回结果
+                    recall_index_type_list: jsonObj.recall_index_type_list, //知识库id
+                    rerank_index_type_list: jsonObj.rerank_index_type_list, //知识库id
+                })
+                console.log(sd, 'sd');
+                setInputs(sd);
+            } catch (error) {
+                console.error(error);
+            } finally {
+                setPageLoading(false);
+            }
+        },
+
+        //获取知识库列表
+        fetchKnowlegde: async () => {
+            try {
+                const res = await apis.fetchKnowledgeList();
+                const list = res.data.map((item: any) => {
+                    return {
+                        label: item.name,
+                        value: item.knowledgeId,
+                    }
+                });
+                setKnowledgeList(list);
+            } catch (error: any) {
+                console.error(error);
+            }
+        },
+
+        // 获取模型列表
+        fetchModelList: async () => {
+            try {
+                const res = await apis.fetchModelList();
+                const list = res.data.data.map((item: any) => {
+                    return {
+                        label: item.modelName,
+                        value: item.modelCode,
+                    }
+                });
+                setModelList(list);
+            } catch (error: any) {
+                console.error(error);
+            }
+        }
+    }
+
     return (
         <div className='questionAnswerInfo'>
-            创建/修改-问答应用
+            <Spin spinning={pageLoading}>
+                <Form
+                    style={{ paddingTop: '20px' }}
+                    form={form}
+                    layout='vertical'
+                    initialValues={{
+                        max_token: 1024
+                    }}
+                >
+                    <div style={{ display: step === 1 ? 'block' : 'none' }} className='questionAnswerInfo-content'>
+                        <FormItem
+                            label='问答应用名称'
+                            name='name'
+                            rules={[{ required: true, message: '问答应用名称不能为空' }]}
+                        >
+                            <Input placeholder="应用名称" style={{ width: 646, padding: 8 }} />
+                        </FormItem>
+
+                        <FormItem
+                            label='问答应用描述'
+                            name='desc'
+                            rules={[{ required: true, message: '问答应用描述不能为空' }]}
+                        >
+                            <TextArea
+                                showCount
+                                maxLength={500}
+                                placeholder="请输入描述"
+                                style={{ height: 120, resize: 'none', width: 646 }}
+                            />
+                        </FormItem>
+
+                        <div>
+                            <h4>添加预设问题</h4>
+                            {
+                                inputs.length === 0 &&
+                                <div>
+                                    <label>问题 {Index}</label>
+                                     <Input
+                                         style={{ width: 300, padding: 8, marginLeft: 20 }} name='question' />
+                                     <PlusCircleOutlined style={{ marginLeft: 20 }} onClick={handleAddInput} />
+                                     <MinusCircleOutlined style={{ marginLeft: 20 }} onClick={handleRemoveInput} />
+                                </div>
+                                
+                            }
+                            {inputs.length > 0 &&
+                                inputs.map((input, index) => (
+                                    <div style={{ paddingTop: 10 }} key={index}>
+                                        <label>问题 {index}</label>
+                                        <Input
+                                            style={{ width: 300, padding: 8, marginLeft: 20 }}
+                                            value={input.question}
+                                        />
+                                        <PlusCircleOutlined style={{ marginLeft: 20 }} onClick={handleAddInput} />
+                                        <MinusCircleOutlined style={{ marginLeft: 20 }} onClick={handleRemoveInput} />
+                                    </div>
+                            ))}
+                        </div>
+                        <br />
+                        <Button type='primary' onClick={() => {
+                            form.validateFields(['name', 'desc']).then(async (values) => {
+                                setStep(2);
+                                setInputs(inputs);
+                                console.log(inputs, 'inputs');
+                            }).catch((error) => {
+                                console.error(error);
+                            });
+                        }} >
+                            下一步
+                        </Button>
+                    </div>
+
+                    <div style={{ display: step === 2 ? 'block' : 'none' }} className='questionAnswerInfo-content'>
+                        <div style={{ paddingTop: '20px', display: 'flex', justifyContent: 'flex-end' }}>
+                            <div>
+                                <Button
+                                    style={{ background: '#f5f5f5' }}
+                                    onClick={() => {
+                                        setStep(1);
+                                    }}
+                                >
+                                    上一步
+                                </Button>
+                                <Button
+                                    type='primary'
+                                    onClick={() => {
+                                        form.validateFields().then(async (values) => {
+                                            const data = values;
+                                            // const question = [{}];
+                                            // if(inputs){
+                                            //     inputs.map((item, index) => {
+                                            //         const questionInfo = {
+                                            //             question: item.question,
+                                            //         }
+                                            //         question.push(questionInfo);
+                                            //     });
+                                            // }
+                                            
+                                            const info = {
+                                                name: values.name,
+                                                desc: values.desc,
+                                                prompt: values.prompt,
+                                                temperature: tempValue,
+                                                top_p: topPValue,
+                                                knowledge_ids: values.knowledge_ids,
+                                                param_desc: values.param_desc,
+                                                max_token: values.max_token,
+                                                questionList: inputs,
+                                                knowledge_info: {
+                                                    model: values.model, // 默认模型名称
+                                                    knowledge_ids: values.knowledge_ids, // 知识库id列表
+                                                    slice_config_type: values.slice_config_type, // 切片类型,默认为 fixed
+                                                    recall_method: values.recall_method, // 召回方式,默认为 embedding
+                                                    recall_index_type_list: values.recall_index_type_list, // 索引配置类型列表,默认为空数组
+                                                    slice_count: values.slice_count, // 切片数量,默认为空字符串
+                                                    rerank_status: values.rerank_status ? 1 : 0, // 是否开启rerank,默认关闭
+                                                    rerank_model_name: values.rerank_model_name, // 模型名称,默认为空字符串
+                                                    show_recall_result: values.show_recall_result, // 是否展示召回结果,默认为空字符串
+                                                    recall_slice_splicing_method: values.recall_slice_splicing_method // 召回切片拼接方式,默认为空字符串
+                                                }
+                                            };
+                                            const id = location?.state?.id;
+                                            if (id) {
+                                                // 编辑应用
+                                                console.log(info, 'value');
+                                                await apis.modifyApplicationApi(id, info);
+                                            } else {
+                                                // 创建应用
+                                                await await apis.createApplicationApi(info);
+                                            }
+                                            router.navigate({ pathname: '/questionAnswer' });
+                                            console.log(info, 'info');
+                                        }).catch((error) => {
+                                            console.error(error);
+                                        });
+                                    }}
+                                >发布应用</Button>
+                            </div>
+                        </div>
+                        <Splitter style={{ height: '100%', boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)' }}>
+                            <Splitter.Panel defaultSize="40%">
+                                <div>Prompt编写</div>
+                                <div style={{ height: '30%' }}>
+                                    <TextArea
+                                        autoSize
+                                        disabled
+                                        placeholder=" 编写Prompt过程中可以引入2项变量:{{知识}} 代表知识库中检索到的知识内容, {{用户}}代表用户输入的内容。您可以在编写Prompt过程中将变量拼接在合适的位置。 "
+                                        style={{ width: '90%' }}
+                                    />
+                                </div>
+                                <Divider plain></Divider>
+                                <div >
+                                    <FormItem name='prompt'>
+                                        <TextArea
+                                            placeholder="提示词"
+                                            style={{ width: '90%', height: '100%' }}
+                                            autoSize
+                                        />
+                                    </FormItem>
+                                </div>
+                            </Splitter.Panel>
+                            <Splitter.Panel defaultSize="60%">
+                                <div style={{ background: '#f5f5f5', width: '100%', height: '100%' }}>
+                                    <div style={{ paddingTop: '80px' }}>欢迎使用 数字监理员</div>
+                                    <div style={{ paddingTop: '40px' }}>
+                                        <FormItem
+                                            label='引用知识库'
+                                            name='knowledge_ids'
+                                            rules={[{ required: true, message: '知识库不能为空' }]}>
+                                            <Select
+                                                mode='multiple'
+                                                maxCount={MAX_COUNT}
+                                                // suffixIcon={suffix}
+                                                style={{ width: '300px' }}
+                                                placeholder='请选择知识库'
+                                            >
+                                                {
+                                                    knowledgeList.map((item, index) => {
+                                                        return <Option value={item.value} key={index}>
+                                                            {item.label}
+                                                        </Option>
+                                                    })
+                                                }
+                                            </Select>
+                                        </FormItem>
+                                        <FormItem
+                                            label='调用模型'
+                                            name="model"
+                                            rules={[{ required: true, message: '模型不能为空' }]}>
+                                            <Select
+                                                style={{ width: '30%' }}
+                                                placeholder='请选择模型'
+                                                allowClear={true}
+                                            >
+                                                {
+                                                    modelList.map((item, index) => {
+                                                        return <Option value={item.value} key={index}>
+                                                            {item.label}
+                                                        </Option>
+                                                    })
+                                                }
+                                            </Select>
+                                        </FormItem>
+                                        <FormItem
+                                            label='max token'
+                                            name='max_token'
+                                            rules={[{ required: true, message: 'max token不能为空' }]}>
+                                            <InputNumber
+                                                style={{ width: '100px' }}
+                                            />
+                                        </FormItem>
+                                        {
+                                            !isVisible &&
+                                            <a onClick={() => {
+                                                setIsVisible(!isVisible);
+                                            }}>
+                                                更多设置
+                                            </a>
+                                        }
+                                        {isVisible &&
+                                            <div>
+                                                {isVisibleCus &&
+                                                    <Space style={{ width: '100%' }} direction="vertical">
+                                                        <FormItem
+                                                            label='Top-p'
+                                                            name='top_p'
+                                                        >
+                                                            <TopPDecimalStep />
+                                                        </FormItem>
+                                                        <FormItem
+                                                            label='Temperature'
+                                                            name='temperature'
+                                                        >
+                                                            <TempDecimalStep />
+                                                        </FormItem>
+
+                                                    </Space >
+                                                }
+
+                                                <FormItem
+                                                    label='回答风格'
+                                                    name='param_desc'
+                                                    rules={[{ required: true, message: '回答风格不能为空' }]}>
+                                                    <Radio.Group buttonStyle="solid">
+                                                        <Radio.Button value='strict'>严谨</Radio.Button>
+                                                        <Radio.Button value='moderate'>适中</Radio.Button>
+                                                        <Radio.Button value='flexib'>发散</Radio.Button>
+                                                        <Radio.Button value='custom'
+                                                            onClick={() => {
+                                                                setIsVisibleCus(!isVisibleCus);
+                                                            }}
+                                                        >自定义
+                                                        </Radio.Button>
+                                                    </Radio.Group>
+                                                </FormItem>
+
+                                                <FormItem
+                                                    label='展示引用知识'
+                                                    name='show_recall_result' >
+                                                    <Switch onChange={onChangeShow} />
+                                                </FormItem>
+
+                                                <FormItem
+                                                    label='召回方式'
+                                                    name='recall_method'
+                                                    rules={[{ required: true, message: '召回方式不能为空' }]}>
+
+                                                    <Radio.Group
+                                                        style={style}
+                                                        onChange={onChangeRecallMethod}
+                                                        options={[
+                                                            { value: 'embedding', label: '向量化检索' },
+                                                            { value: 'keyword', label: '关键词检索' },
+                                                            { value: 'mixed', label: '混合检索' },
+                                                        ]}
+                                                    />
+                                                </FormItem>
+
+                                                <p>重排方式</p>
+
+                                                <FormItem
+                                                    label='Rerank模型'
+                                                    name='rerank_status'
+                                                    valuePropName='checked'
+                                                >
+                                                    <Switch onChange={onChangeModel} />
+                                                </FormItem>
+                                                {isVisibleRerank &&
+                                                    <FormItem
+                                                        label='模型选择'
+                                                        name='rerank_model_name'
+                                                    >
+                                                        <Select
+                                                            style={{ width: '200px' }}
+                                                            placeholder='请选择模型'
+                                                            defaultValue={'默认rerank模型'}
+                                                        >
+                                                            <Option value='rerank'>默认rerank模型</Option>
+                                                        </Select>
+                                                    </FormItem>
+                                                }
+
+                                                <FormItem
+                                                    label='召回切片数量'
+                                                    name='slice_config_type'
+                                                    rules={[{ required: true, message: '召回方式不能为空' }]}>
+                                                    <Select
+                                                        style={{ width: '100%' }}
+                                                        placeholder='请选择'
+                                                        onChange={onChangeCount}>
+                                                        <Option value="fixed">手动设置</Option>
+                                                        <Option value="customized">自动设置</Option>
+                                                    </Select>
+                                                </FormItem>
+
+                                                {isVisibleSlice &&
+                                                    <div>
+                                                        <FormItem
+                                                            label='召回切片数'
+                                                            name='slice_count'
+                                                            rules={[{ required: true, message: '切片数不能为空' }]}>
+                                                            <InputNumber max={1024} changeOnWheel />
+                                                        </FormItem>
+                                                    </div>
+                                                }
+
+                                                <FormItem
+                                                    label='召回切片拼接方式'
+                                                    name='recall_slice_splicing_method'
+                                                >
+                                                    <TextArea
+                                                        rows={4}
+                                                        placeholder="请输入内容"
+                                                    />
+                                                </FormItem>
+                                            </div>
+                                        }
+                                    </div>
+                                </div>
+                            </Splitter.Panel>
+                        </Splitter>
+                    </div>
+                </Form>
+            </Spin>
         </div>
     );
 };

+ 7 - 0
src/pages/questionAnswer/info/style.less

@@ -3,4 +3,11 @@
   height: 100%;
   background: #FFFFFF;
   border-radius: @border-radius-base;
+}
+
+.questionAnswerInfo-content {
+  width: 100%;
+  height: 100%;
+  background: #FFFFFF;
+  padding: 10px;
 }

+ 190 - 102
src/pages/questionAnswer/list/index.tsx

@@ -1,38 +1,84 @@
 import * as React from 'react';
 import { observer } from 'mobx-react';
-import { Card, List, Button, Divider  } from 'antd';
-import { PlusOutlined } from '@ant-design/icons';
+import { List, Button, Divider, Flex, Layout, Empty, Image, Modal } from 'antd';
+import { PlusOutlined, FileOutlined, SettingOutlined, DeleteOutlined } from '@ant-design/icons';
 import { apis } from '@/apis';
 import './style.less';
 import { PaginationConfig } from 'antd/es/pagination';
+import router from '@/router';
 
+const { Header, Footer, Sider, Content } = Layout;
+
+const headerStyle: React.CSSProperties = {
+    textAlign: 'center',
+    //   color: '#fff',
+    height: 24,
+    paddingInline: 48,
+    lineHeight: '30px',
+    backgroundColor: '#fff',
+};
+
+const contentStyle: React.CSSProperties = {
+    textAlign: 'center',
+    //minHeight: 120,
+    //   height: 14,
+    lineHeight: '40px',
+    //   color: '#fff',
+    backgroundColor: '#fff',
+};
+
+const siderStyle: React.CSSProperties = {
+    //   textAlign: 'center',
+    paddingLeft: 30,
+    paddingTop: 30,
+    height: 80,
+    //   width: 20,
+    //   color: '#fff',
+    backgroundColor: '#fff',
+};
+
+const footerStyle: React.CSSProperties = {
+    textAlign: 'center',
+    color: '#fff',
+    height: 24,
+    backgroundColor: '#4096ff',
+};
+
+const layoutStyle = {
+    borderRadius: 8,
+    overflow: 'hidden',
+    width: 'calc(10% - 8px)',
+    maxWidth: 'calc(20% - 8px)',
+};
 const QuestionAnswerList: React.FC = () => {
-    const defaultData = [
-        {
-            name: '什么是年夜饭',
-            desc: '年夜饭是中国传统文化非常重要的一部分......'
-        }
-    ]
+
     interface Item {
         name: string,
         desc: string,
-        id: number,
+        appId: number,
         createBy: string,
     }
+    interface Index {
+        applicationCount: number,
+        knowledgeCount: number,
+    }
 
     const [listLoading, setListLoading] = React.useState(false);
     const [list, setList] = React.useState<Item[]>([]);
     const [page, setPage] = React.useState<any>({
         pageNumber: 1,
-        pageSize: 4,
+        pageSize: 10,
         total: 0
     });
+    const [appCount, setAppCount] = React.useState<string>();
+    const [knowCount, setKnowCount] = React.useState<string>();
+    const { Header, Footer, Sider, Content } = Layout;
 
-    const api = {
+    const appApi = {
         fetchList: async () => {
             setListLoading(true);
             try {
-                const res = await apis.fetchApplicationList({
+                const res = await apis.fetchAppList({
                     pageSize: page.pageSize,
                     pageNumber: page.pageNumber
                 })
@@ -50,22 +96,47 @@ const QuestionAnswerList: React.FC = () => {
         }
     }
 
-    const createApplication = () => {
-        console.log('创建应用');
-    };
+    // 删除应用
+    const delApplication = async (appId: string) => {
+        try {
+            await apis.deleteApplicationApi(appId);
+            await appApi.fetchList();
+        } catch (error) {
+            console.error(error);
+        }
+    }
+
+    const indexApi = {
+        fetchIndex: async () => {
+            try {
+                const res = await apis.fetchIndexCount({
+                    pageSize: page.pageSize,
+                    pageNumber: page.pageNumber
+                })
+                setAppCount(res.data.applicationCount);
+                setKnowCount(res.data.knowledgeCount);
+                setPage({
+                    pageNumber: page.pageNumber,
+                    pageSize: page.pageSize,
+                    total: res.data.total,
+                });
+            } catch (error) {
+                console.error(error);
+            } finally {
+                setListLoading(false);
+            }
+        }
+    }
 
     const init = async () => {
-        await api.fetchList();
+        await appApi.fetchList();
+        await indexApi.fetchIndex();
     }
 
     React.useEffect(() => {
         init();
     }, [page.pageSize, page.pageNumber])
 
-    const detailInfo = () => {
-        console.log('详情');
-    }
-
     const paginationConfig: PaginationConfig = {
         // 显示数据总量
         showTotal: (total: number) => {
@@ -89,92 +160,109 @@ const QuestionAnswerList: React.FC = () => {
         },
     };
 
-    const HasList: React.FC = () => {
-        return (
-            <div id="showList" className='questionAnswerList' onClick={detailInfo}>
-                {/* <Button
-                    type="primary"
-                    style={{ width: 130, height: 55, fontSize: 22 }}
-                    icon={<PlusOutlined />}
-                    onClick={createApplication}
-                > 创建 </Button> */}
-                <div className='applicationList'>
-                    <List style={{ height: 400 }}
-                        grid={{
-                            gutter: 16,
-                            xs: 1,
-                            sm: 2,
-                            md: 4,
-                            lg: 4,
-                            xl: 6,
-                            xxl: 2, // 展示的列数
-                        }}
-                        dataSource={list}
-                        renderItem={(item) => (
-                            <List.Item>
-                                <div className='card'>
-                                    <div className='title'>{item.name}</div>
-                                    <Divider plain></Divider>
-                                    <div className='desc'>
-                                        {
-                                            item.desc.length > 35 ? item.desc.substring(0, 35) + '......' : item.desc
-                                        }
-                                    </div>
-                                    <div>
-                                        分类:<Button>所有分类</Button>
-                                        <Button type="primary" style={{ width: 100, height: 40, fontSize: 16 }} onClick={createApplication}>管理应用</Button>
-                                        <Button type="primary" style={{ width: 100, height: 40, fontSize: 16 }} onClick={createApplication}>立即使用</Button>
-                                    </div>
-                                    
-                                </div>
-                            </List.Item>
-                        )}
-                        pagination={paginationConfig} // 分页
-                    />
-                </div>
-            </div>
-        );
-    }
-
-    const NoList: React.FC = () => {
-        return (
-            <div className='questionAnswerList'>
-                <div>
-                    <List style={{ height: 400 }}
-                        grid={{
-                            gutter: 16,
-                            xs: 1,
-                            sm: 2,
-                            md: 4,
-                            lg: 4,
-                            xl: 6,
-                            xxl: 4, // 展示的列数
-                        }}
-
-                        dataSource={defaultData}
-                        renderItem={(item) => (
-                            <List.Item>
-                                <Card title={item.name}>
-                                    {item.desc}
-                                </Card>
-                            </List.Item>
-                        )}
-                    />
-                    <Button
-                        type="primary"
-                        style={{ width: 130, height: 55, fontSize: 22 }}
-                        icon={<PlusOutlined />}
-                        onClick={createApplication}
-                    > 创建 </Button>
-                </div>
-            </div>)
-
-    }
 
     return (
         <div>
             {
-                list.length ? <HasList /> : <NoList />
+                list.length
+                    ?
+                    <div className='questionAnswerList'>
+                        <div>
+                            <Flex gap="middle" wrap>
+                                <Layout style={layoutStyle}>
+                                    <Sider width="25%" style={siderStyle}>
+                                        <FileOutlined />
+                                    </Sider>
+                                    <Layout>
+                                        <Header style={headerStyle}>问答应用总数</Header>
+                                        <Content style={contentStyle}>{appCount}个</Content>
+                                    </Layout>
+                                </Layout>
+                                <Layout style={layoutStyle}>
+                                    <Sider width="25%" style={siderStyle}>
+                                        <FileOutlined />
+                                    </Sider>
+                                    <Layout>
+                                        <Header style={headerStyle}>知识库总数</Header>
+                                        <Content style={contentStyle}>{knowCount} 个</Content>
+                                    </Layout>
+                                </Layout>
+                            </Flex>
+                        </div>
+                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                            <div>所有问答应用</div>
+                            <Button type='primary'
+                                icon={<PlusOutlined />}
+                                onClick={() => {
+                                    router.navigate({ pathname: '/questionAnswer/create' });
+                                }}>创建问答应用</Button>
+                        </div>
+                        <div className='applicationList'>
+                            <List style={{ height: 400 }}
+                                grid={{
+                                    gutter: 16,
+                                    xs: 1,
+                                    sm: 2,
+                                    md: 4,
+                                    lg: 4,
+                                    xl: 6,
+                                    xxl: 2, // 展示的列数
+                                }}
+                                dataSource={list}
+                                renderItem={(item) => (
+                                    <List.Item>
+                                        <div className='card'>
+                                            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
+                                                <div style={{ display: 'flex', alignItems: 'center' }}>
+                                                    <div style={{ marginRight: 10 }}>
+                                                        <Image
+                                                            width={30}
+                                                            height={30}
+                                                            src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
+                                                        />
+                                                    </div>
+                                                    <div>
+                                                        {item.name}
+                                                    </div>
+                                                </div>
+                                                <div className='title'>{item.createBy}</div>
+                                            </div>
+                                            <Divider plain></Divider>
+                                            <div className='desc'>
+                                                {
+                                                    item.desc !== '' && item.desc.length > 35 ? item.desc.substring(0, 35) + '......' : item.desc
+                                                }
+                                            </div>
+                                            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+                                                <div>
+                                                    <a onClick={() => {
+                                                        router.navigate({ pathname: '/questionAnswer/modify' }, { state: { id: item.appId } });
+                                                    }}>
+                                                        <SettingOutlined /> 编辑
+                                                    </a>
+                                                    <a className='text-error' onClick={() => {
+                                                        Modal.confirm({
+                                                            title: '删除',
+                                                            content: `确定删除应用名称: ` + item.name + ` 吗?`,
+                                                            okType: 'danger',
+                                                            onOk: async () => {
+                                                                await delApplication(item.appId.toString());
+                                                            }
+                                                        });
+                                                    }}>
+                                                        <DeleteOutlined /> 删除
+                                                    </a>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </List.Item>
+                                )}
+                                pagination={paginationConfig} // 分页
+                            />
+                        </div>
+                    </div>
+                    :
+                    <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
             }
         </div>
     )

+ 11 - 5
src/pages/questionAnswer/list/style.less

@@ -6,20 +6,26 @@
 }
 
 .applicationList {
-  width: 1400px;
+  width: 100%;
   height: 400px;
   padding-top: 10px;
 }
 
 .card{
-  border: 1px solid blue;
-  height: 210px;
+  padding: 20px;
+  border: 1px solid @border-color;
+  border-radius: @border-radius-base;
+  height: 200px;
+}
+.card:hover{
+  border-color:@primary-color ;
 }
 
 .desc {
   height: 35px;
 }
 
-.title {
-  height: 45px;
+.info-head {
+  width: 100%;
+  height: 35%;
 }