diff --git a/README.md b/README.md index 15e7dd313..a5c18b8c1 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开 - [x] 支持 url 读取、 CSV 批量导入 - [x] 支持知识库单独设置向量模型 - [x] 源文件存储 - - [x] 文件学习 Agent + - [ ] 文件学习 Agent 3. 多种效果测试渠道 - [x] 知识库单点搜索测试 - [x] 对话时反馈引用并可修改与删除 diff --git a/client/data/config.json b/client/data/config.json index 5bfd65fc1..8ebc01f4e 100644 --- a/client/data/config.json +++ b/client/data/config.json @@ -9,7 +9,7 @@ "show_doc": true, "systemTitle": "FastGPT", "authorText": "Made by FastGPT Team.", - "gitLoginKey": "", + "exportLimitMinutes": 0, "scripts": [] }, "SystemParams": { @@ -61,4 +61,4 @@ "maxToken": 16000, "price": 0 } -} \ No newline at end of file +} diff --git a/client/public/docs/versionIntro.md b/client/public/docs/versionIntro.md index a2e582faf..72e65ccb1 100644 --- a/client/public/docs/versionIntro.md +++ b/client/public/docs/versionIntro.md @@ -1,7 +1,6 @@ -### Fast GPT V4.3 +### Fast GPT V4.4 -1. 新增 - 知识库源文件存储,可以从引用窗口点击文件名,查看源文件。 -2. 新增 - 用户反馈和管理员标注预期答案,以不断提高模型回复准确率。 该功能为测试版,未来交互可能会有变化,欢迎大家提出宝贵意见。 -3. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/) -4. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow) -5. [点击查看商业版](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf) +1. 新增 - 知识库目录结构 +2. 优化 - [使用文档](https://doc.fastgpt.run/docs/intro/) +3. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow) +4. [点击查看商业版](https://fael3z0zfze.feishu.cn/docx/F155dbirfo8vDDx2WgWc6extnwf) diff --git a/client/public/imgs/files/file.svg b/client/public/imgs/files/file.svg new file mode 100644 index 000000000..1f106af9e --- /dev/null +++ b/client/public/imgs/files/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/imgs/files/folder.svg b/client/public/imgs/files/folder.svg new file mode 100644 index 000000000..602393396 --- /dev/null +++ b/client/public/imgs/files/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index 09f9a5ff8..e32eb5db1 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -2,6 +2,10 @@ "App": "App", "Cancel": "No", "Confirm": "Yes", + "Create New": "Create", + "Dataset": "Dataset", + "Folder": "Folder", + "Name": "Name", "Running": "Running", "Select value is empty": "Select value is empty", "UnKnow": "UnKnow", @@ -63,10 +67,8 @@ "online": "Online Chat", "share": "Share", "test": "Test Chat " - } - }, - "commom": { - "Password inconsistency": "Password inconsistency" + }, + "retry": "Retry" }, "common": { "Add": "Add", @@ -76,14 +78,22 @@ "Copy Successful": "Copy Successful", "Course": "", "Delete": "Delete", + "Delete Failed": "Delete Failed", + "Delete Success": "Delete Successful", + "Delete Warning": "Warning", "Filed is repeat": "Filed is repeated", "Filed is repeated": "", "Input": "Input", "Output": "Output", + "Password inconsistency": "Password inconsistency", + "Rename": "Rename", + "Search": "Search", + "Status": "Status", "export": "" }, "dataset": { "Confirm to delete the data": "Confirm to delete the data?", + "Export": "Export", "Queue Desc": "This data refers to the current amount of training for the entire system. FastGPT uses queued training, and if you have too much data to train, you may need to wait for a while", "System Data Queue": "Data Queue" }, @@ -93,9 +103,11 @@ "Create File": "Create File", "Create file": "Create file", "Drag and drop": "Drag and drop files here", + "Embedding": "Embedding", "Fetch Url": "Fetch Url", "If the imported file is garbled, please convert CSV to UTF-8 encoding format": "If the imported file is garbled, please convert CSV to UTF-8 encoding format", "Parse": "{{name}} Parsing...", + "Ready": "Ready", "Release the mouse to upload the file": "Release the mouse to upload the file", "Select a maximum of 10 files": "Select a maximum of 10 files", "Uploading": "Uploading: {{name}}, Progress: {{percent}}%", @@ -149,6 +161,24 @@ "desc": "AI knowledge base question and answer platform based on LLM large model", "slogan": "Let the AI know more about you" }, + "kb": { + "Chunk Length": "Chunk Length", + "Confirm to delete the file": "Are you sure to delete the file and all its data?", + "Create Folder": "Create Folder", + "Delete Dataset Error": "Delete dataset failed", + "Edit Folder": "Edit Folder", + "File Size": "File Size", + "Filename": "Filename", + "Files": "{{total}} Files", + "Folder Name": "Input folder name", + "My Dataset": "My Dataset", + "Other Data": "Other Data", + "Select Dataset": "Select Dataset", + "Select Folder": "Enter folder", + "Upload Time": "Upload Time", + "deleteDatasetTips": "Are you sure to delete the knowledge base? Data cannot be recovered after deletion, please confirm!", + "deleteFolderTips": "Are you sure to delete this folder and all the knowledge bases it contains? Data cannot be recovered after deletion, please confirm!" + }, "navbar": { "Account": "Account", "Apps": "Apps", diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json index 54c848082..5bde238de 100644 --- a/client/public/locales/zh/common.json +++ b/client/public/locales/zh/common.json @@ -2,6 +2,10 @@ "App": "应用", "Cancel": "取消", "Confirm": "确认", + "Create New": "新建", + "Dataset": "知识库", + "Folder": "文件夹", + "Name": "名称", "Running": "运行中", "Select value is empty": "选择的内容为空", "UnKnow": "未知", @@ -63,10 +67,8 @@ "online": "在线使用", "share": "外部链接调用", "test": "测试" - } - }, - "commom": { - "Password inconsistency": "两次密码不一致" + }, + "retry": "重新生成" }, "common": { "Add": "添加", @@ -76,14 +78,22 @@ "Copy Successful": "复制成功", "Course": "", "Delete": "删除", + "Delete Failed": "删除失败", + "Delete Success": "删除成功", + "Delete Warning": "删除警告", "Filed is repeat": "", "Filed is repeated": "字段重复了", "Input": "输入", "Output": "输出", + "Password inconsistency": "两次密码不一致", + "Rename": "重命名", + "Search": "搜索", + "Status": "状态", "export": "" }, "dataset": { "Confirm to delete the data": "确认删除该数据?", + "Export": "导出", "Queue Desc": "该数据是指整个系统当前待训练的数量。{{title}} 采用排队训练的方式,如果待训练的数据过多,可能需要等待一段时间", "System Data Queue": "排队长度" }, @@ -93,9 +103,11 @@ "Create File": "创建新文件", "Create file": "创建文件", "Drag and drop": "拖拽文件至此", + "Embedding": "索引中", "Fetch Url": "链接读取", "If the imported file is garbled, please convert CSV to UTF-8 encoding format": "如果导入文件乱码,请将 CSV 转成 UTF-8 编码格式", "Parse": "{{name}} 解析中...", + "Ready": "可用", "Release the mouse to upload the file": "松开鼠标上传文件", "Select a maximum of 10 files": "最多选择10个文件", "Uploading": "正在上传 {{name}},进度: {{percent}}%", @@ -149,6 +161,24 @@ "desc": "基于 LLM 大模型的 AI 知识库问答平台", "slogan": "让 AI 更懂你的知识" }, + "kb": { + "Chunk Length": "数据总量", + "Confirm to delete the file": "确认删除该文件及其所有数据?", + "Create Folder": "创建文件夹", + "Delete Dataset Error": "删除知识库异常", + "Edit Folder": "编辑文件夹", + "File Size": "文件大小", + "Filename": "文件名", + "Files": "文件: {{total}}个", + "Folder Name": "输入文件夹名称", + "My Dataset": "我的知识库", + "Other Data": "其他数据", + "Select Dataset": "选择该知识库", + "Select Folder": "进入文件夹", + "Upload Time": "上传时间", + "deleteDatasetTips": "确认删除该知识库?删除后数据无法恢复,请确认!", + "deleteFolderTips": "确认删除该文件夹及其包含的所有知识库?删除后数据无法恢复,请确认!" + }, "navbar": { "Account": "账号", "Apps": "应用", diff --git a/client/src/api/plugins/kb.ts b/client/src/api/plugins/kb.ts index ac24a5ef7..7e93698ce 100644 --- a/client/src/api/plugins/kb.ts +++ b/client/src/api/plugins/kb.ts @@ -1,6 +1,12 @@ import { GET, POST, PUT, DELETE } from '../request'; -import type { DatasetItemType, KbItemType, KbListItemType } from '@/types/plugin'; -import { RequestPaging } from '@/types/index'; +import type { + DatasetItemType, + FileInfo, + KbFileItemType, + KbItemType, + KbListItemType, + KbPathItemType +} from '@/types/plugin'; import { TrainingModeEnum } from '@/constants/plugin'; import { Props as PushDataProps, @@ -10,13 +16,17 @@ import { Props as SearchTestProps, Response as SearchTestResponse } from '@/pages/api/openapi/kb/searchTest'; -import { Response as KbDataItemType } from '@/pages/api/plugins/kb/data/getDataById'; import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData'; -import type { KbUpdateParams, CreateKbParams } from '../request/kb'; +import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb'; import { QuoteItemType } from '@/types/chat'; /* knowledge base */ -export const getKbList = () => GET(`/plugins/kb/list`); +export const getKbList = (parentId?: string) => + GET(`/plugins/kb/list`, { parentId }); +export const getAllDataset = () => GET(`/plugins/kb/allDataset`); + +export const getKbPaths = (parentId?: string) => + GET('/plugins/kb/paths', { parentId }); export const getKbById = (id: string) => GET(`/plugins/kb/detail?id=${id}`); @@ -26,25 +36,27 @@ export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, dat export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`); +/* kb file */ +export const getKbFiles = (data: { kbId: string; searchText: string }) => + GET(`/plugins/kb/file/list`, data); +export const deleteKbFileById = (params: { fileId: string; kbId: string }) => + DELETE(`/plugins/kb/file/delFileByFileId`, params); +export const getFileInfoById = (fileId: string) => + GET(`/plugins/kb/file/getFileInfo`, { fileId }); +export const delEmptyFiles = (kbId: string) => + DELETE(`/plugins/kb/file/deleteEmptyFiles`, { kbId }); + /* kb data */ -type GetKbDataListProps = RequestPaging & { - kbId: string; - searchText: string; -}; export const getKbDataList = (data: GetKbDataListProps) => POST(`/plugins/kb/data/getDataList`, data); /** * 获取导出数据(不分页) */ -export const getExportDataList = (kbId: string) => - GET<[string, string, string][]>( - `/plugins/kb/data/exportModelData`, - { kbId }, - { - timeout: 600000 - } - ); +export const getExportDataList = (data: { kbId: string; fileId: string }) => + GET<[string, string, string][]>(`/plugins/kb/data/exportModelData`, data, { + timeout: 600000 + }); /** * 获取模型正在拆分数据的数量 diff --git a/client/src/api/request/kb.d.ts b/client/src/api/request/kb.d.ts index 1ea227e7c..a9d698394 100644 --- a/client/src/api/request/kb.d.ts +++ b/client/src/api/request/kb.d.ts @@ -1,12 +1,23 @@ +import { KbTypeEnum } from '@/constants/kb'; +import type { RequestPaging } from '@/types'; + export type KbUpdateParams = { id: string; - name: string; - tags: string; - avatar: string; + tags?: string; + name?: string; + avatar?: string; }; export type CreateKbParams = { + parentId?: string; name: string; tags: string[]; avatar: string; - vectorModel: string; + vectorModel?: string; + type: `${KbTypeEnum}`; +}; + +export type GetKbDataListProps = RequestPaging & { + kbId: string; + searchText: string; + fileId: string; }; diff --git a/client/src/components/ChatBox/QuoteModal.tsx b/client/src/components/ChatBox/QuoteModal.tsx index 2096b4a9f..4e75016c6 100644 --- a/client/src/components/ChatBox/QuoteModal.tsx +++ b/client/src/components/ChatBox/QuoteModal.tsx @@ -19,7 +19,7 @@ const QuoteModal = ({ rawSearch = [], onClose }: { - onUpdateQuote: (quoteId: string, sourceText: string) => Promise; + onUpdateQuote: (quoteId: string, sourceText?: string) => Promise; rawSearch: SearchType[]; onClose: () => void; }) => { @@ -129,7 +129,7 @@ const QuoteModal = ({ {editDataItem && ( setEditDataItem(undefined)} - onSuccess={() => onUpdateQuote(editDataItem.id, '手动修改')} + onSuccess={() => onUpdateQuote(editDataItem.id)} onDelete={() => onUpdateQuote(editDataItem.id, '已删除')} kbId={editDataItem.kb_id} defaultValues={{ diff --git a/client/src/components/ChatBox/ResponseTags.tsx b/client/src/components/ChatBox/ResponseTags.tsx index 722f4d217..0f68360eb 100644 --- a/client/src/components/ChatBox/ResponseTags.tsx +++ b/client/src/components/ChatBox/ResponseTags.tsx @@ -44,7 +44,7 @@ const ResponseTags = ({ }; }, [responseData]); - const updateQuote = useCallback(async (quoteId: string, sourceText: string) => {}, []); + const updateQuote = useCallback(async (quoteId: string, sourceText?: string) => {}, []); const TagStyles: BoxProps = { mr: 2, diff --git a/client/src/components/ChatBox/SelectDataset.tsx b/client/src/components/ChatBox/SelectDataset.tsx index 331d4e8a4..8d2f59727 100644 --- a/client/src/components/ChatBox/SelectDataset.tsx +++ b/client/src/components/ChatBox/SelectDataset.tsx @@ -12,7 +12,7 @@ import { import MyModal from '../MyModal'; import { useTranslation } from 'next-i18next'; import { useQuery } from '@tanstack/react-query'; -import { useUserStore } from '@/store/user'; +import { useDatasetStore } from '@/store/dataset'; import { useToast } from '@/hooks/useToast'; import Avatar from '../Avatar'; import MyIcon from '@/components/Icon'; @@ -29,10 +29,10 @@ const SelectDataset = ({ const theme = useTheme(); const { isPc } = useGlobalStore(); const { toast } = useToast(); - const { myKbList, loadKbList } = useUserStore(); + const { myKbList, loadKbList } = useDatasetStore(); const [selectedId, setSelectedId] = useState(); - useQuery(['loadKbList'], loadKbList); + useQuery(['loadKbList'], () => loadKbList()); return ( diff --git a/client/src/components/ChatBox/WholeResponseModal.tsx b/client/src/components/ChatBox/WholeResponseModal.tsx index 7c5ee447f..e16e0aadd 100644 --- a/client/src/components/ChatBox/WholeResponseModal.tsx +++ b/client/src/components/ChatBox/WholeResponseModal.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from 'react'; -import { Box, ModalBody, useTheme, ModalHeader, Flex } from '@chakra-ui/react'; +import { Box, ModalBody, useTheme, Flex } from '@chakra-ui/react'; import type { ChatHistoryItemResType } from '@/types/chat'; import { useTranslation } from 'react-i18next'; diff --git a/client/src/components/ChatBox/index.tsx b/client/src/components/ChatBox/index.tsx index d7ecff366..2bbbcfaba 100644 --- a/client/src/components/ChatBox/index.tsx +++ b/client/src/components/ChatBox/index.tsx @@ -55,6 +55,7 @@ const SelectDataset = dynamic(() => import('./SelectDataset')); const InputDataModal = dynamic(() => import('@/pages/kb/detail/components/InputDataModal')); import styles from './index.module.scss'; +import Script from 'next/script'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24); @@ -293,7 +294,7 @@ const ChatBox = ( * user confirm send prompt */ const sendPrompt = useCallback( - async (variables: Record = {}, inputVal = '') => { + async (variables: Record = {}, inputVal = '', history = chatHistory) => { if (!onStartChat) return; if (isChatting) { toast({ @@ -314,7 +315,7 @@ const ChatBox = ( } const newChatList: ChatSiteItemType[] = [ - ...chatHistory, + ...history, { dataId: nanoid(), obj: 'Human', @@ -407,6 +408,22 @@ const ChatBox = ( ] ); + // retry input + const retryInput = useCallback( + async (index: number) => { + if (!onDelMessage) return; + const delHistory = chatHistory.slice(index); + setChatHistory((state) => (index === 0 ? [] : state.slice(0, index))); + + await Promise.all( + delHistory.map((item, i) => onDelMessage({ contentId: item.dataId, index: index + i })) + ); + + sendPrompt(variables, delHistory[0].value, chatHistory.slice(0, index)); + }, + [chatHistory, onDelMessage, sendPrompt, variables] + ); + // output data useImperativeHandle(ref, () => ({ getChatHistory: () => chatHistory, @@ -470,7 +487,7 @@ const ChatBox = ( ); const statusBoxData = useMemo(() => { const colorMap = { - loading: '#67c13b', + loading: 'myGray.700', running: '#67c13b', finish: 'myBlue.600' }; @@ -484,6 +501,7 @@ const ChatBox = ( }; }, [chatHistory, isChatting, t]); + // page change and abort request useEffect(() => { return () => { controller.current?.abort('leave'); @@ -492,16 +510,7 @@ const ChatBox = ( }; }, [router.query]); - useEffect(() => { - event.on('guideClick', ({ text }: { text: string }) => { - if (!text) return; - handleSubmit((data) => sendPrompt(data, text))(); - }); - - return () => { - event.off('guideClick'); - }; - }, [handleSubmit, sendPrompt]); + // page destroy and abort request useEffect(() => { const listen = () => { cancelBroadcast(); @@ -513,8 +522,22 @@ const ChatBox = ( }; }, []); + // add guide text listener + useEffect(() => { + event.on('guideClick', ({ text }: { text: string }) => { + if (!text) return; + handleSubmit((data) => sendPrompt(data, text))(); + }); + + return () => { + event.off('guideClick'); + }; + }, [handleSubmit, sendPrompt]); + return ( + + {showEmpty && } @@ -616,7 +639,7 @@ const ChatBox = ( justifyContent={'flex-end'} mr={3} > - + onclickCopy(item.value)} /> + {!!onDelMessage && ( + + retryInput(index)} + /> + + )} {onDelMessage && ( - + + + + \ No newline at end of file diff --git a/client/src/components/Icon/icons/light/retry.svg b/client/src/components/Icon/icons/light/retry.svg new file mode 100644 index 000000000..97b185b40 --- /dev/null +++ b/client/src/components/Icon/icons/light/retry.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/client/src/components/Icon/icons/light/rightArrow.svg b/client/src/components/Icon/icons/light/rightArrow.svg new file mode 100644 index 000000000..3dc0a8598 --- /dev/null +++ b/client/src/components/Icon/icons/light/rightArrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/client/src/components/Icon/icons/light/search.svg b/client/src/components/Icon/icons/light/search.svg new file mode 100644 index 000000000..493230bee --- /dev/null +++ b/client/src/components/Icon/icons/light/search.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx index 99bd99751..cd1167aba 100644 --- a/client/src/components/Icon/index.tsx +++ b/client/src/components/Icon/index.tsx @@ -79,7 +79,11 @@ const map = { promotionLight: require('./icons/light/promotion.svg').default, logsLight: require('./icons/light/logs.svg').default, badLight: require('./icons/light/bad.svg').default, - markLight: require('./icons/light/mark.svg').default + markLight: require('./icons/light/mark.svg').default, + retryLight: require('./icons/light/retry.svg').default, + rightArrowLight: require('./icons/light/rightArrow.svg').default, + searchLight: require('./icons/light/search.svg').default, + plusFill: require('./icons/fill/plus.svg').default }; export type IconName = keyof typeof map; diff --git a/client/src/components/MyInput/index.tsx b/client/src/components/MyInput/index.tsx new file mode 100644 index 000000000..30e9d6b52 --- /dev/null +++ b/client/src/components/MyInput/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Flex, Input, InputProps } from '@chakra-ui/react'; + +interface Props extends InputProps { + leftIcon?: React.ReactNode; +} + +const MyInput = ({ leftIcon, ...props }: Props) => { + return ( + + + {leftIcon && ( + + {leftIcon} + + )} + + ); +}; + +export default MyInput; diff --git a/client/src/components/MyMenu/index.tsx b/client/src/components/MyMenu/index.tsx new file mode 100644 index 000000000..76d35ff65 --- /dev/null +++ b/client/src/components/MyMenu/index.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Menu, MenuList, MenuItem } from '@chakra-ui/react'; + +interface Props { + width: number; + offset?: [number, number]; + Button: React.ReactNode; + menuList: { + isActive?: boolean; + child: React.ReactNode; + onClick: () => void; + }[]; +} + +const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => { + const menuItemStyles = { + borderRadius: 'sm', + py: 3, + display: 'flex', + alignItems: 'center', + _hover: { + backgroundColor: 'myWhite.600', + color: 'hover.blue' + } + }; + + return ( + + {Button} + + {menuList.map((item, i) => ( + + {item.child} + + ))} + + + ); +}; + +export default MyMenu; diff --git a/client/src/constants/common.ts b/client/src/constants/common.ts index 0604a33f1..103fa9748 100644 --- a/client/src/constants/common.ts +++ b/client/src/constants/common.ts @@ -10,7 +10,8 @@ export const fileImgs = [ { suffix: 'csv', src: '/imgs/files/csv.svg' }, { suffix: '(doc|docs)', src: '/imgs/files/doc.svg' }, { suffix: 'txt', src: '/imgs/files/txt.svg' }, - { suffix: 'md', src: '/imgs/files/markdown.svg' } + { suffix: 'md', src: '/imgs/files/markdown.svg' }, + { suffix: '.', src: '/imgs/files/file.svg' } ]; export enum TrackEventName { diff --git a/client/src/constants/kb.ts b/client/src/constants/kb.ts index 6d299566a..104cb589a 100644 --- a/client/src/constants/kb.ts +++ b/client/src/constants/kb.ts @@ -14,3 +14,24 @@ export const defaultKbDetail: KbItemType = { maxToken: 3000 } }; + +export enum KbTypeEnum { + folder = 'folder', + dataset = 'dataset' +} +export enum FileStatusEnum { + embedding = 'embedding', + ready = 'ready' +} + +export const KbTypeMap = { + [KbTypeEnum.folder]: { + name: 'folder' + }, + [KbTypeEnum.dataset]: { + name: 'dataset' + } +}; + +export const FolderAvatarSrc = '/imgs/files/folder.svg'; +export const OtherFileId = 'other'; diff --git a/client/src/hooks/useConfirm.tsx b/client/src/hooks/useConfirm.tsx index ed08b7014..c8258b051 100644 --- a/client/src/hooks/useConfirm.tsx +++ b/client/src/hooks/useConfirm.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { AlertDialog, AlertDialogBody, @@ -11,21 +11,25 @@ import { } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; -export const useConfirm = (props: { title?: string; content: string }) => { +export const useConfirm = (props: { title?: string | null; content?: string | null }) => { const { t } = useTranslation(); const { title = t('Warning'), content } = props; + const [customContent, setCustomContent] = useState(content); const { isOpen, onOpen, onClose } = useDisclosure(); + const cancelRef = useRef(null); const confirmCb = useRef(); const cancelCb = useRef(); return { openConfirm: useCallback( - (confirm?: any, cancel?: any) => { + (confirm?: any, cancel?: any, customContent?: string) => { confirmCb.current = confirm; cancelCb.current = cancel; + customContent && setCustomContent(customContent); + return onOpen; }, [onOpen] @@ -44,7 +48,7 @@ export const useConfirm = (props: { title?: string; content: string }) => { {title} - {content} + {customContent} + {/* welcome */} + + + + 对话开场白 + + + + +