feat: remove loading from model and use DvaModel instead of redundant types such as kAModelType (#47)

* feat: use DvaModel instead of redundant types such as kAModelType

* feat: set the type for registerServer

* feat: remove loading from model
This commit is contained in:
balibabu 2024-01-30 19:26:29 +08:00 committed by GitHub
parent 96a1a44cb6
commit 362ec6c364
29 changed files with 1911 additions and 1938 deletions

View File

@ -0,0 +1,11 @@
import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil';
import { useSelector } from 'umi';
// Get the loading status of given effects under a certain namespace
export const useOneNamespaceEffectsLoading = (
namespace: string,
effectNames: Array<string>,
) => {
const effects = useSelector((state: any) => state.loading.effects);
return getOneNamespaceEffectsLoading(namespace, effects, effectNames);
};

View File

@ -1,102 +1,116 @@
import React, { useEffect, useState } from 'react' import { Form, Input, Modal } from 'antd';
import { connect, Dispatch } from 'umi'; import React, { useCallback, useEffect, useState } from 'react';
import i18n from 'i18next'; import { useTranslation } from 'react-i18next';
import { useTranslation, Trans } from 'react-i18next' import { useDispatch } from 'umi';
import { Input, Modal, Form } from 'antd' import EditTag from './editTag';
import styles from './index.less';
import type { chunkModelState } from './model'
import EditTag from './editTag'
type FieldType = { type FieldType = {
content_ltks?: string; content_ltks?: string;
}; };
interface kFProps { interface kFProps {
dispatch: Dispatch; getChunkList: () => void;
chunkModel: chunkModelState; isShowCreateModal: boolean;
getChunkList: () => void; doc_id: string;
isShowCreateModal: boolean; chunk_id: string;
doc_id: string;
chunk_id: string
} }
const Index: React.FC<kFProps> = ({ dispatch, getChunkList, doc_id, isShowCreateModal, chunk_id }) => {
// const { , chunkInfo } = chunkModel
const [important_kwd, setImportantKwd] = useState(['Unremovable', 'Tag 2', 'Tag 3']);
const { t } = useTranslation()
const handleCancel = () => {
dispatch({
type: 'chunkModel/updateState',
payload: {
isShowCreateModal: false
}
});
};
useEffect(() => {
console.log(chunk_id, isShowCreateModal)
if (chunk_id && isShowCreateModal) {
dispatch({
type: 'chunkModel/get_chunk',
payload: {
chunk_id
},
callback(info: any) {
console.log(info)
const { content_ltks, important_kwd = [] } = info
form.setFieldsValue({ content_ltks })
setImportantKwd(important_kwd)
}
});
}
}, [chunk_id, isShowCreateModal])
const [form] = Form.useForm()
const handleOk = async () => {
try {
const values = await form.validateFields();
dispatch({
type: 'chunkModel/create_hunk',
payload: {
content_ltks: values.content_ltks,
doc_id,
chunk_id,
important_kwd
},
callback: () => {
dispatch({
type: 'chunkModel/updateState',
payload: {
isShowCreateModal: false
}
});
getChunkList && getChunkList()
}
});
} catch (errorInfo) { const Index: React.FC<kFProps> = ({
console.log('Failed:', errorInfo); getChunkList,
} doc_id,
}; isShowCreateModal,
chunk_id,
}) => {
const dispatch = useDispatch();
const [form] = Form.useForm();
return ( // const { , chunkInfo } = chunkModel
<Modal title="Basic Modal" open={isShowCreateModal} onOk={handleOk} onCancel={handleCancel}> const [important_kwd, setImportantKwd] = useState([
<Form 'Unremovable',
form={form} 'Tag 2',
name="validateOnly" 'Tag 3',
labelCol={{ span: 5 }} ]);
wrapperCol={{ span: 19 }} const { t } = useTranslation();
style={{ maxWidth: 600 }} const handleCancel = () => {
autoComplete="off" dispatch({
> type: 'chunkModel/updateState',
<Form.Item<FieldType> payload: {
label="chunk 内容" isShowCreateModal: false,
name="content_ltks" },
rules={[{ required: true, message: 'Please input value!' }]} });
> };
<Input.TextArea />
</Form.Item>
<EditTag tags={important_kwd} setTags={setImportantKwd} />
</Form>
</Modal >
const getChunk = useCallback(async () => {
if (chunk_id && isShowCreateModal) {
const data = await dispatch<any>({
type: 'chunkModel/get_chunk',
payload: {
chunk_id,
},
});
); if (data?.retcode === 0) {
} const { content_ltks, important_kwd = [] } = data.data;
export default connect(({ chunkModel, loading }) => ({ chunkModel, loading }))(Index); form.setFieldsValue({ content_ltks });
setImportantKwd(important_kwd);
}
}
}, [chunk_id, isShowCreateModal]);
useEffect(() => {
getChunk();
}, [getChunk]);
const handleOk = async () => {
try {
const values = await form.validateFields();
dispatch({
type: 'chunkModel/create_hunk',
payload: {
content_ltks: values.content_ltks,
doc_id,
chunk_id,
important_kwd,
},
// callback: () => {
// dispatch({
// type: 'chunkModel/updateState',
// payload: {
// isShowCreateModal: false,
// },
// });
// getChunkList && getChunkList();
// },
});
} catch (errorInfo) {
console.log('Failed:', errorInfo);
}
};
return (
<Modal
title="Basic Modal"
open={isShowCreateModal}
onOk={handleOk}
onCancel={handleCancel}
>
<Form
form={form}
name="validateOnly"
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
style={{ maxWidth: 600 }}
autoComplete="off"
>
<Form.Item<FieldType>
label="chunk 内容"
name="content_ltks"
rules={[{ required: true, message: 'Please input value!' }]}
>
<Input.TextArea />
</Form.Item>
<EditTag tags={important_kwd} setTags={setImportantKwd} />
</Form>
</Modal>
);
};
export default Index;

View File

@ -1,142 +1,141 @@
import React, { useEffect, useRef, useState } from 'react';
import { PlusOutlined } from '@ant-design/icons';
import type { InputRef } from 'antd'; import type { InputRef } from 'antd';
import { Input, Space, Tag, theme, Tooltip } from 'antd'; import { Input, Space, Tag, Tooltip, theme } from 'antd';
interface editTagsProps { import React, { useEffect, useRef, useState } from 'react';
tags: any[], interface EditTagsProps {
setTags: (tags: any[]) => void tags: any[];
setTags: (tags: any[]) => void;
} }
const App: React.FC<editTagsProps> = ({ tags, setTags }) => { const EditTag: React.FC<EditTagsProps> = ({ tags, setTags }) => {
const { token } = theme.useToken(); const { token } = theme.useToken();
const [inputVisible, setInputVisible] = useState(false); const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
const [editInputIndex, setEditInputIndex] = useState(-1); const [editInputIndex, setEditInputIndex] = useState(-1);
const [editInputValue, setEditInputValue] = useState(''); const [editInputValue, setEditInputValue] = useState('');
const inputRef = useRef<InputRef>(null); const inputRef = useRef<InputRef>(null);
const editInputRef = useRef<InputRef>(null); const editInputRef = useRef<InputRef>(null);
useEffect(() => { useEffect(() => {
if (inputVisible) { if (inputVisible) {
inputRef.current?.focus(); inputRef.current?.focus();
}
}, [inputVisible]);
useEffect(() => {
editInputRef.current?.focus();
}, [editInputValue]);
const handleClose = (removedTag: string) => {
const newTags = tags.filter((tag) => tag !== removedTag);
console.log(newTags);
setTags(newTags);
};
const showInput = () => {
setInputVisible(true);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
if (inputValue && !tags.includes(inputValue)) {
setTags([...tags, inputValue]);
}
setInputVisible(false);
setInputValue('');
};
const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditInputValue(e.target.value);
};
const handleEditInputConfirm = () => {
const newTags = [...tags];
newTags[editInputIndex] = editInputValue;
setTags(newTags);
setEditInputIndex(-1);
setEditInputValue('');
};
const tagInputStyle: React.CSSProperties = {
width: 64,
height: 22,
marginInlineEnd: 8,
verticalAlign: 'top',
};
const tagPlusStyle: React.CSSProperties = {
height: 22,
background: token.colorBgContainer,
borderStyle: 'dashed',
};
return (
<Space size={[0, 8]} wrap>
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<Input
ref={editInputRef}
key={tag}
size="small"
style={tagInputStyle}
value={editInputValue}
onChange={handleEditInputChange}
onBlur={handleEditInputConfirm}
onPressEnter={handleEditInputConfirm}
/>
);
} }
}, [inputVisible]); const isLongTag = tag.length > 20;
const tagElem = (
useEffect(() => { <Tag
editInputRef.current?.focus(); key={tag}
}, [editInputValue]); closable={index !== 0}
style={{ userSelect: 'none' }}
const handleClose = (removedTag: string) => { onClose={() => handleClose(tag)}
const newTags = tags.filter((tag) => tag !== removedTag); >
console.log(newTags); <span
setTags(newTags); onDoubleClick={(e) => {
}; if (index !== 0) {
setEditInputIndex(index);
const showInput = () => { setEditInputValue(tag);
setInputVisible(true); e.preventDefault();
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
if (inputValue && !tags.includes(inputValue)) {
setTags([...tags, inputValue]);
}
setInputVisible(false);
setInputValue('');
};
const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditInputValue(e.target.value);
};
const handleEditInputConfirm = () => {
const newTags = [...tags];
newTags[editInputIndex] = editInputValue;
setTags(newTags);
setEditInputIndex(-1);
setEditInputValue('');
};
const tagInputStyle: React.CSSProperties = {
width: 64,
height: 22,
marginInlineEnd: 8,
verticalAlign: 'top',
};
const tagPlusStyle: React.CSSProperties = {
height: 22,
background: token.colorBgContainer,
borderStyle: 'dashed',
};
return (
<Space size={[0, 8]} wrap>
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<Input
ref={editInputRef}
key={tag}
size="small"
style={tagInputStyle}
value={editInputValue}
onChange={handleEditInputChange}
onBlur={handleEditInputConfirm}
onPressEnter={handleEditInputConfirm}
/>
);
} }
const isLongTag = tag.length > 20; }}
const tagElem = ( >
<Tag {isLongTag ? `${tag.slice(0, 20)}...` : tag}
key={tag} </span>
closable={index !== 0} </Tag>
style={{ userSelect: 'none' }} );
onClose={() => handleClose(tag)} return isLongTag ? (
> <Tooltip title={tag} key={tag}>
<span {tagElem}
onDoubleClick={(e) => { </Tooltip>
if (index !== 0) { ) : (
setEditInputIndex(index); tagElem
setEditInputValue(tag); );
e.preventDefault(); })}
} {inputVisible ? (
}} <Input
> ref={inputRef}
{isLongTag ? `${tag.slice(0, 20)}...` : tag} type="text"
</span> size="small"
</Tag> style={tagInputStyle}
); value={inputValue}
return isLongTag ? ( onChange={handleInputChange}
<Tooltip title={tag} key={tag}> onBlur={handleInputConfirm}
{tagElem} onPressEnter={handleInputConfirm}
</Tooltip> />
) : ( ) : (
tagElem <Tag style={tagPlusStyle} onClick={showInput}>
);
})} </Tag>
{inputVisible ? ( )}
<Input </Space>
ref={inputRef} );
type="text"
size="small"
style={tagInputStyle}
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onPressEnter={handleInputConfirm}
/>
) : (
<Tag style={tagPlusStyle} onClick={showInput}>
</Tag>
)}
</Space>
);
}; };
export default App; export default EditTag;

View File

