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

This commit is contained in:
SwallowGG
2023-11-28 16:01:22 +08:00
11 changed files with 155 additions and 68 deletions

View File

@ -1,3 +1,16 @@
## 3.0.14
`2023-11-20`
**Changelog**
- 🐞【Fixed】Team paging problem
- 🐞【Fixed】Oracle service name bug
- 🐞【Fixed】Oracle datatype error
- 🐞【Fixed】Fixed an issue where MySQL changed table structure without displaying comments.
-【Optimize】Support database or schema
- 【Developer】Friends don't worry, the company has some things recently, and is preparing 3.1.0, be patient
## 3.0.13 ## 3.0.13
`2023-11-15` `2023-11-15`

View File

@ -1,3 +1,15 @@
## 3.0.14
`2023-11-20`
**更新日志**
- 🐞【修复】团队分页问题
- 🐞【修复】Oracle服务名称错误
- 🐞【修复】Oracle数据类型错误
- 🐞【修复】修复MySQL修改表结构不回显注释的问题。
- ⚡️【优化】支持数据库或模式
- 【开发者】友友们不要着急呀最近公司有些事情并且在准备3.1.0,耐心等待哦
## 3.0.13 ## 3.0.13
`2023-11-15` `2023-11-15`

View File

@ -104,8 +104,7 @@ Redis and MongoDB are partially supported , Hbase、Elasticsearch、openGauss、
### CONFIGURE CUSTOM AI ### CONFIGURE CUSTOM AI
* [Refer here to deploy your ChatGLM-6B model](https://github.com/chat2db/chat2db-chatglm-6b-deploy) * The rest api format for Custom AI is same as ChatGPT.
* [Refer here to deploy your sqlcoder model](https://github.com/chat2db/chat2db-sqlcoder-deploy)
## 📦 Docker installation ## 📦 Docker installation
@ -179,12 +178,18 @@ $ npm run build:web:prod / cp -r dist ../chat2db-server/chat2db-server-start/src
## ☎️ Contact Us ## ☎️ Contact Us
Please star and fork on GitHub before joining the group. ### WeChat
Follow our WeChat public account.
<a><img src="https://github.com/chat2db/Chat2DB/assets/22975773/e4239d29-1426-4361-bf57-f1b0b67d1281" width="40%"/></a> <a><img src="https://github.com/chat2db/Chat2DB/assets/22975773/e4239d29-1426-4361-bf57-f1b0b67d1281" width="40%"/></a>
Click and join <a href="https://discord.gg/dqae8nsC">discord server</a> ### Discord
<!-- [![Discord](https://img.shields.io/badge/-Discord-%237289DA.svg?style=flat&logo=Discord&logoColor=white)](您的Discord邀请链接) -->
[![Discord](https://img.shields.io/badge/-Join%20us%20on%20Discord-%237289DA.svg?style=flat&logo=discord&logoColor=white)](https://discord.gg/N6JscF7q)
## LICENSE
The primary license used by this software is the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0), supplemented by the [Chat2DB License](./Chat2DB_LICENSE).
## ❤️ Acknowledgements ## ❤️ Acknowledgements

View File

@ -100,8 +100,7 @@ Redis和MongoDB得到部分支持Hbase、Elasticsearch、openGauss、TiDB、I
### 使用Chat2DB AI 上手即用 ### 使用Chat2DB AI 上手即用
### 使用自定义大模型 ### 使用自定义大模型
- [参考这里部署本地ChatGLM-6B模型](https://github.com/chat2db/chat2db-chatglm-6b-deploy/blob/main/README_CN.md) - 使用自定义大模型接口格式需要和open ai的接口格式保持一致
- [参考这里部署本地sqlcoder模型](https://github.com/chat2db/chat2db-sqlcoder-deploy/blob/main/README_CN.md)
## 📦 Docker 部署 ## 📦 Docker 部署

View File

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Button, Input, Table, Popconfirm, message, Drawer } from 'antd'; import { Button, Input, Table, Popconfirm, message, Drawer } from 'antd';
import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
import ConnectionServer from '@/service/connection' import ConnectionServer from '@/service/connection';
import { createDataSource, deleteDataSource, getDataSourceList, updateDataSource } from '@/service/team'; import { createDataSource, deleteDataSource, getDataSourceList, updateDataSource } from '@/service/team';
import { IConnectionDetails } from '@/typings'; import { IConnectionDetails } from '@/typings';
import { AffiliationType, IDataSourceVO } from '@/typings/team'; import { AffiliationType, IDataSourceVO } from '@/typings/team';
@ -23,13 +23,13 @@ function DataSourceManagement() {
showQuickJumper: true, showQuickJumper: true,
// pageSizeOptions: ['10', '20', '30', '40'], // pageSizeOptions: ['10', '20', '30', '40'],
}); });
const [showCreateConnection, setShowCreateConnection] = useState(false) const [showCreateConnection, setShowCreateConnection] = useState(false);
const connectionInfo = useRef<IConnectionDetails>(); const connectionInfo = useRef<IConnectionDetails>();
const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: AffiliationType; id?: number }>({ const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: AffiliationType; id?: number }>({
open: false, open: false,
type: AffiliationType['DATASOURCE_USER/TEAM'] type: AffiliationType['DATASOURCE_USER/TEAM'],
}) });
const columns = useMemo( const columns = useMemo(
() => [ () => [
@ -49,18 +49,24 @@ function DataSourceManagement() {
width: 300, width: 300,
render: (_: any, record: IDataSourceVO) => ( render: (_: any, record: IDataSourceVO) => (
<> <>
<Button type='link' onClick={() => { <Button
handleEdit(record) type="link"
}}> onClick={() => {
handleEdit(record);
}}
>
{i18n('common.button.edit')} {i18n('common.button.edit')}
</Button> </Button>
<Button type='link' onClick={() => { <Button
setDrawerInfo({ type="link"
...drawerInfo, onClick={() => {
open: true, setDrawerInfo({
id: record.id, ...drawerInfo,
}) open: true,
}}> id: record.id,
});
}}
>
{i18n('team.action.rightManagement')} {i18n('team.action.rightManagement')}
</Button> </Button>
<Popconfirm <Popconfirm
@ -89,6 +95,10 @@ function DataSourceManagement() {
let res = await getDataSourceList({ searchKey, pageNo, pageSize }); let res = await getDataSourceList({ searchKey, pageNo, pageSize });
if (res) { if (res) {
setDataSource(res?.data ?? []); setDataSource(res?.data ?? []);
setPagination({
...pagination,
total: res?.total ?? 0,
} as any);
} }
}; };
@ -100,7 +110,6 @@ function DataSourceManagement() {
}; };
const handleTableChange = (p: any) => { const handleTableChange = (p: any) => {
setPagination({ setPagination({
...pagination, ...pagination,
...p, ...p,
@ -110,7 +119,7 @@ function DataSourceManagement() {
const handleAddDataSource = () => { const handleAddDataSource = () => {
connectionInfo.current = undefined; connectionInfo.current = undefined;
setShowCreateConnection(true); setShowCreateConnection(true);
} };
const handleEdit = async (record: IDataSourceVO) => { const handleEdit = async (record: IDataSourceVO) => {
const { id } = record; const { id } = record;
@ -118,14 +127,14 @@ function DataSourceManagement() {
return; return;
} }
let detail = await ConnectionServer.getDetails({ id }) let detail = await ConnectionServer.getDetails({ id });
connectionInfo.current = detail; connectionInfo.current = detail;
setShowCreateConnection(true) setShowCreateConnection(true);
} };
const handleDelete = async (id?: number) => { const handleDelete = async (id?: number) => {
if (isNumber(id)) { if (isNumber(id)) {
await deleteDataSource({ id }) await deleteDataSource({ id });
message.success(i18n('common.text.successfullyDelete')); message.success(i18n('common.text.successfullyDelete'));
queryDataSourceList(); queryDataSourceList();
} }
@ -140,14 +149,12 @@ function DataSourceManagement() {
const isUpdate = isValid(connectionInfo?.current?.id); const isUpdate = isValid(connectionInfo?.current?.id);
const requestApi = isUpdate ? updateDataSource : createDataSource; const requestApi = isUpdate ? updateDataSource : createDataSource;
try { try {
await requestApi({ ...connectionInfo.current }) await requestApi({ ...connectionInfo.current });
message.success(isUpdate ? i18n('common.tips.updateSuccess') : i18n('common.tips.createSuccess')) message.success(isUpdate ? i18n('common.tips.updateSuccess') : i18n('common.tips.createSuccess'));
setShowCreateConnection(false) setShowCreateConnection(false);
queryDataSourceList() queryDataSourceList();
} catch { } catch {}
};
}
}
return ( return (
<div> <div>
@ -163,6 +170,11 @@ function DataSourceManagement() {
</Button> </Button>
</div> </div>
<Table <Table
style={{
maxHeight: '82vh',
overflow: 'auto',
}}
sticky
rowKey={'id'} rowKey={'id'}
dataSource={dataSource} dataSource={dataSource}
columns={columns} columns={columns}
@ -185,11 +197,11 @@ function DataSourceManagement() {
onClose={() => { onClose={() => {
setDrawerInfo({ setDrawerInfo({
...drawerInfo, ...drawerInfo,
open: false open: false,
}) });
}} }}
/> />
</div > </div>
); );
} }

View File

@ -1,4 +1,9 @@
.teamWrapper { .teamWrapper {
height: 100vh; height: 100vh;
padding: 24px 36px; padding: 14px 16px;
box-sizing: border-box;
}
.teamTabsBox{
height: 100%;
} }

View File

@ -33,6 +33,7 @@ const Team = () => {
return ( return (
<div className={styles.teamWrapper}> <div className={styles.teamWrapper}>
<Tabs <Tabs
className={styles.teamTabsBox}
activeKey={activeKey} activeKey={activeKey}
onChange={(activeKey) => setActiveKey(activeKey)} onChange={(activeKey) => setActiveKey(activeKey)}
items={tabList.map((tab, index) => { items={tabList.map((tab, index) => {

View File

@ -17,7 +17,7 @@ const requireRule = { required: true, message: i18n('common.form.error.required'
function TeamManagement() { function TeamManagement() {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loadding, setLoading] = useState(false) const [loadding, setLoading] = useState(false);
const [dataSource, setDataSource] = useState<ITeamVO[]>([]); const [dataSource, setDataSource] = useState<ITeamVO[]>([]);
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
searchKey: '', searchKey: '',
@ -66,7 +66,7 @@ function TeamManagement() {
...drawerInfo, ...drawerInfo,
open: true, open: true,
teamId: record.id, teamId: record.id,
type: AffiliationType.TEAM_USER type: AffiliationType.TEAM_USER,
}); });
}} }}
> >
@ -79,9 +79,10 @@ function TeamManagement() {
...drawerInfo, ...drawerInfo,
open: true, open: true,
teamId: record.id, teamId: record.id,
type: AffiliationType.TEAM_DATASOURCE type: AffiliationType.TEAM_DATASOURCE,
}); });
}}> }}
>
{i18n('team.action.affiliation.datasource')} {i18n('team.action.affiliation.datasource')}
</Button> </Button>
<Popconfirm <Popconfirm
@ -112,11 +113,14 @@ function TeamManagement() {
let res = await getTeamManagementList({ searchKey, pageNo, pageSize }); let res = await getTeamManagementList({ searchKey, pageNo, pageSize });
if (res) { if (res) {
setDataSource(res?.data ?? []); setDataSource(res?.data ?? []);
setPagination({
...pagination,
total: res?.total ?? 0,
} as any);
} }
} catch (error) { } catch (error) {
} finally { } finally {
setLoading(false) setLoading(false);
} }
}; };
@ -169,6 +173,11 @@ function TeamManagement() {
</Button> </Button>
</div> </div>
<Table <Table
style={{
maxHeight: '82vh',
overflow: 'auto',
}}
sticky
rowKey={'id'} rowKey={'id'}
loading={loadding} loading={loadding}
dataSource={dataSource} dataSource={dataSource}
@ -192,7 +201,7 @@ function TeamManagement() {
.catch((errorInfo) => { .catch((errorInfo) => {
form.scrollToField(errorInfo.errorFields[0].name); form.scrollToField(errorInfo.errorFields[0].name);
form.setFields(errorInfo.errorFields); form.setFields(errorInfo.errorFields);
}) });
}} }}
onCancel={() => { onCancel={() => {
form.resetFields(); form.resetFields();

View File

@ -116,6 +116,10 @@ function UserManagement() {
let res = await getUserManagementList({ searchKey, pageNo, pageSize }); let res = await getUserManagementList({ searchKey, pageNo, pageSize });
if (res) { if (res) {
setDataSource(res?.data ?? []); setDataSource(res?.data ?? []);
setPagination({
...pagination,
total: res?.total ?? 0,
} as any);
} }
}; };
@ -178,6 +182,11 @@ function UserManagement() {
</Button> </Button>
</div> </div>
<Table <Table
style={{
maxHeight: '82vh',
overflow: 'auto',
}}
sticky
rowKey={'id'} rowKey={'id'}
dataSource={dataSource} dataSource={dataSource}
columns={columns} columns={columns}

View File

@ -626,7 +626,8 @@ public class ChatController {
default: default:
break; break;
} }
return schemaProperty; String cleanedInput = schemaProperty.replaceAll("[\r\t]", "");
return cleanedInput;
} }
/** /**

View File

@ -1,15 +1,5 @@
package ai.chat2db.spi.sql; package ai.chat2db.spi.sql;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.constant.EasyToolsConstant;
import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.tools.common.util.I18nUtils;
import ai.chat2db.spi.ValueHandler; import ai.chat2db.spi.ValueHandler;
@ -24,6 +14,12 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.sql.*;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* Dbhub 统一数据库连接管理 * Dbhub 统一数据库连接管理
* *
@ -94,12 +90,12 @@ public class SQLExecutor {
} }
public void executeSql(Connection connection, String sql, Consumer<List<Header>> headerConsumer, public void executeSql(Connection connection, String sql, Consumer<List<Header>> headerConsumer,
Consumer<List<String>> rowConsumer,ValueHandler valueHandler) { Consumer<List<String>> rowConsumer, ValueHandler valueHandler) {
executeSql(connection, sql, headerConsumer, rowConsumer, true,valueHandler); executeSql(connection, sql, headerConsumer, rowConsumer, true, valueHandler);
} }
public void executeSql(Connection connection, String sql, Consumer<List<Header>> headerConsumer, public void executeSql(Connection connection, String sql, Consumer<List<Header>> headerConsumer,
Consumer<List<String>> rowConsumer, boolean limitSize,ValueHandler valueHandler) { Consumer<List<String>> rowConsumer, boolean limitSize, ValueHandler valueHandler) {
Assert.notNull(sql, "SQL must not be null"); Assert.notNull(sql, "SQL must not be null");
log.info("execute:{}", sql); log.info("execute:{}", sql);
try (Statement stmt = connection.createStatement();) { try (Statement stmt = connection.createStatement();) {
@ -147,8 +143,8 @@ public class SQLExecutor {
* @return * @return
* @throws SQLException * @throws SQLException
*/ */
public ExecuteResult execute(final String sql, Connection connection,ValueHandler valueHandler) throws SQLException { public ExecuteResult execute(final String sql, Connection connection, ValueHandler valueHandler) throws SQLException {
return execute(sql, connection, true, null, null,valueHandler); return execute(sql, connection, true, null, null, valueHandler);
} }
public ExecuteResult executeUpdate(final String sql, Connection connection, int n) public ExecuteResult executeUpdate(final String sql, Connection connection, int n)
@ -162,7 +158,7 @@ public class SQLExecutor {
if (affectedRows != n) { if (affectedRows != n) {
executeResult.setSuccess(false); executeResult.setSuccess(false);
executeResult.setMessage("Update error " + sql + " update affectedRows = " + affectedRows + ", Each SQL statement should update no more than one record. Please use a unique key for updates."); executeResult.setMessage("Update error " + sql + " update affectedRows = " + affectedRows + ", Each SQL statement should update no more than one record. Please use a unique key for updates.");
// connection.rollback(); // connection.rollback();
} }
} }
return executeResult; return executeResult;
@ -270,12 +266,12 @@ public class SQLExecutor {
* @return * @return
* @throws SQLException * @throws SQLException
*/ */
public ExecuteResult execute(Connection connection, String sql,ValueHandler valueHandler) throws SQLException { public ExecuteResult execute(Connection connection, String sql, ValueHandler valueHandler) throws SQLException {
return execute(sql, connection, true, null, null,valueHandler); return execute(sql, connection, true, null, null, valueHandler);
} }
public ExecuteResult execute(Connection connection, String sql) throws SQLException { public ExecuteResult execute(Connection connection, String sql) throws SQLException {
return execute(sql, connection, true, null, null,new DefaultValueHandler()); return execute(sql, connection, true, null, null, new DefaultValueHandler());
} }
/** /**
@ -342,8 +338,33 @@ public class SQLExecutor {
*/ */
public List<Table> tables(Connection connection, String databaseName, String schemaName, String tableName, public List<Table> tables(Connection connection, String databaseName, String schemaName, String tableName,
String types[]) { String types[]) {
try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName,
types)) { try {
DatabaseMetaData metadata = connection.getMetaData();
ResultSet resultSet = metadata.getTables(databaseName, schemaName, tableName,
types);
// 如果connection为mysql
if ("MySQL".equalsIgnoreCase(metadata.getDatabaseProductName())) {
// 获取mysql表的comment
List<Table> tables = ResultSetUtils.toObjectList(resultSet, Table.class);
if (CollectionUtils.isNotEmpty(tables)) {
for (Table table : tables) {
String sql = "show table status where name = '" + table.getName() + "'";
try (Statement stmt = connection.createStatement()) {
boolean query = stmt.execute(sql);
if (query) {
try (ResultSet rs = stmt.getResultSet();) {
while (rs.next()) {
table.setComment(rs.getString("Comment"));
}
}
}
}
}
return tables;
}
}
return ResultSetUtils.toObjectList(resultSet, Table.class); return ResultSetUtils.toObjectList(resultSet, Table.class);
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);