Merge remote-tracking branch 'origin/developing' into developing

This commit is contained in:
jipengfei-jpf
2023-07-08 20:17:50 +08:00
25 changed files with 445 additions and 95 deletions

View File

@ -11,6 +11,7 @@
"ahooks", "ahooks",
"antd", "antd",
"asar", "asar",
"bgcolor",
"cascader", "cascader",
"datasource", "datasource",
"echart", "echart",

View File

@ -8,7 +8,7 @@
} }
.ant-spin-container { .ant-spin-container {
height: 100%; height: calc(100% - 48px);
} }
} }

View File

@ -17,6 +17,7 @@ import { IAIState } from '@/models/ai';
import Popularize from '@/components/Popularize'; import Popularize from '@/components/Popularize';
import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils';
import styles from './index.less'; import styles from './index.less';
import { chatErrorCodeArr } from '@/constants/chat';
enum IPromptType { enum IPromptType {
NL_2_SQL = 'NL_2_SQL', NL_2_SQL = 'NL_2_SQL',
@ -195,6 +196,14 @@ function Console(props: IProps) {
setIsLoading(false); setIsLoading(false);
try { try {
const hasError = chatErrorCodeArr.includes(message);
//TODO:
if (hasError) {
closeEventSource();
setIsLoading(false);
return
}
const isEOF = message === '[DONE]'; const isEOF = message === '[DONE]';
if (isEOF) { if (isEOF) {
closeEventSource(); closeEventSource();
@ -306,7 +315,7 @@ function Console(props: IProps) {
onPressEnter={onPressChatInput} onPressEnter={onPressChatInput}
selectedTables={selectedTables} selectedTables={selectedTables}
onSelectTables={(tables: string[]) => { onSelectTables={(tables: string[]) => {
if(tables.length > 8){ if (tables.length > 8) {
message.warning({ message.warning({
content: i18n('chat.input.tableSelect.error.TooManyTable') content: i18n('chat.input.tableSelect.error.TooManyTable')
}) })
@ -332,7 +341,7 @@ function Console(props: IProps) {
onExecute={executeSQL} onExecute={executeSQL}
options={props.editorOptions} options={props.editorOptions}
tables={props.tables} tables={props.tables}
// onChange={} // onChange={}
/> />
{/* <Modal open={modelConfig.open}>{modelConfig.content}</Modal> */} {/* <Modal open={modelConfig.open}>{modelConfig.content}</Modal> */}
<Drawer open={isAiDrawerOpen} getContainer={false} mask={false} onClose={() => setIsAiDrawerOpen(false)}> <Drawer open={isAiDrawerOpen} getContainer={false} mask={false} onClose={() => setIsAiDrawerOpen(false)}>

View File

@ -96,23 +96,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [
name: 'url', name: 'url',
required: true, required: true,
}, },
{
defaultValue: '8.0',
inputType: InputType.SELECT,
labelNameCN: 'JDBC驱动',
labelNameEN: 'JDBC Driver',
name: 'jdbc',
required: true,
selects: [
{
value: '8.0',
},
{
value: '5.0',
},
],
}
], ],
pattern: /jdbc:mysql:\/\/(.*):(\d+)(\/(\w+))?/, pattern: /jdbc:mysql:\/\/(.*):(\d+)(\/(\w+))?/,
template: 'jdbc:mysql://{host}:{port}/{database}', template: 'jdbc:mysql://{host}:{port}/{database}',
@ -3061,3 +3044,34 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [
type: DatabaseTypeCode.MONGODB type: DatabaseTypeCode.MONGODB
}, },
]; ];
export const driveConfig: IConnectionConfig['drive'] = {
items: [
{
defaultValue: '',
inputType: InputType.SELECT,
labelNameCN: 'jdbcDriver',
labelNameEN: 'jdbcDriver',
name: 'jdbcDriver',
required: false,
selects: [
{
value: '1',
label: '/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/src/components/CreateConnection/config/dataSource.ts'
},
{
value: '2',
label: '/222222Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/src/components/CreateConnection/config/dataSource.ts'
},
],
},
// {
// defaultValue: '',
// inputType: InputType.INPUT,
// labelNameCN: 'jdbcDriverClass',
// labelNameEN: 'jdbcDriverClass',
// name: 'jdbcDriverClass',
// required: false,
// },
]
};

View File

@ -4,6 +4,9 @@ import { DatabaseTypeCode, OperationColumn } from '@/constants';
export type ISelect = { export type ISelect = {
value?: AuthenticationType | SSHAuthenticationType | string; value?: AuthenticationType | SSHAuthenticationType | string;
label?: string; label?: string;
rest?: {
[key in string]: any
}
items?: IFormItem[]; items?: IFormItem[];
}; };
@ -34,6 +37,9 @@ export type IConnectionConfig = {
template: string; template: string;
excludes?: OperationColumn[]; excludes?: OperationColumn[];
}, },
drive?: {
items: IFormItem[];
}
ssh: { ssh: {
items: IFormItem[]; items: IFormItem[];
}, },

View File

@ -13,7 +13,8 @@
} }
.connectionBox { .connectionBox {
width: 100%; width: 65%;
flex-shrink: 0;
padding: 20px 20%; padding: 20px 20%;
} }
@ -110,6 +111,34 @@
} }
} }
.downloadDriveFooter{
display: flex;
align-items: center;
justify-content: space-between;
.downloadDrive{
display: flex;
.downloadText{
margin-right: 4px;
color: var(--color-primary);
cursor: pointer;
&:hover{
text-decoration: underline;
}
}
.downloadTextError{
color: var(--color-error-text);
}
}
.uploadCustomDrive{
cursor: pointer;
&:hover{
color: var(--color-primary);
}
}
}
.extendTable { .extendTable {
max-height: 300px; max-height: 300px;
overflow-y: auto; overflow-y: auto;

View File

@ -3,21 +3,22 @@ import { i18n, isEn } from '@/i18n';
import styles from './index.less'; import styles from './index.less';
import classnames from 'classnames'; import classnames from 'classnames';
import connectionService from '@/service/connection'; import connectionService, { IDriverResponse } from '@/service/connection';
import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants'; import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants';
import { dataSourceFormConfigs } from './config/dataSource'; import { dataSourceFormConfigs, driveConfig } from './config/dataSource';
import { IConnectionConfig, IFormItem, ISelect } from './config/types'; import { IConnectionConfig, IFormItem, ISelect } from './config/types';
import { IConnectionDetails } from '@/typings'; import { IConnectionDetails } from '@/typings';
import { InputType } from './config/enum'; import { InputType } from './config/enum';
import { deepClone } from '@/utils'; import { deepClone } from '@/utils';
import { Select, Form, Input, message, Table, Button, Collapse } from 'antd'; import { Select, Form, Input, message, Table, Button, Collapse, Modal } from 'antd';
import Iconfont from '@/components/Iconfont'; import Iconfont from '@/components/Iconfont';
import LoadingContent from '@/components/Loading/LoadingContent'; import LoadingContent from '@/components/Loading/LoadingContent';
import UploadDriver from '@/components/UploadDriver';
const { Option } = Select; const { Option } = Select;
type ITabsType = 'ssh' | 'baseInfo'; type ITabsType = 'ssh' | 'baseInfo' | 'drive';
export enum submitType { export enum submitType {
UPDATE = 'update', UPDATE = 'update',
@ -32,18 +33,44 @@ interface IProps {
submitCallback?: Function; submitCallback?: Function;
} }
enum DownloadStatus {
Default,
Loading,
Error,
Success
}
export default function CreateConnection(props: IProps) { export default function CreateConnection(props: IProps) {
const { className, closeCreateConnection, submitCallback } = props; const { className, closeCreateConnection, submitCallback, connectionData } = props;
const [baseInfoForm] = Form.useForm(); const [baseInfoForm] = Form.useForm();
const [sshForm] = Form.useForm(); const [sshForm] = Form.useForm();
const [backfillData, setBackfillData] = useState<IConnectionDetails>(props.connectionData); const [driveForm] = Form.useForm();
const [backfillData, setBackfillData] = useState<IConnectionDetails>(connectionData);
const [loadings, setLoading] = useState({ const [loadings, setLoading] = useState({
confirmButton: false, confirmButton: false,
testButton: false, testButton: false,
backfillDataLoading: false backfillDataLoading: false
}); });
// const [connectionData, setConnectionData] = useState<IConnectionDetails>(props.connectionData); const [downloadStatus, setDownloadStatus] = useState<DownloadStatus>(DownloadStatus.Default);
// const [currentType, setCurrentType] = useState<DatabaseTypeCode>(createType || DatabaseTypeCode.MYSQL); const [uploadDriverModal, setUploadDriverModal] = useState(false);
const [driverObj, setDriverObj] = useState<IDriverResponse>();
const [driverSaved, setDriverSaved] = useState<any>({});
const dataSourceFormConfigPropsMemo = useMemo<IConnectionConfig>(() => {
const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs)
return deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => {
const flag = t.type === backfillData.type;
if (flag) {
t.drive = driveConfig;
}
return flag
});
}, []);
useEffect(() => {
getDriverList()
}, [backfillData.type])
const [dataSourceFormConfigProps, setDataSourceFormConfigProps] = useState(dataSourceFormConfigPropsMemo);
useEffect(() => { useEffect(() => {
setBackfillData(props.connectionData); setBackfillData(props.connectionData);
@ -55,6 +82,26 @@ export default function CreateConnection(props: IProps) {
} }
}, [backfillData.id]); }, [backfillData.id]);
useEffect(() => {
if (driverObj?.driverConfigList?.length) {
const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs)
const newDataSourceFormConfigProps = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => {
const flag = t.type === backfillData.type;
if (flag) {
t.drive = driveConfig;
}
return flag
});
newDataSourceFormConfigProps.drive!.items[0].selects = driverObj?.driverConfigList.map(t => {
return {
value: t.jdbcDriver,
label: t.jdbcDriver
}
})
setDataSourceFormConfigProps(newDataSourceFormConfigProps)
}
}, [driverObj])
function getConnectionDetails(id: number) { function getConnectionDetails(id: number) {
setLoading({ setLoading({
...loadings, ...loadings,
@ -81,12 +128,54 @@ export default function CreateConnection(props: IProps) {
} }
const getItems = () => [ const getItems = () => [
{
key: 'drive',
label: 'Drive',
children: (
<div className={styles.sshBox}>
<RenderForm
dataSourceFormConfigProps={dataSourceFormConfigProps}
backfillData={backfillData!}
form={driveForm}
tab="drive"
/>
<div className={styles.downloadDriveFooter}>
{
!!driverObj?.driverConfigList?.length ? <div></div> : <div onClick={downloadDrive} className={styles.downloadDrive}>
{
(downloadStatus === DownloadStatus.Default) && <div className={styles.downloadText}>Download</div>
}
{
(downloadStatus === DownloadStatus.Loading) && <div className={styles.downloadText}>Downloading</div>
}
{
(downloadStatus === DownloadStatus.Error) && <div className={classnames(styles.downloadText, styles.downloadTextError)}>Try again download</div>
}
{i18n('connection.title.driver')}
</div>
}
<div
className={styles.uploadCustomDrive}
onClick={() => { setUploadDriverModal(true) }}
>
{i18n('connection.tips.customUpload')}
</div>
</div>
</div>
),
},
{ {
key: 'ssh', key: 'ssh',
label: 'SSH Configuration', label: 'SSH Configuration',
children: ( children: (
<div className={styles.sshBox}> <div className={styles.sshBox}>
<RenderForm backfillData={backfillData!} form={sshForm} tab="ssh" /> <RenderForm
dataSourceFormConfigProps={dataSourceFormConfigProps}
backfillData={backfillData!}
form={sshForm}
tab="ssh"
/>
<div className={styles.testSSHConnect}> <div className={styles.testSSHConnect}>
<div onClick={testSSH} className={styles.testSSHConnectText}> <div onClick={testSSH} className={styles.testSSHConnectText}>
{i18n('connection.message.testSshConnection')} {i18n('connection.message.testSshConnection')}
@ -109,6 +198,7 @@ export default function CreateConnection(props: IProps) {
// 测试、保存、修改连接 // 测试、保存、修改连接
function saveConnection(type: submitType) { function saveConnection(type: submitType) {
const ssh = sshForm.getFieldsValue(); const ssh = sshForm.getFieldsValue();
const driverConfig = driveForm.getFieldsValue()
const baseInfo = baseInfoForm.getFieldsValue(); const baseInfo = baseInfoForm.getFieldsValue();
const extendInfo: any = []; const extendInfo: any = [];
const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton'; const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton';
@ -123,10 +213,10 @@ export default function CreateConnection(props: IProps) {
let p: any = { let p: any = {
ssh, ssh,
driverConfig,
...baseInfo, ...baseInfo,
extendInfo, extendInfo,
// ...values, connectionEnvType: ConnectionEnvType.DAILY,
ConnectionEnvType: ConnectionEnvType.DAILY,
type: backfillData.type, type: backfillData.type,
}; };
@ -185,6 +275,32 @@ export default function CreateConnection(props: IProps) {
}); });
} }
function downloadDrive() {
setDownloadStatus(DownloadStatus.Loading)
connectionService.downloadDriver({ dbType: backfillData.type }).then(res => {
setDownloadStatus(DownloadStatus.Success)
}).catch(() => {
setDownloadStatus(DownloadStatus.Error)
})
}
function getDriverList() {
connectionService.getDriverList({ dbType: backfillData.type }).then(res => {
setDriverObj(res)
})
}
function formChange(data: any) {
setDriverSaved(data)
}
function saveDriver() {
connectionService.saveDriver(driverSaved).then(res => {
setUploadDriverModal(false)
getDriverList()
})
}
return ( return (
<div className={classnames(styles.box, className)}> <div className={classnames(styles.box, className)}>
<LoadingContent className={styles.loadingContent} data={!loadings.backfillDataLoading}> <LoadingContent className={styles.loadingContent} data={!loadings.backfillDataLoading}>
@ -194,9 +310,9 @@ export default function CreateConnection(props: IProps) {
<div>{databaseMap[backfillData.type]?.name}</div> <div>{databaseMap[backfillData.type]?.name}</div>
</div> </div>
<div className={styles.baseInfoBox}> <div className={styles.baseInfoBox}>
<RenderForm backfillData={backfillData!} form={baseInfoForm} tab="baseInfo" /> <RenderForm dataSourceFormConfigProps={dataSourceFormConfigProps} backfillData={backfillData!} form={baseInfoForm} tab="baseInfo" />
</div> </div>
<Collapse items={getItems()} /> <Collapse defaultActiveKey={['drive']} items={getItems()} />
<div className={styles.formFooter}> <div className={styles.formFooter}>
<div className={styles.test}> <div className={styles.test}>
{ {
@ -225,6 +341,19 @@ export default function CreateConnection(props: IProps) {
</div> </div>
</div> </div>
</LoadingContent> </LoadingContent>
<Modal
destroyOnClose={true}
title={i18n('connection.title.uploadDriver')}
open={uploadDriverModal}
onOk={() => { saveDriver() }}
onCancel={() => { setUploadDriverModal(false) }}
>
<UploadDriver
jdbcDriverClass={driverObj?.defaultDriverConfig?.jdbcDriverClass}
formChange={formChange}
databaseType={backfillData.type}
></UploadDriver>
</Modal>
</div> </div>
); );
} }
@ -233,29 +362,25 @@ interface IRenderFormProps {
tab: ITabsType; tab: ITabsType;
form: any; form: any;
backfillData: IConnectionDetails; backfillData: IConnectionDetails;
dataSourceFormConfigProps: IConnectionConfig
} }
function RenderForm(props: IRenderFormProps) { function RenderForm(props: IRenderFormProps) {
const { tab, form, backfillData } = props; const { tab, form, backfillData, dataSourceFormConfigProps } = props;
const editId = backfillData.id; useEffect(() => {
const databaseType = backfillData.type; form.resetFields()
}, [backfillData.id, backfillData.type])
let aliasChanged = false; let aliasChanged = false;
const dataSourceFormConfigMemo = useMemo<IConnectionConfig>(() => { const [dataSourceFormConfig, setDataSourceFormConfig] = useState<IConnectionConfig>(dataSourceFormConfigProps);
return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => {
return t.type === databaseType;
});
}, [databaseType]);
const [dataSourceFormConfig, setDataSourceFormConfig] = useState<IConnectionConfig>(dataSourceFormConfigMemo);
useEffect(() => { useEffect(() => {
setDataSourceFormConfig(dataSourceFormConfigMemo); setDataSourceFormConfig(dataSourceFormConfigProps)
}, [databaseType]); }, [dataSourceFormConfigProps])
const initialValuesMemo = useMemo(() => { const initialValuesMemo = useMemo(() => {
return initialFormData(dataSourceFormConfigMemo[tab].items); return initialFormData(dataSourceFormConfigProps[tab]?.items);
}, []); }, []);
const [initialValues] = useState(initialValuesMemo); const [initialValues] = useState(initialValuesMemo);
@ -273,6 +398,9 @@ function RenderForm(props: IRenderFormProps) {
if (tab === 'ssh') { if (tab === 'ssh') {
regEXFormatting({}, backfillData.ssh || {}); regEXFormatting({}, backfillData.ssh || {});
} }
if (tab === 'drive') {
regEXFormatting({}, backfillData.driverConfig || {});
}
}, [backfillData]); }, [backfillData]);
function initialFormData(dataSourceFormConfig: IFormItem[] | undefined) { function initialFormData(dataSourceFormConfig: IFormItem[] | undefined) {
@ -294,7 +422,7 @@ function RenderForm(props: IRenderFormProps) {
} }
function selectChange(t: { name: string; value: any }) { function selectChange(t: { name: string; value: any }) {
dataSourceFormConfig[tab].items.map((j, i) => { dataSourceFormConfig[tab]?.items.map((j, i) => {
if (j.name === t.name) { if (j.name === t.name) {
j.defaultValue = t.value; j.defaultValue = t.value;
} }
@ -366,6 +494,7 @@ function RenderForm(props: IRenderFormProps) {
if (keyName === 'host' && !aliasChanged) { if (keyName === 'host' && !aliasChanged) {
newData.alias = '@' + keyValue; newData.alias = '@' + keyValue;
} }
form.setFieldsValue({ form.setFieldsValue({
...dataObj, ...dataObj,
...newData, ...newData,
@ -458,23 +587,33 @@ function RenderForm(props: IRenderFormProps) {
</Form> </Form>
); );
} }
interface IRenderExtendTableProps { interface IRenderExtendTableProps {
backfillData: IConnectionDetails; backfillData: IConnectionDetails;
} }
let extendTableData: any = []; let extendTableData: any = [];
interface IExtendTable {
key: number,
label: string,
value: string
}
function RenderExtendTable(props: IRenderExtendTableProps) { function RenderExtendTable(props: IRenderExtendTableProps) {
const { backfillData } = props; const { backfillData } = props;
const databaseType = backfillData.type; const databaseType = backfillData.type;
const [data, setData] = useState<IExtendTable[]>([{ key: 0, label: '', value: '' }]);
const dataSourceFormConfigMemo = useMemo<IConnectionConfig>(() => { const dataSourceFormConfigMemo = useMemo<IConnectionConfig>(() => {
return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => {
return t.type === databaseType; return t.type === databaseType;
}); });
}, [backfillData.type]); }, [backfillData.type]);
const extendInfo = useEffect(() => {
dataSourceFormConfigMemo.extendInfo?.map((t, i) => { const extendInfoList = backfillData?.extendInfo?.length ? backfillData?.extendInfo : dataSourceFormConfigMemo.extendInfo;
const extendInfo = extendInfoList?.map((t, i) => {
return { return {
key: i, key: i,
label: t.key, label: t.key,
@ -482,19 +621,8 @@ function RenderExtendTable(props: IRenderExtendTableProps) {
}; };
}) || []; }) || [];
const [data, setData] = useState([...extendInfo, { key: extendInfo.length, label: '', value: '' }]); setData([...extendInfo, { key: extendInfo.length, label: '', value: '' }])
}, [dataSourceFormConfigMemo, backfillData])
useEffect(() => {
const backfillDataExtendInfo =
(backfillData?.extendInfo || []).map((t, i) => {
return {
key: i,
label: t.key,
value: t.value,
};
}) || [];
setData([...backfillDataExtendInfo, { key: extendInfo.length, label: '', value: '' }]);
}, [backfillData]);
useEffect(() => { useEffect(() => {
extendTableData = data; extendTableData = data;

View File

@ -1,7 +1,38 @@
@import '../../styles/var.less'; @import '../../styles/var.less';
.notification{
padding: 10px 16px; :global {
.ant-notification-notice {
background-color: var(--color-bg-elevated) !important;
padding: 8px 16px !important;
width: 280px !important;
.ant-notification-notice-icon {
font-size: 16px !important;
top: 12px !important;
}
.ant-notification-notice-message {
color: var(--color-text) !important;
}
.ant-notification-notice-close {
color: var(--color-text) !important
}
}
.ant-modal-content {
background-color: var(--color-bg-elevated) !important;
.ant-modal-confirm-title {
color: var(--color-text) !important;
}
.ant-modal-confirm-content {
color: var(--color-text) !important;
}
}
} }
.message { .message {

View File

@ -1,7 +1,8 @@
import { Button, Modal, notification, } from 'antd'; import { Button, ConfigProvider, Modal, notification, } from 'antd';
import React from 'react' import React from 'react'
import styles from './index.less' import styles from './index.less'
import i18n from '@/i18n'; import i18n from '@/i18n';
import { IconType } from 'antd/es/notification/interface';
// import { staticNotification } from '@/layouts' // import { staticNotification } from '@/layouts'
interface IProps { interface IProps {
@ -22,14 +23,12 @@ function MyNotification(props: IProps) {
const type = props.type || 'warning'; const type = props.type || 'warning';
const title = `${errorCode}:${errorMessage}`; const title = `${errorCode}:${errorMessage}`;
const message = props.message || <div className={styles.message}>{errorCode}:{errorMessage}</div> const message = props.message || <div className={styles.message}>{errorCode}:{errorMessage || 'Error'}</div>
const description = <div className={styles.description}> const description = <div className={styles.description}>
<Button style={{ 'marginRight': '8px' }} type='link' onClick={() => { <Button style={{ 'marginRight': '8px' }} type='link' onClick={() => {
Modal.info({ Modal.info({
bodyStyle: { width: 620,
width: '320px'
},
title, title,
content: errorDetail content: errorDetail
}) })
@ -40,7 +39,6 @@ function MyNotification(props: IProps) {
return notification.open({ return notification.open({
...props, ...props,
className: styles.notification,
type, type,
message, message,
description, description,

View File

@ -0,0 +1,4 @@
@import '../../styles/var.less';
.box {
}

View File

@ -0,0 +1,68 @@
import React, { memo, useEffect, useState, useRef } from 'react';
import i18n from '@/i18n'
import styles from './index.less';
import classnames from 'classnames';
import { Button, message, Upload, Form, Input } from 'antd';
import type { UploadProps } from 'antd';
import connectionService from '@/service/connection';
import { DatabaseTypeCode } from '@/constants'
interface IProps {
className?: string;
databaseType: DatabaseTypeCode;
formChange: Function;
jdbcDriverClass: string | undefined;
}
export default memo<IProps>(function UploadDriver(props) {
const { className, databaseType = DatabaseTypeCode.MYSQL, formChange, jdbcDriverClass } = props;
const [formData, setFormData] = useState<any>({
dbType: databaseType,
jdbcDriverClass: jdbcDriverClass,
jdbcDriver: []
});
const uploadProps: UploadProps = {
name: 'multipartFiles',
action: `${window._BaseURL}/api/jdbc/driver/upload`,
multiple: true,
onChange(info) {
if (info.file.percent === 100 && info.file?.response?.data?.[0]) {
setFormData({
...formData,
jdbcDriver: [...(formData.jdbcDriver), info.file?.response?.data?.[0]]
})
}
},
};
useEffect(() => {
formChange(formData)
}, [formData])
function onChange(e: any) {
setFormData({
...formData,
jdbcDriverClass: e.target.value
})
}
return <div className={classnames(styles.box, className)}>
<div>
<Form
name="basic"
labelCol={{ span: 5 }}
wrapperCol={{ span: 16 }}
>
<Form.Item label="jdbcDriverClass">
<Input value={formData.jdbcDriverClass} onChange={onChange} />
</Form.Item>
<Form.Item label={i18n('connection.title.uploadDriver')}>
<Upload {...uploadProps}>
<Button>{i18n('connection.button.clickUpload')}</Button>
</Upload>
</Form.Item>
</Form>
</div>
</div >
})

View File

@ -0,0 +1,15 @@
export const chatError = {
"CHAT2DB_KEY_INVALID": "apikey 不在我们的数据库中需要扫码登录",
"CHAT2DB_KEY_LIMIT": "次数用完了,需要发起推广",
"CHAT2DB_KEY_EXPIRED": "到过期时间了",
"CHAT2DB_SERVICE_BUSY": "这个异常就稍后重试就行了",
"CHAT2DB_AUTH_HEADER_MISSING": "这个是 http 请求 header 没传 Authorization 字段,这个你看要怎么处理",
"CHAT2DB_AUTH_TOKEN_MISSING": "这个是 http 请求 header 中 Authorization 后面没有以 Bearer 开头,也是传的认证信息有问题,你看要怎么处理",
"CHAT2DB_SERVICE_ERROR": "这个是出了意料之外的异常要联系管理员",
"CHAT2DB_BAD_JSON_FORMAT": "这个是传的请求不是 json 格式",
"CHAT2DB_HTTP_METHOD_INVALID": "这个是传的请求不是 post 请求,目前给 openai 的请求必须是 post"
}
export const chatErrorCodeArr = Object.keys(chatError);

View File

@ -58,6 +58,6 @@ export default {
'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.', 'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.',
'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.',
'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.',
'common.notification.detial': 'More Detial', 'common.notification.detial': 'More details',
'common.notification.solution': 'Solution', 'common.notification.solution': 'Solution',
}; };

View File

@ -17,4 +17,9 @@ export default {
'connection.message.testSshConnection': 'Test the ssh connection', 'connection.message.testSshConnection': 'Test the ssh connection',
'connection.tableHeader.name': 'Name', 'connection.tableHeader.name': 'Name',
'connection.tableHeader.value': 'Value', 'connection.tableHeader.value': 'Value',
'connection.title.uploadDriver': 'Upload Driver',
'connection.tips.customUpload': "I don't have the drive I want",
'connection.title.driver': 'Driver',
'connection.button.clickUpload': 'Click to Upload',
}; };

View File

@ -17,4 +17,8 @@ export default {
'connection.message.testSshConnection': '测试ssh连接', 'connection.message.testSshConnection': '测试ssh连接',
'connection.tableHeader.name': '名称', 'connection.tableHeader.name': '名称',
'connection.tableHeader.value': '值', 'connection.tableHeader.value': '值',
'connection.title.uploadDriver': '上传驱动',
'connection.tips.customUpload': '没有我想要的驱动',
'connection.title.driver': '驱动',
'connection.button.clickUpload': '点击上传',
} }

View File

@ -1,7 +1,7 @@
import React, { useEffect, useLayoutEffect } from 'react'; import React, { useEffect, useLayoutEffect } from 'react';
import i18n from '@/i18n'; import i18n from '@/i18n';
import { Outlet } from 'umi'; import { Outlet } from 'umi';
import { ConfigProvider, theme, notification } from 'antd'; import { ConfigProvider, theme, App, Button } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { getAntdThemeConfig } from '@/theme'; import { getAntdThemeConfig } from '@/theme';
@ -22,7 +22,9 @@ import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorag
import { clearOlderLocalStorage } from '@/utils'; import { clearOlderLocalStorage } from '@/utils';
import registerMessage from './init/registerMessage'; import registerMessage from './init/registerMessage';
import registerNotification from './init/registerNotification'; import registerNotification from './init/registerNotification';
import { NotificationInstance } from 'antd/es/notification/interface';
import { ModalStaticFunctions } from 'antd/es/modal/confirm';
import Sub from './sub'
declare global { declare global {
interface Window { interface Window {
_Lang: string; _Lang: string;
@ -69,6 +71,7 @@ export default function Layout() {
return ( return (
<ConfigProvider locale={isEn ? antdEnUS : antdZhCN} theme={antdTheme}> <ConfigProvider locale={isEn ? antdEnUS : antdZhCN} theme={antdTheme}>
<AppContainer></AppContainer> <AppContainer></AppContainer>
{/* <Sub /> */}
</ConfigProvider> </ConfigProvider>
); );
} }
@ -76,12 +79,19 @@ export default function Layout() {
/** 重启次数 */ /** 重启次数 */
const restartCount = 200; const restartCount = 200;
let staticNotification: NotificationInstance;
function AppContainer() { function AppContainer() {
const { token } = useToken(); const { token } = useToken();
const [initEnd, setInitEnd] = useState(false); const [initEnd, setInitEnd] = useState(false);
const [appTheme, setAppTheme] = useTheme(); const [appTheme, setAppTheme] = useTheme();
const [startSchedule, setStartSchedule] = useState(0); // 0 初始状态 1 服务启动中 2 启动成功 const [startSchedule, setStartSchedule] = useState(0); // 0 初始状态 1 服务启动中 2 启动成功
const [serviceFail, setServiceFail] = useState(false); const [serviceFail, setServiceFail] = useState(false);
const { notification } = App.useApp();
// staticNotification = staticFunction.notification
useEffect(() => { useEffect(() => {
let date = new Date('2030-12-30 12:30:00').toUTCString(); let date = new Date('2030-12-30 12:30:00').toUTCString();
@ -210,4 +220,4 @@ function AppContainer() {
)} )}
</div> </div>
); );
} }

View File

@ -53,7 +53,7 @@ function Connections(props: IProps) {
() => () =>
(connectionList || []).map((t) => ({ (connectionList || []).map((t) => ({
key: t.id, key: t.id,
icon: <Iconfont className={styles.menuItemIcon} code={databaseMap[t.type].icon} />, icon: <Iconfont className={styles.menuItemIcon} code={databaseMap[t.type]?.icon} />,
label: t.alias, label: t.alias,
meta: t, meta: t,
})), })),

View File

@ -227,7 +227,7 @@ function TreeNodeRightClick(props: IProps) {
return { return {
key: i, key: i,
label: <div className={styles.operationItem}> label: <div className={styles.operationItem}>
<Iconfont className={styles.operationIcon} code={concrete.icon} /> <Iconfont className={styles.operationIcon} code={concrete?.icon} />
<div className={styles.operationTitle}> <div className={styles.operationTitle}>
{concrete.text} {concrete.text}
</div> </div>

View File

@ -51,7 +51,6 @@ export enum OperationColumn {
ExportDDL = 'exportDDL', ExportDDL = 'exportDDL',
EditSource = 'editSource', EditSource = 'editSource',
Top = 'top' Top = 'top'
} }
export interface ITreeConfigItem { export interface ITreeConfigItem {

View File

@ -1,13 +1,11 @@
import React, { memo, useEffect, useRef, useState, useReducer, useContext } from 'react'; import React, { memo, useRef, } from 'react';
import { connect } from 'umi'; import { connect } from 'umi';
import { Spin } from 'antd'
import styles from './index.less'; import styles from './index.less';
import DraggableContainer from '@/components/DraggableContainer'; import DraggableContainer from '@/components/DraggableContainer';
import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceLeft from './components/WorkspaceLeft';
import WorkspaceRight from './components/WorkspaceRight'; import WorkspaceRight from './components/WorkspaceRight';
import { IConnectionModelType } from '@/models/connection'; import { IConnectionModelType } from '@/models/connection';
import { IWorkspaceModelType } from '@/models/workspace'; import { IWorkspaceModelType } from '@/models/workspace';
import LoadingContent from '@/components/Loading/LoadingContent'
interface IProps { interface IProps {
className?: string; className?: string;
@ -23,14 +21,11 @@ const dvaModel = connect(
); );
const workspace = memo<IProps>((props) =>{ const workspace = memo<IProps>((props) => {
const draggableRef = useRef<any>(); const draggableRef = useRef<any>();
const {workspaceModel,connectionModel} = props; const { workspaceModel, connectionModel } = props;
const { curWorkspaceParams } = workspaceModel;
const { curConnection } = connectionModel;
return ( return (
// <LoadingContent data={curWorkspaceParams}>
<DraggableContainer className={styles.box}> <DraggableContainer className={styles.box}>
<div ref={draggableRef} className={styles.boxLeft}> <div ref={draggableRef} className={styles.boxLeft}>
<WorkspaceLeft /> <WorkspaceLeft />
@ -39,7 +34,6 @@ const workspace = memo<IProps>((props) =>{
<WorkspaceRight /> <WorkspaceRight />
</div> </div>
</DraggableContainer> </DraggableContainer>
// </LoadingContent>
); );
}); });

View File

@ -1,6 +1,6 @@
import { IPageResponse, IConnectionDetails } from '@/typings'; import { IPageResponse, IConnectionDetails } from '@/typings';
import { DatabaseTypeCode } from '@/constants';
import createRequest from './base'; import createRequest from './base';
// import { IPageResponse, IConnectionDetails, IDB } from '@/types';
export interface IGetConnectionParams { export interface IGetConnectionParams {
searchKey?: string; searchKey?: string;
@ -60,6 +60,32 @@ const getDBList = createRequest<{ id: number }, IDB[]>(
{ method: 'get' }, { method: 'get' },
); );
export interface IDriverResponse {
driverConfigList: {
jdbcDriver: string;
jdbcDriverClass: string;
}[],
defaultDriverConfig: {
jdbcDriverClass: string
};
}
interface IDriverParams {
dbType: DatabaseTypeCode;
}
interface IUploadDriver {
multipartFiles: any;
jdbcDriverClass: string;
dbType: string;
}
const getDriverList = createRequest<IDriverParams, IDriverResponse>('/api/jdbc/driver/list', { errorLevel: false, method: 'get' });
const downloadDriver = createRequest<{ dbType: string }, void>('/api/jdbc/driver/download', { errorLevel: false, method: 'get' });
const saveDriver = createRequest<IUploadDriver, void>('/api/jdbc/driver/save', { errorLevel: false, method: 'post' });
export default { export default {
getList, getList,
getDetails, getDetails,
@ -71,4 +97,7 @@ export default {
getDBList, getDBList,
close, close,
testSSH, testSSH,
getDriverList,
downloadDriver,
saveDriver,
}; };

View File

@ -6,5 +6,5 @@ const testApiSmooth = createRequest<void, void>('/api/system/get-version-a', { e
export default { export default {
testService, testService,
systemStop, systemStop,
testApiSmooth testApiSmooth,
} }

View File

@ -48,7 +48,7 @@ export interface IUniversalTableParams {
* 版本返回 * 版本返回
* VersionResponse * VersionResponse
*/ */
export interface IVersionResponse { export interface IVersionResponse {
/** /**
* 基础链接 * 基础链接
* 类似于http://test.sqlgpt.cn/gateway * 类似于http://test.sqlgpt.cn/gateway
@ -67,3 +67,5 @@ export interface IUniversalTableParams {
*/ */
wechatMpName?: string; wechatMpName?: string;
} }

View File

@ -16,6 +16,10 @@ export interface IConnectionDetails {
EnvType: ConnectionEnv; EnvType: ConnectionEnv;
extendInfo: IConnectionExtendInfoItem[]; extendInfo: IConnectionExtendInfoItem[];
ssh: any; ssh: any;
driverConfig: {
jdbcDriver: string;
jdbcDriverClass: string;
};
[key: string]: any; [key: string]: any;
} }

View File

@ -505,10 +505,11 @@ public class ChatController {
: queryRequest.getPromptType(); : queryRequest.getPromptType();
PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType); PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType);
String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : ""; String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : "";
String result = "假设你是个SQL编辑器接下来你返回的SQL代码要和其他内容分隔非SQL代码内容的每一行前面追加-- \n";
String schemaProperty = CollectionUtils.isNotEmpty(tableSchemas) ? String.format( String schemaProperty = CollectionUtils.isNotEmpty(tableSchemas) ? String.format(
"### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# " "%s### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# "
+ "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType, + "%s\n#\n#\n### SQL input: %s", result, pType.getDescription(), ext, dataSourceType,
properties, prompt) : String.format("### 请根据以下SQL input%s. %s\n#\n### SQL input: %s", properties, prompt) : String.format("%s### 请根据以下SQL input%s. %s\n#\n### SQL input: %s", result,
pType.getDescription(), ext, prompt); pType.getDescription(), ext, prompt);
switch (pType) { switch (pType) {
case SQL_2_SQL: case SQL_2_SQL:
@ -521,7 +522,6 @@ public class ChatController {
//if (I18nUtils.isEn()) { //if (I18nUtils.isEn()) {
// schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty); // schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty);
//} //}
String result = String.format("%s. \n要求返回Markdown格式", schemaProperty); return schemaProperty;
return result;
} }
} }