@ -1,225 +1,282 @@
import React, { useEffect, useState, useCallback } from 'react'; import { api_host } from '@/utils/api';
import { useNavigate, connect, Dispatch } from 'umi' import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil';
import { Card, Row, Col, Input, Select, Switch, Pagination, Spin, Button, Popconfirm } from 'antd'; import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons';
import { MinusSquareOutlined, DeleteOutlined, } from '@ant-design/icons';
import type { PaginationProps } from 'antd'; import type { PaginationProps } from 'antd';
import { api_host } from '@/utils/api' import {
import CreateModal from './components/createModal' Button,
Card,
Col,
Input,
Pagination,
Popconfirm,
Row,
Select,
Spin,
Switch,
} from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi';
import CreateModal from './components/createModal';
import styles from './index.less'
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import type { chunkModelState } from './model' import styles from './index.less';
interface chunkProps {
dispatch: Dispatch; interface PayloadType {
chunkModel: chunkModelState; doc_id: string;
doc_id: string keywords?: string;
available_int?: number;
} }
const Index: React.FC<chunkProps> = ({ chunkModel, dispatch, doc_id }) => {
const [keywords, SetKeywords] = useState('') interface IProps {
const [available_int, setAvailableInt] = useState(-1) doc_id: string;
const navigate = useNavigate() }
const [pagination, setPagination] = useState({ page: 1, size: 30 })
const Chunk = ({ doc_id }: IProps) => {
const dispatch = useDispatch();
const chunkModel = useSelector((state: any) => state.chunkModel);
const [keywords, SetKeywords] = useState('');
const [available_int, setAvailableInt] = useState(-1);
const navigate = useNavigate();
const [pagination, setPagination] = useState({ page: 1, size: 30 });
// const [datas, setDatas] = useState(data) // const [datas, setDatas] = useState(data)
const { data = [], total, loading, chunk_id, isShowCreateModal } = chunkModel const { data = [], total, chunk_id, isShowCreateModal } = chunkModel;
console.log(chunkModel) const effects = useSelector((state: any) => state.loading.effects);
const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [
'create_hunk',
'chunk_list',
'switch_chunk',
]);
const getChunkList = (value?: string) => { const getChunkList = (value?: string) => {
dispatch({ const payload: PayloadType = {
type: 'chunkModel/updateState',
payload: {
loading: true
}
});
interface payloadType {
doc_id: string;
keywords?: string;
available_int?: number
}
const payload: payloadType = {
doc_id, doc_id,
keywords: value || keywords, keywords: value || keywords,
available_int available_int,
} };
if (payload.available_int === -1) { if (payload.available_int === -1) {
delete payload.available_int delete payload.available_int;
} }
dispatch({ dispatch({
type: 'chunkModel/chunk_list', type: 'chunkModel/chunk_list',
payload: { payload: {
...payload, ...payload,
...pagination ...pagination,
}
});
}
const confirm = (id: string) => {
console.log(id)
dispatch({
type: 'chunkModel/rm_chunk',
payload: {
chunk_ids: [id]
}, },
callback: getChunkList
}); });
}; };
const confirm = async (id: string) => {
const retcode = await dispatch<any>({
type: 'chunkModel/rm_chunk',
payload: {
chunk_ids: [id],
},
});
retcode === 0 && getChunkList();
};
const handleEditchunk = (chunk_id?: string) => { const handleEditchunk = (chunk_id?: string) => {
dispatch({ dispatch({
type: 'chunkModel/updateState', type: 'chunkModel/updateState',
payload: { payload: {
isShowCreateModal: true, isShowCreateModal: true,
chunk_id, chunk_id,
doc_id doc_id,
}, },
callback: getChunkList
}); });
} getChunkList();
const onShowSizeChange: PaginationProps['onShowSizeChange'] = (page, size) => {
setPagination({ page, size })
}; };
const switchChunk = (id: string, available_int: boolean) => {
dispatch({ const onShowSizeChange: PaginationProps['onShowSizeChange'] = (
type: 'chunkModel/updateState', page,
payload: { size,
loading: true ) => {
} setPagination({ page, size });
}); };
dispatch({
const switchChunk = async (id: string, available_int: boolean) => {
const retcode = await dispatch<any>({
type: 'chunkModel/switch_chunk', type: 'chunkModel/switch_chunk',
payload: { payload: {
chunk_ids: [id], chunk_ids: [id],
available_int: Number(available_int), available_int: Number(available_int),
doc_id doc_id,
}, },
callback: getChunkList
}); });
}
retcode === 0 && getChunkList();
};
useEffect(() => { useEffect(() => {
getChunkList() getChunkList();
}, [doc_id, available_int, pagination]) }, [doc_id, available_int, pagination]);
const debounceChange = debounce(getChunkList, 300)
const debounceCallback = useCallback((value: string) => debounceChange(value), []) const debounceChange = debounce(getChunkList, 300);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { const debounceCallback = useCallback(
const value = e.target.value (value: string) => debounceChange(value),
SetKeywords(value) [],
debounceCallback(value) );
}
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const value = e.target.value;
SetKeywords(value);
debounceCallback(value);
};
const handleSelectChange = (value: number) => { const handleSelectChange = (value: number) => {
setAvailableInt(value) setAvailableInt(value);
} };
console.log('loading', loading) return (
return (<> <>
<div className={styles.chunkPage}> <div className={styles.chunkPage}>
<div className={styles.filter}> <div className={styles.filter}>
<div> <div>
<Input placeholder="搜索" style={{ width: 220 }} value={keywords} allowClear onChange={handleInputChange} /> <Input
<Select placeholder="搜索"
showSearch style={{ width: 220 }}
placeholder="是否启用" value={keywords}
optionFilterProp="children" allowClear
value={available_int} onChange={handleInputChange}
onChange={handleSelectChange} />
style={{ width: 220 }} <Select
options={[ showSearch
{ placeholder="是否启用"
value: -1, optionFilterProp="children"
label: '全部', value={available_int}
}, onChange={handleSelectChange}
{ style={{ width: 220 }}
value: 1, options={[
label: '启用', {
}, value: -1,
{ label: '全部',
value: 0, },
label: '未启用', {
}, value: 1,
]} label: '启用',
/> },
{
value: 0,
label: '未启用',
},
]}
/>
</div>
<Button
onClick={() => {
handleEditchunk();
}}
type="link"
>
</Button>
</div> </div>
<Button onClick={() => { handleEditchunk() }} type='link'></Button> <div className={styles.pageContent}>
</div> <Spin spinning={loading} className={styles.spin} size="large">
<div className={styles.pageContent}> <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }}>
<Spin spinning={loading} className={styles.spin} size='large'> {data.map((item: any) => {
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }} > return (
{ <Col
data.map((item: any) => { className="gutter-row"
return (<Col className="gutter-row" key={item.chunk_id} xs={24} sm={12} md={12} lg={8}> key={item.chunk_id}
<Card className={styles.card} xs={24}
onClick={() => { handleEditchunk(item.chunk_id) }} sm={12}
md={12}
lg={8}
> >
<img style={{ width: '50px' }} src={`${api_host}/document/image/${item.img_id}`} alt="" /> <Card
<div className={styles.container}> className={styles.card}
<div className={styles.content}> onClick={() => {
<span className={styles.context}> handleEditchunk(item.chunk_id);
{item.content_ltks} }}
</span> >
<span className={styles.delete}> <img
<Switch size="small" defaultValue={item.available_int == '1'} onChange={(checked: boolean, e: any) => { style={{ width: '50px' }}
e.stopPropagation(); src={`${api_host}/document/image/${item.img_id}`}
e.nativeEvent.stopImmediatePropagation(); switchChunk(item.chunk_id, checked) alt=""
}} /> />
</span> <div className={styles.container}>
<div className={styles.content}>
<span className={styles.context}>
{item.content_ltks}
</span>
<span className={styles.delete}>
<Switch
size="small"
defaultValue={item.available_int == '1'}
onChange={(checked: boolean, e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
switchChunk(item.chunk_id, checked);
}}
/>
</span>
</div>
<div className={styles.footer}>
<span className={styles.text}>
<MinusSquareOutlined />
{item.doc_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />
{item.chunk_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />
{item.token_num}
</span>
<span style={{ float: 'right' }}>
<Popconfirm
title="Delete the task"
description="Are you sure to delete this task?"
onConfirm={(e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
console.log(confirm);
confirm(item.chunk_id);
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined
onClick={(e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
}}
/>
</Popconfirm>
</span>
</div>
</div> </div>
<div className={styles.footer}> </Card>
<span className={styles.text}> </Col>
<MinusSquareOutlined />{item.doc_num} );
</span> })}
<span className={styles.text}> </Row>
<MinusSquareOutlined />{item.chunk_num} </Spin>
</span> </div>
<span className={styles.text}> <div className={styles.pageFooter}>
<MinusSquareOutlined />{item.token_num} <Pagination
</span> responsive
<span style={{ float: 'right' }}> showLessItems
<Popconfirm showQuickJumper
title="Delete the task" showSizeChanger
description="Are you sure to delete this task?" onChange={onShowSizeChange}
onConfirm={(e: any) => { defaultPageSize={30}
e.stopPropagation(); pageSizeOptions={[30, 60, 90]}
e.nativeEvent.stopImmediatePropagation() defaultCurrent={pagination.page}
console.log(confirm) total={total}
confirm(item.chunk_id) />
</div>
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined onClick={(e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation()
}} />
</Popconfirm>
</span>
</div>
</div>
</Card>
</Col>)
})
}
</Row>
</Spin>
</div> </div>
<div className={styles.pageFooter}> <CreateModal
<Pagination doc_id={doc_id}
responsive isShowCreateModal={isShowCreateModal}
showLessItems chunk_id={chunk_id}
showQuickJumper getChunkList={getChunkList}
showSizeChanger />
onChange={onShowSizeChange} </>
defaultPageSize={30} );
pageSizeOptions={[30, 60, 90]}
defaultCurrent={pagination.page}
total={total}
/>
</div>
</div >
<CreateModal doc_id={doc_id} isShowCreateModal={isShowCreateModal} chunk_id={chunk_id} getChunkList={getChunkList} />
</>
)
}; };
export default connect(({ chunkModel, loading }) => ({ chunkModel, loading }))(Index); export default Chunk;

View File

@ -1,8 +1,7 @@
import kbService from '@/services/kbService'; import kbService from '@/services/kbService';
import { Effect, Reducer } from 'umi'; import { DvaModel } from 'umi';
export interface chunkModelState { export interface ChunkModelState {
loading: boolean;
data: any[]; data: any[];
total: number; total: number;
isShowCreateModal: boolean; isShowCreateModal: boolean;
@ -10,25 +9,10 @@ export interface chunkModelState {
doc_id: string; doc_id: string;
chunkInfo: any; chunkInfo: any;
} }
export interface chunkgModelType {
namespace: 'chunkModel'; const model: DvaModel<ChunkModelState> = {
state: chunkModelState;
effects: {
chunk_list: Effect;
get_chunk: Effect;
create_hunk: Effect;
switch_chunk: Effect;
rm_chunk: Effect;
};
reducers: {
updateState: Reducer<chunkModelState>;
};
// subscriptions: { setup: Subscription };
}
const Model: chunkgModelType = {
namespace: 'chunkModel', namespace: 'chunkModel',
state: { state: {
loading: false,
data: [], data: [],
total: 0, total: 0,
isShowCreateModal: false, isShowCreateModal: false,
@ -36,6 +20,14 @@ const Model: chunkgModelType = {
doc_id: '', doc_id: '',
chunkInfo: {}, chunkInfo: {},
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
// subscriptions: { // subscriptions: {
// setup({ dispatch, history }) { // setup({ dispatch, history }) {
// history.listen(location => { // history.listen(location => {
@ -44,7 +36,7 @@ const Model: chunkgModelType = {
// } // }
// }, // },
effects: { effects: {
*chunk_list({ payload = {}, callback }, { call, put }) { *chunk_list({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.chunk_list, payload); const { data, response } = yield call(kbService.chunk_list, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
@ -55,28 +47,23 @@ const Model: chunkgModelType = {
payload: { payload: {
data: res.chunks, data: res.chunks,
total: res.total, total: res.total,
loading: false,
}, },
}); });
callback && callback();
} }
}, },
*switch_chunk({ payload = {}, callback }, { call, put }) { *switch_chunk({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.switch_chunk, payload); const { data, response } = yield call(kbService.switch_chunk, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { return retcode;
callback && callback();
}
}, },
*rm_chunk({ payload = {}, callback }, { call, put }) { *rm_chunk({ payload = {} }, { call, put }) {
console.log('shanchu'); console.log('shanchu');
const { data, response } = yield call(kbService.rm_chunk, payload); const { data, response } = yield call(kbService.rm_chunk, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
callback && callback(); return retcode;
}
}, },
*get_chunk({ payload = {}, callback }, { call, put }) { *get_chunk({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.get_chunk, payload); const { data, response } = yield call(kbService.get_chunk, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
@ -86,28 +73,16 @@ const Model: chunkgModelType = {
chunkInfo: res, chunkInfo: res,
}, },
}); });
callback && callback(res);
} }
return data;
}, },
*create_hunk({ payload = {} }, { call, put }) { *create_hunk({ payload = {} }, { call, put }) {
yield put({
type: 'updateState',
payload: {
loading: true,
},
});
let service = kbService.create_chunk; let service = kbService.create_chunk;
if (payload.chunk_id) { if (payload.chunk_id) {
service = kbService.set_chunk; service = kbService.set_chunk;
} }
const { data, response } = yield call(service, payload); const { data, response } = yield call(service, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
yield put({
type: 'updateState',
payload: {
loading: false,
},
});
if (retcode === 0) { if (retcode === 0) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
@ -118,13 +93,5 @@ const Model: chunkgModelType = {
} }
}, },
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
}; };
export default Model; export default model;

View File

@ -1,79 +1,73 @@
import React from 'react' import { Form, Input, Modal } from 'antd';
import { connect, Dispatch } from 'umi'; import React from 'react';
import i18n from 'i18next'; import { useTranslation } from 'react-i18next';
import { useTranslation, Trans } from 'react-i18next' import { useDispatch, useSelector } from 'umi';
import { Input, Modal, Form } from 'antd'
import styles from './index.less';
import type { kFModelState } from './model'
type FieldType = { type FieldType = {
name?: string; name?: string;
}; };
interface kFProps { interface kFProps {
dispatch: Dispatch; getKfList: () => void;
kFModel: kFModelState; kb_id: string;
getKfList: () => void;
kb_id: string
} }
const Index: React.FC<kFProps> = ({ kFModel, dispatch, getKfList, kb_id }) => {
const { isShowCEFwModal } = kFModel
const { t } = useTranslation()
const handleCancel = () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowCEFwModal: false
}
});
};
const [form] = Form.useForm()
const handleOk = async () => {
try {
const values = await form.validateFields();
dispatch({
type: 'kFModel/document_create',
payload: {
name: values.name,
kb_id
},
callback: () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowCEFwModal: false
}
});
getKfList && getKfList()
}
});
} catch (errorInfo) { const FileCreatingModal: React.FC<kFProps> = ({ getKfList, kb_id }) => {
console.log('Failed:', errorInfo); const dispatch = useDispatch();
} const kFModel = useSelector((state: any) => state.kFModel);
}; const { isShowCEFwModal } = kFModel;
const [form] = Form.useForm();
const { t } = useTranslation();
return ( const handleCancel = () => {
<Modal title="Basic Modal" open={isShowCEFwModal} onOk={handleOk} onCancel={handleCancel}> dispatch({
<Form type: 'kFModel/updateState',
form={form} payload: {
name="validateOnly" isShowCEFwModal: false,
labelCol={{ span: 8 }} },
wrapperCol={{ span: 16 }} });
style={{ maxWidth: 600 }} };
autoComplete="off" const handleOk = async () => {
> try {
<Form.Item<FieldType> const values = await form.validateFields();
label="文件名" const retcode = await dispatch<any>({
name="name" type: 'kFModel/document_create',
rules={[{ required: true, message: 'Please input value!' }]} payload: {
> name: values.name,
<Input /> kb_id,
</Form.Item> },
});
if (retcode === 0) {
getKfList && getKfList();
}
} catch (errorInfo) {
console.log('Failed:', errorInfo);
}
};
</Form> return (
</Modal > <Modal
title="Basic Modal"
open={isShowCEFwModal}
); onOk={handleOk}
} onCancel={handleCancel}
export default connect(({ kFModel, loading }) => ({ kFModel, loading }))(Index); >
<Form
form={form}
name="validateOnly"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
autoComplete="off"
>
<Form.Item<FieldType>
label="文件名"
name="name"
rules={[{ required: true, message: 'Please input value!' }]}
>
<Input />
</Form.Item>
</Form>
</Modal>
);
};
export default FileCreatingModal;

View File

@ -1,228 +1,273 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil';
import { connect, Dispatch, useNavigate } from 'umi' import { DownOutlined } from '@ant-design/icons';
import { Space, Table, Input, Button, Switch, Dropdown, } from 'antd';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { DownOutlined } from '@ant-design/icons' import { Button, Dropdown, Input, Space, Switch, Table } from 'antd';
import { debounce } from 'lodash';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import UploadFile from './upload' import { debounce } from 'lodash';
import CreateEPModal from './createEFileModal' import React, { useCallback, useEffect, useMemo, useState } from 'react';
import SegmentSetModal from './segmentSetModal' import { useDispatch, useNavigate, useSelector } from 'umi';
import styles from './index.less' import CreateEPModal from './createEFileModal';
import type { kFModelState } from './model' import styles from './index.less';
import SegmentSetModal from './segmentSetModal';
import UploadFile from './upload';
interface DataType { interface DataType {
name: string; name: string;
chunk_num: string; chunk_num: string;
token_num: number; token_num: number;
update_date: string; update_date: string;
size: string; size: string;
status: string; status: string;
id: string; id: string;
parser_id: string parser_id: string;
} }
interface kFProps { interface KFProps {
dispatch: Dispatch; kb_id: string;
kFModel: kFModelState;
kb_id: string
} }
const Index: React.FC<kFProps> = ({ kFModel, dispatch, kb_id }) => { const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
const { data, loading } = kFModel const dispatch = useDispatch();
const [inputValue, setInputValue] = useState('') const kFModel = useSelector((state: any) => state.kFModel);
const [doc_id, setDocId] = useState('0') const effects = useSelector((state: any) => state.loading.effects);
const [parser_id, setParserId] = useState('0') const { data } = kFModel;
let navigate = useNavigate(); const loading = getOneNamespaceEffectsLoading('kFModel', effects, [
const getKfList = (keywords?: string) => { 'getKfList',
const payload = { 'updateDocumentStatus',
kb_id, ]);
keywords const [inputValue, setInputValue] = useState('');
} const [doc_id, setDocId] = useState('0');
if (!keywords) { const [parser_id, setParserId] = useState('0');
delete payload.keywords let navigate = useNavigate();
}
dispatch({
type: 'kFModel/getKfList',
payload
});
}
useEffect(() => {
if (kb_id) {
getKfList()
}
}, [kb_id])
const debounceChange = debounce(getKfList, 300)
const debounceCallback = useCallback((value: string) => debounceChange(value), [])
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const value = e.target.value
setInputValue(value)
debounceCallback(e.target.value)
} const getKfList = (keywords?: string) => {
const onChangeStatus = (e: boolean, doc_id: string) => { const payload = {
dispatch({ kb_id,
type: 'kFModel/updateDocumentStatus', keywords,
payload: {
doc_id,
status: Number(e)
},
callback() {
getKfList()
}
});
}
const onRmDocument = () => {
dispatch({
type: 'kFModel/document_rm',
payload: {
doc_id
},
callback() {
getKfList()
}
});
}
const showCEFModal = () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowCEFwModal: true
}
});
}; };
if (!keywords) {
const showSegmentSetModal = () => { delete payload.keywords;
dispatch({
type: 'kFModel/updateState',
payload: {
isShowSegmentSetModal: true
}
});
};
const actionItems: MenuProps['items'] = useMemo(() => {
return [
{
key: '1',
label: (
<div>
<UploadFile kb_id={kb_id} getKfList={getKfList} />
</div>
),
},
{
key: '2',
label: (
<div>
<Button type="link" onClick={showCEFModal}> </Button>
</div>
),
// disabled: true,
},
]
}, [kb_id]);
const chunkItems: MenuProps['items'] = [
{
key: '1',
label: (
<div>
<Button type="link" onClick={showSegmentSetModal}> </Button>
</div>
),
},
{
key: '2',
label: (
<div>
<Button type="link" onClick={onRmDocument}> </Button>
</div>
),
// disabled: true,
},
]
const toChunk = (id: string) => {
console.log(id)
navigate(`/knowledge/add/setting?activeKey=file&id=${kb_id}&doc_id=${id}`);
} }
const columns: ColumnsType<DataType> = [ dispatch({
{ type: 'kFModel/getKfList',
title: '名称', payload,
dataIndex: 'name', });
key: 'name', };
render: (text: any, { id }) => <div className={styles.tochunks} onClick={() => toChunk(id)}><img className={styles.img} src='https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg' alt="" />{text}</div>,
className: `${styles.column}` useEffect(() => {
}, if (kb_id) {
{ getKfList();
title: '数据总量', }
dataIndex: 'chunk_num', }, [kb_id]);
key: 'chunk_num',
className: `${styles.column}` const debounceChange = debounce(getKfList, 300);
}, const debounceCallback = useCallback(
{ (value: string) => debounceChange(value),
title: 'Tokens', [],
dataIndex: 'token_num', );
key: 'token_num', const handleInputChange = (
className: `${styles.column}` e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
}, ) => {
{ const value = e.target.value;
title: '文件大小', setInputValue(value);
dataIndex: 'size', debounceCallback(e.target.value);
key: 'size', };
className: `${styles.column}` const onChangeStatus = (e: boolean, doc_id: string) => {
}, dispatch({
{ type: 'kFModel/updateDocumentStatus',
title: '状态', payload: {
key: 'status', doc_id,
dataIndex: 'status', status: Number(e),
className: `${styles.column}`, kb_id,
render: (_, { status: string, id }) => ( },
<> });
<Switch defaultChecked={status === '1'} onChange={(e) => { };
onChangeStatus(e, id) const onRmDocument = () => {
}} /> dispatch({
</> type: 'kFModel/document_rm',
), payload: {
}, doc_id,
{ kb_id,
title: 'Action', },
key: 'action', });
className: `${styles.column}`, };
render: (_, record) => ( const showCEFModal = () => {
<Space size="middle"> dispatch({
<Dropdown menu={{ items: chunkItems }} trigger={['click']}> type: 'kFModel/updateState',
<a onClick={() => { payload: {
setDocId(record.id) isShowCEFwModal: true,
setParserId(record.parser_id) },
}}> });
<DownOutlined /> };
</a>
</Dropdown> const showSegmentSetModal = () => {
</Space> dispatch({
), type: 'kFModel/updateState',
}, payload: {
isShowSegmentSetModal: true,
},
});
};
const actionItems: MenuProps['items'] = useMemo(() => {
return [
{
key: '1',
label: (
<div>
<UploadFile kb_id={kb_id} getKfList={getKfList} />
</div>
),
},
{
key: '2',
label: (
<div>
<Button type="link" onClick={showCEFModal}>
{' '}
</Button>
</div>
),
// disabled: true,
},
]; ];
return <> }, [kb_id]);
<div className={styles.filter}> const chunkItems: MenuProps['items'] = [
<div className="search"> {
<Input placeholder="搜索" value={inputValue} style={{ width: 220 }} allowClear onChange={handleInputChange} /> key: '1',
</div> label: (
<div className="operate"> <div>
<Dropdown menu={{ items: actionItems }} trigger={['click']} > <Button type="link" onClick={showSegmentSetModal}>
<a> {' '}
<DownOutlined />
</a> </Button>
</Dropdown>
</div>
</div> </div>
<Table rowKey='id' columns={columns} dataSource={data} loading={loading} pagination={false} scroll={{ scrollToFirstRowOnChange: true, x: true }} /> ),
<CreateEPModal getKfList={getKfList} kb_id={kb_id} /> },
<SegmentSetModal getKfList={getKfList} parser_id={parser_id} doc_id={doc_id} /> {
key: '2',
label: (
<div>
<Button type="link" onClick={onRmDocument}>
{' '}
</Button>
</div>
),
// disabled: true,
},
];
const toChunk = (id: string) => {
console.log(id);
navigate(`/knowledge/add/setting?activeKey=file&id=${kb_id}&doc_id=${id}`);
};
const columns: ColumnsType<DataType> = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
render: (text: any, { id }) => (
<div className={styles.tochunks} onClick={() => toChunk(id)}>
<img
className={styles.img}
src="https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg"
alt=""
/>
{text}
</div>
),
className: `${styles.column}`,
},
{
title: '数据总量',
dataIndex: 'chunk_num',
key: 'chunk_num',
className: `${styles.column}`,
},
{
title: 'Tokens',
dataIndex: 'token_num',
key: 'token_num',
className: `${styles.column}`,
},
{
title: '文件大小',
dataIndex: 'size',
key: 'size',
className: `${styles.column}`,
},
{
title: '状态',
key: 'status',
dataIndex: 'status',
className: `${styles.column}`,
render: (_, { status: string, id }) => (
<>
<Switch
defaultChecked={status === '1'}
onChange={(e) => {
onChangeStatus(e, id);
}}
/>
</>
),
},
{
title: 'Action',
key: 'action',
className: `${styles.column}`,
render: (_, record) => (
<Space size="middle">
<Dropdown menu={{ items: chunkItems }} trigger={['click']}>
<a
onClick={() => {
setDocId(record.id);
setParserId(record.parser_id);
}}
>
<DownOutlined />
</a>
</Dropdown>
</Space>
),
},
];
return (
<>
<div className={styles.filter}>
<div className="search">
<Input
placeholder="搜索"
value={inputValue}
style={{ width: 220 }}
allowClear
onChange={handleInputChange}
/>
</div>
<div className="operate">
<Dropdown menu={{ items: actionItems }} trigger={['click']}>
<a>
<DownOutlined />
</a>
</Dropdown>
</div>
</div>
<Table
rowKey="id"
columns={columns}
dataSource={data}
loading={loading}
pagination={false}
scroll={{ scrollToFirstRowOnChange: true, x: true }}
/>
<CreateEPModal getKfList={getKfList} kb_id={kb_id} />
<SegmentSetModal
getKfList={getKfList}
parser_id={parser_id}
doc_id={doc_id}
/>
</> </>
);
}; };
export default connect(({ kFModel, loading }) => ({ kFModel, loading }))(Index); export default KnowledgeFile;

View File

