add front end code; add chunk API
This commit is contained in:
parent
8eedeff5be
commit
0b0d758356
74 changed files with 20025 additions and 74 deletions
9
client/.gitignore
vendored
Normal file
9
client/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/node_modules
|
||||
/.env.local
|
||||
/.umirc.local.ts
|
||||
/config/config.local.ts
|
||||
/src/.umi
|
||||
/src/.umi-production
|
||||
/src/.umi-test
|
||||
/dist
|
||||
.swc
|
||||
2
client/.npmrc
Normal file
2
client/.npmrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
registry=https://registry.npmmirror.com/
|
||||
|
||||
28
client/.umirc.ts
Normal file
28
client/.umirc.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { defineConfig } from "umi";
|
||||
import routes from './routes'
|
||||
|
||||
export default defineConfig({
|
||||
outputPath: 'dist',
|
||||
// alias: { '@': './src' },
|
||||
routes,
|
||||
npmClient: 'npm',
|
||||
base: '/',
|
||||
publicPath: '/client/dist/',
|
||||
icons: {
|
||||
|
||||
},
|
||||
hash: true,
|
||||
history: {
|
||||
type: 'hash',
|
||||
},
|
||||
plugins: ['@umijs/plugins/dist/dva'],
|
||||
dva: {},
|
||||
proxy: {
|
||||
'/v1': {
|
||||
'target': 'http://54.80.112.79:9380/',
|
||||
'changeOrigin': true,
|
||||
'pathRewrite': { '^/v1': '/v1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
16385
client/package-lock.json
generated
Normal file
16385
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
35
client/package.json
Normal file
35
client/package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"private": true,
|
||||
"author": "zhaofengchao <13723060510@163.com>",
|
||||
"scripts": {
|
||||
"dev": "umi dev",
|
||||
"build": "umi build",
|
||||
"postinstall": "umi setup",
|
||||
"setup": "umi setup",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/pro-components": "^2.6.46",
|
||||
"@ant-design/pro-layout": "^7.17.16",
|
||||
"antd": "^5.12.7",
|
||||
"axios": "^1.6.3",
|
||||
"classnames": "^2.5.1",
|
||||
"i18next": "^23.7.16",
|
||||
"js-base64": "^3.7.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"react-i18next": "^14.0.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"umi": "^4.0.90",
|
||||
"umi-request": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.202",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@umijs/plugins": "^4.1.0",
|
||||
"typescript": "^5.0.3",
|
||||
"umi-plugin-icons": "^0.1.1"
|
||||
}
|
||||
}
|
||||
27
client/reducer.js
Normal file
27
client/reducer.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React, { useReducer } from 'react'
|
||||
const CHANGE_LOCALE = 'CHANGE_LOCALE'
|
||||
|
||||
const mainContext = React.createContext()
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case CHANGE_LOCALE:
|
||||
return { ...state, locale: action.locale || 'zh' }
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const ContextProvider = (props) => {
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
locale: 'zh'
|
||||
})
|
||||
return (
|
||||
<mainContext.Provider value={{ state, dispatch }}>
|
||||
{props.children}
|
||||
</mainContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export { reducer, mainContext, ContextProvider }
|
||||
|
||||
89
client/routes.js
Normal file
89
client/routes.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: '@/pages/login',
|
||||
layout: false
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
component: '@/layouts', // 默认页面
|
||||
redirect: '/knowledge',
|
||||
// wrappers: [
|
||||
// '@/wrappers/auth',
|
||||
// ]
|
||||
},
|
||||
|
||||
{
|
||||
id: 2,
|
||||
name: '知识库',
|
||||
icon: 'home',
|
||||
auth: [3, 4, 100],
|
||||
path: '/knowledge',
|
||||
component: '@/pages/knowledge',
|
||||
pathname: 'knowledge'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '知识库',
|
||||
icon: 'home',
|
||||
auth: [3, 4, 100],
|
||||
path: '/knowledge/add/*',
|
||||
component: '@/pages/add-knowledge',
|
||||
pathname: 'knowledge',
|
||||
// routes: [{
|
||||
// id: 3,
|
||||
// name: '设置',
|
||||
// icon: 'home',
|
||||
// auth: [3, 4, 100],
|
||||
// path: '/knowledge/add/setting',
|
||||
// component: '@/pages/setting',
|
||||
// pathname: "setting"
|
||||
// }, {
|
||||
// id: 1,
|
||||
// name: '文件',
|
||||
// icon: 'file',
|
||||
// auth: [3, 4, 100],
|
||||
// path: '/knowledge/add/file',
|
||||
// component: '@/pages/file',
|
||||
// pathname: 'file'
|
||||
// },]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '聊天',
|
||||
icon: 'home',
|
||||
auth: [3, 4, 100],
|
||||
path: '/chat',
|
||||
component: '@/pages/chat',
|
||||
pathname: "chat"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '设置',
|
||||
icon: 'home',
|
||||
auth: [3, 4, 100],
|
||||
path: '/setting',
|
||||
component: '@/pages/setting',
|
||||
pathname: "setting"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: '文件',
|
||||
icon: 'file',
|
||||
auth: [3, 4, 100],
|
||||
path: '/file',
|
||||
component: '@/pages/file',
|
||||
pathname: 'file'
|
||||
},
|
||||
{
|
||||
path: '/*',
|
||||
component: '@/pages/404',
|
||||
layout: false
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
|
||||
module.exports = routes;
|
||||
BIN
client/src/assets/1.pdf
Normal file
BIN
client/src/assets/1.pdf
Normal file
Binary file not shown.
BIN
client/src/assets/logo.png
Normal file
BIN
client/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
client/src/assets/yay.jpg
Normal file
BIN
client/src/assets/yay.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
10
client/src/icons/github.svg
Normal file
10
client/src/icons/github.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_660_5)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.50662 0.453613C4.07917 0.453613 0.5 4.05917 0.5 8.51972C0.5 12.0853 2.79329 15.1035 5.9747 16.1717C6.37246 16.252 6.51816 15.9981 6.51816 15.7846C6.51816 15.5976 6.50505 14.9566 6.50505 14.2888C4.2778 14.7696 3.81399 13.3272 3.81399 13.3272C3.45606 12.3924 2.92572 12.1522 2.92572 12.1522C2.19674 11.658 2.97882 11.658 2.97882 11.658C3.78744 11.7115 4.21175 12.486 4.21175 12.486C4.92745 13.7145 6.08074 13.3674 6.54471 13.1537C6.61092 12.6328 6.82315 12.2723 7.0485 12.072C5.27211 11.885 3.40312 11.1906 3.40312 8.0923C3.40312 7.21091 3.72107 6.4898 4.22486 5.92897C4.14538 5.7287 3.86693 4.90057 4.30451 3.79219C4.30451 3.79219 4.98055 3.57848 6.50488 4.62016C7.1575 4.44359 7.83054 4.35377 8.50662 4.35302C9.18266 4.35302 9.87181 4.4466 10.5082 4.62016C12.0327 3.57848 12.7087 3.79219 12.7087 3.79219C13.1463 4.90057 12.8677 5.7287 12.7882 5.92897C13.3053 6.4898 13.6101 7.21091 13.6101 8.0923C13.6101 11.1906 11.7411 11.8716 9.95146 12.072C10.2432 12.3257 10.4949 12.8064 10.4949 13.5677C10.4949 14.6493 10.4818 15.5174 10.4818 15.7844C10.4818 15.9981 10.6277 16.252 11.0253 16.1719C14.2067 15.1033 16.5 12.0853 16.5 8.51972C16.5131 4.05917 12.9208 0.453613 8.50662 0.453613Z" fill="#24292F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_660_5">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0.5 0.453613)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
13
client/src/icons/google.svg
Normal file
13
client/src/icons/google.svg
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_660_28)">
|
||||
<path d="M8 6.99902V10.0972H12.3054C12.1164 11.0936 11.549 11.9372 10.6981 12.5045L13.2945 14.5191C14.8072 13.1227 15.68 11.0718 15.68 8.63547C15.68 8.0682 15.6291 7.5227 15.5345 6.99911L8 6.99902Z" fill="#4285F4"/>
|
||||
<path d="M3.51649 9.97632L2.93092 10.4246L0.858154 12.0391C2.17451 14.65 4.8725 16.4536 7.99974 16.4536C10.1597 16.4536 11.9706 15.7409 13.2942 14.5191L10.6979 12.5046C9.98516 12.9846 9.07606 13.2755 7.99974 13.2755C5.91976 13.2755 4.15254 11.8719 3.51976 9.98094L3.51649 9.97632Z" fill="#34A853"/>
|
||||
<path d="M0.858119 4.86816C0.312695 5.94448 0 7.15905 0 8.45357C0 9.74809 0.312695 10.9627 0.858119 12.039C0.858119 12.0462 3.51998 9.97352 3.51998 9.97352C3.35998 9.49352 3.26541 8.98446 3.26541 8.45349C3.26541 7.92251 3.35998 7.41345 3.51998 6.93345L0.858119 4.86816Z" fill="#FBBC05"/>
|
||||
<path d="M7.99991 3.63907C9.17811 3.63907 10.2254 4.04633 11.0617 4.83179L13.3526 2.54091C11.9635 1.24639 10.1599 0.453613 7.99991 0.453613C4.87266 0.453613 2.17451 2.24997 0.858154 4.86816L3.51994 6.93362C4.15263 5.04269 5.91992 3.63907 7.99991 3.63907Z" fill="#EA4335"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_660_28">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0 0.453613)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
38
client/src/layouts/components/user/index.tsx
Normal file
38
client/src/layouts/components/user/index.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { Button, Dropdown, } from 'antd';
|
||||
import { history } from 'umi'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
|
||||
const App: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const logout = () => { history.push('/login') }
|
||||
const toSetting = () => { history.push('/setting') }
|
||||
const items: MenuProps['items'] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<Button type="text" onClick={logout}>{t('header.logout')}</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<Button type="text" onClick={toSetting}>{t('header.setting')}</Button>
|
||||
),
|
||||
},
|
||||
]
|
||||
}, []);
|
||||
|
||||
return (<>
|
||||
<Dropdown menu={{ items }} placement="bottomLeft" arrow>
|
||||
<img
|
||||
style={{ width: '50px', height: '50px', borderRadius: '25px' }}
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
/>
|
||||
</Dropdown>
|
||||
</>)
|
||||
}
|
||||
|
||||
export default App;
|
||||
33
client/src/layouts/index.less
Normal file
33
client/src/layouts/index.less
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
.navs {
|
||||
ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.layout {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tag {
|
||||
height: 40px;
|
||||
padding: 0 30px;
|
||||
margin: 0 5px;
|
||||
border: 1px solid #000;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checked {
|
||||
color: #1677ff;
|
||||
border-color: #1677ff;
|
||||
}
|
||||
74
client/src/layouts/index.tsx
Normal file
74
client/src/layouts/index.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { history, Outlet, useLocation, useNavigate } from 'umi';
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import classnames from 'classnames'
|
||||
import '../locales/config';
|
||||
import logo from '@/assets/logo.png'
|
||||
import {
|
||||
RedditOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { Layout, Button, theme, Space, } from 'antd';
|
||||
import styles from './index.less'
|
||||
import User from './components/user'
|
||||
import { head } from 'lodash';
|
||||
|
||||
const { Header, Content } = Layout;
|
||||
|
||||
const App: React.FC = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
token: { colorBgContainer, borderRadiusLG },
|
||||
} = theme.useToken();
|
||||
const [current, setCurrent] = useState('knowledge');
|
||||
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
if (location.pathname !== '/') {
|
||||
const path = location.pathname.split('/')
|
||||
setCurrent(path[1]);
|
||||
}
|
||||
console.log(location.pathname.split('/'))
|
||||
}, [location.pathname])
|
||||
|
||||
const handleChange = (path: string) => {
|
||||
setCurrent(path)
|
||||
navigate(path);
|
||||
};
|
||||
const tagsData = [{ path: '/knowledge', name: 'knowledge' }, { path: '/chat', name: 'chat' }, { path: '/file', name: 'file' }];
|
||||
|
||||
return (
|
||||
<Layout className={styles.layout} >
|
||||
<Layout>
|
||||
<Header style={{ padding: '0 8px', background: colorBgContainer, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
|
||||
<img src={logo} alt="" style={{ height: 30, width: 30 }} />
|
||||
<Space size={[0, 8]} wrap>
|
||||
{tagsData.map((item) =>
|
||||
(<span key={item.name} className={classnames(styles['tag'], {
|
||||
[styles['checked']]: current === item.name
|
||||
})} onClick={() => handleChange(item.path)}>
|
||||
{item.name}
|
||||
</span>)
|
||||
)}
|
||||
</Space>
|
||||
<User ></User>
|
||||
</Header>
|
||||
<Content
|
||||
style={{
|
||||
margin: '24px 16px',
|
||||
|
||||
minHeight: 280,
|
||||
background: colorBgContainer,
|
||||
borderRadius: borderRadiusLG,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout >
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
24
client/src/locales/config.ts
Normal file
24
client/src/locales/config.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import translation_en from './en.json';
|
||||
import translation_zh from './zh.json';
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: translation_en,
|
||||
},
|
||||
zh: {
|
||||
translation: translation_zh,
|
||||
},
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: 'zh',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
20
client/src/locales/en.json
Normal file
20
client/src/locales/en.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"header": {
|
||||
"register": "Register",
|
||||
"signin": "Sign In",
|
||||
"home": "Home",
|
||||
"setting": "用户设置",
|
||||
"logout": "登出"
|
||||
},
|
||||
"footer": {
|
||||
"detail": "All rights reserved @ React"
|
||||
},
|
||||
"layout": {
|
||||
"file": "file",
|
||||
"knowledge": "knowledge",
|
||||
"chat": "chat"
|
||||
},
|
||||
"setting": {
|
||||
"btn": "en"
|
||||
}
|
||||
}
|
||||
20
client/src/locales/zh.json
Normal file
20
client/src/locales/zh.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"header": {
|
||||
"register": "注册",
|
||||
"signin": "登陆",
|
||||
"home": "首页",
|
||||
"setting": "user setting",
|
||||
"logout": "logout"
|
||||
},
|
||||
"footer": {
|
||||
"detail": "版权所有 @ React"
|
||||
},
|
||||
"layout": {
|
||||
"file": "文件",
|
||||
"knowledge": "知识库",
|
||||
"chat": "聊天"
|
||||
},
|
||||
"setting": {
|
||||
"btn": "中文"
|
||||
}
|
||||
}
|
||||
17
client/src/pages/404.jsx
Normal file
17
client/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
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
client/src/pages/add-knowledge/index.less
Normal file
19
client/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
client/src/pages/add-knowledge/index.tsx
Normal file
106
client/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
client/src/pages/add-knowledge/model.ts
Normal file
34
client/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
client/src/pages/chat/index.tsx
Normal file
19
client/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
client/src/pages/chat/model.ts
Normal file
52
client/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
client/src/pages/file/index.tsx
Normal file
51
client/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
client/src/pages/knowledge/index.less
Normal file
41
client/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
client/src/pages/knowledge/index.tsx
Normal file
109
client/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
client/src/pages/knowledge/model.ts
Normal file
61
client/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
client/src/pages/login/index.less
Normal file
63
client/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
client/src/pages/login/index.tsx
Normal file
157
client/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
client/src/pages/login/model.ts
Normal file
61
client/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
client/src/pages/setting/CPwModal.tsx
Normal file
92
client/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
client/src/pages/setting/List.tsx
Normal file
197
client/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
client/src/pages/setting/SAKModal.tsx
Normal file
84
client/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
client/src/pages/setting/SSModal.tsx
Normal file
153
client/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
client/src/pages/setting/TntModal.tsx
Normal file
58
client/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
client/src/pages/setting/index.less
Normal file
49
client/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
client/src/pages/setting/index.tsx
Normal file
90
client/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
client/src/pages/setting/model.ts
Normal file
181
client/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;
|
||||
70
client/src/services/kbService.ts
Normal file
70
client/src/services/kbService.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import api from '@/utils/api';
|
||||
import registerServer from '@/utils/registerServer';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const {
|
||||
create_kb,
|
||||
update_kb,
|
||||
rm_kb,
|
||||
get_kb_detail,
|
||||
kb_list, get_document_list, document_change_status, document_rm, document_create, document_change_parser } = api;
|
||||
interface kbService {
|
||||
createKb: () => void;
|
||||
updateKb: () => void;
|
||||
rmKb: () => void;
|
||||
get_kb_detail: () => void;
|
||||
getList: () => void;
|
||||
get_document_list: () => void;
|
||||
document_change_status: () => void;
|
||||
document_rm: () => void;
|
||||
document_create: () => void;
|
||||
document_change_parser: () => 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'
|
||||
},
|
||||
|
||||
},
|
||||
request
|
||||
);
|
||||
|
||||
export default kbService;
|
||||
21
client/src/services/uploadService.ts
Normal file
21
client/src/services/uploadService.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import request from '@/utils/request';
|
||||
import api from '@/utils/api';
|
||||
|
||||
const { upload } = api;
|
||||
|
||||
const uploadService = {
|
||||
uploadFile: function (file, kb_id) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('kb_id', kb_id);
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
data: formData
|
||||
};
|
||||
|
||||
return request(upload, options);
|
||||
}
|
||||
};
|
||||
|
||||
export default uploadService;
|
||||
57
client/src/services/userService.ts
Normal file
57
client/src/services/userService.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import api from '@/utils/api';
|
||||
import registerServer from '@/utils/registerServer';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const {
|
||||
login, register, setting, user_info, tenant_info, factories_list, llm_list, my_llm, set_api_key, set_tenant_info } = api;
|
||||
interface userServiceType {
|
||||
login: (params: any) => void
|
||||
}
|
||||
const userService = registerServer(
|
||||
{
|
||||
login: {
|
||||
url: login,
|
||||
method: 'post',
|
||||
|
||||
},
|
||||
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'
|
||||
},
|
||||
},
|
||||
request
|
||||
);
|
||||
|
||||
export default userService;
|
||||
11
client/src/theme/theme.ts
Normal file
11
client/src/theme/theme.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
'primary-color': '#338AFF', // '#338AFF',
|
||||
'border-radius-base': '4px',
|
||||
// 'menu-dark-color': '',
|
||||
// 'menu-dark-danger-color': '',
|
||||
'menu-dark-bg': '#092140',
|
||||
'menu-dark-item-active-bg': '#092140'
|
||||
|
||||
// 'menu-dark-arrow-color': '',
|
||||
// 'menu-dark-inline-submenu-bg': '',
|
||||
};
|
||||
9
client/src/theme/vars.less
Normal file
9
client/src/theme/vars.less
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
@header-height: 64px;
|
||||
@menu-width: 200px;
|
||||
@menu-small-width: 83px;
|
||||
@layout-bg: #f2f3f6;
|
||||
@logo-font-size: 22px;
|
||||
@border-color: #d9d9d9;
|
||||
@dashboard-desc-color: #9d9fa2;
|
||||
@primary-color: #338aff;
|
||||
@primary-color-light: rgba(21, 65, 255, 0.5);
|
||||
42
client/src/utils/api.ts
Normal file
42
client/src/utils/api.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
|
||||
|
||||
let api_host = `/v1`;
|
||||
|
||||
|
||||
export { api_host };
|
||||
|
||||
export default {
|
||||
|
||||
|
||||
// 用户
|
||||
login: `${api_host}/user/login`,
|
||||
register: `${api_host}/user/register`,
|
||||
setting: `${api_host}/user/setting`,
|
||||
user_info: `${api_host}/user/info`,
|
||||
tenant_info: `${api_host}/user/tenant_info`,
|
||||
set_tenant_info: `${api_host}/user/set_tenant_info`,
|
||||
|
||||
// 模型管理
|
||||
factories_list: `${api_host}/llm/factories`,
|
||||
llm_list: `${api_host}/llm/list`,
|
||||
my_llm: `${api_host}/llm/my_llms`,
|
||||
set_api_key: `${api_host}/llm/set_api_key`,
|
||||
|
||||
|
||||
//知识库管理
|
||||
kb_list: `${api_host}/kb/list`,
|
||||
create_kb: `${api_host}/kb/create`,
|
||||
update_kb: `${api_host}/kb/update`,
|
||||
rm_kb: `${api_host}/kb/rm`,
|
||||
get_kb_detail: `${api_host}/kb/detail`,
|
||||
|
||||
// 上传
|
||||
upload: `${api_host}/document/upload`,
|
||||
get_document_list: `${api_host}/document/list`,
|
||||
document_change_status: `${api_host}/document/change_status`,
|
||||
document_rm: `${api_host}/document/rm`,
|
||||
document_create: `${api_host}/document/create`,
|
||||
document_change_parser: `${api_host}/document/change_parser`,
|
||||
|
||||
};
|
||||
20
client/src/utils/date.ts
Normal file
20
client/src/utils/date.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import moment from 'moment';
|
||||
|
||||
export function today() {
|
||||
return formatDate(moment());
|
||||
}
|
||||
|
||||
export function lastDay() {
|
||||
return formatDate(moment().subtract(1, 'days'));
|
||||
}
|
||||
|
||||
export function lastWeek() {
|
||||
return formatDate(moment().subtract(1, 'weeks'));
|
||||
}
|
||||
|
||||
export function formatDate(date) {
|
||||
if (!date) {
|
||||
return '';
|
||||
}
|
||||
return moment(date).format('YYYY-MM-DD');
|
||||
}
|
||||
14
client/src/utils/hooks.ts
Normal file
14
client/src/utils/hooks.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
export const usePagination = function (defaultPage: number, defaultPageSize: number, total: number) {
|
||||
const [page = 1, setPage] = useState(defaultPage);
|
||||
const [pageSize = 10, setPageSize] = useState(defaultPageSize);
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
count: total,
|
||||
setPage,
|
||||
setPageSize,
|
||||
nextPage: () => setPage(page + 1)
|
||||
};
|
||||
};
|
||||
26
client/src/utils/index.ts
Normal file
26
client/src/utils/index.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @param {String} url
|
||||
* @param {Boolean} isNoCaseSensitive 是否区分大小写
|
||||
* @return {Object}
|
||||
*/
|
||||
// import numeral from 'numeral';
|
||||
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
export const getWidth = () => {
|
||||
return { width: window.innerWidth };
|
||||
};
|
||||
export const rsaPsw = (password: string) => {
|
||||
const pub = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB-----END PUBLIC KEY-----"
|
||||
const encryptor = new JSEncrypt()
|
||||
|
||||
encryptor.setPublicKey(pub)
|
||||
|
||||
return encryptor.encrypt(Base64.encode(password))
|
||||
}
|
||||
|
||||
export default {
|
||||
getWidth,
|
||||
rsaPsw
|
||||
};
|
||||
22
client/src/utils/registerServer.ts
Normal file
22
client/src/utils/registerServer.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
const registerServer = (opt: any, request: any): any => {
|
||||
let server = {};
|
||||
for (let key in opt) {
|
||||
server[key] = (params: any) => {
|
||||
if (opt[key].method === 'post' || opt[key].method === 'POST') {
|
||||
return request(opt[key].url, {
|
||||
method: opt[key].method,
|
||||
data: params
|
||||
});
|
||||
}
|
||||
|
||||
if (opt[key].method === 'get' || opt[key].method === 'GET') {
|
||||
return request.get(opt[key].url, {
|
||||
params
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
return server;
|
||||
};
|
||||
|
||||
export default registerServer;
|
||||
116
client/src/utils/request.ts
Normal file
116
client/src/utils/request.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
|
||||
import { extend } from 'umi-request';
|
||||
import { notification, message } from 'antd';
|
||||
import _ from 'lodash';
|
||||
|
||||
import api from '@/utils/api';
|
||||
const { login } = api;
|
||||
|
||||
const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message
|
||||
|
||||
|
||||
|
||||
const retcodeMessage = {
|
||||
200: '服务器成功返回请求的数据。',
|
||||
201: '新建或修改数据成功。',
|
||||
202: '一个请求已经进入后台排队(异步任务)。',
|
||||
204: '删除数据成功。',
|
||||
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
|
||||
401: '用户没有权限(令牌、用户名、密码错误)。',
|
||||
403: '用户得到授权,但是访问是被禁止的。',
|
||||
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
|
||||
406: '请求的格式不可得。',
|
||||
410: '请求的资源被永久删除,且不会再得到的。',
|
||||
422: '当创建一个对象时,发生一个验证错误。',
|
||||
500: '服务器发生错误,请检查服务器。',
|
||||
502: '网关错误。',
|
||||
503: '服务不可用,服务器暂时过载或维护。',
|
||||
504: '网关超时。'
|
||||
};
|
||||
|
||||
/**
|
||||
* 异常处理程序
|
||||
*/
|
||||
const errorHandler = (error: any) => {
|
||||
const { response } = error;
|
||||
// 手动中断请求 abort
|
||||
if (error.message === ABORT_REQUEST_ERR_MESSAGE) {
|
||||
console.log('user abort request');
|
||||
} else {
|
||||
if (response && response.status) {
|
||||
const errorText = retcodeMessage[response.status] || response.statusText;
|
||||
const { status, url } = response;
|
||||
notification.error({
|
||||
message: `请求错误 ${status}: ${url}`,
|
||||
description: errorText,
|
||||
});
|
||||
} else if (!response) {
|
||||
notification.error({
|
||||
description: '您的网络发生异常,无法连接服务器',
|
||||
message: '网络异常',
|
||||
});
|
||||
}
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* 配置request请求时的默认参数
|
||||
*/
|
||||
const request = extend({
|
||||
errorHandler, // 默认错误处理
|
||||
// credentials: 'include', // 默认请求是否带上cookie
|
||||
timeout: 3000000,
|
||||
getResponse: true
|
||||
});
|
||||
|
||||
request.interceptors.request.use((url, options) => {
|
||||
let prefix = '';
|
||||
console.log(url)
|
||||
const Authorization = localStorage.getItem('Authorization')
|
||||
return {
|
||||
url,
|
||||
options: {
|
||||
...options,
|
||||
headers: {
|
||||
...(options.skipToken ? undefined : { Authorization }),
|
||||
...options.headers
|
||||
},
|
||||
interceptors: true
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
* 请求response拦截器
|
||||
* */
|
||||
request.interceptors.response.use(async (response, request) => {
|
||||
console.log(response, request)
|
||||
const data = await response.clone().json();
|
||||
// response 拦截
|
||||
|
||||
if (data.retcode === 401 || data.retcode === 401) {
|
||||
notification.error({
|
||||
message: data.retmsg,
|
||||
description: data.retmsg,
|
||||
duration: 3,
|
||||
});
|
||||
} else if (data.retcode !== 0) {
|
||||
if (data.retcode === 100) {
|
||||
message.error(data.retmsg);
|
||||
} else {
|
||||
notification.error({
|
||||
message: `提示 : ${data.retcode}`,
|
||||
description: data.retmsg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
} else {
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
||||
|
||||
export default request;
|
||||
12
client/src/wrappers/auth.tsx
Normal file
12
client/src/wrappers/auth.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Navigate, Outlet } from 'umi'
|
||||
|
||||
export default (props) => {
|
||||
// const { isLogin } = useAuth();
|
||||
console.log(props)
|
||||
const isLogin = false
|
||||
if (isLogin) {
|
||||
return <Outlet />;
|
||||
} else {
|
||||
return <Navigate to="/login" />;
|
||||
}
|
||||
}
|
||||
6
client/tsconfig.json
Normal file
6
client/tsconfig.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "./src/.umi/tsconfig.json",
|
||||
"@@/*": [
|
||||
"src/.umi/*"
|
||||
]
|
||||
}
|
||||
2
client/typings.d.ts
vendored
Normal file
2
client/typings.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import 'umi/typings';
|
||||
declare module 'lodash'
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import re
|
||||
from elasticsearch_dsl import Q, Search, A
|
||||
from typing import List, Optional, Tuple, Dict, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
from rag.settings import es_logger
|
||||
from rag.utils import rmSpace
|
||||
from rag.nlp import huqie, query
|
||||
import numpy as np
|
||||
|
|
@ -34,30 +37,30 @@ class Dealer:
|
|||
group_docs: List[List] = None
|
||||
|
||||
def _vector(self, txt, sim=0.8, topk=10):
|
||||
qv = self.emb_mdl.encode_queries(txt)
|
||||
return {
|
||||
"field": "q_vec",
|
||||
"field": "q_%d_vec"%len(qv),
|
||||
"k": topk,
|
||||
"similarity": sim,
|
||||
"num_candidates": 1000,
|
||||
"query_vector": self.emb_mdl.encode_queries(txt)
|
||||
"query_vector": qv
|
||||
}
|
||||
|
||||
def search(self, req, idxnm, tks_num=3):
|
||||
keywords = []
|
||||
qst = req.get("question", "")
|
||||
|
||||
bqry, keywords = self.qryr.question(qst)
|
||||
if req.get("kb_ids"):
|
||||
bqry.filter.append(Q("terms", kb_id=req["kb_ids"]))
|
||||
bqry.filter.append(Q("exists", field="q_tks"))
|
||||
if req.get("doc_ids"):
|
||||
bqry.filter.append(Q("terms", doc_id=req["doc_ids"]))
|
||||
bqry.boost = 0.05
|
||||
print(bqry)
|
||||
|
||||
s = Search()
|
||||
pg = int(req.get("page", 1)) - 1
|
||||
ps = int(req.get("size", 1000))
|
||||
src = req.get("field", ["docnm_kwd", "content_ltks", "kb_id",
|
||||
"image_id", "doc_id", "q_vec"])
|
||||
src = req.get("fields", ["docnm_kwd", "content_ltks", "kb_id","img_id",
|
||||
"image_id", "doc_id", "q_512_vec", "q_768_vec",
|
||||
"q_1024_vec", "q_1536_vec"])
|
||||
|
||||
s = s.query(bqry)[pg * ps:(pg + 1) * ps]
|
||||
s = s.highlight("content_ltks")
|
||||
|
|
@ -66,22 +69,24 @@ class Dealer:
|
|||
s = s.sort(
|
||||
{"create_time": {"order": "desc", "unmapped_type": "date"}})
|
||||
|
||||
s = s.highlight_options(
|
||||
fragment_size=120,
|
||||
number_of_fragments=5,
|
||||
boundary_scanner_locale="zh-CN",
|
||||
boundary_scanner="SENTENCE",
|
||||
boundary_chars=",./;:\\!(),。?:!……()——、"
|
||||
)
|
||||
if qst:
|
||||
s = s.highlight_options(
|
||||
fragment_size=120,
|
||||
number_of_fragments=5,
|
||||
boundary_scanner_locale="zh-CN",
|
||||
boundary_scanner="SENTENCE",
|
||||
boundary_chars=",./;:\\!(),。?:!……()——、"
|
||||
)
|
||||
s = s.to_dict()
|
||||
q_vec = []
|
||||
if req.get("vector"):
|
||||
s["knn"] = self._vector(qst, req.get("similarity", 0.4), ps)
|
||||
s["knn"]["filter"] = bqry.to_dict()
|
||||
del s["highlight"]
|
||||
if "highlight" in s: del s["highlight"]
|
||||
q_vec = s["knn"]["query_vector"]
|
||||
es_logger.info("【Q】: {}".format(json.dumps(s)))
|
||||
res = self.es.search(s, idxnm=idxnm, timeout="600s", src=src)
|
||||
print("TOTAL: ", self.es.getTotal(res))
|
||||
es_logger.info("TOTAL: {}".format(self.es.getTotal(res)))
|
||||
if self.es.getTotal(res) == 0 and "knn" in s:
|
||||
bqry, _ = self.qryr.question(qst, min_match="10%")
|
||||
if req.get("kb_ids"):
|
||||
|
|
@ -109,8 +114,7 @@ class Dealer:
|
|||
query_vector=q_vec,
|
||||
aggregation=aggs,
|
||||
highlight=self.getHighlight(res),
|
||||
field=self.getFields(res, ["docnm_kwd", "content_ltks",
|
||||
"kb_id", "image_id", "doc_id", "q_vec"]),
|
||||
field=self.getFields(res, src),
|
||||
keywords=list(kwds)
|
||||
)
|
||||
|
||||
|
|
@ -237,14 +241,4 @@ class Dealer:
|
|||
return sim
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from util import es_conn
|
||||
SE = Dealer(es_conn.HuEs("infiniflow"))
|
||||
qs = [
|
||||
"胡凯",
|
||||
""
|
||||
]
|
||||
for q in qs:
|
||||
print(">>>>>>>>>>>>>>>>>>>>", q)
|
||||
print(SE.search(
|
||||
{"question": q, "kb_ids": "64f072a75f3b97c865718c4a"}, "infiniflow_*"))
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class Dealer:
|
|||
return set(res.keys())
|
||||
return res
|
||||
|
||||
fnm = os.path.join(get_project_base_directory(), "res")
|
||||
fnm = os.path.join(get_project_base_directory(), "rag/res")
|
||||
self.ne, self.df = {}, {}
|
||||
try:
|
||||
self.ne = json.load(open(os.path.join(fnm, "ner.json"), "r"))
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -108,17 +109,17 @@ def build(row, cvmdl):
|
|||
(int(DOC_MAXIMUM_SIZE / 1024 / 1024)))
|
||||
return []
|
||||
|
||||
res = ELASTICSEARCH.search(Q("term", doc_id=row["id"]))
|
||||
if ELASTICSEARCH.getTotal(res) > 0:
|
||||
ELASTICSEARCH.updateScriptByQuery(Q("term", doc_id=row["id"]),
|
||||
scripts="""
|
||||
if(!ctx._source.kb_id.contains('%s'))
|
||||
ctx._source.kb_id.add('%s');
|
||||
""" % (str(row["kb_id"]), str(row["kb_id"])),
|
||||
idxnm=search.index_name(row["tenant_id"])
|
||||
)
|
||||
set_progress(row["id"], 1, "Done")
|
||||
return []
|
||||
# res = ELASTICSEARCH.search(Q("term", doc_id=row["id"]))
|
||||
# if ELASTICSEARCH.getTotal(res) > 0:
|
||||
# ELASTICSEARCH.updateScriptByQuery(Q("term", doc_id=row["id"]),
|
||||
# scripts="""
|
||||
# if(!ctx._source.kb_id.contains('%s'))
|
||||
# ctx._source.kb_id.add('%s');
|
||||
# """ % (str(row["kb_id"]), str(row["kb_id"])),
|
||||
# idxnm=search.index_name(row["tenant_id"])
|
||||
# )
|
||||
# set_progress(row["id"], 1, "Done")
|
||||
# return []
|
||||
|
||||
random.seed(time.time())
|
||||
set_progress(row["id"], random.randint(0, 20) /
|
||||
|
|
@ -155,8 +156,7 @@ def build(row, cvmdl):
|
|||
"doc_id": row["id"],
|
||||
"kb_id": [str(row["kb_id"])],
|
||||
"docnm_kwd": os.path.split(row["location"])[-1],
|
||||
"title_tks": huqie.qie(row["name"]),
|
||||
"updated_at": str(row["update_time"]).replace("T", " ")[:19]
|
||||
"title_tks": huqie.qie(row["name"])
|
||||
}
|
||||
doc["title_sm_tks"] = huqie.qieqie(doc["title_tks"])
|
||||
output_buffer = BytesIO()
|
||||
|
|
@ -179,6 +179,7 @@ def build(row, cvmdl):
|
|||
|
||||
MINIO.put(row["kb_id"], d["_id"], output_buffer.getvalue())
|
||||
d["img_id"] = "{}-{}".format(row["kb_id"], d["_id"])
|
||||
d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19]
|
||||
docs.append(d)
|
||||
|
||||
for arr, img in obj.table_chunks:
|
||||
|
|
@ -193,6 +194,7 @@ def build(row, cvmdl):
|
|||
img.save(output_buffer, format='JPEG')
|
||||
MINIO.put(row["kb_id"], d["_id"], output_buffer.getvalue())
|
||||
d["img_id"] = "{}-{}".format(row["kb_id"], d["_id"])
|
||||
d["create_time"] = str(datetime.datetime.now()).replace("T", " ")[:19]
|
||||
docs.append(d)
|
||||
set_progress(row["id"], random.randint(60, 70) /
|
||||
100., "Continue embedding the content.")
|
||||
|
|
@ -218,23 +220,11 @@ def embedding(docs, mdl):
|
|||
vects = 0.1 * tts + 0.9 * cnts
|
||||
assert len(vects) == len(docs)
|
||||
for i, d in enumerate(docs):
|
||||
d["q_vec"] = vects[i].tolist()
|
||||
v = vects[i].tolist()
|
||||
d["q_%d_vec"%len(v)] = v
|
||||
return tk_count
|
||||
|
||||
|
||||
def model_instance(tenant_id, llm_type):
|
||||
model_config = TenantLLMService.get_api_key(tenant_id, model_type=LLMType.EMBEDDING)
|
||||
if not model_config:
|
||||
model_config = {"llm_factory": "local", "api_key": "", "llm_name": ""}
|
||||
else: model_config = model_config[0].to_dict()
|
||||
if llm_type == LLMType.EMBEDDING:
|
||||
if model_config["llm_factory"] not in EmbeddingModel: return
|
||||
return EmbeddingModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"])
|
||||
if llm_type == LLMType.IMAGE2TEXT:
|
||||
if model_config["llm_factory"] not in CvModel: return
|
||||
return CvModel[model_config.llm_factory](model_config["api_key"], model_config["llm_name"])
|
||||
|
||||
|
||||
def main(comm, mod):
|
||||
global model
|
||||
from rag.llm import HuEmbedding
|
||||
|
|
@ -247,12 +237,12 @@ def main(comm, mod):
|
|||
|
||||
tmf = open(tm_fnm, "a+")
|
||||
for _, r in rows.iterrows():
|
||||
embd_mdl = model_instance(r["tenant_id"], LLMType.EMBEDDING)
|
||||
embd_mdl = TenantLLMService.model_instance(r["tenant_id"], LLMType.EMBEDDING)
|
||||
if not embd_mdl:
|
||||
set_progress(r["id"], -1, "Can't find embedding model!")
|
||||
cron_logger.error("Tenant({}) can't find embedding model!".format(r["tenant_id"]))
|
||||
continue
|
||||
cv_mdl = model_instance(r["tenant_id"], LLMType.IMAGE2TEXT)
|
||||
cv_mdl = TenantLLMService.model_instance(r["tenant_id"], LLMType.IMAGE2TEXT)
|
||||
st_tm = timer()
|
||||
cks = build(r, cv_mdl)
|
||||
if not cks:
|
||||
|
|
|
|||
|
|
@ -241,6 +241,26 @@ class HuEs:
|
|||
es_logger.error("ES search timeout for 3 times!")
|
||||
raise Exception("ES search timeout.")
|
||||
|
||||
def get(self, doc_id, idxnm=None):
|
||||
for i in range(3):
|
||||
try:
|
||||
res = self.es.get(index=(self.idxnm if not idxnm else idxnm),
|
||||
id=doc_id)
|
||||
if str(res.get("timed_out", "")).lower() == "true":
|
||||
raise Exception("Es Timeout.")
|
||||
return res
|
||||
except Exception as e:
|
||||
es_logger.error(
|
||||
"ES get exception: " +
|
||||
str(e) +
|
||||
"【Q】:" +
|
||||
doc_id)
|
||||
if str(e).find("Timeout") > 0:
|
||||
continue
|
||||
raise e
|
||||
es_logger.error("ES search timeout for 3 times!")
|
||||
raise Exception("ES search timeout.")
|
||||
|
||||
def updateByQuery(self, q, d):
|
||||
ubq = UpdateByQuery(index=self.idxnm).using(self.es).query(q)
|
||||
scripts = ""
|
||||
|
|
|
|||
150
web_server/apps/chunk_app.py
Normal file
150
web_server/apps/chunk_app.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
#
|
||||
# Copyright 2019 The FATE Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import base64
|
||||
import hashlib
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
from elasticsearch_dsl import Q
|
||||
from flask import request
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
from rag.nlp import search, huqie
|
||||
from rag.utils import ELASTICSEARCH, rmSpace
|
||||
from web_server.db import LLMType
|
||||
from web_server.db.services import duplicate_name
|
||||
from web_server.db.services.kb_service import KnowledgebaseService
|
||||
from web_server.db.services.llm_service import TenantLLMService
|
||||
from web_server.db.services.user_service import UserTenantService
|
||||
from web_server.utils.api_utils import server_error_response, get_data_error_result, validate_request
|
||||
from web_server.utils import get_uuid
|
||||
from web_server.db.services.document_service import DocumentService
|
||||
from web_server.settings import RetCode
|
||||
from web_server.utils.api_utils import get_json_result
|
||||
from rag.utils.minio_conn import MINIO
|
||||
from web_server.utils.file_utils import filename_type
|
||||
|
||||
retrival = search.Dealer(ELASTICSEARCH, None)
|
||||
|
||||
@manager.route('/list', methods=['POST'])
|
||||
@login_required
|
||||
@validate_request("doc_id")
|
||||
def list():
|
||||
req = request.json
|
||||
doc_id = req["doc_id"]
|
||||
page = req.get("page", 1)
|
||||
size = req.get("size", 30)
|
||||
question = req.get("keywords", "")
|
||||
try:
|
||||
tenants = UserTenantService.query(user_id=current_user.id)
|
||||
if not tenants:
|
||||
return get_data_error_result(retmsg="Tenant not found!")
|
||||
res = retrival.search({
|
||||
"doc_ids": [doc_id], "page": page, "size": size, "question": question
|
||||
}, search.index_name(tenants[0].tenant_id))
|
||||
return get_json_result(data=res)
|
||||
except Exception as e:
|
||||
if str(e).find("not_found") > 0:
|
||||
return get_json_result(data=False, retmsg=f'Index not found!',
|
||||
retcode=RetCode.DATA_ERROR)
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/get', methods=['GET'])
|
||||
@login_required
|
||||
def get():
|
||||
chunk_id = request.args["chunk_id"]
|
||||
try:
|
||||
tenants = UserTenantService.query(user_id=current_user.id)
|
||||
if not tenants:
|
||||
return get_data_error_result(retmsg="Tenant not found!")
|
||||
res = ELASTICSEARCH.get(chunk_id, search.index_name(tenants[0].tenant_id))
|
||||
if not res.get("found"):return server_error_response("Chunk not found")
|
||||
id = res["_id"]
|
||||
res = res["_source"]
|
||||
res["chunk_id"] = id
|
||||
k = []
|
||||
for n in res.keys():
|
||||
if re.search(r"(_vec$|_sm_)", n):
|
||||
k.append(n)
|
||||
if re.search(r"(_tks|_ltks)", n):
|
||||
res[n] = rmSpace(res[n])
|
||||
for n in k: del res[n]
|
||||
|
||||
return get_json_result(data=res)
|
||||
except Exception as e:
|
||||
if str(e).find("NotFoundError") >= 0:
|
||||
return get_json_result(data=False, retmsg=f'Chunk not found!',
|
||||
retcode=RetCode.DATA_ERROR)
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/set', methods=['POST'])
|
||||
@login_required
|
||||
@validate_request("doc_id", "chunk_id", "content_ltks", "important_kwd", "docnm_kwd")
|
||||
def set():
|
||||
req = request.json
|
||||
d = {"id": req["chunk_id"]}
|
||||
d["content_ltks"] = huqie.qie(req["content_ltks"])
|
||||
d["content_sm_ltks"] = huqie.qieqie(d["content_ltks"])
|
||||
d["important_kwd"] = req["important_kwd"]
|
||||
d["important_tks"] = huqie.qie(" ".join(req["important_kwd"]))
|
||||
|
||||
try:
|
||||
tenant_id = DocumentService.get_tenant_id(req["doc_id"])
|
||||
if not tenant_id: return get_data_error_result(retmsg="Tenant not found!")
|
||||
embd_mdl = TenantLLMService.model_instance(tenant_id, LLMType.EMBEDDING.value)
|
||||
v, c = embd_mdl.encode([req["docnm_kwd"], req["content_ltks"]])
|
||||
v = 0.1 * v[0] + 0.9 * v[1]
|
||||
d["q_%d_vec"%len(v)] = v.tolist()
|
||||
ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
|
||||
return get_json_result(data=True)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route('/create', methods=['POST'])
|
||||
@login_required
|
||||
@validate_request("doc_id", "content_ltks", "important_kwd")
|
||||
def set():
|
||||
req = request.json
|
||||
md5 = hashlib.md5()
|
||||
md5.update((req["content_ltks"] + req["doc_id"]).encode("utf-8"))
|
||||
chunck_id = md5.hexdigest()
|
||||
d = {"id": chunck_id, "content_ltks": huqie.qie(req["content_ltks"])}
|
||||
d["content_sm_ltks"] = huqie.qieqie(d["content_ltks"])
|
||||
d["important_kwd"] = req["important_kwd"]
|
||||
d["important_tks"] = huqie.qie(" ".join(req["important_kwd"]))
|
||||
|
||||
try:
|
||||
e, doc = DocumentService.get_by_id(req["doc_id"])
|
||||
if not e: return get_data_error_result(retmsg="Document not found!")
|
||||
d["kb_id"] = [doc.kb_id]
|
||||
d["docnm_kwd"] = doc.name
|
||||
d["doc_id"] = doc.id
|
||||
|
||||
tenant_id = DocumentService.get_tenant_id(req["doc_id"])
|
||||
if not tenant_id: return get_data_error_result(retmsg="Tenant not found!")
|
||||
|
||||
embd_mdl = TenantLLMService.model_instance(tenant_id, LLMType.EMBEDDING.value)
|
||||
v, c = embd_mdl.encode([doc.name, req["content_ltks"]])
|
||||
DocumentService.increment_chunk_num(req["doc_id"], doc.kb_id, c, 1, 0)
|
||||
v = 0.1 * v[0] + 0.9 * v[1]
|
||||
d["q_%d_vec"%len(v)] = v.tolist()
|
||||
ELASTICSEARCH.upsert([d], search.index_name(tenant_id))
|
||||
return get_json_result(data={"chunk_id": chunck_id})
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
|
@ -24,10 +24,9 @@ from rag.nlp import search
|
|||
from rag.utils import ELASTICSEARCH
|
||||
from web_server.db.services import duplicate_name
|
||||
from web_server.db.services.kb_service import KnowledgebaseService
|
||||
from web_server.db.services.user_service import TenantService
|
||||
from web_server.utils.api_utils import server_error_response, get_data_error_result, validate_request
|
||||
from web_server.utils import get_uuid, get_format_time
|
||||
from web_server.db import StatusEnum, FileType
|
||||
from web_server.utils import get_uuid
|
||||
from web_server.db import FileType
|
||||
from web_server.db.services.document_service import DocumentService
|
||||
from web_server.settings import RetCode
|
||||
from web_server.utils.api_utils import get_json_result
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ from web_server.utils.api_utils import get_json_result
|
|||
def factories():
|
||||
try:
|
||||
fac = LLMFactoriesService.get_all()
|
||||
return get_json_result(data=fac.to_json())
|
||||
return get_json_result(data=[f.to_dict() for f in fac])
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
|
@ -60,9 +60,7 @@ def set_api_key():
|
|||
@login_required
|
||||
def my_llms():
|
||||
try:
|
||||
objs = TenantLLMService.query(tenant_id=current_user.id)
|
||||
objs = [o.to_dict() for o in objs]
|
||||
for o in objs: del o["api_key"]
|
||||
objs = TenantLLMService.get_my_llms(current_user.id)
|
||||
return get_json_result(data=objs)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
|
|
|||
|
|
@ -252,3 +252,17 @@ def tenant_info():
|
|||
return get_json_result(data=tenants)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
||||
|
||||
@manager.route("/set_tenant_info", methods=["POST"])
|
||||
@login_required
|
||||
@validate_request("tenant_id", "asr_id", "embd_id", "img2txt_id", "llm_id")
|
||||
def set_tenant_info():
|
||||
req = request.json
|
||||
try:
|
||||
tid = req["tenant_id"]
|
||||
del req["tenant_id"]
|
||||
TenantService.update_by_id(tid, req)
|
||||
return get_json_result(data=True)
|
||||
except Exception as e:
|
||||
return server_error_response(e)
|
||||
|
|
|
|||
|
|
@ -87,3 +87,10 @@ class DocumentService(CommonService):
|
|||
num = Knowledgebase.update(token_num=Knowledgebase.token_num+token_num, chunk_num=Knowledgebase.chunk_num+chunk_num).where(Knowledgebase.id==kb_id).execute()
|
||||
return num
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_tenant_id(cls, doc_id):
|
||||
docs = cls.model.select(Knowledgebase.tenant_id).join(Knowledgebase, on=(Knowledgebase.id == cls.model.kb_id)).where(cls.model.id == doc_id, Knowledgebase.status==StatusEnum.VALID.value)
|
||||
docs = docs.dicts()
|
||||
if not docs:return
|
||||
return docs[0]["tenant_id"]
|
||||
|
|
@ -16,10 +16,11 @@
|
|||
import peewee
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
from rag.llm import EmbeddingModel, CvModel
|
||||
from web_server.db import LLMType
|
||||
from web_server.db.db_models import DB, UserTenant
|
||||
from web_server.db.db_models import LLMFactories, LLM, TenantLLM
|
||||
from web_server.db.services.common_service import CommonService
|
||||
from web_server.utils import get_uuid, get_format_time
|
||||
from web_server.db.db_utils import StatusEnum
|
||||
|
||||
|
||||
|
|
@ -51,3 +52,25 @@ class TenantLLMService(CommonService):
|
|||
if not objs:return
|
||||
return objs[0]
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def get_my_llms(cls, tenant_id):
|
||||
fields = [cls.model.llm_factory, LLMFactories.logo, LLMFactories.tags, cls.model.model_type, cls.model.llm_name]
|
||||
objs = cls.model.select(*fields).join(LLMFactories, on=(cls.model.llm_factory==LLMFactories.name)).where(cls.model.tenant_id==tenant_id).dicts()
|
||||
|
||||
return list(objs)
|
||||
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def model_instance(cls, tenant_id, llm_type):
|
||||
model_config = cls.get_api_key(tenant_id, model_type=LLMType.EMBEDDING)
|
||||
if not model_config:
|
||||
model_config = {"llm_factory": "local", "api_key": "", "llm_name": ""}
|
||||
else:
|
||||
model_config = model_config[0].to_dict()
|
||||
if llm_type == LLMType.EMBEDDING:
|
||||
if model_config["llm_factory"] not in EmbeddingModel: return
|
||||
return EmbeddingModel[model_config["llm_factory"]](model_config["api_key"], model_config["llm_name"])
|
||||
if llm_type == LLMType.IMAGE2TEXT:
|
||||
if model_config["llm_factory"] not in CvModel: return
|
||||
return CvModel[model_config.llm_factory](model_config["api_key"], model_config["llm_name"])
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
# limitations under the License.
|
||||
#
|
||||
import base64
|
||||
from datetime import datetime
|
||||
import datetime
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
|
@ -287,16 +287,16 @@ def get_uuid():
|
|||
return uuid.uuid1().hex
|
||||
|
||||
|
||||
def datetime_format(date_time: datetime) -> datetime:
|
||||
return datetime(date_time.year, date_time.month, date_time.day, date_time.hour, date_time.minute, date_time.second)
|
||||
def datetime_format(date_time: datetime.datetime) -> datetime.datetime:
|
||||
return datetime.datetime(date_time.year, date_time.month, date_time.day, date_time.hour, date_time.minute, date_time.second)
|
||||
|
||||
|
||||
def get_format_time() -> datetime:
|
||||
return datetime_format(datetime.now())
|
||||
def get_format_time() -> datetime.datetime:
|
||||
return datetime_format(datetime.datetime.now())
|
||||
|
||||
|
||||
def str2date(date_time: str):
|
||||
return datetime.strptime(date_time, '%Y-%m-%d')
|
||||
return datetime.datetime.strptime(date_time, '%Y-%m-%d')
|
||||
|
||||
|
||||
def elapsed2time(elapsed):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue