add front end code (#27)
This commit is contained in:
17
web/src/pages/404.jsx
Normal file
17
web/src/pages/404.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
import { history } from 'umi';
|
||||
|
||||
const NoFoundPage = () => {
|
||||
return (<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="页面未找到,请输入正确的地址。"
|
||||
extra={< Button type="primary" onClick={() => history.push('/')}>
|
||||
返回主页
|
||||
</Button>}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export default NoFoundPage;
|
||||
@@ -0,0 +1,72 @@
|
||||
import { connect } from 'umi';
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Input, Modal, Form } from 'antd'
|
||||
import { rsaPsw } from '@/utils'
|
||||
import styles from './index.less';
|
||||
|
||||
type FieldType = {
|
||||
name?: string;
|
||||
};
|
||||
const Index = ({ 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) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="Basic Modal" open={isShowCEFwModal} onOk={handleOk} onCancel={handleCancel}>
|
||||
<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 name!' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
</Modal >
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
export default connect(({ kFModel, loading }) => ({ kFModel, loading }))(Index);
|
||||
@@ -0,0 +1,24 @@
|
||||
.filter {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
justify-content: space-between;
|
||||
|
||||
.search {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.operate {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.column {
|
||||
min-width: 200px
|
||||
}
|
||||
218
web/src/pages/add-knowledge/components/knowledge-file/index.tsx
Normal file
218
web/src/pages/add-knowledge/components/knowledge-file/index.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { connect, useNavigate, useLocation } from 'umi'
|
||||
import { Space, Table, Tag, Input, Button, Switch, Popover, Dropdown, } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { PlusOutlined, DownOutlined } from '@ant-design/icons'
|
||||
import { debounce } from 'lodash';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import UploadFile from './upload'
|
||||
import CreateEPModal from './createEFileModal'
|
||||
import SegmentSetModal from './segmentSetModal'
|
||||
import styles from './index.less'
|
||||
|
||||
interface DataType {
|
||||
name: string;
|
||||
chunk_num: string;
|
||||
token_num: number;
|
||||
update_date: string;
|
||||
size: string;
|
||||
status: string;
|
||||
id: string;
|
||||
parser_id: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Index: React.FC = ({ kFModel, dispatch, id }) => {
|
||||
const { data, loading } = kFModel
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
const [doc_id, setDocId] = useState('0')
|
||||
const [parser_id, setParserId] = useState('0')
|
||||
const changeValue = (value: string) => {
|
||||
{
|
||||
console.log(value)
|
||||
}
|
||||
}
|
||||
const getKfList = () => {
|
||||
dispatch({
|
||||
type: 'kFModel/getKfList',
|
||||
payload: {
|
||||
kb_id: id
|
||||
}
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
getKfList()
|
||||
}
|
||||
}, [id])
|
||||
const debounceChange = debounce(changeValue, 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 onChangeStatus = (e: boolean, doc_id: string) => {
|
||||
dispatch({
|
||||
type: 'kFModel/updateDocumentStatus',
|
||||
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
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const showSegmentSetModal = () => {
|
||||
dispatch({
|
||||
type: 'kFModel/updateState',
|
||||
payload: {
|
||||
isShowSegmentSetModal: true
|
||||
}
|
||||
});
|
||||
};
|
||||
const actionItems: MenuProps['items'] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<div>
|
||||
<UploadFile kb_id={id} getKfList={getKfList} />
|
||||
</div>
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<div>
|
||||
<Button type="link" onClick={showCEFModal}> 导入虚拟文件</Button>
|
||||
</div>
|
||||
),
|
||||
// disabled: true,
|
||||
},
|
||||
]
|
||||
}, [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 columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (text) => <a><img className={styles.img} src='https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg' alt="" />{text}</a>,
|
||||
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} 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={id} />
|
||||
<SegmentSetModal getKfList={getKfList} parser_id={parser_id} doc_id={doc_id} />
|
||||
</>
|
||||
};
|
||||
|
||||
export default connect(({ kFModel, loading }) => ({ kFModel, loading }))(Index);
|
||||
127
web/src/pages/add-knowledge/components/knowledge-file/model.ts
Normal file
127
web/src/pages/add-knowledge/components/knowledge-file/model.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { message } from 'antd';
|
||||
import { addParam } from '@/utils';
|
||||
import kbService from '@/services/kbService';
|
||||
|
||||
const Model = {
|
||||
namespace: 'kFModel',
|
||||
state: {
|
||||
isShowCEFwModal: false,
|
||||
isShowTntModal: false,
|
||||
isShowSegmentSetModal: false,
|
||||
loading: false,
|
||||
tenantIfo: {}
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
});
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
* createKf({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(kbService.createKb, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
|
||||
message.success('创建成功!');
|
||||
}
|
||||
},
|
||||
* updateKf({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(kbService.updateKb, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('修改成功!');
|
||||
|
||||
}
|
||||
},
|
||||
*getKfDetail({ 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)
|
||||
}
|
||||
},
|
||||
*getKfList({ payload = {} }, { call, put }) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: true
|
||||
}
|
||||
});
|
||||
const { data, response } = yield call(kbService.get_document_list, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: false
|
||||
}
|
||||
});
|
||||
if (retcode === 0) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
data: res
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
*updateDocumentStatus({ payload = {}, callback }, { call, put }) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: true
|
||||
}
|
||||
});
|
||||
const { data, response } = yield call(kbService.document_change_status, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('修改成功!');
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: false
|
||||
}
|
||||
});
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
},
|
||||
*document_rm({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(kbService.document_rm, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('删除成功!');
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
},
|
||||
*document_create({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(kbService.document_create, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('创建成功!');
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
},
|
||||
*document_change_parser({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(kbService.document_change_parser, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('修改成功!');
|
||||
callback && callback()
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
updateState(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Model;
|
||||
@@ -0,0 +1,84 @@
|
||||
import { connect } from 'umi';
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Input, Modal, Form, Tag, Space } from 'antd'
|
||||
import { rsaPsw } from '@/utils'
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './index.less';
|
||||
const { CheckableTag } = Tag;
|
||||
type FieldType = {
|
||||
name?: string;
|
||||
};
|
||||
const Index = ({ kFModel, settingModel, dispatch, getKfList, parser_id, doc_id }) => {
|
||||
const [selectedTag, setSelectedTag] = useState('')
|
||||
const { tenantIfo = {} } = settingModel
|
||||
const { parser_ids = '' } = tenantIfo
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'settingModel/getTenantInfo',
|
||||
payload: {
|
||||
}
|
||||
});
|
||||
setSelectedTag(parser_id)
|
||||
}, [parser_id])
|
||||
const { isShowSegmentSetModal } = kFModel
|
||||
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) => {
|
||||
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 connect(({ kFModel, settingModel, loading }) => ({ kFModel, settingModel, loading }))(Index);
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'umi'
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import type { UploadProps } from 'antd';
|
||||
import { Button, message, Upload } from 'antd';
|
||||
import uploadService from '@/services/uploadService'
|
||||
|
||||
|
||||
const Index = ({ kb_id, getKfList }) => {
|
||||
console.log(kb_id)
|
||||
const createRequest = async function ({ file, onSuccess, onError }) {
|
||||
const { retcode, data } = await uploadService.uploadFile(file, kb_id);
|
||||
if (retcode === 0) {
|
||||
onSuccess(data, file);
|
||||
|
||||
} else {
|
||||
onError(data);
|
||||
}
|
||||
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);
|
||||
@@ -0,0 +1,3 @@
|
||||
export default () => {
|
||||
return <div>知识库搜索</div>
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
.tags {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.preset {
|
||||
display: flex;
|
||||
height: 80px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100px;
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.4);
|
||||
margin: 10px 0px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate, connect } from 'umi'
|
||||
import { Button, Form, Input, InputNumber, Radio, Select, Tag, Space, Avatar, Divider, List, Skeleton } from 'antd';
|
||||
import styles from './index.less'
|
||||
const { CheckableTag } = Tag;
|
||||
const layout = {
|
||||
labelCol: { span: 8 },
|
||||
wrapperCol: { span: 16 },
|
||||
labelAlign: 'left' as const
|
||||
};
|
||||
const { Option } = Select
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
const validateMessages = {
|
||||
required: '${label} is required!',
|
||||
types: {
|
||||
email: '${label} is not a valid email!',
|
||||
number: '${label} is not a valid number!',
|
||||
},
|
||||
number: {
|
||||
range: '${label} must be between ${min} and ${max}',
|
||||
},
|
||||
};
|
||||
/* eslint-enable no-template-curly-in-string */
|
||||
|
||||
|
||||
interface DataType {
|
||||
gender: string;
|
||||
name: {
|
||||
title: string;
|
||||
first: string;
|
||||
last: string;
|
||||
};
|
||||
email: string;
|
||||
picture: {
|
||||
large: string;
|
||||
medium: string;
|
||||
thumbnail: string;
|
||||
};
|
||||
nat: string;
|
||||
}
|
||||
const tags = [{ title: '研报' }, { title: '法律' }, { title: '简历' }, { title: '说明书' }, { title: '书籍' }, { title: '演讲稿' }]
|
||||
|
||||
const Index: React.FC = ({ settingModel, kSModel, dispatch, ...props }) => {
|
||||
let navigate = useNavigate();
|
||||
const { tenantIfo = {} } = settingModel
|
||||
const { parser_ids = '', embd_id = '' } = tenantIfo
|
||||
const { id = '' } = props
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'settingModel/getTenantInfo',
|
||||
payload: {
|
||||
}
|
||||
});
|
||||
if (id) {
|
||||
|
||||
dispatch({
|
||||
type: 'kSModel/getKbDetail',
|
||||
payload: {
|
||||
kb_id: 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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}, [id])
|
||||
const [selectedTag, setSelectedTag] = useState('')
|
||||
const values = Form.useWatch([], form);
|
||||
console.log(values, '......变化')
|
||||
const onFinish = () => {
|
||||
form.validateFields().then(
|
||||
() => {
|
||||
if (id) {
|
||||
dispatch({
|
||||
type: 'kSModel/updateKb',
|
||||
payload: {
|
||||
...values,
|
||||
parser_id: selectedTag,
|
||||
kb_id: id,
|
||||
embd_id: undefined
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'kSModel/createKb',
|
||||
payload: {
|
||||
...values,
|
||||
parser_id: selectedTag
|
||||
},
|
||||
callback(id: string) {
|
||||
navigate(`/knowledge/add/setting?activeKey=file&id=${id}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
() => {
|
||||
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
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 }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label="知识库描述">
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
<Form.Item name="permission" label="可见权限">
|
||||
<Radio.Group>
|
||||
<Radio value="me">只有我</Radio>
|
||||
<Radio value="team">所有团队成员</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="embd_id"
|
||||
label="Embedding 模型"
|
||||
hasFeedback
|
||||
rules={[{ required: true, message: 'Please select your country!' }]}
|
||||
>
|
||||
<Select placeholder="Please select a country" disabled={id}>
|
||||
{embd_id.split(',').map((item: string) => {
|
||||
return <Option value={item} key={item}>{item}</Option>
|
||||
})}
|
||||
|
||||
</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>
|
||||
</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>
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default connect(({ settingModel, kSModel, loading }) => ({ settingModel, kSModel, loading }))(Index);
|
||||
@@ -0,0 +1,53 @@
|
||||
import { message } from 'antd';
|
||||
import { addParam } from '@/utils';
|
||||
import kbService from '@/services/kbService';
|
||||
|
||||
const Model = {
|
||||
namespace: 'kSModel',
|
||||
state: {
|
||||
isShowPSwModal: false,
|
||||
isShowTntModal: false,
|
||||
loading: false,
|
||||
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: {
|
||||
updateState(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Model;
|
||||
19
web/src/pages/add-knowledge/index.less
Normal file
19
web/src/pages/add-knowledge/index.less
Normal file
@@ -0,0 +1,19 @@
|
||||
.container {
|
||||
display: flex;
|
||||
|
||||
.menu {
|
||||
.defaultWidth {
|
||||
width: 256px;
|
||||
}
|
||||
|
||||
.minWidth {
|
||||
width: 50px
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
106
web/src/pages/add-knowledge/index.tsx
Normal file
106
web/src/pages/add-knowledge/index.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { connect, useNavigate, useLocation } from 'umi'
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Radio, Space, Tabs, Menu } from 'antd';
|
||||
import {
|
||||
ToolOutlined,
|
||||
BarsOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons';
|
||||
import File from './components/knowledge-file'
|
||||
import Setting from './components/knowledge-setting'
|
||||
import Search from './components/knowledge-search'
|
||||
import styles from './index.less'
|
||||
import { getWidth } from '@/utils'
|
||||
|
||||
|
||||
const Index: React.FC = ({ kAModel, dispatch }) => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const { id, activeKey } = kAModel
|
||||
const [windowWidth, setWindowWidth] = useState(getWidth());
|
||||
let navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
// 标记一下
|
||||
useEffect(() => {
|
||||
const widthSize = () => {
|
||||
const width = getWidth()
|
||||
console.log(width)
|
||||
|
||||
setWindowWidth(width);
|
||||
};
|
||||
window.addEventListener("resize", widthSize);
|
||||
return () => {
|
||||
window.removeEventListener("resize", widthSize);
|
||||
};
|
||||
}, []);
|
||||
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: {
|
||||
...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,
|
||||
children?: MenuItem[],
|
||||
type?: 'group',
|
||||
): MenuItem {
|
||||
return {
|
||||
key,
|
||||
icon,
|
||||
children,
|
||||
label,
|
||||
type,
|
||||
} as MenuItem;
|
||||
}
|
||||
const items: MenuItem[] = [
|
||||
getItem('配置', 'setting', <ToolOutlined />),
|
||||
getItem('知识库', 'file', <BarsOutlined />),
|
||||
getItem('搜索测试', 'search', <SearchOutlined />),
|
||||
];
|
||||
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' && <File id={id} />}
|
||||
{activeKey === 'setting' && <Setting id={id} />}
|
||||
{activeKey === 'search' && <Search id={id} />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ kAModel, loading }) => ({ kAModel, loading }))(Index);
|
||||
34
web/src/pages/add-knowledge/model.ts
Normal file
34
web/src/pages/add-knowledge/model.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { message } from 'antd';
|
||||
import { addParam } from '@/utils';
|
||||
import kbService from '@/services/kbService';
|
||||
|
||||
const Model = {
|
||||
namespace: 'kAModel',
|
||||
state: {
|
||||
isShowPSwModal: false,
|
||||
isShowTntModal: false,
|
||||
loading: false,
|
||||
tenantIfo: {},
|
||||
activeKey: 'setting',
|
||||
id: ''
|
||||
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
});
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
|
||||
},
|
||||
reducers: {
|
||||
updateState(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Model;
|
||||
19
web/src/pages/chat/index.tsx
Normal file
19
web/src/pages/chat/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { FC } from 'react';
|
||||
import { IndexModelState, ConnectProps, Loading, connect } from 'umi';
|
||||
|
||||
interface PageProps extends ConnectProps {
|
||||
index: IndexModelState;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
const IndexPage: FC<PageProps> = ({ index, dispatch }) => {
|
||||
const { name } = index;
|
||||
return <div>chat: {name}</div>;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
({ index, loading }: { index: IndexModelState; loading: Loading }) => ({
|
||||
index,
|
||||
loading: loading.models.index,
|
||||
}),
|
||||
)(IndexPage);
|
||||
52
web/src/pages/chat/model.ts
Normal file
52
web/src/pages/chat/model.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Effect, ImmerReducer, Reducer, Subscription } from 'umi';
|
||||
|
||||
export interface IndexModelState {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface IndexModelType {
|
||||
namespace: 'index';
|
||||
state: IndexModelState;
|
||||
effects: {
|
||||
query: Effect;
|
||||
};
|
||||
reducers: {
|
||||
save: Reducer<IndexModelState>;
|
||||
// 启用 immer 之后
|
||||
// save: ImmerReducer<IndexModelState>;
|
||||
};
|
||||
subscriptions: { setup: Subscription };
|
||||
}
|
||||
|
||||
const IndexModel: IndexModelType = {
|
||||
namespace: 'index',
|
||||
state: {
|
||||
name: 'kate',
|
||||
},
|
||||
|
||||
effects: {
|
||||
*query({ payload }, { call, put }) { },
|
||||
},
|
||||
reducers: {
|
||||
save(state, action) {
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
// 启用 immer 之后
|
||||
// save(state, action) {
|
||||
// state.name = action.payload;
|
||||
// },
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
return history.listen((query) => {
|
||||
console.log(query)
|
||||
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default IndexModel;
|
||||
51
web/src/pages/file/index.tsx
Normal file
51
web/src/pages/file/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
import { Button, Upload } from 'antd';
|
||||
import type { UploadFile } from 'antd/es/upload/interface';
|
||||
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [fileList, setFileList] = useState([{
|
||||
uid: '0',
|
||||
name: 'xxx.png',
|
||||
status: 'uploading',
|
||||
percent: 10,
|
||||
}])
|
||||
const obj = {
|
||||
uid: '-1',
|
||||
name: 'yyy.png',
|
||||
status: 'done',
|
||||
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||
thumbUrl: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||
}
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setFileList((fileList) => {
|
||||
const percent = fileList[0]?.percent
|
||||
if (percent + 10 >= 100) {
|
||||
clearInterval(timer)
|
||||
return [obj]
|
||||
}
|
||||
const list = [{ ...fileList[0], percent: percent + 10 }]
|
||||
console.log(list)
|
||||
return list
|
||||
|
||||
})
|
||||
}, 300)
|
||||
}, [])
|
||||
return (
|
||||
|
||||
<>
|
||||
<Upload
|
||||
action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
|
||||
listType="picture"
|
||||
fileList={[...fileList]}
|
||||
multiple
|
||||
>
|
||||
<Button icon={<UploadOutlined />}>Upload</Button>
|
||||
</Upload>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default App;
|
||||
41
web/src/pages/knowledge/index.less
Normal file
41
web/src/pages/knowledge/index.less
Normal file
@@ -0,0 +1,41 @@
|
||||
.knowledge {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.context {
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 20px;
|
||||
|
||||
.text {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
:global {
|
||||
.ant-card-body {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
109
web/src/pages/knowledge/index.tsx
Normal file
109
web/src/pages/knowledge/index.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import React, { useEffect, useState, } from 'react';
|
||||
import { useNavigate, connect } from 'umi'
|
||||
import { Card, List, Popconfirm, message, FloatButton, Row, Col } from 'antd';
|
||||
import { MinusSquareOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import styles from './index.less'
|
||||
import { formatDate } from '@/utils/date'
|
||||
|
||||
const dd = [{
|
||||
title: 'Title 4',
|
||||
text: '4',
|
||||
des: '111'
|
||||
}]
|
||||
const Index: React.FC = ({ knowledgeModel, dispatch }) => {
|
||||
const navigate = useNavigate()
|
||||
// const [datas, setDatas] = useState(data)
|
||||
const { data } = knowledgeModel
|
||||
const confirm = (id) => {
|
||||
dispatch({
|
||||
type: 'knowledgeModel/rmKb',
|
||||
payload: {
|
||||
kb_id: id
|
||||
},
|
||||
callback: () => {
|
||||
dispatch({
|
||||
type: 'knowledgeModel/getList',
|
||||
payload: {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const handleAddKnowledge = () => {
|
||||
navigate(`add/setting?activeKey=setting`);
|
||||
}
|
||||
const handleEditKnowledge = (id: string) => {
|
||||
navigate(`add/setting?activeKey=file&id=${id}`);
|
||||
}
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'knowledgeModel/getList',
|
||||
payload: {
|
||||
|
||||
}
|
||||
});
|
||||
}, [])
|
||||
return (<>
|
||||
<div className={styles.knowledge}>
|
||||
<FloatButton onClick={handleAddKnowledge} icon={<PlusOutlined />} type="primary" style={{ right: 24, top: 100 }} />
|
||||
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
||||
{
|
||||
data.map((item, index) => {
|
||||
return (<Col className="gutter-row" key={item.title} xs={24} sm={12} md={8} lg={6}>
|
||||
<Card className={styles.card}
|
||||
onClick={() => { handleEditKnowledge(item.id) }}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<span className={styles.context}>
|
||||
{item.name}
|
||||
</span>
|
||||
<span className={styles.delete}>
|
||||
<Popconfirm
|
||||
title="Delete the task"
|
||||
description="Are you sure to delete this task?"
|
||||
onConfirm={(e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
confirm(item.id)
|
||||
|
||||
}}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
>
|
||||
<DeleteOutlined onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
}} />
|
||||
</Popconfirm>
|
||||
|
||||
</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' }}>
|
||||
{formatDate(item.update_date)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
</Col>)
|
||||
})
|
||||
}
|
||||
</Row>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export default connect(({ knowledgeModel, loading }) => ({ knowledgeModel, loading }))(Index);
|
||||
61
web/src/pages/knowledge/model.ts
Normal file
61
web/src/pages/knowledge/model.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { message } from 'antd';
|
||||
import { addParam } from '@/utils';
|
||||
import kbService from '@/services/kbService';
|
||||
|
||||
const Model = {
|
||||
namespace: 'knowledgeModel',
|
||||
state: {
|
||||
loading: false,
|
||||
data: []
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
console.log(location)
|
||||
});
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
* rmKb({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(kbService.rmKb, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
callback && callback()
|
||||
|
||||
}
|
||||
},
|
||||
*getList({ payload = {} }, { call, put }) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: true
|
||||
}
|
||||
});
|
||||
const { data, response } = yield call(kbService.getList, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: false
|
||||
}
|
||||
});
|
||||
if (retcode === 0) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
data: res
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
updateState(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Model;
|
||||
63
web/src/pages/login/index.less
Normal file
63
web/src/pages/login/index.less
Normal file
@@ -0,0 +1,63 @@
|
||||
@import '../../theme/vars';
|
||||
|
||||
.loginPage {
|
||||
// width: 100%;
|
||||
display: flex;
|
||||
|
||||
|
||||
.loginLeft {
|
||||
// width: 610px;
|
||||
width: 40%;
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width:957px) {
|
||||
.loginLeft {
|
||||
// width: 610px;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.loginRight {
|
||||
flex: 1;
|
||||
background-color: #F2F4F7;
|
||||
;
|
||||
}
|
||||
|
||||
.loginTitle {
|
||||
//styleName: Heading/1;
|
||||
font-family: SF Pro Text;
|
||||
font-size: 38px;
|
||||
font-weight: 600;
|
||||
line-height: 46px;
|
||||
letter-spacing: 0em;
|
||||
height: 80px;
|
||||
margin-bottom: 69px;
|
||||
|
||||
// text-align: center;
|
||||
span {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
|
||||
color: #000000A6;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
// width: 360px;
|
||||
width: 60%;
|
||||
height: 724px;
|
||||
padding: 5px, 0px, 5px, 0px;
|
||||
margin: 80px auto
|
||||
}
|
||||
157
web/src/pages/login/index.tsx
Normal file
157
web/src/pages/login/index.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import { connect, Icon, Dispatch } from 'umi';
|
||||
import { Input, Form, Button, Checkbox } from 'antd';
|
||||
import styles from './index.less';
|
||||
import { rsaPsw } from '@/utils'
|
||||
import { useState, useEffect, FC } from 'react';
|
||||
interface LoginProps {
|
||||
dispatch: Dispatch;
|
||||
}
|
||||
const View: FC<LoginProps> = ({
|
||||
dispatch,
|
||||
}) => {
|
||||
const [title, setTitle] = useState('login')
|
||||
const changeTitle = () => {
|
||||
setTitle((title) => title === 'login' ? 'register' : 'login')
|
||||
}
|
||||
const [form] = Form.useForm();
|
||||
const [checkNick, setCheckNick] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
form.validateFields(['nickname']);
|
||||
}, [checkNick, form]);
|
||||
|
||||
const onCheck = async () => {
|
||||
try {
|
||||
const params = await form.validateFields();
|
||||
|
||||
var rsaPassWord = rsaPsw(params.password)
|
||||
if (title === 'login') {
|
||||
dispatch({
|
||||
type: 'loginModel/login',
|
||||
payload: {
|
||||
email: params.email,
|
||||
password: rsaPassWord
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'loginModel/register',
|
||||
payload: {
|
||||
nickname: params.nickname,
|
||||
email: params.email,
|
||||
password: rsaPassWord,
|
||||
},
|
||||
callback() {
|
||||
setTitle('login')
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 6 },
|
||||
// wrapperCol: { span: 8 },
|
||||
};
|
||||
|
||||
|
||||
const toGoogle = () => {
|
||||
window.location.href = "https://github.com/login/oauth/authorize?scope=user:email&client_id=302129228f0d96055bee"
|
||||
}
|
||||
return (
|
||||
<div className={styles.loginPage}>
|
||||
|
||||
<div className={styles.loginLeft}>
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.loginTitle}>
|
||||
<div>
|
||||
{title === 'login' ? 'Sign in' : 'Create an account'}
|
||||
</div>
|
||||
<span >
|
||||
{title === 'login' ? 'We’re so excited to see you again!' : 'Glad to have you on board!'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Form form={form} layout="vertical" name="dynamic_rule" style={{ maxWidth: 600 }}>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="email"
|
||||
label="Email"
|
||||
rules={[{ required: true, message: 'Please input your name' }]}
|
||||
>
|
||||
<Input size='large' placeholder="Please input your name" />
|
||||
</Form.Item>
|
||||
{
|
||||
title === 'register' && <Form.Item
|
||||
{...formItemLayout}
|
||||
name="nickname"
|
||||
label="Nickname"
|
||||
rules={[{ required: true, message: 'Please input your nickname' }]}
|
||||
>
|
||||
<Input size='large' placeholder="Please input your nickname" />
|
||||
</Form.Item>
|
||||
}
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="password"
|
||||
label="Password"
|
||||
rules={[{ required: true, message: 'Please input your name' }]}
|
||||
>
|
||||
<Input size='large' placeholder="Please input your name" />
|
||||
</Form.Item>
|
||||
{
|
||||
title === 'login' && <Form.Item
|
||||
name="remember"
|
||||
valuePropName="checked"
|
||||
|
||||
>
|
||||
<Checkbox> Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
}
|
||||
<div> {
|
||||
title === 'login' && (<div>
|
||||
Don’t have an account?
|
||||
<Button type="link" onClick={changeTitle}>
|
||||
Sign up
|
||||
</Button>
|
||||
</div>)
|
||||
}
|
||||
{
|
||||
title === 'register' && (<div>
|
||||
Already have an account?
|
||||
<Button type="link" onClick={changeTitle}>
|
||||
Sign in
|
||||
</Button>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
<Button type="primary" block size='large' onClick={onCheck}>
|
||||
{title === 'login' ? 'Sign in' : 'Continue'}
|
||||
</Button>
|
||||
{
|
||||
title === 'login' && (<><Button block size='large' onClick={toGoogle} style={{ marginTop: 15 }}>
|
||||
<div >
|
||||
<Icon icon="local:google" style={{ verticalAlign: 'middle', marginRight: 5 }} />
|
||||
Sign in with Google
|
||||
</div>
|
||||
</Button>
|
||||
<Button block size='large' onClick={toGoogle} style={{ marginTop: 15 }}>
|
||||
<div >
|
||||
<Icon icon="local:github" style={{ verticalAlign: 'middle', marginRight: 5 }} />
|
||||
Sign in with Github
|
||||
</div>
|
||||
</Button></>)
|
||||
}
|
||||
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.loginRight}>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ loginModel, loading }) => ({ loginModel, loading }))(View);
|
||||
61
web/src/pages/login/model.ts
Normal file
61
web/src/pages/login/model.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { message } from 'antd';
|
||||
import { addParam } from '@/utils';
|
||||
import userService from '@/services/userService';
|
||||
|
||||
const Model = {
|
||||
namespace: 'loginModel',
|
||||
state: {
|
||||
list: [],
|
||||
info: {},
|
||||
visible: false,
|
||||
pagination: {},
|
||||
campaignInfo: {}
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
history.listen(location => { });
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
*login({ payload = {} }, { call, put }) {
|
||||
console.log(111, payload)
|
||||
const { data, response } = yield call(userService.login, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
console.log()
|
||||
const Authorization = response.headers.get('Authorization')
|
||||
if (retcode === 0) {
|
||||
message.success('登录成功!');
|
||||
const token = res.access_token;
|
||||
const userInfo = {
|
||||
avatar: res.avatar,
|
||||
name: res.nickname,
|
||||
email: res.email
|
||||
};
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfo))
|
||||
localStorage.setItem('Authorization', Authorization)
|
||||
setTimeout(() => {
|
||||
window.location.href = '/file';
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
*register({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(userService.register, payload);
|
||||
console.log()
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('注册成功!');
|
||||
callback && callback()
|
||||
}
|
||||
}
|
||||
},
|
||||
reducers: {
|
||||
updateState(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Model;
|
||||
92
web/src/pages/setting/CPwModal.tsx
Normal file
92
web/src/pages/setting/CPwModal.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { connect, Dispatch } from 'umi';
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Input, Modal, Form } from 'antd'
|
||||
import { rsaPsw } from '@/utils'
|
||||
import styles from './index.less';
|
||||
import { FC } from 'react';
|
||||
|
||||
type FieldType = {
|
||||
newPassword?: string;
|
||||
password?: string;
|
||||
};
|
||||
interface CPwModalProps {
|
||||
dispatch: Dispatch;
|
||||
settingModel: any
|
||||
}
|
||||
const Index: FC<CPwModalProps> = ({ settingModel, dispatch }) => {
|
||||
const { isShowPSwModal } = settingModel
|
||||
const { t } = useTranslation()
|
||||
const handleCancel = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowPSwModal: false
|
||||
}
|
||||
});
|
||||
};
|
||||
const [form] = Form.useForm()
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
var password = rsaPsw(values.password)
|
||||
var new_password = rsaPsw(values.newPassword)
|
||||
|
||||
dispatch({
|
||||
type: 'settingModel/setting',
|
||||
payload: {
|
||||
password,
|
||||
new_password
|
||||
},
|
||||
callback: () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowPSwModal: false
|
||||
}
|
||||
});
|
||||
dispatch({
|
||||
type: 'settingModel/getUserInfo',
|
||||
payload: {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="Basic Modal" open={isShowPSwModal} onOk={handleOk} onCancel={handleCancel}>
|
||||
<Form
|
||||
form={form}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label="旧密码"
|
||||
name="password"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label="新密码"
|
||||
name="newPassword"
|
||||
rules={[{ required: true, message: 'Please input your newPassword!' }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
</Modal >
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
export default connect(({ settingModel, loading }) => ({ settingModel, loading }))(Index);
|
||||
197
web/src/pages/setting/List.tsx
Normal file
197
web/src/pages/setting/List.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import { connect, Dispatch } from 'umi';
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
|
||||
import styles from './index.less';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { useEffect, useState, FC } from 'react';
|
||||
|
||||
import { RadarChartOutlined } from '@ant-design/icons';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { Button, Tag, Row, Col, Card } from 'antd';
|
||||
import { divide } from 'lodash';
|
||||
|
||||
|
||||
interface DataType {
|
||||
key: React.Key;
|
||||
name: string;
|
||||
age: number;
|
||||
address: string;
|
||||
description: string;
|
||||
}
|
||||
interface ListProps {
|
||||
dispatch: Dispatch;
|
||||
settingModel: any
|
||||
}
|
||||
const Index: FC<ListProps> = ({ settingModel, dispatch }) => {
|
||||
const { llmInfo = {}, factoriesList, myLlm = [] } = settingModel
|
||||
const { OpenAI = [], tongyi = [] } = llmInfo
|
||||
console.log(OpenAI)
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const { t } = useTranslation()
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{ title: 'Name', dataIndex: 'name', key: 'name' },
|
||||
{ title: 'Age', dataIndex: 'age', key: 'age' },
|
||||
{
|
||||
title: 'Action',
|
||||
dataIndex: '',
|
||||
key: 'x',
|
||||
render: () => <a>Delete</a>,
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'settingModel/factories_list',
|
||||
payload: {
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: 'settingModel/llm_list',
|
||||
payload: {
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: 'settingModel/my_llm',
|
||||
payload: {
|
||||
},
|
||||
});
|
||||
|
||||
}, [])
|
||||
const data: DataType[] = [
|
||||
{
|
||||
key: 1,
|
||||
name: 'John Brown',
|
||||
age: 32,
|
||||
address: 'New York No. 1 Lake Park',
|
||||
description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: 'Jim Green',
|
||||
age: 42,
|
||||
address: 'London No. 1 Lake Park',
|
||||
description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: 'Not Expandable',
|
||||
age: 29,
|
||||
address: 'Jiangsu No. 1 Lake Park',
|
||||
description: 'This not expandable',
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
name: 'Joe Black',
|
||||
age: 32,
|
||||
address: 'Sydney No. 1 Lake Park',
|
||||
description: 'My name is Joe Black, I am 32 years old, living in Sydney No. 1 Lake Park.',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.list}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 24,
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
{
|
||||
myLlm.map((item: any) => {
|
||||
return (<ProCard
|
||||
key={item.llm_factory}
|
||||
// title={<div>可折叠-图标自定义</div>}
|
||||
collapsibleIconRender={({
|
||||
collapsed: buildInCollapsed,
|
||||
}: {
|
||||
collapsed: boolean;
|
||||
}) => {
|
||||
return (<div>
|
||||
<h3><RadarChartOutlined />{item.llm_factory}</h3>
|
||||
<div>{item.tags.split(',').map((d: string) => {
|
||||
return <Tag key={d}>{d}</Tag>
|
||||
})}</div>
|
||||
{
|
||||
buildInCollapsed ? <span>显示{OpenAI.length}个模型</span> : <span>收起{OpenAI.length}个模型 </span>
|
||||
}
|
||||
</div>)
|
||||
}}
|
||||
extra={
|
||||
<Button
|
||||
size="small"
|
||||
type='link'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
llm_factory: item.llm_factory,
|
||||
isShowSAKModal: true
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
设置
|
||||
</Button>
|
||||
}
|
||||
style={{ marginBlockStart: 16 }}
|
||||
headerBordered
|
||||
collapsible
|
||||
defaultCollapsed
|
||||
>
|
||||
{/* <ul>
|
||||
{OpenAI.map(item => {
|
||||
return <li key={item.llm_name}>
|
||||
<span>{item.llm_name}</span>
|
||||
<span className={styles[item.available ? 'statusAvailable' : 'statusDisaabled']}>
|
||||
</span>
|
||||
</li>
|
||||
})}
|
||||
</ul> */}
|
||||
</ProCard>)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
||||
{
|
||||
factoriesList.map((item: any) => {
|
||||
return (<Col key={item.name} xs={24} sm={12} md={8} lg={6}>
|
||||
<Card title={item.name} bordered={false} extra={
|
||||
<Button
|
||||
size="small"
|
||||
type='link'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
llm_factory: item.name,
|
||||
isShowSAKModal: true
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
设置
|
||||
</Button>
|
||||
}>
|
||||
|
||||
<div>
|
||||
{
|
||||
item.tags.split(',').map((d: string) => {
|
||||
return <Tag key={d}>{d}</Tag>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</Card>
|
||||
</Col>)
|
||||
})
|
||||
}
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default connect(({ settingModel, loading }) => ({ settingModel, loading }))(Index);
|
||||
84
web/src/pages/setting/SAKModal.tsx
Normal file
84
web/src/pages/setting/SAKModal.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { connect, Dispatch } from 'umi';
|
||||
import i18n from 'i18next';
|
||||
import { FC } from 'react'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Input, Modal, Form } from 'antd'
|
||||
import { rsaPsw } from '@/utils'
|
||||
import styles from './index.less';
|
||||
|
||||
type FieldType = {
|
||||
api_key?: string;
|
||||
};
|
||||
interface SAKModalProps {
|
||||
dispatch: Dispatch;
|
||||
settingModel: any
|
||||
}
|
||||
const Index: FC<SAKModalProps> = ({ settingModel, dispatch }) => {
|
||||
const { isShowSAKModal, llm_factory } = settingModel
|
||||
console.log(llm_factory)
|
||||
const { t } = useTranslation()
|
||||
const handleCancel = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowSAKModal: false
|
||||
}
|
||||
});
|
||||
};
|
||||
const [form] = Form.useForm()
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
dispatch({
|
||||
type: 'settingModel/set_api_key',
|
||||
payload: {
|
||||
api_key: values.api_key,
|
||||
llm_factory: llm_factory
|
||||
},
|
||||
callback: () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowSAKModal: false
|
||||
}
|
||||
});
|
||||
// dispatch({
|
||||
// type: 'settingModel/getUserInfo',
|
||||
// payload: {
|
||||
|
||||
// }
|
||||
// });
|
||||
}
|
||||
});
|
||||
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="Basic Modal" open={isShowSAKModal} onOk={handleOk} onCancel={handleCancel}>
|
||||
<Form
|
||||
form={form}
|
||||
name="validateOnly"
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label="API Key"
|
||||
name="api_key"
|
||||
rules={[{ required: true, message: 'Please input ' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
</Form>
|
||||
</Modal >
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
export default connect(({ settingModel, loading }) => ({ settingModel, loading }))(Index);
|
||||
153
web/src/pages/setting/SSModal.tsx
Normal file
153
web/src/pages/setting/SSModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { connect, Dispatch } from 'umi';
|
||||
import { FC } from 'react'
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Input, Modal, Form, Select } from 'antd'
|
||||
import { rsaPsw } from '@/utils'
|
||||
import styles from './index.less';
|
||||
|
||||
type FieldType = {
|
||||
embd_id?: string;
|
||||
img2txt_id?: string;
|
||||
llm_id?: string;
|
||||
asr_id?: string
|
||||
};
|
||||
interface SSModalProps {
|
||||
dispatch: Dispatch;
|
||||
settingModel: any
|
||||
}
|
||||
const Index: FC<SSModalProps> = ({ settingModel, dispatch }) => {
|
||||
const { isShowSSModal, llmInfo = {}, tenantIfo } = settingModel
|
||||
|
||||
const { t } = useTranslation()
|
||||
const handleCancel = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowSSModal: false
|
||||
}
|
||||
});
|
||||
};
|
||||
const [form] = Form.useForm()
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
console.log(values)
|
||||
dispatch({
|
||||
type: 'settingModel/set_tenant_info',
|
||||
payload: {
|
||||
...values,
|
||||
tenant_id: tenantIfo.tenant_id,
|
||||
},
|
||||
callback: () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowSSModal: false
|
||||
}
|
||||
});
|
||||
// dispatch({
|
||||
// type: 'settingModel/getUserInfo',
|
||||
// payload: {
|
||||
|
||||
// }
|
||||
// });
|
||||
}
|
||||
});
|
||||
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
};
|
||||
const handleChange = () => {
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title="Basic Modal" open={isShowSSModal} onOk={handleOk} onCancel={handleCancel}>
|
||||
<Form
|
||||
form={form}
|
||||
name="validateOnly"
|
||||
// labelCol={{ span: 8 }}
|
||||
// wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
autoComplete="off"
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label="embedding 模型"
|
||||
name="embd_id"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
initialValue={tenantIfo.embd_id}
|
||||
|
||||
>
|
||||
<Select
|
||||
// style={{ width: 200 }}
|
||||
onChange={handleChange}
|
||||
// fieldNames={label:}
|
||||
options={Object.keys(llmInfo).map(t => {
|
||||
const options = llmInfo[t].filter((d: any) => d.model_type === 'embedding').map((d: any) => ({ label: d.llm_name, value: d.llm_name, }))
|
||||
return { label: t, options }
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label="chat 模型"
|
||||
name="llm_id"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
initialValue={tenantIfo.llm_id}
|
||||
|
||||
>
|
||||
<Select
|
||||
// style={{ width: 200 }}
|
||||
onChange={handleChange}
|
||||
// fieldNames={label:}
|
||||
options={Object.keys(llmInfo).map(t => {
|
||||
const options = llmInfo[t].filter((d: any) => d.model_type === 'chat').map((d: any) => ({ label: d.llm_name, value: d.llm_name, }))
|
||||
return { label: t, options }
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label="image2text 模型"
|
||||
name="img2txt_id"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
initialValue={tenantIfo.img2txt_id}
|
||||
|
||||
>
|
||||
<Select
|
||||
// style={{ width: 200 }}
|
||||
onChange={handleChange}
|
||||
// fieldNames={label:}
|
||||
options={Object.keys(llmInfo).map(t => {
|
||||
const options = llmInfo[t].filter((d: any) => d.model_type === 'image2text').map((d: any) => ({ label: d.llm_name, value: d.llm_name, }))
|
||||
return { label: t, options }
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label="speech2text 模型"
|
||||
name="asr_id"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
initialValue={tenantIfo.asr_id}
|
||||
|
||||
>
|
||||
<Select
|
||||
// style={{ width: 200 }}
|
||||
onChange={handleChange}
|
||||
// fieldNames={label:}
|
||||
options={Object.keys(llmInfo).map(t => {
|
||||
const options = llmInfo[t].filter((d: any) => d.model_type === 'speech2text').map((d: any) => ({ label: d.llm_name, value: d.llm_name, }))
|
||||
return { label: t, options }
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
</Form>
|
||||
</Modal >
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
export default connect(({ settingModel, loading }) => ({ settingModel, loading }))(Index);
|
||||
58
web/src/pages/setting/TntModal.tsx
Normal file
58
web/src/pages/setting/TntModal.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { connect, Dispatch } from 'umi';
|
||||
import { FC } from 'react'
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Modal, Table } from 'antd'
|
||||
import styles from './index.less';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
|
||||
|
||||
interface DataType {
|
||||
key: React.Key;
|
||||
name: string;
|
||||
role: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
interface TntodalProps {
|
||||
dispatch: Dispatch;
|
||||
settingModel: any
|
||||
}
|
||||
|
||||
const Index: FC<TntodalProps> = ({ settingModel, dispatch }) => {
|
||||
const { isShowTntModal, tenantIfo, loading, factoriesList } = settingModel
|
||||
const { t } = useTranslation()
|
||||
const handleCancel = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowTntModal: false
|
||||
}
|
||||
});
|
||||
};
|
||||
console.log(tenantIfo)
|
||||
const handleOk = async () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowTntModal: false
|
||||
}
|
||||
});
|
||||
};
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{ title: '姓名', dataIndex: 'name', key: 'name' },
|
||||
{ title: '活动时间', dataIndex: 'update_date', key: 'update_date' },
|
||||
{ title: '角色', dataIndex: 'role', key: 'age' },
|
||||
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal title="用户" open={isShowTntModal} onOk={handleOk} onCancel={handleCancel}>
|
||||
<div className={styles.tenantIfo}>
|
||||
{tenantIfo.name}
|
||||
</div>
|
||||
<Table rowKey='name' loading={loading} columns={columns} dataSource={factoriesList} />
|
||||
</Modal >
|
||||
);
|
||||
}
|
||||
export default connect(({ settingModel, loading }) => ({ settingModel, loading }))(Index);
|
||||
49
web/src/pages/setting/index.less
Normal file
49
web/src/pages/setting/index.less
Normal file
@@ -0,0 +1,49 @@
|
||||
.settingPage {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tenantIfo {
|
||||
height: 50px;
|
||||
background-color: #f4dfdf;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list {
|
||||
:global {
|
||||
.ant-pro-card-header {
|
||||
height: 150px;
|
||||
background-color: rgb(229, 231, 235);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.statusDisaabled {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 40%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.statusAvailable {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
web/src/pages/setting/index.tsx
Normal file
90
web/src/pages/setting/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { connect, Dispatch } from 'umi';
|
||||
import i18n from 'i18next';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { Button, Input, Modal, Form, FloatButton, Table } from 'antd'
|
||||
|
||||
|
||||
import styles from './index.less';
|
||||
import CPwModal from './CPwModal'
|
||||
import SAKModal from './SAKModal'
|
||||
import TntModal from './TntModal'
|
||||
import SSModal from './SSModal'
|
||||
import List from './List'
|
||||
import { useEffect, useState, FC } from 'react';
|
||||
interface CPwModalProps {
|
||||
dispatch: Dispatch;
|
||||
settingModel: any
|
||||
}
|
||||
const Index: FC<CPwModalProps> = ({ settingModel, dispatch }) => {
|
||||
// const [llm_factory, set_llm_factory] = useState('')
|
||||
const { t } = useTranslation()
|
||||
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '')
|
||||
const changeLang = (val: string) => { // 改变状态里的 语言 进行切换
|
||||
i18n.changeLanguage(val);
|
||||
}
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'settingModel/getTenantInfo',
|
||||
payload: {
|
||||
}
|
||||
});
|
||||
}, [])
|
||||
const showCPwModal = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowPSwModal: true
|
||||
}
|
||||
});
|
||||
};
|
||||
const showTntModal = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowTntModal: true
|
||||
}
|
||||
});
|
||||
};
|
||||
const showSSModal = () => {
|
||||
dispatch({
|
||||
type: 'settingModel/updateState',
|
||||
payload: {
|
||||
isShowSSModal: true
|
||||
}
|
||||
});
|
||||
// dispatch({
|
||||
// type: 'settingModel/getTenantInfo',
|
||||
// payload: {
|
||||
// }
|
||||
// });
|
||||
};
|
||||
return (
|
||||
<div className={styles.settingPage}>
|
||||
<div className={styles.avatar}>
|
||||
<img style={{ width: 50, marginRight: 5 }} src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" alt="" />
|
||||
<div>
|
||||
<div>账号:{userInfo.name}</div>
|
||||
<div><span>密码:******</span><Button type='link' onClick={showCPwModal}>修改密码</Button></div>
|
||||
|
||||
</div>
|
||||
</div >
|
||||
<div>
|
||||
<Button type="link" onClick={showTntModal}>
|
||||
租户
|
||||
</Button>
|
||||
<Button type="link" onClick={showSSModal}>
|
||||
系统模型设置
|
||||
</Button>
|
||||
<List />
|
||||
</div>
|
||||
<CPwModal />
|
||||
<SAKModal />
|
||||
<SSModal />
|
||||
<TntModal />
|
||||
<FloatButton shape='square' description={t('setting.btn')} onClick={() => i18n.changeLanguage(i18n.language == 'en' ? 'zh' : 'en')} type="default" style={{ right: 94, fontSize: 14 }} />
|
||||
</div >
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
export default connect(({ settingModel, loading }) => ({ settingModel, loading }))(Index);
|
||||
181
web/src/pages/setting/model.ts
Normal file
181
web/src/pages/setting/model.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { Effect, Reducer, Subscription } from 'umi';
|
||||
import { message } from 'antd';
|
||||
import { addParam } from '@/utils';
|
||||
import userService from '@/services/userService';
|
||||
import { rearg } from 'lodash';
|
||||
|
||||
export interface settingModelState {
|
||||
isShowPSwModal: boolean;
|
||||
isShowTntModal: boolean;
|
||||
isShowSAKModal: boolean;
|
||||
isShowSSModal: boolean;
|
||||
llm_factory: string;
|
||||
loading: boolean;
|
||||
tenantIfo: any,
|
||||
llmInfo: any,
|
||||
myLlm: any[],
|
||||
factoriesList: any[]
|
||||
}
|
||||
|
||||
export interface settingModelType {
|
||||
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',
|
||||
state: {
|
||||
isShowPSwModal: false,
|
||||
isShowTntModal: false,
|
||||
isShowSAKModal: false,
|
||||
isShowSSModal: false,
|
||||
llm_factory: '',
|
||||
loading: false,
|
||||
tenantIfo: {},
|
||||
llmInfo: {},
|
||||
myLlm: [],
|
||||
factoriesList: []
|
||||
},
|
||||
subscriptions: {
|
||||
setup({ dispatch, history }) {
|
||||
history.listen(location => {
|
||||
});
|
||||
}
|
||||
},
|
||||
effects: {
|
||||
*setting({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(userService.setting, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('密码修改成功!');
|
||||
callback && callback()
|
||||
}
|
||||
},
|
||||
*getUserInfo({ payload = {} }, { call, put }) {
|
||||
const { data, response } = yield call(userService.user_info, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
const userInfo = {
|
||||
avatar: res.avatar,
|
||||
name: res.nickname,
|
||||
email: res.email
|
||||
};
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfo))
|
||||
if (retcode === 0) {
|
||||
// localStorage.setItem('userInfo',res.)
|
||||
}
|
||||
},
|
||||
*getTenantInfo({ payload = {} }, { call, put }) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: true
|
||||
}
|
||||
});
|
||||
const { data, response } = yield call(userService.get_tenant_info, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
// llm_id 对应chat_id
|
||||
// asr_id 对应speech2txt
|
||||
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
loading: false
|
||||
}
|
||||
});
|
||||
if (retcode === 0) {
|
||||
res.chat_id = res.llm_id
|
||||
res.speech2text_id = res.asr_id
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
tenantIfo: res
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
*set_tenant_info({ payload = {} }, { call, put }) {
|
||||
const { data, response } = yield call(userService.set_tenant_info, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
// llm_id 对应chat_id
|
||||
// asr_id 对应speech2txt
|
||||
if (retcode === 0) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
isShowSSModal: false
|
||||
}
|
||||
});
|
||||
yield put({
|
||||
type: 'getTenantInfo'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
*factories_list({ payload = {} }, { call, put }) {
|
||||
const { data, response } = yield call(userService.factories_list, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
factoriesList: res
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
*llm_list({ payload = {} }, { call, put }) {
|
||||
const { data, response } = yield call(userService.llm_list, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
llmInfo: res
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
*my_llm({ payload = {} }, { call, put }) {
|
||||
const { data, response } = yield call(userService.my_llm, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
yield put({
|
||||
type: 'updateState',
|
||||
payload: {
|
||||
myLlm: res
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
*set_api_key({ payload = {}, callback }, { call, put }) {
|
||||
const { data, response } = yield call(userService.set_api_key, payload);
|
||||
const { retcode, data: res, retmsg } = data
|
||||
if (retcode === 0) {
|
||||
message.success('设置API KEY成功!');
|
||||
callback && callback()
|
||||
}
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
updateState(state, { payload }) {
|
||||
return {
|
||||
...state,
|
||||
...payload
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
export default Model;
|
||||
Reference in New Issue
Block a user