@ -1,57 +1,47 @@
import kbService from '@/services/kbService'; import kbService from '@/services/kbService';
import { message } from 'antd'; import { message } from 'antd';
import { Effect, Reducer, Subscription } from 'umi'; import pick from 'lodash/pick';
import { DvaModel } from 'umi';
export interface kFModelState { export interface KFModelState {
isShowCEFwModal: boolean; isShowCEFwModal: boolean;
isShowTntModal: boolean; isShowTntModal: boolean;
isShowSegmentSetModal: boolean; isShowSegmentSetModal: boolean;
loading: boolean;
tenantIfo: any; tenantIfo: any;
data: any[]; data: any[];
} }
export interface kFModelType {
namespace: 'kFModel'; const model: DvaModel<KFModelState> = {
state: kFModelState;
effects: {
createKf: Effect;
updateKf: Effect;
getKfDetail: Effect;
getKfList: Effect;
updateDocumentStatus: Effect;
document_rm: Effect;
document_create: Effect;
document_change_parser: Effect;
};
reducers: {
updateState: Reducer<kFModelState>;
};
subscriptions: { setup: Subscription };
}
const Model: kFModelType = {
namespace: 'kFModel', namespace: 'kFModel',
state: { state: {
isShowCEFwModal: false, isShowCEFwModal: false,
isShowTntModal: false, isShowTntModal: false,
isShowSegmentSetModal: false, isShowSegmentSetModal: false,
loading: false,
tenantIfo: {}, tenantIfo: {},
data: [], data: [],
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen((location) => {}); history.listen((location) => {});
}, },
}, },
effects: { effects: {
*createKf({ payload = {}, callback }, { call, put }) { *createKf({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.createKb, payload); const { data, response } = yield call(kbService.createKb, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
message.success('创建成功!'); message.success('创建成功!');
} }
}, },
*updateKf({ payload = {}, callback }, { call, put }) { *updateKf({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.updateKb, payload); const { data, response } = yield call(kbService.updateKb, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
@ -67,23 +57,12 @@ const Model: kFModelType = {
} }
}, },
*getKfList({ payload = {} }, { call, put }) { *getKfList({ payload = {} }, { call, put }) {
yield put({
type: 'updateState',
payload: {
loading: true,
},
});
const { data, response } = yield call( const { data, response } = yield call(
kbService.get_document_list, kbService.get_document_list,
payload, payload,
); );
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
yield put({
type: 'updateState',
payload: {
loading: false,
},
});
if (retcode === 0) { if (retcode === 0) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
@ -93,64 +72,64 @@ const Model: kFModelType = {
}); });
} }
}, },
*updateDocumentStatus({ payload = {}, callback }, { call, put }) { *updateDocumentStatus({ payload = {} }, { call, put }) {
yield put({
type: 'updateState',
payload: {
loading: true,
},
});
const { data, response } = yield call( const { data, response } = yield call(
kbService.document_change_status, kbService.document_change_status,
payload, pick(payload, ['doc_id', 'status']),
); );
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
message.success('修改成功!'); message.success('修改成功!');
yield put({ put({
type: 'updateState', type: 'getKfList',
payload: { payload: { kb_id: payload.kb_id },
loading: false,
},
}); });
callback && callback();
} }
}, },
*document_rm({ payload = {}, callback }, { call, put }) { *document_rm({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.document_rm, payload); const { data, response } = yield call(kbService.document_rm, {
doc_id: payload.doc_id,
});
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
message.success('删除成功!'); message.success('删除成功!');
callback && callback(); put({
type: 'getKfList',
payload: { kb_id: payload.kb_id },
});
} }
}, },
*document_create({ payload = {}, callback }, { call, put }) { *document_create({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.document_create, payload); const { data, response } = yield call(kbService.document_create, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
put({
type: 'kFModel/updateState',
payload: {
isShowCEFwModal: false,
},
});
message.success('创建成功!'); message.success('创建成功!');
callback && callback();
} }
return retcode;
}, },
*document_change_parser({ payload = {}, callback }, { call, put }) { *document_change_parser({ payload = {} }, { call, put }) {
const { data, response } = yield call( const { data, response } = yield call(
kbService.document_change_parser, kbService.document_change_parser,
payload, payload,
); );
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
put({
type: 'updateState',
payload: {
isShowSegmentSetModal: false,
},
});
message.success('修改成功!'); message.success('修改成功!');
callback && callback();
} }
}, return retcode;
},
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
}, },
}, },
}; };
export default Model; export default model;

View File

@ -1,91 +1,87 @@
import React from 'react'; import { Modal, Space, Tag } from 'antd';
import { connect, Dispatch } from 'umi'; import React, { useEffect, useState } from 'react';
import i18n from 'i18next'; import { useTranslation } from 'react-i18next';
import { useTranslation, } from 'react-i18next' import { useDispatch, useSelector } from 'umi';
import { Modal, Tag, Space } from 'antd'
import { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
import type { kFModelState } from './model'
import type { settingModelState } from '@/pages/setting/model'
const { CheckableTag } = Tag; const { CheckableTag } = Tag;
interface kFProps { interface kFProps {
dispatch: Dispatch; getKfList: () => void;
kFModel: kFModelState; parser_id: string;
settingModel: settingModelState; doc_id: string;
getKfList: () => void;
parser_id: string;
doc_id: string;
} }
const Index: React.FC<kFProps> = ({ kFModel, settingModel, dispatch, getKfList, parser_id, doc_id }) => { const SegmentSetModal: React.FC<kFProps> = ({
const [selectedTag, setSelectedTag] = useState('') getKfList,
const { tenantIfo = {} } = settingModel parser_id,
const { parser_ids = '' } = tenantIfo doc_id,
useEffect(() => { }) => {
dispatch({ const dispatch = useDispatch();
type: 'settingModel/getTenantInfo', const kFModel = useSelector((state: any) => state.kFModel);
payload: { const settingModel = useSelector((state: any) => state.settingModel);
} const [selectedTag, setSelectedTag] = useState('');
}); const { tenantIfo = {} } = settingModel;
setSelectedTag(parser_id) const { parser_ids = '' } = tenantIfo;
}, [parser_id]) const { isShowSegmentSetModal } = kFModel;
const { isShowSegmentSetModal } = kFModel const { t } = useTranslation();
const { t } = useTranslation()
const handleCancel = () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowSegmentSetModal: false
}
});
};
const handleOk = () => {
console.log(1111, selectedTag)
dispatch({
type: 'kFModel/document_change_parser',
payload: {
parser_id: selectedTag,
doc_id
},
callback: () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowSegmentSetModal: false
}
});
getKfList && getKfList()
}
});
};
const handleChange = (tag: string, checked: boolean) => { useEffect(() => {
const nextSelectedTag = checked dispatch({
? tag type: 'settingModel/getTenantInfo',
: selectedTag; payload: {},
console.log('You are interested in: ', nextSelectedTag); });
setSelectedTag(nextSelectedTag); setSelectedTag(parser_id);
}; }, [parser_id]);
return ( const handleCancel = () => {
<Modal title="Basic Modal" open={isShowSegmentSetModal} onOk={handleOk} onCancel={handleCancel}> dispatch({
<Space size={[0, 8]} wrap> type: 'kFModel/updateState',
<div className={styles.tags}> payload: {
{ isShowSegmentSetModal: false,
parser_ids.split(',').map((tag: string) => { },
return (<CheckableTag });
key={tag} };
checked={selectedTag === tag}
onChange={(checked) => handleChange(tag, checked)}
>
{tag}
</CheckableTag>)
})
}
</div>
</Space>
</Modal >
const handleOk = async () => {
console.log(1111, selectedTag);
const retcode = await dispatch<any>({
type: 'kFModel/document_change_parser',
payload: {
parser_id: selectedTag,
doc_id,
},
});
); retcode === 0 && getKfList && getKfList();
} };
export default connect(({ kFModel, settingModel, loading }) => ({ kFModel, settingModel, loading }))(Index);
const handleChange = (tag: string, checked: boolean) => {
const nextSelectedTag = checked ? tag : selectedTag;
console.log('You are interested in: ', nextSelectedTag);
setSelectedTag(nextSelectedTag);
};
return (
<Modal
title="Basic Modal"
open={isShowSegmentSetModal}
onOk={handleOk}
onCancel={handleCancel}
>
<Space size={[0, 8]} wrap>
<div className={styles.tags}>
{parser_ids.split(',').map((tag: string) => {
return (
<CheckableTag
key={tag}
checked={selectedTag === tag}
onChange={(checked) => handleChange(tag, checked)}
>
{tag}
</CheckableTag>
);
})}
</div>
</Space>
</Modal>
);
};
export default SegmentSetModal;

View File

@ -1,33 +1,39 @@
import React from 'react'; import uploadService from '@/services/uploadService';
import { connect } from 'umi'
import type { UploadProps } from 'antd'; import type { UploadProps } from 'antd';
import { Button, Upload } from 'antd'; import { Button, Upload } from 'antd';
import uploadService from '@/services/uploadService' import React from 'react';
interface PropsType { interface PropsType {
kb_id: string; kb_id: string;
getKfList: () => void getKfList: () => void;
} }
type UploadRequestOption = Parameters< type UploadRequestOption = Parameters<
NonNullable<UploadProps["customRequest"]> NonNullable<UploadProps['customRequest']>
>[0]; >[0];
const Index: React.FC<PropsType> = ({ kb_id, getKfList }) => {
const createRequest: (props: UploadRequestOption) => void = async function ({ file, onSuccess, onError }) {
const { retcode, data } = await uploadService.uploadFile(file, kb_id);
if (retcode === 0) {
onSuccess && onSuccess(data, file);
} else { const FileUpload: React.FC<PropsType> = ({ kb_id, getKfList }) => {
onError && onError(data); const createRequest: (props: UploadRequestOption) => void = async function ({
} file,
getKfList && getKfList() onSuccess,
}; onError,
const uploadProps: UploadProps = { }) {
customRequest: createRequest, const { retcode, data } = await uploadService.uploadFile(file, kb_id);
showUploadList: false, if (retcode === 0) {
}; onSuccess && onSuccess(data, file);
return (<Upload {...uploadProps} > } else {
<Button type="link"></Button> onError && onError(data);
</Upload>) }
} getKfList && getKfList();
};
const uploadProps: UploadProps = {
customRequest: createRequest,
showUploadList: false,
};
return (
<Upload {...uploadProps}>
<Button type="link"></Button>
</Upload>
);
};
export default connect(({ kFModel, settingModel, loading }) => ({ kFModel, settingModel, loading }))(Index); export default FileUpload;

View File

