diff --git a/CHANGELOG.md b/CHANGELOG.md index 01868895..0326f5ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.0.7 +## ⭐ New Features + +- Export query result as file is supported + +## 🐞 Bug Fixes + +- Fixed ai config issues [Issue #346](https://github.com/chat2db/Chat2DB/issues/346) + + # 2.0.6 ## 🐞 Bug Fixes diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 7512115d..56c27a4c 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -195,15 +195,12 @@ export default function SettingAI(props: IProps) { )}
- {/* */}
- {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && } + {/* {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && } */} ); } diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index df860043..a2471bbd 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -9,12 +9,11 @@ import ProxySetting from './ProxySetting'; import About from './About'; import { connect } from 'umi'; import { IAIState } from '@/models/ai'; -import styles from './index.less'; -import configService from '@/service/config'; -import { AiSqlSourceType } from '@/typings/ai'; import TestVersion from '@/components/TestVersion'; import { IAiConfig } from '@/typings'; +import styles from './index.less'; + interface IProps { aiConfig: IAiConfig; className?: string; @@ -28,6 +27,12 @@ function Setting(props: IProps) { const [currentMenu, setCurrentMenu] = useState(0); + useEffect(() => { + if (isModalVisible) { + getAiSystemConfig(); + } + }, [isModalVisible]); + useEffect(() => { getAiSystemConfig(); }, []); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index fda0d7bf..b26a3cd5 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -111,6 +111,7 @@ // top: 0; // right: 0; // bottom: 0; + cursor: pointer; i { font-size: 15px; @@ -139,3 +140,7 @@ } } } + +.monacoEditor { + height: 60vh; +} diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index b40a3c33..4ed7c415 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -24,7 +24,7 @@ interface ITableProps { config: IResultConfig; onConfigChange: (config: IResultConfig) => void; onSearchTotal: () => Promise; - onExport: (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void; + onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void; } interface IViewTableCellData { @@ -54,42 +54,46 @@ export default function TableBox(props: ITableProps) { const [appTheme] = useTheme(); const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); + const handleExport = (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + props.onExport && props.onExport(data.sql, data.originalSql, exportType, exportSize); + }; + const items: MenuProps['items'] = useMemo( () => [ { label: '导出全部数据为csv', key: '1', - icon: , + // icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); + handleExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); }, }, { label: '导出全部数据为插入语句', key: '2', - icon: , + // icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); + handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); }, }, { label: '导出当前页数据为csv', key: '3', - icon: , + // icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); + handleExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); }, }, { label: '导出当前页数据为插入语句', key: '4', - icon: , + // icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); + handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); }, }, ], - [], + [data], ); const defaultSorts: SortItem[] = useMemo( @@ -260,6 +264,7 @@ export default function TableBox(props: ITableProps) { open={!!viewTableCellData?.name} onCancel={handleCancel} width="60vw" + height="70vh" maskClosable={false} footer={ <> diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 99e205e7..07bf9ebc 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useState, useMemo, Fragment } from 'react'; +import React, { memo, useEffect, useState, useMemo, Fragment, useCallback } from 'react'; import classnames from 'classnames'; import Tabs, { IOption } from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; @@ -16,7 +16,7 @@ interface IProps { manageResultDataList?: IManageResultData[]; resultConfig: IResultConfig[]; onExecute: (sql: string, config: IResultConfig, index: number) => void; - onExport: (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise; + onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise; onTabEdit: (type: 'add' | 'remove', value?: number | string) => void; onSearchTotal: (index: number) => Promise; isLoading?: boolean; @@ -45,7 +45,7 @@ const handleTabs = (result: IManageResultData[]) => { }; export default memo(function SearchResult(props) { - const { className, manageResultDataList = [], isLoading, onExecute, onSearchTotal } = props; + const { className, manageResultDataList = [], isLoading, onExecute, onExport } = props; const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState([]); const [resultConfig, setResultConfig] = useState([]); @@ -64,7 +64,7 @@ export default memo(function SearchResult(props) { setCurrentTab(manageResultDataList[0].uuid); } - setResultDataList(manageResultDataList); + setResultDataList([...manageResultDataList]); setTabs(handleTabs(manageResultDataList)); }, [manageResultDataList]); @@ -85,48 +85,50 @@ export default memo(function SearchResult(props) { ); }; - const renderTable = useMemo(() => { + const renderTable = () => { if (!tabs || !tabs.length) { return renderEmpty(); } if (!resultDataList || !resultDataList.length) { return renderEmpty(); } - return (resultDataList || []).map((item, index: number) => { - if (item.success) { - return ( - - { - if (props.onSearchTotal) { - return await props.onSearchTotal(index); - } - }} - onExport={(exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { - props.onExport && props.onExport(item.originalSql, exportType, exportSize); - }} - /> - - ); - } else { - return ( - - - - ); - } - }); - }, [currentTab, resultDataList, resultConfig]); + + return renderTableContent; + }; + + const renderTableContent = (resultDataList || []).map((item, index: number) => { + console.log(item.sql, item.originalSql); + if (item.success) { + return ( + { + onExecute && onExecute(item.originalSql, config, index); + }} + onSearchTotal={async () => { + if (props.onSearchTotal) { + return await props.onSearchTotal(index); + } + }} + onExport={(sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + onExport && onExport(sql, originalSql, exportType, exportSize); + }} + /> + ); + } else { + return ( + + ); + } + }); return (
@@ -144,7 +146,7 @@ export default memo(function SearchResult(props) {
) : null} - {renderTable} + {renderTable()} ); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 2b346282..0b91cd55 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -15,6 +15,7 @@ import { v4 as uuidV4 } from 'uuid'; import sql from '@/service/sql'; import { isNumber } from 'lodash'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; +import { downloadFile } from '@/utils/common'; interface IProps { className?: string; isActive: boolean; @@ -139,10 +140,14 @@ const WorkspaceRightItem = memo(function (props) { return total; }; - const handleExportSQLResult = async (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { - const params: IExportParams = { ...data, originalSql, exportType, exportSize }; - - await sqlServer.exportResultTable(params); + const handleExportSQLResult = async ( + sql: string, + originalSql: string, + exportType: ExportTypeEnum, + exportSize: ExportSizeEnum, + ) => { + const params: IExportParams = { ...data, sql, originalSql, exportType, exportSize }; + downloadFile(window._BaseURL + '/api/rdb/dml/export', params); }; const handleResultTabEdit = (type: 'add' | 'remove', uuid?: string | number) => { diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 91d23eec..89144cba 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -123,7 +123,7 @@ export interface IExportParams extends IExecuteSqlParams { /** * 导出-表格 */ -const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); +// const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); export default { getList, @@ -142,5 +142,5 @@ export default { addTablePin, deleteTablePin, getDMLCount, - exportResultTable + // exportResultTable }; diff --git a/chat2db-client/src/utils/common.ts b/chat2db-client/src/utils/common.ts index 007da3d1..ee293312 100644 --- a/chat2db-client/src/utils/common.ts +++ b/chat2db-client/src/utils/common.ts @@ -14,3 +14,45 @@ export function formatParams(obj: { [key: string]: any }) { }); return params.toString(); } + +export function downloadFile(url: string, params: any) { + // 创建POST请求 + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', // 或者根据服务端的要求设置其他的内容类型 + }, + body: JSON.stringify(params), // 将参数转换为JSON字符串 + }) + .then((response) => { + // 从content-disposition头中获取文件名 + const contentDisposition = response.headers.get('content-disposition'); + const filename = contentDisposition ? decodeURIComponent(contentDisposition.split("''")[1]) : 'file.txt'; + + // 获取返回的Blob数据 + return response.blob().then((blob) => ({ blob, filename })); + }) + .then(({ blob, filename }) => { + // 创建一个代表Blob对象的URL + const blobUrl = URL.createObjectURL(blob); + + // 创建一个隐藏的 标签,并设置其 href 属性 + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = blobUrl; + + // 使用从响应头解析的文件名 + a.download = filename; + + // 将 标签附加到 DOM,并触发点击事件 + document.body.appendChild(a); + a.click(); + + // 清理:从 DOM 中移除 标签,并释放Blob URL + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + }) + .catch((error) => { + console.error('下载文件失败:', error); + }); +} diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 25567ad0..2203cefc 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -4,7 +4,6 @@ import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; import com.dtflys.forest.springboot.annotation.ForestScan; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; @@ -37,7 +36,7 @@ public class Application { // Represents that the current version has been successfully launched if (StringUtils.isNotBlank(currentVersion) && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { // Flyway doesn't need to start every time to increase startup speed - args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); + //args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); log.info("The current version {} has been successfully launched once and will no longer load Flyway.", currentVersion); } diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index 7ddebb44..7ec7b4ee 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -37,13 +37,13 @@ public class MybatisGeneratorTest extends BaseTest { private void doGenerator(List tableList) { - // 当前项目地址 拿到的是ali-dbhub-server-start地址 + // 当前项目地址 拿到的是chat2db-server-start地址 String outputDir = System.getProperty("user.dir") - + "/../ali-dbhub-server-domain/ali-dbhub-server-domain-repository/src/main" + + "/../chat2db-server-domain/chat2db-server-domain-repository/src/main" + "/java"; String xmlDir = System.getProperty("user.dir") - + "/../ali-dbhub-server-domain/ali-dbhub-server-domain-repository/src/main" - + "/resources/com/alibaba/dbhub/server/domain/repository"; + + "/../chat2db-server-domain/chat2db-server-domain-repository/src/main" + + "/resources/ai/chat2db/server/domain/repository"; // 不要生成service controller Map pathInfo = new HashMap<>(); @@ -58,7 +58,7 @@ public class MybatisGeneratorTest extends BaseTest { //全局配置 .globalConfig(builder -> { // 设置作者 - builder.author("ali-dbhub") + builder.author("chat2db") //执行完毕不打开文件夹 .disableOpenDir() // 指定输出目录