@ -1,247 +1,278 @@
import React, { useEffect, useState, useCallback, } from 'react'; import { api_host } from '@/utils/api';
import { useNavigate, connect, Dispatch } from 'umi' import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons';
import { Card, Row, Col, Input, Select, Switch, Pagination, Spin, Button, Popconfirm } from 'antd';
import { MinusSquareOutlined, DeleteOutlined, } from '@ant-design/icons';
import type { PaginationProps } from 'antd'; import type { PaginationProps } from 'antd';
import { api_host } from '@/utils/api' import {
import CreateModal from '../knowledge-chunk/components/createModal' Card,
Col,
Input,
Pagination,
Popconfirm,
Row,
Select,
Spin,
Switch,
} from 'antd';
import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'umi';
import CreateModal from '../knowledge-chunk/components/createModal';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import styles from './index.less'
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import type { kSearchModelState } from './model' import styles from './index.less';
import type { chunkModelState } from '../knowledge-chunk/model'
interface chunkProps { interface chunkProps {
dispatch: Dispatch; kb_id: string;
kSearchModel: kSearchModelState;
chunkModel: chunkModelState;
kb_id: string
} }
const Index: React.FC<chunkProps> = ({ kSearchModel, chunkModel, dispatch, kb_id }) => {
const { data = [], total, loading, d_list = [], question, doc_ids, pagination, } = kSearchModel const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => {
const { chunk_id, doc_id, isShowCreateModal } = chunkModel const dispatch = useDispatch();
const kSearchModel = useSelector((state: any) => state.kSearchModel);
const chunkModel = useSelector((state: any) => state.chunkModel);
const loading = useOneNamespaceEffectsLoading('kSearchModel', [
'chunk_list',
'switch_chunk',
]);
const {
data = [],
total,
d_list = [],
question,
doc_ids,
pagination,
} = kSearchModel;
const { chunk_id, doc_id, isShowCreateModal } = chunkModel;
const getChunkList = () => { const getChunkList = () => {
dispatch({
type: 'kSearchModel/updateState',
payload: {
loading: true
}
});
interface payloadType {
kb_id: string;
question?: string;
doc_ids: any[];
similarity_threshold?: number
}
const payload: payloadType = {
kb_id,
question,
doc_ids,
similarity_threshold: 0.1
}
dispatch({ dispatch({
type: 'kSearchModel/chunk_list', type: 'kSearchModel/chunk_list',
payload: { payload: {
...payload, kb_id,
...pagination },
}
}); });
} };
const confirm = (id: string) => { const confirm = (id: string) => {
console.log(id)
dispatch({ dispatch({
type: 'kSearchModel/rm_chunk', type: 'kSearchModel/rm_chunk',
payload: { payload: {
chunk_ids: [id] chunk_ids: [id],
kb_id,
}, },
callback: getChunkList
}); });
}; };
const handleEditchunk = (item: any) => { const handleEditchunk = (item: any) => {
const { chunk_id, doc_id } = item const { chunk_id, doc_id } = item;
dispatch({ dispatch({
type: 'chunkModel/updateState', type: 'chunkModel/updateState',
payload: { payload: {
isShowCreateModal: true, isShowCreateModal: true,
chunk_id, chunk_id,
doc_id doc_id,
}, },
callback: getChunkList
}); });
} getChunkList();
const onShowSizeChange: PaginationProps['onShowSizeChange'] = (page, size) => { };
const onShowSizeChange: PaginationProps['onShowSizeChange'] = (
page,
size,
) => {
dispatch({ dispatch({
type: 'kSearchModel/updateState', type: 'kSearchModel/updateState',
payload: { payload: {
pagination: { page, size } pagination: { page, size },
} },
}); });
}; };
useEffect(() => { useEffect(() => {
dispatch({ dispatch({
type: 'kSearchModel/updateState', type: 'kSearchModel/updateState',
payload: { payload: {
loading: false,
doc_ids: [], doc_ids: [],
question: "" question: '',
} },
}); });
dispatch({ dispatch({
type: 'kSearchModel/getKfList', type: 'kSearchModel/getKfList',
payload: { payload: {
kb_id kb_id,
} },
}); });
}, []) }, []);
const switchChunk = (item: any, available_int: boolean) => { const switchChunk = (item: any, available_int: boolean) => {
const { chunk_id, doc_id } = item const { chunk_id, doc_id } = item;
dispatch({
type: 'kSearchModel/updateState',
payload: {
loading: true
}
});
dispatch({ dispatch({
type: 'kSearchModel/switch_chunk', type: 'kSearchModel/switch_chunk',
payload: { payload: {
chunk_ids: [chunk_id], chunk_ids: [chunk_id],
doc_id, doc_id,
available_int available_int,
kb_id,
}, },
callback: getChunkList
}); });
} };
useEffect(() => { useEffect(() => {
getChunkList() getChunkList();
}, [doc_ids, pagination, question]) }, [doc_ids, pagination, question]);
const debounceChange = debounce((value) => { const debounceChange = debounce((value) => {
dispatch({ dispatch({
type: 'kSearchModel/updateState', type: 'kSearchModel/updateState',
payload: { payload: {
question: value question: value,
} },
}); });
}, 300) }, 300);
const debounceCallback = useCallback((value: string) => debounceChange(value), [])
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { const debounceCallback = useCallback(
const value = e.target.value (value: string) => debounceChange(value),
debounceCallback(value) [],
} );
const handleSelectChange = (value: const handleInputChange = (
any[]) => { e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const value = e.target.value;
debounceCallback(value);
};
const handleSelectChange = (value: any[]) => {
dispatch({ dispatch({
type: 'kSearchModel/updateState', type: 'kSearchModel/updateState',
payload: { payload: {
doc_ids: value doc_ids: value,
} },
}); });
} };
console.log('loading', loading)
return (<>
<div className={styles.chunkPage}>
<div className={styles.filter}>
<Select
showSearch
placeholder="文件列表"
optionFilterProp="children"
onChange={handleSelectChange}
style={{ width: 300, marginBottom: 20 }}
options={d_list}
fieldNames={{ label: 'name', value: 'id' }}
mode='multiple'
/>
<Input.TextArea autoSize={{ minRows: 6, maxRows: 6 }} placeholder="搜索" style={{ width: 300 }} allowClear onChange={handleInputChange} /> return (
<>
<div className={styles.chunkPage}>
<div className={styles.filter}>
<Select
showSearch
placeholder="文件列表"
optionFilterProp="children"
onChange={handleSelectChange}
style={{ width: 300, marginBottom: 20 }}
options={d_list}
fieldNames={{ label: 'name', value: 'id' }}
mode="multiple"
/>
</div> <Input.TextArea
<div className={styles.pageContainer}> autoSize={{ minRows: 6, maxRows: 6 }}
<div className={styles.pageContent}> placeholder="搜索"
<Spin spinning={loading} className={styles.spin} size='large'> style={{ width: 300 }}
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }} > allowClear
{ onChange={handleInputChange}
data.map((item: any) => {
return (<Col className="gutter-row" key={item.chunk_id} xs={24} sm={12} md={12} lg={8}>
<Card className={styles.card}
onClick={() => { handleEditchunk(item) }}
>
<img style={{ width: '50px' }} src={`${api_host}/document/image/${item.img_id}`} alt="" />
<div className={styles.container}>
<div className={styles.content}>
<span className={styles.context}>
{item.content_ltks}
</span>
<span className={styles.delete}>
<Switch size="small" defaultValue={item.doc_ids == '1'} onChange={(checked: boolean, e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation(); switchChunk(item, checked)
}} />
</span>
</div>
<div className={styles.footer}>
<span className={styles.text}>
<MinusSquareOutlined />{item.doc_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />{item.chunk_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />{item.token_num}
</span>
<span style={{ float: 'right' }}>
<Popconfirm
title="Delete the task"
description="Are you sure to delete this task?"
onConfirm={(e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation()
console.log(confirm)
confirm(item.chunk_id)
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined onClick={(e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation()
}} />
</Popconfirm>
</span>
</div>
</div>
</Card>
</Col>)
})
}
</Row>
</Spin>
</div>
<div className={styles.pageFooter}>
<Pagination
responsive
showLessItems
showQuickJumper
showSizeChanger
onChange={onShowSizeChange}
defaultPageSize={30}
pageSizeOptions={[30, 60, 90]}
defaultCurrent={pagination.page}
total={total}
/> />
</div> </div>
<div className={styles.pageContainer}>
<div className={styles.pageContent}>
<Spin spinning={loading} className={styles.spin} size="large">
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }}>
{data.map((item: any) => {
return (
<Col
className="gutter-row"
key={item.chunk_id}
xs={24}
sm={12}
md={12}
lg={8}
>
<Card
className={styles.card}
onClick={() => {
handleEditchunk(item);
}}
>
<img
style={{ width: '50px' }}
src={`${api_host}/document/image/${item.img_id}`}
alt=""
/>
<div className={styles.container}>
<div className={styles.content}>
<span className={styles.context}>
{item.content_ltks}
</span>
<span className={styles.delete}>
<Switch
size="small"
defaultValue={item.doc_ids == '1'}
onChange={(checked: boolean, e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
switchChunk(item, checked);
}}
/>
</span>
</div>
<div className={styles.footer}>
<span className={styles.text}>
<MinusSquareOutlined />
{item.doc_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />
{item.chunk_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />
{item.token_num}
</span>
<span style={{ float: 'right' }}>
<Popconfirm
title="Delete the task"
description="Are you sure to delete this task?"
onConfirm={(e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
console.log(confirm);
confirm(item.chunk_id);
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined
onClick={(e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
}}
/>
</Popconfirm>
</span>
</div>
</div>
</Card>
</Col>
);
})}
</Row>
</Spin>
</div>
<div className={styles.pageFooter}>
<Pagination
responsive
showLessItems
showQuickJumper
showSizeChanger
onChange={onShowSizeChange}
defaultPageSize={30}
pageSizeOptions={[30, 60, 90]}
defaultCurrent={pagination.page}
total={total}
/>
</div>
</div>
</div> </div>
<CreateModal
</div > getChunkList={getChunkList}
<CreateModal getChunkList={getChunkList} isShowCreateModal={isShowCreateModal} chunk_id={chunk_id} doc_id={doc_id} /> isShowCreateModal={isShowCreateModal}
</> chunk_id={chunk_id}
) doc_id={doc_id}
/>
</>
);
}; };
export default connect(({ kSearchModel, chunkModel, loading }) => ({ kSearchModel, chunkModel, loading }))(Index); export default KnowledgeSearching;

View File

@ -1,8 +1,8 @@
import { Effect, Reducer, Subscription } from 'umi'
import { message } from 'antd';
import kbService from '@/services/kbService'; import kbService from '@/services/kbService';
import omit from 'lodash/omit';
import { DvaModel } from 'umi';
export interface kSearchModelState { export interface KSearchModelState {
loading: boolean; loading: boolean;
data: any[]; data: any[];
total: number; total: number;
@ -13,26 +13,10 @@ export interface kSearchModelState {
question: string; question: string;
doc_ids: any[]; doc_ids: any[];
pagination: any; pagination: any;
doc_id: string doc_id: string;
}
} const model: DvaModel<KSearchModelState> = {
export interface chunkgModelType {
namespace: 'kSearchModel';
state: kSearchModelState;
effects: {
chunk_list: Effect;
get_chunk: Effect;
create_hunk: Effect;
switch_chunk: Effect;
rm_chunk: Effect;
getKfList: Effect;
};
reducers: {
updateState: Reducer<kSearchModelState>;
};
subscriptions: { setup: Subscription };
}
const Model: chunkgModelType = {
namespace: 'kSearchModel', namespace: 'kSearchModel',
state: { state: {
loading: false, loading: false,
@ -45,114 +29,132 @@ const Model: chunkgModelType = {
question: '', question: '',
doc_ids: [], doc_ids: [],
pagination: { page: 1, size: 30 }, pagination: { page: 1, size: 30 },
doc_id: '' doc_id: '',
},
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
}, },
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen(location => { history.listen((location) => {
console.log(location) console.log(location);
}); });
} },
}, },
effects: { effects: {
*getKfList({ payload = {} }, { call, put }) { *getKfList({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.get_document_list, payload); const { data, response } = yield call(
kbService.get_document_list,
payload,
);
const { retcode, data: res, retmsg } = data const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
payload: { payload: {
d_list: res d_list: res,
} },
}); });
} }
}, },
* chunk_list({ payload = {}, callback }, { call, put }) { *chunk_list({ payload = {} }, { call, put, select }) {
const { data, response } = yield call(kbService.retrieval_test, payload); const { question, doc_ids, pagination }: KSearchModelState = yield select(
const { retcode, data: res, retmsg } = data (state: any) => state.kSearchModel,
);
const { data } = yield call(kbService.retrieval_test, {
...payload,
...pagination,
question,
doc_ids,
similarity_threshold: 0.1,
});
const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
console.log(res)
yield put({ yield put({
type: 'updateState', type: 'updateState',
payload: { payload: {
data: res.chunks, data: res.chunks,
total: res.total, total: res.total,
loading: false },
}
}); });
callback && callback()
} }
}, },
*switch_chunk({ payload = {}, callback }, { call, put }) { *switch_chunk({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.switch_chunk, payload); const { data } = yield call(
const { retcode, data: res, retmsg } = data kbService.switch_chunk,
omit(payload, ['kb_id']),
);
const { retcode } = data;
if (retcode === 0) { if (retcode === 0) {
callback && callback() yield put({
type: 'chunk_list',
payload: {
kb_id: payload.kb_id,
},
});
} }
}, },
*rm_chunk({ payload = {}, callback }, { call, put }) { *rm_chunk({ payload = {} }, { call, put }) {
console.log('shanchu') const { data } = yield call(kbService.rm_chunk, {
const { data, response } = yield call(kbService.rm_chunk, payload); chunk_ids: payload.chunk_ids,
const { retcode, data: res, retmsg } = data });
const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
callback && callback() // TODO: Can be extracted
yield put({
type: 'chunk_list',
payload: {
kb_id: payload.kb_id,
},
});
} }
}, },
* get_chunk({ payload = {}, callback }, { call, put }) { *get_chunk({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.get_chunk, payload); const { data, response } = yield call(kbService.get_chunk, payload);
const { retcode, data: res, retmsg } = data const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
payload: { payload: {
chunkInfo: res chunkInfo: res,
} },
}); });
callback && callback(res)
} }
}, },
*create_hunk({ payload = {} }, { call, put }) { *create_hunk({ payload = {} }, { call, put }) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
payload: { payload: {
loading: true loading: true,
} },
}); });
let service = kbService.create_chunk let service = kbService.create_chunk;
if (payload.chunk_id) { if (payload.chunk_id) {
service = kbService.set_chunk service = kbService.set_chunk;
} }
const { data, response } = yield call(service, payload); const { data, response } = yield call(service, payload);
const { retcode, data: res, retmsg } = data const { retcode, data: res, retmsg } = data;
yield put({ yield put({
type: 'updateState', type: 'updateState',
payload: { payload: {
loading: false loading: false,
} },
}); });
if (retcode === 0) { if (retcode === 0) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
payload: { payload: {
isShowCreateModal: false isShowCreateModal: false,
} },
}); });
} }
}, },
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload
};
}
}
}; };
export default Model; export default model;

View File

@ -1,170 +1,157 @@
import React, { useEffect, useState } from 'react'; import { Button, Form, Input, Radio, Select, Space, Tag } from 'antd';
import { useNavigate, connect, Dispatch } from 'umi' import React, { useCallback, useEffect, useState } from 'react';
import { Button, Form, Input, Radio, Select, Tag, Space, } from 'antd'; import { useDispatch, useNavigate, useSelector } from 'umi';
import type { kSModelState } from './model' import styles from './index.less';
import type { settingModelState } from '@/pages/setting/model'
import styles from './index.less'
const { CheckableTag } = Tag; const { CheckableTag } = Tag;
const layout = { const layout = {
labelCol: { span: 8 }, labelCol: { span: 8 },
wrapperCol: { span: 16 }, wrapperCol: { span: 16 },
labelAlign: 'left' as const labelAlign: 'left' as const,
}; };
const { Option } = Select const { Option } = Select;
/* eslint-disable no-template-curly-in-string */ /* eslint-disable no-template-curly-in-string */
interface kSProps { interface kSProps {
dispatch: Dispatch; kb_id: string;
kSModel: kSModelState;
settingModel: settingModelState;
kb_id: string
} }
const Index: React.FC<kSProps> = ({ settingModel, kSModel, dispatch, kb_id }) => { const KnowledgeSetting: React.FC<kSProps> = ({ kb_id }) => {
let navigate = useNavigate(); const dispatch = useDispatch();
const { tenantIfo = {} } = settingModel const settingModel = useSelector((state: any) => state.settingModel);
const { parser_ids = '', embd_id = '' } = tenantIfo let navigate = useNavigate();
const [form] = Form.useForm(); const { tenantIfo = {} } = settingModel;
const { parser_ids = '', embd_id = '' } = tenantIfo;
const [form] = Form.useForm();
const [selectedTag, setSelectedTag] = useState('');
const values = Form.useWatch([], form);
useEffect(() => { const getTenantInfo = useCallback(async () => {
dispatch({
type: 'settingModel/getTenantInfo',
payload: {},
});
if (kb_id) {
const data = await dispatch<any>({
type: 'kSModel/getKbDetail',
payload: {
kb_id,
},
});
if (data.retcode === 0) {
const { description, name, permission, embd_id } = data.data;
form.setFieldsValue({ description, name, permission, embd_id });
setSelectedTag(data.data.parser_id);
}
}
}, [kb_id]);
const onFinish = async () => {
try {
await form.validateFields();
if (kb_id) {
dispatch({ dispatch({
type: 'settingModel/getTenantInfo', type: 'kSModel/updateKb',
payload: { payload: {
} ...values,
parser_id: selectedTag,
kb_id,
embd_id: undefined,
},
}); });
if (kb_id) { } else {
const retcode = await dispatch<any>({
type: 'kSModel/createKb',
payload: {
...values,
parser_id: selectedTag,
},
});
retcode === 0 &&
navigate(`/knowledge/add/setting?activeKey=file&id=${kb_id}`);
}
} catch (error) {
console.warn(error);
}
};
dispatch({ useEffect(() => {
type: 'kSModel/getKbDetail', getTenantInfo();
payload: { }, [getTenantInfo]);
kb_id
},
callback(detail: any) {
console.log(detail)
const { description, name, permission, embd_id } = detail
form.setFieldsValue({ description, name, permission, embd_id })
setSelectedTag(detail.parser_id)
}
});
}
}, [kb_id]) const handleChange = (tag: string, checked: boolean) => {
const [selectedTag, setSelectedTag] = useState('') const nextSelectedTag = checked ? tag : selectedTag;
const values = Form.useWatch([], form); console.log('You are interested in: ', nextSelectedTag);
console.log(values, '......变化') setSelectedTag(nextSelectedTag);
const onFinish = () => { };
form.validateFields().then(
() => {
if (kb_id) {
dispatch({
type: 'kSModel/updateKb',
payload: {
...values,
parser_id: selectedTag,
kb_id,
embd_id: undefined
}
});
} else {
dispatch({
type: 'kSModel/createKb',
payload: {
...values,
parser_id: selectedTag
},
callback(id: string) {
navigate(`/knowledge/add/setting?activeKey=file&id=${kb_id}`);
}
});
}
},
() => {
}, return (
); <Form
{...layout}
form={form}
name="validateOnly"
}; style={{ maxWidth: 1000, padding: 14 }}
const handleChange = (tag: string, checked: boolean) => {
const nextSelectedTag = checked
? tag
: selectedTag;
console.log('You are interested in: ', nextSelectedTag);
setSelectedTag(nextSelectedTag);
};
return <Form
{...layout}
form={form}
name="validateOnly"
style={{ maxWidth: 1000, padding: 14 }}
> >
<Form.Item name='name' label="知识库名称" rules={[{ required: true }]}> <Form.Item name="name" label="知识库名称" rules={[{ required: true }]}>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item name='description' label="知识库描述"> <Form.Item name="description" label="知识库描述">
<Input.TextArea /> <Input.TextArea />
</Form.Item> </Form.Item>
<Form.Item name="permission" label="可见权限"> <Form.Item name="permission" label="可见权限">
<Radio.Group> <Radio.Group>
<Radio value="me"></Radio> <Radio value="me"></Radio>
<Radio value="team"></Radio> <Radio value="team"></Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="embd_id" name="embd_id"
label="Embedding 模型" label="Embedding 模型"
hasFeedback hasFeedback
rules={[{ required: true, message: 'Please select your country!' }]} rules={[{ required: true, message: 'Please select your country!' }]}
> >
<Select placeholder="Please select a country" > <Select placeholder="Please select a country">
{embd_id.split(',').map((item: string) => { {embd_id.split(',').map((item: string) => {
return <Option value={item} key={item}>{item}</Option> return (
})} <Option value={item} key={item}>
{item}
</Select> </Option>
</Form.Item> );
<div style={{ marginTop: '5px' }}> })}
Embedding <span style={{ color: '#1677ff' }}></span> </Select>
</Form.Item>
<div style={{ marginTop: '5px' }}>
Embedding <span style={{ color: '#1677ff' }}></span>
</div>
<Space size={[0, 8]} wrap>
<div className={styles.tags}>
{parser_ids.split(',').map((tag: string) => {
return (
<CheckableTag
key={tag}
checked={selectedTag === tag}
onChange={(checked) => handleChange(tag, checked)}
>
{tag}
</CheckableTag>
);
})}
</div> </div>
<Space size={[0, 8]} wrap> </Space>
<div className={styles.tags}> <Space size={[0, 8]} wrap></Space>
{ <div className={styles.preset}>
parser_ids.split(',').map((tag: string) => { <div className={styles.left}>xxxxx文章</div>
return (<CheckableTag <div className={styles.right}></div>
key={tag} </div>
checked={selectedTag === tag} <Form.Item wrapperCol={{ ...layout.wrapperCol, offset: 8 }}>
onChange={(checked) => handleChange(tag, checked)} <Button type="primary" onClick={onFinish}>
>
{tag} </Button>
</CheckableTag>) <Button htmlType="button" style={{ marginLeft: '20px' }}>
})
} </Button>
</div> </Form.Item>
</Space>
<Space size={[0, 8]} wrap>
</Space>
<div className={styles.preset}>
<div className={styles.left}>
xxxxx文章
</div>
<div className={styles.right}>
</div>
</div>
<Form.Item wrapperCol={{ ...layout.wrapperCol, offset: 8 }}>
<Button type="primary" onClick={onFinish}>
</Button>
<Button htmlType="button" style={{ marginLeft: '20px' }}>
</Button>
</Form.Item>
</Form> </Form>
} );
};
export default KnowledgeSetting;
export default connect(({ settingModel, kSModel, loading }) => ({ settingModel, kSModel, loading }))(Index);

View File

@ -1,72 +1,54 @@
import { message } from 'antd';
import { Effect, Reducer, Subscription } from 'umi'
import kbService from '@/services/kbService'; import kbService from '@/services/kbService';
import { message } from 'antd';
import { DvaModel } from 'umi';
export interface kSModelState { export interface KSModelState {
isShowPSwModal: boolean; isShowPSwModal: boolean;
isShowTntModal: boolean; isShowTntModal: boolean;
loading: boolean; tenantIfo: any;
tenantIfo: any
} }
export interface kSModelType {
namespace: 'kSModel'; const model: DvaModel<KSModelState> = {
state: kSModelState;
effects: {
createKb: Effect;
updateKb: Effect;
getKbDetail: Effect;
};
reducers: {
updateState: Reducer<kSModelState>;
};
subscriptions: { setup: Subscription };
}
const Model: kSModelType = {
namespace: 'kSModel', namespace: 'kSModel',
state: { state: {
isShowPSwModal: false, isShowPSwModal: false,
isShowTntModal: false, isShowTntModal: false,
loading: false, tenantIfo: {},
tenantIfo: {}
},
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
});
}
},
effects: {
* createKb({ payload = {}, callback }, { call, put }) {
const { data, response } = yield call(kbService.createKb, payload);
const { retcode, data: res, retmsg } = data
if (retcode === 0) {
message.success('创建知识库成功!');
callback && callback(res.kb_id)
}
},
* updateKb({ payload = {}, callback }, { call, put }) {
const { data, response } = yield call(kbService.updateKb, payload);
const { retcode, data: res, retmsg } = data
if (retcode === 0) {
message.success('更新知识库成功!');
}
},
*getKbDetail({ payload = {}, callback }, { call, put }) {
const { data, response } = yield call(kbService.get_kb_detail, payload);
const { retcode, data: res, retmsg } = data
if (retcode === 0) {
// localStorage.setItem('userInfo',res.)
callback && callback(res)
}
},
}, },
reducers: { reducers: {
updateState(state, { payload }) { updateState(state, { payload }) {
return { return {
...state, ...state,
...payload ...payload,
}; };
} },
} },
subscriptions: {
setup({ dispatch, history }) {
history.listen((location) => {});
},
},
effects: {
*createKb({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.createKb, payload);
const { retcode } = data;
if (retcode === 0) {
message.success('创建知识库成功!');
}
return retcode;
},
*updateKb({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.updateKb, payload);
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
message.success('更新知识库成功!');
}
},
*getKbDetail({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.get_kb_detail, payload);
return data;
},
},
}; };
export default Model; export default model;

View File

@ -1,123 +1,120 @@
import { connect, useNavigate, useLocation, Dispatch } from 'umi' import { getWidth } from '@/utils';
import React, { useState, useEffect, useMemo } from 'react'; import { BarsOutlined, SearchOutlined, ToolOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Menu } from 'antd'; import { Menu } from 'antd';
import { import React, { useEffect, useMemo, useState } from 'react';
ToolOutlined, import { useDispatch, useLocation, useNavigate, useSelector } from 'umi';
BarsOutlined, import Chunk from './components/knowledge-chunk';
SearchOutlined import File from './components/knowledge-file';
} from '@ant-design/icons'; import Search from './components/knowledge-search';
import File from './components/knowledge-file' import Setting from './components/knowledge-setting';
import Setting from './components/knowledge-setting' import styles from './index.less';
import Search from './components/knowledge-search'
import Chunk from './components/knowledge-chunk'
import styles from './index.less'
import { getWidth } from '@/utils'
import { kAModelState } from './model'
const KnowledgeAdding = () => {
const dispatch = useDispatch();
const kAModel = useSelector((state: any) => state.kAModel);
const { id, activeKey, doc_id } = kAModel;
interface kAProps { const [collapsed, setCollapsed] = useState(false);
dispatch: Dispatch; const [windowWidth, setWindowWidth] = useState(getWidth());
kAModel: kAModelState; let navigate = useNavigate();
} const location = useLocation();
const Index: React.FC<kAProps> = ({ kAModel, dispatch }) => {
const [collapsed, setCollapsed] = useState(false);
const { id, activeKey, doc_id } = kAModel
const [windowWidth, setWindowWidth] = useState(getWidth());
let navigate = useNavigate();
const location = useLocation();
// 标记一下
console.log(doc_id, '>>>>>>>>>>>>>doc_id')
useEffect(() => {
const widthSize = () => {
const width = getWidth()
console.log(width)
setWindowWidth(width); // 标记一下
}; console.log(doc_id, '>>>>>>>>>>>>>doc_id');
window.addEventListener("resize", widthSize); useEffect(() => {
return () => { const widthSize = () => {
window.removeEventListener("resize", widthSize); const width = getWidth();
}; console.log(width);
}, []);
useEffect(() => {
console.log(location)
const search = location.search.slice(1)
const map = search.split('&').reduce((obj, cur) => {
const [key, value] = cur.split('=')
obj[key] = value
return obj
}, {})
dispatch({
type: 'kAModel/updateState',
payload: {
doc_id: undefined,
...map,
setWindowWidth(width);
};
window.addEventListener('resize', widthSize);
return () => {
window.removeEventListener('resize', widthSize);
};
}, []);
useEffect(() => {
const search: string = location.search.slice(1);
const map = search.split('&').reduce<Record<string, string>>((obj, cur) => {
const [key, value] = cur.split('=');
obj[key] = value;
return obj;
}, {});
dispatch({
type: 'kAModel/updateState',
payload: {
doc_id: undefined,
...map,
},
});
}, [location]);
useEffect(() => {
if (windowWidth.width > 957) {
setCollapsed(false);
} else {
setCollapsed(true);
}
}, [windowWidth.width]);
type MenuItem = Required<MenuProps>['items'][number];
function getItem(
label: React.ReactNode,
key: React.Key,
icon?: React.ReactNode,
disabled?: boolean,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
disabled,
} as MenuItem;
}
const items: MenuItem[] = useMemo(() => {
const disabled = !id;
return [
getItem('配置', 'setting', <ToolOutlined />),
getItem('知识库', 'file', <BarsOutlined />, disabled),
getItem('搜索测试', 'search', <SearchOutlined />, disabled),
];
}, [id]);
const handleSelect: MenuProps['onSelect'] = (e) => {
navigate(`/knowledge/add/setting?activeKey=${e.key}&id=${id}`);
};
return (
<>
<div className={styles.container}>
<div className={styles.menu}>
<Menu
selectedKeys={[activeKey]}
mode="inline"
className={
windowWidth.width > 957 ? styles.defaultWidth : styles.minWidth
} }
}); inlineCollapsed={collapsed}
}, [location]) items={items}
useEffect(() => { onSelect={handleSelect}
if (windowWidth.width > 957) { />
setCollapsed(false) </div>
} else { <div className={styles.content}>
setCollapsed(true) {activeKey === 'file' && !doc_id && <File kb_id={id} />}
} {activeKey === 'setting' && <Setting kb_id={id} />}
}, [windowWidth.width]) {activeKey === 'search' && <Search kb_id={id} />}
type MenuItem = Required<MenuProps>['items'][number]; {activeKey === 'file' && !!doc_id && <Chunk doc_id={doc_id} />}
</div>
function getItem( </div>
label: React.ReactNode, </>
key: React.Key, );
icon?: React.ReactNode,
disabled?: boolean,
children?: MenuItem[],
type?: 'group',
): MenuItem {
return {
key,
icon,
children,
label,
type,
disabled
} as MenuItem;
}
const items: MenuItem[] = useMemo(() => {
const disabled = !id
return [
getItem('配置', 'setting', <ToolOutlined />),
getItem('知识库', 'file', <BarsOutlined />, disabled),
getItem('搜索测试', 'search', <SearchOutlined />, disabled),
]
}, [id]);
const handleSelect: MenuProps['onSelect'] = (e) => {
navigate(`/knowledge/add/setting?activeKey=${e.key}&id=${id}`);
}
return (
<>
<div className={styles.container}>
<div className={styles.menu}>
<Menu
selectedKeys={[activeKey]}
mode="inline"
className={windowWidth.width > 957 ? styles.defaultWidth : styles.minWidth}
inlineCollapsed={collapsed}
items={items}
onSelect={handleSelect}
/>
</div>
<div className={styles.content}>
{activeKey === 'file' && !doc_id && <File kb_id={id} />}
{activeKey === 'setting' && <Setting kb_id={id} />}
{activeKey === 'search' && <Search kb_id={id} />}
{activeKey === 'file' && !!doc_id && <Chunk doc_id={doc_id} />}
</div>
</div>
</>
);
}; };
export default connect(({ kAModel, loading }) => ({ kAModel, loading }))(Index); export default KnowledgeAdding;

View File

@ -1,6 +1,4 @@
import { Effect, Reducer, Subscription } from 'umi' import { DvaModel } from 'umi';
import { message } from 'antd';
import kbService from '@/services/kbService';
export interface kAModelState { export interface kAModelState {
isShowPSwModal: boolean; isShowPSwModal: boolean;
isShowTntModal: boolean; isShowTntModal: boolean;
@ -8,20 +6,10 @@ export interface kAModelState {
tenantIfo: any; tenantIfo: any;
activeKey: string; activeKey: string;
id: string; id: string;
doc_id: string doc_id: string;
} }
export interface kAModelType {
namespace: 'kAModel';
state: kAModelState;
effects: {
}; const model: DvaModel<kAModelState> = {
reducers: {
updateState: Reducer<kAModelState>;
};
subscriptions: { setup: Subscription };
}
const Model: kAModelType = {
namespace: 'kAModel', namespace: 'kAModel',
state: { state: {
isShowPSwModal: false, isShowPSwModal: false,
@ -30,25 +18,21 @@ const Model: kAModelType = {
tenantIfo: {}, tenantIfo: {},
activeKey: 'setting', activeKey: 'setting',
id: '', id: '',
doc_id: '' doc_id: '',
},
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
});
}
},
effects: {
}, },
reducers: { reducers: {
updateState(state, { payload }) { updateState(state, { payload }) {
return { return {
...state, ...state,
...payload ...payload,
}; };
} },
} },
subscriptions: {
setup({ dispatch, history }) {
history.listen((location) => {});
},
},
effects: {},
}; };
export default Model; export default model;

View File

@ -1,15 +1,8 @@
import React from 'react'; import { useSelector } from 'umi';
import { connect, Dispatch } from 'umi';
import type { chatModelState } from './model'
interface chatProps { const Chat = () => {
chatModel: chatModelState; const { name } = useSelector((state: any) => state.chatModel);
dispatch: Dispatch return <div>chat:{name} </div>;
}
const View: React.FC<chatProps> = ({ chatModel, dispatch }) => {
const { name } = chatModel;
return <div>chat:{name} </div>;
}; };
export default connect(({ chatModel, loading }) => ({ chatModel, loading }))(View); export default Chat;

View File

@ -1,46 +1,32 @@
import { Effect, Reducer, Subscription } from 'umi'; import { DvaModel } from 'umi';
export interface chatModelState { export interface ChatModelState {
name: string; name: string;
} }
export interface chatModelType { const model: DvaModel<ChatModelState> = {
namespace: 'chatModel'; namespace: 'chatModel',
state: chatModelState; state: {
effects: { name: 'kate',
query: Effect; },
}; reducers: {
reducers: { save(state, action) {
save: Reducer<chatModelState>; return {
}; ...state,
subscriptions: { setup: Subscription }; ...action.payload,
} };
const Model: chatModelType = {
namespace: 'chatModel',
state: {
name: 'kate',
}, },
},
effects: { subscriptions: {
*query({ payload }, { call, put }) { }, setup({ dispatch, history }) {
}, return history.listen((query) => {
reducers: { console.log(query);
save(state, action) { });
return {
...state,
...action.payload,
};
},
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen((query) => {
console.log(query)
});
},
}, },
},
effects: {
*query({ payload }, { call, put }) {},
},
}; };
export default Model; export default model;

View File

@ -1,51 +1,50 @@
import React, { useEffect, useState } from 'react';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { Button, Upload } from 'antd'; import { Button, Upload } from 'antd';
import type { UploadFile } from 'antd/es/upload/interface'; import React, { useEffect, useState } from 'react';
const File: React.FC = () => {
const App: React.FC = () => { const [fileList, setFileList] = useState([
const [fileList, setFileList] = useState([{ {
uid: '0', uid: '0',
name: 'xxx.png', name: 'xxx.png',
status: 'uploading', status: 'uploading',
percent: 10, percent: 10,
}]) },
const obj = { ]);
uid: '-1', const obj = {
name: 'yyy.png', uid: '-1',
status: 'done', name: 'yyy.png',
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', status: 'done',
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
} thumbUrl:
useEffect(() => { 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
const timer = setInterval(() => { };
setFileList((fileList: any) => { useEffect(() => {
const percent = fileList[0]?.percent const timer = setInterval(() => {
if (percent + 10 >= 100) { setFileList((fileList: any) => {
clearInterval(timer) const percent = fileList[0]?.percent;
return [obj] if (percent + 10 >= 100) {
} clearInterval(timer);
const list = [{ ...fileList[0], percent: percent + 10 }] return [obj];
console.log(list) }
return list const list = [{ ...fileList[0], percent: percent + 10 }];
console.log(list);
}) return list;
}, 300) });
}, []) }, 300);
return ( }, []);
return (
<> <>
<Upload <Upload
action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188" action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
listType="picture" listType="picture"
fileList={[...fileList]} fileList={[...fileList]}
multiple multiple
> >
<Button icon={<UploadOutlined />}>Upload</Button> <Button icon={<UploadOutlined />}>Upload</Button>
</Upload> </Upload>
</> </>
) );
}; };
export default App; export default File;

View File

@ -5,21 +5,22 @@ import {
PlusOutlined, PlusOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Card, Col, FloatButton, Popconfirm, Row } from 'antd'; import { Card, Col, FloatButton, Popconfirm, Row } from 'antd';
import React, { useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { Dispatch, connect, useNavigate } from 'umi'; import { useDispatch, useNavigate, useSelector } from 'umi';
import styles from './index.less'; import styles from './index.less';
import type { knowledgeModelState } from './model';
interface KnowledgeProps {
dispatch: Dispatch;
knowledgeModel: knowledgeModelState;
}
const Index: React.FC<KnowledgeProps> = ({ knowledgeModel, dispatch }) => {
const navigate = useNavigate();
// const [datas, setDatas] = useState(data)
const { data = [] } = knowledgeModel;
console.log(knowledgeModel);
// const x = useSelector((state) => state.knowledgeModel); const Knowledge = () => {
const dispatch = useDispatch();
const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
const navigate = useNavigate();
const { data = [] } = knowledgeModel;
const fetchList = useCallback(() => {
dispatch({
type: 'knowledgeModel/getList',
payload: {},
});
}, []);
const confirm = (id: string) => { const confirm = (id: string) => {
dispatch({ dispatch({
@ -27,12 +28,6 @@ const Index: React.FC<KnowledgeProps> = ({ knowledgeModel, dispatch }) => {
payload: { payload: {
kb_id: id, kb_id: id,
}, },
callback: () => {
dispatch({
type: 'knowledgeModel/getList',
payload: {},
});
},
}); });
}; };
const handleAddKnowledge = () => { const handleAddKnowledge = () => {
@ -42,11 +37,8 @@ const Index: React.FC<KnowledgeProps> = ({ knowledgeModel, dispatch }) => {
navigate(`add/setting?activeKey=file&id=${id}`); navigate(`add/setting?activeKey=file&id=${id}`);
}; };
useEffect(() => { useEffect(() => {
dispatch({ fetchList();
type: 'knowledgeModel/getList', }, [fetchList]);
payload: {},
});
}, []);
return ( return (
<> <>
<div className={styles.knowledge}> <div className={styles.knowledge}>
@ -125,7 +117,4 @@ const Index: React.FC<KnowledgeProps> = ({ knowledgeModel, dispatch }) => {
); );
}; };
export default connect(({ knowledgeModel, loading }) => ({ export default Knowledge;
knowledgeModel,
loading,
}))(Index);

View File

@ -1,58 +1,38 @@
import kbService from '@/services/kbService'; import kbService from '@/services/kbService';
import { Effect, Reducer } from 'umi'; import { DvaModel } from 'umi';
export interface knowledgeModelState { export interface KnowledgeModelState {
loading: boolean;
data: any[]; data: any[];
} }
export interface knowledgegModelType {
namespace: 'knowledgeModel'; const model: DvaModel<KnowledgeModelState> = {
state: knowledgeModelState;
effects: {
rmKb: Effect;
getList: Effect;
};
reducers: {
updateState: Reducer<knowledgeModelState>;
};
// subscriptions: { setup: Subscription };
}
const Model: knowledgegModelType = {
namespace: 'knowledgeModel', namespace: 'knowledgeModel',
state: { state: {
loading: false,
data: [], data: [],
}, },
// subscriptions: { reducers: {
// setup({ dispatch, history }) { updateState(state, { payload }) {
// history.listen((location) => { return {
// console.log(location); ...state,
// }); ...payload,
// }, };
// }, },
},
effects: { effects: {
*rmKb({ payload = {}, callback }, { call, put }) { *rmKb({ payload = {}, callback }, { call, put }) {
const { data, response } = yield call(kbService.rmKb, payload); const { data } = yield call(kbService.rmKb, payload);
const { retcode, data: res, retmsg } = data; const { retcode } = data;
if (retcode === 0) { if (retcode === 0) {
callback && callback(); yield put({
type: 'getList',
payload: {},
});
} }
}, },
*getList({ payload = {} }, { call, put }) { *getList({ payload = {} }, { call, put }) {
yield put({ const { data } = yield call(kbService.getList, payload);
type: 'updateState',
payload: {
loading: true,
},
});
const { data, response } = yield call(kbService.getList, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
yield put({
type: 'updateState',
payload: {
loading: false,
},
});
if (retcode === 0) { if (retcode === 0) {
yield put({ yield put({
type: 'updateState', type: 'updateState',
@ -63,13 +43,5 @@ const Model: knowledgegModelType = {
} }
}, },
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
}; };
export default Model; export default model;

View File

@ -1,15 +1,20 @@
import { rsaPsw } from '@/utils'; import { rsaPsw } from '@/utils';
import { Button, Checkbox, Form, Input } from 'antd'; import { Button, Checkbox, Form, Input } from 'antd';
import { FC, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Dispatch, Icon, connect, useNavigate } from 'umi'; import { Icon, useDispatch, useNavigate, useSelector } from 'umi';
import styles from './index.less'; import styles from './index.less';
interface LoginProps { const Login = () => {
dispatch: Dispatch;
}
const View: FC<LoginProps> = ({ dispatch }) => {
let navigate = useNavigate();
const [title, setTitle] = useState('login'); const [title, setTitle] = useState('login');
let navigate = useNavigate();
const dispatch = useDispatch();
const effectsLoading: any = useSelector<any>( // TODO: Type needs to be improved
(state) => state.loading.effects,
);
const signLoading =
effectsLoading['loginModel/login'] || effectsLoading['loginModel/register'];
const changeTitle = () => { const changeTitle = () => {
setTitle((title) => (title === 'login' ? 'register' : 'login')); setTitle((title) => (title === 'login' ? 'register' : 'login'));
}; };
@ -26,27 +31,29 @@ const View: FC<LoginProps> = ({ dispatch }) => {
var rsaPassWord = rsaPsw(params.password); var rsaPassWord = rsaPsw(params.password);
if (title === 'login') { if (title === 'login') {
const ret = await dispatch({ const retcode = await dispatch<any>({
type: 'loginModel/login', type: 'loginModel/login',
payload: { payload: {
email: params.email, email: params.email,
password: rsaPassWord, password: rsaPassWord,
}, },
}); });
console.info(ret); if (retcode === 0) {
navigate('/knowledge'); navigate('/knowledge');
}
} else { } else {
dispatch({ // TODO: Type needs to be improved
const retcode = await dispatch<any>({
type: 'loginModel/register', type: 'loginModel/register',
payload: { payload: {
nickname: params.nickname, nickname: params.nickname,
email: params.email, email: params.email,
password: rsaPassWord, password: rsaPassWord,
}, },
callback() {
setTitle('login');
},
}); });
if (retcode === 0) {
setTitle('login');
}
} }
} catch (errorInfo) { } catch (errorInfo) {
console.log('Failed:', errorInfo); console.log('Failed:', errorInfo);
@ -106,7 +113,7 @@ const View: FC<LoginProps> = ({ dispatch }) => {
label="Password" label="Password"
rules={[{ required: true, message: 'Please input value' }]} rules={[{ required: true, message: 'Please input value' }]}
> >
<Input size="large" placeholder="Please input value" /> <Input.Password size="large" placeholder="Please input value" />
</Form.Item> </Form.Item>
{title === 'login' && ( {title === 'login' && (
<Form.Item name="remember" valuePropName="checked"> <Form.Item name="remember" valuePropName="checked">
@ -132,7 +139,13 @@ const View: FC<LoginProps> = ({ dispatch }) => {
</div> </div>
)} )}
</div> </div>
<Button type="primary" block size="large" onClick={onCheck}> <Button
type="primary"
block
size="large"
onClick={onCheck}
loading={signLoading}
>
{title === 'login' ? 'Sign in' : 'Continue'} {title === 'login' ? 'Sign in' : 'Continue'}
</Button> </Button>
{title === 'login' && ( {title === 'login' && (
@ -175,6 +188,4 @@ const View: FC<LoginProps> = ({ dispatch }) => {
); );
}; };
export default connect(({ loginModel, loading }) => ({ loginModel, loading }))( export default Login;
View,
);

View File

@ -2,32 +2,29 @@ import { Authorization } from '@/constants/authorization';
import userService from '@/services/userService'; import userService from '@/services/userService';
import authorizationUtil from '@/utils/authorizationUtil'; import authorizationUtil from '@/utils/authorizationUtil';
import { message } from 'antd'; import { message } from 'antd';
import { Effect, Reducer, Subscription } from 'umi'; import { DvaModel } from 'umi';
export interface loginModelState { export interface LoginModelState {
list: any[]; list: any[];
info: any; info: any;
visible: boolean; visible: boolean;
} }
export interface logingModelType {
namespace: 'loginModel'; const model: DvaModel<LoginModelState> = {
state: loginModelState;
effects: {
login: Effect;
register: Effect;
};
reducers: {
updateState: Reducer<loginModelState>;
};
subscriptions: { setup: Subscription };
}
const Model: logingModelType = {
namespace: 'loginModel', namespace: 'loginModel',
state: { state: {
list: [], list: [],
info: {}, info: {},
visible: false, visible: false,
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen((location) => {}); history.listen((location) => {});
@ -53,29 +50,18 @@ const Model: logingModelType = {
userInfo: JSON.stringify(userInfo), userInfo: JSON.stringify(userInfo),
Token: token, Token: token,
}); });
// setTimeout(() => {
// window.location.href = '/file';
// }, 300);
} }
return data; return retcode;
}, },
*register({ payload = {}, callback }, { call, put }) { *register({ payload = {} }, { call, put }) {
const { data, response } = yield call(userService.register, payload); const { data, response } = yield call(userService.register, payload);
console.log(); console.log();
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;
if (retcode === 0) { if (retcode === 0) {
message.success('注册成功!'); message.success('注册成功!');
callback && callback();
} }
}, return retcode;
},
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
}, },
}, },
}; };
export default Model; export default model;

View File

@ -1,9 +1,9 @@
import userService from '@/services/userService'; import userService from '@/services/userService';
import authorizationUtil from '@/utils/authorizationUtil'; import authorizationUtil from '@/utils/authorizationUtil';
import { message } from 'antd'; import { message } from 'antd';
import { Effect, Reducer, Subscription } from 'umi'; import { DvaModel } from 'umi';
export interface settingModelState { export interface SettingModelState {
isShowPSwModal: boolean; isShowPSwModal: boolean;
isShowTntModal: boolean; isShowTntModal: boolean;
isShowSAKModal: boolean; isShowSAKModal: boolean;
@ -16,25 +16,7 @@ export interface settingModelState {
factoriesList: any[]; factoriesList: any[];
} }
export interface settingModelType { const model: DvaModel<SettingModelState> = {
namespace: 'settingModel';
state: settingModelState;
effects: {
setting: Effect;
getUserInfo: Effect;
getTenantInfo: Effect;
set_tenant_info: Effect;
factories_list: Effect;
llm_list: Effect;
my_llm: Effect;
set_api_key: Effect;
};
reducers: {
updateState: Reducer<settingModelState>;
};
subscriptions: { setup: Subscription };
}
const Model: settingModelType = {
namespace: 'settingModel', namespace: 'settingModel',
state: { state: {
isShowPSwModal: false, isShowPSwModal: false,
@ -48,6 +30,14 @@ const Model: settingModelType = {
myLlm: [], myLlm: [],
factoriesList: [], factoriesList: [],
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
history.listen((location) => {}); history.listen((location) => {});
@ -176,13 +166,5 @@ const Model: settingModelType = {
} }
}, },
}, },
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
}; };
export default Model; export default model;

View File

@ -19,101 +19,83 @@ const {
get_chunk, get_chunk,
switch_chunk, switch_chunk,
rm_chunk, rm_chunk,
retrieval_test } = api; retrieval_test,
interface kbService { } = api;
createKb: () => void;
updateKb: () => void; const methods = {
rmKb: () => void; // 知识库管理
get_kb_detail: () => void; createKb: {
getList: () => void; url: create_kb,
get_document_list: () => void; method: 'post',
document_change_status: () => void;
document_rm: () => void;
document_create: () => void;
document_change_parser: () => void;
chunk_list: () => void;
create_chunk: () => void;
set_chunk: () => void;
get_chunk: () => void;
switch_chunk: () => void;
rm_chunk: () => void;
retrieval_test: () => void;
}
const kbService: kbService = registerServer(
{
// 知识库管理
createKb: {
url: create_kb,
method: 'post'
},
updateKb: {
url: update_kb,
method: 'post'
},
rmKb: {
url: rm_kb,
method: 'post'
},
get_kb_detail: {
url: get_kb_detail,
method: 'get'
},
getList: {
url: kb_list,
method: 'get'
},
// 文件管理
get_document_list: {
url: get_document_list,
method: 'get'
},
document_change_status: {
url: document_change_status,
method: 'post'
},
document_rm: {
url: document_rm,
method: 'post'
},
document_create: {
url: document_create,
method: 'post'
},
document_change_parser: {
url: document_change_parser,
method: 'post'
},
// chunk管理
chunk_list: {
url: chunk_list,
method: 'post'
},
create_chunk: {
url: create_chunk,
method: 'post'
},
set_chunk: {
url: set_chunk,
method: 'post'
},
get_chunk: {
url: get_chunk,
method: 'get'
},
switch_chunk: {
url: switch_chunk,
method: 'post'
},
rm_chunk: {
url: rm_chunk,
method: 'post'
},
retrieval_test: {
url: retrieval_test,
method: 'post'
},
}, },
request updateKb: {
); url: update_kb,
method: 'post',
},
rmKb: {
url: rm_kb,
method: 'post',
},
get_kb_detail: {
url: get_kb_detail,
method: 'get',
},
getList: {
url: kb_list,
method: 'get',
},
// 文件管理
get_document_list: {
url: get_document_list,
method: 'get',
},
document_change_status: {
url: document_change_status,
method: 'post',
},
document_rm: {
url: document_rm,
method: 'post',
},
document_create: {
url: document_create,
method: 'post',
},
document_change_parser: {
url: document_change_parser,
method: 'post',
},
// chunk管理
chunk_list: {
url: chunk_list,
method: 'post',
},
create_chunk: {
url: create_chunk,
method: 'post',
},
set_chunk: {
url: set_chunk,
method: 'post',
},
get_chunk: {
url: get_chunk,
method: 'get',
},
switch_chunk: {
url: switch_chunk,
method: 'post',
},
rm_chunk: {
url: rm_chunk,
method: 'post',
},
retrieval_test: {
url: retrieval_test,
method: 'post',
},
};
const kbService = registerServer<keyof typeof methods>(methods, request);
export default kbService; export default kbService;

View File

@ -3,55 +3,61 @@ import registerServer from '@/utils/registerServer';
import request from '@/utils/request'; import request from '@/utils/request';
const { const {
login, register, setting, user_info, tenant_info, factories_list, llm_list, my_llm, set_api_key, set_tenant_info } = api; login,
interface userServiceType { register,
login: (params: any) => void setting,
} user_info,
const userService = registerServer( tenant_info,
{ factories_list,
login: { llm_list,
url: login, my_llm,
method: 'post', set_api_key,
set_tenant_info,
} = api;
}, const methods = {
register: { login: {
url: register, url: login,
method: 'post' method: 'post',
},
setting: {
url: setting,
method: 'post'
},
user_info: {
url: user_info,
method: 'get'
},
get_tenant_info: {
url: tenant_info,
method: 'get'
},
set_tenant_info: {
url: set_tenant_info,
method: 'post'
},
factories_list: {
url: factories_list,
method: 'get'
},
llm_list: {
url: llm_list,
method: 'get'
},
my_llm: {
url: my_llm,
method: 'get'
},
set_api_key: {
url: set_api_key,
method: 'post'
},
}, },
request register: {
); url: register,
method: 'post',
},
setting: {
url: setting,
method: 'post',
},
user_info: {
url: user_info,
method: 'get',
},
get_tenant_info: {
url: tenant_info,
method: 'get',
},
set_tenant_info: {
url: set_tenant_info,
method: 'post',
},
factories_list: {
url: factories_list,
method: 'get',
},
llm_list: {
url: llm_list,
method: 'get',
},
my_llm: {
url: my_llm,
method: 'get',
},
set_api_key: {
url: set_api_key,
method: 'post',
},
} as const;
const userService = registerServer<keyof typeof methods>(methods, request);
export default userService; export default userService;

View File

@ -1,17 +1,24 @@
const registerServer = (opt: any, request: any): any => { import { RequestMethod } from 'umi-request';
let server = {};
type Service<T extends string> = Record<T, (params: any) => any>;
const registerServer = <T extends string>(
opt: Record<T, { url: string; method: string }>,
request: RequestMethod,
) => {
const server: Service<T> = {} as Service<T>;
for (let key in opt) { for (let key in opt) {
server[key] = (params: any) => { server[key] = (params) => {
if (opt[key].method === 'post' || opt[key].method === 'POST') { if (opt[key].method === 'post' || opt[key].method === 'POST') {
return request(opt[key].url, { return request(opt[key].url, {
method: opt[key].method, method: opt[key].method,
data: params data: params,
}); });
} }
if (opt[key].method === 'get' || opt[key].method === 'GET') { if (opt[key].method === 'get' || opt[key].method === 'GET') {
return request.get(opt[key].url, { return request.get(opt[key].url, {
params params,
}); });
} }
}; };

View File

@ -1,5 +1,5 @@
import { message, notification } from 'antd'; import { message, notification } from 'antd';
import { extend } from 'umi-request'; import { RequestMethod, extend } from 'umi-request';
import { Authorization } from '@/constants/authorization'; import { Authorization } from '@/constants/authorization';
import api from '@/utils/api'; import api from '@/utils/api';
@ -9,7 +9,7 @@ const { login } = api;
const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message
const retcodeMessage = { const RetcodeMessage = {
200: '服务器成功返回请求的数据。', 200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。', 201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。', 202: '一个请求已经进入后台排队(异步任务)。',
@ -26,7 +26,7 @@ const retcodeMessage = {
503: '服务不可用,服务器暂时过载或维护。', 503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。', 504: '网关超时。',
}; };
type retcode = type ResultCode =
| 200 | 200
| 201 | 201
| 202 | 202
@ -45,7 +45,7 @@ type retcode =
/** /**
* *
*/ */
interface responseType { interface ResponseType {
retcode: number; retcode: number;
data: any; data: any;
retmsg: string; retmsg: string;
@ -62,7 +62,7 @@ const errorHandler = (error: {
} else { } else {
if (response && response.status) { if (response && response.status) {
const errorText = const errorText =
retcodeMessage[response.status as retcode] || response.statusText; RetcodeMessage[response.status as ResultCode] || response.statusText;
const { status, url } = response; const { status, url } = response;
notification.error({ notification.error({
message: `请求错误 ${status}: ${url}`, message: `请求错误 ${status}: ${url}`,
@ -81,7 +81,7 @@ const errorHandler = (error: {
/** /**
* request请求时的默认参数 * request请求时的默认参数
*/ */
const request = extend({ const request: RequestMethod = extend({
errorHandler, // 默认错误处理 errorHandler, // 默认错误处理
timeout: 3000000, timeout: 3000000,
getResponse: true, getResponse: true,
@ -108,7 +108,7 @@ request.interceptors.request.use((url: string, options: any) => {
request.interceptors.response.use(async (response: any, request) => { request.interceptors.response.use(async (response: any, request) => {
console.log(response, request); console.log(response, request);
const data: responseType = await response.clone().json(); const data: ResponseType = await response.clone().json();
// response 拦截 // response 拦截
if (data.retcode === 401 || data.retcode === 401) { if (data.retcode === 401 || data.retcode === 401) {

View File

@ -0,0 +1,9 @@
export const getOneNamespaceEffectsLoading = (
namespace: string,
effects: Record<string, boolean>,
effectNames: Array<string>,
) => {
return effectNames.some(
(effectName) => effects[`${namespace}/${effectName}`],
);
};